Skip to content

Pad v0.15.0

Latest

Choose a tag to compare

@github-actions github-actions released this 31 Aug 12:22
· 1 commit to main since this release

Highlights

Behavior change: pushes into agent sessions are now opt-in per session (PLAN-2613). In 0.14, installing the Claude Code plugin made every session reachable by pad push — a monitor started in every terminal, unasked. A push is deliberate instruction injection into a running agent, so 0.15 gates it on consent, and there is no grandfathering: updating the plugin replaces the always-on monitor with the gated one for everyone. Until a session connects, nothing streams and nothing listens — watches and pushes alike. /pad:connect arms a session locally and starts the monitor, which announces the armed state when its stream connects; /pad:disconnect withdraws; /pad:status reports honestly (armed is consent, not a live connection). A repo can opt its sessions in at start with push.auto_arm = true in .pad.toml — an explicit file edit, never a machine-global default, vetoable per user in ~/.pad/config.toml. Server-side, delivery is filtered to armed sessions and pushes stay self-addressed only; a CLI broadcast reports delivered_sessions rather than refusing, a targeted push to a non-accepting session skips the publish, and the web composer and quick actions target armed sessions, show the real counts ("2 connected, 0 accepting pushes") with enable instructions, and withhold a send nobody would accept. The accepted caveat, on the record: an agent can run the arm command from inside its own session — visible in the transcript, within the operator's sight; the gate protects sessions from the outside, not the inside. pad session arm | disarm | status; PRs #1130, #1149, #1150, #1151.

