Skip to content

fix(runtime): a sandboxed body or an action handler reading the stored-metadata tables is served the data door's form (#21454) - #21513

Merged
objectstack-fleet[bot] merged 12 commits into
mainfrom
claude/issue-21454-reach-measurement
Oct 3, 2026
Merged

objectstack-fleet[bot] merged 12 commits into
mainfrom
claude/issue-21454-reach-measurement

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Part of #21454

Clause-②: yes

The stored-metadata-body family serves a stored metadata body only as its type's read projection, and the stored content hash over it only in keyed form, at every door. Step 1 of this card measured three in-process reader contexts serving the two stored-metadata tables as stored, body and hash alike. The action contexts run elevated, so a member who invokes the action receives the same answer as an administrator. Triage ruled disposition A: apply the family's projection and keyed serve at the reader-context seams, consume the shared functions, and leave the engine alone. This PR does that for every context the measurement found. It is Part of, not Fixes: the flow reader that triage folded into this card was measured and is reached, but its fix lies outside this claim's surface (see "Not closed by this PR").

Disclosure discipline: classes, doors, roles, codes and forms only.

How

  • packages/runtime/src/stored-metadata-reader-seam.ts (new). One serve, built only from the generic data door's own functions:

    • the type companion for a projection that names the body without the type;
    • the body projection, which consumes the family's one redactor in @objectstack/spec/kernel;
    • the keyed serve, under the crypto provider's digest or, when none is registered, the data door's own process-scoped ephemeral key.

    serveStoredMetadataRead serves one read. serveStoredMetadataReadsThrough serves a scoped API: find, findOne and aggregate on a family object, through every context the API can derive (sudo(), withRunAs(...), a transaction(...) callback's context and the context beginTransaction() returns). It is idempotent, so a hash is never keyed twice. Writes, count and every other object pass through unchanged.

  • sandbox/body-runner.ts, buildSandboxApi. Both body faces (hook and action) get their ctx.api here, every fallback included. That API is now served through the seam, so a hook body that copies what it read can only copy the projected form.

  • action-execution.ts. Two call sites are served through the seam: buildActionApi, the ctx.api an action body and a host code handler both receive, and buildActionEngineFacade's find. Both action doors (REST /actions and MCP run_action) build these two, so both doors are covered.

  • packages/metadata-protocol/src/index.ts. Exports the data door's four stored-row serve functions and the digest type, so the runtime consumes them rather than copying them. The exports are additive and change no behaviour.

  • scripts/engine-double-contract.pinned.json. One new pinned row: the seam unit test's scoped-API double routes findOne through the engine's predicate. Written by the gate's own --write.

A declared in-place widening. The handler's ctx.api (③ in the table below) was not one of the two contexts the ruling named. It is the same scoped context object an action body receives (buildActionApi), and it was measured reaching (administrator and member: stored body, stored hash). The fix is the same call, in a file this claim already holds. All four in-place conditions hold, so it lands here and the pins cover it.

Readings: before and after

context door role before after
① action body, ctx.api.object(...) REST actions administrator, member stored body, stored hash (both tables) projected body, keyed hash
① action body, inside ctx.api.transaction(...) REST actions administrator, member stored (administrator, measured this run) projected, keyed
① hook body that copies what it read into an ordinary record data-door insert administrator stored content landed in the record only projected content lands
① hook body data-door insert member refused, 403 PERMISSION_DENIED unchanged
① action body authored at runtime through /meta metadata, then actions administrator authors; administrator and member invoke stored projected, keyed
② handler ctx.engine.find REST actions administrator, member stored (both tables) projected, keyed
③ handler ctx.api.object(...) REST actions administrator, member stored (measured this run) projected, keyed
control: generic data door data administrator projected, keyed unchanged
control: generic data door data member refused, 403 PERMISSION_DENIED unchanged

For every row marked "after", the pins assert four things:

  • no stored credential and no stored hash anywhere in the answer;
  • the row's non-credential configuration is present, so the body is the projection of this row, not one that was withheld;
  • the served hash equals the keyed value the data door serves for the same row, which shows one serve and one key.

Tests

  • Pins. packages/runtime/src/stored-metadata-reader-contexts.pin.test.ts is step 1's probe with its context cases flipped from RECORD to ASSERT. It boots the bootStack plugin set, in order, once in beforeAll (never inside a case) and runs 15 cases. packages/runtime/src/stored-metadata-reader-seam.test.ts holds 8 unit cases for the routes around the seam that the composition does not exercise one by one. Both files are green at 532c96d70f (15/15, 8/8).
  • Runtime suite. At 532c96d70f, pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 answered: Test Files 311 passed (311), Tests 4393 passed, 19 skipped. --project repo at 265f1c1f32: Test Files 3 passed, Tests 751 passed.
  • metadata-protocol suite. At 265f1c1f32, pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2 answered: Test Files 205 passed, 3 skipped; Tests 3092 passed, 19 skipped. After that sha the package's only change came in through the main merge, and it did not touch this package.
  • Typecheck. At 532c96d70f, pnpm --filter @objectstack/runtime typecheck (tsc --noEmit plus check:test-typecheck, OK, ledger unchanged) and pnpm --filter @objectstack/metadata-protocol typecheck were both green.
  • Built artifacts. Both the CJS and ESM builds of @objectstack/runtime load, and both builds of @objectstack/metadata-protocol export the four functions.

Reverse verification (forms only)

The fix was committed first, at 265f1c1f32. Then:

  • Mutation leg. The three exercised seam calls (the sandbox API, the action ctx.api, the engine facade's find) were replaced through scripts/ablation-replace.mjs. Each anchor went from 1 to 0 and each blob changed. The runtime was rebuilt, and scripts/ablation-dist-preflight.mjs found the mutation marker in both dist bundles. The pins then answered 10 failed, 13 passed. Every context case failed with "the stored credential reached the answer". The precondition, the three controls, the member hook refusal and the 8 seam unit cases stayed green.
  • Restore leg. A trap ran git checkout HEAD -- on the two paths. Both blobs equal HEAD, git diff HEAD is empty and the porcelain is clean. After a rebuild, the preflight with --absent was clean and the pins answered 23/23 green.
  • Direction. The expected direction, red. buildActionApi's malformed-envelope fallback is the one seam call no case exercises, so it was not mutated.

Gates

  • Derived gates. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack was derived with no paths at 532c96d70f and gave 72 lines. All 72 were run at that head, each exited 0, and --ran reconciled them: 72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN.
  • CJS load gate. check:dual-build-cjs-loads answered PREREQUISITE NOT MET on an earlier head. It was re-run once the tree's packages were built, and it measured 106 entry points across 66 packages.
  • check:engine-double-contract asked for the new pinned row above.
  • check:slot-lookup asked for the pins' handler plugin to read the engine slot through its contract type. Both are fixed in this PR.
  • Lint, a proven narrowing. pnpm lint (the repo-wide eslint .) was not run locally; CI runs it.
    • Population, read from eslint's own config: 6 of the 8 changed paths are linted. The changeset and the JSON ledger answer "no matching configuration".
    • Count, read from --format json at 532c96d70f: 6 files, 0 errors, 0 warnings.
    • Invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project, no typed rules) and carries no cross-file import rules. Its custom plugins and baselines are per file, so this diff cannot move the verdict on any untouched file.

Not closed by this PR (measured; reported on the card for routing)

  1. The flow record-read node. Triage folded it into this card, to be measured first. It is reached. Under both run identities, a flow's record-read node answers the stored body and the stored hash. That answer goes into the run's declared output, which the flow-action door returns to its caller, and into any record the flow writes.
    • How it was measured: in the service-automation tier, on an in-process engine with the automation service, with the row seeded in its stored form. No security plugin was loaded, so the run identities are not role-gated there.
    • Why it is not edited here: the keyed serve cannot be reached from @objectstack/service-automation, which does not depend on @objectstack/metadata-protocol. Consuming it there needs either that dependency edge or the keyed serve moved beside the body primitives in @objectstack/spec/kernel. Both are outside this claim's surface, and the second is packages/spec.
  2. Evaluate shapes through these contexts. A predicate over the body column, run through a body's count, answers whether a guess matches (measured, member invoking). The data door refuses these shapes. The ruled disposition changes only what a read serves, and so does this PR.
  3. An object name the engine's registry does not resolve. The engine's in-process verbs hand such a name to the driver, and the driver can resolve it to a family table.
    • The seam judges the object by the family's own predicate on the name, exactly as the data door does. The data door, however, also refuses an unregistered name (404 OBJECT_NOT_FOUND), and the in-process verbs do not.
    • Measured after this fix: an action body, invoked by an administrator or a member, still receives the stored body and hash this way.
    • This bypasses every name-keyed in-process guard, not only this one, so the fix belongs in the engine.
  4. A hook bound to a family object. It receives the stored row in its own hook context and can copy it (measured: fired by the metadata door's own save). That row does not come through ctx.api, and this PR does not project it, because a hook's input is also its write-back channel.
  5. A write through these contexts. It returns the written row in its stored form (measured for an elevated body's update). Under dispatch assumption A3 this is reported, not widened into this PR. The same measurement shows that an elevated body can write the family tables directly.

Acceptance notes

  • The engine's ScopedRepo.execute() hands a code handler a raw scoped context that no seam wraps. Its own census found zero callers. That is engine lane: noted, not filed.
  • Changeset: .changeset/21454-reader-context-family-serve.md, patch for @objectstack/runtime and @objectstack/metadata-protocol.

Generated by Claude Code

claude added 11 commits October 2, 2026 22:58
…-metadata-body family (wip)

Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Co-authored-by: Claude <noreply@anthropic.com>
…condition, refusal codes (wip)

Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Co-authored-by: Claude <noreply@anthropic.com>
…control and both contexts (wip)

Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Co-authored-by: Claude <noreply@anthropic.com>
…rve for the in-process reader contexts

Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Co-authored-by: Claude <noreply@anthropic.com>
… the test typecheck

Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Co-authored-by: Claude <noreply@anthropic.com>
…e engine predicate, pinned in the double ledger

Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Co-authored-by: Claude <noreply@anthropic.com>
… its contract type

Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/metadata-protocol, @objectstack/runtime, touching 16 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/metadata-protocol/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/data-flow.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/automation/hook-bodies.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/automation/webhooks.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/kernel/contracts/data-engine.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/kernel/contracts/index.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/kernel/events.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/permissions/attachments-access.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/permissions/field-level-security.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/permissions/record-view-auditing.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/permissions/rls.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/permissions/system-context.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/protocol/objectql/query-syntax.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/protocol/objectql/schema.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/ui/react-pages.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))

⛔ 6 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v15.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/releases/v16.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/releases/v17/17-0.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/releases/v17/17-5.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/releases/v17/17-6.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))
  • content/docs/releases/v17/index.mdx (via findOne (literal, a string literal in ROW_SERVING_READS))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/metadata-protocol/src/index.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 31 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json f9a8eb889ead684cb3f35f4ee013e634513ad1af → packageMentionDocs.

