Roadmap

Maidan ships in clusters. Each cluster ends with a release tag and a retrospective. Within a cluster, work is broken into PRs tracked by the GitHub issues labelled with that cluster.

Cluster ladder

ClusterThemeTarget tag
AFoundation: workspace, schema, /healthv0.0.1
BRouting + event bus + MCP surfacev0.1.0
CSearch + indexingv0.2.0
DFSM-driven thread lifecyclev0.3.0
EArtifact substrate (S3, types, refs)v0.4.0
FAuth, workspaces, capabilitiesv0.5.0
GAgent-to-Agent transportv0.6.0
HWeb UI + MCP stdio + polishv0.7.0
1.0Production gates metv1.0.0

Cross-cutting tracks

These run in parallel with delivery clusters and do not have their own tags; they raise the bar each time they ship.

TrackThemeNotes
TTelemetry + perfOTLP, tracing, latency budgets.
UPerformance workBenchmarks, mutation tests, profiling.
VSecurity + privacyThreat models, GDPR, secret hygiene.
WDocumentationThe vault, runbooks, API docs.
XRelease engineeringTags, release notes, signed artifacts.

Current cluster

Clusters A–H and 1.0 are complete (v1.0.0). Optional minors v1.1.0v1.4.0 are complete.

Post-1.0 work is organized in Post-1.0.md and Tracks/README.md.

Cross-cutting tracks T, U, V, W, X are complete.

