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
i18n: an app hook's business refusal has no translation key — a zh-CN user reads "A current clean version is required before signing." in English #26170
Filing class: ① product defect (i18n surface) — reach: public door, measured on 18.1.0 with objectstack-ai/hotclm main (pnpm demo:zh, Console and user in zh-CN). Reader: the spec/i18n lane (translation face for hook refusals) and the runtime lane (packages/runtime/src/sandbox/body-runner.ts, the HTTP refusal envelope).
Business reason
Business rules that need the record's state live in hooks; their refusals are the sentences a user reads most often when the app says "not yet". Every other refusal door already speaks the reader's language — validation rules, flow end refusals (flows.<flow>.refusals.<node>.message, #22450), flow write refusals (#25929, #25809). A hook refusal is the one left: an app shipping a zh-CN pack still shows these in its source language, and no pack can fix it.
Measured
hotclm's contract_state_machine hook (src/objects/contract.hook.ts, refuse(message, code, status)) refuses, as designed:
发起签署 without a clean version → 422 INVALID_STATE "A current clean version is required before signing."
送审 without an approved review → 422 "An approved legal review is required before the contract enters approval."
a create born in a non-draft state → "A contract is created as a draft, not as active. …"
Each reaches a zh-CN user verbatim as the toast. hotclm's i18n gate reports 0 missing keys — there is no key.
Why
translation.zod.ts declares refusal keys for flows (refusals.<node_id>.message) and messages for validation rules; hook-api.ts / hook-body.zod.ts give a hook body no keyed way to refuse, so the thrown string is the message.
Expected
A hook body can refuse with a key (e.g. objects.<object>._hooks.<hook>.refusals.<code> or a top-level message id) plus params, the runtime renders it in the caller's locale with the English string as fallback, and the i18n coverage gate counts the keys.
Dedupe: objectstack and objectui issues (2,000 most recently updated each, all states) grepped for hook … message|refus|throw … translat|locale|English|i18n — hits #25929 (flow write refusals, closed), #25383 (auth hooks refactor), objectui#12260 (activity labels), none this.
Filed from the hotclm browser regression thread (2026-10-26).
p1 by 「协议新增 p1」: a hook refusal gets a translation slot (for example hooks.<hook>.refusals.<code>.message; the shape is the spec seat's to settle). It is the one refusal door every other door has already closed (validation rules, flow end, flow writes). The caliber question is still open, and this card is named in triage's report as a protocol addition.
Decision filed · domain:spec seat 3 · os-support-ai · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-27T00:44Z · ⛔ not a claim
Premise check (origin/main6390fe52) before dispatch: the defect holds, but the fix's key shape and how a hook body raises a keyed refusal are an open public-contract choice. No rule or ruling settles it, so the card goes to the maintainer's decision box. The rest of the lane's queue keeps moving.
Director seat, summon #51 continuation (ledger objectstack#12708). Presented to the maintainer in batch #405 as item 2 of 3. Seat increment (re-verified on origin/main6390fe52; the spec seat's decision comment 6430864109 is not restated):
Half-state: needs-user-decision alone (triage's pm:queue was removed by the spec seat's decision stroke); the ruling's stroke returns it to pm:queue.
Facets (the card's block, re-read): ① A gives validation rules, flow ends and hooks one model — refusal text on a named declaration, the key on the declaration, coverage countable; B/D add a second undeclared convention; C conflicts with multi-object hooks. ② hotclm's 3 sentences measured in the browser; every hook-driven state machine hits it. ③ A refuses an undeclared code loudly and the coverage gate reports a missing translation; B/D show English silently with "0 missing". ④ one declared key + one translation group; no gate.
Ruling: batch #405 item 2 · letter A · maintainer 「同意」 2026-10-27T01:17Z
Recorded by the director seat (summon #51 continuation, session_01KrKXQVvgYk2AJ4VkqMobyb, ledger objectstack#12708). The maintainer answered batch #405 with 「同意」 — every seat letter. Brief 6431195984; the spec seat's decision comment 6430864109. Letter A: a hook declares its refusal catalogue — refusals: { <code>: { message, status? } } on Hook — and its body refuses by code; the translation key is hooks.<hook>.refusals.<code>.message; the runtime renders it in the caller's locale with the declared message as the fallback; the i18n extract/check gates count the declared keys; an undeclared code is refused loudly. Not B (an uncountable refusal written into the contract: the coverage gate reports "0 missing" while a zh-CN user reads English), not C (one copy per bound object, nowhere for '*'), not D (a second dictionary with free-text keys).
Workload of the chosen letter (card column): L — spec half (hook.zod.ts, translation.zod.ts group, lint reference check) and runtime half (body-runner.ts / quickjs-runner.ts refusal envelope carries the code and params; locale render) in one version, two PRs; i18n extract/check counting, skills/objectstack-data hooks page and the i18n docs in the same PRs (Skills-docs: line owed under 6431004737). Precedent PR #22525 (flow end refusals, #22450): +1110 / −25, 26 files. The body-side spelling (throw { code, params } or api.refuse(code, params)) is the dev's to measure; a fork is reported, not forced. Serial: behind #25785 / PR #26159 on translation.zod.ts. Grade p1 stands under 6431014712 (a protocol addition is p1). Clause-②: yes, Metadata-protocol: added.
Execution: needs-user-decision → pm:queue in this act (domain:spec, p1, area:i18n). Ruling-ref: names this comment.
Claim: PM loop round 2 · 2026-10-27T13:15Z
Session: session_01WGufzgbJhFoXYsLSe5WSPP
Account: os-support-ai
Branch: claude/project-thread-ivr552 (card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead of claude/issue-26170-<slug>)
Worktree: objectstack-issue-26170
Domain: domain:spec
Seat: domain:spec#3
File surface: packages/spec/src/data/hook.zod.ts (Hook.refusals), packages/spec/src/system/translation.zod.ts (a new hooks group region only), packages/cli/src/utils/i18n-extract.ts and i18n check (a hooks walker only), the runtime sandbox and refusal path (packages/runtime/src/sandbox/**, the hook refusal envelope), the hook-refusal lint branch, skills/objectstack-data hooks docs, tests, generated output, changesets — ruling 6431311681 option A only; ⛔ options B/C/D (stop on a fork; explain in the report)
Container & model: L, mode:thread, model: TIER_DEFAULT — --tier: no path-derived mandate (schema face; one CONTRACT_REVIEW_TIER review owed before the queue, run by the seat), effort: high
Clause-②: yes
Metadata-protocol: added
Responsibility: the spec lane owns the hook declaration face; the runtime half lands in the same version
Thread-read: 6431311681
Premise: holds — re-read on origin/main43731031 (no hooks group in translation.zod.ts; __errorInfo carries code/fields/status/userMessage only, quickjs-runner.ts ~363)
Serial constraints cleared: #25785 (PR #26159) landed 11:56Z; siblings #26112+#26142, #26128, #26463 edit other regions of translation.zod.ts / other walkers in i18n-extract.ts (region-disjoint, declared in each claim; merge main after each lands, generated files via os-regen-merge.sh); no other open PR touches translation.zod.ts or hook.zod.ts
{
"issue": 26170,
"status": "done",
"branch": "claude/project-thread-ivr552",
"pr": "https://github.com/objectstack-ai/objectstack/pull/26831",
"session": "session_01WGufzfyVQuRsL6SRGuKCem",
"premise_still_valid": true,
"summary": "Ruling A landed as one PR (1703+/99-, under the 3000-line line): Hook.refusals { CODE: { message, status? } } with a save-time refusal of literal undeclared codes, a hooks translation group (hooks.HOOK.refusals.CODE.message) walked by os i18n extract/check and judged by lint, and a runtime resolver in the sandboxed body runner that answers a declared code with the translated (else declared) message and status, and an undeclared or missing code with a 500 fault naming the hook. Body spelling reuses the existing throw shape plus code (no api.refuse); no params. HookContext.session.locale is produced in objectql buildSession. The new group rippled into the console translation preview and loader list, the i18n docs, the walk-parity fixture, a property count and an ADR-0122 pin; the Studio hook form does not offer refusals (the record editor would stamp a key the closed entry refuses), recorded in the form gate's ledger.",
"tests": "New: spec hook-refusals.test.ts 45, runtime hook-refusal-catalogue.test.ts 17 (real factory + QuickJS, read off mapDataError), cli i18n-hook-refusal-coverage.test.ts 7, lint +5, objectql session-locale 3. Ablation on disk, restored: resolveHookRefusal call replaced by err -> 9/17 runtime tests fail; host-relay marker not recorded -> passthrough test fails. After last merge at 78dc14528e: runtime 7465 passed, lint 7435, objectql 8846, spec 22063 + census fix re-run 3/3, cli i18n 13, metadata-protocol degenerate 18, touched ui 1445, spec typecheck exit 0. Before it (547ffcae02): cli 6415, metadata-protocol 21580, platform-objects 1359, spec test:repo 1295.",
"gates": "dispatch-gates --commands derived 130; all run under os-verify-lock, exit codes captured before pipes; 129 exit 0, pnpm check:spec-parsed-alias exit 1 -> fixed (HookRefusal isomorphism pin) -> exit 0. --ran reconciliation: 130 derived, 130 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. Also spec check:generated (14 current), check:i18n 0, check:i18n-walk-parity 0, check:skills-token-ratchet 0. PR check-runs: pending at report time.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "2 — mcp__github__issue_read (card + comments, read), mcp__claude-code-remote__subscribe_pr_activity (not a GitHub write); no MCP GitHub write tool",
"api_writes": "2 relay writes via fleet-write: pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST /issues/26831/assignees), and this os-dev-report comment (POST /issues/26170/comments); plus git push of the pinned branch",
"open_questions": [
{
"question": "Should a declared hook refusal carry params (holes in the message)?",
"options": [
"A keep plain text, as validation-rule messages (shipped)",
"B later widening: params on the thrown error and {{ }} holes in message and translation"
],
"recommendation": "A now; B only on demand, as an additive widening"
}
],
"out_of_scope_findings": [
"carrier: none · the Studio hook form cannot offer refusals until the record editor supports key-only records (it stamps keyField into each entry); recorded in metadata-form-zod-reconciliation's ledger, noted, not filed",
"Skills-docs follow-up owed: skills/objectstack-data/evals/hooks-security-seeds-datasources.json has no refusals case and sits at its token ceiling"
],
"deviations": "Files outside the order's surface, each named in the PR: objectql engine-execution-plumbing.ts (session.locale producer), runtime app-plugin.ts / app-artifact-handlers.ts (i18n reader wiring), ui translation preview + loader list (exhaustive group Record), i18n docs, walk-parity fixture, metadata-protocol count test, ADR-0122 pin file, form-reconciliation ledger. data-hooks.md paid for its new section by deleting duplicate error-handling examples (998 -> 972 lines; package md 3849 -> 3823). No reviewer requested: the order names none."
}
Tier H (touches skills/**): awaiting an authorized approval from os-zhuang or hotlong; needs-user-decision is on the PR. The seat lands it after approval.
approved by os-zhuang (review 5675480122, Tier H); merged through the queue at 2026-10-28T02:23Z, merge commit b3f73445f4
origin/main
c79eda5e contains the merge commit (git merge-base --is-ancestor)
State: Fixes #26170 closed this card as completed. pm:dispatched comes off in this stroke; domain:spec, the area and the grade stay. Open on other cards: #26834 (skill eval), #26835 (Studio hook form). One stale TSDoc word (translation.zod.ts ~:2082, "Fifteen groups" after #26819's books group) lands in a trailing docs PR from the same thread (contract review round 4, 6458660736).
This is the one-word follow-up from PR #26831 round 4: "Fifteen groups" becomes "Sixteen groups". The contract review passed (6459095198). The PR is ready, with auto-merge armed.
merged through the queue at 2026-10-28T06:23Z, merge commit d90657b2ca
origin/main
d90657b2 contains the merge commit (git merge-base --is-ancestor)
TranslationDataSchema's TSDoc now says "Sixteen groups" (the stale word flagged by PR #26831's round-4 review, 6458660736). Nothing is left on this card from seat 3. #26834 and #26835 stay open on their own cards.
Activity
objectstack-fleet commentedon Oct 26, 2026
Triage: first grade —
bug·priority:p1·domain:spec·area:i18n·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-26T14:55Z⛔ Not a claim, ⛔ not a dispatch.
hooks.<hook>.refusals.<code>.message; the shape is the spec seat's to settle). It is the one refusal door every other door has already closed (validation rules, flowend, flow writes). The caliber question is still open, and this card is named in triage's report as a protocol addition.body-runner.ts) resolves the key in the caller's locale. It shares the file with runtime: an app hook's intentional business refusal is logged as ERROR with a stack trace, twice per refused action #26153, so one holder is natural.skills/objectstack-datahooks) and the i18n docs name the new key, so authors use it.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Decision filed ·
domain:specseat 3 ·os-support-ai· sessionsession_01WGufzgbJhFoXYsLSe5WSPP· 2026-10-27T00:44Z · ⛔ not a claimPremise check (
origin/main6390fe52) before dispatch: the defect holds, but the fix's key shape and how a hook body raises a keyed refusal are an open public-contract choice. No rule or ruling settles it, so the card goes to the maintainer's decision box. The rest of the lane's queue keeps moving.背景
一句话问题: 应用自己写的业务拦截(hook 里"签署前必须有干净版本"这类话)是平台上唯一一种无法翻译的拒绝提示,中文用户看到的是英文原句,而且任何语言包都补不上。
前提读数(各带复查命令):
code/fields/status/userMessage,不带任何翻译键。复查:grep -n "__errorInfo = " packages/runtime/src/sandbox/quickjs-runner.ts(阳性对照:同文件grep -n "__errorRefusal"必中)。_hooks键。复查:grep -n "hooks" packages/spec/src/system/translation.zod.ts,只命中第 2249 行附近的一句注释(阳性对照:grep -n "refusals"同文件必中)。object: 'a'或['a','b']或'*')。复查:grep -n "z.union(\[z.string(), z.array(z.string())\])" packages/spec/src/data/hook.zod.ts。body-runner.ts)已经解除:runtime: an app hook's intentional business refusal is logged as ERROR with a stack trace, twice per refused action #26153 已关闭,PR fix(runtime): log a sandboxed business refusal once, at info, without a stack #26249 在 18:02Z 合入。contract_state_machinehook 有 3 句拦截文案,浏览器里实测在 zh-CN 下原样显示(见卡正文)。Governing text:
flows.<flow>.refusals.<node_id>.message(translation.zod.ts,i18n(flows): anendnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450)。objects.<object>._validations.<rule>.message。hook body refusal message translation key locale ruling(本仓 issue,语义检索):10 条命中,没有一条是关于 hook 拒绝提示的裁决。是否改协议: 是。下面三个选项都会给作者新增可写的东西(
Clause-②: yes,Metadata-protocol: added)。选项
Hook新增refusals: { <code>: { message, status? } }。body 只按 code 拒绝(抛{ code },或者新增api.refuse(code, params))。翻译键是hooks.<hook>.refusals.<code>.message。i18n extract/check 按声明逐条计数。抛出未声明的 code 时,保存时或运行时会响亮报错。{ code, message },运行时去查hooks.<hook>.refusals.<code>.message,查不到就退回原句。objects.<object>._hooks.<hook>.refusals.<code>。'*'通配的 hook 没地方挂。messageKey+ params,指向一张顶层messages.<id>表。业务含义直译: A 等于"拒绝话术先登记在案、再按编号使用",和校验规则、流程的做法一致。B 等于"话术写在代码里,翻译靠事后对号",对不上时没人知道。C 等于"同一句话按对象各存一份"。D 等于"另开一本通用词典"。
四轴
Hook.refusals)和一个翻译分组,是永久义务。B 少一个键,但换来一个检查不到的面。D 扩散最大。新增门禁:无,A 的计数复用既有 i18n coverage 检查,lint 只是既有引用校验的一个新分支。os-decision-facets
Prior rulings read: hook refusal translation key locale ruling → 10 hits; ADR none; thread: none
推荐
推荐 A。 只看①选 A;②③④ 是否翻转:否。B 不取,因为它把"数不到的拒绝"写进契约。C 不取,因为它与多对象 hook 冲突。D 不取,因为它开了第二本词典。置信缺口:body 侧的写法是抛
{ code }还是新增api.refuse(code, params),我倾向复用现有抛错形状加code,留给实施时实测。如果实测发现现有形状装不下 params,dev 会报 fork,不会硬做。裁后执行
裁定后,本席按选项派发一张 L 卡(卡线程,契约复审档复核)。spec 半与 runtime 半在同一版本落地。i18n 分组、extract/check 计数、lint 引用校验、
skills/objectstack-data的 hooks 文档同 PR。派发时如果 #25785(PR #26159)还没落地,本卡排在它之后(同为area:i18n,共用translation.zod.ts)。Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Brief-claimed-by: session_01KrKXQVvgYk2AJ4VkqMobyb · batch #405 · 2026-10-27T01:09Z
Director seat, summon #51 continuation (ledger objectstack#12708). Presented to the maintainer in batch #405 as item 2 of 3. Seat increment (re-verified on
origin/main6390fe52; the spec seat's decision comment 6430864109 is not restated):quickjs-runner.ts:363carries onlycode | fields | status | userMessageout of a hook throw;translation.zod.tshas nohooksgroup (the one hit at :2249 is a comment) whilerefusalsis declared for flows (:1376, i18n(flows): anendnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450);hook.zod.ts:295binds a hook to one object, a list, or'*'. Premise 4 holds: runtime: an app hook's intentional business refusal is logged as ERROR with a stack trace, twice per refused action #26153 closed, PR fix(runtime): log a sandboxed business refusal once, at info, without a stack #26249 merged. Serial line still live: rest/i18n: GET /i18n/translations/:locale serves the whole locale bundle (288–361 KB raw, 80–88 KB compressed) on every console open with no ETag or Cache-Control, and carries Studio's metadataForms (46.8 KB) into the boot read #25785 ispm:dispatchedwith PR fix(i18n)!: the console translation bundle leaves out Studio's metadataForms (75 KB of 221 KB on app-crm) #26159 open as a draft ontranslation.zod.ts— this card's PR waits behind it.endrefusal translation, i18n(flows): anendnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450) was +1110 / −25 over 26 files; A is the same shape (declaration + key + runtime render + coverage count) plus a hook-siderefuse(code)door and the skills page, so larger: L, two PRs in one version.needs-user-decisionalone (triage'spm:queuewas removed by the spec seat's decision stroke); the ruling's stroke returns it topm:queue.Hook.refusals: { <code>: { message, status? } }, body refuses by code, keyhooks.<hook>.refusals.<code>.message). Not B (an uncountable refusal written into the contract), not C (N copies per bound object, nowhere for'*'), not D (a second dictionary). The body-side spelling (throw{ code }versusapi.refuse(code, params)) is the dev's to measure, as the card says.Prior rulings read: hook refusal translation key → 10 hits, none a ruling; i18n(flows): an
endnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450 (flow end refusals), fix(service-automation): a flow write node's refusal is worded in the run's locale, as the record doors word it #25929, i18n(flows): a validation rule tripped by a flow's create_record answers in English — the record doors translate the same rule's message per locale #25809 (flow write refusals); ADR none; thread: 6430864109.objectstack-fleet commentedon Oct 27, 2026
Ruling: batch #405 item 2 · letter A · maintainer 「同意」 2026-10-27T01:17Z
Recorded by the director seat (summon #51 continuation,
session_01KrKXQVvgYk2AJ4VkqMobyb, ledger objectstack#12708). The maintainer answered batch #405 with 「同意」 — every seat letter. Brief 6431195984; the spec seat's decision comment 6430864109. Letter A: a hook declares its refusal catalogue —refusals: { <code>: { message, status? } }onHook— and its body refuses by code; the translation key ishooks.<hook>.refusals.<code>.message; the runtime renders it in the caller's locale with the declared message as the fallback; the i18n extract/check gates count the declared keys; an undeclared code is refused loudly. Not B (an uncountable refusal written into the contract: the coverage gate reports "0 missing" while a zh-CN user reads English), not C (one copy per bound object, nowhere for'*'), not D (a second dictionary with free-text keys).Workload of the chosen letter (card column): L — spec half (
hook.zod.ts,translation.zod.tsgroup, lint reference check) and runtime half (body-runner.ts/quickjs-runner.tsrefusal envelope carries the code and params; locale render) in one version, two PRs; i18n extract/check counting,skills/objectstack-datahooks page and the i18n docs in the same PRs (Skills-docs:line owed under 6431004737). Precedent PR #22525 (flowendrefusals, #22450): +1110 / −25, 26 files. The body-side spelling (throw{ code, params }orapi.refuse(code, params)) is the dev's to measure; a fork is reported, not forced. Serial: behind #25785 / PR #26159 ontranslation.zod.ts. Grade p1 stands under 6431014712 (a protocol addition is p1).Clause-②: yes,Metadata-protocol: added.Execution:
needs-user-decision→pm:queuein this act (domain:spec, p1,area:i18n).Ruling-ref:names this comment.objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 2 · 2026-10-27T13:15Z
Session:
session_01WGufzgbJhFoXYsLSe5WSPPAccount:
os-support-aiBranch:
claude/project-thread-ivr552(card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead ofclaude/issue-26170-<slug>)Worktree:
objectstack-issue-26170Domain:
domain:specSeat:
domain:spec#3File surface:
packages/spec/src/data/hook.zod.ts (Hook.refusals), packages/spec/src/system/translation.zod.ts (a new hooks group region only), packages/cli/src/utils/i18n-extract.ts and i18n check (a hooks walker only), the runtime sandbox and refusal path (packages/runtime/src/sandbox/**, the hook refusal envelope), the hook-refusal lint branch, skills/objectstack-data hooks docs, tests, generated output, changesets— ruling 6431311681 option A only; ⛔ options B/C/D (stop on a fork; explain in the report)Container & model:
L,mode:thread,model: TIER_DEFAULT — --tier: no path-derived mandate (schema face; one CONTRACT_REVIEW_TIER review owed before the queue, run by the seat),effort: highClause-②: yes
Metadata-protocol: added
Responsibility: the spec lane owns the hook declaration face; the runtime half lands in the same version
Thread-read: 6431311681
Premise: holds — re-read on
origin/main43731031(no hooks group in translation.zod.ts;__errorInfocarries code/fields/status/userMessage only, quickjs-runner.ts ~363)Serial constraints cleared:
#25785 (PR #26159) landed 11:56Z; siblings #26112+#26142, #26128, #26463 edit other regions of translation.zod.ts / other walkers in i18n-extract.ts (region-disjoint, declared in each claim; merge main after each lands, generated files via os-regen-merge.sh); no other open PR touches translation.zod.ts or hook.zod.tsobjectstack-fleet commentedon Oct 27, 2026
os-dev-report
{ "issue": 26170, "status": "done", "branch": "claude/project-thread-ivr552", "pr": "https://github.com/objectstack-ai/objectstack/pull/26831", "session": "session_01WGufzfyVQuRsL6SRGuKCem", "premise_still_valid": true, "summary": "Ruling A landed as one PR (1703+/99-, under the 3000-line line): Hook.refusals { CODE: { message, status? } } with a save-time refusal of literal undeclared codes, a hooks translation group (hooks.HOOK.refusals.CODE.message) walked by os i18n extract/check and judged by lint, and a runtime resolver in the sandboxed body runner that answers a declared code with the translated (else declared) message and status, and an undeclared or missing code with a 500 fault naming the hook. Body spelling reuses the existing throw shape plus code (no api.refuse); no params. HookContext.session.locale is produced in objectql buildSession. The new group rippled into the console translation preview and loader list, the i18n docs, the walk-parity fixture, a property count and an ADR-0122 pin; the Studio hook form does not offer refusals (the record editor would stamp a key the closed entry refuses), recorded in the form gate's ledger.", "tests": "New: spec hook-refusals.test.ts 45, runtime hook-refusal-catalogue.test.ts 17 (real factory + QuickJS, read off mapDataError), cli i18n-hook-refusal-coverage.test.ts 7, lint +5, objectql session-locale 3. Ablation on disk, restored: resolveHookRefusal call replaced by err -> 9/17 runtime tests fail; host-relay marker not recorded -> passthrough test fails. After last merge at 78dc14528e: runtime 7465 passed, lint 7435, objectql 8846, spec 22063 + census fix re-run 3/3, cli i18n 13, metadata-protocol degenerate 18, touched ui 1445, spec typecheck exit 0. Before it (547ffcae02): cli 6415, metadata-protocol 21580, platform-objects 1359, spec test:repo 1295.", "gates": "dispatch-gates --commands derived 130; all run under os-verify-lock, exit codes captured before pipes; 129 exit 0, pnpm check:spec-parsed-alias exit 1 -> fixed (HookRefusal isomorphism pin) -> exit 0. --ran reconciliation: 130 derived, 130 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. Also spec check:generated (14 current), check:i18n 0, check:i18n-walk-parity 0, check:skills-token-ratchet 0. PR check-runs: pending at report time.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "2 — mcp__github__issue_read (card + comments, read), mcp__claude-code-remote__subscribe_pr_activity (not a GitHub write); no MCP GitHub write tool", "api_writes": "2 relay writes via fleet-write: pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST /issues/26831/assignees), and this os-dev-report comment (POST /issues/26170/comments); plus git push of the pinned branch", "open_questions": [ { "question": "Should a declared hook refusal carry params (holes in the message)?", "options": [ "A keep plain text, as validation-rule messages (shipped)", "B later widening: params on the thrown error and {{ }} holes in message and translation" ], "recommendation": "A now; B only on demand, as an additive widening" } ], "out_of_scope_findings": [ "carrier: none · the Studio hook form cannot offer refusals until the record editor supports key-only records (it stamps keyField into each entry); recorded in metadata-form-zod-reconciliation's ledger, noted, not filed", "Skills-docs follow-up owed: skills/objectstack-data/evals/hooks-security-seeds-datasources.json has no refusals case and sits at its token ceiling" ], "deviations": "Files outside the order's surface, each named in the PR: objectql engine-execution-plumbing.ts (session.locale producer), runtime app-plugin.ts / app-artifact-handlers.ts (i18n reader wiring), ui translation preview + loader list (exhaustive group Record), i18n docs, walk-parity fixture, metadata-protocol count test, ADR-0122 pin file, form-reconciliation ledger. data-hooks.md paid for its new section by deleting duplicate error-handling examples (998 -> 972 lines; package md 3849 -> 3823). No reviewer requested: the order names none." }Generated by Claude Code
fix(spec): refuse `refusals` on a hook with no js body, and a `{{ }}`…
objectstack-fleet commentedon Oct 27, 2026
ACCEPT → PR #26831 comment 6453820397 ·
domain:specseat 3 · 2026-10-27T21:17ZTier H (touches
skills/**): awaiting an authorized approval fromos-zhuangorhotlong;needs-user-decisionis on the PR. The seat lands it after approval.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Landed ·
domain:specseat 3 ·os-support-ai· 2026-10-28T02:23Zos-zhuang(review 5675480122, Tier H); merged through the queue at 2026-10-28T02:23Z, merge commitb3f73445f4origin/mainc79eda5econtains the merge commit (git merge-base --is-ancestor)State:
Fixes #26170closed this card as completed.pm:dispatchedcomes off in this stroke;domain:spec, the area and the grade stay. Open on other cards: #26834 (skill eval), #26835 (Studio hook form). One stale TSDoc word (translation.zod.ts~:2082, "Fifteen groups" after #26819'sbooksgroup) lands in a trailing docs PR from the same thread (contract review round 4, 6458660736).Generated by Claude Code
chore(changeset): a patch changeset for the TranslationDataSchema com…
objectstack-fleet commentedon Oct 28, 2026
ACCEPT → PR #27033 comment 6461536766 ·
domain:specseat 3 · 2026-10-28T05:36ZThis is the one-word follow-up from PR #26831 round 4: "Fifteen groups" becomes "Sixteen groups". The contract review passed (6459095198). The PR is ready, with auto-merge armed.
Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Landed (follow-up) ·
domain:specseat 3 ·os-support-ai· 2026-10-28T06:25Zd90657b2caorigin/maind90657b2contains the merge commit (git merge-base --is-ancestor)TranslationDataSchema's TSDoc now says "Sixteen groups" (the stale word flagged by PR #26831's round-4 review, 6458660736). Nothing is left on this card from seat 3. #26834 and #26835 stay open on their own cards.Generated by Claude Code