Which tree this was computed on

This run read content/docs from ef064c15277ee33057fc686f55fe9bef78eaffd9 — the merge of head c69c33396dc50ea173464a5d09cc4e7f731b8d9c into base f9a8eb889ead684cb3f35f4ee013e634513ad1af, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ef064c15277ee33057fc686f55fe9bef78eaffd9 && git checkout ef064c15277ee33057fc686f55fe9bef78eaffd9
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f9a8eb889ead684cb3f35f4ee013e634513ad1af c69c33396dc50ea173464a5d09cc4e7f731b8d9c && git checkout -B drift-repro f9a8eb889ead684cb3f35f4ee013e634513ad1af && git merge --no-ff c69c33396dc50ea173464a5d09cc4e7f731b8d9c

node scripts/docs-audit/affected-docs.mjs --json f9a8eb889ead684cb3f35f4ee013e634513ad1af

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs f9a8eb889ead684cb3f35f4ee013e634513ad1af → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 3, 2026
…inor under Clause-② yes

Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c69c33396dc50ea173464a5d09cc4e7f731b8d9c
Local-runs: none

Inputs: card #21454 (body and all 11 comments, rulings 5960355158, 5963299937, 5964426684 included), PR #21513 (body, 8-file list, net diff against main), and the check-runs on the head. Read-only. Classes, doors and roles only.