Behavior change: a connection that declined "may create workspaces" is now refused, not quietly allowed (IDEA-2756, #1212). The consent checkbox used to gate only the post-creation auto-add, so an MCP connection whose user unticked it could still create workspaces — invisibly when the connection carried an allow-list, visibly under the all-workspaces wildcard. pad_workspace.create and POST /workspaces/import now answer 403 when the grant carries may_create_workspaces=false. Existing connections are affected: one that has been creating workspaces without the flag will start receiving 403s on update. There is deliberately no bypass parameter — the gate encodes the user's consent decision, so only the user lifts it: re-authorize, or enable the flag on the existing connection at /console/connected-apps. ToolSurfaceVersion 0.26.

Agents have names, and sessions carry them. Comments, the item timeline and the admin per-user activity views show the agent's stamped display name instead of a generic "agent" (TASK-2759/2760). A session with the plugin records itself in ~/.pad/sessions on start — harness pid, agent name, working directory — and pad session list answers "which of my sessions on this machine are running, and as which agent" with a liveness verdict per row, no server round-trip; pad session prune clears the dead (TASK-2767, #1200). The activity page gained a Live view that folds the feed into episodes (IDEA-2755).

Multi-instance delivery hardened, end to end (Pad Cloud / Redis). Watch notifications now cross instances on a Redis-backed bus (BUG-2651); every Redis keyspace is namespaced per installation so two Pads sharing one Redis no longer cross-feed (BUG-2724 — note the rolling-upgrade caveat in docs/deployment.md); the watch stream shares one connection budget with the activity stream (BUG-2726) and Redis has readiness and metrics (BUG-2727). A long family of silent-loss cases became honest sync_required answers or refusals: cold replay buffers (BUG-2731), pub/sub resubscriptions and undecodable messages (BUG-2739), gaps announced to clients holding a stream open (BUG-2730), publishes lost in the window before Redis registered a new subscription — subscribers now wait for the registration (BUG-2747) — and a SUBSCRIBE the client could not deliver — which used to be installed as a live-looking subscription forever — now refuses its callers with 503 subscription_failed + Retry-After (BUG-2764). Connect-path capacity is released when a client leaves mid-establishment (BUG-2749), the Redis dial no longer runs under the bus lock (BUG-2748), TLS dials honor the request context (BUG-2754), and session-targeted pushes resolve cross-instance and report a dropped publish honestly (BUG-2698, BUG-2699).

Two new phase-2 rollouts (three flags), each a two-phase roll — the order is not optional. All three flags ship OFF, and none may be flipped until every instance runs the 0.15 binary:

  • Event ID-space epoch (PAD_EVENTS_PUBLISH_EPOCH, BUG-2736): a single-process restart used to reuse the ID space, so a replica could mix two spaces in one replay buffer and answer a resume wrongly. Phase-2 instances stamp every message with an epoch; replicas that see a new one drop their buffers and answer sync_required.

    Phase What you do What instances publish What they accept
    1 Roll the new binary everywhere. Leave PAD_EVENTS_PUBLISH_EPOCH unset. The historical bare JSON Both forms
    2 Set PAD_EVENTS_PUBLISH_EPOCH=true and roll again. <epoch>|<id>|<json> Both forms

    Flipping before every instance is upgraded loses events on the ones that are not — not a resync, a silent loss. Rolling back to phase 1 is safe.

  • Half-open connection detection (PAD_EVENTS_HEARTBEAT, PAD_WATCH_HEARTBEAT, BUG-2738 / BUG-2769): a route that stops working without closing left an instance reading forever from a dead socket while its buffer looked complete. Phase-2 instances publish a liveness frame every 30s on the channels they subscribe to and cycle a subscription that has received nothing for 90s, ending its coverage first so the next resume is honest. Detection is part of phase 2, not phase 1.

    Phase What you do What instances publish What they do with a frame
    1 Roll the new binary everywhere. Leave both flags unset. No heartbeats Recognise and ignore it. No idle detection.
    2 Set the flag (PAD_EVENTS_HEARTBEAT and/or PAD_WATCH_HEARTBEAT) and roll again. One frame per 30s — per subscribed workspace on the activity bus, once per instance on the watch bus Recognise and ignore it. Idle detection active.

    Out of order, a pre-0.15 instance treats the frame as a hole in coverage and tells every live subscriber to resync every 30s for as long as the fleet is mixed. Rolling back to phase 1 is safe and immediate; downgrading past it is a second step, in the reverse order. Full procedure and metrics: docs/deployment.md.

An event choke point with a transactional outbox (PLAN-2656 phase 0: TASK-2657, TASK-2658, TASK-2714). Every item mutation now emits through one path, into an outbox committed with the write; webhooks are delivered from the outbox drain instead of from handlers (TASK-2658, TASK-2714). Collection kernel traits replace the hardcoded conventions / playbooks slugs, so a workspace that renamed either collection works everywhere the literal used to be assumed (TASK-2657, BUG-2702; ToolSurfaceVersion 0.25).

MCP: writes that used to be silently dropped now land or fail loudly (ToolSurfaceVersion 0.20 → 0.26 across this release; the fields change is 0.24). The fields object that reads return is finally a valid write shape on pad_item create/update, and undeclared top-level keys are rejected with a structured error instead of being accepted and ignored (BUG-2497, PR #1159 for GitHub #1066). Item history is bounded on every surface (BUG-2608); three advertised actions that were unrouted over HTTP are routed, with a catalog↔route parity test (BUG-2304); a deterministic unreadable-stored-data refusal is no longer reported as a retryable server error (BUG-2675).

Data integrity under concurrency. A wiki-link cascade no longer overwrites a concurrent edit to a linked document (BUG-2785); backlink rewrites take their row locks in order, so concurrent renames cannot deadlock on Postgres (BUG-2778); the activity debounce no longer loses a change entry, merges across actors, or attributes a concurrent writer's change to the wrong agent (BUG-2770, BUG-2763, BUG-2776), and an activity merge that fails no longer hands back a stale id for the item-update handler to link a comment to (BUG-2779); timeline entry ids cannot collide and hide an entry (BUG-2783). Moving an item keeps its implementation notes, decision log and PR link (BUG-2674); double-encoded notes are no longer minted (BUG-2627); the attachment GC family (BUG-2406, BUG-2415, BUG-2388, BUG-2614, BUG-2615, BUG-2622, BUG-2629) is closed, and share pages render embedded images through a token-scoped, variants-only byte endpoint (TASK-2637).

CLI and operations. PAD_TOKEN overrides stored credentials for the current command (#879, #1160). Invalid-UTF-8 path segments answer 400 invalid_path on both engines (BUG-2782 — it was 500 on Postgres and 404 on SQLite); malformed timeline cursors answer 400 (BUG-2774). golangci-lint's config verify no longer fetches a remote schema in CI (BUG-2568).

Upgrading

  • Plugin users: after updating, sessions receive no pushes until they run /pad:connect (or the repo sets push.auto_arm = true). The web composer's counts show which sessions are accepting.
  • MCP connections that create workspaces: check the "may create workspaces" flag at /console/connected-apps before relying on pad_workspace.create.
  • Pad Cloud / Redis operators: roll the binary everywhere first; flip PAD_EVENTS_PUBLISH_EPOCH, PAD_EVENTS_HEARTBEAT, PAD_WATCH_HEARTBEAT only afterwards, one at a time, per the tables above.

Addenda — merged after the draft SHA (1933041), receipts on the closed items

  • Invalid UTF-8 or NUL bytes in query parameters now answer 400 instead of 500 on Postgres deployments (BUG-2784, #1217) — completes the invalid-bytes transport class alongside the path-segment fix above.
  • The document-rename cascade is bounded at the title and at the projected total (BUG-2798, #1218): titles capped at 255 runes at write time (existing over-limit titles stay readable and editable), and a rename whose rewrite projects past the cap is refused before any allocation — the measured 10 GB-from-500 KB OOM class is closed. Titles containing wiki-link syntax are refused at write time (BUG-2796, same PR): renaming a document to a bracket-bearing title used to emit links that pointed at nothing and report success.
  • A workspace import now counts against the plan's workspace limit (BUG-2793, #1219; Pad Cloud only) — an import is a new workspace and counts, with no exemption for re-importing something you previously owned.
  • A decoded NUL character in a JSON request body is refused with 400 at the transport (BUG-2803, #1220) — closing the body leg of the invalid-bytes class (path: BUG-2782, query: BUG-2784). The measured doors are closed, with filed residuals: two known map-model disagreement gaps carry pinned failing-on-fix tests and are the spec of the token-walk rewrite (BUG-2812); store-layer enforcement and at-rest repair are designed together as BUG-2810/2813/2814. The PR's release-note section carries the full 13-item behavior list, including the chunked-watch-body fix and dot-segment attachment-id refusal found by its closing review series.
  • The consent-gated push monitor is documented where agents and operators read (TASK-2620, #1216) — plugin skill, MCP instructions, README.

Install

brew install PerpetualSoftware/tap/pad

or nix run github:PerpetualSoftware/pad/release, or docker pull ghcr.io/perpetualsoftware/pad:0.15.0.

Verifying

All artifacts ship with SLSA provenance, SBOMs, and Sigstore signatures:

cosign verify-blob \
  --certificate-identity-regexp "^https://github.com/PerpetualSoftware/pad/.github/workflows/release.yml@.*" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  --bundle ./checksums.txt.sigstore.json ./checksums.txt

Changelog

Features

  • 5784d90: feat(cli): PAD_TOKEN environment override for stored credentials (#879) (#1160) (@b4rk13)
  • e40df6b: feat(cli): pad session arm/disarm/status + consent config resolution (PLAN-2613 S2, TASK-2617) (#1149) (@xarmian)
  • 4a6a748: feat(events): identify the shared Redis ID space, behind a two-phase flip (BUG-2736) (@xarmian)
  • 3ba5dcc: feat(events): report unservable resumes and coverage resets (BUG-2731) (@xarmian)
  • 052c971: feat(plugin): consent-gated push monitor + tri-state arm/disarm + envelope (PLAN-2613 S3, TASK-2618) (#1150) (@xarmian)
  • 0877b26: feat(redis): namespace every Redis keyspace from one shared config value (BUG-2724) (@xarmian)
  • d68474f: feat(server): armed-session declaration + push delivery filter (PLAN-2613 S1, TASK-2616) (#1130) (@xarmian)
  • 6a37512: feat(server): outbox drain — webhooks delivered from the choke point (TASK-2714) (#1173) (@xarmian)
  • 91d92f1: feat(server): refuse workspace creation without may_create_workspaces consent (IDEA-2756) (#1212) (@xarmian)
  • 99ffad1: feat(server): timeline comment rows carry the agent name (TASK-2760) (#1196) (@xarmian)
  • 720b792: feat(server,config): bound the watch-events stream, with one budget across both SSE endpoints (BUG-2726) (@xarmian)
  • e747a16: feat(session): registry keyed on the harness session, carrying the agent name; pad session list / prune (TASK-2767) (#1200) (@xarmian)
  • 2ed6e71: feat(store): transactional event outbox — the SPEC-3 choke point (TASK-2658) (#1172) (@xarmian)
  • 402f79e: feat(store,server,web): collection kernel traits — de-hardcode conventions/playbooks slugs (TASK-2657, BUG-2702) (#1171) (@xarmian)
  • 25c7cd2: feat(watchevents): Redis-backed bus so watch notifications cross instances (BUG-2651) (#1167) (@xarmian)
  • 8dea9ab: feat(watchevents,metrics): operational observability for the Redis notification bus (BUG-2727) (@xarmian)
  • d4e7be4: feat(web): Live view on the activity page — the feed folded into episodes (IDEA-2755) (@xarmian)
  • 482815a: feat(web): composer + quick actions target armed sessions with honest counts (PLAN-2613 S4, TASK-2619) (#1151) (@xarmian)
  • de3c9b8: feat(web): name the agent on the admin per-user activity views too (TASK-2759) (@xarmian)
  • 7e7d6e8: feat(web): render agents' stamped names wherever agent actors display (TASK-2759) (@xarmian)
  • b5f0cd3: feat(web,server): render embedded image attachments on share pages via a variants-only byte endpoint (BUG-2389 2b, TASK-2637) (#1153) (@xarmian)

Bug fixes

  • bc68b84: fix(cli,mcp): send raw collection slug so an alias can't shadow a real collection (BUG-2630) (#1162) (@xarmian)
  • bc87aee: fix(docs,metrics): repair the metrics table I split, and bound the fan-out claim (BUG-2739, codex round 19) (@xarmian)
  • 4275407: fix(documents): bound the rename cascade at the title and at the projected total (BUG-2798, BUG-2796) (#1218) (@xarmian)
  • 99cb56f: fix(events): a broken host clock must not make the repair fatal (BUG-2740, codex round 7) (@xarmian)
  • 53afc21: fix(events): a generation we cannot vouch for ends coverage, not just the message (BUG-2736) (@xarmian)
  • b5615cc: fix(events): a live subscriber is told when it has a hole (BUG-2730) (@xarmian)
  • 94cc249: fix(events): a phase-1 counter restart must not leave a live epoch behind (BUG-2736) (@xarmian)
  • b227764: fix(events): a refused connection is not a sync_required (BUG-2730, codex round 1) (@xarmian)
  • 9f88e94: fix(events): a resume must not be answered from coverage we never had (BUG-2731) (@xarmian)
  • 1f4ab9b: fix(events): abandon and teardown must not strand a joiner or leak a PubSub (@xarmian)
  • 9fe2839: fix(events): an epoch that overflows int64 is corruption too (BUG-2736) (@xarmian)
  • 4d84453: fix(events): apply the registration bound to the buffer, before the cursor filter (@xarmian)
  • f3aa865: fix(events): assign the in-memory id under the lock that orders the buffer (BUG-2736) (@xarmian)
  • 548b315: fix(events): await Redis registration before admitting an SSE subscriber (@xarmian)
  • d936464: fix(events): bound the mid-stream signal, and stop it moving existing alerts (BUG-2730, codex round 4) (@xarmian)
  • 703e746: fix(events): bound the replay at registration instead of withholding fan-out (@xarmian)
  • 650d9df: fix(events): bound the replay by append POSITION and buffer identity (@xarmian)
  • 1e83933: fix(events): concurrent publishes must deliver in ID order (BUG-2730, codex round 9) (@xarmian)
  • effd019: fix(events): detect a half-open Redis connection with a bus heartbeat (BUG-2738) (#1195) (@xarmian)
  • 6afe683: fix(events): do not arm reset detection where interleave is ordinary traffic (BUG-2736) (@xarmian)
  • c017ad3: fix(events): give each in-memory bus incarnation its own ID space (BUG-2736) (@xarmian)
  • 538b0d5: fix(events): guard the generation counter the way the epoch key beside it is guarded (BUG-2740) (@xarmian)
  • be47896: fix(events): hold both wire forms to the same id rule (BUG-2736) (@xarmian)
  • c3d485d: fix(events): make the ID space's epoch a monotonic generation (BUG-2736) (@xarmian)
  • d81f67a: fix(events): publish the generation Redis holds, not Lua's rendering of it (BUG-2740, codex round 2) (@xarmian)
  • 417776c: fix(events): recover when the generation counter goes backwards and stays (BUG-2736) (@xarmian)
  • 72336aa: fix(events): release SSE admission slots when a client leaves mid-establishment (BUG-2749) (#1186) (@xarmian)
  • 1933041: fix(events): surface the SUBSCRIBE error and refuse callers instead of admitting a dead stream (BUG-2764) (#1215) (@xarmian)
  • 19f29e1: fix(events): the channel is the authority on whose event a message is (BUG-2736) (@xarmian)
  • e00b1d2: fix(events): the generation ceiling is one digit under the epoch ceiling (BUG-2740, codex round 4) (@xarmian)
  • f243540: fix(events): three failure paths that lost events without saying so (BUG-2736) (@xarmian)
  • db7a931: fix(events): two mechanisms that were each right alone (BUG-2736) (@xarmian)
  • de96cce: fix(items,server,web): reserved metadata survives a move; referential metadata travels only within its context (BUG-2674) (#1165) (@xarmian)
  • 6e7d34c: fix(mcp): accept a fields object on pad_item create/update; reject undeclared input keys (#1066) (#1159) (@b4rk13)
  • f0cbcb5: fix(mcp): route backlinks/history/report over HTTP transport + catalog↔route parity test (BUG-2304) (#1126) (@xarmian)
  • 2b33184: fix(metrics,watchevents,server): three codex round-1 findings (BUG-2727) (@xarmian)
  • 6a2910d: fix(models): refuse an append that would destroy an unreadable structured field (BUG-2627, part 3) (#1164) (@xarmian)
  • f756e85: fix(oauth): keep zero-workspace consent authorizable via the wildcard path (BUG-2303) (#1124) (@xarmian)
  • 5003718: fix(push): apply delivery's visibility gate to delivered_sessions (BUG-2725) (#1187) (@xarmian)
  • cc3cfee: fix(redis): honour the caller's context on TLS dials (BUG-2754) (#1198) (@xarmian)
  • f465d4c: fix(server): a malformed before_id is a 400, not a 500 (BUG-2774) (#1205) (@xarmian)
  • 6b5e8be: fix(server): a structured timeline id must not collide with a row id (BUG-2783) (#1210) (@xarmian)
  • 50499bc: fix(server): charge workspace imports against the plan limit (BUG-2793) (#1219) (@xarmian)
  • effea01: fix(server): diff the activity change list against the store's pre-image (BUG-2776) (#1206) (@xarmian)
  • 2e4f3d5: fix(server): refuse a PATCH carrying both a fields hierarchy key and top-level parent_id (BUG-2594) (#1133) (@xarmian)
  • ba12558: fix(server): refuse a decoded NUL in a JSON request body (BUG-2803) (#1220) (@xarmian)
  • 771ec5b: fix(server): refuse invalid-UTF-8 and NUL query values at the transport (BUG-2784) (#1217) (@xarmian)
  • a167005: fix(server): register the stream gauge per metrics instance, and drop the comments the last refactor falsified (BUG-2726) (@xarmian)
  • e4e914d: fix(server): reject path segments the database cannot be asked about (BUG-2782) (#1207) (@xarmian)
  • 50a442d: fix(server): resolve collection slugs against the workspace's real collections (BUG-2578) (#1146) (@xarmian)
  • 9a94398: fix(server): restore the readiness route I broke, and stop the tests bypassing it (BUG-2727) (@xarmian)
  • b4989aa: fix(server): retire a cursor we just refused, and stop trusting one we cannot read (BUG-2731) (@xarmian)
  • 08dfbdb: fix(server): rowless-blob GC sweep — reclaim blobs no attachments row references (BUG-2406) (#1137) (@xarmian)
  • 22c5a85: fix(server): stop counting disabled conventions as completed work (#1152) (@asjdf)
  • 7e20e29: fix(server): two defects codex round 10 found INSIDE round 9's fixes (BUG-2726, BUG-2727) (@xarmian)
  • 9d54f24: fix(server,cli): the half of round 12's fix I missed (BUG-2726) (@xarmian)
  • 3e3170e: fix(server,cli,docs): the consumer contract, per codex round 12 (BUG-2726) (@xarmian)
  • 449ac10: fix(server,mcp,cli): refuse system-metadata keys in fields_patch + retry-hostile error code (BUG-2627 part 2, BUG-2675) (#1166) (@xarmian)
  • 0351846: fix(server,metrics,docs): five codex round-2 findings (BUG-2724, BUG-2726) (@xarmian)
  • 9afedbe: fix(server,metrics,watchevents): seven codex round-4 findings — operator and next-author angle (BUG-2727, BUG-2724) (@xarmian)
  • c03a485: fix(server,redisns): two codex round-3 findings — DoS via legacy tokens, blank namespace (BUG-2724, BUG-2726) (@xarmian)
  • ea13927: fix(server,watchevents): shared session presence + honest push acceptance (BUG-2698, BUG-2699) (#1175) (@xarmian)
  • 7c03beb: fix(sse): bound the ordering barrier by a count, not by the channel emptying (BUG-2730, codex round 15) (@xarmian)
  • 3a00783: fix(sse): queued events go out before the gap announcement (BUG-2730, codex round 13) (@xarmian)
  • 3a63b52: fix(store): activity debounce must not merge across writers (BUG-2763) (#1201) (@xarmian)
  • e0c5792: fix(store): attachment delete vs thumbnail derivation race — atomic cascade, locked conditional insert, orphaned-variant GC class (BUG-2388) (#1134) (@xarmian)
  • 31aba0b: fix(store): compare-and-set the activity debounce merge (BUG-2770) (#1204) (@xarmian)
  • cade263: fix(store): compare-and-set the wiki-link cascade's writes (BUG-2785) (#1213) (@xarmian)
  • 2c8ddff: fix(store): cover documents and comment bodies in the attachment reference walks (BUG-2614, BUG-2615) (#1145) (@xarmian)
  • 692b3e1: fix(store): erase a deleted account's user id from frozen outbox payloads (TASK-2719) (#1185) (@xarmian)
  • fab3833: fix(store): never return an activity id alongside an error (BUG-2779) (#1209) (@xarmian)
  • 8cdeeb1: fix(store): orphan GC claim protocol — writer reference stamps + conditional row-first deletes (BUG-2415) (#1129) (@xarmian)
  • 7680919: fix(store): re-stamp attachment refs on item/document restore so a racing GC claim is refused (BUG-2629) (#1155) (@xarmian)
  • 31075d9: fix(store): route cross-workspace copy's lock-held reads through the copy transaction (BUG-2409) (#1136) (@xarmian)
  • 7732223: fix(store): serialize document renames and stop reading the pool inside transactions (BUG-2778) (#1208) (@xarmian)
  • aa1a4f9: fix(store): workspace-scope the orphaned-variant GC's parent check (BUG-2622) (#1143) (@xarmian)
  • d6e153a: fix(test): the new config test needed a writable HOME (Nix CI) (@xarmian)
  • d8b098f: fix(watchevents): bound the resume settle window by the request context (BUG-2751) (#1197) (@xarmian)
  • c735840: fix(watchevents): detect a half-open Redis connection with a bus heartbeat (BUG-2769) (#1199) (@xarmian)
  • 5184aba: fix(watchevents): detect the two holes the watch bus could not see (BUG-2739) (@xarmian)
  • d1501d9: fix(watchevents): keep backward detection alive across a coverage drop (BUG-2739, codex round 13) (@xarmian)
  • 937b424: fix(watchevents): make the subscribe/replay overlap test deterministic (BUG-2707) (#1170) (@xarmian)
  • c854c52: fix(watchevents): refuse the ambiguous cursor after a counter reset (BUG-2739, codex round 21) (@xarmian)
  • 4269bdd: fix(watchevents): the same hole, told to the stream holding it open (BUG-2730) (@xarmian)
  • a3a98b2: fix(watchevents,metrics): unify the reset label on counter_backward (BUG-2739) (@xarmian)
  • 640c5a0: fix(watchevents,server): four concurrency findings from codex round 9 (BUG-2727, BUG-2726) (@xarmian)
  • ad0deac: fix(web): Activity's item_id was a phantom — the wire field is document_id (@xarmian)
  • a219599: fix(web): an entry missing from a refreshed first page is not necessarily deleted (BUG-2773) (#1203) (@xarmian)
  • 2322fb2: fix(web): give the IDB write path the seq guard RAM already had (BUG-2609) (#1148) (@xarmian)
  • 9048785: fix(web): heal collection renames missed by SSE — sync-pass route reconcile + localIndex retag (BUG-2601) (#1127) (@xarmian)
  • 0d08e70: fix(web): isolate self-declared agent names so they cannot rewrite the audit around them (TASK-2759) (@xarmian)
  • e09216d: fix(web): keep named actors out of the anonymous fold key; type the dashboard fixture (TASK-2759) (@xarmian)
  • e36be90: fix(web): order-and-merge contract for the localIndex cache — tombstones, durable retag overlay, equal-seq merge (PLAN-2636 unit 2, BUG-2633/2634/2635) (#1157) (@xarmian)
  • 2521e3e: fix(web): portal the pane action-bar menus — anchored panels clipped against the pane's scroll container (BUG-2610) (#1132) (@xarmian)
  • 08b165a: fix(web): restore the audit-log formatter guard I narrowed (TASK-2759) (@xarmian)
  • 46e3430: fix(web): restore the chip title, make the feed's fold-key test discriminating (TASK-2759) (@xarmian)
  • 54526c5: fix(web): route ItemDetail collection writes through a semantic adopt gate (BUG-2602) (#1128) (@xarmian)
  • cc26288: fix(web): share pages render attachment refs as honest placeholders (BUG-2389) (#1135) (@xarmian)
  • fbea948: fix(web): source-identity guards on the SSE non-sync listeners (BUG-2611) (#1131) (@xarmian)
  • d1c5c39: fix(web): stop the badge CSS upper-casing a stamped agent name (TASK-2759) (@xarmian)
  • 31c8d42: fix(web): suppress notes/decision change pills in the activity feed (BUG-2628) (#1211) (@xarmian)
  • 817bb0a: fix(web): widen the name bound off my own over-correction; assert bdi at every binding (TASK-2759) (@xarmian)
  • 625cab9: fix: bound item history and stop resolving bodies nobody reads (BUG-2608) (#1147) (@xarmian)
  • bb003dd: fix: five claims the final comment-truth round found (BUG-2724, BUG-2726) (@xarmian)
  • 7c8ed3c: fix: nine false or overstated claims in this diff's own prose (codex round 15) (@xarmian)
  • 35e5642: fix: seven more prose claims, one real metric gap, and a flaky test of my own (codex round 17) (@xarmian)
  • 6f16003: fix: surface implementation notes + decision log in the item timeline (BUG-2301) (#1144) (@xarmian)
  • 2fa1316: fix: three claims round 15's corrections got wrong or missed (codex round 16) (@xarmian)
  • 11f67b0: fix: timeline can answer has_more=true with zero entries, and the client cannot page past it (BUG-2765) (#1202) (@xarmian)

Performance

  • f5ca67c: perf(events): shorten the subscribe-confirmation bound to a measured 1s (@xarmian)

Refactors

  • b7ae022: refactor(events): every way of subscribing hands back the gap signal (BUG-2730, codex round 5) (@xarmian)
  • 3c6b412: refactor(events): let go-redis own the redial, and record what neither form detects (BUG-2731) (@xarmian)
  • 1e40c97: refactor(events): one home for the asymmetry, one assertion for the count (BUG-2736) (@xarmian)
  • 44efef5: refactor(events): remove the dead global-limit parameter from SubscribeIfAllowed (BUG-2726) (@xarmian)
  • a51320c: refactor(metrics,server): make pad_stream_connections_active a scrape-time collector (BUG-2726) (@xarmian)
  • bc6d354: refactor(watchevents,docs): order the startup test, move operator prose out of the code (BUG-2739, codex round 16) (@xarmian)
  • d24df55: refactor(web): delete unmounted components carrying real logic (TASK-2632) (#1163) (@xarmian)
  • 6dc6499: refactor(web): drop the one-use helper wrapper, bound name width (TASK-2759) (@xarmian)
  • ec70f13: refactor: act on codex round 8's scope review (BUG-2727) (@xarmian)
  • 461c5a3: refactor: prune the claim surface, and turn one prose claim into a test (@xarmian)

Other changes

  • 55fce49: chore(ci)(deps): bump docker/setup-buildx-action (#1189) (@dependabot[bot])
  • ff969d2: chore(deps)(deps): bump the go-minor-and-patch group across 1 directory with 4 updates (#1180) (@dependabot[bot])
  • 3f9daa5: chore(deps)(deps): bump the npm-minor-and-patch group (#1190) (@dependabot[bot])
  • ac0f118: chore(deps)(deps): bump the npm-minor-and-patch group across 1 directory with 17 updates (#1140) (@dependabot[bot])
  • 0ccf178: chore(deps)(deps-dev): bump jsdom from 26.1.0 to 29.1.1 in /web (#1141) (@dependabot[bot])
  • 019c335: chore(docker)(deps): bump golang in the docker-minor-and-patch group (#1188) (@dependabot[bot])
  • 0e2cb06: chore(nix): bump package version to 0.15.0 ahead of the v0.15.0-rc.1 tag (@xarmian)
  • 8a95d29: chore(web): name the GENERIC_AGENT_IDS shim's retirement condition (CONVE-2757) (#1192) (@xarmian)
  • d6832d6: docs(bus): state the gap seam's invariant for the third cause (BUG-2730, codex round 17) (@xarmian)
  • e4167a9: docs(deployment): five corrections from reading the doc as a document (BUG-2739, codex round 20) (@xarmian)
  • f4d1616: docs(deployment): name the resume-boundary residual for operators too (BUG-2739) (@xarmian)
  • 988bea6: docs(deployment): state the watch reset metric has no released contract (BUG-2739, codex round 11) (@xarmian)
  • db8c5b7: docs(deployment): sync_required is not only a resume answer (BUG-2730) (@xarmian)
  • 3b2df81: docs(deployment): the activity stream cannot detect ID-sequence holes (BUG-2739, codex round 1) (@xarmian)
  • cadf0fa: docs(events): name the joiner retry as defence in depth, and make it loud (@xarmian)
  • a0eb070: docs(events): name the mixed-roll straggler window, and assert what bounds it (BUG-2736) (@xarmian)
  • e12cc58: docs(events): say why each mechanism is here, after a scope review (BUG-2736) (@xarmian)
  • 739f573: docs(events): the namespace helper's comment described a window this fix closed (@xarmian)
  • 6e590b4: docs(events): the straggler window closes per workspace, not globally (BUG-2736) (@xarmian)
  • 5cbeb52: docs(events): three claims the split left describing code that is gone (BUG-2731) (@xarmian)
  • 161e603: docs(events): why per-workspace backward detection is enough for a global counter (BUG-2740, codex round 8) (@xarmian)
  • 8c05b02: docs(events,metrics): document the repair path an operator will actually meet (BUG-2740, codex round 4) (@xarmian)
  • 383c6dc: docs(events,metrics): finish the epoch_regressed claim and fix the heading level (BUG-2740, codex round 5) (@xarmian)
  • 378dec5: docs(idspace): name the assumption the incarnation bound rests on (BUG-2736) (@xarmian)
  • 76aa111: docs(metrics,deployment): finish the undecodable wording and answer the label question in the artifact (BUG-2739, codex round 6) (@xarmian)
  • d28c28e: docs(plugin,mcp,readme): the push monitor is consent-gated — say so where agents and operators read (PLAN-2613 S5, TASK-2620) (#1216) (@xarmian)
  • 86b0f75: docs(server): name the subscribe-then-replay window where it lives (BUG-2730) (@xarmian)
  • a963e68: docs(skills): de-assume the slash-command surface + route onboard shortcut through the canonical playbook (BUG-2573/2574/2575) (#1139) (@xarmian)
  • c9f0dc6: docs(watchevents): an observer must not call Close either (BUG-2739, codex round 4) (@xarmian)
  • 603c7f7: docs(watchevents): bring the shared Bus contract up to what RedisBus now does (BUG-2739, codex round 18) (@xarmian)
  • c844185: docs(watchevents): cost out the malformed-message flood rather than waving at it (BUG-2739, codex round 10) (@xarmian)
  • 90f4191: docs(watchevents): record why dropCoverage is not counter-gated (BUG-2739, codex round 8) (@xarmian)
  • 47a83cc: docs(watchevents): scope round 21's boundary claim to what it actually closes (BUG-2739, codex round 22) (@xarmian)
  • 48d3c90: docs(watchevents): score the knownFrom reset honestly — it is defence, not a tested line (BUG-2739) (@xarmian)
  • 720182a: docs(watchevents): sweep the undecodable overclaim as a class, with its boundary (BUG-2739, codex round 9) (@xarmian)
  • 5d5450a: docs(watchevents,deployment): name the failover cost and the dropped-confirmation road (BUG-2739, codex round 2) (@xarmian)
  • 927202a: docs(web): put the leaf-not-fragment rule where the next edit will read it (TASK-2759) (@xarmian)
  • fa22b66: docs+test: correct two over-claims and pin name escaping (TASK-2759) (@xarmian)
  • 454afe5: perf+a11y(web): one metadata parse per audit row, drop a redundant tooltip (TASK-2759) (@xarmian)
  • d6480c1: revert(sse): remove the ordering barrier; its failure mode is worse than the problem (BUG-2730, codex round 16) (@xarmian)
  • b3c5ba9: style: gofmt the struct-field alignment the new Server field broke (@xarmian)
  • bc6ee29: test(events): assert WHERE the namespaced bus publishes, not only where it subscribes (@xarmian)
  • ad8853f: test(events): assert the widened-window premise after the defect assertion (@xarmian)
  • 63c45bc: test(events): buy ordering-race detection with repetition, not a load-dependent sample (BUG-2742) (@xarmian)
  • d393126: test(events): close the gaps a tests-as-production-code pass found (BUG-2736) (@xarmian)
  • 8597d60: test(events): close the two remaining instances of both shapes (BUG-2742, codex round 4) (@xarmian)
  • 8ef4069: test(events): correct an arrangement the split made vacuous (BUG-2731) (@xarmian)
  • 5f8252c: test(events): cover the migration's central claim and three untested branches (BUG-2736) (@xarmian)
  • 0d50176: test(events): derive the drop-boundary tests from the depth, and name what the waits stop testing (BUG-2742, codex round 6) (@xarmian)
  • 483ac6e: test(events): drive all three rotation branches, not one (BUG-2740) (@xarmian)
  • 4148278: test(events): drive the double-delivery window through the gap it actually opens (@xarmian)
  • f6a58b6: test(events): finish the depth migration, close the go-redis bound, correct a rationale (BUG-2742, codex round 7) (@xarmian)
  • 2c74ac5: test(events): hold the SUBSCRIBE ack at the wire until the timer arm is taken (BUG-2786) (#1214) (@xarmian)
  • 729abfd: test(events): isolate the id==1 rotation branch, and record the ruling on the seed (BUG-2740, codex round 3) (@xarmian)
  • 9a8e41f: test(events): keep invariants in the comments and process history in the trail (BUG-2742, codex round 12) (@xarmian)
  • c719bf4: test(events): make the confirm-versus-timer race deterministic (@xarmian)
  • bfaa1d8: test(events): make the nearest example safe to copy (BUG-2742, codex round 10) (@xarmian)
  • 6c81cf9: test(events): make three comments true of the code they sit on (BUG-2742, codex round 5) (@xarmian)
  • fa2ddf3: test(events): pin the drop signal, its metric, and the subscribe-replay atomicity (BUG-2730) (@xarmian)
  • 7378707: test(events): prove the replacement subscription is live by getting an event back (BUG-2742, codex round 9) (@xarmian)
  • a9544a5: test(events): repair the resume tests the base guard made vacuous (BUG-2736) (@xarmian)
  • 6ba8e8d: test(events): retract the reconnect wait, which guaranteed nothing (BUG-2742, codex round 8) (@xarmian)
  • 79b0f23: test(events): strengthen the ordering test and correct the registration mechanism (BUG-2742, codex rounds 2-3) (@xarmian)
  • d075b39: test(events): the Redis atomicity test now exercises its interleaving (BUG-2730, codex round 20) (@xarmian)
  • a049121: test(events): the clamp table needs no Redis (BUG-2740) (@xarmian)
  • e1f256b: test(events): wait for the subscription to register before publishing (BUG-2742) (@xarmian)
  • 0accca7: test(events,docs): pin the detection chain a colliding repair actually relies on (BUG-2740, codex round 5) (@xarmian)
  • cbf2dd2: test(events,metrics): assert exactly-once, cover the new counter, drop an overclaim (@xarmian)
  • 1af9fa2: test(mcp): guard the OAuth workspace allow-list population (TASK-2753) (#1193) (@xarmian)
  • a061c17: test(server): pin the Redis health prober and presence failure reporting (BUG-2727) (@xarmian)
  • af99762: test(server): the gap signal must reach the wire, not just the bus (BUG-2730, CONVE-19) (@xarmian)
  • 1cee8e6: test(server): the quiet-control leg could not see a gap announced at connect (BUG-2730) (@xarmian)
  • a82bbd6: test(server): the rate limit has to be tested where it is BOUND (BUG-2730, codex round 11) (@xarmian)
  • 2f1896b: test(watchevents): a diagnostic that described go-redis behaviour it does not have (BUG-2739, codex round 12) (@xarmian)
  • b24a1bc: test(watchevents): close two mutation survivors in the BUG-2739 tests (@xarmian)
  • 9f23364: test(watchevents): pin both gap-signal scopes with their counterfactuals (BUG-2730) (@xarmian)
  • 3895fc4: test(watchevents): record the startup guard's one unordered window, with the measurement (BUG-2739, codex round 17) (@xarmian)
  • b27407c: test(watchevents): wait on the expected reset REASON, and pin the startup control with its mutation (BUG-2739, codex round 3) (@xarmian)
  • e7ba441: test(watchevents,metrics): cover the three behaviours nothing exercised (BUG-2739, codex round 7) (@xarmian)
  • 0719286: test(web): assert the agent-name binding from each consuming surface (TASK-2759) (@xarmian)
  • 501ba83: test(web): close the generic-id gap in the admin suite (TASK-2759) (@xarmian)
  • 3984419: test(web): close two coverage holes the mutation matrix found (TASK-2759) (@xarmian)
  • 3a213d9: test(web): cover the overview binding and agentNameOf's own contract (TASK-2759) (@xarmian)
  • b759898: test(web): fake-indexeddb harness for the localIndex persistence layer (PLAN-2636 unit 1) (#1156) (@xarmian)