fix(mcp): carry briefing-blocked entities' SEI on the read surface (federation keying gap)#79
Merged
Conversation
…urn/recently_changed The entity_high_churn_list / entity_recent_change_list MCP surfaces were dead-by-design: loomweave does not populate git_churn_count in v1.0 and, by the loomweave<->warpline seam HARD RULE, retains no cross-run history. This wires the read-time join to Warpline's FROZEN warpline_entity_churn_count_get (warpline.entity_churn_count.v1, 2026-06-13 interface lock SS1A / GV-LW-2). Read-only / enrich-only / dependency-sink: - New WarplineLookup trait (single method, the churn read) + WarplineMcpClient (MCP-stdio subprocess, mirrors the Filigree consumer pattern), injected as an Option<Arc<dyn ..>> defaulting to None. Lives in loomweave-federation. - Candidate universe = the entity catalogue (new entities_for_churn_candidates query), NOT the empty git_churn_count scan; warpline holds the counts. Scope-filter BEFORE the warpline call, then rank the scoped set by the returned counts. SEI-keyed refs with locator fallback (never drops a candidate). One bounded call, joined at read time, retained nowhere. - Honest-degrade (lock SS1C): warpline disabled/unreachable -> honest-empty with a warpline-named missing-signal note + churn_source provenance; never empty-as-clean, never a hard error breaking the core flow. GV-LW-2 is an executable test: the full frozen envelope fixture parsed through the real parse path, driven via an injected fake (no live MCP call from the hub context). Both honest-degrade paths (disabled, unreachable) are tested. Blast radius: READS the frozen warpline_entity_churn_count_get contract only; mints no new sibling obligation (the producer already ships). Live cross-member validation against a real Warpline, deep-pagination beyond warpline's limit, and the >CHURN_SCAN_CAP single-frame request bound are tracked follow-ups. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ing disclosure The Warpline churn consumer was a live NO-GO: entity_high_churn_list / entity_recent_change_list hung when integrations.warpline.enabled=true. Two transport bugs, both confirmed against warpline's source and live on lacuna: 1. Framing mismatch. The client drove warpline over Loomweave's Content-Length plugin framing, but warpline-mcp reads NEWLINE-delimited JSON-RPC (`for line in sys.stdin`, one response line per request line) — so warpline never parsed the request and the read blocked forever. 2. Wrong launcher (found only live). The default command was `warpline mcp`, which is not a valid warpline subcommand (usage error -> broken pipe). The MCP stdio server ships as the standalone `warpline-mcp` binary. Transport rewrite (loomweave-federation/src/warpline.rs): - Newline-delimited JSON-RPC; the whole handshake+call runs on a worker thread bounded by recv_timeout + kill-on-timeout, so a hung warpline degrades to the honest `warpline-unreachable` response instead of hanging (req #5). stderr is discarded (Stdio::null) so a large traceback can't block the child. - Default launcher resolves to `warpline-mcp` (not `warpline mcp`). - Send required `repo: <project_root>` (req #2); drop the unsupported `actor` param (req #3) — it is not in warpline's frozen churn schema. Parse result.structuredContent, falling back to content[0].text (req #4). - Config: keep WarplineConfig.actor (reserved, for deny_unknown_fields back-compat with configs that set it) and add an honored timeout_seconds (default 10), retiring the stale "no timeout knob" doc. Honesty floor (two zeros must not be conflated with a never-observed 0): - Send limit = refs.len() so warpline's default 100-item page does not silently cap the join at the top-100-by-churn. - churn_truncated + ChurnCountResponse::overflow_partial(): warpline keeps a 200-item in-band lead and spills the rest (apply_overflow), so an over-cap scope's truncated tail is DISCLOSED, not shipped as fabricated zeros. - churn_unresolved + unresolved_ref_count(): warpline returns locator:null for an unresolved SEI ref (a resolve hit always sets the NOT-NULL locator), so a keying-miss 0 ("real churn UNKNOWN") is disclosed rather than read as "this code never changes". Closes the disclosure asymmetry with churn_truncated. Disabled / unreachable / empty degrade paths (warpline-disabled, warpline-unreachable, churn_source:"warpline", signal) are preserved (req #6). Validated live on /home/john/lacuna (release + uv-installed binary): enabled -> real nonzero ranked churn with churn_truncated{counted:200,total:580}; recent_change -> real recency, no hang; scoped -> churn_unresolved{count:54}; disabled -> honest-empty. 564 federation+mcp tests pass; fmt + workspace clippy + cargo doc all clean. Follow-ups (filed as filigree observations, not addressed here): deep-pagination via warpline's overflow dump for >200-candidate scopes; the loomweave<->warpline locator-dialect + NULL-sei keying gap that produces the unresolved refs; and the same Content-Length-vs-newline framing latent in filigree.rs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… clarion-0715faa9d6; repo-hygiene cleanup; PDR-0007
Live-validated on lacuna (real ranked churn, recent_change no hang, disabled->honest-empty; 564 federation+mcp tests pass). NDJSON transport fix + honest paging/keying disclosure (weft-670ec2fe90; resolves deadlock weft-e585382ff3 + validation weft-6fc4a166dc). Enrich-only: consumes warpline's frozen entity_churn_count.v1; no sibling obligation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ederation keying) Briefing-blocked (secret-bearing) entities were projected with `sei: null` on the MCP read/resolve surface (blocked_entity_stub, stack_entity_json, compact_blocked_node_json), even when an alive SEI binding existed. The locator `id` and `content_hash` were already exposed (A3, clarion-719e7320f5), so nulling the content-free SEI hash protected no secret content — it only broke SEI-keyed federation joins through secret-bearing files. Concretely it defeated Warpline's churn backfill: `reresolve-sei` resolves the qualname but receives `sei: null`, so `entity_keys.sei` stays NULL and `entity_high_churn_list` / `entity_recent_change_list` undercount those files to 0 (the keying gap, clarion-obs-30c0ef3b0a — disclosed via `churn_unresolved`, now closed at its loomweave-side root). Add `blocked_sei`: the SEI rides along (REQ-C-04/ADR-038) EXCEPT when the entity id is itself secret-like (high-entropy A3 guard), where the durable key is withheld with its locator. The secret CONTENT (summary/source/docstring) is still never projected. The HTTP `BRIEFING_BLOCKED` surface (ADR-034 §3) is unchanged. This reverses a deliberate secret-handling posture (owner-ratified): the residual it defended — a sibling durably binding a secret-bearing entity by a rename- surviving key — is outweighed by the permanent churn-undercount cost, and loomweave already emits that SEI ephemerally on the churn-query seam regardless. Recorded as the 2026-06-29 amendment to ADR-034. Tests: blocked_entity_stub unit tests + the entity_resolve integration tests flip from asserting "sei null / sei absent" to asserting the bound SEI rides along (and stays null when unbound or when the id is secret-like). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…re); PDR-0008 Records the owner-ratified security-posture reversal behind fix/briefing-blocked-sei-federation-key: briefing-blocked entities now carry their content-free SEI on the MCP read surface so federation siblings can key on them. PDR-0008 (with reversal trigger) + roadmap (keying gap → Shipped, warpline #77 transport still in flight) + current-state refresh. Tracked as clarion-4b3061b1ac (closed by merge); deep-pagination half split to clarion-obs-acffc4e8a1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Closes the warpline churn keying gap (clarion-4b3061b1ac) at its loomweave-side root: briefing-blocked (secret-bearing) entities now carry their content-free SEI binding key on the MCP read/resolve surface, so federation siblings can key on them through the block.
Root cause (traced end-to-end)
entity_high_churn_list/entity_recent_change_listread churn0for every entity in a secret-bearing file (e.g. all of lacuna'stour/steps.py). Cause: loomweave's MCP read surface deliberately nulled theseifor briefing-blocked entities (blocked_entity_stub/stack_entity_json/compact_blocked_node_json), even when an alive SEI binding existed. Warpline'sreresolve-seiresolved the qualname fine but gotsei: nullback, soentity_keys.seistayed NULL → the SEI churn join missed. Live contrast on lacuna:tour.steps._runresolved withsei: nulldespite an alive binding (loomweave:eid:a82891aadb36…);tests/entities healed.The null was an ADR-034 gloss layered onto A3 (clarion-719e7320f5) — A3 ("redact content, not identity") restored
id/name/path/content_hashand is silent on the SEI. The surface already exposed the locator + content hash (more revealing than an opaque SEI hash), yet withheld the one field every sibling keys on.The fix
New
blocked_seihelper carries the content-free SEI (REQ-C-04/ADR-038) across all three blocked projections, except when theidis itself secret-like (then the durable key is withheld with its locator). Secret content (summary/source/docstring) is still never projected; the stricter HTTPBRIEFING_BLOCKEDsurface (ADR-034 §3) is unchanged.This is a security-posture reversal, owner-ratified before implementation (PDR-0008 + the 2026-06-29 amendment to ADR-034). The residual it defended — a sibling durably binding a secret-bearing entity by a rename-surviving key — is outweighed by the permanent churn-undercount cost, and loomweave already emits that SEI ephemerally on the churn-query seam regardless.
Verification
entity_resolveintegration tests flipped from "sei absent" to "sei rides along"; two new bound-blocked-entity tests for theentity_atstack and execution-path surfaces, each proven load-bearing by reverting its site to red).-D warnings, doc-D warnings, nextest 1972/1973 — the lone failure is the pre-existing wardline-sibling-drift oracle, clarion-72e1c1a07d, unrelated).tour.steps._run(was null).Follow-ups (not in this PR)
reresolve-seito heal already-minted NULLentity_keys.seirows.🤖 Generated with Claude Code