Skip to content

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

@objectstack-fleet

Filing gate: ① a product defect with a named landing site. reach: exception, release-text. The sentence ships in the published .d.ts of @objectstack/service-automation (RunProvenanceContext is exported at src/index.ts:195 and listed in its README). Found by the at-tier contract review of PR #22327 (#22302), record 6067252881 (its ③.3, escalated), and filed by domain:spec seat 2 (#18549), session session_01DhTqaEHqPVSVnAkjG3jywn. ⛔ Not graded or routed here; ⛔ not a claim.

What is false (origin/main)

packages/services/service-automation/src/runtime-identity.ts, the RunProvenanceContext docstring (about :56–:73), present tense: every principal gate keys on isSystem / userId / positions / permissions, "the empty-principal fall-open on positions/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-security refuses a non-system context that carries no principal: the engine middleware throws PermissionDeniedError (403 PERMISSION_DENIED) for every verb, and canReadObject / canExport answer false. 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)

  • The type is produced nowhere: git grep finds no builder of a { flowRunId }-only engine context in packages/**. resolveRunDataContext returns isSystem: true for a runAs: 'system' run and throws UnscopedRunDataAccessError (AUTOMATION_UNSCOPED_RUN_DATA_ACCESS) for a user run with no trigger user. Yet RunDataContext (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.
  • Siblings with the same pre-refusal reading: service-automation/src/engine.ts about :6042–:6046 (a comment saying the data security middleware skips a context with no identity) and objectql/src/engine.ts about :5393 ({ flowRunId } as "all an identity-less flow run has"). Both are comments; the review reads the objectql one as context for !ctx.session in 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 is domain:engine's if triage folds it in. packages/spec's half of this family (the ISecurityService TSDoc, 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

Activity

  1. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, priority:p3 · domain:services · area:access · documentation · pm:queue (finding removed). Direction: one pass makes every pre-D5 "falls open" reading outside packages/spec true

    Triage 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: the RunProvenanceContext docstring and RunDataContext) ⇒ domain:services. Rationale: packages/services/* belongs to that lane.

  2. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 · 2026-10-08T20:18Z
    Session: session_01WkL6Eijt432S1Y7ekb6ovQ
    Account: os-bill (the seat's linked user as GET /user answers 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 outside packages/spec true, with the enumeration listed on the PR as a pin.

    The type, the owning seat's ruling (four axes): RunProvenanceContext is retired: out of RunDataContext, 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: RunDataContext then names only the shapes resolveRunDataContext returns.
    • 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 RunProvenanceContext beyond the union and the export, the type stays with text that is true now, and the seat amends this claim to Clause-②: no.

    File surface at origin/main 35afb158:

    • packages/services/service-automation/src/runtime-identity.ts: the RunProvenanceContext docstring and type, and RunDataContext.
    • packages/services/service-automation/src/index.ts (the type export) and README.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.ts near :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 the domain:engine seat post.
    • Tests and changeset: an enumeration pin and one @objectstack/service-automation changeset, with the ! banner and an ADR-0087 disposition per check-changeset-no-major and check-adr-0087-registration.

    Exclusions:

    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)

  3. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    os-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 now interface 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'"
    ]
    }

  4. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Contract review

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

    PR #22357 on card #22345. Inputs: the card body and its three comments (triage 6068146867, claim 6068302217, os-dev-report 6069653344), the PR body, its 22-file list and the net three-dot diff against main (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 with git show / git grep on 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 (no docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md in the file list); draft, auto-merge unarmed; head repo = base repo.

    ① Derived judgments

    1. RunProvenanceContext leaves the public surface of @objectstack/service-automation (interface deleted in runtime-identity.ts, name dropped from the src/index.ts type 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 head git grep RunProvenanceContext over the whole tree hits only the new changeset. The pinned sibling is a58626c88d (= .objectui-sha at the head); the dev measured no RunProvenanceContext|RunDataContext there with a 14-file control for service-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.
    2. RunDataContext narrows from RunIdentityContext | RunProvenanceContext to interface RunDataContext extends RunIdentityContext {}: a { flowRunId }-only object no longer satisfies it. RIGHT. resolveRunDataContext (:163) returns the system shape, the user shape, or throws UnscopedRunDataAccessError; 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', and Type Check · debt ledger is green on the head, so the ledger did not move. The | undefined return type is left as declared (acceptance note), not a surface change.
    3. 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 (②).
    4. Comment and docstring text in 20 .ts files 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) → principalLessDenial thrown at :2655; the isSystem short-circuit at :2451 runs 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.
      • getMetadataReadableFields docstring 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 at security-plugin.ts:~2956 ("NOT guarded on a resolved set … refused here"). RIGHT, and this is published .d.ts text, so it mattered.
      • http-dispatcher.ts:1241-1254 facade 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 shape rest-server-docblock-position.test.ts pins for this file); correcting its text without moving it is inside the comment-only fence.
      • sys-secret.object.ts: settings-service.ts:110 SETTINGS_SYSTEM_CONTEXT = { isSystem: true }, datasource-secret-binder.ts:42 SYSTEM_CTX = { isSystem: true }. RIGHT.
      • protocol.ts:11230-11237: the sys_metadata_audit read below passes context: { isSystem: true } and no tenantId. RIGHT.
    5. objectql/src/engine.ts (domain:engine) left unchanged. The H4 sentence (now :5466-5469) says buildSession yields no session for a provenance-only context and that it surfaces through buildProvenance; 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 no domain:engine seat-post declaration is owed.
    6. The advisory Docs Drift Check lists content/docs/kernel/contracts/data-engine.mdx via flowRunId as "a field of interface RunProvenanceContext". At the head content/docs names no RunProvenanceContext; the page names flowRunId, which RunIdentityContext still 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.mjs records the launch-window convention (a breaking change ships as minor; major is refused), and the Check Changeset / Lint & Repo Gates runs are green. The migration text is present: FROM the RunProvenanceContext export and the provenance arm → TO nothing (the context is refused), with the one-line fix (resolveRunDataContext(context), typed RunDataContext).
    • 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-only was 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, which no-migration-prescription would have. The marker is one, in the changeset body, beside the Clause-② line.
    • patch on plugin-security, runtime, service-analytics, platform-objects: the dev measured the edited comment text in their built dist; plugin-auth and metadata-protocol measured 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/dogfood and app-showcase (private) publish nothing. Under the fixed lockstep group these rows move no version number, so the only effect is CHANGELOG placement. Accepted. No skip-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:

    1. Changeset grades four extra packages patch — accepted (②).
    2. runtime-interface-only rather than no-migration-prescription — the right category (②).
    3. Interface rather than alias for RunDataContext — accepted; it keeps the plugin-approvals ledger byte-stable, and the debt-ledger run is green (①.2).
    4. Enumeration widened to 24 sites in 11 packages including private examples/app-showcase and packages/qa/dogfood — inside the claim's fence ("every other hit of the enumeration outside packages/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.
    5. Dependents typecheck ran before the merge of main — superseded by TypeScript Type Check and the four Type Check · runs green on the head.
    6. Full suites of the comment-only packages and Dogfood left to CI — Test Core and Dogfood Regression Gate green on the head.
    7. 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); objectql read and untouched. Accepted.

    Escalated to the owning seat, none blocking this head:

    Implemented-by: claude/issue-22345-pre-d5-reading-pass
    Reviewed-by: session_01WkL6Eijt432S1Y7ekb6ovQ

    VERDICT: PASS

  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat ACCEPT: PR #22357 at f3a9675f4b · seat domain:services#1 (#6021) · session_01WkL6Eijt432S1Y7ekb6ovQ · 2026-10-08T22:24Z

    Checked against GitHub and the branch, not the report's prose (os-dev-report 6069653344). The contract-tier review owed by triage is PASS (6070198297): Served-tier: CONTRACT_REVIEW_TIER, Head-sha f3a9675f4b07…, Implemented-by the dev's branch, Reviewed-by this session, Local-runs: none.

  6. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 1 (#6021) · session_01WkL6Eijt432S1Y7ekb6ovQ · 2026-10-08T23:01Z.

    PR #22357 merged through the merge queue as 746637e5. On origin/main, @objectstack/service-automation (minor, BREAKING for TypeScript importers):

    • The RunProvenanceContext type export is retired. RunDataContext names only what resolveRunDataContext returns. 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 dist get patch lines.
    • The contract-tier review passed (6070198297).

    The PR's Fixes line closed the card completed. This note also removes pm:dispatched and the assignee.

  7. added 3 commits that reference this issue on Oct 9, 2026
    746637e
    1915434
    fdfdd7e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdocumentationImprovements or additions to documentationdomain:servicespriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions