Repository navigation
security(metadata-protocol, objectql, core): platform store reads and writes reach the engine with no principal and no system opt-in — the engine-lane producers of #21908's closure #21911
Description
Activity
- addedpriority: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 guardsand removed
on Oct 5, 2026 objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsClaim: PM loop round 3 · 2026-10-05T21:55Z
Session:session_011K3zqE8Pv1Evw5hc8tZCnN
Account:os-steve(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21911-principal-less-producers-engine
Worktree:objectstack-issue-21911
Domain:domain:services
Seat:domain:services#1(seat post #6021)
File surface (atorigin/maincab63967), per the seat's verdict on #21908 (6003795556):packages/metadata-protocol/src/protocol.ts(the functions of rows 1, 2, 3, 4 and 6) andsrc/sys-metadata-repository.ts(row 5),packages/objectql/src/plugin.ts(row 9) andpackages/core/src/fallbacks/authored-translation-sync.ts(row 10). Their unit tests, theisSystemcensus page, and apatchchangeset per package. Cross-lane on [PM seat] domain:engine — ⏳ vacant #6367 in this act.
⛔ No edit inplugin-security,packages/specorpackages/qa. ⛔ The deny is security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908's.
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; default tier).
Clause-②: no- Each producer takes the explicit system opt-in that exists today. A producer on which a gate before the hand-off fires is reported, not moved, so nothing accepted or refused changes today.
Thread-read: 6003795556
Serial constraints cleared: at 2026-10-05T21:55Z: hotlong's PR fix(rest,metadata-protocol): a public form's intake withdrawal at any metadata layer holds; layering can only narrow intake #21864 (security(forms): a public-form setting at one metadata layer can re-open intake that another layer withdrew — 17.7 regression, detail withheld pending maintainer #21835) also editsmetadata-protocol/src/protocol.ts, in the public-form kill-switch region. This card keeps to its named functions and test-merges fix(rest,metadata-protocol): a public form's intake withdrawal at any metadata layer holds; layering can only narrow intake #21864's head before opening its PR. A conflict stops it.
Selection: the lane, read fresh at this pick, holds security(metadata-protocol, objectql, core): platform store reads and writes reach the engine with no principal and no system opt-in — the engine-lane producers of #21908's closure #21911, security(plugin-auth, runtime): raw-engine reads and writes outside the adapter's system context reach the engine principal-less, two of them behind a fail-open catch — the identity and runtime producers of #21908's closure #21912 and security(service-settings, service-messaging, service-datasource, plugin-webhooks): plumbing reads and writes reach the engine with no principal and no system opt-in — the services-lane producers of #21908's closure #21913 (p1, this seat's slices of security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908) and security(automation): a paused flow run's stored state keeps record-change values that #21830's mask removes elsewhere — detail withheld pending maintainer #21867 (p2security, withheld; by the maintainer's routing it is dispatched by the session holding its detail). This card and security(service-settings, service-messaging, service-datasource, plugin-webhooks): plumbing reads and writes reach the engine with no principal and no system opt-in — the services-lane producers of #21908's closure #21913 take two of the three slots. security(plugin-auth, runtime): raw-engine reads and writes outside the adapter's system context reach the engine principal-less, two of them behind a fail-open catch — the identity and runtime producers of #21908's closure #21912 waits on PR fix(plugin-auth)!: implicit account linking requires the standard local-ownership condition; unlink is honoured #21872 (auth-manager.ts).
Generated by Claude Code
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21911,
"status": "done",
"branch": "claude/issue-21911-principal-less-producers-engine",
"pr": "#21938",
"session": "session_011K3zqE8Pv1Evw5hc8tZCnN — subagent of the PM session (harness Claude-Session line)",
"premise_still_valid": true,
"summary": "Every engine call in the card's functions now passes the explicit system opt-in, context { isSystem: true }, or { ...ctx, isSystem: true } inside a SysMetadataRepository transaction so the handle rides along. That is 32 calls in metadata-protocol (rows 1-6), 5 in objectql (row 9) and 2 in core (row 10). No new API, export or door change. None of the six pre-hand-off gates applies to the sys_metadata family: four are scoped to other objects, the engine-owned gate needs a userId, and the delegated-admin gate covers only the RBAC link tables and sys_member. A local instrument confirmed it: 0 gate throws on 35248 + 388 principal-less operations before the change, and 0 would-fire on a dry run over every moved call after it. The same instrument recorded 0 principal-less operations from these functions after the change, over the dogfood suite and a booted showcase composition. The unit pins and four ablations are red as expected. objectql's protocol suites needed test-side updates, so H4 was falsified test-side only (see deviations).",
"tests": "Unit pins: metadata-protocol protocol.platform-store-system-opt-in.test.ts 3/3; objectql plugin-authored-actions + hooks 25/25; core authored-translation-sync 6/6. Ablations via scripts/ablation-replace.mjs, each restored with blob == HEAD and git diff HEAD empty, direction red as expected: (1) findServedOverlayRow opt-in dropped, 2/3 red; (2) put history-insert reverted to { context: ctx }, 2/3 red; (3) readAuthoredHookRows first read set to isSystem:false, 1/11 red; (4) readAuthoredTranslationLayer first read set to isSystem:false, 1/6 red. The first attempt at 3 and 4 was refused by the tool as a no-op (the replacement was a prefix of the anchor), so nothing ran; they were re-run. Suites at 89ced04: metadata-protocol 217 files passed + 3 skipped, 27921 tests passed; objectql (local+repo) 376 files, 7476 tests passed; core 80 files, 2226 tests passed. typecheck for all three exit 0, including the test-typecheck legs. The final merge to 9fd7113 brought only two docs pages and one rest test. Instrumented dogfood (7 chunks, 206 files): before and after both 205 passed + 1 skipped, 1590 tests passed + 9 skipped, every chunk vitest-exit 0. Showcase boot before/after: admin reads 200, anonymous 401. Gates at 9fd7113: 76/76 derived lines exit 0; --ran reconciles 76 derived, 76 run, 0 NOT-MEASURED. Narrowed lint at 9fd7113: the population is read from eslint.config.mjs; eslint --no-inline-config --format json over the 13 changed .ts files gave 13 files, 0 errors, 0 warnings; the config enables no type-aware linting, so no untouched file's verdict can move.",
"mcp_calls": "0",
"api_writes": "3 — (1) POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft) via relay run 37404376030, read back 11218/11218 bytes identical; (2) POST /issues/21938/assignees os-steve via scripts/pm/label-write.mjs, relay run 37404436822, read back matches; (3) POST /issues/21911/comments, this os-dev-report, via scripts/pm/post-stamped.mjs. git push is not REST. Note (#21892): the pr_create output names its relay run but does not carry the literal phrase 'via the relay run'; label-write's does.",
"producers_moved": [
"row 1 findServedOverlayRow",
"row 2 overlayLockLayerAt",
"row 3 readActiveOverlayRows/queryByOrg, readFlattenedMetaItems",
"row 4 foldStoredCollection (the reads of assertRuntimeAuthoringRules)",
"row 5 SysMetadataRepository get/put/delete/promoteDraft/restoreVersion/listDrafts/nextItemVersion/nextEventSeq",
"row 6 recordMetadataAudit, persistPackageCommitRow, publishPackageDrafts, resolveOverlayPackageBinding, storedFlowBindingAgrees, deletePackage, duplicatePackage, reassignOrphanedMetadata",
"row 9 ObjectQLPlugin.readAuthoredActionRows/readAuthoredHookRows",
"row 10 readAuthoredTranslationLayer"
],
"producers_reported": [],
"private_producers": "0",
"instrument_counts": {
"findServedOverlayRow": "dogfood 13614 -> 0 (isSystem after: 13618); boot 80 -> 0 (80)",
"overlayLockLayerAt": "dogfood 13736 -> 0; boot 80 -> 0 (isSystem-side count 10 is a probe artifact: the function is not async, so it is absent from the async stack the probe filtered system records by)",
"queryByOrg / readActiveOverlayRows": "dogfood 2037 -> 0 (2037); boot 16 -> 0 (14)",
"readFlattenedMetaItems (draft preview reads)": "dogfood 0 -> 0; boot 0 -> 0 (no measured run used previewDrafts; covered by the unit pin)",
"foldStoredCollection (the reads of assertRuntimeAuthoringRules)": "dogfood 761 -> 0 (761)",
"SysMetadataRepository.get / put / delete": "dogfood 169 / 296 / 41 -> 0 (169 / 296 / 41)",
"SysMetadataRepository.promoteDraft / restoreVersion / listDrafts": "dogfood 6 / 2 / 1 -> 0 (6 / 2 / 1)",
"SysMetadataRepository.nextItemVersion / nextEventSeq": "dogfood 105 / 105 -> 0 (105 / 105)",
"recordMetadataAudit / persistPackageCommitRow": "dogfood 110 / 1 -> 0 (110 / 1)",
"publishPackageDrafts / resolveOverlayPackageBinding / storedFlowBindingAgrees": "dogfood 1 / 1 / 6 -> 0 (1 / 1 / 6)",
"deletePackage / duplicatePackage / reassignOrphanedMetadata": "dogfood 1 / 1 / 2 -> 0 (1 / 1 / 2)",
"ObjectQLPlugin.readAuthoredActionRows / readAuthoredHookRows": "dogfood 810 / 496 -> 0 (810 / 496); boot 3 / 2 -> 0 (3 / 2)",
"readAuthoredTranslationLayer": "dogfood 496 -> 0 (496); boot 2 -> 0 (2)",
"ALL principal-less ops, any producer": "dogfood 35248 -> 2476; boot 388 -> 204 (the remainder are the other slices' producers: settings, messaging, storage, auth, webhooks, datasource)"
},
"gates": [
{
"command": "git push -u origin claude/issue-21911-principal-less-producers-engine (empty-branch probe)",
"exit": 0
},
{
"command": "os-verify-lock: instrumented dogfood chunks 00..06 before / after",
"exit": "0 x7 / 0 x7"
},
{
"command": "os-verify-lock: showcase boot probe before / after",
"exit": "0 / 0"
},
{
"command": "os-verify-lock: ablations 1, 2, 3b, 4b (vitest red as intended)",
"exit": "1 x4, restored blob == HEAD"
},
{
"command": "os-verify-lock: typecheck metadata-protocol + objectql + core at 89ced04",
"exit": 0
},
{
"command": "os-verify-lock: vitest metadata-protocol / objectql / core at 89ced04",
"exit": "0 / 0 / 0"
},
{
"command": "dispatch-gates --commands at 9fd7113: all 76 lines",
"exit": "0 x76"
},
{
"command": "dispatch-gates --ran",
"exit": 0
},
{
"command": "eslint --no-inline-config --format json over 13 changed .ts files at 9fd7113",
"exit": 0
},
{
"command": "H5 test-merge of #21864 head d8657b5 onto 9fd7113",
"exit": "0, no conflict, 16/16 protocol calls keep the opt-in"
}
],
"line_budget": "n/a",
"deviations": [
"H4 falsified test-side: objectql's protocol suites held 7 exact-argument expectations (protocol-meta) plus the reassign rebind and the listDrafts WHERE; each now includes context { isSystem: true }. The two revert/rollback conflict pins (protocol-commit-history, protocol-writepath-object-ownership) identified put's in-transaction read by a bare 'context' key, which every repository read now carries. Their engine now hands its transaction callback a handle and the pins discriminate on the handle. metadata-protocol and core suites passed unchanged.",
"scripts/engine-double-contract.pinned.json gains 3 rows (--write, the grow-only coverage ledger) for the new pin's double, copied from a pinned one. This is outside the claim's declared file surface, and check:engine-double-contract requires it.",
"Out-of-scope-for-the-gates note: under isSystem the engine's referential-integrity check on a caller-supplied lookup also stands down. On these writes the only lookup it judged was sys_metadata.organization_id (door-derived). The probe recorded 0 refusals. It is stated in the changeset and the PR.",
"origin/main moved four times during the run (cab6396 -> e6dc7a2 -> 54fb60a -> be97cf3 -> faf8dce -> 9dce635). Each was merged, and the closure rebuilt and re-verified as stated.",
"Gate first passes: check-engine-split-ratio refused on the shallow clone (deepened with --shallow-since=2026-06-30); check:dual-build-cjs-loads needed unbuilt dists and a plugin-audit declaration (built); check:type-check-debt's own re-measure build of service-messaging died under box contention once. All three were green on every later run, including the final one.",
"The instrument's boot step and both dogfood runs used dists built at the commit under test; the instrument was never committed, its source blob is back at HEAD 5b4ab280, and plugin-security dist was rebuilt clean (ablation-dist-preflight --absent passed)."
],
"files_changed": [
".changeset/21911-principal-less-producers.md",
"packages/core/src/fallbacks/authored-translation-sync.ts",
"packages/core/src/fallbacks/authored-translation-sync.test.ts",
"packages/metadata-protocol/src/protocol.ts",
"packages/metadata-protocol/src/sys-metadata-repository.ts",
"packages/metadata-protocol/src/protocol.platform-store-system-opt-in.test.ts",
"packages/objectql/src/plugin.ts",
"packages/objectql/src/plugin-authored-actions.test.ts",
"packages/objectql/src/plugin-authored-hooks.test.ts",
"packages/objectql/src/protocol-commit-history.test.ts",
"packages/objectql/src/protocol-meta.test.ts",
"packages/objectql/src/protocol-package-lifecycle.test.ts",
"packages/objectql/src/protocol-writepath-object-ownership.test.ts",
"packages/objectql/src/sys-metadata-repository-list-drafts.test.ts",
"scripts/engine-double-contract.pinned.json"
],
"open_questions": [],
"out_of_scope_findings": [
"class: b (same family as #21908's closure, not single-point) · reach: static only; no measured run reached them (0 records in either probe) · SysMetadataRepository.getByHash, list, history and replayFromHistory still reach the engine with no context; they are outside the card's named list and belong to #21908's producer census, so name them to that closure card · dedupe words: principal-less producer, SysMetadataRepository, getByHash, replayFromHistory, isSystem opt-in",
"carrier: #21908 (the deny card) · noted, not filed: the census page needed no edit, since object-literal isSystem producers are not elevation reads (check-system-context-census green, counts unchanged)"
],
"resume": "The container restarted at about 01:15Z, before the report. Found: remote branch = HEAD = 3d50c58, worktree clean, no PR, no report comment, card carries only the claim. Every measurement completed before the restart has its VERDICT line and stands: the before/after instrument runs, the ablations, H5, the gate battery at 3d50c58, and the objectql final suite (green 375/7471). In flight or never acquired, so NOT MEASURED and re-run: the metadata-protocol final suite (queue-timeout 99) and the core final suite (cut by the restart). main had moved (+2, one objectql commit), so I merged (89ced04), rebuilt, and re-ran typecheck, all three suites, the gate battery and --ran, H5 and lint. main then moved again (docs + one rest test); I merged that (9fd7113) and re-ran the gate battery, --ran, H5 and lint at that head. Then pr_create, label-write and this report."
}
Generated by Claude Code
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsReview: PR #21938 at
9fd7113eaa· seatdomain:services#1(#6021) ·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-06T02:34ZACCEPT. Verdict on the dev report
6008139342. Checked against the PR, not the report:- What moved. 15 files: the card's functions in
metadata-protocol(rows 1 to 6),objectql(row 9) andcore(row 10), their pins, theobjectqlprotocol suites' expectations, andscripts/engine-double-contract.pinned.json. The changeset ispatchfor the three packages,Clause-②: noon PR line 2 and in the changeset. - The trap was measured, not assumed. None of the six gates before the hand-off applies to the
sys_metadatafamily: four are scoped to other objects, the engine-owned gate needs a user id, and the delegated-administration gate covers only the RBAC link tables. The instrument recorded 0 gate throws before the change. After the change it recorded 0 principal-less operations from these functions over the dogfood suite and a booted composition. - Ablations: four, each red as intended, each restored with blob equal to HEAD.
- Accepted deviations:
- The
engine-double-contractledger gains 3 rows. That is the grow-only ledgercheck:engine-double-contractrequires, outside the declared surface but forced by a gate. - The
objectqlprotocol suites' exact-argument expectations now include the opt-in. That is test-side only. - Under
isSystemthe engine's referential check on a caller-supplied lookup also stands down. On these writes the only judged lookup is the door-derivedorganization_id, with 0 refusals measured, and the changeset states it.
- The
- Serial: a test-merge with
hotlong's PR fix(rest,metadata-protocol): a public form's intake withdrawal at any metadata layer holds; layering can only narrow intake #21864 head is clean, and every protocol call keeps the opt-in. - Contract review: not owed. There is no
packages/specsource, noClause-②: yesand no governed text.
Carried to #21908 (the closure's census):
SysMetadataRepository.getByHash,list,historyandreplayFromHistorystill reach the engine with no context. They were found by static read, with 0 records in either run, and are outside this card's named list.
Generated by Claude Code
- What moved. 15 files: the card's functions in
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsLanded · seat
domain:services#1·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-06T04:31Z. ⛔ Classes, positions and functions only.- PR fix(metadata-protocol, objectql, core): platform store reads and writes carry the explicit system opt-in #21938 merged through the merge queue as
d16b9fbf, which is an ancestor oforigin/main(verified withgit merge-base --is-ancestorafter a fetch). Merged at 2026-10-06T04:31Z. The earlier redLint & Repo Gateswas inherited frommain(the seat's note6008329315). A base merge of test(dogfood): each file's temporary cwd is created from a base the scratch-dir scan can read #21935 (9e33ee7c59) cleared it. Fixes #21911closed this cardcompleted. In this act the seat clears thepm:dispatchedstate label and the assigneeos-steve. Thedomain:services,area:access,securityandpriority:p1labels stay.- What shipped: the platform's own
sys_metadatareads and writes inmetadata-protocol(rows 1 to 6),objectql(row 9) andcore(row 10) carry the explicit system opt-in instead of reaching the engine with no principal. No gate before the hand-off fires on them, and measured door answers are unchanged. The changeset ispatchfor the three packages. - Carried to security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908:
SysMetadataRepository.getByHash,list,historyandreplayFromHistory(census row 23).
Generated by Claude Code
- PR fix(metadata-protocol, objectql, core): platform store reads and writes carry the explicit system opt-in #21938 merged through the merge queue as
- added a commit that references this issue
on Oct 7, 2026
Why now. #21908 closes the principal-less hand-off in the security middleware: a non-system engine context with no principal will be denied (
403 PERMISSION_DENIED, the seat's verdict on #21908). The measure-first round (6003676228) found the producers that reach the hand-off today. Each must take a route before the deny lands, or the deny breaks it. This card is one slice, a seat-owned sub-issue of #21908 with its domain and priority (domain:servicesseat 1, #6021,session_011K3zqE8Pv1Evw5hc8tZCnN).Part of #21908.
The route for every producer here: the explicit system opt-in that exists today (
isSystem: trueon the engine call's context). ⛔ No new elevation API. ⛔ No change to what any door authorizes.The trap to measure, per producer: an
isSystemcontext short-circuits the gates the hand-off still runs beforenext(): package-managed, system-row, curated-capability, audience-anchor, engine-owned, and the delegated-administration gate. Moving a producer is neutral today only if none of those gates fires on its calls. Measure that per producer (an instrumented run is fine; commit nothing of it). A producer on which a gate fires is reported, not moved.The producers (rows of
6003676228, positions onorigin/maincab63967):metadata-protocolsrc/protocol.tsfindServedOverlayRow(sys_metadata). It is also reached at boot throughplugin-security's permission-set reconcile.overlayLockLayerAt.queryByOrg/readActiveOverlayRows/readFlattenedMetaItems. These are on every data request through the API-exposure gate.foldStoredCollection/assertRuntimeAuthoringRules.src/sys-metadata-repository.tsSysMetadataRepositoryget/put/delete/promoteDraft/restoreVersion/listDrafts/nextItemVersion/nextEventSeq.recordMetadataAudit,persistPackageCommitRow,publishPackageDrafts,resolveOverlayPackageBinding,storedFlowBindingAgrees,deletePackage,duplicatePackageandreassignOrphanedMetadata.objectqlsrc/plugin.tsreadAuthoredActionRows/readAuthoredHookRows(boot and resync).coresrc/fallbacks/authored-translation-sync.tsreadAuthoredTranslationLayer(boot and resync).Done when: each producer above passes the explicit system opt-in, or is reported with the gate that fires on it. An instrumented run of the dogfood suite and a booted dev composition records no principal-less context from these functions. The packages' suites are unchanged, and the
isSystemcensus page (check-system-context-census) is current.Cross-lane:
packages/metadata-protocol,packages/objectqlandpackages/corearedomain:enginepackages. The seat declares the edit on #6367 when it claims.Generated by Claude Code