Repository navigation
service-automation: the exported RunProvenanceContext docstring still says a { flowRunId }-only context falls open ("indistinguishable from passing no context at all"); ADR-0096 D5 now refuses it, and the type is produced nowhere #22345
Description
Activity
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsTriage: first grade,
priority:p3·domain:services·area:access·documentation·pm:queue(findingremoved). Direction: one pass makes every pre-D5 "falls open" reading outsidepackages/spectrueTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T20:09Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/services/service-automation(runtime-identity.ts: theRunProvenanceContextdocstring andRunDataContext) ⇒domain:services. Rationale:packages/services/*belongs to that lane.- Why p3: the published
.d.tstext describes a fall-open that ADR-0096 D5 strict mode (PR feat(plugin-security)!: refuse a principal-less, non-system data-engine context (ADR-0096 D5 strict mode) #22297) removed. Runtime behaviour is already the stricter one, so the cost is a misleading contract sentence, not exposure. - Family: this is the second card of the "pre-D5 reading" family, after docs(spec): ISecurityService TSDoc still says a context with no principal is admitted or keeps its scope; PR #22297 (#21908, ADR-0096 D5 strict mode) refuses it #22302. docs(spec): ISecurityService TSDoc still says a context with no principal is admitted or keeps its scope; PR #22297 (#21908, ADR-0096 D5 strict mode) refuses it #22302 covers
packages/specand is in flight with PR docs(spec): ISecurityService and the data-engine contract page state the ADR-0096 D5 refusal of a principal-less context #22327, so it is not merged into this one. To keep a third card from appearing, this card is the closing pass for everything outsidepackages/spec:- Enumerate:
git grepevery comment or docstring outsidepackages/specthat says a principal-less or{ flowRunId }-only context falls open, is admitted, or is skipped by the data security middleware. List the hits on the PR as an enumeration pin. - Fix each hit in the same PR. The two siblings the review named are in scope:
service-automation/src/engine.ts, about:6042–:6046.objectql/src/engine.ts, about:5393. This is a comment-only cross-lane path, in adomain:enginefile; declare it on the PR, and change it only if its sentence asserts the pre-D5 behaviour.
- Enumerate:
- The type: the owning seat decides, as the card says, whether
RunProvenanceContextleavesRunDataContextor stays with text that is true now. Retiring an exported type narrows the public surface:Clause-②: no (narrowing). That stays in this lane and owes one contract-tier review.
- Why p3: the published
- addedarea:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdocumentationImprovements or additions to documentationImprovements or additions to documentationand removed
on Oct 8, 2026 objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsClaim: PM loop round 3 · 2026-10-08T20:18Z
Session:session_01WkL6Eijt432S1Y7ekb6ovQ
Account:os-bill(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-22345-pre-d5-reading-pass
Worktree:objectstack-issue-22345
Domain:domain:services
Seat:domain:services#1(seat post #6021)Executes triage's direction (
6068146867): one pass makes every pre-D5 "falls open" reading outsidepackages/spectrue, with the enumeration listed on the PR as a pin.The type, the owning seat's ruling (four axes):
RunProvenanceContextis retired: out ofRunDataContext, out of the package entry's export and the README's export list.- Business need: the card measures no producer in
packages/**, and since ADR-0096 D5 (PR feat(plugin-security)!: refuse a principal-less, non-system data-engine context (ADR-0096 D5 strict mode) #22297) a{ flowRunId }-only context is refused, so the type declares a context the runtime does not honour. - Long-term:
RunDataContextthen names only the shapesresolveRunDataContextreturns. - AI error-proofing: a published type that reads as a valid data context invites code that is refused at runtime. Declared means enforced.
- Startup stage: a shipped, zero-consumer surface retires now, with no staged window.
- Condition: the dev measures producers and readers first. If any in-repo code builds or reads a
RunProvenanceContextbeyond the union and the export, the type stays with text that is true now, and the seat amends this claim toClause-②: no.
File surface at
origin/main35afb158:packages/services/service-automation/src/runtime-identity.ts: theRunProvenanceContextdocstring and type, andRunDataContext.packages/services/service-automation/src/index.ts(the type export) andREADME.md(its export list, near:451).packages/services/service-automation/src/engine.ts: the comment near:6042-:6046. Comment only.- Every other hit of the enumeration outside
packages/spec, comments and docstrings only, listed on the PR. CHANGELOG entries are historical records and are not rewritten. - Cross-lane, comment only:
packages/objectql/src/engine.tsnear:5393(domain:engine), changed only if its sentence asserts the pre-D5 behaviour. If it is changed, it is declared on the PR and the seat declares it on thedomain:engineseat post. - Tests and changeset: an enumeration pin and one
@objectstack/service-automationchangeset, with the!banner and an ADR-0087 disposition percheck-changeset-no-majorandcheck-adr-0087-registration.
Exclusions:
- ⛔ No
packages/spec(docs(spec): ISecurityService TSDoc still says a context with no principal is admitted or keeps its scope; PR #22297 (#21908, ADR-0096 D5 strict mode) refuses it #22302, PR docs(spec): ISecurityService and the data-engine contract page state the ADR-0096 D5 refusal of a principal-less context #22327). - ⛔ No runtime behaviour change:
resolveRunDataContextand every gate are unchanged. - ⛔ No CHANGELOG text.
Stop on breach; explain in the report.
Container & model:S,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; default tier). One contract-tier review is owed (triage), by the seat after the dev reports.
Clause-②: no (narrowing)- An exported type with no producer leaves the public surface. No runtime answer moves.
Responsibility:this repository's own code: the RunProvenanceContext docstring and two sibling comments still describe the fall-open ADR-0096 D5 removed, and the type names a context D5 refuses | none: no gate checks published docstrings against runtime behaviour | readers of the published .d.ts and agents writing flow data contexts
Thread-read: 6068146867
Serial constraints cleared: - PR feat(spec)!: flow text slots read the {{ }} delimiter, refusing a single-brace token with its hole spelling (#22110) #22315 (
domain:specseat 2, [v18] flow text slots: read ADR-0032 §3's{{ }}delimiter instead of single-brace{token}(notify title/message and the other flow string slots), converting only what renders the same #22110) editsservice-automation'sengine.ts(hunks near:48,:247,:10717,:10969) andREADME.md(near:189). PR fix(objectql): a find/findOne projection that names a formula field returns that projection, not every stored column #22337 (domain:engine) editsobjectql'sengine.ts(near:1534,:1578,:12091onward). Both are region-level overlaps: whichever lands later mergesmain.
- Business need: the card measures no producer in
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22345,
"status": "done",
"branch": "claude/issue-22345-pre-d5-reading-pass",
"pr": "#22357",
"session": "session_01WkL6Eijt432S1Y7ekb6ovQ (subagent = the parent seat session)",
"premise_still_valid": true,
"type_retired": true,
"summary": "Draft PR #22357 (head f3a9675, which merges main at 54c3ce1; no conflict, PR #22337 landed meanwhile). RunProvenanceContext is retired: it is gone from runtime-identity.ts, from the src/index.ts type export and from the README export list. RunDataContext is nowinterface RunDataContext extends RunIdentityContext {}, exactly what resolveRunDataContext returns. An interface, not an alias: an alias prints as 'RunIdentityContext | undefined' and re-spells plugin-approvals' ledgered TS2352 signature (probed with tsc 6.0.3). 24 comment or docstring sites in 11 packages that said, in the present tense, that a principal-less or { flowRunId }-only context falls open, is handed through or is skipped by the data security middleware now say it used to, and that ADR-0096 D5 refuses it. With comments stripped, 18 of the 20 changed .ts files are byte-identical to base; the other 2 differ only by the type declarations and the export entry. The objectql sentence (H4) is unchanged: it makes no admission claim. One changeset: service-automation minor (BREAKING, narrowing), plus patch for 4 packages whose edited comment text ships in dist.",
"h1_census": {
"base": "35afb15878",
"RunProvenanceContext_refs": ["packages/services/service-automation/src/runtime-identity.ts:75 declaration", "runtime-identity.ts:124 RunDataContext union", "src/index.ts:195 type export", "README.md:451 export list"],
"RunDataContext_refs": ["runtime-identity.ts:124 declaration", "runtime-identity.ts:178 resolveRunDataContext return type", "runtime-identity.ts:275 stampSystemInsertOwner param", "src/index.ts:195", "README.md:450", "packages/plugins/plugin-approvals/test-typecheck-debt.json:43: a ledgered TS2352 naming 'RunDataContext | undefined' for record-lock-schedule-run.integration.test.ts:150, which casts the resolveRunDataContext result to a record"],
"producers_of_flowRunId_only_engine_context": "production: none. Every data node goes through resolveRunDataContext (crud-nodes.ts:484/572/720/812, plugin.ts:1248). engine.ts:6079 stamps flowRunId on the AutomationContext, not on an engine context. Tests only: objectql/src/engine.test.ts:601 (bare engine, hook provenance pin), plugin-security delegated-admin-gate.test.ts:137 and system-write-guard.test.ts:94 (gate units), principal-less-strict-mode.test.ts:135 (refused). The HookContext provenance fixtures in the plugin-approvals, plugin-audit and service-storage tests are hook shapes. None names the type.",
"non_data_provenance_uses_claimed_by_docstring": "none. The #3712 consumer is the approvals lock, which reads HookContext.provenance.flowRunId (plugin-approvals lifecycle-hooks.ts:489), built by objectql buildProvenance from any ExecutionContext's flowRunId. RunIdentityContext already carries flowRunId.",
"sibling_objectui_pin": "objectui a58626c88d (shallow fetch into scratch, now deleted): git grep -E 'RunProvenanceContext|RunDataContext' exit 1; control 'service-automation' matches 14 files"
},
"h2": [
"plugin-security/src/security-plugin.ts:2451:if (opCtx.context?.isSystem) return next(), which runs first",
"security-plugin.ts:2654-2656: isPrincipalLessContext (:383-387) → throw principalLessDenial = PermissionDeniedError 403 PERMISSION_DENIED for every verb. It runs after the package-managed, system-row, curated-capability, audience-anchor, engine-owned-write and delegated-admin gates.",
"security-plugin.ts probes: getReadFilter :5947 returns the deny filter; canReadObject :6384 false; canExport :6778 false. Commit a3bcbcf (PR #22297): merge-base --is-ancestor origin/main exit 0.",
"resolveRunDataContext, runAs:'system': { isSystem: true, actor: 'svc:flow:NAME', userId?, tenantId?, positions: [], permissions: [], flowRunId? }",
"resolveRunDataContext, runAs:'user' with a user: { isSystem: false, userId, positions, permissions, tenantId?, flowRunId? }",
"resolveRunDataContext, no user: throws UnscopedRunDataAccessError (AUTOMATION_UNSCOPED_RUN_DATA_ACCESS)"
],
"enumeration": {
"pin": "No test file. The fitting precedent, rest-server-docblock-position.test.ts, declines to pin wording, and nothing parses these comments. The pin is the git grep command listed in the PR body (pattern stored in scratch pin-pattern2.txt), plus its output.",
"pin_counts": "82 lines at base 35afb15; 76 at head f3a9675. Head classes: fixed-here 15, historical 36, true 15, string-not-comment 2, ADR-0056-D2 sibling family 3, other subject 5, unclassified 0.",
"fixed_current_tense_false": [
"service-automation/src/runtime-identity.ts:56-74 RunProvenanceContext docstring (retired)",
"service-automation/src/runtime-identity.ts:85-86",
"service-automation/src/runtime-identity.ts:220-223",
"service-automation/src/runtime-identity.ts:333-336",
"service-automation/src/engine.ts:6057-6058",
"service-automation/src/builtin/crud-runas.test.ts:219-220",
"service-automation/src/builtin/crud-runas.test.ts:258-259",
"service-analytics/src/strategies/objectql-strategy.ts:410-411",
"plugin-security/src/security-plugin.ts:6061-6065 (getMetadataReadableFields)",
"plugin-security/src/get-metadata-readable-fields.test.ts:8-10 and :84",
"platform-objects/src/system/sys-secret.object.ts:50-51",
"metadata-protocol/src/protocol.ts:11233-11237",
"metadata-protocol/src/protocol.platform-store-system-opt-in.test.ts:7-9",
"plugin-auth/src/auth-plugin.ts:2480-2482",
"plugin-auth/src/auth-manager.ts:3384-3386",
"plugin-auth/src/scim-connection-service.ts:121-122",
"plugin-auth/src/principal-less-producers-system-context.test.ts:13-15",
"runtime/src/http-dispatcher.ts:1244-1246 (orphaned facade docblock: NO ExecutionContext → skips)",
"runtime/src/http-dispatcher.ts:1510-1511",
"runtime/src/dispatcher-plugin.endpoint-fallback.integration.test.ts:551-556",
"trigger-record-change/src/record-change-integration.test.ts:395-396",
"qa/dogfood/test/flow-runas-schedule.dogfood.test.ts:10-12",
"qa/dogfood/test/declarative-endpoint-anonymous-guest.dogfood.test.ts:15-17",
"examples/app-showcase/src/automation/flows/index.ts:1584-1586"
],
"historical_left": "release text, not edited in a code PR: .changeset/21908-.md (3 lines), content/docs/releases/v17/17-0.mdx:302. ADR records (Tier H): 0096:11/38/156, 0138:111/635. Comments that say Before / used to / which D5 closes: auth-manager.org-slug-guard-system-context.test.ts:10, http-dispatcher.membership-system-context.test.ts:10, principal-less-strict-mode.test.ts:9, security-plugin.ts:359/2650/6212, zero-set-capability-fold.test.ts:40, zero-set-masking.test.ts:37, webhook-system-context.pin.test.ts:11, datasource-system-context.pin.test.ts:16, inbox-system-context.ts:16, sql-http-outbox.ts:473, service-messaging system-context.pin.test.ts:18, settings-system-context.pin.test.ts:11, metadata-store.ts:143/174/196-198, owner-of-private-object-under-strict-mode.dogfood.test.ts:8. #3597 and #1888 history: analytics-rls.dogfood.test.ts:9/163, flow-runas-fixture.ts:27, flow-runas.dogfood.test.ts:30, execution-context-bridge.test.ts:16, service-analytics plugin.ts:427, objectql-strategy.ts:852. All CHANGELOG.md files.",
"true_left": "auto-enqueuer.ts:30, redeliver-guard.ts:70, datasource-admin-plugin.ts:104, datasource-secret-binder.ts:40, fan-out-system-context.ts:25, outbox-dispatcher-scope.ts:94/118/135, settings-service-plugin.ts:414/538, settings-service.ts:104 (each names the hand-off as what ADR-0096 D5 closes); public-form-grant-masking.test.ts:33/233 (a negation); tenant-audit-update-delete-half-repairs.test.ts:798 (the opt-in replaces the hand-off); this PR's changeset :17",
"string_not_comment_left": "dispatcher-plugin.endpoint-fallback.integration.test.ts:571 and runas-grant-resolution.integration.test.ts:102 (assertion messages); test titles security-plugin.test.ts:2317/2571/2674, can-write-object-admission.test.ts:640, schedule-runas-e2e.test.ts:95-96",
"other_subject": "docs/adr/0111:126, better-auth-schema-parity.test.ts:13, can-write-object-admission.test.ts:576, security-plugin.ts:6521, text-match-sql.ts:237"
},
"h4_reading": "objectql/src/engine.ts:5466-5469, after #22337 (was about :5397-5401): 'A context carrying only write PROVENANCE ({ flowRunId }, all an identity-less flow run has — #3712) is such a case: it says what produced the write, not who is calling, and surfaces through buildProvenance instead.' It does NOT assert the pre-D5 behaviour: it makes no claim that the security middleware admits or skips that context. It describes buildSession returning no session, which is still what the code does. NOT changed, and no objectql file is in the diff. Its parenthetical producer claim has been stale since #3760 and is listed in the acceptance notes (together with :5524).",
"dependents_typecheck": "turbo run typecheck --filter=...^@objectstack/service-automation (18 dependents) plus explicit filters for plugin-security, plugin-auth, runtime, metadata-protocol, platform-objects and service-analytics: 24 packages, all of which declare typecheck. Result 'Tasks: 89 successful, 89 total', 36 cached, at c19dde3 (before the merge). plugin-approvals' check:test-typecheck stayed green with its ledger unchanged. Reverse verification: a probe in plugin-approvals/src (which resolves the service-automation dist d.ts through exports) gave TS2305 'no exported member RunProvenanceContext' and TS2739 '{ flowRunId: string; }' missing isSystem, positions, permissions from 'RunDataContext'. The probe was removed by a trap, and git status --porcelain was empty afterwards.",
"tests": "At head f3a9675. (1) pnpm --filter @objectstack/service-automation exec vitest run --maxWorkers=2: 'Test Files 177 passed (177) / Tests 2171 passed (2171)'; at c19dde3 before the merge it was 176/2163. (2) pnpm --filter @objectstack/service-automation typecheck: 'check:test-typecheck: OK … 0 file(s) / 0 error(s)'. (3) The edited test files, one targeted run each: plugin-security get-metadata-readable-fields 7/7; metadata-protocol protocol.platform-store-system-opt-in 7/7; plugin-auth principal-less-producers-system-context 4/4; runtime dispatcher-plugin.endpoint-fallback.integration 21/21; trigger-record-change record-change-integration 9/9. (4) Full turbo build after the merge: 72/72 tasks successful. (5) Comment-only proof: stripComments plus whitespace collapse over every changed .ts file; 18/20 identical to base, 2 with only the allowed type and export delta. (6) ESLint, narrowed to the 20 changed .ts files: errors=0 warnings=0 (count from --format json). Population: the config block '**/.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'. Invariance: eslint.config.mjs:327-328 says the config never enables type-aware linting, so the narrowing cannot move the verdict on an untouched file. Ablation: none run; the reverse verification above stands in for it.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 79 commands from 22 paths at f3a9675; all 79 were run and exited 0. Reconciled with --ran plus recorded exit codes: '79 derived famil(ies) accounted for — 79 run, 0 NOT-MEASURED (a DERIVED zero — all 79 recorded an exit code and none of them is 3)'. Verdict lines: check-adr-0087-registration '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition … not-required (runtime-interface-only) -- verified: …runtime-identity.ts#RunDataContext (interface)'; check-changeset-no-major 'This diff introduces no major bump'; check-empty-changeset '1 declaring changeset(s) added'; check-system-context-census 'OK — 118 elevation read sites in 20 packages'; check:nul-bytes 'OK (scanned 10334 text file(s) … no raw ASCII control bytes)'; check:dual-build-cjs-loads '106 published require entry point(s) across 66 package(s) load'; check:dts-closure '170/170 declared declaration file(s) present across 72 package(s)'; check:published-files '69 publishable package(s) … declare a files whitelist'; check:cross-package-test-inputs 'OK: 30 package(s) read outside themselves, all declared'; check-issue-citations 'every citation this change adds resolves'; check:type-check-debt 'OK — none above its recorded number'. Compared with the seat's lead list, the derivation dropped check-engine-split-ratio, check:stack-collection-maps and check:swallow-census-controls (no objectql file in the diff) and added auth-mount-ledger, error-status-conformance, examples-live-imports, filter-alias-parity, i18n (3), route-envelope, react-page-adapter-contract, check-dev-prereqs --self-test and spec check:empty-state/liveness/strictness-ledger/variant-docs. All ran. CI: in_progress, not waited on.",
"deviations": [
"Changeset: one file, but besides service-automation (minor, BREAKING) it lists plugin-security, runtime, service-analytics and platform-objects as patch. Their edited comment text measurably ships in dist: plugin-security js+d.ts, runtime js+d.ts, service-analytics js, platform-objects js. The plugin-auth and metadata-protocol comments do not ship (control: the adjacent code tokens are present).",
"ADR-0087 disposition is runtime-interface-only naming #RunDataContext, verified by the gate, not no-migration-prescription. The body's migration line is prose ('There is no replacement … builds its context with resolveRunDataContext'), the shape the merged precedent 8c5aa50 used for exports with no replacement.",
"RunDataContext is narrowed as an interface extending RunIdentityContext, not as a type alias, so plugin-approvals' ledgered diagnostic keeps its name. An alias would have forced a test edit and a ledger re-record outside the fence.",
"The enumeration covered more than the two named siblings: 24 sites in 11 packages, including the private examples/app-showcase and packages/qa/dogfood. All are comment-only, inside the fence's 'comment-only hits in other packages', and listed in the PR.",
"The dependents typecheck sweep ran at c19dde3, before the merge of main. After the merge (PR #22337 landed), a full build, the service-automation test and typecheck, and all 79 gates were re-run at f3a9675. The 24-package typecheck was not re-run: the merged commits move no service-automation export.",
"The full test suites of the comment-only packages and the Dogfood Regression Gate are declared to CI. The narrowing rests on the comment-only proof plus the targeted runs of each edited test file.",
"Git pushes: 5 (an empty-branch probe and 4 WIP and step pushes, as the push-before-heavy-steps rule requires)."
],
"files_changed": [
".changeset/22345-run-provenance-context-retired.md",
"examples/app-showcase/src/automation/flows/index.ts",
"packages/metadata-protocol/src/protocol.platform-store-system-opt-in.test.ts",
"packages/metadata-protocol/src/protocol.ts",
"packages/platform-objects/src/system/sys-secret.object.ts",
"packages/plugins/plugin-auth/src/auth-manager.ts",
"packages/plugins/plugin-auth/src/auth-plugin.ts",
"packages/plugins/plugin-auth/src/principal-less-producers-system-context.test.ts",
"packages/plugins/plugin-auth/src/scim-connection-service.ts",
"packages/plugins/plugin-security/src/get-metadata-readable-fields.test.ts",
"packages/plugins/plugin-security/src/security-plugin.ts",
"packages/qa/dogfood/test/declarative-endpoint-anonymous-guest.dogfood.test.ts",
"packages/qa/dogfood/test/flow-runas-schedule.dogfood.test.ts",
"packages/runtime/src/dispatcher-plugin.endpoint-fallback.integration.test.ts",
"packages/runtime/src/http-dispatcher.ts",
"packages/services/service-analytics/src/strategies/objectql-strategy.ts",
"packages/services/service-automation/README.md",
"packages/services/service-automation/src/builtin/crud-runas.test.ts",
"packages/services/service-automation/src/engine.ts",
"packages/services/service-automation/src/index.ts",
"packages/services/service-automation/src/runtime-identity.ts",
"packages/triggers/trigger-record-change/src/record-change-integration.test.ts"
],
"mcp_calls": "0",
"api_writes": "3, all through the fleet relay (scripts/pm/fleet-write/dispatch.mjs). Each is POST /repos/objectstack-ai/objectstack/dispatches, executed by objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls, giving #22357 (run 37848599477; body read back identical, 23386 bytes); (2) label-write.mjs --issue 22357 --assign os-bill → POST /repos//issues/22357/assignees (run 37848679451; read-back matches); (3) the os-dev-report comment → POST /repos//issues/22345/comments. git push: 5 (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed · sibling family outside this card's definition (ADR-0056 D2 deny baseline: an AUTHENTICATED caller with zero permission sets, not a principal-less one). These say the middleware skips its CRUD gate for an empty set list; the step-2 gate has not been guarded on a resolved set since that change (security-plugin.ts:2956-2964): rest/src/rest-server.ts:2639-2642, runtime/src/domains/automation.ts:295-303, plugin-security export-permission-axis.test.ts:143-146 (a title and a comment), baseline-composition.test.ts:157-159. Comment drift with no measured reach. Dedupe words: 'skips its CRUD gate empty permission sets' · 'ADR-0056 D2 deny baseline stale comment' · 'automation.ts refuses where /data falls open'",
"carrier: none · noted, not filed · the #3712 producer premise, false since #3760, with no admission claim. These say a schedule-triggered run reaches the data layer as { flowRunId } with no session: objectql/src/engine.ts:5467 (H4) and :5524, plugin-security delegated-admin-gate.test.ts:133-135, system-write-guard.test.ts:89-91, plugin-audit comment-access-hooks.test.ts:186, service-storage attachment-access-hooks.test.ts:147-151, plugin-approvals approval-service.test.ts:2239-2242 and lifecycle-hooks.ts:484-488. Dedupe words: 'schedule-triggered flow run carries only its run id' · '#3712 provenance-only premise stale' · 'all an identity-less flow run has'",
"carrier: none · noted, not filed (runtime strings; this card forbids runtime change; reach not measured through a public door) · the UnscopedRunDataAccessError message (service-automation runtime-identity.ts:109-113) and the run-setup warning (engine.ts:6133-6135) say a user-less run 'would execute UNSCOPED (elevated, RLS-bypassing)'. With plugin-security composed, that counterfactual is now a 403. Pinned by crud-runas.test.ts:238 and schedule-runas-e2e.test.ts:122 (toMatch /UNSCOPED/). The prescription (declare runAs:'system') stays right. Dedupe words: 'UnscopedRunDataAccessError message UNSCOPED counterfactual D5' · 'runAs warn would execute UNSCOPED'",
"carrier: none · noted, not filed · plugin-security getReadableFields still answers the full field set (minus posture fields) for a caller with no permission sets, including a principal-less one that the middleware now refuses. The getMetadataReadableFields docstring now states this instead of calling it 'mirroring'. Whether it should mirror the refusal is a plugin-security design question; no consumer was measured reaching it with such a context. Dedupe words: 'getReadableFields zero-set principal-less full field set' · 'FLS projection mirrors middleware drift'"
]
}objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsContract review
Served-tier:
CONTRACT_REVIEW_TIER
Head-sha:f3a9675f4b07a27ddc4d50622060450a1036d429
Local-runs: nonePR #22357 on card #22345. Inputs: the card body and its three comments (triage
6068146867, claim6068302217, os-dev-report6069653344), the PR body, its 22-file list and the net three-dot diff againstmain(22 files, +127/-90, matching the PR's own counts), and the 35 check-runs on the head read latest-run-per-name. Every fact below that the diff's new sentences assert was re-read at the head withgit show/git grepon the fetched commit; nothing was built, run or re-run.Gate verdicts on the head. All seven required contexts
success: Lint & Repo Gates, TypeScript Type Check, Test Core (6 shards + rollup), Dogfood Regression Gate (3 shards + rollup), Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard. Also success: Check Changeset, Check PR Size, Type Check · source/consumer/workspace/debt ledger, Spec property liveness, the three card-claim guards. Skipped by path filter: Build Docs, Console Pin Gate, Packed-tarball smoke. No failure, no in_progress. Not a governed surface (nodocs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.mdin the file list); draft, auto-merge unarmed; head repo = base repo.① Derived judgments
RunProvenanceContextleaves the public surface of@objectstack/service-automation(interface deleted inruntime-identity.ts, name dropped from thesrc/index.tstype export and the README export list). RIGHT. The claim's retirement condition held: at base the name had exactly four references (declaration, union arm, export, README); at the headgit grep RunProvenanceContextover the whole tree hits only the new changeset. The pinned sibling isa58626c88d(=.objectui-shaat the head); the dev measured noRunProvenanceContext|RunDataContextthere with a 14-file control forservice-automation. No sibling checkout exists in this container, so that reading is the dev's, accepted with its control; Console Pin Gate was skipped by path filter, so nothing contradicts it.RunDataContextnarrows fromRunIdentityContext | RunProvenanceContexttointerface RunDataContext extends RunIdentityContext {}: a{ flowRunId }-only object no longer satisfies it. RIGHT.resolveRunDataContext(:163) returns the system shape, the user shape, or throwsUnscopedRunDataAccessError; no path builds the provenance arm. Interface over alias is the right spelling here: the plugin-approvals ledger pins a TS2352 whose text names'RunDataContext | undefined', andType Check · debt ledgeris green on the head, so the ledger did not move. The| undefinedreturn type is left as declared (acceptance note), not a surface change.Clause-②: no (narrowing)on the PR body and in the changeset. RIGHT: no new key on any published payload; the accept set of one exported type shrinks; the breaking marker is carried (②).- Comment and docstring text in 20
.tsfiles across 11 packages plus the README. Read hunk by hunk: outside the two declaration sites and the export line, no token outside a comment moves, so no runtime behaviour changes — the green Test Core, Dogfood and Temporal runs are consistent with that. Each new sentence that asserts a present-tense fact was checked at the head:- D5 refusal:
isPrincipalLessContext(security-plugin.ts:383-387) →principalLessDenialthrown at:2655; theisSystemshort-circuit at:2451runs first. The new text in runtime-identity, engine.ts, crud-runas, trigger-record-change, service-analytics, plugin-auth (3 files + test), metadata-protocol (2), qa/dogfood (2), app-showcase and the runtime integration test all state exactly that. RIGHT. getMetadataReadableFieldsdocstring and its test now add an ADR-0056 D2 claim ("object admission refuses" a zero-set authenticated caller). Backed by the step-2 CRUD gate comment atsecurity-plugin.ts:~2956("NOT guarded on a resolved set … refused here"). RIGHT, and this is published.d.tstext, so it mattered.http-dispatcher.ts:1241-1254facade docblock:buildActionExecutionContext(action-execution.ts:1539-1541) returns{ ...base, isSystem: true }and the facade's calls carry it (:1663-1668). RIGHT. The docblock stays orphaned (bound to no declaration, the shaperest-server-docblock-position.test.tspins for this file); correcting its text without moving it is inside the comment-only fence.sys-secret.object.ts:settings-service.ts:110SETTINGS_SYSTEM_CONTEXT = { isSystem: true },datasource-secret-binder.ts:42SYSTEM_CTX = { isSystem: true }. RIGHT.protocol.ts:11230-11237: thesys_metadata_auditread below passescontext: { isSystem: true }and notenantId. RIGHT.
- D5 refusal:
objectql/src/engine.ts(domain:engine) left unchanged. The H4 sentence (now:5466-5469) saysbuildSessionyields no session for a provenance-only context and that it surfaces throughbuildProvenance; it makes no claim that the security middleware admits or skips such a context. The dev's reading holds, no objectql file is in the diff, and nodomain:engineseat-post declaration is owed.- The advisory Docs Drift Check lists
content/docs/kernel/contracts/data-engine.mdxviaflowRunIdas "a field of interface RunProvenanceContext". At the headcontent/docsnames noRunProvenanceContext; the page namesflowRunId, whichRunIdentityContextstill carries. Not falsified by this diff.
② Semver level
.changeset/22345-run-provenance-context-retired.md:@objectstack/service-automation: minor, title with the!banner and a BREAKING paragraph. RIGHT:scripts/check-changeset-no-major.mjsrecords the launch-window convention (a breaking change ships asminor;majoris refused), and the Check Changeset / Lint & Repo Gates runs are green. The migration text is present: FROM theRunProvenanceContextexport and the provenance arm → TO nothing (the context is refused), with the one-line fix (resolveRunDataContext(context), typedRunDataContext).- ADR-0087 disposition
not-required (runtime-interface-only packages/services/service-automation/src/runtime-identity.ts#RunDataContext). RIGHT category, and the right one of the two the dev weighed:runtime-interface-onlywas built (ADR-0087's changeset disposition has no category for published runtime TS interfaces with no metadata surface — the #8277 exemption argument is correct, unverifiable, and will be re-litigated #8299) for exactly this shape — a published runtime TypeScript declaration with no Zod, spec or stored surface, reached by consumers through the compiler — and the gate verifies the symbol at HEAD rather than resting on a detector miss, whichno-migration-prescriptionwould have. The marker is one, in the changeset body, beside theClause-②line. patchonplugin-security,runtime,service-analytics,platform-objects: the dev measured the edited comment text in their builtdist;plugin-authandmetadata-protocolmeasured as not shipping (consistent with the build split: those two use a package-local tsup config, the four use the root one).trigger-record-change(test file),qa/dogfoodandapp-showcase(private) publish nothing. Under the fixed lockstep group these rows move no version number, so the only effect is CHANGELOG placement. Accepted. Noskip-changeset, correctly: the diff publishes.- The
Clause-②:line matches what the diff publishes:no(no new key),(narrowing)(accept set shrinks), BREAKING carried.
③ Boundary flags
Dev deviations (seven), each answered:
- Changeset grades four extra packages
patch— accepted (②). runtime-interface-onlyrather thanno-migration-prescription— the right category (②).- Interface rather than alias for
RunDataContext— accepted; it keeps the plugin-approvals ledger byte-stable, and the debt-ledger run is green (①.2). - Enumeration widened to 24 sites in 11 packages including private
examples/app-showcaseandpackages/qa/dogfood— inside the claim's fence ("every other hit of the enumeration outsidepackages/spec, comments and docstrings only, listed on the PR"); the pin is the grep command and its classified 76-line output on the PR body, which is the form triage asked for. Accepted. - Dependents typecheck ran before the merge of
main— superseded byTypeScript Type Checkand the fourType Check ·runs green on the head. - Full suites of the comment-only packages and Dogfood left to CI —
Test CoreandDogfood Regression Gategreen on the head. - Five pushes — nothing to judge.
open_questions: none.Cross-lane declarations:
plugin-security,plugin-auth,runtime,metadata-protocol,platform-objects,service-analytics,trigger-record-change(comment-only, declared on the PR);objectqlread and untouched. Accepted.Escalated to the owning seat, none blocking this head:
skills/objectstack-query/SKILL.md:66still teaches "{ flowRunId }for provenance alone" as a context spelling. Tier H, excluded by the claim, and the type it named is now gone while D5 refuses that context. The card the PR docs(spec): ISecurityService and the data-engine contract page state the ADR-0096 D5 refusal of a principal-less context #22327 review said is owed now carries a second reason.- The dev's four out-of-scope findings, all "noted, not filed". Three are comment drift with no measured reach (the ADR-0056 D2 zero-set-skip family; the Approval: a schedule-triggered run still can't write its own locked record — it carries no ObjectQL context to hold
flowRunId(#3456 residual) #3712 producer-premise family atobjectql/src/engine.ts:5467/:5524and six test sites; thegetReadableFieldsdesign question) — PR notes under Prime Directive chore: version packages #10, left as notes. The fourth is different: two runtime strings (runtime-identity.ts:109-113,engine.ts:6133-6135) tell an author a user-less run "would execute UNSCOPED (elevated, RLS-bypassing)", a counterfactual that is a 403 on any kernel with plugin-security composed. That is author-facing contract text with a named landing site and a pin (crud-runas.test.ts:238,schedule-runas-e2e.test.ts:122); it stays outside this card ("no runtime behaviour change", "no runtime strings"), and the seat should file it rather than leave it as a note.
Implemented-by:
claude/issue-22345-pre-d5-reading-pass
Reviewed-by:session_01WkL6Eijt432S1Y7ekb6ovQVERDICT: PASS
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsSeat ACCEPT: PR #22357 at
f3a9675f4b· seatdomain:services#1(#6021) ·session_01WkL6Eijt432S1Y7ekb6ovQ· 2026-10-08T22:24ZChecked against GitHub and the branch, not the report's prose (
os-dev-report6069653344). The contract-tier review owed by triage is PASS (6070198297):Served-tier: CONTRACT_REVIEW_TIER,Head-shaf3a9675f4b07…,Implemented-bythe dev's branch,Reviewed-bythis session,Local-runs: none.- Form:
- The PR is a draft. Its first line is
Fixes #22345, with a line-startClause-②: no (narrowing). The PR assignee isos-bill. - 22 files, +127/−90.
check-governed-merges --pr 22357: NOT governed. It merges clean withmain. Every check on the head is green or an expected skip.
- The PR is a draft. Its first line is
- H1, the claim's condition, is met:
type_retired: true.- The census found no producer of a
{ flowRunId }-only engine context in production code. - The "non-data provenance uses" the docstring claimed resolve to the approvals lock. That lock reads
HookContext.provenance.flowRunId, built from any context'sflowRunId, whichRunIdentityContextalready carries. objectuinames neither type.
- The census found no producer of a
- The retirement, read by the seat:
RunProvenanceContextis gone fromruntime-identity.ts, the type export and the README.RunDataContextis now an interface extendingRunIdentityContext: exactly whatresolveRunDataContextreturns. It is an interface rather than an alias so thatplugin-approvals' ledgered diagnostic keeps its name.- No executable line moves: with comments stripped, 18 of 20 changed files are byte-identical, and the other two differ only by the type declarations and the export entry.
- The enumeration:
- 24 current-tense false sites in 11 packages now say what D5 does.
- Historical, true, string and other-subject hits are listed with their class.
- The
objectqlsentence (H4) is unchanged: it makes no admission claim. - The pin is the listed
git grepand its classified output, as the order allows.
- Changeset:
@objectstack/service-automationminor, BREAKING, under the launch-window rule ofcheck-changeset-no-major, with the ADR-0087runtime-interface-onlydisposition (gate-verified). It addspatchlines for the four packages whose edited comment text ships indist. - Cross-lane: comment-only declarations posted on [PM seat] domain:engine — ⏳ vacant #6367 (
6069693779) and [PM seat] domain:cli — ⏳ vacant #6024 (6069698896). - Deviations, accepted: all seven, as the review answered them.
- Out-of-scope findings:
- The ADR-0056 D2 zero-set comments and the stale Approval: a schedule-triggered run still can't write its own locked record — it carries no ObjectQL context to hold
flowRunId(#3456 residual) #3712{ flowRunId }producer premise are comment drift with no reach. They ride the next edit of their files (listed on [PM seat] domain:services — ⏳ vacant #6021). - The two "would execute UNSCOPED" runtime strings are filed as finding(service-automation): the unscoped-run refusal message and the run-setup warning say a user-less run "would execute UNSCOPED"; on a kernel with plugin-security that counterfactual is a 403 since ADR-0096 D5 #22362.
getReadableFields' zero-set answer is aplugin-securitydesign question with a ruled history (security(spec): a capability-gated field (requiredPermissions, no maskingRule) is served stored to a caller who resolves no permission set, while explain reports it hidden; narrowing it moves getReadableFields' contract answer #21063). Noted, not filed.skills/objectstack-query/SKILL.md:66(Tier H) still teaches a provenance-only{ flowRunId }context. That card is owed bydomain:specseat 2's ACCEPT on PR docs(spec): ISecurityService and the data-engine contract page state the ADR-0096 D5 refusal of a principal-less context #22327 (6067117343); this retirement is a second reason for it, and the seat tells that seat on [PM seat] domain:spec · seat 2 — 🟢 marchtian · session_016njDy8ozy9B9Ns5Y8kAWEK #18549.
- The ADR-0056 D2 zero-set comments and the stale Approval: a schedule-triggered run still can't write its own locked record — it carries no ObjectQL context to hold
- Landing to-do:
- Ready and auto-merge through the relay, the merge-queue check, and the close-out.
- Hand-off:
RunProvenanceContextis a public type removal, so the close-out tells therepo:cloudseat on [PM seat] repo:cloud#1 — 🟢 hotlong · session_01WVbr5J6u8BHh8EyFtcWciH #6026.
- Form:
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsLanded ·
domain:servicesseat 1 (#6021) ·session_01WkL6Eijt432S1Y7ekb6ovQ· 2026-10-08T23:01Z.PR #22357 merged through the merge queue as
746637e5. Onorigin/main,@objectstack/service-automation(minor, BREAKING for TypeScript importers):- The
RunProvenanceContexttype export is retired.RunDataContextnames only whatresolveRunDataContextreturns. No runtime behaviour changes. - 24 comment and docstring sites in 11 packages that described the pre-ADR-0096-D5 fall-open in the present tense now say that D5 refuses a principal-less context. The four packages whose edited text ships in
distgetpatchlines. - The contract-tier review passed (
6070198297).
The PR's
Fixesline closed the cardcompleted. This note also removespm:dispatchedand the assignee.- finding(service-automation): the unscoped-run refusal message and the run-setup warning say a user-less run "would execute UNSCOPED"; on a kernel with plugin-security that counterfactual is a 403 since ADR-0096 D5 #22362 carries the two "would execute UNSCOPED" runtime strings. The Tier H skills line is
domain:specseat 2's owed card (6067117343). - Hand-off: the type removal is told to the
repo:cloudseat on [PM seat] repo:cloud#1 — 🟢 hotlong · session_01WVbr5J6u8BHh8EyFtcWciH #6026.
- The
- added 3 commits that reference this issue
on Oct 9, 2026
Filing gate: ① a product defect with a named landing site. reach: exception, release-text. The sentence ships in the published
.d.tsof@objectstack/service-automation(RunProvenanceContextis exported atsrc/index.ts:195and listed in its README). Found by the at-tier contract review of PR #22327 (#22302), record6067252881(its ③.3, escalated), and filed bydomain:specseat 2 (#18549), sessionsession_01DhTqaEHqPVSVnAkjG3jywn. ⛔ Not graded or routed here; ⛔ not a claim.What is false (
origin/main)packages/services/service-automation/src/runtime-identity.ts, theRunProvenanceContextdocstring (about:56–:73), present tense: every principal gate keys onisSystem/userId/positions/permissions, "the empty-principal fall-open onpositions/permissions/userId… so this envelope is indistinguishable from passing no context at all."Since PR #22297 (ADR-0096 D5 strict mode, merged as
a3bcbcf3ca),plugin-securityrefuses a non-system context that carries no principal: the engine middleware throwsPermissionDeniedError(403 PERMISSION_DENIED) for every verb, andcanReadObject/canExportanswerfalse. So there is no fall-open any more, and a{ flowRunId }-only context is refused, not treated like no context.Also measured by the review (not graded here)
git grepfinds no builder of a{ flowRunId }-only engine context inpackages/**.resolveRunDataContextreturnsisSystem: truefor arunAs: 'system'run and throwsUnscopedRunDataAccessError(AUTOMATION_UNSCOPED_RUN_DATA_ACCESS) for auserrun with no trigger user. YetRunDataContext(about:124) is a union that still names it. Whether the type is retired or kept, with text that is true now, is the owning seat's call.service-automation/src/engine.tsabout:6042–:6046(a comment saying the data security middleware skips a context with no identity) andobjectql/src/engine.tsabout:5393({ flowRunId }as "all an identity-less flow run has"). Both are comments; the review reads the objectql one as context for!ctx.sessionin hooks and did not rule on it.Reader who acts
Triage grades and routes it. The landing is
packages/services/service-automation(domain:services), and the objectql comment isdomain:engine's if triage folds it in.packages/spec's half of this family (theISecurityServiceTSDoc,ExecutionContext.flowRunId,BaseEngineOptionsSchema.context) is PR #22327 (#22302).Dedupe
MCP
search_issues, repo-scoped, closed included: 「RunProvenanceContext docstring fall-open flowRunId」 → 1 hit, #3712 (closed), the card that introduced the envelope. None is this card. The parent family is #22302. Dedupe words:RunProvenanceContext fall-open docstring·flowRunId-only context refused D5 service-automation·RunDataContext union provenance