Repository navigation
[Decision] security(runtime): may an app-authored body touch the stored-metadata family's tables at all — a hook bound to them, or an elevated body writing them directly (#21454 items 3 and 4) #21520
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p1High: required for production / M2High: required for production / M2area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guards
on Oct 3, 2026 objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsRuling: batch #272 item 1 · letter A · maintainer 「同意」 2026-10-03T03:26Z
Director seat, summon #32,
session_016tKoy8NJa35Yih1FdzrVmn. Written asobjectstack-fleet[bot]through the relay. ⛔ Classes, doors and roles only.- How it was ruled. Batch 🔗 Broken links detected in documentation #272 was presented in the live director chat with options A, B and C. The director recommended A, with fallback B, and measured the card's two NOT MEASURED premises (below). The maintainer answered the batch as presented: 「同意」.
- The freshness gate: this card has no comment.
The census that filled premise 1 (read this session)
Zero app bodies bind a hook to the stored-metadata family's tables, and zero app bodies write them directly, in objectstack
examples/**(ad7c351898), hotcrm (a9bb57b3ec) and cloud (c5a4c9e6cb). The onlyobject: 'sys_metadata'hits are the platform's own list views inpackages/metadata-core. So A breaks no shipped app.The ruling
A: an app-authored body may not touch the stored-metadata family's tables.
- Binding a body hook to a family table is refused at registration, with a prescription naming the metadata API.
- A direct write to a family table by an elevated body, through the in-process write verbs, is refused at the seam, with the same prescription.
- Changes to metadata go through the metadata protocol only, where validation and provenance live.
This is now governing text: for app-authored bodies, the metadata protocol is the family's only writer. No ADR stated it before.
- Platform code is outside this ruling: the metadata protocol, its own internal writers and the platform's own objects.
- The census of platform writers (premise 2) is the claim's to take before the refusal lands, so the seam refuses bodies only.
Not taken:
- B: a projected hook context with writeback refusal, and redirecting writes. Its hard case, where a hook's input is also its write-back channel, is the kind of special case that leaks over time.
- C: elevated means trusted, projecting returns only. That leaves a silent copy-out path.
四棱(本裁决新记录)
- ① 长远:这组表只有一个写入方(元数据协议:校验 + 来源记录);在注册/写入时拒收,不在运行时补丁(Prime Directive 12)。
- ② 拉动:口子已实测;正当用途四仓零处。
- ③ 防 AI:绑定或写入当场按名拒收并给处方;C 是静默外泄。
- ④ 不扩散:两处拒收,无新机制。
- 只看①选 A;②③④ 是否翻转:否。
Execution parameters (ruled here; no further decision card)
- This card:
needs-user-decision→pm:queuein this act,domain:cli, p1,security. - The two refusals: at hook registration for a family table, and on the elevated body's write verbs for a family table.
- Each refusal carries a ledgered code and a prescription.
- Pins: the refusal fires on each family table; an ordinary table binds and writes as before; the metadata door's own save still fires the platform's internal hooks.
Clause-②: yes (narrowing), with an at-tier contract review.
- Independent of this ruling, already routed on [finding] [security] An action/automation body's object API and an action handler's engine handle read the stored-metadata family outside its body projection and keyed serve (reach NOT MEASURED) #21454: projecting what a write verb returns at the seam, and the evaluate-shape refusals.
Generated by Claude Code
- added a commit that references this issue
on Oct 3, 2026 objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsPointer from the
domain:cliseat (session_016GiHYRmLSNWTfbX9gVQkpz) · 2026-10-03T04:39Z · ⛔ Not a claim. ⛔ Classes, doors and roles only.One position for this card's claim, measured by the #21454 round-2 dev (os-dev report
5965573345, out-of-scope finding 1): a write verb's ownwherepredicate over the family's body or content-hash column evaluates it. It is the same oracle a read's predicate is, measured through an elevated sandboxed body's write verbs. PR #21539 serves write RETURNS and deliberately leaves write predicates alone, because refusing a body's family-table write is this card's ruling (A,5965059068). Refusing the write at the seam closes this predicate path with it.Where it attaches: the same write verbs
packages/runtime/src/stored-metadata-reader-seam.tswraps, inserveRepository's write branch, as a throw BEFORE the write runs. PR #21539 names the point in a code comment.Serial: this card is held behind PR #21539 (#21454), which edits the same seam file.
Generated by Claude Code
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 (moved to the front by the maintainer: 「21520 插队」)
Session:session_016GiHYRmLSNWTfbX9gVQkpz
Account:os-bill(the seat's linked user asget_meanswers it; the card's assignee)
Branch:claude/issue-21520-family-body-boundary
Worktree:objectstack-issue-21520
Domain:domain:cli
Seat:domain:cli#1
File surface, per the ruling5965059068(A, maintainer 「同意」) and triage5965718076(the write predicate rides this claim):- census first (the record's premise 2): every platform writer of the stored-metadata family, so the refusals hit app-authored bodies only;
- the body-hook binding point (measured; the runtime's artifact handler binder, or wherever a body hook is registered against an object): a body hook bound to a family table is refused at registration, with a prescription naming the metadata API;
packages/runtime/src/stored-metadata-reader-seam.ts: an elevated body's write verbs on a family table are refused at the seam before the write runs, which also closes the write verb's own predicate path;- the pins: the refusal fires on each family table; an ordinary table binds and writes as before; the metadata door's own save still fires the platform's internal hooks;
.changeset/.
Codes: each refusal carries a ledgered code. The ledger ispackages/spec/src/api/error-code-ledger.zod.ts, which isdomain:spec's. An existing ledger code that fits is used as-is. A NEW code is a spec-lane edit, so the dev stops and reports it, and this seat routes it. ⛔ No edit underpackages/spec.
⛔ Platform code is outside the ruling. ⛔ No projected-hook-context or write-redirect mechanism (B, not taken). (stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default (opus)— p1security, so the build is at the default tier, and the contract review of record is owed atCONTRACT_REVIEW_TIER(isolated subagent) before enqueue
Clause-②: yes (narrowing)
Thread-read: 5965718076
Serial constraints:stored-metadata-reader-seam.tsis held by PR fix(runtime)!: refuse the stored-metadata family evaluate shapes and serve write returns at the reader-context seams #21539 ([finding] [security] An action/automation body's object API and an action handler's engine handle read the stored-metadata family outside its body projection and keyed serve (reach NOT MEASURED) #21454 round 2, in its contract review).- The census, the binding refusal and the pins land first, on
origin/main. - The seam's write refusal attaches to PR fix(runtime)!: refuse the stored-metadata family evaluate shapes and serve write returns at the reader-context seams #21539's write branch:
- if fix(runtime)!: refuse the stored-metadata family evaluate shapes and serve write returns at the reader-context seams #21539 has merged by then, the branch merges
origin/main; - if not, it merges PR fix(runtime)!: refuse the stored-metadata family evaluate shapes and serve write returns at the reader-context seams #21539's head and the PR stays draft until fix(runtime)!: refuse the stored-metadata family evaluate shapes and serve write returns at the reader-context seams #21539 lands. ⛔ Never a parallel edit of the seam.
- if fix(runtime)!: refuse the stored-metadata family evaluate shapes and serve write returns at the reader-context seams #21539 has merged by then, the branch merges
- Other open PRs were read 2026-10-03T06:03Z on
origin/mainfd5a1cd597. In flight on this seat:- [finding] os migrate resume, recorded-by and value-shapes exit 1 on a project whose database does not exist yet, with an opaque "The database refused to run this query" from their own first read of a deferred table #21529: the migrate commands,
migration-recovery-plugin.ts; - PR fix(cli): os dev -a and os start --artifact serve the named artifact beside a cwd objectstack.config.ts — one artifact precedence for start, dev and the serve child #21549:
dev,start,serve; - PR fix(mcp): serverInfo.version defaults to the package version, not a literal 1.0.0 #21548:
packages/mcp.
All are disjoint. [finding] os init and os compile print a refusal twice across two streams: a printError line on stdout, then this.error re-renders the same sentence as oclif's Error block on stderr #21542's claim was withdrawn for this slot (5966173470).
- [finding] os migrate resume, recorded-by and value-shapes exit 1 on a project whose database does not exist yet, with an opaque "The database refused to run this query" from their own first read of a deferred table #21529: the migrate commands,
domain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· 2026-10-03T06:03Z
Generated by Claude Code
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 21520, "status": "done", "branch": "claude/issue-21520-family-body-boundary", "pr": "https://github.com/objectstack-ai/objectstack/pull/21563", "session": "session_016GiHYRmLSNWTfbX9gVQkpz (the dispatching PM session; this run is its subagent)", "premise_still_valid": true, "summary": "Ruling A is implemented with two refusals in packages/runtime, each PERMISSION_DENIED / 403 with a prescription naming the metadata API. BINDING: a hook with a sandboxed body whose object names sys_metadata or sys_metadata_history (string or list form) is refused at registration in hookBodyRunnerFactory. That is the one point every door's body hooks pass through: the artifact binder (boot, install-local install and rehydrate) and the runtime-authored metadata-service bind through the engine's default runner. bindAppArtifactHandlers alone would miss that last door. A wildcard body hook still binds, but its body is never run for a family table's event, and the bind says so once at info. WRITING: a sandboxed body's write of either table through ctx.api is refused before the write runs: every write verb and every derived context, elevated or not. A refused write runs nothing and answers the same whatever its predicate names, which closes the predicate oracle triage carried onto this card. A1's assumption measured FALSE: the read seam is not body-only, because buildActionApi also serves host code handlers. So the write refusal is a separate body-only layer in the seam file (refuseStoredMetadataBodyWrites, sharing one derived-context walk with the read seam), applied only at buildSandboxApi, not a throw in serveRepository's shared write branch. Census by TS AST symbol walk: every platform writer is module code calling the engine or driver, and none goes through a sandboxed body. A3: PERMISSION_DENIED fits truthfully, so no new ledger code and no packages/spec edit. Reach was measured as classes before the fix. PR #21539 landed during the run and origin/main was merged on top, so the PR is not stacked: 7 files vs main.", "tests": "All heavy runs went through scripts/pm/os-verify-lock.sh (slot issue-21520-dev); each verdict line read VERDICT command-exit 0 unless stated otherwise. FINAL HEAD 9a95e459d1: runtime --project local: Test Files 316 passed (316), Tests 4444 passed | 19 skipped. --project repo: Test Files 3 passed, Tests 751 passed. pnpm --filter @objectstack/runtime typecheck: exit 0, with check:test-typecheck OK and the ledger unchanged; tsc --listFilesOnly on tsconfig.test.json lists all 4 new runtime files. New pins: stored-metadata-body-boundary.test.ts 8/8, stored-metadata-body-writes.test.ts 10/10, stored-metadata-body-boundary.pin.test.ts 7/7 (composed kernel, boot in beforeAll); with the merged seam unit test, 35/35 unit cases. REACH (A5), before the fix: the pins ran against BASE fd5a1cd597's body-runner.ts, then the file was restored and proven (blob == HEAD, git diff HEAD empty). Unit tier: 6 red, 2 controls green; a body hook on each family table bound and ran via the artifact binder, the runtime-authored default runner and the wildcard. Composed tier: 4 red, 3 controls green; the metadata door's save ran the explicit, wildcard and runtime-authored family body hooks (neutral markers on the saved row), and an action body's sys_metadata insert answered 200 for both administrator and member. ABLATION, fix committed first, at 0d8af06c80, via scripts/ablation-replace.mjs: each anchor hit 1 to 0, the blob changed, and the restore was proven each leg. Leg A1 (registration throw disabled): 5 red, 20 green; the composed no-body-ran pin held through the dispatch-side check. Leg A2 (dispatch-side check disabled): 2 red, the wildcard pins. Leg B (body write layer removed from buildSandboxApi): 4 red, both sandbox unit pins and both composed write pins. Direction as predicted: red. Pins resolve body-runner.ts by relative path (src), so no dist leg applies. body-runner.ts and the seam file at the final head are byte-identical to the ablated commit's.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack with no paths at 9a95e459d1 derived 62 families. All 62 were run, each exit 0. --ran (with exit codes recorded) reconciled 62 derived, 62 run, 0 NOT-MEASURED (a derived zero). check:dual-build-cjs-loads first answered PREREQUISITE NOT MET (no full build); after turbo run build (72/72 tasks) it measured 106 entries across 66 packages, exit 0. Lint, a proven narrowing (pnpm lint itself is CI's): population from eslint's own config, 6 of the 7 changed paths linted (the changeset .md matches no configuration); count from --format json, 6 files, 0 errors, 0 warnings; invariance: eslint.config.mjs enables no type-aware linting and its only import rule (no-restricted-imports) is per-file. check:nul-bytes OK; control-byte self-scan of the 7 changed paths: grep exit 1 (none). CI: not awaited (in_progress).", "line_budget": "n/a", "mcp_calls": "0", "api_writes": "3. Each is one dispatch to the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]). (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls, draft #21563; read back as 11761 bytes sent and 11761 stored, identical. (2) label-write.mjs --assign os-bill: POST /repos/objectstack-ai/objectstack/issues/21563/assignees; read back MATCHES (the path labeler's size/l is preserved and is not mine; no labels were written, since the dispatch named none and skip-changeset does not apply). (3) post-stamped.mjs: POST /repos/objectstack-ai/objectstack/issues/21520/comments, this os-dev-report. git push is not a REST write.", "deviations": "1. WRITE-REFUSAL ATTACH POINT, which differs from A4, the seat's pointer and the coordinator's later update (all name serveRepository's write branch). Measured: that branch is the repository of every context the read seam serves, including buildActionApi, which is a host code action handler's ctx.api as well as an action body's. A throw there would refuse deployer host code, but the ruling text says the seam refuses bodies only, and triage 5964836549 put deployer host code outside the family. So the refusal is a body-only layer in the same seam file, applied only at buildSandboxApi; the branch's comment now says why. Raised as open question 1 for the seat to confirm. 2. STACKING: per A4, the open PR #21539's head (19b2cb6e50) was merged into this branch, which AGENTS.md Multi-agent §2 calls an unsupported stacked form; the conflict is stated here, not resolved silently. After #21539 landed, origin/main (ce532184d1) was merged on top per the coordinator. The seam file's #21539 bytes equal the landed squash, so the conflict resolved to this branch's side, and the PR's diff vs main is exactly 7 files and not stacked. 3. BINDING POINT: hookBodyRunnerFactory, not bindAppArtifactHandlers (A2): the artifact binder does not see runtime-authored hooks, while the factory is shared by every door. 4. WILDCARD: a '*' body hook is not refused (refusing it would refuse every wildcard body hook, with no ruling for that), but its body is never run for a family table's event, since ruling A says a body may not touch the tables; an info line at bind says so. 5. A5 NARROWING: the composed reach and pins use neutral markers and scalar predicates only. A write predicate over the body or hash columns was not constructed as a composed probe (the card's no-recipe constraint); the unit pin asserts that the refusal answers identically whatever the predicate names and runs nothing. 6. CODE: 'ledgered' is read as a member of the ledger's ErrorCode union (StandardErrorCode plus ERROR_CODE_LEDGER), since the ledger's admission rule sends a generic permission condition to PERMISSION_DENIED. 7. The ablation commit (0d8af06c80) predates the two origin/main merges; the two ablated files are byte-identical at the final head. 8. The harness attribution reminder names a model in its trailer and an emoji PR footer; commits carry the model-free trailer pair and the PR body ends with the AGENTS.md session-URL footer.", "files_changed": [ ".changeset/21520-body-family-boundary.md (new; @objectstack/runtime minor; Clause-②: yes (narrowing); ADR-0087 not-required (no-migration-prescription))", "packages/runtime/src/stored-metadata-body-boundary.ts (new: the two refusal builders, PERMISSION_DENIED / 403, metadata-API prescription)", "packages/runtime/src/sandbox/body-runner.ts (binding refusal in hookBodyRunnerFactory; wildcard dispatch-side check and info line; buildSandboxApi layers the body write refusal)", "packages/runtime/src/stored-metadata-reader-seam.ts (refuseStoredMetadataBodyWrites body layer; the derived-context walk shared as deriveThroughSeam; doc comments updated)", "packages/runtime/src/stored-metadata-body-boundary.test.ts (new, 8 binding pins on a real engine)", "packages/runtime/src/stored-metadata-body-writes.test.ts (new, 10 write pins, counting double plus the real sandbox)", "packages/runtime/src/stored-metadata-body-boundary.pin.test.ts (new, 7 composed-kernel pins)" ], "open_questions": [ { "question": "Confirm the write refusal's attach point. The pointer, A4 and the coordinator's later update name serveRepository's write branch, but that branch also serves a host code action handler's ctx.api (buildActionApi), and the ruling says the seam refuses bodies only.", "options": [ "A: keep the body-only layer in the seam file, applied at buildSandboxApi (as delivered). Bodies are refused; host code handlers keep their writes, served on return.", "B: move the throw into serveRepository's shared write branch. That also refuses deployer host code handlers' family writes, a wider boundary than ruling A sets, needing a ruling of its own." ], "recommendation": "A, because it is what the ruling text says (the seam refuses bodies only), it keeps one derived-context walk shared by both layers, and the census shows no body-reachable platform writer that A would miss. B widens the boundary to deployer code, which triage ruled outside the family." } ], "out_of_scope_findings": [ "class: c · reach: public door, wrong answer, measured. PUT /api/v1/meta/hook/:name, as the administrator, answered 200 for a runtime-authored hook whose body targets sys_metadata, recorded by the composed pin. The runtime then refuses it at bind (logged at error, never fired), so authoring accepts a hook the runtime will not honour. Seam: spec:data HookSchema object target → runtime:sandbox/body-runner.ts hookBodyRunnerFactory (bind refusal); the save door does not consult the boundary. The fix belongs at authoring/publish (a HookSchema refinement, domain:spec, or the metadata door's save validation, protocol.ts, held by PR #21473). The family's collection card, if one exists, is the home. · dedupe words: metadata door accepts hook on sys_metadata; hook body target stored-metadata table refused at bind not at save; runtime-authored hook family table 200 then refused", "carrier: none (承接者:无) · noted, not filed. An expected body refusal (this boundary's, like any body throw) is logged by the existing body-runner catch at error as '[BodyRunner] sandboxed action threw'; the boundary did not change that posture." ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsACCEPT — PR #21563 at
9a95e459d1, pending its contract reviewdomain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· review of record, read on GitHub 2026-10-03T08:12Z · ⛔ classes, doors and roles onlyShape:
- Draft, base
main. First lineFixes #21520. Clause-②: yes (narrowing)is line-initial in the PR body and in the changeset.- The title and summary carry
fix(runtime)!:. - 7 files, all in
packages/runtime, +965 / −18. Not governed. - The branch merged
origin/mainafter PR fix(runtime)!: refuse the stored-metadata family evaluate shapes and serve write returns at the reader-context seams #21539 landed, so the PR is not stacked: its diff againstmainis exactly these 7 files.
Against ruling A (
5965059068) and triage5965718076, read from the diff:- Census first: a TS AST symbol walk finds every platform writer of the family is module code calling the engine or driver. None goes through a sandboxed body, so the refusals hit app-authored bodies only.
- Binding. A hook with a sandboxed
bodywhoseobjectnames either family table, alone or in a list, is refused at registration inhookBodyRunnerFactory. That is the one point every door's body hooks pass through: the artifact binder on boot, install-local install and rehydrate, and the runtime-authored hooks the binder alone would miss.- A
'*'body hook still binds, but its body never runs for a family table's event, and the bind says so once at info. Refusing every wildcard hook would have no ruling behind it.
- A
- Writing. A sandboxed body's write of either table through
ctx.api, on every write verb and every derived context, elevated or not, answersPERMISSION_DENIED/ 403 before the write runs. A refused write runs nothing and answers the same whatever its predicate names, which closes the write-predicate oracle triage carried onto this card. - Codes:
PERMISSION_DENIEDis a ledgered code that truthfully names the condition, so there is no new code and nopackages/specedit. The prescription names the metadata API. - ⛔ No B mechanism (no projected hook context, no write redirect).
The open question (the write refusal's attach point): answered A, as delivered.
- The seat's own pointer (
5965604037) and its update namedserveRepository's shared write branch. The dev measured that branch serves a host code action handler'sctx.apitoo. - The ruling says the seam refuses BODIES only, and triage (
5964836549) put deployer host code outside the family. - So the refusal is a body-only layer in the same seam file (
refuseStoredMetadataBodyWrites), applied atbuildSandboxApi, and it shares one derived-context walk with the read seam. The seat's pointer was imprecise. The ruling text governs.
Pins and reverse verification:
- Binding: 8 unit pins on a real engine. Writes: 10 pins. Composed kernel: 7 pins. The platform's own hooks still fire on the metadata door's save, and ordinary tables bind and write as before.
- Reach was measured red before the fix:
- unit tier: 6 red;
- composed tier: 4 red, including a family body hook fired by the door's save, and an action body's family insert answering 200 for administrator and member.
- Ablation legs A1, A2 and B each went red where predicted, with restores blob-proven.
- Full runtime suite: 4444 passed.
Changeset:
@objectstack/runtimeminor, withClause-②: yes (narrowing);- one ADR-0087 marker,
not-required (no-migration-prescription); - a
**BREAKING**paragraph naming, as classes, what is refused, what is unchanged and the route (the metadata API). It matches the diff.
Gates: 62 derived, all 62 exit 0. The lint narrowing is proven. CI on
9a95e459d1is to be read at landing.needs:contract-reviewis hung on the PR in this act. An at-tier review of record runs once CI settles.Out-of-scope:
- Filed in this act as [finding] The metadata save door accepts a hook whose sandboxed body targets a stored-metadata table, which the runtime then refuses at bind: authoring answers 200 for a hook that never runs #21565: the metadata save door answers 200 for a hook the runtime will refuse at bind. The authoring refusal is missing, and the position is
domain:specor the save door. - Noted, not filed: an expected body refusal logs at error, through the existing catch.
Generated by Claude Code
- Draft, base
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsLanded: PR #21563 →
bd70706713domain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· read 2026-10-03T09:03Z · ⛔ classes, doors and roles only- Merged 2026-10-03T09:03Z through the merge queue (
added_to_merge_queue08:44:47Z), at head9a95e459d1. That is the head the ACCEPT5967073500and the contract review PASS5967280729both read. - Shape:
git rev-list --parents -n 1 bd70706713gives 2 fields, so it is a single-parent squash. The commit is an ancestor oforigin/main. Its 7 files and +981/−18 match the PR. - Content read on
origin/main:packages/runtime/src/stored-metadata-body-boundary.tsis present. Both refusals carryPERMISSION_DENIED/ 403. The binding refusal sits in the body-runner resolver. The write refusal is the body-only layer atbuildSandboxApi. - The card closed
completedviaFixes #21520.pm:dispatchedis stripped in this act. - Carried elsewhere, not on this card:
- The metadata door's save accepts a family-table hook that the bind then refuses: [finding] The metadata save door accepts a hook whose sandboxed body targets a stored-metadata table, which the runtime then refuses at bind: authoring answers 200 for a hook that never runs #21565.
- The reviewer's escalations: [finding] [security] An in-process engine verb passes an object name the registry does not resolve to the driver as a raw table name, so a sandboxed body reads a protected table by a name the data door refuses #21516 (
5967297021) and security(automation): the flow record-read node serves the stored-metadata family unprojected — consume PR #21513's door exports (#21454 item A4) #21519, the flow write node (5967301775).
Generated by Claude Code
- Merged 2026-10-03T09:03Z through the merge queue (
- added 4 commits that reference this issue
on Oct 7, 2026
Ruled: 5965059068 · letter A · 2026-10-03T03:27Z
Filing gate: ② a decision only the maintainer can make. It is a security boundary, and the existing rules do not decide it. Filed by the triage seat (objectstack-wide, seat post #6015,
session_01AavokzJ5DndAwitDXvKy4U). It answers thepm:retriageon #21454 (5964337187), items 3 and 4. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Classes, doors and roles only.Reader who acts: the maintainer, or the director seat. The
domain:cliseat then dispatches the ruled letter.维护者速读
Measured (the #21454 fix dev, os-dev report
5964258278, out-of-scope findings 3 and 4, after PR #21513's seam)packages/runtime/src/sandbox/body-runner.ts,buildSandboxContext.Governing text
5963299937on [finding] [security] An action/automation body's object API and an action handler's engine handle read the stored-metadata family outside its body projection and keyed serve (reach NOT MEASURED) #21454): every door that serves, copies or evaluates the family's stored content projects it and serves it keyed. That rule covers what a body is served. It does not say whether a body may bind to, or write, the family's tables.docs/adrfor sole-writer / only-writer / bypassing-the-protocol wording: no hit on this family.Premises (each with its re-check)
examples/**andobjectstack-ai/hotcrmhook bindings and body write targets.Independent of the ruling (already routed on #21454)
Dedupe: MCP
search_issues, repo-scoped, for 「hook bound to sys_metadata elevated body writes metadata table directly bypass protocol」 → 8 hits, none this question: #21470 (open, the save door's name check), #15206 (open, tenancy of the family), and six closed cards on other subjects.