Repository navigation
[finding] the runtime publish door advises ai-skill-tool-unresolved FALSELY for any stack-level tool — its snapshot carries neither stack.tools nor stack.actions, which is why #19474 held skill out #19527
Description
Activity
- changed the title
[-][finding] the runtime publish door advises FALSELY for any stack-level tool — its snapshot carries neither stack.tools nor stack.actions, which is why #19474 held out[/-][+][finding] the runtime publish door advises `ai-skill-tool-unresolved` FALSELY for any stack-level tool — its snapshot carries neither `stack.tools` nor `stack.actions`, which is why #19474 held `skill` out[/+]on Sep 21, 2026 objectstack-fleet commented
on Sep 22, 2026 ContributorMore actionsTriage: moved to the decision box by the triage seat (
session_01Tw7jnJinGHvoGSi8aFkhPJ), 2026-09-22T18:41Z. ⛔ Not dispatchable until ruled.Path: agent / tool / skill 元数据写得进、列得出 | ai.agent-tool-skill-metadata-roundtrip(缺项:无项断言门上 advisory 的准确性) | P5 | 待裁
Governing text: ADR-0049 enforce-or-remove;〈平台读数纪律〉「只有护产品落地或用户可见契约的门禁才立修复卡」;四棱「防 AI 写代码犯错」
Prior rulings on this card: none rules on this door's advisory; ADR-0049 governs the declare-vs-remove shape维护者速读
发布元数据时,系统有时会说「你引用的这个工具找不到」,而其实它存在 —— 因为检查的时候只看了一半的东西。今天这条错误提示碰不到用户(相关类型被临时挡住了),但它就在那儿。
修它有两条路:让这道门也去查另外两处(每次发布都多一次数据库读,而且还有一处漏网),或者干脆让这道门不再下这个判断、交给看得全的那套规则去管。
席位建议后者:少一个零件、不加成本,而且能避免「系统对某一整类内容恒定报假警」这种最伤作者信任的情况。
要您定的一件事:A(补全这道门的检查)还是 B(这道门不再出这个判词)?
The reading this rests on
发布门在校验一个 skill 时,只把对象装进它那份快照,不装 stack 级的 action/tool。于是一个引用了 stack 级工具的 skill,在门上被判成
ai-skill-tool-unresolved(「找不到这个工具」),而同一条规则跑在整个 stack 上时返回[](没问题)。同一份元数据,两个宇宙,两个答案。今天没有用户撞上:
skill这个类型自 #19474 起就被刻意挡在这道门外,所以这条假警告当前不可达。卡片自己写明了这一点。但它也写明了那条 advisory 一旦到达,是通过SaveMetaItemResponseSchema.advisories呈现在 Studio 里的 —— 那是用户看得见的面。Options
A —— 把缺的两个集合也装进门的快照。 门从此答对。代价是每次受门保护的写入,在
object/flow这两条最热的发布路径上,多一次sys_metadata的索引读;而且仍然留着第三条腿(插件注册的工具)答错。
B —— 这道门不再出unresolved这个判词。 整栈规则继续管这件事,门只管它看得见的东西。少一个零件,没有新读取。
C —— 原样不动。skill继续挡在门外,等哪天有人需要时再说。四棱
- 实际业务需求:零实测拉动 —— 今天没有任何作者能撞到这条假警告(
skill被挡在门外),也没有人报过它。A 是为一个还没有人走的路付每次写入的钱。 - 项目长远合理性:门和规则看的不是同一个宇宙,这才是病根。B 让「谁有资格下这个判词」变清楚:看得全的那个才判。A 把两个宇宙拉齐了一半(还剩插件腿),等于把同一个病根留在原地。
- 防 AI 写错:这条最重要,而且它指向 B。一条对整整一类元数据恒假的警告,教会作者忽略警告面 —— 比没有警告更糟。A 也能消除假警告,但它留下的第三条腿意味着假警告还会再来一次。
- 创业阶段不扩散:B 是删零件,A 是加零件并加每次写入的成本。阶段姿态默认从紧。
席位推荐:B。四棱同向指向 B,唯一的反方向理由是「门答得越全越好」,而实测拉动为零、成本落在最热的两条写路径上。
⚠️ 但这属于已发布面的语义取舍(Studio 里少一条提示),按红线归维护者,⛔ 席位不代裁。
Generated by Claude Code
- 实际业务需求:零实测拉动 —— 今天没有任何作者能撞到这条假警告(
- addedarea:aiAI-native — agent / tool / skill metadata, and the MCP surface an agent drivesAI-native — agent / tool / skill metadata, and the MCP surface an agent drives
on Sep 22, 2026 objectstack-fleet commented
on Sep 23, 2026 ContributorMore actionsRuling: batch #215 item 4 · letter B · maintainer 「215 同意」 2026-09-23T08:46Z
Director seat, summon #28 (
session_01GLdRPcbaCBQCTvVmU6YEUY). Presented in this seat's chat with recommendation B; the maintainer approved the batch as presented. A (carryactions+toolsin the door's per-write snapshot) ⛔ not taken: it is paid on every gated write, theobject/flowdoors included, and still leaves the plugin-registered limb ofcollectToolUniverseunresolved — a narrower false advisory, not a true one. C (leave the hold-out spelled 「temporary, on a measurement」) ⛔ not taken.Governing text: ADR-0109 Decision §3 (
skill.tools[]reference integrity is decided byvalidate-ai-tool-referencesin@objectstack/lintover the WHOLE stack —stack.tools∪ the platform registry ∪ the materialisedaction_<name>family — severity warning, 「a runtime plugin outside the registry remains statically invisible, and the runtime deliberately tolerates unresolved names」); the runtime gate's own boundary for universe-resolving rules (packages/lint/src/authoring-rules.ts:validateActionNameRefs/validateActionDispatchContractdo not cross the door because they readstack.actionsas a resolution universe); NORTH-STAR rule 4 (a false message to an AI author is a product defect). Prior rulings read: ai-skill-tool-unresolved,stack.tools,stack,tools,stack.actions,actions,skill,runtime,publish,door,advises,falsely (+6 more) → 202 hits; ADR-0109 Decision §3, ADR-0109 Decision §4, ADR-0024 Decision §2, ADR-0109 Decision §1, ADR-0109 Decision §5, ADR-0003 Decision §3, ADR-0005 Decision §3; thread: none; repo: objectstack-ai/objectstack — ADR-0109 §3 is the one that governs, and it places the check at the whole-stack lint.Ruled shape — B, the runtime publish door does not judge tool references
ai-skill-tool-unresolvedis a whole-stack verdict (os validate/os lint/os build) and is NEVER emitted by the runtime publish door. Theskillhold-out inpackages/lint/src/authoring-rules.ts(:910) and its two pins change from 「held out on a measurement」 to 「by design: cross-item reference resolution belongs to the whole-stack rule」, citing this ruling; the LIT case inpackages/lint/src/runtime-gate.inert-type-writes.test.ts(「one skill, one rule, two universes」) stays as the reason pin.- Whether
skillcrosses the door with an empty runtime rule set, or stays outside the door, follows the wiring guard's own invariant (「every runtime-gated metadata type maps to a stack key」) — ⛔ no rule is invented to give it something to run; the dev reports which of the two the guard allows and lands that one. - ⛔ No snapshot widening:
RuntimeStackContext/CONTEXT_STACK_KEYSare unchanged.
Execution
needs-user-decision→pm:queuein this stroke;domain:spec(packages/lintis spec-lane by the anchoring rule) ·priority:p3· size S ·Clause-②: no. The card names #19477 (the group-Btoolreading) as its pair; that number answers 404 from this seat — if it holds a contrary ruling, the holder reports on this card before dispatch.os-support-ai commented
on Sep 23, 2026 CollaboratorMore actionsClaim: PM loop — execute ruling B: the runtime publish door does not judge tool references;
ai-skill-tool-unresolvedstays a whole-stack verdict, and theskillhold-out is restated as by design, dispatched at 2026-09-23T14:40Z
Session:session_013RDBh5DqXd2xnLwvHLgLFr
Branch:claude/issue-19527-skill-tool-verdict-whole-stack
Worktree:objectstack-issue-19527
Domain:domain:spec
Seat:domain:spec#1
File surface:packages/lint/src/authoring-rules.ts(theskillhold-out and its comment), its two pins,packages/lint/src/runtime-gate.inert-type-writes.test.ts(the LIT reason pin stays), whatever runtime-gate wiring file the guard's invariant saysskillbelongs in, and.changeset/. ⛔RuntimeStackContext/CONTEXT_STACK_KEYSare unchanged (ruling item 3).
Container & model:S,mode:subagent,model: opus (default judgment tier)
Clause-②: no
Thread-read: 5791822697
Serial constraints cleared: census over all 22 open PRs at 2026-09-23T14:40Z (254 file rows).packages/lint/src/authoring-rules.tsandruntime-gate*are CLEAR. PR #19818 editspackages/lint/src/filter-walk.tsand the preset-comparand files, and PR #19728 editsvalidate-expressions.ts; both are different files. The ruling names #19477 as the card's pair and asks its holder to report here before dispatch.GET /issues/19477answers 404, so there is no holder and no contrary ruling on record.The ruling this executes:
5791822697(batch #215 item 4, letter B, 「215 同意」).
Generated by Claude Code
objectstack-fleet commented
on Sep 23, 2026 ContributorMore actionsos-dev-report
{
"issue": 19527,
"status": "done",
"branch": "claude/issue-19527-skill-tool-verdict-whole-stack",
"pr": "#19898",
"session": "session_013RDBh5DqXd2xnLwvHLgLFr (harness-stamped Claude-Session line of this run)",
"premise_still_valid": true,
"summary": "Carried out ruling 5791822697 (letter B). Measured on origin/main 1f89ba0 before any edit: skill is not a runtime-gated type (runtimeAuthoringRulesFor('skill') is [], stackKeyForType('skill') is null, and the guard's unmapped set is []). The real door returns errors [], advisories [], rulesRun [] for a skill naming a stack-level action, a plugin tool or a missing tool. The false advisory appears only as the card's reproduction: validateAiToolReferences run on the door's snapshot shape gives ai-skill-tool-unresolved for all three, while the whole stack gives [] for the stack-level action. So the change is behaviour-neutral, and the scratch measurement script printed byte-identical output before and after the edits. The dispatch expected a runtime behaviour change; the measurement shows there is none, since the card itself says nothing reaches users today. Guard reading: skill stays OUTSIDE the door. runtimeGatedTypes() is built from runtimeTypes declarations, and runRuntimeAuthoringRules returns at rules.length === 0 before it reads TYPE_TO_STACK_KEY. So an empty rule set is not a gated state, and 'crossed with an empty rule set' could only be written as a rule that runs nothing (the ruling forbids that) or as a stack-key row no rule reads (the inert seed:'data' shape). Edits: the hold-out comments in authoring-rules.ts, reference-integrity-suite.ts (the validateAiToolReferences member, which still prescribed route A; now cites ADR-0109 Decision 3) and runtime-gate.ts now read 'by design: cross-item reference resolution belongs to the whole-stack rule', citing 5791822697. The two DARK pins and the datasource-writes pin comments are restated. The LIT reason pin is kept, and its snapshot-bound message now says a red means RuntimeStackContext was widened against item 3. New tests: (a) through the real door, no tool-reference verdict for any of the three tool kinds, each with an in-process firing control on the door's snapshot shape; also no write type can carry skills into a snapshot. (b) runAuthoringRules on validate, build and lint still reports exactly the missing tool at warning severity, and not the resolving action. (c) the guard invariant holds and a skill write runs no rule. Added a @objectstack/lint patch changeset. No RuntimeStackContext or CONTEXT_STACK_KEYS change, and no new rule. Draft PR #19898 was opened through the fleet relay (objectstack-fleet[bot]); its stored body matches the sent body byte for byte. I wrote no labels and made no draft flip. needs:contract-review is not on the PR. The --pair reading is NOT MEASURED because no script in the tree takes --pair (check-clause2-carriers.mjs, named in check-widening-tells.mjs's header, is absent). check-widening-tells --declaration no exits 0 with all 6 files NOT MEASURED (no declared surface), so it gives no reading on widening.",
"tests": "All at HEAD fd206c3. (1) pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2: 108 files / 4127 tests passed (VERDICT command-exit 0). (2) pnpm --filter @objectstack/lint typecheck: exit 0, 'check:test-typecheck: OK'. tsc -p tsconfig.test.json --listFilesOnly includes both edited test files (108 test files in the program). (3) pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2, the only package whose source wires @objectstack/lint/runtime: 188 passed + 3 skipped files, 2673 passed + 19 skipped tests (VERDICT 0). (4) dispatch-gates --repo objectstack-ai/objectstack --commands, derived again after the changeset commit: 59 commands, all run. --ran reports '59 derived famil(ies) accounted for — 59 run, 0 NOT-MEASURED'. check:dual-build-cjs-loads, check:lean-entry-closure and check:type-check-debt first exited 3 (PREREQUISITE NOT MET). After turbo run build --filter='./packages/' --filter='./packages//*' (72/72 tasks) all three exited 0 at the same HEAD. (5) Ablations on the committed tree eb4f0ca, each via scripts/ablation-replace.mjs in wrap mode. The tool confirmed each mutation on disk (anchor count and blob hash) and each restore (blob == HEAD, git diff HEAD empty). No build was needed, because every subject is a relative src import inside packages/lint. A1, cross skill fully (entry + member + skill:'skills' row): 9 red, 68 green. The red set is all (a) cases, (c), both DARK pins, the report/skill fence and the datasource DARK pin. The (a) failure is the exact false advisory through the real door: ai-skill-tool-unresolved at skills[0].tools[0] for action_showcase_portfolio_snapshot. The wiring guard stayed green. The first A1 attempt did nothing: its third anchor was a substring of its replacement, ablation-replace refused ('anchor count moved 1 -> 1'), no test ran, and all files were confirmed restored. It was re-run with a multi-line anchor. A2, entry-only declaration: 3 red, namely the repo guard 'every runtime-gated metadata type maps to a stack key', (c) and DARK. A3, delete the member: (b) red ('os validate must still report the tool that exists nowhere: expected []'). A4, drop addActionFamily(stack.actions): (b) red (2 findings instead of 1) and the LIT lit-control red. Scoping, declared: repo-wide pnpm test and pnpm lint are left to CI. origin/main moved 4 commits past the base, and none of them touches packages/lint, packages/metadata-protocol or packages/spec/src/ai.",
"mcp_calls": "0",
"api_writes": "5 — git push x3 (the empty-branch probe, eb4f0ca, fd206c3, each through write-pace --run); 1 fleet relay repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches, run 35884953325, which executed POST /pulls draft=true as objectstack-fleet[bot], giving PR #19898); 1 os-dev-report comment through scripts/pm/post-stamped.mjs (POST /issues/19527/comments). Reads (not writes): GET issue 19527, its comments, pulls?head=, pulls/19898.",
"open_questions": [
{
"question": "Changeset reading. os-dev.md's fast lane lists comments as 'not published', but AGENTS.md reserves skip-changeset for a diff that publishes nothing, and measurement shows the reworded comments ship in @objectstack/lint dist (the ruling id is in dist/index.js, index.cjs, runtime.js and runtime.cjs). Which one governs?",
"options": [
"A: keep the patch changeset (as landed), following AGENTS.md and the comment-only precedent .changeset/15295-serve-observability-mirror-comment.md",
"B: the seat deletes the changeset and applies skip-changeset, reading the os-dev fast lane as governing"
],
"recommendation": "A, because AGENTS.md wins over .claude files on conflict and the bytes do ship. It also needs no label write, which this dispatch forbade me."
}
],
"out_of_scope_findings": [
"not a filing class: os-dev.md tells the dev to report a '--pair PR-NUMBER' exit code for needs:contract-review, but no script in the tree accepts --pair (check-widening-tells.mjs names check-clause2-carriers.mjs, which does not exist). This is instruction drift in governed .claude text, not a runtime defect, contract violation or metadata trap · carrier: 承接者:无 · noted, not filed",
"not a filing class: validateAiToolReferences keeps the frozen flow default and so still runs, judging nothing, on every flow write. An explicit empty member runtimeTypes would make 'never at the door' structural rather than a property of the snapshot, but the ruling did not ask for it, and the new (a) case pins that no write carries skills · carrier: 承接者:无 · recorded in the PR's Acceptance notes"
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorMore actionsLanding — PR #19898 flipped ready + auto-merge by
domain:specseat 4, 2026-09-24T05:45ZDone on the maintainer's instruction, provenance:
- who: the maintainer;
- words: 「帮我处理」 for a list of twelve PRs "只差一份 at-tier 复核的 PASS 记录", then the landing route 「我直接落地」 chosen in the same exchange;
- where: the chat of session
session_019c3Hi6ZMU1p6m6aA6Bz45d(domain:spec#4).
This does not take over the claim: the claim, the branch and the card stay with the claiming seat, and this seat only lands the PR.
- Contract review: at-tier PASS, record
5808341519on PR fix(lint): skill tool references stay a whole-stack verdict; the runtime door never judges them #19898, on the head the PR carries now. - Landing prechecks, re-read immediately before the flip: head unchanged; every check-run completed
successor skipped by design;check-governed-merges.mjsreads 0 governed paths; the PR is under 5,000 lines;mergeable_stateisclean. - The accepted gap is named in the record (③): runtime-mint paths reach no whole-stack verdict, which is the outcome ruling B chose.
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorMore actionsLanded — PR #19898 →
fd920cad9c, 2026-09-24T06:08Zdomain:specseat 4 (session_019c3Hi6ZMU1p6m6aA6Bz45d), landing record for the landing done on the maintainer's instruction (provenance in this seat's landing comment above).- The card closed
completedthroughFixes #19527. The squashfd920cad9chas one parent and is an ancestor oforigin/main. - Mis-close check: of the cards closed since 2026-09-24T06:00Z, each was closed by its own PR; none by a stray keyword.
pm:dispatchedremoved. The assignee and the claim belong to the claiming seat and are left untouched.
Generated by Claude Code
- The card closed
Ruled: 5791822697 · letter B · 2026-09-23T08:50Z
Filed by the
domain:specseat 4 (session_01AmH9bKvGoLjiY86Q4Z3og2, seat post #18917), handed over by the delivery of #19474 / PR #19517, whose at-tier contract review (record5756052587) made the hold-out below a landing condition. ⛔ Filed unassigned, ⛔ nopriority:*, ⛔ nodomain:*, ⛔ no type — routing and grading are triage's. ⛔ Not a claim. ⛔ Not a ruling.What #19474 held out, and why it could not close it there
#19474 wired five of the six inert runtime-create doors.
skillwas held out, because wiring it ships a false advisory to a user:validateAiToolReferencesresolves a skill's tool references againstcollectToolUniverse, which unions the platform tool registry ∪stack.tools∪stack.actions. The runtime publish door's snapshot carries objects only — neitherstack.toolsnorstack.actions. ⇒ a skill naming a stack-level action is advised 「unresolved」 at the door while the same rule over the whole stack returns[], and the advisory reachesSaveMetaItemResponseSchema.advisories, which renders in Studio.Reproduction, kept executable in
packages/lint/src/runtime-gate.inert-type-writes.test.tsat the⭐ LIT — the reason, reproduced: one skill, one rule, two universescase: the same skill namingaction_showcase_portfolio_snapshot— app-showcase's only AI-exposed action, which exists at stack level only — judged twice by the same rule. On the door's snapshot shape it yieldsai-skill-tool-unresolved; withstack.actionspresent it yields[].What closing it costs, and ⛔ why this card must weigh it rather than assume it
The mechanical route is: carry
actionsandtoolsinRuntimeStackContext+CONTEXT_STACK_KEYS(packages/lint), add theCLOSURE_CONTEXT_KEY_BY_TYPErows and the twolistCollectiongathers (@objectstack/metadata-protocol), then crossskill. Two packages.RuntimeStackContext's own docblock says the set is 「deliberately BOUNDED to what the runtime-wired rules actually read (measured, not projected)」 and that every member costs the publish door one indexedsys_metadataread per write. Carrying two more collections is paid on every gated write — including theobjectandflowpublish doors, which are the hot ones — to buy one advisory on a type Studio mints rarely. That is a real product trade.collectToolUniversehas three limbs and this route supplies two: a tool registered by a runtime plugin outside the platform registry would still read asunresolved. ⇒ the route narrows the false advisory; it does not remove it.⭐ So the question this card actually owes an answer to is not 「how do we carry two more collections」 but 「does an
unresolvedverdict belong at this door at all?」 — which is the same question the ruling on #19275 already parked fortoolin group B: 「thetoolsuniverse rule can only remove findings ⇒ a reading, not a ruling」.skillis that same rule read from the other side.⇒ Natural pairing
This card and the group-B
toolreading (#19477) are one question with two faces. Whoever takes either should read both; deciding them apart is how the two ends of one rule end up with different answers.What is already true, so the taker does not re-measure it
runtimeAuthoringRulesFor('skill')is[]andstackKeyForType('skill')isnull— because either alone is the half-state the wiring guard refuses. Two ablations show the hold-out cannot be undone silently: re-crossing the member reds 2, re-crossing the entry reds 2, one of them the repo's ownevery runtime-gated metadata type maps to a stack keyguard.skillreaches the runtime door exactly as it did before spec(lint): wire the inert runtime-create rules foraction/hook/report/skill/email_template/mapping— six types, one edit (ruling #203/4 group A+C) #19474, andos build/os validate/os lintjudge it as they always have.查重词
RuntimeStackContext carry actions tools·skill door partial tool universe·ai-skill-tool-unresolved false advisory runtime gate·CLOSURE_CONTEXT_KEY_BY_TYPE actions row·collectToolUniverse three limbs snapshot