FE-1053: Collapse structured-exchange companion contracts#250
Conversation
PR SummaryLow Risk Overview Review discipline: graduates the opaque companion to an enforced discriminant lens into Evidence conventions: probe/fixture docs now treat Product shell docs: Pi UI patterns memo now records native Planning: Reviewed by Cursor Bugbot for commit e4c95dc. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Pull request overview
Collapses the structured-exchange surface to a single terminal request_response while merging option prompts into present_question, and hardens several “discriminant + required companion” contracts by teaching/enforcing companion shapes at tool/RPC schema boundaries (review-set payload, mutate_graph.create_node.detail, and read_graph mode companions).
Changes:
- Replace
present_options+ request-family terminals withpresent_question(free-text/choice/multi-choice) + singlerequest_response, updating projections/renderers/session logic plus probes/tests/docs. - Promote per-kind node
detailJSON-schema ownership intograph/schema/nodes.tsand consume it in both agent tool schemas and dev-RPC schemas (and validation). - Add/strengthen boundary-teaching schemas for review-set payloads and
read_graphmode-specific required params (with loudSTRUCTURAL_ILLEGALdiagnostics).
Reviewed changes
Copilot reviewed 84 out of 85 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/web/README.md | Updates web feature doc to match request_response naming. |
| src/session/structured-exchange-loop/pending-exchange.ts | Pending exchange reconstruction updated for present_question + request_response. |
| src/session/structured-exchange-loop/accepted-response.ts | Accepted-response materialization updated (partial) for request_response. |
| src/session/README.md | Session README updated for request_response broker wording. |
| src/session/exchange-projection.ts | Request/present pairing updated to infer expected request-detail tool from present details. |
| src/session/tests/structured-exchange-loop.test.ts | Tests updated for new tool names and merged prompt shapes. |
| src/session/tests/session-transcript.test.ts | Transcript renderer tests updated for present_question + request_response. |
| src/session/tests/exchange-projection.test.ts | Projection tests updated for merged present + single terminal. |
| src/rpc/README.md | RPC docs updated for request_response promise semantics. |
| src/rpc/methods/dev-graph.ts | Dev-RPC mutate schema now enforces per-kind detail companions. |
| src/rpc/tests/handlers.test.ts | Handler fixtures updated for merged present + request_response. |
| src/renderers/README.md | Renderer ledger updated to remove present_options and expand present_question. |
| src/renderers/exchanges/present-question.ts | Renderer now formats both question and option prompts. |
| src/renderers/exchanges/present-options.ts | Deleted (merged into present-question). |
| src/projections/README.md | Projection inventory updated to reflect merged exchange tools. |
| src/projections/exchanges/request-choices.ts | Request tool_meta.prev updated to present_question. |
| src/projections/exchanges/request-choice.ts | Updates pairing vocabulary to present_question (vs present_options). |
| src/projections/exchanges/present-review-set.ts | Present review-set tool_meta.next now request_response. |
| src/projections/exchanges/present-question.ts | Present-question projection now owns option/multi-choice shape + response_kind. |
| src/projections/exchanges/present-options.ts | Deleted (merged into present-question). |
| src/projections/exchanges/tests/request-choice.test.ts | Tests updated for present_question pairing. |
| src/projections/tests/topology-boundaries.test.ts | Removes present-options exception; keeps exchange projections exceptions. |
| src/probes/structured-exchange-ordering-proof.ts | Probe updated for present_question + request_response. |
| src/probes/public-rpc-parity-proof.ts | Probe updated to assert simplified tool coverage and option detection. |
| src/probes/project-graph-review-cycle-proof.ts | Probe updated to count request_response (review flow). |
| src/probes/deterministic-exchange-script.ts | Deterministic exchange script now uses present_question for option prompts. |
| src/probes/tests/structured-exchange-rpc-proof.test.ts | Test fixtures updated for present_question prev tool. |
| src/probes/tests/structured-exchange-ordering-proof.test.ts | Ordering proof tests updated for renamed tools. |
| src/probes/tests/public-rpc-parity-proof.test.ts | Parity proof expected coverage updated to present_question + request_response. |
| src/probes/tests/project-graph-review-cycle-proof.test.ts | Review-cycle proof fixtures updated for request_response. |
| src/graph/schema/nodes.ts | Adds exported per-kind detail schemas + metadata helpers. |
| src/graph/review-set.ts | Adds graph-owned boundary-teaching Zod schema for nested review-set payload. |
| src/graph/README.md | Graph README updated to mention detail-schema ownership and boundary payload teaching. |
| src/graph/index.ts | Re-exports new detail-schema owners/helpers. |
| src/graph/command-executor/command-validation.ts | Validation now consumes shared detail-schema owner metadata. |
| src/graph/tests/mutate-graph-edge-schema.test.ts | Adds tests for per-kind detail companions and schema visibility. |
| src/dev/tests/web-driver-streaming.exchange-convergence.test.ts | Updates streaming convergence test for request_response. |
| src/dev/tests/web-driver-streaming-support.ts | Streaming support helpers updated to look for request_response. |
| src/app/tests/brunch-tui.test.ts | TUI tool list expectations updated to request_response. |
| src/.pi/extensions/system-prompts/tests/compose.test.ts | Prompt composition tests updated for new active tool names. |
| src/.pi/extensions/system-prompts/previews/elicitor--pushed-context.md | Preview updated for request_response. |
| src/.pi/extensions/system-prompts/previews/elicitor--pinned-strategy-lens.md | Preview updated for request_response. |
| src/.pi/extensions/system-prompts/previews/elicitor--auto-high-coverage.md | Preview updated for request_response. |
| src/.pi/extensions/system-prompts/previews/elicitor--auto-floor-gaps-open.md | Preview updated for request_response. |
| src/.pi/extensions/runtime/state.ts | Runtime method→tool mapping updated to simplified exchange tools. |
| src/.pi/extensions/runtime/state.test.ts | Tests updated for simplified exchange tools (contains an issue). |
| src/.pi/extensions/runtime/authority-matrix.test.ts | Authority matrix updated for request_response. |
| src/.pi/extensions/introspection/debug-cache.ts | Debug-cache allowlist updated for simplified exchange tools. |
| src/.pi/extensions/graph/tool-schemas.ts | mutate_graph detail typed; read_graph params enforce mode companions via oneOf. |
| src/.pi/extensions/graph/index.ts | Adapter now fails loud for malformed read_graph companions. |
| src/.pi/extensions/exchanges/shared/ui-context.ts | New shared ctx slice for response collectors. |
| src/.pi/extensions/exchanges/shared/review-source.ts | New extracted review collector for request_response. |
| src/.pi/extensions/exchanges/shared/recovery.ts | Recovery now records a single continuation tool (request_response). |
| src/.pi/extensions/exchanges/shared/choices-editor.ts | Refactors editor exchange logic into shared helper (tool removed). |
| src/.pi/extensions/exchanges/shared/choice-source.ts | New choice collector for request_response. |
| src/.pi/extensions/exchanges/shared/answer-source.ts | New answer collector shared by request_response. |
| src/.pi/extensions/exchanges/schemas/shared.ts | Removes present_options and updates tool_meta enums for single-terminal flow. |
| src/.pi/extensions/exchanges/schemas/README.md | Docs updated for merged present + request_response terminal. |
| src/.pi/extensions/exchanges/schemas/present.ts | present_question details become union with/without options + response_kind. |
| src/.pi/extensions/exchanges/schemas/params.ts | Collapses request params to request_response({exchangeId}); expands present_question params. |
| src/.pi/extensions/exchanges/request-review.ts | Deleted (review collected via request_response). |
| src/.pi/extensions/exchanges/request-response.ts | New unified terminal tool router/collector. |
| src/.pi/extensions/exchanges/request-choice.ts | Deleted (replaced by request_response). |
| src/.pi/extensions/exchanges/request-answer.ts | Deleted (replaced by request_response). |
| src/.pi/extensions/exchanges/README.md | Updated exchange tool topology and single-terminal contract documentation. |
| src/.pi/extensions/exchanges/present-review-set.ts | Tool docs updated to point to request_response. |
| src/.pi/extensions/exchanges/present-question.ts | Tool docs updated for merged free-text/options behavior + request_response. |
| src/.pi/extensions/exchanges/present-options.ts | Deleted (merged into present-question). |
| src/.pi/extensions/exchanges/index.ts | Registry updated to register request_response instead of request-family tools. |
| src/.pi/tests/structured-exchange-schemas.test.ts | Updates schema tests for merged present + single terminal + review payload teaching. |
| src/.pi/tests/structured-exchange-present-request.test.ts | Updates end-to-end structured exchange tool tests for request_response. |
| src/.pi/tests/structured-exchange-extension.test.ts | Renderer wiring tests updated for present_question + request_response. |
| src/.pi/tests/structured-exchange-editor-envelope.test.ts | Updates editor envelope test to new prev tool meta. |
| src/.pi/tests/runtime-switch-command.test.ts | Updates tool list fixture for request_response. |
| src/.pi/tests/prompting.test.ts | Updates prompt tests for simplified tool surface (contains an issue). |
| src/.pi/tests/operational-mode.test.ts | Updates operational-mode tool list expectations for request_response. |
| src/.pi/tests/graph-tools.test.ts | Adds schema enforcement tests for read_graph mode companions + loud failures. |
| src/.pi/tests/extension-registry.test.ts | Updates explicit extension registry expectations for simplified exchange tools. |
| package-lock.json | Normalizes bin path for pi-ai. |
| memory/PLAN.md | Updates plan status and records delivery + new contract lens graduation. |
| docs/design/STRUCTURED_EXCHANGE_COLLAPSE.md | Updates design doc status from proposal to built and reconciles naming. |
| docs/archive/PLAN_HISTORY.md | Archives older plan summaries during sync. |
| .oxfmtrc.json | Adds package-lock.json to ignore list. |
| .agents/skills/ln-review/references/contract-lenses.md | Adds “opaque companion to an enforced discriminant” contract lens. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| expect(floorTools).toEqual( | ||
| expect.arrayContaining(['present_question', 'present_options', 'request_answer']), | ||
| expect.arrayContaining(['present_question', 'present_question', 'request_response']), | ||
| ); |
| export const zPresentedOptionParam = z | ||
| .object({ | ||
| id: z.string().min(1).describe('Stable option id for later request_* response correlation.'), | ||
| id: z.string().min(1).describe('Stable option id for the later request_response result details.'), | ||
| content: z.string().describe('Markdown-readable option content.'), | ||
| rationale: z.string().describe('Why this option is plausible or recommended.').optional(), | ||
| }) |
| function toolResultMessageBase( | ||
| pending: PendingStructuredExchange, | ||
| requestTool: 'request_answer' | 'request_choice' | 'request_choices' | 'request_review', | ||
| requestTool: 'request_response' | 'request_review', | ||
| ) { | ||
| return { |
| 'present_question', | ||
| 'request_response', | ||
| 'present_review_set', | ||
| 'request_review', | ||
| 'read_graph', |
Collapse the delivered demo lower line in Sequencing, trim Recently Completed to the rolling window (older entries archived to PLAN_HISTORY.md), fix done-item drift (poc-live-ship-gate pointers, dependency-graph status), record the spec-structural-relief D3 deferral, and correct the stale SPEC verification-infrastructure line. Co-authored-by: Cursor <cursoragent@cursor.com>
…type mutate_graph.detail Materialize the discriminant-companion contract lens across the structured-exchange and graph-mutation surfaces so the legal companion shape is taught/derived at the point of choice instead of validated only downstream. Structured exchange (single terminal): - present_question is the merged prompt anchor (options[] presence + multiple derive the response kind); request_response is the sole terminal, dispatching answer / choice / choices / review from server-owned pending-present state. - Retire the present_options / request_answer / request_choice / request_choices / request_review tools; preserve their request_*/capture_* result-detail vocabulary. - request_response is a thin router on tool_meta.curr with exhaustive narrowing; collectors (answer/choice/choices/review) each own availability + formatting and share one typed StructuredExchangeUiContext. Graph mutation (typed detail): - graph/schema/nodes.ts owns NODE_DETAIL_JSON_SCHEMAS + NODE_KINDS_REQUIRING_DETAIL; command-validation.ts consumes the shared owner while preserving I37 diagnostics; mutate_graph tool schema and the dev-RPC mirror expose per-kind create_node detail instead of Type.Unknown(). Reconciles SPEC (I23-L, A30-L, D27-L, D86-L, lexicon), PLAN, the collapse design doc, and topology READMEs. Co-authored-by: Cursor <cursoragent@cursor.com>
f04c7c2 to
e4c95dc
Compare
f1e70bc to
e330230
Compare
Merge activity
|
* docs: capture ontology review protocol (thread audit + grilled scope)
Audit of the external GPT-Pro ontology thread against the current graph
model, plus the refined active scope from the design grill: edge renames
(witness/rationale/exclusion/cross_reference) + refinement, node adds
(unknown/entity/sketch), thesis→claim and vv_*/AC recodes. coverage and
the rest deferred or parked. Working artifact, not yet a SPEC decision.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: reconcile ontology review protocol to grilled resolution
Reframe to the closure rule (methods are validation lenses, not sources of
kinds: spec.kind + detail.form + renderer + heuristic-set). Fix baseline
labels to actual NODE_KIND_METADATA codes. Mark the thread audit (§1-5)
historical and supersede §6-8 with the resolved scope: node/edge deltas,
detail.form mechanism, spec.kind scope model + story node, epistemic triad
+ given/theorem routing, witness/evidence naming, Gherkin mapping,
heuristics list, deferrals/ceilings, and follow-ons.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ln-spec: propagate the multi-method ontology revision (FE-1052)
Record the grilled ontology resolution as canonical SPEC decisions:
- D87-L: closure rule (methods are validation lenses, not kinds); the
edge renames + refinement (8->9), node renames, and adds (entity,
sketch, story, unknown); thesis kept + sharpened; feature is spec.kind.
- D88-L: detail.form method-payload union on requirement/criterion/
invariant (kind drives behavior, form is inert payload).
- D89-L: spec.kind ownership relation; story as intra-spec grouping;
project graph + role deferred; readiness_band computed.
Cross-link D51-L/D54-L/D56-L/D61-L/D45-L; forward-note I37-L. Graduate
the deferred risk/unknown Future-Direction item to the unknown node add;
add the methods-as-lenses + heuristics-SoT follow-on. Lexicon: sharpen
Thesis, clarify Claim, Risk->Unknown, add Spec kind / Story / Node
detail form / Method as lens / Witness / Rationale / Refinement.
GRAPH_MODEL.md gets a forward-pointer banner only; its body and the
schema enums change during the FE-1052 build, not now.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ln-plan: make ontology-revision (FE-1052) the single data-model frontier
Add the ontology-revision frontier (FE-1052) implementing SPEC
D87-L/D88-L/D89-L: edge renames + refinement (8->9), node renames/adds
(vv_*, entity, sketch, story, unknown; thesis sharpened), detail.form
union, spec.kind field. It absorbs the two other data-model/ontology
frontiers as accessory tasks: graph-model-doc-retirement (retire
GRAPH_MODEL.md, re-point ~15 citations) and graph--edge-impact-remodel
(affected+impactKind+stanceRequired). Scope principle: FE-1052 owns the
ontology shape; consumers (coherence, oracle/design/plan planes) stay
separate and depend on it. Update the active line, the absorbed frontier
notes, and the dependency graph accordingly.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: reconcile edge-impact-remodel card to FE-1052 / D87-L vocabulary
The card is now an accessory task inside ontology-revision (FE-1052), not
a follow-on to a separate graph-model-doc-retirement frontier. Reconcile
its per-category table to the D87-L edge renames (witness/rationale/
exclusion/cross_reference), add the new refinement row as an explicit
DECIDE-in-build call (not a silently baked row), and update the stance
check and header framing to match. Core mechanism (affected + impactKind
+ stanceRequired replacing impactOn*Change) is unchanged.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ln-scope: scope FE-1052 as a 5-card schema-migration slice sequence
Mode:slices scope file: card 1 (edge vocabulary + impact remodel, full,
next) -> 2 (node vocabulary) -> 3 (detail.form union) -> 4 (spec.kind +
story/unknown) -> 5 (GRAPH_MODEL.md retirement). Card 1 is fully scoped
as the thinnest atomic first slice and folds in the absorbed
graph--edge-impact-remodel (same graph/schema + policy files). Add a
Current execution pointer to the FE-1052 frontier definition.
Co-authored-by: Cursor <cursoragent@cursor.com>
* FE-1052: Rename graph edge vocabulary
* comparative architecture with pi-web
* FE-1052: Rename graph node vocabulary
Card 2 of the ontology-revision schema migration (D87-L node deltas):
rename validation_method->vv_method and obligation->vv_obligation;
add story/unknown (intent, elicitation band) and entity/sketch (design);
sharpen thesis to a testable/refutable/refinable bet without renaming it.
Updates NODE_KIND_METADATA, the intent kind-category derivation (new
elicitation category), web kind-display sections, regenerated seed
fixtures + the kind-band-spread graph-overview golden, and the
GRAPH_MODEL.md thesis rubric.
Co-authored-by: Cursor <cursoragent@cursor.com>
* FE-1052: detail.form union on claim kinds (card 3, D88-L)
Add the form-discriminated detail union (plain|gherkin|formal on
requirement/criterion/invariant; given on context) per D88-L. kind still
drives band/edge-legality/source-question; form is inert payload.
CommandExecutor validates per-form payloads and rejects non-allowed
forms + unknown fields; agent mutate_graph and dev-RPC boundaries
advertise the form companions table-driven from one source. Payloads
decided: gherkin {given?,when?,then[]}, formal {language,statement},
given {statement}, plain {}. Reconcile I37-L.
Co-authored-by: Cursor <cursoragent@cursor.com>
* FE-1052: strip the unused intentKindCategory axis (D56-L)
The basic|structural|reasoning intent-kind category had no code/test/
prompt reader (only a definition plus one re-export), and the I36-L
"covered by command-executor.test.ts" citation was to a test that does
not exist. Card 2 had begun extending it with a 4th value ('elicitation',
a readiness-band name) to place story/unknown, conflating the D56-L
category axis with the D64-L band axis. Rather than fix the conflation,
remove the axis: delete IntentKindCategory + intentKindCategory() from
nodes.ts and the graph/index re-export. Reconcile D56-L (no category
axis), both I36-L rows, the retired Lexicon entry, and D61-L/Claim (name
the truth-bearing kinds directly). Readiness band (D64-L) stays the only
live grouping over kinds. No property without a clear reader.
Co-authored-by: Cursor <cursoragent@cursor.com>
* FE-1052: spec.kind field + story/unknown wiring (card 4, D89-L)
Add SPEC_KINDS (product|feature|function) to the drizzle-free taxonomy
leaf and a specs.kind column (NOT NULL DEFAULT 'product', migration
0006) so existing createSpec({name,slug}) callers are untouched.
SpecRecord.kind / CreateSpecInput.kind thread through createSpec /
getSpec / listSpecs and the seed/export fixture contract. story/unknown
need no new wiring (card 2 landed the kinds; composition has no kind-pair
restriction) — added tests proving both are legal intent claims, unknown
spawns no elicitation_gap, and story->requirement reuses composition.
Deferred: project graph + role:main|alt; readiness_band stays computed.
Reconcile db/README + graph/README spec-row drift.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: record three deferred ontology reflections (FE-1052 session)
Capture as Future Direction, none built in FE-1052:
- thesis -> pitch (annotated rename; later vocabulary pass)
- term may move to project/workspace level (with the deferred project graph)
- readiness-band re-mapping + new 'projection' band: a candidate D64-L
amendment (overturns the locked "differentiation grows typologies, not
bands" clause; re-assigns kinds across many live band readers), so it
needs its own ln-grill/ln-spec pass, not a mid-migration fold-in.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ln-plan: add readiness-bands-interrogation frontier (proving)
A Next/parallel proving frontier to interrogate the D64-L readiness-band
model: is it earning its readers, or another instance of the
over-ambitious-gating pattern already retired three times (intentKindCategory
strip; readiness-grade gating D45-L/D74-L/D86-L)? Carries the candidate
projection-band amendment surfaced by the FE-1052 grill but is NOT
committed to building it — the product is the verdict. Enumerates every
live readinessBands reader, tries four hypotheses (incl. simplify/retire),
honors I31-L (readiness never bars work), and routes to ln-spec/ln-scope
only if a change is warranted. Independent of the FE-1052 spine.
Co-authored-by: Cursor <cursoragent@cursor.com>
* FE-1052: retire docs/design/GRAPH_MODEL.md (card 5)
Durable content was already homed by cards 1-4 (taxonomy -> NODE_KIND_METADATA
/ EDGE_CATEGORY_METADATA / schema/kinds.ts; invariants -> SPEC; policy ->
category-policy.ts; source-questions -> ELICITATION_QUESTIONS.md; edge teaching
-> tool-schemas .describe()). Rehome the two orphaned authoring heuristics
(interrogative normalization, context-promotion ladder) into the commit-graph
SKILL.md and fix its stale proof/support -> witness/rationale. Re-point every
citation (~16 SPEC, 10 src file-headers, CONTRIBUTING/protocol/seam-extensions/
elicitation-questions/review-sets/bilal-port), then delete the doc. Zero
dangling markdown links remain.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ln-plan: close out ontology-revision (FE-1052) — all 5 cards landed
Move FE-1052 from Active to Recently Completed with a full card summary;
mark the frontier definition done and its dependency-graph node done;
delete the exhausted scope file memory/cards/ontology-revision--schema-migration.md.
Next chosen frontier is readiness-bands-interrogation (user tees it off
separately). Residual ELICITATION_QUESTIONS.md vocabulary drift noted as
an ln-sync candidate.
Co-authored-by: Cursor <cursoragent@cursor.com>
* FE-1052: finish boundary→exclusion rename (review-bot induction)
Close the homograph bleed and dangling refs left by the boundary→exclusion
edge-category rename, surfaced by Copilot review on #251 plus two unsampled
instances found by auditing the lens across the tree:
- tool-schemas.ts: ToolMutateCreateEdgeOp exclusion variant declared its
endpoint field as `exclusion` while the runtime schema builds it as
`boundary` (EDGE_CATEGORY_METADATA.exclusion.sourceRole) — a silent
type/runtime divergence for tool callers; thread the real `boundary` field.
- tool-schemas.ts / seed-fixtures.ts: header/description prose where the
English word "boundary" was blindly replaced ("adapter-exclusion",
"parameter schema exclusion", "mutation exclusion").
- labels.ts: comment "anchor is the exclusion" (role is `boundary`).
- REVIEW_SETS.md: stance rule + example still used retired proof/support
names (now witness/rationale).
Co-authored-by: Cursor <cursoragent@cursor.com>
* FE-1052: land #250 structured-exchange review findings on this branch
Apply the in-tree review-bot findings from #250 (fe-1053 is downstack/linear
under this branch; squash-merge makes a re-stack unnecessary):
- accepted-response.ts: the review terminal emitted toolName/toolCall
`request_review`, but exchanges/README §"Single terminal" makes
`request_response` the only terminal tool — `request_review` survives only
as a `tool_meta.curr` result-detail discriminant. Route the review flow
through `request_response`; details keep `curr: request_review`. Updates the
direct unit assertion in structured-exchange-loop.test.ts.
- params.ts: option ids round-trip through a per-line `<!-- option-id: … -->`
marker recovered by a regex that stops at `>`; constrain the id at the
params boundary to forbid `>` and line breaks so malformed ids fail loud
instead of silently dropping during markdown recovery.
- state.test.ts: drop the duplicate `present_question` in the floor-tools
arrayContaining assertion.
- prompting.test.ts: remove the retired `request_review` from both
getAllTools fixtures so the test env matches the registered tool set.
Co-authored-by: Cursor <cursoragent@cursor.com>
* weave setup
---------
Co-authored-by: Cursor <cursoragent@cursor.com>

Stack Context
This branch sits above FE-811 and turns the live-run structured-exchange failures into boundary contracts agents can see before they call tools.
What?
read_graphcompanion shapes at the tool/RPC boundary.ln-review.Why?
The previous tool schemas enforced discriminants downstream while leaving companion payloads opaque at the boundary, causing avoidable repair turns and silent malformed reads.
Verification
npm test -- src/.pi/__tests__/graph-tools.test.ts src/graph/__tests__/spec-ownership.test.ts src/graph/__tests__/observed-shapes-coverage.test.tsnpm run verify