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
| Cluster | Theme | Target tag |
|---|---|---|
| A | Foundation: workspace, schema, /health | v0.0.1 ✓ |
| B | Routing + event bus + MCP surface | v0.1.0 ✓ |
| C | Search + indexing | v0.2.0 ✓ |
| D | FSM-driven thread lifecycle | v0.3.0 ✓ |
| E | Artifact substrate (S3, types, refs) | v0.4.0 ✓ |
| F | Auth, workspaces, capabilities | v0.5.0 ✓ |
| G | Agent-to-Agent transport | v0.6.0 ✓ |
| H | Web UI + MCP stdio + polish | v0.7.0 ✓ |
| 1.0 | Production gates met | v1.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.
| Track | Theme | Notes |
|---|---|---|
| T | Telemetry + perf | OTLP, tracing, latency budgets. |
| U | Performance work | Benchmarks, mutation tests, profiling. |
| V | Security + privacy | Threat models, GDPR, secret hygiene. |
| W | Documentation | The vault, runbooks, API docs. |
| X | Release engineering | Tags, release notes, signed artifacts. |
Current cluster
Clusters A–H and 1.0 are complete (v1.0.0). Optional minors v1.1.0–v1.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.0–v100.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.0–v120.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_session→dispatch→tools_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 harness — scripts/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 assembly — build_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_event → MessageEdited, tombstone_message_with_event → MessageTombstoned; 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_event → MemberJoined, create_workspace_with_event → WorkspaceCreated; 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 API — POST/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 claim — thread_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 tools — Arc 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 REST — PUT /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 inbox — GET /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 runbook — scripts/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 PostgresSearch → AppState → metrics.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/cancel→CancelTask, 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.0→v312.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_exists → NotFound (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.md → Capability-Map.md; README image pin v315 → v339. 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_filter → ensure_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.0 — channel: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.0 — request_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.0 — GET /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.0 — maidan-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.0–101.0 — Operator UI v1, collaboration, operator gate e2e
(Product Ladder 77+.md, retros under docs/Retros/Cluster 93.0.md … 101.0.md).
Recently closed: Clusters 91.0–92.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.0–90.0 — Helm profiles, OTLP metrics, SLO alerts at v88.0.0–v90.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.0–v76.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.0 — mcp-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.0–v27.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.