Product Ladder 77–101 is closed on main; the operator gate maidan-operator-1.0 is tagged at v101.0.0 (the Pi/edge integration point, see Pi.md). Clusters 93–101 shipped as one batch (PR #264) released as v101.0.0, so there are no separate v93.0.0v100.0.0 tags.

Product Ladder 102+ is COMPLETE. Product Ladder 102+scale-out, hardening & correctness — closed across Phases XIX (scale-out core, 102–105), XX (hot-path hardening, 106–110), XXI (correctness & coverage, 111–115), XXII (search & indexer at scale, 116–118), and XXIII (supply chain & scale gate, 119–120), tags v102.0.0v120.0.0. The maidan-scale-1.0 product gate is tagged at v120.0.0 (Gates/maidan-scale-1.0), alongside maidan-operator-1.0 (v101), maidan-agent-1.0 (v76), and maidan-2.0 (v58) — all four gate tags are cut. No further ladder cluster is defined past 120; future work is post-gate human-product and the cross-cutting tracks (Open Work, Remaining Work).

Post-gate hardening (Phase XXIV): with the ladder closed, work continues opportunistically from Open Work / Remaining Work, tagged on the same vX.0.0 ladder but without new gate tags. Cluster 121.0 (v121.0.0) opened it (OpenAPI-wide capability map in CI + scale-out SLO coverage); Cluster 122.0 (v122.0.0) added promtool execution of the SLO alert rules; Cluster 123.0 (v123.0.0) proved OTLP export end-to-end against a real collector; Cluster 124.0 (v124.0.0) consolidated the rule validators and promoted the alert-rules + otlp-smoke jobs to required checks (8 total); Cluster 125.0 (v125.0.0) added opt-in at-least-once event delivery; Cluster 126.0 (v126.0.0) extended it to the MCP SSE transport; Cluster 127.0 (v127.0.0) reconciled the backlog; 128.0 (v128.0.0) hardened A2A delivery; 129.0 (v129.0.0) bounded buffers + error visibility; 130.0 (v130.0.0) lifted observability/MCP test coverage; 131.0 (v131.0.0) closed delivery-unification; 132.0 (v132.0.0) shipped the global admin audit query API (completing the 127–132 sweep). A UI track then began: 133.0 (v133.0.0) repaired the broken /ui write path + added a JS guard; 134.0 (v134.0.0) added message reactions; 135.0 (v135.0.0) added message pins; 136.0 (v136.0.0) added group DMs (new tab); 137.0 (v137.0.0) added a deliveries & DLQ operator view (list + filter + replay); 138.0 (v138.0.0) completed the "Operator" tab with global-audit + reindex controls (operator-console arc 137–138 complete); 139.0 (v139.0.0) added 1:1 direct messages (new "DMs" tab, the parallel to group DMs); 140.0 (v140.0.0) added a workspace presence roster (new "Presence" tab, rendering the WS presence_snapshot frames). 141.0 (v141.0.0) fixed the published mdBook site — its sidebar had ~20 dead links (mdBook silently skipped the ../docs/* sources); a build-time staging step now publishes all 21 SUMMARY pages, plus a landing quickstart and a helpful 404. 142.0 (v142.0.0) added the slash-command registry (new "Slash" tab: register/list/revoke), surfacing the last unsurfaced backend collaboration feature. The /ui now covers the full backend surface; remaining work is polish / new product rather than catch-up. 143.0 (v143.0.0) began UI polish: richer message rendering (timestamps + inline slash-command results), surfacing payload data the thread view didn't show. 144.0 (v144.0.0) added a docs dead-link gate (mdbook-linkcheck, warning-policy = error) — the 141 follow-up — which surfaced + fixed 35 latent broken published links (space-files hyphenated, out-of-set links GitHub-rewritten) and reconciled the backlog docs (132 audit API + 134–143 UI track). The docs pipeline now self-guards against broken-nav regressions. An MCP streamable spec-completeness arc (145–148) then began: 145.0 (v145.0.0) landed the JSON-RPC/lifecycle conformance basics — initialize protocol-version negotiation, MCP-Protocol-Version header validation, JSON-RPC batching + notifications on POST /mcp; the streamable-transport gaps (GET SSE + Accept negotiation, resumability, server→client requests) follow in 146–148. 146.0 (v146.0.0) added GET /mcp/streamable (server→client SSE stream) + Accept-based JSON/SSE content negotiation on the POST; 147.0 (v147.0.0) added resumability — SSE id: on session frames + Last-Event-ID reconnect replay (bounded per-session log; the session now survives a dropped POST leg). 148.0 (v148.0.0) concluded the arc with server→client requests (sampling / roots / elicitation via request_client, capability-gated + correlated) + per-session client-capability tracking. The MCP streamable spec-completeness backlog item is closed — no open backend capability gaps remain. After next-arc research (UI polish, missing features, token efficiency, request_client), an MCP-agent-surface arc began: 149.0 (v149.0.0) added MCP inbox/mention tools (list_mentions/get_inbox/mark_inbox_read) so an MCP-only agent can discover it was @mentioned; 150.0 (v150.0.0) added thread/member/kind filters to /mcp/stream (await my mention). The MCP-agent-surface pair is complete. A token-efficiency cluster followed: 151.0 (v151.0.0) made get_thread_context edits lean by default ({id, editor, edited_at}; opt-in include_edits=true for full bodies) — edit bodies were the largest token cost in a context pack — and clamped list_messages to 1..=500. 152.0 (v152.0.0) brought the same lean-edits default to the REST context pack (GET /threads/:id/context + /workspaces/:wid/context, via MessageEditView with optional bodies + include_edits query param) and added snippet_only=true to GET …/search (drops full bodies; semantic hits get a truncated snippet). The token-efficiency lane now covers both context surfaces + search. 153.0 (v153.0.0) shipped lane 2 — a live-updating /ui thread view: WS message/reaction/pin frames for the open thread now refresh the message list (debounced) instead of only landing as Events-tab log lines. Lane 3 (request_client) then began: 154.0 (v154.0.0) fixed GET-stream delivery — server→client requests (sampling/roots/elicitation) now ride a per-session broadcast merged into the spec-canonical GET /mcp/streamable stream (they previously reached only a POST-leg SSE holder). 155.0 (v155.0.0) closed it with a real caller: the sampling-backed summarize_thread tool threads the streamable session id through handle_in_sessiondispatchtools_call and issues a server→client sampling/createMessage over the GET stream. The three-lane next-arc plan is complete (token efficiency 151+152, live UI 153, request_client 154+155). A 5-agent research sweep (feature-gaps, performance, CI/CD, token, production-readiness) then set the next program — four arcs to run in order toward enterprise production-readiness: (1) hardening (quick-wins → channel/thread RBAC, the #1 finding), (2) perf + CI/CD, (3) agentic features (structured content, backpressure, HITL approvals, task handoff), (4) token round 3. Arc 1 began: 156.0 (v156.0.0) shipped production-safety defaults — SIGTERM graceful shutdown (k8s/systemd drain) + a default 30 s statement_timeout. 157.0 (v157.0.0) made AUTH_DISABLED fail-closed — it now requires the explicit MAIDAN_ALLOW_INSECURE_NO_AUTH ack (and never in production), closing the silent-open-door risk; coordinated across the compose/helm CI manifests. 158.0 (v158.0.0) added keyless cosign signatures to the container images (server + postgres, by digest), closing the unsigned-images gap. Arc-1 hardening quick-wins are done; the arc's flagship channel/thread RBAC then began (the #1 finding — authz is workspace-flat), planned as three clusters (membership model → enforcement → management API; Postgres RLS deferred). 159.0 (v159.0.0) landed part A: the channel_members model + store + migration (both backends), additive with no enforcement. 160.0 (v160.0.0) landed part B: ensure_channel_access enforced on every REST content route + search + workspace-context (private channels need a membership row; public + __dm__ unchanged; creator auto-added on private create) — closing the workspace-flat read/write vuln on REST. 161.0 (v161.0.0) landed part C: MCP point-access enforcement (a pre-dispatch gate on the content tools + resources/read), closing the MCP read/write path into private channels. 162.0 (v162.0.0) filtered the MCP aggregate reads (search / list-channels / workspace-context), closing the channel-content vuln on REST + MCP. 163.0 (v163.0.0) verified WS/MCP subscribe grants against membership, closing the private-channel event leak. 164.0 (v164.0.0) added the channel:admin capability + /channels/:cid/members REST + MCP membership API, making private channels operational. 165.0 (v165.0.0) guarded reference.rs (REST + MCP add_reference) via the entity→channel access helpers, completing the channel/thread RBAC arc (159–165). Arc 1 (enterprise hardening) is done; arc 2 (perf + CI/CD) began: 166.0 (v166.0.0) fixed the SQLite per-connection pragma bug (R3) + the per-event all-workspaces webhook scan (H1). 167.0 (v167.0.0) = R2 rate-limiter map eviction (memory leak) + H6 embedding model→table cache. 168.0 (v168.0.0) = H4 (outbox list_pending JOINs the event payload → the relay publishes without a per-row get_stored_event; batch mark_published_batch after the loop) + R1 (env-tunable MAIDAN_BUS_BROADCAST_CAP) + a main-red hotfix (two Cluster 166 webhook_worker unwrap()s the strict -D clippy::unwrap_used lint step rejected once GitHub Actions recovered — outage-time local validation missed them). GitHub Actions is back, so the flow returns to green-CI-then-merge. 169.0 (v169.0.0) = H2 — coalesce the optimistic-path delivery-cursor write (forward_bus_items buffered the highest delivered log_id, persisting per 64 events / 500 ms + flush on stream end, instead of a DB UPSERT per event; lag-replay advances once to the batch high-water). Safe: best-effort cursor (the authoritative at-least-once reconcile_deliver already batches), monotonic advance, at-least-once tolerates the re-delivery. Arc 2's code-perf items (R1/R2/R3, H1/H4/H6, H2) are done. 170.0 (v170.0.0) closed arc 2 with the CI/CD speedups: release.yml's arm64 maidan-server image now builds on a native ubuntu-24.04-arm runner instead of QEMU (the emulated Rust cargo build --release was the ~2 h leg that dominated the release), plus a report-only trivy scan of the released server image. (docker-postgres left as-is — no compile; cargo caching already present via Swatinem/rust-cache.) Arc 2 (perf + CI/CD) is complete. Arc 3 (agentic features) began: 171.0 (v171.0.0) added thread task assignment / handoff — a Thread.assignee_id axis (orthogonal to the state FSM) with REST (PUT/DELETE /threads/:id/assignee, POST …/assignee/claim) + MCP (assign_thread/claim_thread/unassign_thread), an atomic compare-and-set claim (exactly one concurrent winner), and a ThreadAssignmentChanged event; reuses thread:transition + per-channel RBAC. 172.0 (v172.0.0) added MCP structured backpressure — a rate-limited POST /mcp / /mcp/streamable now returns a JSON-RPC error envelope (-32029 + data.retry_after_ms, still 429 + Retry-After) so an agent's JSON-RPC layer gets a typed backoff signal instead of an opaque transport 429. 173.0 (v173.0.0) added structured message content — typed content blocks (text/code/tool_use/tool_result/resource_link) on messages over REST + MCP, persisted in a new JSONB/JSON column; body is derived from the blocks so search is unaffected. 174.0 (v174.0.0) added HITL approvals — a request_approval MCP tool that asks the human on the client to approve/reject via a server→client elicitation/create (returns {approved, action, content}), the elicitation analogue of summarize_thread. Arc 3 (agentic features) is complete (171 assignment, 172 backpressure, 173 structured content, 174 HITL). Arc 4 — token round 3 began: 175.0 (v175.0.0) brought the REST snippet_only token-saver to the MCP search_messages tool (drop full bodies, keep the snippet). 176.0 (v176.0.0) made tools/list capability-filtered — a caller sees only the tools its token can invoke (via catalog_for), instead of the whole ~40-tool catalog. 177.0 (v177.0.0) omitted empty Message.metadata from the wire. 178.0 (v178.0.0) added opt-in lean event frames — a lean subscribe flag (WS + MCP SSE) so event frames carry {log_id, kind, ...ids} pointers instead of full events. Token round 3 (175–178) — and the entire post-v155 four-arc program (enterprise hardening 156–165, perf + CI/CD 166–170, agentic features 171–174, token round 3 175–178) — is COMPLETE. A fresh 5-agent research sweep then produced a new security-led four-arc program (chosen 2026-08-07, "all in order"): Arc A — security & correctness, then B — multi-tenant SaaS ops, C — agentic task-queue depth, D — performance & scale. 179.0 (v179.0.0) opened Arc A by closing a real vuln: POST /a2a/v1/rpc now enforces channel/thread access (an external A2A token could post into / read a private channel it wasn't a member of — the one surface the 160–165 RBAC arc missed). 180.0 (v180.0.0) closed the next Arc-A gap: DM/group-DM threads live in the shared __dm__ channel, which ensure_channel_access exempts — so the generic thread/message routes (and the A2A ingress, and workspace search + workspace-context) let a non-participant read/write a DM. ensure_thread_access is now DM-participant-aware (ensure_dm_participant), all thread/message-scoped surfaces gate on it, and the search/context filters key on per-thread access (can_access_thread). 181.0 (v181.0.0) closed the EventKind-parity risk by removing the duplication rather than guarding it: the store kept its own parse_kind copy per backend (duplicating EventKind::parse), and append re-parses the kind column on read-back — so a missing variant made the insert fail after INSERT and silently roll back (the Cluster 171 bug). Both store copies now delegate to the single EventKind::parse; EventKind::ALL + a round-trip guard (with a compile-time tripwire on new variants) lock the survivor. 182.0 (v182.0.0) extended the audit trail to the security-sensitive mutations that left no trace: token.mint/token.revoke (incl. the OIDC first-admin session mint), app_token.mint/app_installation.revoke, channel_member.add/.remove, and message.purge, via a best-effort crate::audit::record helper (a failed audit write logs audit.write_failed rather than breaking the operation — a mint must never lose its secret). Table-level 401/403 denial auditing was declined (an attacker-controlled write amplifier; denials stay in logs/metrics). 183.0 (v183.0.0) gave an unconfigured deployment a DoS floor — a built-in global per-client rate limit (1200 req/60s per bearer/IP) applied when MAIDAN_RATE_LIMIT_MAX is unset (server-binary only via an AppState flag, so tests/embedders are untouched; explicit env incl. 0 overrides) — and made the request body cap explicit + tunable (MAIDAN_MAX_BODY_BYTES, default 2 MiB; oversized → 413 not 400). 184.0 (v184.0.0) closed Arc A by hardening the domain-write → event-append dual write: publish() now retries the durable append on transient store errors, distinguishes an append failure (the event is lost) from a benign bus-publish failure (already logged), and meters hard losses via maidan_event_append_failures_total. True single-transaction atomicity (a transactional outbox across every mutation × both backends) is a larger, tracked follow-up — the message-post path is entangled (insert → slash-edit → publish), so a partial refactor would leave a mixed-atomicity codebase. Arc A (security & correctness) is complete (179–184). Arc B (multi-tenant SaaS ops) began: 185.0 (v185.0.0) hardened the Helm chart — liveness + a new startupProbe moved to the shallow /health/live (the old chart pointed liveness at /health, which 503s on any degraded dependency, so a transient DB blip restart-stormed the pod mid-recovery), readiness to the deep /health/ready; plus opt-in PodDisruptionBudget (on in prod), an opt-in safe-by-default NetworkPolicy, and existingSecret support. 186.0 (v186.0.0) added opt-in data-retention pruning for the unbounded-growth tables (event log, audit trail, delivery queues): a batched background sweeper deletes rows past a per-table age (MAIDAN_RETENTION_*_DAYS), with the event log floored at min_delivery_cursor (the lowest at-least-once watermark, so no lagging durable consumer loses an undelivered event) and deliveries limited to terminal rows. 187.0 (v187.0.0) added workspace export / portability: GET /workspaces/:id/export (gated on token:admin) returns the workspace's content graph — members, channels with members, threads, messages with edits, pins, references — as one JSON bundle (DM message content included; secrets and operational tables excluded), so a tenant can be migrated or archived rather than only deleted. 188.0 (v188.0.0) added per-workspace usage / metering: GET /workspaces/:id/usage (gated on workspace:read) returns live member/channel/thread/message counts (tombstones excluded) — a metering basis that stays low-cardinality (an on-demand DB aggregate, not a per-tenant Prometheus series, which would blow up cardinality as tenants grow); artifact storage bytes omitted (content-addressed dedup makes per-tenant bytes ill-defined). 189.0 (v189.0.0) closed Arc B with a secret-rotation keyring: at-rest secrets were AEAD-encrypted with a single FEDERATION_ENCRYPTION_KEY and no rotation path (changing it stranded every ciphertext). A try-all-keys decrypt keyring now lets you rotate — set the new key as the primary and move old keys into FEDERATION_DECRYPT_KEYS (decrypt fallbacks); encryption uses the new primary, decryption tries the primary then the fallbacks. No ciphertext-format change (backward-compatible); AEAD authentication makes trying keys safe. Arc B (multi-tenant SaaS ops) is complete (185–189). Arc C (agentic task-queue depth) began: 190.0 (v190.0.0) added the thread-assignment read-side (Cluster 171 shipped only the write side) — GET /members/:id/assigned-threads (a member's work queue, RBAC-filtered) + POST /channels/:cid/threads/claim-next (atomically claim the oldest unassigned thread; Postgres FOR UPDATE SKIP LOCKED so concurrent claimers each get a distinct thread). 191.0 (v191.0.0) completed 190's deferral — the MCP tools for the assignment read-side: claim_next_thread (channel access enforced pre-dispatch) + list_assigned_threads (a member-scoped aggregate read, RBAC-filtered to the caller's access like search_messages) — so an MCP-native agent can discover and pull its work. 192.0 (v192.0.0) added claim leases + reclaim (dead-agent recovery): claim_next_thread takes an optional lease_secs, and a thread is claimable when unassigned OR its lease has expired — so a claimed-then-dead agent no longer holds a thread forever (the next claimer reclaims it, no reaper); POST /threads/:id/claim/renew + MCP renew_claim are the holder-only heartbeat. 193.0 (v193.0.0) added the list_roots MCP tool — the server→client roots/list request's first organic caller (after sampling → summarize_thread and elicitation → request_approval), so an agent can ask its client which roots it exposes. 194.0 (v194.0.0) closed the federation parts→content deferral: A2A ingest (POST /a2a/v1/rpc) built its message with content: None, discarding the parts' structure — it now maps each text part to a ContentBlock::Text so an A2A message carries the same structured content as a REST/MCP post (Cluster 173); body stays the joined searchable projection. 195.0 (v195.0.0) added handoff notes on assignment: assign_thread (REST PUT /threads/:id/assignee + the MCP tool) accepts an optional note that rides the ThreadAssignmentChanged event to the new assignee + subscribers in real time (event-only, not persisted — the assignment log lives in the event stream); note-less claim/unassign/claim_next are byte-identical to before, and the federation event-rewrite threads the note through. 196.0 (v196.0.0) added wait_for_mention, a blocking MCP long-poll: an MCP-native agent subscribes to the event bus filtered to its MentionRecorded events and blocks until one arrives (or a timeout_ms window lapses, default 30 s), returning the mention or null — so an agent can await work instead of polling get_inbox. Live-only (drain existing mentions first; the resumable GET /mcp/stream SSE stream is the at-least-once alternative) and RBAC-filtered (a mention in an inaccessible private channel is skipped). 197.0 (v197.0.0) closed Arc C with tool-call transcripts: tool_transcript walks a thread's messages and pairs every Cluster-173 ToolUse block with its ToolResult by id (order-independent — a result may land later), returning a token-lean ToolTranscript (ordered calls each with {name, input, result?} + message context, plus orphan_results; Text/Code/body dropped) over REST GET /threads/:id/tool-transcript + MCP get_tool_transcript (both workspace:read, thread-RBAC). Arc C (agentic task-queue depth) is complete (190–197). Arc D — performance & scale then began with the discipline of measuring first: 198.0 (v198.0.0) added a load / soak harnessscripts/loadgen.sh + an #[ignore]d load_baseline test that drives concurrent REST traffic (post/read/search) and reports per-op latency percentiles + throughput (in-process SQLite by default, or a live deployment via MAIDAN_LOADGEN_URL), so the arc's optimizations (sharded fan-out, filtered-ANN, batched context) can each be shown to move the number. The percentile math is pure + unit-tested in CI; the load run itself is never a pass/fail gate. 199.0 (v199.0.0) took the first optimization: concurrent workspace-context assemblybuild_workspace_context built each page thread's context (~7 store round-trips) in a sequential loop, stacking a 50-thread page's latency linearly; it now builds them with a bounded-concurrency buffered stream (cap 8), collapsing Σ per-thread toward ceil(N/8)× a single build while capping pool fan-out. buffered preserves page order + short-circuits on error, so the response/query-count/404 contract is unchanged (guarded by the query-count test + a new no-cross-contamination test). 200.0 (v200.0.0) shipped filtered-ANN search: message search fetched top-K then post-filtered inaccessible hits, which wasted ranking work and under-filled the limit (ask for 10, get 4 because 6 top hits were in a private channel). The server now computes the caller's private-channel deny-set (private_channel_deny_set) and both backends exclude those channels in the query (SQLite NOT IN, Postgres <> ALL($n); lexical + semantic), so a full page of accessible hits comes back and private content is excluded at the source. The thread-level post-filter stays authoritative for DMs (__dm__ is intentionally out of the channel-level pre-filter). 201.0 (v201.0.0) shipped workspace-sharded event fan-out: the bus used one broadcast channel, so every publish woke every subscriber to filter-and-discard other tenants' events (O(all subscribers)/event); a new ShardedBroadcast routes a publish to just the event's workspace shard + a global shard (cross-workspace subscribers), so fan-out is O(relevant). Behavior is unchanged (an optimization under the existing EventFilter); shards are lazily created + pruned. Batched pg_notify was declined (low hot-path value + delivery-core risk) and read-replica routing deferred (needs a Store read-pool refactor + a real replica to validate) — both logged in Open Work. The Arc D tractable perf wins are done (198 harness, 199 concurrent context, 200 filtered-ANN, 201 sharded fan-out). The security-led four-arc program (179–201) is COMPLETE. A fresh 5-agent research sweep (2026-08-12) then set a new four-program arc (user: "handle all 4 of these opportunities"), run in order: (A) Security & correctness round 2 (3 real residual vulns + transactional outbox + federation trust/RLS), (B) agentic orchestration (DAG/scheduling/skills/queue-depth), (C) notifications & reach, (D) scale & durability. Program A began: 202.0 (v202.0.0) closed a session-impersonation vuln — only post_message pinned a session (browser/OIDC) caller to its own member; every other member-attributed write trusted a caller-supplied member id, so a /ui session user could act as any member (DMs, edits, votes, reactions, pins, thread transition/assign/claim). A shared ensure_acting_member guard is now applied on every such surface (bearer = act-as-any, unchanged; the mention target + assignee are correctly left unguarded). 203.0 (v203.0.0) closed the DM/group-DM participation gaps that Cluster 180 left on the real-time + metadata surfaces: expand_event_filter had no participant check, so anyone with event:subscribe could tail any DM's live messages (via dm_conversation_id or the __dm__ thread_id) — it now runs ensure_thread_access (DM-aware) on the resolved thread, closing both paths on WS + MCP-SSE; and DM/group-DM metadata reads now require participation for a session caller (list is self-only), with bearer = orchestrator (unchanged). 204.0 (v204.0.0) closed cross-tenant artifact isolation: artifacts are content-addressed + deduped across workspaces (no workspace_id), and GET /artifacts/:sha gated only on workspace:read — so a known SHA leaked another tenant's blob (+ a dedup oracle). A new maidan_artifact_refs table records which workspaces may access each SHA (written on upload, backfilled from the uploader's workspace); get_artifact* requires the caller's-workspace ref, returning 404 when absent. Dedup is preserved (two workspaces uploading the same bytes each get a ref); ref-counted blob GC is a documented follow-up. 205.0 (v205.0.0) began the transactional-outbox refactor (the 184 deferral; user chose the full multi-cluster path): a mutation committed its domain row, then publish() appended the durable event in a separate tx — a crash between lost the event. The foundation now: a reusable events::append_in_tx(&mut tx, event) + create_channel_with_event/create_thread_with_event that insert the row and append the event (+ outbox) in one transaction, atomic-or-nothing; routes use them + publish_stored for the post-commit bus notify. Behaviour unchanged (same events reach the stream); only crash-consistency is new. Remaining mutations migrate in follow-up clusters. 206.0 (v206.0.0) migrated votes + reactions: cast_vote/add_reaction/remove_reaction gained *_with_event variants (shared events::message_scope_in_tx resolver; remove emits its event only when a row was actually removed), so those events are now crash-consistent with their mutation. Pins + mentions, thread transitions/assignments, and the slash-edit-entangled message post follow; the non-atomic publish() set keeps shrinking. 207.0 (v207.0.0) migrated pins + mentions: pin_message/unpin_message/record_mention gained *_with_event variants over the same message_scope_in_tx resolver (pins carry the channel it already returned; unpin emits MessageUnpinned only when a row was removed), so those events are now crash-consistent too. Thread transitions/assignments, DM/group-DM posts, and the entangled message post remain. 208.0 (v208.0.0) migrated thread FSM transitions: transition_thread_with_event commits the state change + its ThreadStateChanged event in one tx, over a new events::thread_scope_in_tx resolver (thread-scoped twin of 206's message resolver); the ~75-line FSM step is refactored into a shared transition_in_tx core so the non-event path is unchanged. Assignments (reusing the new resolver), DM/group-DM posts, and the entangled message post remain. 209.0 (v209.0.0) migrated thread assignments (assign/unassign/claim/claim_next → ThreadAssignmentChanged), reusing thread_scope_in_tx via a shared append_assignment_event; assign/unassign now capture the previous assignee in-tx (closing a read-then-write race the separate get_thread had), claim/claim_next are conditional, and the route's publish_assignment helper is gone. The thread-scoped batch is complete; DM/group-DM posts and the entangled message post remain. 210.0 (v210.0.0) migrated DM / group-DM posts: a new post_message_with_event(new, dm_conversation_id) inserts the message + appends MessagePosted in one tx (via message_scope_in_tx; Some for a 1:1 DM, None for a group), and both post routes use it + publish_stored. Only the slash-edit-entangled regular message post still uses publish() — the last mutation before the refactor closes. 211.0 (v211.0.0) migrated the regular message post: the route branches — no-slash → post_message_with_event (atomic); slash → provisional insert, external dispatch, then edit_message_with_posted_event (edit + MessagePosted of the edited message in one tx). This closes the message-post hold-out, but a grep found publish() still serves message edit/tombstone, the A2A ingest post, and member/workspace/reference/artifact events (+ the federation relay), so publish() stays — the migration has a real tail beyond what the earlier plan implied. 212.0 (v212.0.0) migrated message edit + tombstone (edit_message_with_eventMessageEdited, tombstone_message_with_eventMessageTombstoned; shared edit_in_tx core with 211's posted variant), emptying message.rs of publish() entirely. publish() now serves only the A2A ingest post and member/workspace/reference/artifact events (+ the federation relay). 213.0 (v213.0.0) migrated the A2A ingest post (reuses post_message_with_event — the DM-post shape) and member + workspace creation (create_member_with_eventMemberJoined, create_workspace_with_eventWorkspaceCreated; no scope resolution — the created entity is the subject). publish() now serves only the reference + artifact events (+ the federation relay). 214.0 (v214.0.0) migrated references + artifacts — the last domain mutations: add_reference_with_event (ReferenceAdded, scope-less) + upsert_artifact_with_event(new, ref_workspace) (the widest fold — upsert + the Cluster-204 access ref + ArtifactUpserted in one tx, preserving order and strengthening 204 isolation). The domain-mutation outbox migration is complete (205–214): every event tied to a domain-table write now commits atomically with it. publish() correctly remains for the two callers that append standalone events (no domain-table row to be atomic with) — the federation relay (re-publishing remote events) and publish_routed_mentions (fanning realtime MentionRecorded routing) — so the refactor concludes here with no cleanup cluster. With the outbox refactor done, Program A turned to its remaining security items: 215.0 (v215.0.0) shipped the federation ingest trust policy — an EventKind::federatable() allowlist (allowlist-by-default via an exhaustive match; ArtifactUpserted excluded since blobs aren't federated) enforced at ingest, plus a fix for a MemberJoined remap that leaked the peer's remote member.workspace_id into the local view. 216.0 (v216.0.0) resolved the RLS spike — a decision ADR that assesses Postgres Row-Level Security and defers it (the shared pool has no per-request tenant binding, the Store trait is workspace-agnostic, SQLite has no RLS, and app-layer RBAC already covers every surface); app-layer RBAC stays authoritative. With 216, Program A (security & correctness round 2, Clusters 202–216) is COMPLETE — 3 residual vulns closed, the transactional-outbox refactor (205–214), the federation ingest trust policy (215), and the RLS decision. Program B (agentic orchestration) then began: 217.0 (v217.0.0) landed the task-dependency DAG store foundation — a maidan_thread_dependencies edge table + store (add/remove/list-deps/list-dependents/dependencies_satisfied, readiness = all deps terminal), reusing the existing thread-as-task model (FSM + assignee/claim/lease). Landed as a zero-blast-radius foundation (no routes yet, the Cluster-159 pattern). 218.0 (v218.0.0) made claim_next readiness-aware — a NOT EXISTS clause in the claim candidate query (both backends, base + _with_event) excludes tasks with a non-terminal dependency, so the existing REST claim-next route and MCP claim_next_thread tool now respect the DAG with no new API. 219.0 (v219.0.0) added the DAG-management REST APIPOST/GET /threads/:id/dependencies (add; list + ready), DELETE …/:dep_id, GET /threads/:id/dependents (RBAC on both edge threads + same-workspace; full new-route preflight). 220.0 (v220.0.0) added the MCP DAG tools (add_thread_dependency, list_thread_dependencies) — so agents can build + inspect the DAG over MCP; the read/write surface is now complete over REST + MCP. 221.0 (v221.0.0) added transitive cycle prevention to add_thread_dependency (recursive-CTE reachability, both backends) — the DAG is now actually acyclic. 222.0 (v222.0.0) added the reactive ThreadReady event — a terminal transition that unblocks dependents pushes readiness so agents subscribe instead of poll. 223.0 (v223.0.0) added the wait_for_ready MCP long-poll (the wait_for_mention analogue) — an agent blocks on a single tool call until a task becomes claimable; the DAG surface is now complete end-to-end. 224.0 (v224.0.0) added channel queue-depth (GET /channels/:cid/queue-depth → ready/assigned/blocked counts) — the task-queue's observability read for scaling decisions. 225.0 (v225.0.0) added the get_queue_depth MCP tool (the MCP twin) — the task-queue subsystem is now feature-complete over REST + MCP. 226.0 (v226.0.0) opened the scheduled/recurring-task subsystem with a zero-blast-radius foundation (task_schedules table + model + store CRUD/due-scan, both backends; no worker/routes yet). 227.0 (v227.0.0) added the scheduler sweeper worker (opt-in background loop; atomic claim-and-advance so replicas don't double-fire; fires a task thread per due schedule). 228.0 (v228.0.0) added the task-schedule REST management API (create/list/pause-resume/delete; workspace:write + target-channel access). 229.0 (v229.0.0) added the task-schedule MCP tools (create_task_schedule, list_task_schedules) — the scheduled/recurring-task subsystem is now complete over REST + MCP. 230.0 (v230.0.0) opened Arc E (capability registry + skill routing) with a zero-blast-radius foundation (member_skills table + model + store add/remove/list, both backends; no routes). 231.0 (v231.0.0) added skill-aware claimthread_required_skills + claim_next skips a task whose required skills the claimer lacks (the existing claim route + MCP tool become skill-routing for free). 232.0 (v232.0.0) added the capability-registry REST (member-skill + thread-required-skill CRUD). 233.0 (v233.0.0) added the capability-registry MCP toolsArc E is complete (skill routing over REST + MCP, enforced in claim_next). 234.0 (v234.0.0) opened Arc F (coordination waits + structured results) with a zero-blast-radius foundation (thread_results table + model + store set/get, both backends; no routes). 235.0 (v235.0.0) wired it over RESTPUT /threads/:id/result (thread:transition, upsert) + GET /threads/:id/result (workspace:read, 404 until produced), both under DM-participant-aware thread RBAC — and added a ThreadResultSet event on set (a "go fetch" pointer, observable on WS + MCP-SSE like ThreadReady; locally-derived → non-federatable). 236.0 (v236.0.0) closed Arc F — and Program B — with the MCP surface: set_thread_result / get_thread_result (the twins of 235's REST), wait_for_result (block on a thread's ThreadResultSet, return the result payload — the coordination wait, the wait_for_ready analogue), and get_dependency_results (a parent aggregates its dependencies' outputs as [{thread_id, result}], RBAC-filtered). Program B (agentic orchestration) is COMPLETE — task-DAG + queue (217–225), scheduled/recurring tasks (226–229), capability registry + skill routing (Arc E, 230–233), coordination waits + structured results (Arc F, 234–236). Program C (notifications & reach) then began: 237.0 (v237.0.0) opened Arc G with the per-recipient notification ledger foundation — a maidan_notifications table (one row per recipient × source event; kind reuses EventKind, source_log_id points at the event-log row with no FK so it survives retention pruning, denormalized context for rendering, read_at NULL = unread) + Notification model + store CRUD, both backends. Where a mention was one shared row read through a single inbox cursor, this is the per-recipient delivery/read layer the notification router + unified inbox build on; zero-blast-radius (no router/routes yet). 238.0 (v238.0.0) added the notification router — an always-on, reconnecting event-bus consumer (spawned in main.rs) that resolves each event to the members it concerns and writes per-recipient rows; it routes MentionRecorded → the mentioned member, with writes deduped by a UNIQUE(member_id, source_log_id) index + ON CONFLICT DO NOTHING (so a replay or a second replica running the consumer can't double-notify) and a maidan_notifications_created_total{kind} metric. An @mention is now delivered to the recipient's ledger, not just recorded and polled. 239.0 (v239.0.0) added the REST unified inboxGET /members/:id/notifications (list; unread_only, limit) + GET …/unread-count + POST …/:nid/read + POST …/read-all, all workspace:read and self-only for a session caller (a member reads their own inbox; a bearer is the act-as-any orchestrator); mark_notification_read is now recipient-scoped in the store so a mark can't touch another member's notification. 240.0 (v240.0.0) closed Arc G with the MCP surface: list_notifications / get_unread_count / mark_notification_read (the twins of 239's REST) + wait_for_notification (block on the member's next notification-worthy event — the general form of wait_for_mention, via a shared wait_for_member_event helper). Arc G (per-recipient notification ledger + router + unified inbox) is complete (ledger 237 → router 238 → REST 239 → MCP 240). Arc H — preferences + subscription then began: 241.0 (v241.0.0) landed the mute-preferences foundation — a maidan_notification_prefs table (one row per member × EventKind with a muted flag, absent = notify) + NotificationPref model + store set/list/is_notification_muted, both backends; the routing brain the notification router will consult, zero-blast-radius (no router change yet). 242.0 (v242.0.0) wired mute into the router (route_event skips a muted (member, kind), metered via maidan_notifications_suppressed_total) + REST PUT/GET /members/:id/notification-prefs (set/list, workspace:read + self-only), delivering the mute feature end-to-end over REST. 243.0 (v243.0.0) added the MCP mute tools (set_notification_pref / list_notification_prefs), completing the mute half of Arc H over REST + MCP. 244.0 (v244.0.0) opened the follows half with the subscription foundation — maidan_channel_follows + maidan_thread_follows tables (presence = following, reverse index for the router's fan-out) + ChannelFollow/ThreadFollow models + store follow/unfollow/list/*_followers, both backends; zero-blast-radius (no router change yet). 245.0 (v245.0.0) wired follows into the router (a MessagePosted fans to the channel + thread followers, minus the author, honoring mutes) + REST follow/unfollow/list (/members/:id/channel-follows + thread; self-only, follow gated on target access) — following now delivers new activity to the inbox. 246.0 (v246.0.0) closed Arc H with the MCP follow tools (follow_channel / unfollow_channel / list_channel_follows + the thread triple; follow_* gate on target access). Arc H (preferences + subscription) is complete — mute (241–243) + follows (244–246) over REST + MCP. Arc I (transport + reach) then began (user: "email transport first"): 247.0 (v247.0.0) landed the email/SMTP transport foundation — a MailTransport trait + a lettre-backed SmtpTransport + SmtpConfig::from_env (MAIDAN_SMTP_*), the first off-platform transport, config-gated + unwired (cargo deny green with 0BSD on the allow-list). 248.0 (v248.0.0) added the recipient-address store — a maidan_member_emails table (one per member; a separate table to avoid the member-row ripple) + MemberEmail model + store set/get/delete. 249.0 (v249.0.0) wired email delivery into the router — when a per-recipient notification is written, it's also delivered by email to members with an address (248), if SMTP is configured (247); spawned best-effort so a slow mail server never blocks routing, metered by maidan_email_delivered_total, address-presence = opt-in. 250.0 (v250.0.0) added the REST surface to set/read/clear a member's delivery address (PUT/GET/DELETE /members/:id/email, self-only) — the email feature now works end-to-end over REST (register an address → notifications arrive by email when SMTP is configured). 251.0 (v251.0.0) added the /ui notification center — a "Notifications" tab (list + unread badge + mark-read/read-all + unread-only filter) over /ui/api/members/:id/notifications* routes reusing the Cluster-239 handlers under the session middleware. 252.0 (v252.0.0) added durable member last-seen (maidan_member_last_seen + store touch/get, both backends) — the persistent presence signal for presence-aware routing (presence is in-memory only today); zero-blast-radius foundation, unwired until 253. 253.0 (v253.0.0) wired it: the WS handler touches last_seen on presence registration (best-effort, spawned), and deliver_notification_email skips the send when the recipient was seen within MAIDAN_EMAIL_PRESENCE_WINDOW_SECS (opt-in; unset/0 = send as before, the Cluster-249 behaviour), metered outcome="skipped_present", fail-open on a read error — presence-aware routing now works end-to-end. 254.0 (v254.0.0) opened scheduled digests with the data model (store foundation): the user chose the alternative-mode product (a member picks immediate per-notification emails OR a periodic digest, not both), so this landed EmailDeliveryMode + DigestDue + two per-member tables (maidan_member_delivery_prefs, maidan_member_digest_state) + store set/get_delivery_mode / set_last_digest_at / members_due_for_digest, both backends — zero-blast-radius, unwired. 255.0 (v255.0.0) wired it: the router now skips a digest-mode member's immediate email (metered skipped_digest), and an opt-in digest sweeper worker (MAIDAN_DIGEST_TICK_SECS) drains members_due_for_digest, emails each an unread-count rollup, and advances the watermark on a successful send (at-least-once, self-healing; no-op without a transport; not single-flighted across replicas — a low-harm duplicate, run on one replica for exactly-once). The alternative-mode digest now works end-to-end. 256.0 (v256.0.0) added the delivery-mode REST (PUT/GET /members/:id/delivery-mode, workspace:read + self-only) — a member switches between immediate emails and a digest over the API. 257.0 (v257.0.0) added the delivery-mode MCP tools (set_delivery_mode / get_delivery_mode, workspace:read, member-scoped) — the twins of the 256 REST, closing the core of Arc I (the digest is now reachable over REST + MCP). The only remaining Arc-I item is the optional, low-value MCP email-address tools. Program D (scale & durability) has begun: 258.0 (v258.0.0) shipped the event-bus self-healing NOTIFY floor — the PG LISTEN/NOTIFY bus tracks a high-water log_id and back-fills the missed range from the log on a gap or reconnect, so the optimistic local broadcast no longer silently drops events appended during a LISTEN disconnect. 259.0 (v259.0.0) added the chaos / fault-injection harness — an #[ignore]d soak that publishes under load while killing the LISTEN backend, asserting no event is lost (measured 40/40 across 5 kills), validating the 258 floor end-to-end; pure fault_due helper unit-tested in CI. 260.0 (v260.0.0) added backup/restore + a DR runbookscripts/backup.sh (pg_dump + artifact tar) / scripts/restore.sh (guarded pg_restore) + a "Backup & disaster recovery" section in docs/Production.md. Remaining Program D: read-replica routing (the last, largest item) — user chose the full LSN causality-token design (strong read-your-writes), built as a multi-cluster arc validated against real streaming replication. 261.0 (v261.0.0) opened it: the Lsn token type + store LSN helpers + scripts/replica-harness.sh (a real local pgvector primary+standby) + an #[ignore]d test proving the helpers against real replication — validate-first, inert. 262.0 (v262.0.0) added the inert reader-pool split (PostgresStore { pool, reader } + with_replica_reader + MAIDAN_DB_REPLICA_URL boot wiring; reads still on the primary). 263.0 (v263.0.0) added the consistency token on writes (Store::write_lsn + a consistency::middleware stamping Maidan-Consistency-Token on successful mutations when a replica is configured). 264.0 (v264.0.0) shipped the routing heart — GET/HEAD read-consistency scope + read_pool selector + a cached-replay-LSN poller + entity reads routed to the replica once caught up to the client's token (validated vs real streaming replication). 265.0 (v265.0.0) routed the remaining content read families + added maidan_replica_reads_total{outcome} (auth/control-plane reads stay on the primary). 266.0 (v266.0.0) closed the arc — maidan_replica_lag_bytes gauge + the Production.md "Read replicas" section (config, Maidan-Consistency-Token contract, routing policy, metrics, harness). The LSN read-replica arc (261–266) and Program D (scale & durability) are COMPLETE — and with them the security-led four-program run (A security round 2, B agentic orchestration, C notifications & reach, D scale & durability). Remaining work lives in Open Work / Remaining Work. Optional-deferrals sweep begun (user: "take on the optional deferrals"): 267.0 (v267.0.0) A2A egress content→parts; 268.0 (v268.0.0) MCP email-address tools (parity w/ 250 REST). 269.0 (v269.0.0) workspace import store foundation — WorkspaceImport (deserializable mirror of the 187 export) + Store::import_workspace (one transaction, full-column inserts preserving ids/state/timestamps; both backends). Zero-blast-radius; the mode flag + token:admin REST route + 409 guard land in 270. 270.0 (v270.0.0) workspace import REST — POST /workspaces/import (token:admin): body = the export bundle (WorkspaceExport now Deserialize); ?mode=new (default) remaps all ids → fresh workspace, ?mode=restore preserves ids (409 if it exists unless &force erases first). Pure import::remap/flatten unit-tested; e2e proves export→new→restore-409→force. Remaining optional deferral: search token-aware routing (271–272). 271.0 (v271.0.0) search token-aware read routing — PostgresSearch routes reads to a replica once caught up to the request's Maidan-Consistency-Token (own reader pool + replay poller + read_pool), single-sourced via new maidan_store::postgres::replica_route. Lexical + semantic reads route; embedding writes/DDL/reindex stay primary. Validated vs real streaming replication. 272 adds the maidan_search_replica_reads_total metric to close the deferral. 272.0 (v272.0.0) search replica-reads metric — maidan_search_replica_reads_total{outcome} (metrics-agnostic SearchReadMetrics in PostgresSearchAppStatemetrics.rs delta-sync), the search-side twin of maidan_replica_reads_total; counter assertions added to the real-replica test. Closes the optional-deferrals sweep (267–272) and the LSN read-replica program end-to-end — no optional deferrals remain from the security-led four-program run. 273.0 (v273.0.0) strategy-pack reconciliation — folded the 2026-08-25 grokbot strategy pack (Handoff, Pre-Public Hardening, Path to Impressive, Expansion Bets, Launch, Protocols, Providers) into the canonical backlog: a "Post-272 forward work" section in Open Work.md is now the single source (MCP 2026-07-28 upgrade, durable mail retry queue, MCP example pack, SDKs, Slack/Git projectors, pre-public cleanup nits, launch). Reverted the pack's "Handoff.md is the backlog, not Open Work" redirect (CLAUDE.md/README) — Handoff.md is the strategy index that feeds Open Work. Fixed the docs-build linkcheck breakers + staleness. Docs-only. 274.0 (v274.0.0) launch positioning — new problem-first pitch off "Slack for agents" ("AI agents are brilliant and forgetful…") across README/Integration/Architecture/OpenAPI-description; fixed the broken AUTH_DISABLED quickstart command; relabeled A2A experimental + "what Maidan is not"; refreshed the Architecture baseline; folded a verified external launch-readiness review (ran the released binary) into a new Open Work "Public-launch readiness" backlog (version-truthfulness, SQLite first-write lock, quickstart, maidan init, framework recipes+CI, benchmark, A2A v1.0 compliance, GitHub metadata). 275.0 (v275.0.0) the pitch — final tagline + positioning: "The operating layer for teams of AI agents" ("Run your agents as one coordinated team that works from a shared, durable memory and spends only the tokens it needs") across README/Integration/Architecture/OpenAPI-description. Gap → combination-that-closes-it → outcome (better work, fewer tokens); access control first-class; em-dashes/AI-voice tells removed. Supersedes the 274 hook. 276.0 (v276.0.0) runtime version truthfulness (launch-readiness P0) — /health/binary/image reported 0.0.0; the release pipeline now bakes the tag into every build path (native release.yml, aarch64 cross via Cross.toml, image via Dockerfile ARG/ENV), with a build.rs rerun-if-env-changed=MAIDAN_VERSION so a warm cache can't ship a stale version. Cargo version stays 0.0.0 (publish = false). First burn-down from the "Public-launch readiness" backlog. Verified: the v276.0.0 released binary reports v276.0.0. 277.0 (v277.0.0) SQLite write-contention fix (launch-readiness P0) — root-caused the "database is locked" first-write failure: single-writer SQLite + sqlx deferred pool.begin() on a multi-connection pool deadlocks read-then-write (a harness showed ~90% of contended writes failing at 8 connections). Fix: SQLite backend defaults to 1 connection (DEFAULT_SQLITE_MAX_CONNECTIONS, override MAIDAN_DB_MAX_CONNECTIONS); Postgres unaffected. Regression guard sqlite_write_contention. 278.0 (v278.0.0) one-command quickstart (launch-readiness P0) — docker compose -f compose.quickstart.yaml up + scripts/quickstart-two-agents.sh: a clean machine to two agents collaborating with no Rust toolchain. docker/Dockerfile.quickstart pulls a pinned, SHA-verified v277.0.0 release binary (non-root, SQLite + localfs + loopback + dev auth ack). Built + run end-to-end locally (/health reports the real version, no SQLite lock); CI guards file validity. 279.0 (v279.0.0) maidan init (launch-readiness P0) — a one-time CLI bootstrap that seeds the first workspace + admin member + an all-capabilities token through the store (migrations first), prints it once, and refuses on an already-initialized database. Kills the bootstrap chicken-and-egg: production needs no AUTH_DISABLED or public bootstrap routes. New capability::all(); integration-tested; documented in Production.md. Next: framework recipes + interop CI. 280.0 (v280.0.0) framework integration recipes (launch-readiness P1) — copy-paste, live-verified LangChain / AutoGen / REST clients (examples/) + a docs/Framework Integrations.md guide (in the published book) so an integrator points an agent framework at Maidan's MCP endpoint in minutes: LangChain MultiServerMCPClient and AutoGen mcp_server_tools each load all 78 tools; REST via httpx. Baked in the two real gotchas found by running them live — the mcp>=1.9,<2 pin (SDK 2.x's stateless rewrite drops modules the adapters import) and AutoGen's every-param-needs-a-type rule (fixed the one untyped catalog param, set_thread_result.result). Interop CI deferred (network/adapter-version-fragile). Next: published benchmark, then A2A v1.0. 281.0 (v281.0.0) published benchmark (launch-readiness P1) — a post_to_observer_latency measurement in the loadgen harness (producer-post → WebSocket-observer-receive, read concurrently with the POST so it's fan-out not round-trip) + docs/Benchmark.md (in the published book) reporting real numbers on named hardware/commit/backend with reproduction commands: Apple M3 Max / in-process SQLite (one connection) → post→observer p50 0.71 ms/p99 1.00 ms, mixed throughput 1 586 ops/s at 8 workers / 666 ops/s at 32 (the single-writer SQLite ceiling), zero errors. Also fixed the harness to use the shipped 1-connection SQLite default (was 16 → the Cluster-277 deadlock). Measured, not asserted. Next: A2A v1.0 compliance. 282.0 (v282.0.0) A2A v1.0 compliance, arc part 1 (launch-readiness P1) — canonicalized the A2A JSON-RPC method strings to the spec's §5.3 Method Mapping Reference: tasks/cancelCancelTask, tasks/pushNotificationConfig/{set,get}{Create,Get}TaskPushNotificationConfig, dropped the non-spec tasks/resubscribe alias; SendMessage/SendStreamingMessage/GetTask/SubscribeToTask + TASK_STATE_* were already correct. Grounded in the authoritative spec (a2aproject/A2A a2a.proto + §5.3) — which disproved the backlog's "rename to message/send" assumption before any code changed. Deliberate pre-1.0 wire break on the experimental endpoint. The user chose the full multi-transport + TCK scope: arc continues 283 missing ops + per-task push-config → 284 Agent Card §4.4.1 schema → 285 HTTP/REST binding → 286 gRPC binding → 287 transport negotiation → 288 official SDK/TCK interop CI. 283.0 (v283.0.0) A2A v1.0 compliance, arc part 2 (launch-readiness P1) — added the missing additive JSON-RPC ops: ListTasks (workspace-scoped task list, contextId/pageSize filters, per-channel RBAC-filtered via can_access_thread; new Store::list_a2a_tasks both backends; single-page for now) + GetExtendedAgentCard (auth-gated card via a shared agent_card_payload()), both advertised in the Agent Card. Arc continues: 284 per-task push-config model (List/Delete + configId) → 285 Agent Card §4.4.1 schema → 286 REST binding → 287 gRPC binding → 288 transport negotiation → 289 SDK/TCK interop CI. 284.0 (v284.0.0) A2A v1.0 compliance, arc part 3 (launch-readiness P1) — moved push notification configs to the spec's per-task, multi-config-with-configId model (new maidan_a2a_task_push_configs table pg 0049 / sqlite 0048 + create/get/list/delete store methods both backends), completing all four push-config JSON-RPC ops (Create/Get/List/Delete, RBAC-checked per task); task-update notifications now fan out to all a task's configs. Arc continues: 285 Agent Card §4.4.1 schema → 286 REST binding → 287 gRPC binding → 288 transport negotiation → 289 SDK/TCK interop CI. 285.0 (v285.0.0) A2A v1.0 compliance, arc part 4 (launch-readiness P1) — rewrote the Agent Card (/.well-known/agent-card.json + GetExtendedAgentCard) to the spec §4.4.1 AgentCard object: supportedInterfaces ({url, protocolBinding "JSONRPC", protocolVersion "1.0"}), a capabilities object (streaming/pushNotifications/extendedAgentCard), skills, provider, defaultInput/OutputModes — replacing the flat {rpcUrl, ingressUrl, capabilities:[methods]}. protocolVersion moved per-interface; URLs host-relative pending a configurable origin (288). Arc continues: 286 REST binding → 287 gRPC binding → 288 transport negotiation → 289 SDK/TCK interop CI. 286.0 (v286.0.0) A2A v1.0 compliance, arc part 5 (launch-readiness P1) — the HTTP+JSON/REST binding (§11): 9 request/response routes under /a2a/v1 (message:send, tasks list/get, tasks/{id}:cancel custom method, push-config CRUD, extendedAgentCard) as thin adapters over the shared JSON-RPC op handlers (a rest_response converter maps result→200 / error→HTTP status). The :action paths route on axum 0.7 (matchit accepts a literal mid-segment :; the cancel route captures the whole segment and splits on :). Agent Card advertises the HTTP+JSON interface as a 2nd supportedInterfaces entry. Streaming REST (message:stream, tasks:subscribe) deferred. Arc continues: 287 gRPC binding → 288 transport negotiation → 289 SDK/TCK interop CI. 287.0 (v287.0.0) A2A v1.0 compliance, arc part 6 (launch-readiness P1) — the gRPC binding (§10): a tonic A2AService (GetTask/CancelTask/ListTasks) on a config-gated 2nd port (MAIDAN_A2A_GRPC_ADDR), thin adapters over the shared op handlers, auth from gRPC metadata. Risk-first probe: tonic/prost already vetted (OTLP) so low deny risk, but tonic 0.14's server transport pulls axum 0.8 → one deny.toml skip-tree. No protoc in CI/Docker → vendored codegen (minimal self-contained proto → local tonic-prost-build → committed generated.rs). Off by default. Scope: task read/cancel/list (SendMessage/push/streaming/extended-card + card gRPC interface + compose/Helm port → 288+). Arc continues: 288 transport negotiation + configurable origin → 289 SDK/TCK interop CI. 288.0 (v288.0.0) A2A v1.0 compliance, arc part 7 (launch-readiness P1) — transport negotiation (§5.2): the Agent Card advertises Maidan's transports configurably — MAIDAN_A2A_PUBLIC_ORIGIN makes HTTP interface URLs absolute, MAIDAN_A2A_GRPC_PUBLIC_ADDR adds a GRPC AgentInterface (discoverable gRPC from 287). Config read into AppState at startup, threaded through the well-known card + GetExtendedAgentCard; default card unchanged. Advertised gRPC addr is distinct from the bind addr (correct behind a proxy). Production.md documents A2A deployment. Arc finale: 289 official A2A SDK/TCK interop CI. 289.0 (v289.0.0) A2A v1.0 compliance, arc FINALE (launch-readiness P1) — an external interop conformance client (examples/a2a_interop.py, httpx-only) validating the Agent Card §4.4.1 + JSON-RPC + REST bindings against the spec's canonical method names, a scripts/a2a-interop.sh boot-run-teardown harness, and a report-only a2a interop CI job (continue-on-error; binding behavior stays gated by the required Rust e2e tests). Live-verified: all checks pass against a source-built server. The A2A v1.0 compliance arc (282–289) is COMPLETE — all three transports (JSON-RPC, REST, gRPC), §4.4.1 Agent Card, per-task push configs, transport negotiation. Last launch-readiness P1 item done. 290.0 (v290.0.0) a2a-interop harness hotfix — the report-only a2a interop CI job (289) went red on a cold cache: the harness's cargo run compile outlasted the 120 s health-wait → Connection refused. Now cargo builds first (blocking), runs the built binary, and fails fast if /health doesn't come up in 60 s. Harness-only; conformance behavior unchanged. 291.0 (v291.0.0) fold grokbot adoption/SDK pack — the concurrent agent's adoption/SDK strategy pack (Adoption/Clients/Client Contract/Client Testing + sdk/ 0.0.1 name-hold scaffolds for TS/Python/Rust/Go) folded into Open Work as an "Adoption & ecosystem (deferred)" backlog section (single source), with the pack committed as the spec/index behind it (reconciliation banners supersede its "do not fold" rules). All gated — no SDK implementation without a go; scaffolds inert (not workspace members). Same fold pattern as 273. Docs/governance only. 292.0 (v292.0.0) Architecture docs currency + split (launch-readiness P1) — split the stale, version-interleaved Architecture.md into a current, version-neutral conceptual overview + Architecture-history.md (release-by-release record). The conceptual doc was rewritten to describe today's system (agentic task layer, notifications, three-transport A2A, LSN read-replica, per-channel RBAC — it had gone stale ~v104); no vX.0.0/cluster vocab on the first user-facing page. Next: 293 GitHub metadata, then the SDK arc. 293.0 (v293.0.0) GitHub repo metadata (launch-readiness P1/P2) — set the repo homepage (published docs site) + 10 topics (rust/multi-agent/mcp/model-context-protocol/a2a/ai-agents/agent-infrastructure/agentic/postgres/websocket) via gh; added .github/ISSUE_TEMPLATE/ (bug/protocol-compat/benchmark + config). Terminal GIF/screenshot deferred (manual asset). Launch-readiness polish done. Next: the SDK arc (294+, TS→Python→Go→Rust to 0.1.0). 294.0 (v294.0.0) TypeScript SDK 0.1.0 (SDK arc, part 1) — the first usable language client, to the frozen v1 contract (docs/Client Contract.md): a dependency-free Client (REST + WebSocket; global fetch + pluggable WebSocket) with namespaced methods, the hero claimNextThread/renewClaim, subscribe + waitFor{Result,Mention,Ready}, a MaidanError (status/body/retryAfter, isConflict/isForbidden/isRateLimited), full .d.ts types (branded IDs), and client.mcpUrl as a string (no MCP dep); bumped 0.0.1 → 0.1.0. Plus a language-agnostic black-box harness (scripts/sdk-test.sh, build-then-boot SQLite server + run suite) + a node --test suite (5/5 pass locally). setResult writes need auth-enabled (produced_by member FK; the server's thread_result_e2e proves the write) so the SDK test exercises the result route via getResult→404. Not yet published to npm (needs NPM_TOKEN + an sdk-* tag). Next: 295 Python → 296 Go → 297 Rust, each to 0.1.0. 295.0 (v295.0.0) Python SDK 0.1.0 (SDK arc, part 2) — the second usable language client, to the frozen v1 contract, dependency-free (stdlib only): REST via urllib, and subscribe via a small hand-rolled RFC-6455 WebSocket client (_WebSocketConn — handshake + one masked send + a text-frame receive loop with auto-pong/close/fragmentation), so pip install maidan needs no third-party dep (Python's stdlib has no WebSocket client). snake_case surface (per the contract), claim_next_thread/renew_claim, subscribe + wait_for_{result,mention,ready}, a MaidanError (status/body/retry_after, is_conflict/is_forbidden/is_rate_limited); bumped 0.0.1 → 0.1.0. pytest black-box suite (5/5 pass through the Cluster-294 harness — the hand-rolled WS is verified against a real message_posted frame). Same set_result write constraint as TS (nil member under the auth-disabled harness → the test exercises the result route via get_result→404). Not yet published to PyPI. Next: 296 Go → 297 Rust. 296.0 (v296.0.0) Go SDK 0.1.0 (SDK arc, part 3) — the third usable language client, to the frozen v1 contract, dependency-free (stdlib only): REST via net/http, and Subscribe via a small hand-rolled RFC-6455 WebSocket client (ws.go — dial/handshake over net/crypto/tls, one masked send, a receive loop with auto-pong/close/fragmentation over a bufio.Reader), since Go's stdlib has no WebSocket client. Service-struct surface (Workspaces/Channels/Threads/Messages/Artifacts, PascalCase), ClaimNextThread/RenewClaim, Subscribe + WaitFor{Result,Mention,Ready}, an APIError (Status/Body/RetryAfter, IsConflict/IsForbidden/IsRateLimited via errors.As); responses as maidan.M (map[string]any, unknown fields ignored — typed models a future refinement). go test black-box suite (all pass through the Cluster-294 harness; go vet + gofmt clean); zero-dependency module → no go.sum. Same result-write constraint as TS/Python (nil member → the test uses GetResult→404). Not yet tagged for go get by version. Next: 297 Rust (concludes the arc). 297.0 (v297.0.0) Rust SDK 0.1.0 (SDK arc finale) — the fourth/final usable language client, to the frozen v1 contract; a standalone crate that does NOT depend on any maidan-* server crate (the contract's hard constraint), detached from the repo's Cargo workspace via an empty [workspace] table so its client-only dep tree never touches the strict workspace lint / cargo deny. Service-handle surface (workspaces()/channels()/threads()/messages()/artifacts()), claim_next_thread/renew_claim, subscribe (a reader thread; closes on Drop/close() via flag + TCP shutdown) + wait_for_{result,mention,ready}, a MaidanError (status/body/retry_after, is_conflict/is_forbidden/is_rate_limited/is_transport, impl std::error::Error); responses as serde_json::Value. Rust's std has no HTTP/TLS, so it takes a small sync stack (ureq over rustls + tungstenite + serde_json) — the one place the four SDKs diverge from "stdlib only". cargo test black-box (5/5 through the harness); clippy -D warnings + fmt + doctest clean. Completes the SDK arc (294 TypeScript → 295 Python → 296 Go → 297 Rust), all at 0.1.0, each verified black-box against a running server. Remaining (Open Work): SDK interop CI, registry publishing on sdk-* tags (needs secrets), typed response models. 298.0 (v298.0.0) SDK release workflow (post-arc publishing) — .github/workflows/sdk-release.yml publishes the four SDKs to their registries on per-language tags (sdk-ts-vX.Y.Z→npm, sdk-py-vX.Y.Z→PyPI, sdk-rs-vX.Y.Z→crates.io, sdk-go-vX.Y.Z→re-tag sdk/go/vX.Y.Z, Go's module version — no registry), each job gated on its tag prefix + a version guard (tag must equal the manifest version). Auth via NPM_TOKEN/PYPI_TOKEN/CRATES_TOKEN repo secrets (loaded from the maintainer's tokens via gh secret set, never committed — release_secrets.txt gitignored). All four verified publish-ready by local dry-run (npm publish --dry-run, cargo publish --dry-run, python -m build+twine check, go vet/build); docs/SDK Release.md documents the process. Next: push the sdk-*-v0.1.0 tags to publish, then 299 SDK interop CI, then the rest of the five-arc program (MCP 2026-07-28, mail retry queue, Slack/Git projectors, launch). 299.0 (v299.0.0) SDK interop CI (report-only) — a new sdk-interop job in ci.yml boots a source-built server (SQLite, auth disabled) and runs each client SDK's black-box suite against it via scripts/sdk-test.sh (typescript→python→go→rust; the four toolchains installed, the server build warmed once, suites run sequentially so they don't collide on the port). continue-on-error: true + not a required check — the server contract is already gated by the required Rust e2e tests, so this proves the four clients interop end-to-end without ever blocking a merge (the Cluster-289 a2a interop posture). Closes the SDK loop (294 TS → 295 Py → 296 Go → 297 Rust → 298 publish workflow → 299 interop CI.) Remaining of the five-arc program: MCP 2026-07-28, durable mail retry queue, Slack/Git projectors, public launch. SDK arc addendum: all four clients published + verified live (2026-08-27): npm maidan@0.1.0, PyPI maidan 0.1.0, crates.io maidan 0.1.0, Go sdk/go/v0.1.0 (via gh workflow run sdk-release.yml -f tag=… — pushing >3 tags at once suppresses GitHub's tag-push triggers, so dispatch or push ≤3). 300.0 (v300.0.0) MCP 2026-07-28 arc, part 1 — version negotiation (J3.1) — SUPPORTED_PROTOCOL_VERSIONS = ["2026-07-28","2024-11-05"], so a current client requesting 2026-07-28 on initialize (or via the validated MCP-Protocol-Version header) gets it echoed; 2024-11-05 stays a full fallback. preferred_protocol_version() now returns an explicit DEFAULT_PROTOCOL_VERSION (held at 2024-11-05, decoupled from SUPPORTED[0]) so version-less/older clients keep their transport — additive, no advertisement. Grounded in the 2026-07-28 spec (blog.modelcontextprotocol.io): version via MCP-Protocol-Version header, Mcp-Session-Id+initialize handshake removed (stateless), Mcp-Method/Mcp-Name routing headers, client info in _meta.io.modelcontextprotocol/clientInfo, ttlMs/cacheScope on list responses. Next: 301 stateless streamable core (J3.3–4), 302 routing headers (J3.2), 303 advertise 2026 (J3.5). 301.0 (v301.0.0) MCP 2026-07-28 arc, part 2 — stateless streamable core (J3.3–4) — a POST /mcp/streamable carrying MCP-Protocol-Version: 2026-07-28 now lands cold: served inline as a single JSON-RPC response (handle_in_session(…, None)), never minting or requiring an Mcp-Session-Id, regardless of Accept (sessions were removed in the revision — is_stateless_request/STATELESS_PROTOCOL_VERSION in mcp.rs). The 2024-11-05 SSE-session path (open_new_streamable_session) is untouched; live-wait + server→client requests keep riding GET /mcp/stream/WS/wait_for_* (a 2026 client is not told a POST GET-session is Streamable HTTP). POST /mcp was already stateless. e2e: a 2026 tools/list with Accept: text/event-stream + no session id → inline JSON, no Mcp-Session-Id. Known limit: the request_client server→client tools still use the 2024 session (niche for a stateless client). Next: 302 Mcp-Method/Mcp-Name routing headers (J3.2), 303 advertise 2026 (J3.5). 302.0 (v302.0.0) MCP 2026-07-28 arc, part 3 — SEP-2243 routing headers (J3.2) — Mcp-Method / Mcp-Name on POST /mcp + /mcp/streamable, optional but when present must match the body (Mcp-Method==request.method; Mcp-Name==the named target: params.name for tools/call/prompts/get, params.uri for resources/*) else 400, so a gateway can route/authorize without parsing JSON (crate::mcp::validate_routing_headers). Batches skip validation (one header can't describe many ops); a stray Mcp-Name on an unnamed method (tools/list/initialize) is ignored (body does no more than authorized). Header-less traffic unaffected. Unit tests (match/method-mismatch/name-mismatch/absent/unnamed/resource-uri) + e2e (mismatched Mcp-Method → 400). Next: 303 advertise 2026 (flip DEFAULT_PROTOCOL_VERSION, federation card/reference/Integration; J3.5/J1/J2) — closes the arc. 303.0 (v303.0.0) MCP 2026-07-28 arc, part 4 / finale — advertise 2026 (J3.5/J1/J2) — with negotiation (300), stateless core (301), and routing headers (302) green, DEFAULT_PROTOCOL_VERSION flips to 2026-07-28: a version-less client negotiates the current revision, an explicit 2024-11-05 request still gets it (fallback retained). The federation card (.well-known/maidan.json) now reports maidan_mcp::preferred_protocol_version() (auto-syncs); the generated MCP reference + maidan-mcp crate doc describe 2026 (stateless + SEP-2243 headers); Integration.md + Protocols.md advertise 2026 (banner/table/how-to/decision-tree/J-rows) and retire the J2 "say 2024-only" holding pattern. The default flip is the advertise switch; the stateless transport still keys on the explicit MCP-Protocol-Version header (a version-less legacy client is echoed 2026 but, absent the header, keeps the 2024 session — harmless). Tests: mcp_e2e 9/9 (2 default-flip assertions updated), mcp_streamable 13/13, federation 5/5. CLOSES THE MCP 2026-07-28 ARC (300–303). Remaining of the five-arc program: durable mail retry queue, Slack/Git projectors, public launch. 304.0 (v304.0.0) durable mail retry queue, part 1 — maidan_mail_outbox foundation (zero-blast-radius) — table (pg 0050 / sqlite 0049; status pending/delivered/dead, attempts, next_attempt_at, last_error, partial due index) + MailOutbox/NewMailOutbox/MailOutboxId + store both backends: enqueue_mail, claim_next_due_mail(now, lease_secs) (atomic leased claim — pg FOR UPDATE SKIP LOCKED, sqlite serialized select-then-update; bumps attempts + pushes next_attempt_at forward so a crashed worker's row is retried, at-least-once), mark_mail_delivered, mark_mail_failed(id, error, retry_at) (reschedule or dead-letter), count_dead_mail. Replaces (in 305) the best-effort fire-and-forget send at notification_router.rs. No worker/router/route wiring yet. Store test both backends. Next: 305 worker + router enqueue, 306 DLQ ops read. 305.0 (v305.0.0) durable mail retry queue, part 2 — worker + router enqueue — the notification router now enqueue_mails (after its transport/address/digest/presence suppression checks) instead of a best-effort inline mail.send that dropped on a transient SMTP failure; a new mail_worker background loop (mail_worker.rs, mirrors the digest sweeper) drains the outbox each tick: leased claim_next_due_mail → send → mark_mail_delivered (sent), or on failure reschedule with exponential backoff (retry; base 30s ×2 cap 1h) or dead-letter after 8 attempts (dead). Spawned in main.rs whenever a transport is configured (paired with the router's enqueue-only path); tick default 5s (MAIDAN_MAIL_WORKER_TICK_SECS); multi-replica-safe via the leased FOR UPDATE SKIP LOCKED claim. Metric outcomes enqueued/sent/retry/dead. The 3 existing router/presence/digest e2es gained a sweep_once before their mailer.sent assertions (skipped members never enqueue); new mail_worker_e2e proves delivery + retry-not-drop; backoff unit-tested. Next: 306 DLQ ops read (list/retry dead mail) — closes the arc. 306.0 (v306.0.0) durable mail retry queue, part 3 / finale — mail DLQ ops — GET /operator/mail/dead (token:admin) lists dead-lettered notification emails (DeadMail: id/to/subject/attempts/last_error/updated_at; newest first; limit 1..=500) + POST /operator/mail/dead/{id}/requeue (token:admin) resets a dead entry to pending/due/attempts-cleared so the mail_worker retries it (204/404). Store list_dead_mail/requeue_dead_mail both backends + a DeadMail view. token:admin (global/system, no new capability); bodyless OpenAPI stubs (no components ripple). Coverage: both-backend store test (list/requeue/reset), http_capability_matrix_e2e (cap enforcement + {id} substitution), openapi_e2e (bijection). CLOSES THE DURABLE-MAIL-RETRY ARC (304 outbox → 305 worker+enqueue → 306 DLQ ops). Follow-up: retention pruning of terminal outbox rows. Five-arc program remaining: Slack/Git projectors, public launch. Projector arc (David chose "both projectors config-gated, HOLD launch"): build Slack + Git projector code inert-without-credentials (SMTP/OIDC config-gate pattern; no LLM in Maidan — pure relay), then stop before the public-launch trigger (outward-facing/irreversible, gated on David's go per Launch.md). 307.0 (v307.0.0) Slack projector, part 1 — ingress foundation (config-gated) — slack.rs: SlackConfig::from_env (MAIDAN_SLACK_SIGNING_SECRET +optional MAIDAN_SLACK_BOT_TOKEN), verify_slack_signature (Slack's v0:{ts}:{body} HMAC-SHA256, ±5-min replay window, constant-time, reusing the in-tree hmac/sha2/subtle) + its inverse slack_signature, and the slack_events handler. POST /integrations/slack/events is unauthed (Slack signs its own requests, verified in-handler — the route lives on the public router next to /oauth/app/token): 404 when unconfigured, 401 on a bad/stale signature, echoes the url_verification setup challenge, ACKs event_callbacks (message→thread routing is 308). AppState.slack/attach_slack (the attach_mail gate pattern), wired in main.rs. slack unit 4/4 + ingress e2e 3/3 (challenge/404-disabled/401). Next: 308 channel-link mapping + inbound message→thread, 309 egress (Maidan→Slack), then the Git/GitHub App projector (310+). 308.0 (v308.0.0) Slack projector, part 2 — channel links + inbound routing — maidan_slack_channel_links table (pg 0051 / sqlite 0050) + SlackChannelLink/NewSlackChannelLink maps slack_channel_id → the Maidan channel/thread it projects into + the member inbound messages post as; store both backends (link upsert / get / list / unlink). slack.rs::route_slack_event routes an event_callback plain user message in a linked channel into the mapped thread ("{user}: {text}" via post_message_with_event + publish_stored — flows through the normal event/notification path). Loop prevention baked in: skips bot_id/subtype events + stamps metadata.slack so egress (309) never re-echoes Slack-sourced messages. Best-effort (ingress always ACKs). Link management is store-level (tests seed via store); a REST/MCP surface can follow. store test both backends + ingress e2e (message projected; bot message not re-projected). Next: 309 egress (Maidan→Slack chat.postMessage), then Git projector (310+). 309.0 (v309.0.0) Slack projector, part 3 / finale — egress — a Maidan message in a linked thread now appears in Slack, completing the bidirectional projector. SlackSender trait + SlackWebClient (Slack Web API chat.postMessage via bot_token; HTTP-200-{"ok":false} → error); route_message_to_slack(state, thread_id, message) — no-op unless a sender is configured, skips Slack-sourced messages (the metadata.slack tag from 308) so a projected inbound message is never echoed back (loop prevention closed both ways), resolves the thread's channel via the new store get_slack_channel_link_by_thread, relays the body; best-effort, metered maidan_slack_egress_total. Hooked into the existing notification-router MessagePosted arm (no new bus consumer — one line + the decision logic in slack.rs). AppState.slack_sender/attach_slack_sender, wired in main.rs from MAIDAN_SLACK_BOT_TOKEN (ingress works without one). Egress e2e with a mock SlackSender (relay / skip-slack-sourced / skip-unlinked). COMPLETES THE BIDIRECTIONAL SLACK PROJECTOR (307 ingress → 308 links+inbound → 309 egress), config-gated + loop-safe. Next: the Git/GitHub App projector (310+), then hold at the launch gate. 310.0 (v310.0.0) Git/GitHub projector, part 1 — webhook ingress foundation (config-gated) — github.rs: GithubConfig::from_env (MAIDAN_GITHUB_WEBHOOK_SECRET +optional MAIDAN_GITHUB_TOKEN) + the github_events handler. POST /integrations/github/events is unauthed (GitHub signs X-Hub-Signature-256, verified in-handler): 404 when unconfigured, 401 on a bad signature, 200 for the ping setup event, ACKs other events (issue_comment→thread routing is 311). Reused webhooks::verify_signature — GitHub's sha256=hex(HMAC-SHA256(secret, body)) is byte-identical to Maidan's own outbound-webhook signature, so no new crypto (contrast Slack's v0:{ts}:{body} + replay window). AppState.github/attach_github, wired in main.rs. github ingress e2e 3/3 (ping/404-disabled/401). Next: 311 repo/issue link mapping + issue_comment→thread, 312 egress (Maidan→issue/PR comment; installation-token JWT flow deferred, a configured token first). 311.0 (v311.0.0) Git/GitHub projector, part 2 — issue links + inbound routing — maidan_github_issue_links table (pg 0052 / sqlite 0051; PK (repo, issue_number)) + GithubIssueLink/NewGithubIssueLink maps a GitHub issue/PR → the Maidan channel/thread it projects into + the member inbound comments post as; store both backends (link upsert / get / get_by_thread (egress reverse lookup) / list / unlink). github.rs::route_github_issue_comment routes an issue_comment (action=="created") on a linked issue/PR into the mapped thread ("{login}: {body}" via post_message_with_event + publish_stored); skips comment.user.type=="Bot" (our egress echo) + stamps metadata.github for egress loop-prevention (312). Composite key so the same repo's many issues are distinct links. store test both backends + ingress e2e (comment projected; Bot comment not re-projected). Next: 312 egress (Maidan→issue/PR comment via a configured token), then hold at the launch gate.

312.0 (v312.0.0) Git/GitHub projector, part 3 / finale — egress — a Maidan message in a linked thread now appears as a GitHub issue/PR comment, completing the bidirectional projector. GithubSender trait + GithubApiClient (GitHub REST POST /repos/{repo}/issues/{n}/comments via a bearer token + the required User-Agent + Accept: application/vnd.github+json; GithubError); route_message_to_github(state, thread_id, message) — no-op unless a sender is configured, skips GitHub-sourced messages (the metadata.github tag from 311) so a projected inbound comment is never echoed back (loop prevention closed both ways), resolves the thread's issue/PR via get_github_issue_link_by_thread, posts the body; best-effort, metered maidan_github_egress_total. Hooked into the existing notification-router MessagePosted arm beside the Slack egress (no new bus consumer). AppState.github_sender/attach_github_sender, wired in main.rs from MAIDAN_GITHUB_TOKEN (ingress works without one). Egress e2e with a mock GithubSender (relay / skip-github-sourced / skip-unlinked). COMPLETES THE BIDIRECTIONAL GITHUB PROJECTOR (310 ingress → 311 links+inbound → 312 egress) and the projector arc (Slack 307–309 + Git 310–312), config-gated + loop-safe. GitHub App JWT/installation-token auto-exchange + Check Runs are logged follow-ups (a configured token gets a working projector first). Next: the public launch (arc #5) is gated on the maintainer's explicit go per Launch.md — HELD, not auto-triggered.

313.0 (v313.0.0) launch hardening — default-secure quickstart (Pre-Public Hardening F4 / Launch L1) — the quickstart no longer teaches AUTH_DISABLED ("one AUTH_DISABLED screenshot kills the launch"). compose.quickstart.yaml runs auth ON (dev MAIDAN_SESSION_SECRET + MAIDAN_BOOTSTRAP=1 so the demo still seeds its two agents while content ops require a token); the README happy path mints a bearer token with maidan init (bundled in the quickstart image, bumped v277.0.0v312.0.0 since init landed in v279; re-pinned the two tarball SHA-256s) and scripts/quickstart-two-agents.sh is auth-aware (MAIDAN_TOKEN/MAIDAN_WORKSPACE, bearer on every content call). AUTH_DISABLED demoted to a clearly-labelled local-only appendix backed by a new compose.quickstart.insecure.yaml override; Integration.md's seed section leads with maidan init. Both paths validated end-to-end against a source-built server (token mode exit 0 / two messages round-trip; insecure mode exit 0; unauth content POST → 401); CI validates both compose files. Launch-prep remaining (Cluster 314): L3 release-notes template, L4 claims sheet, L6 SECURITY/CONTRIBUTING, L5 cosign verify — then the launch itself, gated on the maintainer's go.

314.0 (v314.0.0) launch honesty (Launch L3/L4/L6 + Pre-Public Hardening F2/G5) — writing the claims sheet immediately caught a real bug: the README headline one-liner (DATABASE_URL=sqlite::memory: cargo run …) didn't boot (auth on requires a ≥32-byte MAIDAN_SESSION_SECRET) — the most-run newcomer command errored before /health; fixed + verified {"status":"ok"}. Shipped docs/Claims.md (published, linked from README) mapping every load-bearing claim → a gate/test/"not yet" (+ an honest "not yet" section: no hosted SaaS, projectors/email config-gated + unproven-in-public, SDKs 0.1.0, not on crates.io); a keyless-cosign "Verifying a release" section in SECURITY.md (image + binary/SBOM bundles, identity/issuer from release.yml); a human CHANGELOG-highlights.md + Release-notes template; and reconciled CONTRIBUTING.md to the solo-maintained/admin-merge/8-required-checks model (dropped "one approval required" + a stale cluster ref). All launch-prep is now done (313 F4 + 314 L3/L4/L5/L6). The public launch itself — public-preview cut, un-hold, announce — remains gated on the maintainer's explicit go per Launch.md; it is NOT auto-triggered.

315.0 (v315.0.0) pre-launch correctness & DX + research-sweep fold — the first cluster of the 2026-08-28 4-thread research sweep (which also folded the sweep's plan into Open Work.md: v314 currency + the 315–318 sequence + the fidelity/context flagship arc + a locked anti-goals block). Fixes: a hash-v1 boot warn! (the default embedding provider is a deterministic hash → "semantic search" silently returns near-random hits if MAIDAN_EMBEDDING_PROVIDER is unset); the README "Run it (SQLite, no Docker)" 28-byte MAIDAN_SESSION_SECRET (needs ≥32 → didn't boot); event_stream replay now logs a failed delivery-cursor advance instead of let _ =. Key correction: the sweep's headline "live authz defect" (legacy /members/:id/mentions+/inbox not self-only) was a false positive on verification — those routes are bearer-only (auth::middleware rejects sessions → 401), no /ui/api mount, and bearers are act-as-any by design; kept ensure_acting_member as a no-op defensive guard (future-proofs a /ui/api mount) + a test documenting the 401 truth. Deferred to its own cluster: outbox FOR UPDATE SKIP LOCKED (a naive fix is a no-op; needs a lease column or held-tx + a multi-replica test). Next: 316 honesty scrub + no-clone image, 317 Bet 2 snippet pack, 318 token-pack evidence, then the fidelity/context flagship arc.

316.0 (v316.0.0) honesty scrub + honest prebuilt-image path — corrected every verified stale/false doc at v315: the Claims.md A2A-gRPC overclaim (gRPC = task read/cancel/list only, no SendMessage), mail.rs/server.rs/Framework Integrations/Threat-Model/sdk/README/Clients/Client Testing/Promotion/AGENTS/Integration/CLAUDE(latest-tag)/SECURITY, and the README "experimental A2A bridge" understatement. Fixed two more won't-boot commands (the class 314/315 kept finding): book/src/introduction.md's cargo run (no MAIDAN_SESSION_SECRET) and Pi.md's docker run -e AUTH_DISABLED=1 (missing the MAIDAN_ALLOW_INSECURE_NO_AUTH ack; :latest→pinned; native path → maidan init). No-clone image: the smoke reshaped it — the published ghcr.io/…/maidan-server:v315.0.0 boots with auth on + is signed/multi-arch, but it's distroless with no bundled maidan CLI, so the planned "docker run … then exec maidan init" is impossible; added an honest "Prebuilt image (no clone)" README note (seed via maidan init against your DB) and deferred a true one-command no-clone eval (needs the quickstart image on GHCR) to its own cluster. Published the stuck v300.0.0 release draft; documented (didn't cut) the missing v311 tag. Next: 317 Bet 2 snippet pack, 318 token-pack evidence, then the fidelity/context flagship arc.

317.0 (v317.0.0) Bet 2 MCP snippet pack + the two-language lease demo — the falsifiable hello-world: a Python SDK worker and a TypeScript SDK worker both claim_next_thread on one channel; Maidan hands each open task to exactly one (no cross-language double-claim; drained queue → null; no LLM), verified end-to-end via scripts/lease-demo.sh (examples/lease_demo/). Rewrote the LangChain + AutoGen examples to filter the catalog to the six-tool hero loop (claim_next_thread/post_message/get_thread_context/set_thread_result/wait_for_result/wait_for_ready) — client-side filter only, the catalog stays 78 server-side and the pi 8-method seam stays callable. Added Cursor/Claude MCP client configs (/mcp/streamable, bearer, 2026-07-28), reworked examples/README.md + Framework Integrations.md around the hero pack, and guarded the new scripts/configs in CI. Next: 318 token-pack evidence, then the fidelity/context flagship arc (where the no-backwards-compat directive — rename Reference.relation free-string → a controlled type, etc. — applies in full).

318.0 (v318.0.0) token-pack evidence — a number for the README's "far fewer tokens" claim (assertion-only until now). token_pack (#[ignore]d harness + pure estimator unit-tested in CI, the load_baseline pattern) measures the scoped context pack vs dumping every message in the channel: ~6.8× fewer tokens (in-process SQLite, 8 threads × 40 msgs; scoped pack ~4 951 vs naive ~33 908 tokens), plus ~1.3× from lean edits. Bytes are exact (serialized JSON = what the agent receives); tokens are ≈chars/4 and the ratio is tokenizer-independent. Benchmark.md gained a "Context-pack token savings" section (method + numbers + reproduce); Claims.md's token row → "Shipped + measured" with the evidence link. This closes the launch-prep leg of the 2026-08-28 sweep (315 correctness → 316 honesty scrub → 317 snippet pack → 318 token evidence). Next: the fidelity + context flagship arc (typed relations → glossary → confidence → as-of replay → seed → snapshot artifact), then the public launch (gated on the maintainer's go).

319.0 (v319.0.0) fidelity + context flagship arc — cluster 1: typed reference relations (the keystone). Reference.relation is now a controlled RelationKind (supports/refutes/defines/depends/duplicates/grounds/supersedes + Other(String) escape) instead of a free string — the same subject→predicate→object shape as IBIS/W3C-PROV/ClaimReview/GitHub-Linear relations, turning the reference graph into a machine-navigable argument/provenance graph. It serializes as the bare snake_case string, so REST/MCP/event/export payloads are byte-identical (type-safety + canonicalization, not a wire break); both store backends bind as_str() / parse from_wire, the maidan_references column stays TEXT (no migration), and the ReferenceAdded event carries it automatically. REST CreateReference + MCP add_reference inputs are typed (unknown → Other); OpenAPI/MCP schemas still declare relation as string (accurate — no contract/bijection change). No backwards-compat shim (pre-launch, per David's directive). Next: 320 reverse-edge + by-type reference queries (list_references_to — "what refutes X" — the traversal payoff), then glossary → confidence/conventions → as-of context replay → seed-from-message → context snapshot artifact.

320.0 (v320.0.0) flagship arc — cluster 2: reverse-edge + by-type reference queries. The traversal payoff for 319's typed relations: Store::list_references_to (the reverse edge — "what references this" — reusing the existing idx_references_dst index, no migration), GET /references reshaped to query FROM a source or TO a target + an optional relation filter (exactly one pair, anchor-gated, same route + cap so no new-route preflight), and a new MCP list_references tool (MCP could add_reference but had no way to list references at all). The reference graph from 319 is now navigable in both directions and by relation type. Next: shared glossary / definitions layer (the defines edge's target; the anti-drift pin), then confidence/conventions → as-of context replay → seed-from-message → context snapshot artifact.

321.0 (v321.0.0) flagship arc — cluster 3: shared glossary foundation. A workspace's canonical term -> definition (+ aliases) so agents use words the same way — the anti-drift pin and the target of 319's defines relation. maidan_glossary_terms (pg 0053 / sqlite 0052, UNIQUE(workspace_id, term), aliases as JSONB / TEXT-JSON), GlossaryTerm/NewGlossaryTerm models, and Store::{set,get,list,delete}_glossary_term (both backends; set upserts, preserving authorship + bumping updated_at). Flat by design — hierarchy is a knowledge-graph product line, out of scope (locked anti-goal). Zero-blast-radius store foundation (the 159 / 217 / 234 pattern) — no routes/tools yet. Next: 322 surfaces the glossary over REST + MCP + folds it into the context pack, then confidence/conventions → as-of context replay → seed-from-message → context snapshot artifact.

322.0 (v322.0.0) flagship arc — cluster 4: glossary REST + MCP. The 321 glossary foundation, surfaced over both wire surfaces: REST PUT/GET/DELETE /workspaces/:wid/glossary/:term + GET /workspaces/:wid/glossary (list), and MCP set_glossary_term/get_glossary_term/list_glossary_terms. Agents can define, look up, and list a workspace's canonical term -> definition. set upserts (workspace:write, created_by = acting member); reads are workspace:read; delete stays REST-only (the 220/229 precedent). Full new-route/tool preflight (OpenAPI paths + schemas, capability-map, matrix {term} + PUT body clause, both sorted MCP contracts). Next: 323 folds the glossary into the context pack (a thread's GET …/context carries the workspace's definitions), then confidence/conventions → as-of context replay → seed-from-message → context snapshot artifact.

323.0 (v323.0.0) flagship arc — cluster 5: glossary in the context pack. The grounding payoff: GET /threads/:id/context + GET /workspaces/:wid/context (REST) and the get_thread_context/get_workspace_context MCP tools now carry a glossary field, so an agent's context is grounded in shared vocabulary without a second call. New include_glossary param (default true; skip_serializing_if empty → byte-neutral when no glossary; opt out for a token-tight pack); the workspace pack carries it once at the top (build_workspace_context dedups — not repeated per nested thread). One constant query per pack, so the query-count independence invariant holds. The glossary layer (321 store → 322 REST/MCP → 323 context fold) is complete. Next: optional confidence + near-zero-code conventions (a decision-record shape over thread_results + the supersedes edge; an ack grounding act), then as-of context replay → seed-from-message → context snapshot artifact.

324.0 (v324.0.0) flagship arc — cluster 6: optional vote confidence. An optional confidence weight (0..1) on a vote for weighted consensus. maidan_votes.confidence (pg 0054 / sqlite 0053, nullable); Vote/NewVote gain confidence: Option<f64> (omitted when absent); REST POST/GET /messages/:id/votes + MCP cast_vote; range validated at the API edge (400/InvalidParams). Re-casting the same (message, member, kind) upserts the confidence (count idempotent — ON CONFLICT DO UPDATE). First slice of the arc's "confidence + conventions" item. Next: near-zero-code conventions — a decision-record shape over thread_results + the supersedes edge, an ack grounding act — then as-of context replay → seed-from-message → context snapshot artifact.

325.0 (v325.0.0) flagship arc — cluster 7: agent conventions (decisions, supersession, grounding acks). The "near-zero-code conventions" half of the confidence-and-conventions item — codified as docs + a convention-proving e2e with zero new server code ("a room, not a brain"). docs/Integration.md "Agent conventions" documents: decision records (ADR-shaped thread_result JSON), supersession (a supersedes reference edge + status flip; the reverse relation-filtered query answers "what replaced this?"), and grounding acks (an ack vote grounding a message as of its created_at, detectably stale once edited later). decision_convention_e2e proves the trio over the real HTTP API. Next: the net-new context lane — as-of context replay (GET /threads/:id/context?as_of=<event_id> + MCP twin, deterministic over the immutable log), then seed-from-message → immutable context snapshot artifact.

326.0 (v326.0.0) flagship arc — cluster 8: as-of context replay. GET /threads/:id/context?as_of=<event_id> (+ MCP get_thread_context as_of arg) reconstructs a thread as it stood at that event-log id — deterministic over the immutable log, no fresh search. Because MessagePosted/MessageEdited carry the full Message, a since-edited message shows its as-of body and a since-tombstoned message reappears (impossible from current rows). Store::list_thread_events_through (both backends) + shared maidan_types::reconstruct_messages_through; additive components cut by the anchor's time; glossary omitted; unknown id → 404. Serves audit + re-ask-from-before-a-tangent. Next: seed-from-message (the write side of re-ask — POST /messages/{id}/seed, a seeded_from typed edge, prefix mode delegating to this replay), then immutable context snapshot artifact → flow template.

327.0 (v327.0.0) flagship arc — cluster 9: seed-from-message (REST). The write side of "re-ask": POST /messages/:id/seed spawns a titled, claimable child thread from a source message, linked by a seeded_from reference edge (new thread → source). inclusion: pointer (default, edge only) or quote (first message quotes the source). Source untouched; N seeds per source; gated workspace:write + source read + target-channel write. Reuses existing primitives — no bespoke table, no new event kind (emits ThreadCreated + ReferenceAdded); lineage queryable via the 320 reverse reference query. New RelationKind::SeededFrom (controlled vocab → 8). Next: 328 the MCP seed_from_message tool, then the last arc items — immutable context snapshot artifact → flow template (+ optional pack/prefix inclusion + a WorkSeeded signal).

328.0 (v328.0.0) flagship arc — cluster 10: seed-from-message MCP tool. The twin of the 327 REST route: MCP seed_from_message ({message_id, title, inclusion?, channel_id?}) spawns a titled child thread + a seeded_from reference edge (+ a quoting first message for inclusion=quote). workspace:write; source access via the pre-dispatch gate, target channel checked in-handler; uses *_with_event store methods + a bus-notify of the returned event (atomic log + real-time parity — the first MCP thread-creating tool). Both contracts → 83 tools. Seed-from-message is now complete over REST + MCP (pointer + quote). Next: the last flagship items — immutable context snapshot artifact → flow template (+ optional pack/prefix inclusion + a WorkSeeded signal).

329.0 (v329.0.0) flagship arc — cluster 11: immutable context snapshot artifact. POST /threads/:id/context/snapshot freezes the assembled context pack (live or as_of) into the content-addressed artifact store — a tamper-evident, deduped record of exactly what the agent was handed. Returns the Artifact (kind=context_snapshot, application/json); fetchable at GET /artifacts/:sha; gated artifact:upload + thread access. New ArtifactKind::ContextSnapshot + migration pg 0055 / sqlite 0054 widening the artifact-kind CHECK. Reuses the artifact store wholesale. Remaining arc tail (all optional): MCP snapshot tool, seed pack inclusion (attach a snapshot sha), a WorkSeeded signal, and item 7 flow/setup template (likely declined as covered by export/import). After that the flagship arc is complete — a good point to open a research round.

330.0 (v330.0.0) flagship arc — cluster 12: context snapshot MCP tool. MCP snapshot_thread_context — the twin of the 329 REST route: freeze the assembled context pack (live or as_of) into the content-addressed artifact store, returning the Artifact (kind=context_snapshot). artifact:upload; reuses context::get_thread_context + the modern upsert_artifact_with_event + Cluster-204 ref + bus-notify (an MCP-frozen snapshot is fetchable by its workspace, unlike the older MCP artifact tools). Both contracts → 84 tools. Context snapshot is now complete over REST + MCP. Next: 331 closes the flagship arc with an explicit decision on the optional tail (seed pack/prefix inclusion, WorkSeeded, flow template) — reaching a clean point to open a research round.

331.0 (v331.0.0) flagship arc — cluster 13 (closeout). Docs-only decision cluster: a "Product scope" ADR in docs/Decisions.md records the fidelity + context flagship arc complete (319–331) and declines its optional tail (seed pack/prefix inclusion, a WorkSeeded event, the flow/setup template) as composable from shipped primitives — declined, not deferred, with revisit conditions. Open Work / Roadmap marked complete. The flagship arc is done — a clean point to open a research round. The public launch remains gated on the maintainer's go.

332.0 (v332.0.0) post-flagship audit program — cluster 1: MCP artifact tenant isolation (P0.1). The one P0 from the 2026-08-30 full-repo audit: the MCP artifact tools bypassed the Cluster-204 per-workspace isolation the REST path enforces. get_artifact_metadata + the maidan://artifacts/{sha} resource read now gate on artifact_ref_existsNotFound (no cross-tenant oracle); MCP uploads record the per-workspace ref; resources::read uses size_bytes instead of loading the blob. e2e mcp_artifact_tools_enforce_tenant_isolation. Next (audit program): P1.1 MCP write-path event/atomicity parity (edit_message first — it silently breaks the flagship as-of replay + embedding reindex), then P1.2 unify context assembler → P1.3 whoami/initialize → P1.4 post-path → P1.5 egress tests + LSN CI → P2 docs/polish.

333.0 (v333.0.0) post-flagship audit program — cluster 2: MCP edit_message emits MessageEdited (P1.1a). The sharpest verified correctness bug: MCP edit_message was event-less, so an MCP edit appended no MessageEdited → the flagship as-of replay returned the stale body forever and the embedding indexer never reindexed (stale semantic search). Now edit_message_with_event + a new McpServer::publish_stored bus-notify → replay/reindex/realtime all see MCP edits like REST. e2e mcp_edit_message_appends_messageedited_event. Next: 334 (P1.1b) migrate the remaining event-less MCP write tools (votes/reactions/pins/mention/reference) + publish MentionRecorded from MCP posts, then P1.2 unify context assembler → P1.3 whoami/initialize → P1.4 post-path → P1.5 tests.

334.0 (v334.0.0) post-flagship audit program — cluster 3: MCP write-path event parity, the rest (P1.1b). The 7 remaining event-less MCP write tools now emit domain events via McpServer::publish_stored: cast_vote/add_reaction/remove_reaction/pin_message/unpin_message/add_reference (*_with_event), record_mention (record_mention_with_event), and MCP post_message/post_dm_message publish MentionRecorded per @mentioned member (recorded but never published before). MCP mutations now reach WS/SSE, at-least-once, federation, and the notification router / wait_for_mention like REST. P1.1 (MCP write-path parity) is complete (333 edit + 334 rest). Next: P1.2 unify the REST↔MCP context assembler (MCP has an N+1 + omits artifacts) → P1.3 whoami/initialize → P1.4 post-path → P1.5 egress tests + LSN CI → P2 docs/polish.

335.0 (v335.0.0) post-flagship audit program — cluster 4: MCP context batch reads + artifacts (P1.2). The MCP context assembler had a per-message N+1 and omitted artifacts (REST batched + included them). Now get_thread_context/get_thread_context_as_of use batched shared helpers (collect_references/collect_edit_views/collect_artifacts) + surface an artifacts array, matching REST; sha extractor shared via maidan_types::artifact_shas_from_metadata. REST unchanged (query-count guard green). Full cross-crate assembler hoist deferred with rationale (maidan-router ThreadContext name collision + utoipa/futures plumbing; maintainability-only). Next: P1.3 whoami + initialize instructions (cheapest adoption unlock) → P1.4 post-path → P1.5 egress tests + LSN CI → P2 docs/polish.

336.0 (v336.0.0) post-flagship audit program — cluster 5: agent cold-start whoami + initialize instructions (P1.3). The cheapest adoption unlock: an agent with only a base URL + token couldn't run the hero loop (every hero-loop tool needs its own member_id; MCP initialize had no instructions). New MCP whoami tool → {member_id, workspace_id, capabilities, is_bearer, bypass} from auth; initialize.instructions cold-start guide; AuthContext::capabilities() accessor. 85 tools. Next: 337 REST GET /me twin, then P1.4 post-path round-trips → P1.5 egress tests + LSN CI → P2 docs/polish.

337.0 (v337.0.0) post-flagship audit program — cluster 6: REST GET /me identity endpoint (P1.3). The HTTP twin of 336's MCP whoami, closing agent self-discovery on REST: GET /me{member_id, workspace_id, capabilities, is_bearer} reflected from the request's auth (no store access), so an agent or /ui session with only a base URL + token can discover the member_id every member-attributed write requires. workspace:read; full new-route preflight (OpenAPI + WhoAmI schema + capability-map). Audit P1.3 (agent cold-start) now complete across both transports. Next: P1.4 post-path round-trip reduction → P1.5 egress wire tests + LSN replica CI → P2 docs/polish.

338.0 (v338.0.0) post-flagship audit program — cluster 7: post-path mention-routing round-trip reduction (P1.4a). Every message post re-ran resolve_message_chain (message→thread→channel→workspace) inside mention routing just to re-derive a workspace id the caller already had — even for posts with no @handles. publish_routed_mentions (REST + MCP) now short-circuits on an empty parse_at_handles (no store work for a plain post) and otherwise routes via route_mentions_in_message with the known workspace; removed the now-unused route_mentions_for_message. Behaviour-preserving. Next: 339 (P1.4b) the systemic thread+channel double-fetch (fetch-once authorize_thread), then P1.5 egress tests + LSN CI → P2 docs/polish.

339.0 (v339.0.0) post-flagship audit program — cluster 8: fetch-once thread authorization (P1.4b). ~30 thread-scoped handlers called resolve_thread_context (get_thread + get_channel) then ensure_thread_access (the same two fetches again) + a redundant ensure_workspace. New maidan_auth::authorize_thread resolves ThreadScope {workspace_id, channel_id, thread_id} and authorizes in one fetch; ensure_thread_access delegates to it (rule single-sourced, also sheds its own duplicate get_channel). Handlers that use the scope call authorize_thread; the rest keep only ensure_thread_access. Behaviour-identical (404/403 with the same messages); per-request thread+channel fetches halve. Next: 340 (P1.4c, optional) the message-keyed twin authorize_message, then P1.5 egress tests + LSN CI → P2 docs/polish.

340.0 (v340.0.0) post-flagship audit program — cluster 9: fetch-once message authorization (P1.4c). The message-keyed twin of 339, completing audit P1.4. ~12 handlers in message.rs/social.rs called resolve_message_chain then an access helper that resolved the same chain again + a redundant ensure_workspace. New maidan_auth::authorize_message resolves MessageScope {workspace_id, channel_id, thread_id, message_id} and authorizes in one pass; ensure_message_access delegates to it. Handlers using the scope (edit/tombstone/purge/seed) call authorize_message; the rest keep ensure_message_access. Message-scoped fetches drop ~5→3, behaviour-identical. Audit P1.4 (post-path round-trip reduction) is complete (338 mentions + 339 thread-keyed + 340 message-keyed). Next: P1.5 egress wire tests + LSN replica CI → P2 docs/polish.

341.0 (v341.0.0) post-flagship audit program — cluster 10: docs accuracy reconciliation (P2). Ground-truth-verified fixes: A2A gRPC reconciled to "partial" across Architecture.md (implied full parity) + Protocols.md ("No gRPC binding" — wrong) to match Claims.md — the gRPC A2AService is get_task/cancel_task/list_tasks only (send/push/streaming stay JSON-RPC/REST); tool-count 78 → 85 in the live integrator docs; dead GitHub link Capability-Map.mdCapability-Map.md; README image pin v315v339. Docs-only. Next: P1.5 egress wire tests + LSN replica CI, then remaining P2 code-side items.

342.0 (v342.0.0) post-flagship audit program — cluster 11: surface flagship context features to integrators (P2). Integration.md documented the context pack but omitted the differentiators; a new "Fidelity & context" subsection covers glossary grounding, as-of replay (time travel), context snapshots, lean edits, seed/re-ask, and the tool-call transcript — exact wire surface + MCP-tool parity, all verified against code. Folded a Cluster-341 miss (Protocols.md "78" → 85 tools). Docs-only. Next: P1.5 egress wire tests + LSN replica CI, then remaining P2 code-side items.

343.0 (v343.0.0) post-flagship audit program — cluster 12: keyset-paginate the channel thread list (P2). The last unpaginated list: GET /channels/:cid/threads + MCP list_threads called unbounded Store::list_threads. New page_threads_for_channel(channel_id, after, limit) (both backends; keyset (created_at, id) ASC, exclusive cursor, LIMIT in SQL) backs limit (default 100, clamp 1..=500) + cursor on the REST route + MCP tool; Postgres routes it via the read replica. Unbounded list_threads kept for internal full-list callers. Next: P1.5 egress wire tests + LSN replica CI, then remaining P2 code-side items.

344.0 (v344.0.0) post-flagship audit program — cluster 13: bounded-concurrency notification fan-out (P2). The notification router is a serial bus consumer; a MessagePosted fanned out to followers in a sequential loop (2 × followers store round-trips), head-of-line-blocking the pipeline on a widely-followed message. Per-recipient writes now run with bounded concurrency (buffer_unordered, cap 8 — the Cluster-199 pattern). Behaviour-preserved. Batch insert logged as a further optimization. Next: P1.5 egress wire tests + LSN replica CI, then remaining P2 code-side items (notification batch insert, projector link-management, Store trait split, MCP post_message slash-dispatch decision).

345.0 (v345.0.0) post-flagship audit program — cluster 14: MCP post_message slash-command parity (P2). MCP post_message ignored registered slash commands while REST ran them (user chose parity over documenting the difference). A dependency-inverted maidan_mcp::SlashDispatcher trait — implemented by maidan-server and attached to the McpServer at startup (set_slash_dispatcher, server-binary only) — lets the MCP post path run slash dispatch when a command is registered, merging the same {slash_command, slash_response} metadata as REST. The MCP no-slash post was also upgraded to the atomic outbox path. Next: P1.5 egress wire tests + LSN replica CI, then remaining P2 code-side items (notification batch insert, projector link-management, Store trait split).

346.0 (v346.0.0) post-flagship audit program — cluster 15: projector link-management REST surface (P2). The Slack/GitHub projectors shipped ingress + egress + a store link table, but no route ever created a link, so the egress could never fire (a launch feature that couldn't be turned on). New POST/GET/DELETE /workspaces/:wid/slack-links + …/github-links surface; the link's channel_id/workspace_id are derived from authorize_thread (can't disagree with the thread). POST/DELETE=workspace:write, GET=workspace:read; full new-route preflight; projector_links_e2e proves the created link is what the egress reverse-lookup reads. Next: P1.5 egress wire tests + LSN replica CI, then remaining P2 code-side items (notification batch insert, Store trait split).

347.0 (v347.0.0) post-flagship audit program — cluster 16: projector egress wire-path tests (P1.5). The production HTTP clients that build the actual projector-egress request (SlackWebClient, GithubApiClient) had no test (the egress tests drive mock sender traits). Added a with_base_url constructor to each (production new targets the real host), and egress_wire_e2e drives the real clients against a loopback recorder asserting the exact URL/headers/body + success/error decoding. Next: the LSN-replica CI job (P1.5 second half), then P2 code-side (notification batch insert, Store trait split).

348.0 (v348.0.0) post-flagship audit program — cluster 17: batch the notification fan-out mute check (P2). The follow-up to Cluster 344: a MessagePosted fan-out still ran one is_notification_muted query per follower. New Store::filter_muted_members(kind, &[MemberId]) (SQLite dynamic IN, Postgres = ANY) resolves the muted subset in one query; the fan-out batch-fetches it, meters the suppressed, and writes only the unmuted (concurrently, per 344). Cuts 2 × followers toward followers + 1 round-trips. The multi-row batch INSERT (collapsing the writes too) is a logged further optimization. Next: notification multi-row INSERT + LSN-replica CI job; Store trait split deferred (large, low external value).

Integrators: use Integration.md — not this roadmap.

Recently closed: Cluster 234.0 — Program B (Arc F): structured-results foundation (thread_results table + model + store set/get, both backends; zero-blast-radius, no routes); Program B part 18, at v234.0.0 (Retros/Cluster 234.0).

Recently closed: Cluster 233.0 — Program B (Arc E complete): capability-registry MCP tools (member-skill + thread-required-skill declare/list; MCP twin of 232); Program B part 17, at v233.0.0 (Retros/Cluster 233.0).

Recently closed: Cluster 232.0 — Program B (Arc E): capability-registry REST (member-skill + thread-required-skill CRUD; workspace:write/thread:transition writes); Program B part 16, at v232.0.0 (Retros/Cluster 232.0).

Recently closed: Cluster 231.0 — Program B (Arc E): skill-aware claim (thread_required_skills + claim_next skips tasks whose required skills the claimer lacks, both backends; existing claim route/tool inherit it); Program B part 15, at v231.0.0 (Retros/Cluster 231.0).

Recently closed: Cluster 230.0 — Program B (Arc E): capability-registry foundation (member_skills table + model + store add/remove/list, both backends; zero-blast-radius, no routes); Program B part 14, at v230.0.0 (Retros/Cluster 230.0).

Recently closed: Cluster 229.0 — Program B: task-schedule MCP tools (create_task_schedule, list_task_schedules; scheduler subsystem complete over REST + MCP); Program B part 13, at v229.0.0 (Retros/Cluster 229.0).

Recently closed: Cluster 228.0 — Program B: task-schedule REST management API (create/list/pause-resume/delete; workspace:write + target-channel access; set_task_schedule_active); Program B part 12, at v228.0.0 (Retros/Cluster 228.0).

Recently closed: Cluster 227.0 — Program B: scheduler sweeper worker (opt-in background loop; atomic claim-and-advance, multi-replica safe; fires a task thread per due schedule); Program B part 11, at v227.0.0 (Retros/Cluster 227.0).

Recently closed: Cluster 226.0 — Program B: scheduled/recurring task foundation (task_schedules table + model + store CRUD/due-scan, both backends; zero-blast-radius, no worker/routes); Program B part 10, at v226.0.0 (Retros/Cluster 226.0).

Recently closed: Cluster 225.0 — Program B: get_queue_depth MCP tool (the MCP twin of the 224 REST endpoint; shared channel_queue_depth); Program B part 9, at v225.0.0 (Retros/Cluster 225.0).

Recently closed: Cluster 224.0 — Program B: channel task-queue depth (GET /channels/:cid/queue-depth → ready/assigned/blocked partition of open task threads; one aggregate query, both backends); Program B part 8, at v224.0.0 (Retros/Cluster 224.0).

Recently closed: Cluster 223.0 — Program B: wait_for_ready MCP long-poll (blocks until a task becomes claimable, the wait_for_mention analogue for the DAG; optional channel scope, RBAC-filtered); Program B part 7, at v223.0.0 (Retros/Cluster 223.0).

Recently closed: Cluster 222.0 — Program B: reactive task readiness (ThreadReady event on dependency-unblock + newly_ready_dependents query, both backends; non-federatable); Program B part 6, at v222.0.0 (Retros/Cluster 222.0).

Recently closed: Cluster 221.0 — Program B: task-DAG transitive cycle prevention (add_thread_dependency rejects direct + transitive cycles via a recursive-CTE reachability check, both backends); Program B part 5, at v221.0.0 (Retros/Cluster 221.0).

Recently closed: Cluster 220.0 — Program B: task-dependency DAG MCP tools (add_thread_dependency, list_thread_dependencies; both-thread RBAC; completes the DAG surface over REST + MCP); Program B part 4, at v220.0.0 (Retros/Cluster 220.0).

Recently closed: Cluster 219.0 — Program B: task-dependency DAG management REST API (add/list+ready/remove edges, dependents; both-thread RBAC + same-workspace; full new-route preflight); Program B part 3, at v219.0.0 (Retros/Cluster 219.0).

Recently closed: Cluster 218.0 — Program B: readiness-aware claim_next (a NOT EXISTS clause skips tasks with non-terminal deps; existing claim-next route + MCP tool become DAG-aware, no new API); Program B part 2, at v218.0.0 (Retros/Cluster 218.0).

Recently closed: Cluster 217.0 — Program B opens: task-dependency DAG store foundation (maidan_thread_dependencies edges + store; readiness = all deps terminal; zero-blast-radius, no routes yet); Program B part 1, at v217.0.0 (Retros/Cluster 217.0).

Recently closed: Cluster 216.0 — security: RLS spike resolved as a decision ADR (Postgres Row-Level Security assessed + deferred; app-layer RBAC authoritative) — concludes Program A (202–216); Program A part 15, at v216.0.0 (Retros/Cluster 216.0).

Recently closed: Cluster 215.0 — security: federation ingest trust policy (EventKind::federatable() allowlist enforced at ingest, ArtifactUpserted excluded; MemberJoined nested-workspace remap leak fixed); Program A part 14, at v215.0.0 (Retros/Cluster 215.0).

Recently closed: Cluster 214.0 — correctness: transactional-outbox migration for references + artifacts (add_reference_with_event; upsert_artifact_with_event folds upsert + 204 ref + event in one tx) — completes the domain-mutation migration; Program A part 13, at v214.0.0 (Retros/Cluster 214.0).

Recently closed: Cluster 213.0 — correctness: transactional-outbox migration for the A2A ingest post (reuses post_message_with_event) + member/workspace creation (create_member_with_event/create_workspace_with_event); Program A part 12, at v213.0.0 (Retros/Cluster 213.0).

Recently closed: Cluster 212.0 — correctness: transactional-outbox migration for message edit + tombstone (edit_message_with_event/tombstone_message_with_event; shared edit_in_tx; message.rs now publish()-free); Program A part 11, at v212.0.0 (Retros/Cluster 212.0).

Recently closed: Cluster 211.0 — correctness: transactional-outbox migration for the regular message post (route branch: no-slash → post_message_with_event; slash → edit_message_with_posted_event); Program A part 10, at v211.0.0 (Retros/Cluster 211.0).

Recently closed: Cluster 210.0 — correctness: transactional-outbox migration for DM/group-DM posts (post_message_with_event(new, dm_conversation_id)); Program A part 9, at v210.0.0 (Retros/Cluster 210.0).

Recently closed: Cluster 209.0 — correctness: transactional-outbox migration for thread assignments (assign/unassign/claim/claim_next *_with_event; previous-assignee captured in-tx; claim/claim_next conditional; publish_assignment removed); Program A part 8, at v209.0.0 (Retros/Cluster 209.0).

Recently closed: Cluster 208.0 — correctness: transactional-outbox migration for thread FSM transitions (transition_thread_with_event + new thread_scope_in_tx resolver; FSM step extracted into a shared transition_in_tx core); Program A part 7, at v208.0.0 (Retros/Cluster 208.0).

Recently closed: Cluster 207.0 — correctness: transactional-outbox migration for pins + mentions (pin_message/unpin_message/record_mention *_with_event; unpin conditional; pins reuse the resolver's channel); Program A part 6, at v207.0.0 (Retros/Cluster 207.0).

Recently closed: Cluster 206.0 — correctness: transactional-outbox migration for votes + reactions (*_with_event + shared message_scope_in_tx; conditional event on remove); Program A part 5, at v206.0.0 (Retros/Cluster 206.0).

Recently closed: Cluster 205.0 — correctness: transactional-outbox foundation (events::append_in_tx + create_{channel,thread}_with_event — atomic domain-write + event-append in one tx; multi-cluster refactor begins); Program A part 4, at v205.0.0 (Retros/Cluster 205.0).

Recently closed: Cluster 204.0 — security: cross-tenant artifact isolation (maidan_artifact_refs per-workspace access link; get_artifact* requires the caller's ref → 404; dedup preserved); Program A part 3, at v204.0.0 (Retros/Cluster 204.0).

Recently closed: Cluster 203.0 — security: DM/group-DM participation on subscribe (expand_event_filterensure_thread_access; closes a DM live-tail leak via dm_conversation_id/thread_id) + metadata reads (session-participant / self-only list); Program A part 2, at v203.0.0 (Retros/Cluster 203.0).

Recently closed: Cluster 202.0 — security: session-bound acting identity (ensure_acting_member — a session caller may only act as its own member, applied to every member-attributed write; closes a session-impersonation vuln); new arc — Program A (security round 2) part 1, at v202.0.0 (Retros/Cluster 202.0).

Recently closed: Cluster 201.0 — perf: workspace-sharded event fan-out (ShardedBroadcast routes a publish to the workspace shard + global shard, O(relevant) not O(all); behavior unchanged under the existing filter); Arc D part 4, at v201.0.0 (Retros/Cluster 201.0).

Recently closed: Cluster 200.0 — perf + security: filtered-ANN search (RBAC private-channel deny pushed into the query, SQLite NOT IN / Postgres <> ALL; honors limit, no leak; post-filter stays authoritative for DMs); Arc D part 3, at v200.0.0 (Retros/Cluster 200.0).

Recently closed: Cluster 199.0 — perf: concurrent workspace-context assembly (build_workspace_context builds page threads via a bounded buffered stream, cap 8, order/query-count/error semantics unchanged); Arc D part 2, at v199.0.0 (Retros/Cluster 199.0).

Recently closed: Cluster 198.0 — perf: load / soak harness (scripts/loadgen.sh + #[ignore]d load_baseline; concurrent REST load → latency percentiles + throughput; pure percentile math unit-tested); Arc D part 1 (the baseline), at v198.0.0 (Retros/Cluster 198.0).

Recently closed: Cluster 197.0 — agentic: tool-call transcripts (tool_transcript pairs ToolUse/ToolResult by id → token-lean ToolTranscript; REST GET /threads/:id/tool-transcript + MCP get_tool_transcript); Arc C part 8 — Arc C COMPLETE, at v197.0.0 (Retros/Cluster 197.0).

Recently closed: Cluster 196.0 — agentic: wait_for_mention blocking MCP long-poll (subscribe to the member's MentionRecorded events, block until one arrives or timeout_ms lapses; live-only + RBAC-filtered); Arc C part 7, at v196.0.0 (Retros/Cluster 196.0).

Recently closed: Cluster 195.0 — agentic: handoff notes on thread assignment (optional note on assign_thread, rides the ThreadAssignmentChanged event; event-only); Arc C part 6, at v195.0.0 (Retros/Cluster 195.0).

Recently closed: Cluster 194.0 — agentic: A2A ingest preserves parts as structured content (maps text parts to ContentBlock::Text, was content: None); Arc C part 5, at v194.0.0 (Retros/Cluster 194.0).

Recently closed: Cluster 178.0 — token: opt-in lean event frames (lean subscribe flag → {log_id, kind, ...ids} pointers); arc 4 (round 3) part 4 — token round 3 + the four-arc program complete, at v178.0.0 (Retros/Cluster 178.0).

Recently closed: Cluster 177.0 — token: omit empty Message.metadata from the wire; arc 4 (round 3) part 3, at v177.0.0 (Retros/Cluster 177.0).

Recently closed: Cluster 176.0 — token: capability-filtered tools/list (caller sees only invokable tools); arc 4 (round 3) part 2, at v176.0.0 (Retros/Cluster 176.0).

Recently closed: Cluster 175.0 — token: MCP search_messages snippet_only parity (drop bodies); arc 4 (round 3) part 1, at v175.0.0 (Retros/Cluster 175.0).

Recently closed: Cluster 174.0 — agentic: HITL approvals (request_approval MCP tool via server→client elicitation/create); arc 3 part 4 — arc 3 complete, at v174.0.0 (Retros/Cluster 174.0).

Recently closed: Cluster 173.0 — agentic: structured message content (typed content blocks on messages, REST + MCP, both backends; body derived); arc 3 part 3, at v173.0.0 (Retros/Cluster 173.0).

Recently closed: Cluster 172.0 — agentic: MCP structured backpressure (rate-limited /mcp → JSON-RPC -32029 + retry_after_ms); arc 3 part 2, at v172.0.0 (Retros/Cluster 172.0).

Recently closed: Cluster 171.0 — agentic: thread task assignment / handoff (assignee_id axis; assign/claim/unassign over REST+MCP; atomic claim; ThreadAssignmentChanged event); arc 3 part 1, at v171.0.0 (Retros/Cluster 171.0).

Recently closed: Cluster 170.0 — CI/CD: native ubuntu-24.04-arm release build (kills the ~2 h QEMU Rust compile) + report-only trivy image scan; arc 2 part 5 — arc 2 complete, at v170.0.0 (Retros/Cluster 170.0).

Recently closed: Cluster 169.0 — perf: coalesce the optimistic-path delivery-cursor write (H2) — buffer + flush instead of a DB UPSERT per event; arc 2 part 4 (code-perf items done), at v169.0.0 (Retros/Cluster 169.0).

Recently closed: Cluster 168.0 — perf: outbox relay JOINs the payload + batch mark_published (H4) + env-tunable broadcast cap (R1) + a webhook unwrap() main-red hotfix; arc 2 part 3, at v168.0.0 (Retros/Cluster 168.0).

Recently closed: Cluster 167.0 — perf: rate-limiter map eviction (R2) + embedding model→table cache (H6); arc 2 part 2, at v167.0.0 (Retros/Cluster 167.0).

Recently closed: Cluster 166.0 — perf/correctness: per-connection SQLite pragmas (R3) + per-workspace webhook fan-out (H1); arc 2 part 1, at v166.0.0 (Retros/Cluster 166.0).

Recently closed: Cluster 165.0 — reference authorization (REST + MCP add_reference gated on entity→channel access); RBAC arc complete, at v165.0.0 (Retros/Cluster 165.0).

Recently closed: Cluster 164.0channel:admin capability + /channels/:cid/members membership API (REST+MCP); RBAC part F, at v164.0.0 (Retros/Cluster 164.0).

Recently closed: Cluster 163.0 — verified WS/MCP subscribe grants (drop asserted private grants for non-members); RBAC part E, at v163.0.0 (Retros/Cluster 163.0).

Recently closed: Cluster 162.0 — MCP aggregate-read filtering (search / list-channels / workspace-context); RBAC part D, at v162.0.0 (Retros/Cluster 162.0).

Recently closed: Cluster 161.0 — private-channel access control over MCP (pre-dispatch gate on point-access content tools + resources/read); RBAC part C, at v161.0.0 (Retros/Cluster 161.0).

Recently closed: Cluster 160.0 — private-channel access control over REST (ensure_channel_access on all content routes + search + workspace-context; creator auto-added); RBAC part B, at v160.0.0 (Retros/Cluster 160.0).

Recently closed: Cluster 159.0 — channel membership model (channel_members table + store + migration, both backends; no enforcement); RBAC part A, at v159.0.0 (Retros/Cluster 159.0).

Recently closed: Cluster 158.0 — keyless cosign signatures on the container images (server + postgres, by digest); enterprise-hardening arc part 3, at v158.0.0 (Retros/Cluster 158.0).

Recently closed: Cluster 157.0 — fail-closed AUTH_DISABLED (explicit MAIDAN_ALLOW_INSECURE_NO_AUTH ack; never in prod); enterprise-hardening arc part 2, at v157.0.0 (Retros/Cluster 157.0).

Recently closed: Cluster 156.0 — production-safety defaults (SIGTERM graceful shutdown + default 30 s statement_timeout); enterprise-hardening arc part 1, at v156.0.0 (Retros/Cluster 156.0).

Recently closed: Cluster 155.0 — sampling-backed summarize_thread (first request_client caller; session id threaded through tool dispatch); closes lane 3 + the three-lane plan, at v155.0.0 (Retros/Cluster 155.0).

Recently closed: Cluster 154.0request_client GET-stream delivery fix (per-session broadcast; server→client requests reach the canonical GET /mcp/streamable); lane 3 part 1, at v154.0.0 (Retros/Cluster 154.0).

Recently closed: Cluster 153.0 — live-updating /ui thread view (WS message/reaction/pin frames → debounced loadMessages); UI polish lane, at v153.0.0 (Retros/Cluster 153.0).

Recently closed: Cluster 152.0 — lean HTTP context pack (MessageEditView, opt-in include_edits) + snippet_only search; token-efficiency part 2 (REST parity), at v152.0.0 (Retros/Cluster 152.0).

Recently closed: Cluster 151.0 — token-efficient lean context reads (get_thread_context edits metadata-only by default, opt-in include_edits; list_messages clamped 1..=500), at v151.0.0 (Retros/Cluster 151.0).

Recently closed: Cluster 150.0 — thread/member/kind filters on GET /mcp/stream (await my mention); completes the MCP-agent-surface pair, at v150.0.0 (Retros/Cluster 150.0).

Recently closed: Cluster 149.0 — MCP inbox + mention tools (list_mentions/get_inbox/mark_inbox_read); an MCP-only agent can now discover its @mentions, at v149.0.0 (Retros/Cluster 149.0).

Recently closed: Cluster 148.0 — MCP server→client requests (sampling/roots/elicitation, capability-gated) + client-capability tracking; concludes the MCP streamable spec-completeness arc (145–148), at v148.0.0 (Retros/Cluster 148.0).

Recently closed: Cluster 147.0 — MCP streamable resumability (SSE event ids + Last-Event-ID replay; session survives a dropped POST leg); part 3 of the MCP spec-completeness arc, at v147.0.0 (Retros/Cluster 147.0).

Recently closed: Cluster 146.0GET /mcp/streamable server→client SSE + Accept content negotiation; part 2 of the MCP spec-completeness arc, at v146.0.0 (Retros/Cluster 146.0).

Recently closed: Cluster 145.0 — MCP conformance basics (initialize version negotiation, MCP-Protocol-Version header, JSON-RPC batching + notifications); first of the MCP spec-completeness arc 145–148, at v145.0.0 (Retros/Cluster 145.0).

Recently closed: Cluster 144.0 — docs dead-link gate (mdbook-linkcheck fails the build on broken internal links) + fixed 35 latent broken published links + backlog reconciliation, at v144.0.0 (Retros/Cluster 144.0).

Recently closed: Cluster 143.0 — richer message rendering in the /ui thread view (timestamps + inline slash-command results) at v143.0.0 (Retros/Cluster 143.0).

Recently closed: Cluster 142.0 — slash-command registry in the /ui console (register/list/revoke over /ui/api, new "Slash" tab; one-time secret for http handlers) at v142.0.0 (Retros/Cluster 142.0).

Recently closed: Cluster 141.0 — fixed the published mdBook site (every docs/* sidebar link 404'd; now all 21 SUMMARY pages build + serve, via a build-time staging step) at v141.0.0 (Retros/Cluster 141.0).

Recently closed: Cluster 140.0 — workspace presence roster in the /ui console (new "Presence" tab rendering the WS presence_snapshot frames; online/away controls) at v140.0.0 (Retros/Cluster 140.0).

Recently closed: Cluster 139.0 — 1:1 direct messages in the /ui console (open/list/read/post over /ui/api, new "DMs" tab; parallel to group DMs) at v139.0.0 (Retros/Cluster 139.0).

Recently closed: Cluster 138.0 — global-audit + reindex controls in the /ui "Operator" tab (bearer-gated audit; workspace/global reindex + poll), completing the operator-console arc, at v138.0.0 (Retros/Cluster 138.0).

Recently closed: Cluster 137.0 — deliveries & DLQ operator view in the /ui console (list + status/kind filter + replay over /ui/api, new "Operator" tab) at v137.0.0 (Retros/Cluster 137.0).

Recently closed: Cluster 136.0 — group DMs in the /ui console (open/list/read/post over /ui/api, new tab) at v136.0.0 (Retros/Cluster 136.0).

Recently closed: Cluster 135.0 — pin/unpin in the /ui thread view (toggle over /ui/api) at v135.0.0 (Retros/Cluster 135.0).

Recently closed: Cluster 134.0 — emoji reactions in the /ui console (chips/quick-add/toggle over /ui/api) at v134.0.0 (Retros/Cluster 134.0).

Recently closed: Cluster 133.0/ui write-path repair (4 undefined JS refs) + ui_js_contract guard, at v133.0.0 (Retros/Cluster 133.0).

Recently closed: Cluster 132.0 — global cross-workspace admin audit query API (GET /operator/audit, gated by audit:read-global) at v132.0.0 (Retros/Cluster 132.0).

Recently closed: Cluster 131.0 — delivery-unification verification-close (signing/backoff + operator API already unified; storage intentionally separate; risky migration declined) at v131.0.0 (Retros/Cluster 131.0).

Recently closed: Cluster 130.0 — test-coverage uplift (observability env-parsing pure parsers + MCP prompts integrity) at v130.0.0 (Retros/Cluster 130.0).

Recently closed: Cluster 129.0 — hardening: bounded MCP streamable buffer, outbox quarantine-failure visibility, unreachable!() → typed errors, at v129.0.0 (Retros/Cluster 129.0).

Recently closed: Cluster 128.0 — A2A delivery robustness (client timeouts; push retry/backoff + maidan_a2a_push_total; SSE error visibility) at v128.0.0 (Retros/Cluster 128.0).

Recently closed: Cluster 127.0 — backlog reconciliation (corrected ~11 phantom entries + the stale Open Work tail against code at v126) at v127.0.0 (Retros/Cluster 127.0).

Recently closed: Cluster 126.0 — MCP SSE at-least-once parity (at_least_once on /mcp/stream, reusing the reconcile loop) at v126.0.0 (Retros/Cluster 126.0).

Recently closed: Cluster 125.0 — at-least-once event delivery (opt-in at_least_once subscribe: cursor-driven reconcile over a stability horizon; closes the silent out-of-order gap) at v125.0.0 (Retros/Cluster 125.0).

Recently closed: Cluster 124.0 — CI/observability loose ends (one SLO-rule validator; promtool (alert rules) + otlp smoke promoted to required, 8 checks total) at v124.0.0 (Retros/Cluster 124.0).

Recently closed: Cluster 123.0 — OTLP end-to-end collector smoke (server pushes traces + metrics to a real OpenTelemetry Collector; CI asserts delivery) at v123.0.0 (Retros/Cluster 123.0).

Recently closed: Cluster 122.0 — execute the SLO alert rules in CI with promtool (caught + fixed a $value-rendering bug; corrected the OTLP-export status) at v122.0.0 (Retros/Cluster 122.0).

Recently closed: Cluster 121.0 — observability & contract completeness (every OpenAPI op classified in CI; SLO alerts/dashboard extended to the Cluster 116 indexer metrics) at v121.0.0, opening Phase XXIV (post-gate hardening) (Retros/Cluster 121.0).

Recently closed: Cluster 120.0 — scale product gate at v120.0.0 / maidan-scale-1.0, closing Phase XXIII and the 102+ ladder (Retros/Cluster 120.0).

Recently closed: Cluster 119.0 — dependency dedupe & currency (thiserror 2, deny.toml duplicate-major gate, edition-2024 eval) at v119.0.0, opening Phase XXIII (Retros/Cluster 119.0).

Recently closed: Cluster 118.0 — hybrid lexical+semantic relevance + eval harness at v118.0.0, closing Phase XXII (Retros/Cluster 118.0).

Recently closed: Cluster 117.0 — pluggable production provider (dimension auto-detect + boot-time model registration) at v117.0.0 (Retros/Cluster 117.0).

Recently closed: Cluster 116.0 — batch embedding pipeline (bounded backpressure + chunked backfill) at v116.0.0, opening Phase XXII (Retros/Cluster 116.0).

Recently closed: Cluster 115.0 — module split + unwrap() purge at v115.0.0, closing Phase XXI (Retros/Cluster 115.0).

Recently closed: Cluster 114.0 — coverage uplift + envelope fuzz (full-suite gate at 40%) at v114.0.0 (Retros/Cluster 114.0).

Recently closed: Cluster 113.0 — backend parity harness at v113.0.0 (Retros/Cluster 113.0).

Recently closed: Cluster 112.0 — FSM property tests at v112.0.0 (Retros/Cluster 112.0).

Recently closed: Cluster 111.0maidan-auth test suite at v111.0.0, opening Phase XXI (Retros/Cluster 111.0).

Recently closed: Cluster 110.0 — per-workspace fairness at v110.0.0, closing Phase XX (Retros/Cluster 110.0).

Recently closed: Cluster 109.0 — ANN index tuning + search bench at v109.0.0 (Retros/Cluster 109.0).

Recently closed: Cluster 108.0 — adaptive outbox relay (drain-until-empty + idle backoff + enqueue nudge) at v108.0.0 (Retros/Cluster 108.0).

Recently closed: Cluster 107.0 — configurable DB pool & timeouts at v107.0.0 (Retros/Cluster 107.0).

Recently closed: Cluster 106.0 — bulk context reads (N+1 elimination) at v106.0.0 (Retros/Cluster 106.0).

Recently closed: Cluster 105.0 — multi-replica scale-out smoke at v105.0.0, closing Phase XIX (Retros/Cluster 105.0).

Recently closed: Cluster 104.0 — durable ephemeral state (OAuth codes + reindex jobs) at v104.0.0 (Retros/Cluster 104.0).

Recently closed: Cluster 103.0 — distributed presence & roster at v103.0.0 (Retros/Cluster 103.0).

Recently closed: Cluster 102.0 — cross-replica MCP resource notifications at v102.0.0 (Retros/Cluster 102.0); first cluster of Product Ladder 102+.

Recently closed: Clusters 93.0101.0 — Operator UI v1, collaboration, operator gate e2e (Product Ladder 77+.md, retros under docs/Retros/Cluster 93.0.md101.0.md).

Recently closed: Clusters 91.092.0 — bootstrap strip + /ui channel browser at v91.0.0 / v92.0.0 (Retros/Cluster 91.0, Retros/Cluster 92.0).

Recently closed: Clusters 88.090.0 — Helm profiles, OTLP metrics, SLO alerts at v88.0.0v90.0.0 (Retros/Cluster 88.0, Retros/Cluster 89.0, Retros/Cluster 90.0).

Recently closed: Clusters 86.0 and 87.0 — per-model search param + reindex job API at v86.0.0 / v87.0.0 (Retros/Cluster 86.0, Retros/Cluster 87.0).

Recently closed: Cluster 77.0 — HTTP capability map at v77.0.0 (Clusters/Cluster 77.0).

Recently closed: Clusters 71–76 (transport depth + context + ops) at v71.0.0v76.0.0.

Recently closed: Cluster 70.0 — Vault truth pass at v70.0.0 (Retros/Cluster 70.0).

Recently closed: Cluster 69.0 — Capabilities matrix complete at v69.0.0 (Retros/Cluster 69.0).

Recently closed: Cluster 68.0 — Automation delivery guarantees at v68.0.0 (Retros/Cluster 68.0).

Recently closed: Product Ladder 59+ at v67.0.0 (Clusters/Product Ladder 59+, Agent Integration).

Recently closed: Cluster 67.0 — Workspace context packages at v67.0.0.

Recently closed: Cluster 58.0 — Maidan 2.0 completion gate at v58.0.0 (Retros/Cluster 58.0).

Recently closed: Cluster 57.0 — Agent app model at v57.0.0 (Retros/Cluster 57.0).

Recently closed: Cluster 56.0 — Delivery guarantees at v56.0.0 (Retros/Cluster 56.0).

Recently closed: Cluster 55.0 — Helm production bundle at v55.0.0 (Retros/Cluster 55.0).

Recently closed: Cluster 54.0 — Capability quotas at v54.0.0 (Retros/Cluster 54.0).

Recently closed: Cluster 53.0 — Workspace full erasure at v53.0.0 (Retros/Cluster 53.0).

Recently closed: Cluster 52.0 — FSM automation hooks at v52.0.0 (Retros/Cluster 52.0).

Recently closed: Cluster 51.0 — Slash commands at v51.0.0 (Retros/Cluster 51.0).

Recently closed: Cluster 49.0 — Agent context export at v49.0.0 (Retros/Cluster 49.0).

Recently closed: Cluster 38.0 — MCP resource fan-out complete at v38.0.0 (Retros/Cluster 38.0).

Recently closed: Cluster 37.0 — A2A SendStreamingMessage at v37.0.0 (Retros/Cluster 37.0).

Recently closed: Cluster 36.0mcp-stdio Postgres at v36.0.0 (Retros/Cluster 36.0).

Recently closed: Cluster 35.0 — MCP streamable bidirectional mux at v35.0.0 (Retros/Cluster 35.0).

Recently closed: Product Ladder 30–34 at v34.0.0 (Retros/Product Ladder 30-34, Clusters/Product Ladder 30-34).

Recently closed: Cluster 32.0 — Helm umbrella at v32.0.0 (Retros/Cluster 32.0).

Recently closed: Cluster 31.0 — workspace artifact purge at v31.0.0 (Retros/Cluster 31.0).

Recently closed: Cluster 30.0 — rate limits at v30.0.0 (Retros/Cluster 30.0).

Recently closed: Cluster 29.0 — message edit at v29.0.0 (Retros/Cluster 29.0).

Recently closed: Cluster 28.0 — privacy complete at v28.0.0 (Retros/Cluster 28.0).

Recently closed: Product Ladder 17–27 at v27.0.0 (Retros/Cluster 27.0, PR #198); tags v23.0.0v27.0.0 documented in CHANGELOG (GitHub Release cut at v27.0.0).

Before that: Product Ladder integration (Clusters/Product Ladder 17-27); v22.0.0 — capabilities hardening (Retros/Cluster 22.0). Before that: v21.0.0 — A2A agent transport (Retros/Cluster 21.0). Before that: v20.0.0 — message router (Retros/Cluster 20.0). Before that: v19.0.0 — S3 multipart artifacts (Retros/Cluster 19.0). Before that: v18.0.0 — SQLite semantic search (Retros/Cluster 18.0). Before that: v17.0.0 — MCP resource fan-out (Retros/Cluster 17.0). Before that: v16.0.0 — MCP HTTP resource notifications (Retros/Cluster 16.0). Before that: v15.0.0 — MCP stdio resource subscribe (Retros/Cluster 15.0). Before that: v14.0.0 — SQLite outbox (Retros/Cluster 14.0). Before that: v13.0.0 — delivery ledger (Retros/Cluster 13.0). Before that: v12.0.0 — outbox relay hardening (Retros/Cluster 12.0). Before that: v11.0.0 — coverage 11% (Retros/Cluster 11.0). Before that: v10.0.0 — Postgres transactional outbox (Retros/Cluster 10.0). Before that: v9.0.0 — coverage depth (Retros/Cluster 9.0). Before that: v8.0.0 — bus hydrate observability (Retros/Cluster 8.0). Before that: v7.0.0 — bus pointer delivery (Retros/Cluster 7.0). Before that: v6.0.0 — delivery reliability (Retros/Cluster 6.0). Before that: v5.0.0 — coverage & search quality (Retros/Cluster 5.0). Before that: v4.0.0 — subscriber continuity (Retros/Cluster 4.0). Before that: v3.0.0 — search & subscriber depth (Retros/Cluster 3.0). Before that: v2.1.0 — OIDC operator hardening (Retros/Cluster 2.1).

Also on deck: ad-hoc reliability/search backlog in Open Work.

Closing a cluster

Each cluster closes with a dedicated retro PR that:

  • Creates the retro note for that cluster.
  • Updates Capabilities.
  • Updates the root CHANGELOG.md.
  • Cuts the release tag.

This pattern is mandatory; tags are never cut without a retro.