① Derived judgments

  1. Public surface, @objectstack/metadata-protocol (src/index.ts). Four functions and one type that were module-internal door primitives become exports: storedMetadataBodyProjection, redactStoredMetadataRows, serveStoredMetadataHashColumnRows, ephemeralStoredHashDigest, type StoredHashDigest. Additive, no behaviour moved. Exporting the ephemeral digest shares the ONE process key with every in-process consumer, which is the intent (one key, one keyed form per row); a consumer can compute a keyed form, never read the key. Right. No API-surface baseline names this package, and Lint and Repo Gates is green.
  2. Accept set at the reader seams (stored-metadata-reader-seam.ts). On an object the family predicate names (the door's own predicate from spec/kernel), the row-serving verbs find, findOne, aggregate are served; count, the write verbs and execute pass through bound; every other object passes by reference, query untouched. Judged against the scoped repository's actual member set (find, findOne, insert, update, delete, count, aggregate, execute): the three named are exactly its row-serving reads. Right. (Write-verb returns and the count predicate oracle are routed, see ③.)
  3. Derived contexts. object(name), sudo(), withRunAs(...), the transaction(fn) callback context and beginTransaction().ctx are re-served; commitTransaction/rollbackTransaction take a handle, not a context, and pass through; withRunAs may answer the unscoped hook api, which refuses every data door, so wrapping it is harmless. Served-once marker makes the double wrap (action door, then sandbox) a no-op, so a keyed hash is never keyed twice. Checked against the scoped context's member set. Right.
  4. Projection widening. A fields list naming the body column without the type column gets the type read beside it and taken back off (dropType), the door's own rule; a row the redactor cannot judge (no type beside the body, as in an aggregate group that did not group by type) fails closed with the body omitted, as on the door. Right.
  5. Keyed serve. The crypto provider's keyed digest is read off the engine per use, else the process-scoped ephemeral key: the door's two sources in the door's order. A host engine without the accessor keys under the ephemeral key, which is still never the stored value. The composed pins assert equality with the door's served hash per row id, so one key is proven in the assembled stack. Right.
  6. Seam placement against ruling 5963299937 (disposition A). buildSandboxApi wraps both body faces and every fallback (the one place both faces get ctx.api); buildActionApi wraps both branches; buildActionEngineFacade.find is wrapped, and find is that facade's only row-returning verb (insert returns an id, update void, delete void). Both action doors build these two (REST /actions in domains/actions.ts, MCP run_action in action-execution.ts). Engine untouched (not B), no refusal (not C). The facade's envelope check runs on the caller's query before the seam widens fields. Right.
  7. Pins. The step-1 probe is inverted into assertions: each context, administrator and member, asserts no sentinel and no stored hash anywhere in the answer, this row's non-credential configuration present, and the served hash equal to the door's keyed value for the same row id; the data-door control and the member refusals are unchanged; the hook copy exit lands only projected content. The seam unit file covers the routes the composition does not, and its double's findOne is pinned in the engine-double ledger by the gate's own writer. Right.
  8. Check-runs on the head. Latest run per name: every one is success or skipped (skipped: Build Docs, Console Pin Gate, the opt-in tarball smoke, and the superseded Auto Label and Check PR Size re-runs). No failure, nothing pending. Gate verdicts: green.
  9. Governed surfaces. None in the file list; this review is owed under Clause-②: yes, not under a tier.

② Semver level

  • Changeset .changeset/21454-reader-context-family-serve.md: @objectstack/runtime patch, @objectstack/metadata-protocol minor, body line Clause-②: yes, no arm.
  • Right. The export widening is Clause-② by the lanes rule (放宽接受集或扩大公开面的卡,不论多小,即条款②), and the widened package is graded minor, which is what yes takes. The runtime half is a bug fix in a released package with no public-surface change (the seam is not exported from the runtime index), so patch; a runtime security-behaviour change is not itself Clause-②. No (narrowing) arm: nothing an author can write is removed or renamed, and the stored form was never the published contract, so no ADR-0087 marker is owed. skip-changeset does not apply.
  • Clause-②: line in the PR body: yes, matching the changeset (corrected by the seat; Check Changeset is green on this head after the edit).
  • Changeset prose checked against the diff: the three contexts, the three verbs, the four derived contexts, what does not change, and the five new exports are named exactly. Right.

③ Boundary flags

Dev report 5964258278 (deviations 1 to 7, open_questions, out_of_scope), patch round 5964334002, seat ACCEPT 5964347376, triage 5964426684:

  1. Part of, not Fixes: answered (seat accepted partial; triage keeps the card open; the Part-of gate is green).
  2. Cross-lane metadata-protocol/src/index.ts: answered, declared by the seat on [PM seat] domain:engine · seat 2 — 🟢 os-litant · session_01Ujdtvqs7ree7WyQmEDwEnG #20966 per 5964347376; additive only.
  3. engine-double-contract.pinned.json row: answered, written by the gate, ungoverned.
  4. In-place widening to the handler's ctx.api: judged right. It is the same scoped-context class the ruling's sandbox seam serves, measured reaching for both roles, in a file the claim held, and pinned; leaving it unserved would have left a measured route open under the same ruling.
  5. Evaluate-shape refusals absent at the seams: answered, triage item 2, this card's next claim.
  6. and 7. Comment count, trailers: no contract content.

Escalated, unanswered on the card:

  • The repository's execute verb (the engine's own action path) hands a code handler a raw scoped context that no seam wraps; the dev's census found zero in-repo callers and wrote it into Acceptance notes as "noted, not filed". It is reachable in principle from a served body, since the seam passes execute through, and triage's routing answer does not name it. Ask triage: fold into this card's next claim (same seam class) or file in the engine lane. Not a wrong in this diff.
  • Observation for the family census, not a dev flag: host-code hooks registered directly with the engine receive the engine-built hook api, not the sandbox's, so this seam does not serve them. Deployer-authored, not a context the card named, not measured. Routing, not this PR.

Implemented-by: claude/issue-21454-reach-measurement
Reviewed-by: session_016GiHYRmLSNWTfbX9gVQkpz

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 3, 2026 02:31
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 3, 2026 02:31
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants