Skip to content

fix(mcp): carry briefing-blocked entities' SEI on the read surface (federation keying gap)#79

Merged
tachyon-beep merged 6 commits into
mainfrom
fix/briefing-blocked-sei-federation-key
Jun 29, 2026
Merged

fix(mcp): carry briefing-blocked entities' SEI on the read surface (federation keying gap)#79
tachyon-beep merged 6 commits into
mainfrom
fix/briefing-blocked-sei-federation-key

Conversation

@tachyon-beep

Copy link
Copy Markdown
Collaborator

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_list read churn 0 for every entity in a secret-bearing file (e.g. all of lacuna's tour/steps.py). Cause: loomweave's MCP read surface deliberately nulled the sei for briefing-blocked entities (blocked_entity_stub / stack_entity_json / compact_blocked_node_json), even when an alive SEI binding existed. Warpline's reresolve-sei resolved the qualname fine but got sei: null back, so entity_keys.sei stayed NULL → the SEI churn join missed. Live contrast on lacuna: tour.steps._run resolved with sei: null despite 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_hash and 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_sei helper carries the content-free SEI (REQ-C-04/ADR-038) across all three blocked projections, except when the id is itself secret-like (then the durable key is withheld with its locator). Secret content (summary/source/docstring) is still never projected; the stricter HTTP BRIEFING_BLOCKED surface (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

  • TDD red→green on all three surfaces (unit + the 4 entity_resolve integration tests flipped from "sei absent" to "sei rides along"; two new bound-blocked-entity tests for the entity_at stack and execution-path surfaces, each proven load-bearing by reverting its site to red).
  • CI floor green locally (fmt, workspace clippy -D warnings, doc -D warnings, nextest 1972/1973 — the lone failure is the pre-existing wardline-sibling-drift oracle, clarion-72e1c1a07d, unrelated).
  • Live-proven on lacuna: the fixed binary returns the SEI for the blocked tour.steps._run (was null).

Follow-ups (not in this PR)

  • Warpline-side: re-run reresolve-sei to heal already-minted NULL entity_keys.sei rows.
  • Deep-pagination of warpline's overflow dump for >200-candidate scopes: clarion-obs-acffc4e8a1.

🤖 Generated with Claude Code

tachyon-beep and others added 6 commits June 27, 2026 12:49
…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>
@tachyon-beep tachyon-beep merged commit a980ef2 into main Jun 29, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant