Repository navigation
[finding] ToolExecutionContext.confirmedBlueprintIdentity now authorizes the largest metadata write in the product, but it is declared only on cloud's augmented context — the protocol's ToolExecutionContext (contracts/ai-service.ts) does not carry it, unlike userMessageText #15937
Description
Activity
Maintainer ruling recorded — option 1: declare
confirmedBlueprintIdentityin the protocol'sToolExecutionContextDirector seat, summon #15,
session_01TezFG8ZMrNH6n5VTNpPpdH(os-zhuang), 2026-09-05T15:5xZ.Provenance (who / verbatim / where): maintainer, live director chat, 2026-09-05, after asking 「objectstack#15937 具体什么情况」 and reading the seat's two-option analysis (1 = declare in the protocol; 2 = rule cloud's augmentation the intended extension point). Verbatim: 「同意」 — on the seat's recommendation 1.
Freshness re-read before recording:
packages/spec/src/contracts/ai-service.tsToolExecutionContextonorigin/main04679418still carriesuserMessageTextand notconfirmedBlueprintIdentity; cloudorigin/main5c965861still declares it only onpackages/service-ai/src/tools/tool-registry.ts:45and reads it by structural cast inpackages/service-ai-studio/src/tools/confirm-gate.ts:124-126. Nothing later on this card.What the ruling means for the implementer (spec seat)
- Add
confirmedBlueprintIdentity?: stringtoToolExecutionContextwith a provenance docblock of the same shapeuserMessageTextcarries: the blueprint-identity digest the route-owning layer stamps on a confirm replay of an approved proposal; populated only by in-process server code that owns the agent route (cloud, post-ADR-0025); never derived from a request body;undefinedmeans "no confirmed identity on this turn" and authorizes nothing. Cite cloud#1954 / cloud PR chore(showcase): seed Field Zoo with all field types + guard budget hook #2005 as the consumer that authorizesapply_blueprinton it. - Changeset
@objectstack/specminor.Clause-②: yes— the public contract widens by one optional field; contract-review tier. - While in the file, read
systemInvocation(cloud's augmentation declares it beside the two others) and record on the PR whether it is the same class of omission; if it is, say so and leave it for its own card unless the maintainer folds it in — ⛔ do not widen this card silently. - Downstream (cloud, after the spec pin carries the field): delete the augmentation on
tool-registry.ts:45, replace the cast inconfirm-gate.tswith the typed read. That is a cloud follow-up card the cloud seat files withBlocked-by:this one published and pinned; not this card's scope.
State transition, same stroke:
findingkept (type);pm:queueadded for the spec seat's queue. Not dispatched by this seat (maintainer instruction 2026-09-05 15:1xZ 「你只需要决裁,后续的单子你不用再派」). Ledger: director seat post #12708, summon #15.
Generated by Claude Code
- Add
- addedenhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3and removed
on Sep 5, 2026 分诊 ·
domain:spec/enhancement/priority:p2/needs-user-decision分诊席位。⛔ 不认领、不派发、不写代码、不合并、不裁决 decision-box 卡 —— 本卡就是 decision-box。⛔ 本 session 是
claude-opus-5,CONTRACT_REVIEW_TIER硬闸要求 fable,所以下面只装框、不选项。origin/main@a4816a7,2026-09-06T02:26Z。三条测量复现
断言 实测 interface ToolExecutionContext在 spec✅ packages/spec/src/contracts/ai-service.ts:406userMessageText在该接口里✅ 该文件命中 1 confirmedBlueprintIdentity在 objectstack 全packages/0 文件 活控制: userMessageText在全packages/1 文件 ⇒ grep 起火 ⚠️ 控制口径说明:userMessageText全仓只在 spec 那一个文件里出现,所以这个控制虽然起火但很薄。它证明的是「grep 确实扫到了packages/,并且能找到那个类比字段」——足够支撑「confirmedBlueprintIdentity的零是真零」,但它不是一个大样本控制,据此不要推出更多结论。⇒ 卡的核心对照成立:PR 说「和
userMessageText、systemInvocation一起放进 cloud 自己的增强上下文」,但userMessageText恰恰不在 cloud 的增强里,它在协议里。 两个字段不同类。车道
domain:spec两个选项的落点都在
packages/spec/src/contracts/ai-service.ts:选项 1 加一个可选字段,选项 2 改该接口的 docblock。⇒domain:spec。⛔ 不是repo:cloud:cloud 侧的删除(选项 1)是后果,不是落点;协议先动,cloud 才跟。状态
needs-user-decision(原pm:queue)pm:queue会让派发把它当可派,但卡正文自己写着「The seat records this as the likely direction, not as a ruling」,而两个选项的产物完全不同(选项 1 = spec minor + Clause-② yes + 契约评审;选项 2 = docs-only)。⇒ 换needs-user-decision,已移除pm:queue(本仓约定二者不共存,对照 #15854 / #15617 / #15542)。四面框(给裁决者)
一、已确立的事实
一个决定「要不要建整个 app」的同意字段,现在由一个已发布的 handler 读取来做授权(cloudconfirm-gate.ts:123-127结构化 cast,authorizeApply的第 3 条),但只声明在一个消费者的增强类型里(cloudtool-registry.ts:45)。协议侧零声明。二、今天没有人坏
卡自己说清楚了,我复核同意:cast 在缺字段的上下文上读到undefined,且不从缺失推出授权。所以这不是一个在跑的漏洞,是一个声明位置问题。⇒ 不是 p1。三、两读,以及维护者原则指向哪一边
维护者 2026-09-05 原话:「本项目以协议为基准。所以开发应该对其协议,协议有问题应该立卡修改协议」。卡据此把选项 1 记为默认方向。⛔ 我不替裁决者认定这句原则已经把话说死——它是原则不是对本字段的裁决,而选项 2 恰恰是「把增强点写进协议 docblock」,同样是一种「对齐协议」。两条都在原则之内,所以要人来选。四、裁错的代价,两个方向不对称
- 选 1 而实际应为 2:协议多了一个可选字段,冗余但无害,cloud 的增强被删。可回退(ADR-0087 处置一次)。
- 选 2 而实际应为 1:授权字段永久活在 cast 里。
userMessageText的存在会持续制造「同类字段有的在协议、有的不在」的不一致,下一个 route-owning layer 照着 cloud 抄,第二个、第三个 cast 出现,且每一个都是 handler 用来授权的。⇒ 这一侧的错更贵、更难收。
五、
⚠️ 选项 2 的连带后果,卡点到了但值得放大
卡写「userMessageTextarguably should have stayed out of the protocol by the same rule」。⇒ 选 2 不只是加一段 docblock,它同时把userMessageText变成一个历史遗留,逻辑上要么跟着退出协议(那是 Clause-② breaking),要么被解释为例外。⛔ 裁决者选 2 时请连这条一起裁,否则下一轮会以「userMessageText为什么还在协议里」的形式回来。优先级 p2
无人今天受损(上面第二点),所以不是 p1;但它管的是产品里最大的一次元数据写入的同意判定,且错误方向不对称、会随时间加重。⇒ p2。
类型
enhancement选项 1 是在公开面上加一个可选字段 ⇒ 加宽 public surface ⇒ 不是 bug/tidy。⛔ 我没有提前挂
needs:contract-review:那是选项 1 才带的(Clause-② yes),选项 2 是 docs-only 不带。裁决落地后由接手席位按结果挂。
Generated by Claude Code
Director seat, 2026-09-06 12:37Z — state corrected: this card was already ruled. The maintainer's ruling (option 1, verbatim 「同意」) is recorded above at 5553002827 (2026-09-05 15:5xZ); the triage stroke at 02:27Z today re-hung
needs-user-decisionon top of it, which put a decided card back in the maintainer's inbox. Nothing to re-decide:needs-user-decision→pm:queuein this stroke;domain:spec,priority:p2,enhancement,findingkept. The implementer's brief is the ruling comment.
Generated by Claude Code
Claim: PM loop round 4
Session:session_01LvwGppdonww4zGLWZo5rho
Branch:claude/issue-15937-confirmed-blueprint-identity-protocol
Worktree:objectstack-issue-15937
Domain:domain:spec
Seat:domain:spec#1
File surface:packages/spec/src/contracts/ai-service.tsand its tests, the changeset, and whatever generated artefact the added field pulls in (stop on breach; explain in the report)
Container & model:S,mode:subagent,model: default judgment tier (opus)— mandatory clause ② applies (the ruling declaresClause-②: yes), and that clause puts the BUILD at the default judgment tier with the REVIEW atCONTRACT_REVIEW_TIER
Clause-②: yes
Thread-read: 5559258277
Serial constraints cleared:none— the only other card this seat has in flight is #15811 (PR #18638), whose surface is the 36 evaluated-expression slots plussystem/tracing.zod.ts,migrations/**andcontent/docs/references/**; ⛔ it does not touchcontracts/ai-service.tsClause-② is
yesand the ruling says so itselfRuling item 2, verbatim: 「Changeset
@objectstack/specminor.Clause-②: yes— the public contract widens by one optional field; contract-review tier.」 ⇒ carrier hung on the card at claim time, PR side when the PR exists. ⛔ The seat does not second-guess a declaration the ruling makes.Premise check at claim time — three faces, measured
Card-reference surface. The card has a state correction worth reading before anything else: the maintainer's ruling (option 1, verbatim 「同意」) is
5553002827, 2026-09-05; a triage stroke the next morning re-hungneeds-user-decisionon top of an already-decided card, and the director seat reversed it at5559258277with 「Nothing to re-decide」. ⇒ ⛔ This card is NOT in the decision box and must not be read as though it were. The implementer's brief is the ruling comment.Work-item surface, on
origin/mainwith controls — the gap is still open:target files confirmedBlueprintIdentityacrosspackages/0 lit control userMessageText1 lit control ToolExecutionContext8 dark control 0 ⚠️ Note the first control's width, as triage did on this card:userMessageTextexists in exactly one file repo-wide, so it proves the grep reachespackages/and finds the analogue field — ⛔ but it is a thin control, and nothing more should be derived from it. The second control (8 files) is the wide one.Action surface (shallow-safe via
git-history.mjs touch):packages/spec/src/contracts/ai-service.tslast touchedfe0d9a424, 2026-09-07T10:00Z, which is two days AFTER the 2026-09-05 ruling, and the touch was large (+134 lines). ⇒⚠️ the interface the ruling names has moved since it was written; the dispatch order requires the field list and theuserMessageTextdocblock shape to be re-read on the current tree rather than taken from the ruling's quotation.
Generated by Claude Code
os-dev-report
{
"issue": 15937,
"status": "done",
"branch": "claude/issue-15937-confirmed-blueprint-identity-protocol",
"pr": "#18676",
"session": "session_01LvwGppdonww4zGLWZo5rho",
"premise_still_valid": true,
"summary": "Ruling items 1 and 2 implemented.ToolExecutionContext.confirmedBlueprintIdentity?: stringis declared in packages/spec/src/contracts/ai-service.ts besideuserMessageText, with all four provenance clauses item 1 names (confirm-replay digest of an approved proposal; stamped only by in-process server code owning the agent route; never derived from a request body, tool argument or transcript;undefinedmeans no confirmed identity and authorizes nothing), citing cloud#1954 / cloud PR #2005 as the authorizing consumer. Changeset is@objectstack/specminor withClause-②: yes (widening)beginning its own line, and the same line begins line 5 of the PR body (not inside a heading). A new pin, confirmed-blueprint-identity-contract.pin.test.ts (6 legs, each negative leg paired with a lit positive one), was added becauseToolExecutionContexthad zero tests naming it anywhere in the repo before this PR —git grep -l ToolExecutionContextlit 8 files and not one is a test. Item 3 is answered on the PR as a read-and-report and NOT folded in:systemInvocationis absent from the protocol too (0 files repo-wide; lit controlsuserMessageText1 file,isSystem853 files; dark control 0), but the second half of the class — being load-bearing for an authorization a published handler makes through a cast — lives in cloud and was not measurable from here, and the field sits besideisSystem, which this same interface declares as 'the ONLY way to obtain system behaviour from the tool loop'. So its first question is 'does it duplicateisSystem', not 'declare it' — a different first question, its own card, and this PR does not widen into it. Item 4 is cloud's; cloud was not opened, touched or filed against. Assignee:os-litantwas already set by the PM dispatch; this seat never wrote it. Claim check: the newest comment on the card is 5715275276 and it names this exact branch — match confirmed before any edit.",
"tests": "All readings cited at the commit they were taken on. FINAL COMMIT 5ddd6f4 (merge of origin/main 7f7b855 into the implementation commit 651ccde). BUILDpnpm --filter @objectstack/spec buildunder scripts/pm/os-verify-lock.sh (slot dev-15937): VERDICT command-exit 0, held 172s. Freshness proof: packages/spec/dist/contracts/index.d.ts carriesconfirmedBlueprintIdentity(grep -c = 1), andgit status --porcelainis empty after the build — no generator moved a tracked artefact. TYPECHECK + TEST joined with '&&' so the verdict covers both:pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/spec testunder the same lock: VERDICT command-exit 0, held 254s.check:test-typecheck: OK — 54 file(s) / 259 error(s) / 144 pinned signature(s)(the shrink-only ratchet, unchanged).Test Files 486 passed (486) / Tests 13874 passed (13874). The new pin run on its own:Test Files 1 passed (1) / Tests 6 passed (6), exit 0. ABLATION — direction predicted RED before the run, and RED is what happened. Run from the COMMITTED state, withtrap '(git -C REPO_ROOT checkout HEAD -- ABS_PATH)' EXIT INT TERMand absolute paths resolved fromgit rev-parse --show-toplevel. On-disk proof of the mutation, not the editor's exit code: delete-anchorconfirmedBlueprintIdentity?: string;went 1 to 0 and inject-anchorconfirmedBlueprintIdentityAblatedwent 0 to 1, andgit hash-objectmoved bf79a0cc3f6fb0505e0edf6c5665c3f004e371f4 to a646979419e2f975f19795bd715cb7a3707f4b08. MUTATED LEGpnpm --filter @objectstack/spec check:test-typecheckexit 1, naming '10 type error(s) in a file the ledger does not cover' in the new pin. RESTORE LEG viagit checkout HEAD -- PATH(never a baregit checkout --): hash back to bf79a0cc3f6fb0505e0edf6c5665c3f004e371f4 (byte identity, not an exit code),git diff HEADempty,git status --porcelainempty; the same gate returned exit 0. NO dist preflight, and that is deliberate: the pin imports './ai-service' relatively, so it resolves to src and a dist reading would be about the wrong artefact. LINT, narrowed and declared, at 5ddd6f4 — three pieces of evidence, none of them a guess. (i) Population read from eslint's own config: the base block isfiles: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'], so the changeset .md is outside the population entirely and eslint says so itself: 'File ignored because no matching configuration was supplied'. (ii) File count read from--format json: 3 entries, 2 of them in-population, 0 errors and 0 warnings on both .ts files;npx eslint --no-inline-configexit 0. (iii) Invariance, quoted from eslint.config.mjs line 327: 'this repo runs oneeslint.config.mjs, which never enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules) for ANY file, test or not' — with no type-aware rule this diff cannot move the verdict on any file it does not touch, and the config's own file rosters (NEVER_LINTED, SLOT_LOOKUP_UNSWEPT, COMMENT_SWALLOW_FILES) are untouched. The repo-widepnpm lintis CI's run and was NOT run here; that is the declared narrowing, not a gap. CONTROL BYTES:pnpm check:nul-bytesexit 0 over 8798 files, plus a directgrep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'sweep of the three changed files, exit 1 (clean). Every exit code above was captured by redirecting first and reading $? — never through a pipe.",
"gates": "Re-derived on MY head, never inherited:node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsat 5ddd6f4. The first derivation at 651ccde printed a STALE TREE warning naming scripts/pm/dispatch-gates.mjs itself as changed upstream, so origin/main was merged and the derivation re-run; both derivations produced the SAME 81 commands (diff over the sorted lists is empty). RECONCILIATION, with exit codes recorded in theCOMMAND :: exit Nform the tool demands: '81 derived famil(ies) accounted for — 77 run, 4 NOT-MEASURED (4 DERIVED from a recorded exit 3)', UNRUN 0. 77 of 81 exit 0. The 4 non-zero are all exit 3 = PREREQUISITE NOT MET on an unbuilt workspace — the class this lane has already established, declared here consistently and NOT reported as new:pnpm --filter @objectstack/lint run check:doc-formula-expressions,pnpm check:dual-build-cjs-loads('Runpnpm buildfirst. This is NOT a pass: nothing was measured.'),pnpm check:lean-entry-closure('This is NOT MEASURED. It is neither a pass nor a failure'),pnpm check:type-check-debt. Only @objectstack/spec was built in this worktree, which is the whole reason those four refuse. Two readings worth naming because they contradict an inherited expectation rather than confirming one:pnpm check:cross-package-test-inputsexit 0 here (the dispatch flagged it as pre-existing red when packages/spec/dist exists — it is NOT red on this tree), andpnpm --filter @objectstack/spec run check:api-surfaceexit 0, which confirms that adding a MEMBER to an existing interface does not move api-surface/contracts.json, because that artefact records exported symbol names and not members.pnpm check:query-options-erasurefirst returned 124 from a 180s harness timeout — NOT MEASURED, not a red — and was re-run with a longer budget to a real exit 0. OUTSIDE the derived 81 and NOT claimed as cleared, quoting the tool: 50 artifact-roster families whose silence is a fact about a list (6 of them keep that roster under a directory one of my paths is in), 11 declared WIDE-population families, the unreachable listing, 5 path-scheduled CI jobs running 28 steps with no local invocation, and the always-runs tail. CI is the authority on those. CI convergence was deliberately NOT waited for: this dispatch declared no per-card exception, so the report ships at the end of local verification and any gate that reddens afterwards returns as a patch round on this claim.",
"clause_2": "Declaredyesby the ruling itself and carried in three places: the claim comment 5715275276, the changeset ('Clause-②: yes (widening)' beginning its own line), and the PR body (line 5, beginning a line, outside every heading — the failure mode a previous round in this lane hit by writing it as a heading). CARRIER READING, report only, nothing written:node scripts/pm/check-clause2-carriers.mjs --pair 18676exits 4 on row C1 — the card #15937 carriesneeds:contract-reviewand the delivering draft PR #18676 does not. That is the expected state at this instant: the dispatch reserves the PR-side hang for the seat and forbids this seat from touching either carrier, and it was not touched. PR #18676's labels aredocumentation, size/m, tests, tooling, all bot-applied and none of them mine; card labels are unchanged atenhancement, priority:p2, pm:dispatched, domain:spec, needs:contract-review.",
"line_budget": "Not applicable, measured rather than assumed: the diff's three paths are .changeset/15937-confirmed-blueprint-identity-protocol.md, packages/spec/src/contracts/ai-service.ts and packages/spec/src/contracts/confirmed-blueprint-identity-contract.pin.test.ts — none under skills/, so the published-skill line ratchet is not engaged and no before/after file or package line count is owed. Diff size: 3 files changed, 160 insertions, 0 deletions on the implementation commit.",
"files_changed": [
"packages/spec/src/contracts/ai-service.ts — +22 lines: the member and its provenance docblock, placed betweenuserMessageTextandtraceId",
"packages/spec/src/contracts/confirmed-blueprint-identity-contract.pin.test.ts — new, 6 legs",
".changeset/15937-confirmed-blueprint-identity-protocol.md — new,@objectstack/specminor"
],
"deviations": "Three, all declared. (1) NARROWED LINT: the repo-wideeslint . --no-inline-configwas not run; the targeted run plus the three evidence pieces above is the declared narrowing, and CI owns the farm. (2) FOUR GATES NOT MEASURED at exit 3 because only @objectstack/spec was built in this worktree; building the whole workspace to clear four prerequisite refusals would have cost far more shared-box seconds than the readings are worth, and CI builds fresh. (3) NO CI WAIT: the report ships at the end of local verification per the standing clause, and HEAD is 1 commit behind origin/main (f6c2eb7 landed after the merge) — the re-derivation reports that none of the commits this tree can see touched what the gate answer derives from, but it cannot speak for unseen upstream work.",
"mcp_calls": "0 — no MCP GitHub tool was called at all, read or write; every GitHub interaction went through the REST proxy with curl",
"api_writes": "2 — POST /repos/objectstack-ai/objectstack/pulls (draft PR #18676) and POST /repos//issues/15937/comments (this report). No label write, no PATCH on any body, no POST /issues. Plus 2 non-REST writes:git pushof the implementation commit and of the merge commit. PR-BODY FOOTER READING, because the dispatch asked for a count: the body was sent with exactly one session-URL footer under a rule line and read back immediately. Stored length 11659 vs sent 11660 — the sole difference is a trimmed trailing newline — and the stored body carries exactly ONE footer, byte-identical in the session-URL form, with zero angle-bracket fragments surviving anywhere in it. No PATCH was issued, so the appended-bare-footer hazard was never entered.",
"open_questions": [],
"out_of_scope_findings": [
"to file (class b, declared-contract violation; dedupe words: ADR-0025, bare ADR citation, cloud ADR spelling, userMessageText docblock, contracts/ai-service.ts) —userMessageText's docblock in packages/spec/src/contracts/ai-service.ts, the line directly above the new member, cites '(cloud, post-ADR-0025)': a BARE ADR number for what is in context cloud's record. AGENTS.md Prime Directive 13, verbatim: an ADR 'lives in the repository whose code it governs', cloud decisions are cited 'ascloud ADR-NNNN— never as a bare number, whichscripts/check-adr-anchors.mjsresolves against this registry (the two number independently)'. Measured: this repo's own ADR-0025 is docs/adr/0025-plugin-package-distribution.md, and every other bareADR-0025in the tree (content/docs, changesets) means that one, so a reader following this citation lands on a real page about plugin packaging. Nothing is red today: check-adr-anchors reads only the entries under scripts/adr-anchors/, not source docblocks, and no entry there names this file. NOT folded in and NOT filed by this seat — it is a different defect class from this card's, so the bounded in-place-fix exemption does not open. The new member spells itcloud ADR-0025, which is why the two adjacent lines differ; the PR body says so explicitly so a reviewer does not read it as sloppiness.",
"noted, not filed:ToolExecutionContextcarried no test naming it anywhere in the repository before this PR —git grep -l ToolExecutionContextlit 8 files (2 CHANGELOGs, api-surface/contracts.json, export-origins/contracts.json, one consumer in service-knowledge, the contract itself) and not one of them is a test file. The new pin closes only the new member's share and deliberately leaves the other twelve members unasserted; widening it is not this card's scope. Who picks this up: the next PR that touches packages/spec/src/contracts/ai-service.ts, which is an actively edited file — last touch fe0d9a4, 2026-09-07, +134 lines.",
"noted, not filed: a platform reading in .claude/skills/pm-dispatch/references/platform-readings.md is contradicted by a measurement taken this round, and that file is a governed surface this seat must not edit. The doc's PR-body write cell says a trailing rule line plus the attribution footer is EATEN and prescribes sending the footer with no rule line, while its fourth form says a create whose body tail is not a rule plus footer block gets one APPENDED. Measured onPOST /repos/.../pullsfor PR #18676: a body ending in a rule line plus the session-URL footer survived byte-identical, one footer, with only the trailing newline trimmed. So at least the create cell in that table is not the whole story. Reported rather than filed because the fix is an edit to a governed reference doc; who picks it up: the pm-dispatch skills seat."
]
}
Generated by Claude Code
- added a commit that references this issue
on Sep 28, 2026 - added a commit that references this issue
on Oct 7, 2026
Filed by the director seat (#12708, summon #15,
session_01TezFG8ZMrNH6n5VTNpPpdH) while auditing cloud#1954's landing against the maintainer's 2026-09-05 principle, verbatim: 「本项目以协议为基准。所以开发应该对其协议,协议有问题应该立卡修改协议」. Observation class;domain:*/ type / priority are triage's. Not a defect in the landed fix — a question of where a contract field is declared.Governing text.
packages/spec/src/contracts/ai-service.ts,interface ToolExecutionContext(origin/main6c439f2): declaresactor,isSystem,conversationId,turnId,messageId,environmentId,agentId, the UI-context block,userMessageText("Forwarded so a tool can detect intent — e.g. an explicit confirm/approval … Populated by whichever layer owns the agent route (cloud, post-ADR-0025)"),traceId,onProgress. NoconfirmedBlueprintIdentity.Measured
AIService.runApprovedProposalReplay(cloudpackages/service-ai/src/ai-service.ts:1835) stampsexecCtx.confirmedBlueprintIdentity = identity; the field is declared on cloud's augmentedToolExecutionContextinpackages/service-ai/src/tools/tool-registry.ts:45;service-ai-studio'sconfirm-gate.ts:123-127reads it offexecby structural cast;authorizeApply(blueprint-tools.ts) makes it clause 3 of the authorization ofapply_blueprint.@objectstack/specchange.confirmedBlueprintIdentityis added to cloud's own augmentedToolExecutionContextin@objectstack/service-ai, alongsideuserMessageTextandsystemInvocation." — butuserMessageTextis in the spec's interface (quoted above), so the two fields are not alike in where they are declared.git grep confirmedBlueprintIdentity origin/main -- packagesin objectstack: zero hits.The question (for triage / the spec seat; the director seat does not rule it)
The protocol declares the tool-execution context a handler may rely on. A consent field that decides whether a whole app is built is now read by a handler but declared only in one consumer's augmentation. Two readings, and the protocol-baseline principle says which is default:
confirmedBlueprintIdentity?: stringtoToolExecutionContextwith the same provenance noteuserMessageTextcarries ("populated by the layer that owns the agent route; set only by in-process code, never from a request body"). Spec minor; Clause-② yes (public surface widens by one optional field); contract-review tier. Cloud's augmentation then becomes redundant and is deleted.ToolExecutionContextdocblock should say so ("route-owning layers may augment; handlers read augmented fields structurally") — a docs-only spec change, anduserMessageTextarguably should have stayed out of the protocol by the same rule.Default under the principle: 1 — the protocol is the baseline; a field a published handler authorizes on belongs in the declared contract, not in a cast. The seat records this as the likely direction, not as a ruling.
Not claimed
That the landed behaviour is wrong (S3/S6 closed, S2 residual tracked as cloud#2006), or that any consumer breaks today — the cast reads
undefinedon any context that lacks the field and authorizes nothing from its absence.Refs: cloud#1954 · cloud PR #2005 · cloud#2006 (blind-route residual) · cloud#1912 (kept
collectPendingBlueprintmodule-local) · #15929 (skills finding: protocol-baseline rule for decision cards).