You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
audit: an action run through MCP leaves no audit row naming its surface; ruling 6513869839 parameter 3 (#27681) needs an execution-context surface key, the audit writer and the forward headers #27908
Carries parameter 3 of the maintainer's ruling on #27681 (letter B, record 6513869839): "The audit row of an action run through MCP records the surface, so an administrator can tell an agent's call from a click without a second log." The mechanism PR for #27681 does not carry it, because it needs a packages/spec change (claim amendment 6514100238). Filed by the domain:server#2 seat. ⛔ Not a claim.
What is there today (read from code at origin/maine320e366 by the #27681 card thread)
Neither the REST action door nor MCP run_action writes an action-invocation audit row.
The script branch of invokeBusinessAction logs console.info [action-audit] and already tells MCP run_action from a REST action. The api, flow and declarative-update branches log nothing.
sys_audit_log records the data writes an action's body makes. Its rows have no surface field.
ExecutionContext has no key that carries the surface. performedBy is set only by the OAuth /mcp door, and it is lost when an api action is forwarded and re-authenticated.
Wanted (route is triage's)
A durable audit record of each action invocation that names its surface (REST, MCP), on every action branch (script, api, flow, declarative update), and survives the api forward. This likely means an execution-context attribution key in packages/spec (Clause-② per the spec lane), the audit writer in plugin-audit, and the forward headers in packages/runtime. Triage decides the lanes and whether it splits per lane. A log line is not the audit row the ruling asks for.
Links: #27681, ruling 6513869839, decision card #27900 (the declarations).
Claim: PM loop round 3 · 2026-10-30T10:58Z
Session: session_017ihL2fi9zfJhUTubyJYb6V
Account: marchtian
Branch: claude/issue-27908-action-audit-surface
Worktree: objectstack-issue-27908
Domain: domain:spec
Seat: domain:spec#4
Ruling-ref: maintainer ruling 6513869839 on #27681 (letter B, parameter 3: "The audit row of an action run through MCP records the surface, so an administrator can tell an agent's call from a click without a second log."); triage 6514607572 (Seam card: "the spec lane declares the surface key on ExecutionContext. The consumers are the audit writer (plugin-audit, one durable action-invocation row on every branch: script, api, flow, declarative update) and the forward headers (packages/runtime, so the key survives the api re-authentication)." "⛔ A log line is not the audit row.")
File surface: packages/spec/src/kernel/execution-context.zod.ts (one optional, server-constructed surface key on ExecutionContext, the performedBy docblock shape: never client-supplied, attribution only), the doors that construct it (packages/runtime REST action door and the /mcp bridge's run_action / resume_run), packages/runtime/src/action-execution.ts (invokeBusinessAction on all four branches; the api forward carries the surface across re-authentication beside API_FORWARD_MARK_HEADER, server-to-server only, refused from an outside caller), packages/plugins/plugin-audit/src/** (one durable action-invocation row per invocation naming its surface), tests in those packages, regenerated spec artefacts, changesets (@objectstack/spec minor; runtime / plugin-audit per their published change). ⛔ No change to authorization: the key never becomes a subject, a scope or a gate (stop on breach; explain in the report). ⛔ A console.info line does not count as the row.
Container & model: M, mode:subagent, model: TIER_DEFAULT (opus) — the quota-exhaustion exemption (claude-fable-5-1 answered HTTP 429; maintainer 「你没有fable了,使用 opus开发完之后等契约复审」), effort: high
Clause-②: yes (widening)
Metadata-protocol: none
Responsibility: an administrator reading the audit trail cannot tell an action an agent ran over MCP from one a person clicked, because no branch of invokeBusinessAction writes an action-invocation row and ExecutionContext carries no surface | the performedBy attribution precedent (ADR-0090 D10 rule 4: server-constructed, attribution only, recorded by the audit writer) is the path | every deployment that serves /mcp actions to agents, on every action branch (script, api, flow, declarative update)
Thread-read: 6514607572
Premise: holds — on 3e0fd7a8f7ExecutionContextSchema has performedBy (execution-context.zod.ts:309) and no surface key; [action-audit] is logged only from packages/runtime/src/action-execution.ts and domains/actions.ts; API_FORWARD_MARK_HEADER is at action-execution.ts:1186; the #27681 mechanism PR #27940 merged (2026-10-30T10:36Z), so there is no in-flight intersection with it
Serial constraints cleared: draft PR #28047 touches packages/plugins/plugin-audit/src/audit-log-read-visibility.ts and its tests (the read side; this card writes the invocation row), so whichever lands second merges origin/main. Draft PR #27951 touches only packages/runtime/src/mcp-resume-run.test.ts; if this card's pins land in the same file, the second merges. No open PR touches execution-context.zod.ts or action-execution.ts (open PR file lists read just before this claim)
domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V
os-dev-report
{
"status": "pr_open_draft",
"posted_at": "2026-10-30T15:09Z",
"card": 27908,
"branch": "claude/issue-27908-action-audit-surface",
"pr": 28171,
"head_sha": "c6228a99cd1ef544af6e17b2211b2eb9b2fb9c1a",
"session": "session_017ihL2fi9zfJhUTubyJYb6V",
"premise_still_valid": true,
"summary": "ExecutionContext.surface (rest | mcp) set by the REST action door and the /mcp run_action / resume_run door on a copy of the invocation envelope; plugin-audit writes one sys_audit_log row (action action_invocation, metadata.surface) per invocation on every branch (script, api, flow, update), success or post-dispatch refusal; no new field on sys_audit_log.",
"seat_decisions": [
"key name ExecutionContext.surface and its closed values rest, mcp (maintainer approves in the PR, Clause-2 yes)",
"new sys_audit_log.action enum value action_invocation (a value, not a field)",
"no surface header on the api forward (deviation 1): accept, or require a trusted in-process carry",
"Metadata-protocol added with minor bumps instead of the claim none (deviation 2)"
],
"tests": {
"packages/runtime/src/action-invocation-audit.test.ts": "24 passed (branch x door success and failure, subject 404 row, D4 gate no row, forged forward mark + surface header + upstream surface refused, api forward carries no surface header, envelope not mutated, performedBy carried, resume_run rows)",
"packages/plugins/plugin-audit/src/action-invocation-audit.test.ts": "7 passed (real ObjectQL read-back)",
"package_suites": "core 128 files / 2873 passed; plugin-audit 66 / 1058; runtime 484 / 7895 passed 19 skipped; spec 756 / 22510 passed 1 todo",
"ablation": [
"runtime: removed await sink.recordActionInvocation(event); gives 20 failed / 4 passed; restored blob d014ccafe5bb",
"plugin-audit: removed await persistActionInvocationAuditRow(engine, row); gives 3 failed / 4 passed; restored blob 9d2076906454"
]
},
"gates": {
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack",
"derived": 131,
"run": 131,
"not_measured": 0,
"unrun": 0,
"nonzero_final": 0,
"reconciliation": "dispatch-gates --ran: 131 accounted, 0 NOT-MEASURED, derived zero (every line carries its exit code)",
"head_measured": "c6228a99cd; 127 commands re-run on this head, pnpm check:pm-dispatch-gates measured exit 0 on 26e3fb2 (the last commit touches no scripts/pm file), check:skill-examples / check:dual-build-cjs-loads / check:type-check-debt measured exit 0 on this head content after pnpm build under the verify lock",
"first_pass_reds_fixed": [
"check-tenant-audit-census (+ --self-test): new write site; options cast dropped, page recounted 251 to 252",
"check:engine-double-contract: ledger --write",
"check:objectql-double-limit: test double applies limit",
"check:swallow-census-controls: persistActionInvocationAuditRow copied into WRITE_SHAPED_CALLEES",
"check:doc-authoring: issue id dropped from the surface .describe()"
],
"first_pass_exit3_then_green": [
"check:skill-examples",
"check:dual-build-cjs-loads",
"check:type-check-debt"
],
"adr_0087": "node scripts/check-adr-0087-registration.mjs --base origin/main: exit 0",
"check_generated": "14/14 up to date",
"typecheck": "spec, core, runtime, plugin-audit exit 0",
"not_run_locally": "test:repo and the full CI farm (left to CI)"
},
"dogfood_battery": "n/a, no ! in the title and no (narrowing) arm",
"line_budget": {
"declared": "none in the order",
"files": 29,
"added": 1312,
"removed": 56,
"of_added_tests": 558,
"of_added_generated_or_census_docs": 54
},
"deviations": [
{
"id": 1,
"what": "no surface header on the api forward",
"why": "measured: the forward mark carries no trust (any caller can send x-objectstack-action-forward), the row is written door-side after the forward from the door envelope, no far-side writer reads a surface, a sound carry needs a new in-process credential honoured by every envelope-resolving face; pinned by a test that the forward carries none and the row says mcp",
"seat": "decide"
},
{
"id": 2,
"what": "Metadata-protocol added, changesets minor x4 (spec, core, runtime, plugin-audit)",
"why": "the key moves packages/spec/authorable-surface/kernel.json and check-changeset-no-major none-baseline-moved refuses none"
},
{
"id": 3,
"what": "new enum value action_invocation on sys_audit_log.action",
"why": "a value, not a field; no new column; surface lives in metadata"
},
{
"id": 4,
"what": "surface stamped on a copy of the envelope, not the request ec",
"why": "nothing upstream or downstream sees a moved request envelope"
},
{
"id": 5,
"what": "resume_run also writes a row (branch flow, run_id)",
"why": "it is an MCP door continuing an admitted flow action"
},
{
"id": 6,
"what": "tenant_id is the caller active org (ExecutionContext.tenantId), not the subject record org",
"why": "the row does not read the record"
},
{
"id": 7,
"what": "test:repo and full farm not run locally",
"why": "CI owns them per common.md"
}
],
"files_changed": [
".changeset/27908-action-invocation-audit-surface.md",
"content/docs/permissions/tenant-audit-census.mdx",
"content/docs/references/data/data-engine.mdx",
"content/docs/references/kernel/execution-context.mdx",
"docs/audits/2026-08-tenant-audit-write-call-sites.counts.md",
"packages/core/src/security/assemble-execution-context.test.ts",
"packages/core/src/security/assemble-execution-context.ts",
"packages/plugins/plugin-audit/src/action-invocation-audit.test.ts",
"packages/plugins/plugin-audit/src/action-invocation-audit.ts",
"packages/plugins/plugin-audit/src/audit-log-field-redaction.ts",
"packages/plugins/plugin-audit/src/audit-log-read-visibility.ts",
"packages/plugins/plugin-audit/src/audit-plugin.ts",
"packages/plugins/plugin-audit/src/index.ts",
"packages/plugins/plugin-audit/src/objects/sys-audit-log-retired-actions.test.ts",
"packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts",
"packages/plugins/plugin-audit/src/translations/en.objects.generated.ts",
"packages/plugins/plugin-audit/src/translations/es-ES.objects.generated.ts",
"packages/plugins/plugin-audit/src/translations/ja-JP.objects.generated.ts",
"packages/plugins/plugin-audit/src/translations/zh-CN.objects.generated.ts",
"packages/runtime/src/action-execution.ts",
"packages/runtime/src/action-invocation-audit.test.ts",
"packages/runtime/src/action-invocation-audit.ts",
"packages/runtime/src/domains/actions.ts",
"packages/runtime/src/domains/mcp.ts",
"packages/spec/authorable-surface/kernel.json",
"packages/spec/src/kernel/execution-context.zod.ts",
"scripts/check-durability-degradation-log-level.mjs",
"scripts/engine-double-contract.pinned.json",
"scripts/measure-durability-swallow-family.mjs"
],
"mcp_calls": 0,
"api_writes": 2,
"api_writes_detail": [
"pr_create via fleet-write dispatch (run 41077866252, read-back identical)",
"this comment via post-stamped"
],
"open_questions": [
"Should the forward carry a trusted surface (new in-process credential) so CRUD rows written by an api target also name the door? Not needed for the action_invocation row."
],
"out_of_scope_findings": [
{
"class": "a (candidate)",
"reach": "not measured; read from code",
"evidence": "packages/runtime/src/http-dispatcher.ts:753 path-matches acceptOAuthAccessToken to /mcp, and forwardApiAction relays the caller bearer to a non-/mcp target, so an OAuth-token agent api action would resolve anonymous at the target (likely 401 on sys* identity api actions)",
"dedupe": "oauth forward api action anonymous acceptOAuthAccessToken"
},
{
"class": "observation",
"reach": "harmless",
"evidence": "API_FORWARD_MARK_HEADER x-objectstack-action-forward is forgeable by any caller; its only effect is the loop refusal (pinned in action-invocation-audit.test.ts)"
}
]
}
Landed · 2026-10-31T05:47Z · PR #28171 merged by the queue at 2026-10-31T05:46Z as 3a054cbe1f; the card closed by Fixes. This seat strips pm:dispatched in the same act. Contract review: PASS 6537892848 on the landed head. 田騏 approved the names ExecutionContext.surface (rest / mcp) and action_invocation. Every action invocation now leaves one sys_audit_log row naming the door it came through, on both the REST and MCP doors and on every branch. Should-fixes S1 (join the two rows of an api forward to an action route) and S2 (the awaited ledger insert) are filed as #28281; the OAuth-bearer gap on api forwards is #28178.
Activity
objectstack-fleet commentedon Oct 30, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 真挡得住的权限 | none | P②
Triage: p1 ·
domain:spec·area:automation· Seam · 2026-10-30T03:58Z⛔ Not a claim.
6513869839parameter 3 (mcp: run_action refuses every action on a sys_* object, so an agent cannot reach the identity actions (ban_user, impersonate_user, set_user_password) that REST POST /actions serves #27681), and it adds an execution-context attribution key, which is a protocol addition. 「协议新增定 p1」.ExecutionContext. The consumers are the audit writer (plugin-audit, one durable action-invocation row on every branch: script, api, flow, declarative update) and the forward headers (packages/runtime, so the key survives theapire-authentication). One claim declares both ends and runs the in-flight intersection check against the mcp: run_action refuses every action on a sys_* object, so an agent cannot reach the identity actions (ban_user, impersonate_user, set_user_password) that REST POST /actions serves #27681 mechanism PR.Clause-②: yes: the maintainer approves the key in the PR. A contract review is owed. ⛔ A log line is not the audit row.enhancement,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V
Claim: PM loop round 3 · 2026-10-30T10:58Z
Session:
session_017ihL2fi9zfJhUTubyJYb6VAccount:
marchtianBranch:
claude/issue-27908-action-audit-surfaceWorktree:
objectstack-issue-27908Domain:
domain:specSeat:
domain:spec#4Ruling-ref: maintainer ruling 6513869839 on #27681 (letter B, parameter 3: "The audit row of an action run through MCP records the surface, so an administrator can tell an agent's call from a click without a second log."); triage 6514607572 (Seam card: "the spec lane declares the surface key on
ExecutionContext. The consumers are the audit writer (plugin-audit, one durable action-invocation row on every branch: script, api, flow, declarative update) and the forward headers (packages/runtime, so the key survives theapire-authentication)." "⛔ A log line is not the audit row.")File surface:
packages/spec/src/kernel/execution-context.zod.ts(one optional, server-constructed surface key onExecutionContext, theperformedBydocblock shape: never client-supplied, attribution only), the doors that construct it (packages/runtimeREST action door and the/mcpbridge'srun_action/resume_run),packages/runtime/src/action-execution.ts(invokeBusinessActionon all four branches; theapiforward carries the surface across re-authentication besideAPI_FORWARD_MARK_HEADER, server-to-server only, refused from an outside caller),packages/plugins/plugin-audit/src/**(one durable action-invocation row per invocation naming its surface), tests in those packages, regenerated spec artefacts, changesets (@objectstack/specminor;runtime/plugin-auditper their published change). ⛔ No change to authorization: the key never becomes a subject, a scope or a gate (stop on breach; explain in the report). ⛔ Aconsole.infoline does not count as the row.Container & model:
M,mode:subagent,model: TIER_DEFAULT(opus) — the quota-exhaustion exemption (claude-fable-5-1answered HTTP 429; maintainer 「你没有fable了,使用 opus开发完之后等契约复审」),effort: highClause-②: yes (widening)
Metadata-protocol: none
Responsibility: an administrator reading the audit trail cannot tell an action an agent ran over MCP from one a person clicked, because no branch of
invokeBusinessActionwrites an action-invocation row andExecutionContextcarries no surface | theperformedByattribution precedent (ADR-0090 D10 rule 4: server-constructed, attribution only, recorded by the audit writer) is the path | every deployment that serves/mcpactions to agents, on every action branch (script, api, flow, declarative update)Thread-read: 6514607572
Premise: holds — on
3e0fd7a8f7ExecutionContextSchemahasperformedBy(execution-context.zod.ts:309) and no surface key;[action-audit]is logged only frompackages/runtime/src/action-execution.tsanddomains/actions.ts;API_FORWARD_MARK_HEADERis ataction-execution.ts:1186; the #27681 mechanism PR #27940 merged (2026-10-30T10:36Z), so there is no in-flight intersection with itSerial constraints cleared: draft PR #28047 touches
packages/plugins/plugin-audit/src/audit-log-read-visibility.tsand its tests (the read side; this card writes the invocation row), so whichever lands second mergesorigin/main. Draft PR #27951 touches onlypackages/runtime/src/mcp-resume-run.test.ts; if this card's pins land in the same file, the second merges. No open PR touchesexecution-context.zod.tsoraction-execution.ts(open PR file lists read just before this claim)objectstack-fleet commentedon Oct 30, 2026
domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V
os-dev-report
{
"status": "pr_open_draft",
"posted_at": "2026-10-30T15:09Z",
"card": 27908,
"branch": "claude/issue-27908-action-audit-surface",
"pr": 28171,
"head_sha": "c6228a99cd1ef544af6e17b2211b2eb9b2fb9c1a",
"session": "session_017ihL2fi9zfJhUTubyJYb6V",
"premise_still_valid": true,
"summary": "ExecutionContext.surface (rest | mcp) set by the REST action door and the /mcp run_action / resume_run door on a copy of the invocation envelope; plugin-audit writes one sys_audit_log row (action action_invocation, metadata.surface) per invocation on every branch (script, api, flow, update), success or post-dispatch refusal; no new field on sys_audit_log.",
"seat_decisions": [
"key name ExecutionContext.surface and its closed values rest, mcp (maintainer approves in the PR, Clause-2 yes)",
"new sys_audit_log.action enum value action_invocation (a value, not a field)",
"no surface header on the api forward (deviation 1): accept, or require a trusted in-process carry",
"Metadata-protocol added with minor bumps instead of the claim none (deviation 2)"
],
"tests": {
"packages/runtime/src/action-invocation-audit.test.ts": "24 passed (branch x door success and failure, subject 404 row, D4 gate no row, forged forward mark + surface header + upstream surface refused, api forward carries no surface header, envelope not mutated, performedBy carried, resume_run rows)",
"packages/plugins/plugin-audit/src/action-invocation-audit.test.ts": "7 passed (real ObjectQL read-back)",
"package_suites": "core 128 files / 2873 passed; plugin-audit 66 / 1058; runtime 484 / 7895 passed 19 skipped; spec 756 / 22510 passed 1 todo",
"ablation": [
"runtime: removed await sink.recordActionInvocation(event); gives 20 failed / 4 passed; restored blob d014ccafe5bb",
"plugin-audit: removed await persistActionInvocationAuditRow(engine, row); gives 3 failed / 4 passed; restored blob 9d2076906454"
]
},
"gates": {
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack",
"derived": 131,
"run": 131,
"not_measured": 0,
"unrun": 0,
"nonzero_final": 0,
"reconciliation": "dispatch-gates --ran: 131 accounted, 0 NOT-MEASURED, derived zero (every line carries its exit code)",
"head_measured": "c6228a99cd; 127 commands re-run on this head, pnpm check:pm-dispatch-gates measured exit 0 on 26e3fb2 (the last commit touches no scripts/pm file), check:skill-examples / check:dual-build-cjs-loads / check:type-check-debt measured exit 0 on this head content after pnpm build under the verify lock",
"first_pass_reds_fixed": [
"check-tenant-audit-census (+ --self-test): new write site; options cast dropped, page recounted 251 to 252",
"check:engine-double-contract: ledger --write",
"check:objectql-double-limit: test double applies limit",
"check:swallow-census-controls: persistActionInvocationAuditRow copied into WRITE_SHAPED_CALLEES",
"check:doc-authoring: issue id dropped from the surface .describe()"
],
"first_pass_exit3_then_green": [
"check:skill-examples",
"check:dual-build-cjs-loads",
"check:type-check-debt"
],
"adr_0087": "node scripts/check-adr-0087-registration.mjs --base origin/main: exit 0",
"check_generated": "14/14 up to date",
"typecheck": "spec, core, runtime, plugin-audit exit 0",
"not_run_locally": "test:repo and the full CI farm (left to CI)"
},
"dogfood_battery": "n/a, no ! in the title and no (narrowing) arm",
"line_budget": {
"declared": "none in the order",
"files": 29,
"added": 1312,
"removed": 56,
"of_added_tests": 558,
"of_added_generated_or_census_docs": 54
},
"deviations": [
{
"id": 1,
"what": "no surface header on the api forward",
"why": "measured: the forward mark carries no trust (any caller can send x-objectstack-action-forward), the row is written door-side after the forward from the door envelope, no far-side writer reads a surface, a sound carry needs a new in-process credential honoured by every envelope-resolving face; pinned by a test that the forward carries none and the row says mcp",
"seat": "decide"
},
{
"id": 2,
"what": "Metadata-protocol added, changesets minor x4 (spec, core, runtime, plugin-audit)",
"why": "the key moves packages/spec/authorable-surface/kernel.json and check-changeset-no-major none-baseline-moved refuses none"
},
{
"id": 3,
"what": "new enum value action_invocation on sys_audit_log.action",
"why": "a value, not a field; no new column; surface lives in metadata"
},
{
"id": 4,
"what": "surface stamped on a copy of the envelope, not the request ec",
"why": "nothing upstream or downstream sees a moved request envelope"
},
{
"id": 5,
"what": "resume_run also writes a row (branch flow, run_id)",
"why": "it is an MCP door continuing an admitted flow action"
},
{
"id": 6,
"what": "tenant_id is the caller active org (ExecutionContext.tenantId), not the subject record org",
"why": "the row does not read the record"
},
{
"id": 7,
"what": "test:repo and full farm not run locally",
"why": "CI owns them per common.md"
}
],
"files_changed": [
".changeset/27908-action-invocation-audit-surface.md",
"content/docs/permissions/tenant-audit-census.mdx",
"content/docs/references/data/data-engine.mdx",
"content/docs/references/kernel/execution-context.mdx",
"docs/audits/2026-08-tenant-audit-write-call-sites.counts.md",
"packages/core/src/security/assemble-execution-context.test.ts",
"packages/core/src/security/assemble-execution-context.ts",
"packages/plugins/plugin-audit/src/action-invocation-audit.test.ts",
"packages/plugins/plugin-audit/src/action-invocation-audit.ts",
"packages/plugins/plugin-audit/src/audit-log-field-redaction.ts",
"packages/plugins/plugin-audit/src/audit-log-read-visibility.ts",
"packages/plugins/plugin-audit/src/audit-plugin.ts",
"packages/plugins/plugin-audit/src/index.ts",
"packages/plugins/plugin-audit/src/objects/sys-audit-log-retired-actions.test.ts",
"packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts",
"packages/plugins/plugin-audit/src/translations/en.objects.generated.ts",
"packages/plugins/plugin-audit/src/translations/es-ES.objects.generated.ts",
"packages/plugins/plugin-audit/src/translations/ja-JP.objects.generated.ts",
"packages/plugins/plugin-audit/src/translations/zh-CN.objects.generated.ts",
"packages/runtime/src/action-execution.ts",
"packages/runtime/src/action-invocation-audit.test.ts",
"packages/runtime/src/action-invocation-audit.ts",
"packages/runtime/src/domains/actions.ts",
"packages/runtime/src/domains/mcp.ts",
"packages/spec/authorable-surface/kernel.json",
"packages/spec/src/kernel/execution-context.zod.ts",
"scripts/check-durability-degradation-log-level.mjs",
"scripts/engine-double-contract.pinned.json",
"scripts/measure-durability-swallow-family.mjs"
],
"mcp_calls": 0,
"api_writes": 2,
"api_writes_detail": [
"pr_create via fleet-write dispatch (run 41077866252, read-back identical)",
"this comment via post-stamped"
],
"open_questions": [
"Should the forward carry a trusted surface (new in-process credential) so CRUD rows written by an api target also name the door? Not needed for the action_invocation row."
],
"out_of_scope_findings": [
{
"class": "a (candidate)",
"reach": "not measured; read from code",
"evidence": "packages/runtime/src/http-dispatcher.ts:753 path-matches acceptOAuthAccessToken to /mcp, and forwardApiAction relays the caller bearer to a non-/mcp target, so an OAuth-token agent api action would resolve anonymous at the target (likely 401 on sys* identity api actions)",
"dedupe": "oauth forward api action anonymous acceptOAuthAccessToken"
},
{
"class": "observation",
"reach": "harmless",
"evidence": "API_FORWARD_MARK_HEADER x-objectstack-action-forward is forgeable by any caller; its only effect is the loop refusal (pinned in action-invocation-audit.test.ts)"
}
]
}
objectstack-fleet commentedon Oct 31, 2026
domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V
Landed · 2026-10-31T05:47Z · PR #28171 merged by the queue at 2026-10-31T05:46Z as
3a054cbe1f; the card closed byFixes. This seat stripspm:dispatchedin the same act. Contract review: PASS 6537892848 on the landed head. 田騏 approved the namesExecutionContext.surface(rest/mcp) andaction_invocation. Every action invocation now leaves onesys_audit_logrow naming the door it came through, on both the REST and MCP doors and on every branch. Should-fixes S1 (join the two rows of an api forward to an action route) and S2 (the awaited ledger insert) are filed as #28281; the OAuth-bearer gap on api forwards is #28178.