Repository navigation
translation.zod's _actions convention docblock omits description and params.*, which the same schema declares #14708
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentationand removed
on Sep 2, 2026 Triage — graded
p3,findingcleared,documentation,pm:queue, routeddomain:spec. Applied and read back first.Confirmed, and your "check the sibling" is answered: yes, identically
At
origin/main75adf11, both convention lists are built from the sameactionTranslationSchema(...)factory and both carry the same four lines and the same two omissions:_actions (:334) globalActions (:666) …_actions.<action_name>.label globalActions.<action_name>.label …_actions.<action_name>.confirmText globalActions.<action_name>.confirmText …_actions.<action_name>.successMessage globalActions.<action_name>.successMessage …_actions.<action_name>.resultDialog.* globalActions.<action_name>.resultDialog.*The factory declares, beyond those:
description(:19, "the explanatory line under the title in the action's param dialog") andparams.<param>.{label, helpText, placeholder, options}(:22onward).So the fix is two docblocks, not one plus a check — the sibling has the identical gap because it consumes the identical factory.
:74says so outright: "Shared by object_actionsandglobalActions."⭐ The mechanism is worth naming, because it is this file's third instance today
Both lists are hand-maintained prose copies of what one factory declares. Nothing binds them, so the factory can grow a key and neither list notices.
That is the same shape as:
- translateAction ignores
_actions.ACTION.descriptionand_actions.ACTION.params.*, which the schema declares and the lint validates #14254 itself — the resolver silently ignored declared keys (fixed in PR fix(spec): translateAction overlays an action's description and its param dialog copy #14707); - [finding] Nothing gates that
TranslationDataSchema's declared key groups are actually walked by the i18n extractor — the resolver-first / extractor-second drift has now recurred four times #14653 (p2, queued) —TranslationDataSchema's declared key groups are not walked by the i18n extractor, four recurrences; - and, one layer out, the five-card
SKILL_MAPcluster where hand-written lists drift from the bodies they describe.
Your own sentence is the cleanest statement of it: "there the resolver silently ignored declared keys, here the documentation silently omits them." ⭐ Same file, same week, three layers — resolver, extractor, docblock — each with its own hand-copy of one declaration.
⛔ Do not fold that observation into this card as work. It is
p3docs, and the class already has an owner in #14653. But whoever eventually builds #14653's parity gate should know the docblocks are a fourth consumer of the same declaration, and may be gateable by the same walk.Scope
⛔ Docs-only, one file, two docblocks. Add
descriptionandparams.<param_name>.{label, helpText, placeholder, options}to both lists.⛔ Do not touch the schema, the factory, or any resolver. #14254's ruling scoped that card to the resolver and forbade edits to
translation.zod.ts; this card is the other side of that fence and does not cross back.⚠️ Keep the placeholder spelling consistent with each list's existing convention (<action_name>in the file, though the card body uses bareACTIONbecause the issue sanitizer eats angle-bracket fragments). ⛔ Take the spelling from the file, not from this card's text.On the filing
Recording it rather than fixing it in PR #14707 was right — that PR refreshed the equivalent list in
i18n-resolver.ts's own header, which was inside its fence, and stopping at the fence is what keeps a ruling's diff reviewable.
Generated by Claude Code
- translateAction ignores
Claim: PM loop round 3
Session:session_01T6HeZvT9wdSJD1ZxJb5Eno
Branch:claude/issue-14708-actions-convention-docblock-keys
Worktree:objectstack-issue-14708
Domain:domain:spec
File surface:packages/spec/src/system/translation.zod.ts,.changeset/(stop on breach; explain in the report)
Container & model:S 级机械卡,mode:subagent,model: claude-opus-5—node scripts/pm/dispatch-gates.mjs --tier packages/spec/src/system/translation.zod.tsprints no path-derived mandate (the surface hits none of the 3 declared globs) and markspackages/spec/src/**as a clause-② SUSPECT surface, "a hint, not a verdict"
Clause-②: no
Serial constraints cleared:none—system/translation.zod.tsis named by no in-flight claim on this lane's serial queue (seat post #6017 §3, re-read at claim time).⚠️ It is notpackages/spec/liveness/translation.json(released this shift by PR #16138) and notsystem/i18n-resolver.ts(PR #14707, long merged); do not confuse the three.Why clause ② is
no, stated so it can be checked rather than trustedThe suspect glob is a hint this seat did not take as the answer; the judgment is from content. The deliverable is prose inside a docblock on a convention list. The schema already accepts the keys in question —
actionTranslationSchemadeclares them today — so nothing about what the contract accepts or rejects moves, no shape changes, no.describe()changes, no export changes. The docblock is a strict subset of the schema and the fix makes it match.⚠️ That verdict has one measurable precondition, and it is yours to measure, not to assume: if the text you end up touching is a.describe()string rather than a docblock, or if the docblock projects into a generated artifact, clause ② is back in play and you must stop and say so in the report rather than continue. Method, with the control that makes it a reading:- The
#13852dev established on this same package that per-schema docblocks do not reachcontent/docs/references/api/*.mdx— the generator renders.describe()strings and the module-level file header only. It proved it with a lit control: a sibling schema's docblock prose occurs 0 times in the generated page while a.describe()string occurs. ⭐ Reproduce that control fortranslation.zod.ts's own generated page — a result measured onprotocol.zod.tsis not a result about this file. packages/specpublishessrc/**/*.zod.tsdirectly (itspackage.jsonfilesarray), so the text does ship to consumers either way ⇒ a changeset is owed. ⛔ Apatch/documentation-only changeset is still a changeset.
Target re-verified on
origin/maincbca47d09at claim time — still stale, with its controlpackages/spec/src/system/translation.zod.ts :337 objects.<object>._actions.<action_name>.resultDialog.* ← the convention list, still 4 lines :110 const actionTranslationSchema = (surface: string) => strictObject({ :140 helpText: z.string()…describe('Translated action parameter help/hint text') :141 placeholder: z.string()…describe('Translated action parameter placeholder')⇒ The list at
:337omitsdescriptionandparams.<param>.{label, helpText, placeholder, options}, which the factory at:110declares — the defect as filed, confirmed on today's tree. The:140-141rows are the control: the keys genuinely exist, so the omission is a real gap and not a stale card.Card-specific increments (⛔ not a restatement of
os-dev.md— it wins on conflict by its own rule)- The sibling the card told you to check has the same shape, and this seat has already found it for you:
:669—globalActions.<action_name>.resultDialog.*. The card said "check the siblingglobalActionsdocblock for the same omission"; that is where it is. Check it, and if it is short too, fix both in this PR — ⛔ but do not wander further than the two_actions-convention lists. ⚠️ The card's line numbers are already wrong ("around line 334", "around line 110" ⇒ actually:337and:110), and so will mine be by the time you read them. ⛔ Re-locate by pattern. This lane has had three independent instances this shift of a hand-kept integer rotting in prose — write the boundary, never the count.⚠️ The card is a 2026-09-02 observation filed as a by-product of translateAction ignores_actions.ACTION.descriptionand_actions.ACTION.params.*, which the schema declares and the lint validates #14254. Its scope note ("that card's ruling scoped it to the resolver and forbade edits totranslation.zod.ts") describes a past constraint that this card lifts. ⛔ Do not read it as still binding.- This repo is a shallow clone (~51 commits). Windowed
git log/git rev-listanswer WRONG with exit 0 here — this seat was caught by it this shift. ⛔ Usenode scripts/pm/git-history.mjsfor every commit-window question.
domain:specPM dispatch seat · seat post #6017 · R3
Generated by Claude Code
- The
os-dev-report
{ "issue": 14708, "status": "done", "branch": "claude/issue-14708-actions-convention-docblock-keys", "pr": "https://github.com/objectstack-ai/objectstack/pull/16246", "premise_still_valid": true, "summary": "Both action convention docblocks in packages/spec/src/system/translation.zod.ts now name every key actionTranslationSchema(...) declares. Ten added comment lines: `description` after `label`, and the four params.PARAM leaves (label, helpText, placeholder, options.VALUE) before resultDialog.*, in the factory's declaration order and in the spelling already landed in i18n-resolver.ts's own file header by PR #14707. Both sites re-located by pattern, not by the rotted line numbers in the card or the claim; the file's own angle-bracket placeholder spelling was kept, not the card's bare-word spelling. No schema, factory or resolver touched; a documentation-only patch changeset is included because packages/spec publishes src/**/*.zod.ts. Clause-2 stays `no` and its precondition was measured, not assumed - see `tests`.", "tests": "ALL GREEN at branch tip a6879f8b6; every exit code captured before any pipe, verdicts quoted from the gates' own lines. BUILD: `pnpm --filter '@objectstack/spec^...' build` matched zero projects - verified honest, spec declares zero workspace deps, not the zero-scripts trap; `pnpm --filter @objectstack/spec build` VERDICT command-exit 0, 34/34 declaration files present. GATES: `pnpm --filter @objectstack/spec check:generated` -> 'All 15 generated artifacts are up to date' (zero regeneration owed, check:docs and check:api-surface among them). `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 68 families from the real change set; 67 run, all exit 0, reconciled with --ran (it names exactly the 1 skipped). `pnpm check:nul-bytes` exit 0, plus a manual control-character scan of both touched files (grep -naP over the C0 set, exit 1 = clean). `pnpm check:i18n-walk-parity` - the family most directly about this file - green: '11 declared group(s), 8 walked, 3 exempted'. TYPECHECK: `pnpm --filter @objectstack/spec typecheck` exit 0 (tsc --noEmit + check:scripts-typecheck + check:test-typecheck). TESTS: targeted vitest over translation.test.ts, translation-typegen.test.ts, i18n-resolver.test.ts, action.test.ts, action-description.test.ts -> 5 files / 538 tests passed. LINT: full-population `eslint . --no-inline-config --format json` -> 6196 files, 0 errors, 0 warnings, 64s; no narrowing claimed because the whole population was linted, the count read from eslint's own JSON. PROJECTION CONTROL (the clause-2 precondition, reproduced on THIS file's own generated page content/docs/references/system/translation.mdx, not on protocol.zod.ts): lit control - the .describe() strings 'Action translations keyed by action name' -> 3 occurrences and 'Global action translations keyed by action name' -> 2; docblock prose -> 0 for 'Convention (auto-resolved by', 0 for the same opening sentence once its docblock-only '(snake_case)' suffix is included, 0 for the globalActions sentence, 0 for the confirmText and successMessage address rows. The 3-vs-0 pair on nearly identical words is the sharp discriminator: .describe() reaches the page, the docblock does not. Mechanically corroborated by check:generated finding all 15 artifacts current. WHERE THE TEXT DOES LAND, reported because it was measured rather than assumed: the published src/**/*.zod.ts (spec's package.json files array), plus JSDoc preserved in build output - 9 of 22 emitted .js chunks and 1 of 64 emitted declaration files carry the new lines. All three are comment positions and check:api-surface is green, so no export signature moved; this is why a changeset is owed and not why clause-2 reopens. NOT MEASURED, declared rather than counted as failures: `pnpm --filter @objectstack/lint run check:doc-formula-expressions` first returned exit 3 PREREQUISITE NOT MET (unbuilt @objectstack/formula and @objectstack/lint) and `pnpm check:i18n-walk-parity` exit 1 same cause (unbuilt @objectstack/cli) - built all three via the shared lock and re-ran both green; the numbers above are the re-runs. NO ABLATION: the deliverable is prose, there is no guard to delete and no behaviour a mutation could flip - the projection control above is this card's equivalent evidence. DECLARED NARROWINGS, both handed to CI: (1) `pnpm check:dual-build-cjs-loads` is the single derived family not run - its prerequisite is a whole-repo pnpm build, it named 86 unbuilt packages and measured nothing; the diff changes no exports map, no manifest, no emitted module format. (2) turbo ls --affected against merge base 0c3eaf578 lists ~45 packages, all downstream of @objectstack/spec and reached only through its build output; ran the five translation/action test files rather than all 482 spec test files or any downstream package. RESOURCE NOTE: every build and test went through scripts/pm/os-verify-lock.sh under slot names issue-14708-spec-build / issue-14708-spec-verify; longest wait 390s (6m30s) behind a sibling's spec+rest test run, spent in-turn, never by ending a round.", "mcp_calls": "4 - issue_read(get_comments) to verify the claim, create_pull_request, pull_request_read(get) to read the body back, add_issue_comment for this report. Channel declaration: the repo-scoped REST probe returned HTTP 403 ('GitHub access is not enabled for this session') in this container, so REST was unavailable for reads and writes; the card body and the triage comment came from the zero-quota public-repo page payload channel and only the claim verification, the PR write and its read-back went through MCP.", "open_questions": [], "out_of_scope_findings": [] }
Generated by Claude Code
ACCEPT — PR #16246. Clause-②
nois now measured on this file, not declared; the diff was re-verified against the factory rather than against the reportdomain:specPM dispatch seat, R3, 2026-09-06T07:4xZ. Verdict on the delivery forClaim:5557579650.What I checked myself, ⛔ not taken from the dev's report
I read the diff and then read
actionTranslationSchemaon the PR branch, because a wrong address written into a convention list is worse than the omission it replaces — the omission is silent, a wrong address actively misleads.claim verified against FETCH_HEAD:packages/spec/src/system/translation.zod.tsverdict insertion order matches the factory factory declares label→description→confirmText→successMessage→params→resultDialog; the diff putsdescriptiondirectly afterlabeland the fourparams.*rows aftersuccessMessage, beforeresultDialog.*✅ exact params.<param_name>keyingparams: z.record(z.string(), strictObject(…))— keyed by parameter name✅ the four inner leaves and their order label,helpText,placeholder,options— that order✅ exact options.<value>is the right addressoptions: z.record(z.string(), z.string())describing "Param select option value to translated label"✅ the map is keyed by value, so options.<value>is correctplaceholder spelling <action_name>/<param_name>/<value>— the file's own convention, ⛔ not the card's bare-wordACTION/PARAM✅ took it from the file, as instructed nothing structural moved both hunks are comment lines inside existing docblocks; no schema, factory or resolver line changed ✅ ⭐ And the changeset's prose is accurate to the source rather than invented: the factory's own comment on
descriptionrecords thatobjects.{object}._actions.{action}.descriptionalready resolves in objectui with aglobalActions.{action}.descriptionfallback, which is exactly what the changeset says.⭐ The clause-② precondition was discharged the way it was asked for
I set one condition on the
no: reproduce the projection control on this file's own generated page, because a result measured onprotocol.zod.tsis not a result abouttranslation.zod.ts. Done, oncontent/docs/references/system/translation.mdx:- lit control —
.describe()strings reach the page: "Action translations keyed by action name" 3 occurrences, "Global action translations keyed by action name" 2. - the reading — docblock prose reaches it 0 times, across four separately-probed strings.
⇒ The 3-vs-0 on nearly identical words is the discriminator, and it is what makes this a reading rather than an unlit zero. Clause ② stays
no, now on evidence. ⛔ Noneeds:contract-reviewis owed, and none is hung — consistent with 「开着的载体恒 = 真实待审」.⭐ The half that could have reopened clause ②, measured and reported unprompted
The dev did not stop at the generated page. It measured where the text does land: JSDoc survives into the build output — 9 of 22 emitted
.jschunks and 1 of 64 emitted declaration files carry the new lines — on top of the publishedsrc/**/*.zod.ts. All three are comment positions, andcheck:api-surfaceis green, so no export signature moved.That is the correct disposition of an inconvenient finding: it is why a changeset is owed, and it is not why clause ② would reopen. Finding it, and then correctly declining to let it change the verdict, is the harder half.
Other verification worth recording
pnpm --filter '@objectstack/spec^...' buildmatched zero projects, and the dev checked that this is honest (spec declares zero workspace deps) rather than the zero-scripts trap — ⭐ exactly the "a zero without a control is not a reading" discipline, applied without being told.- 68 gate families derived from the real change set, 67 run all exit 0, reconciled with
--rannaming the one skipped (check:dual-build-cjs-loads, prerequisite is a whole-repo build; 86 packages unbuilt, measured nothing ⇒ declared to CI, ⛔ not counted as a pass). check:i18n-walk-parity— the family most directly about this file — green: "11 declared group(s), 8 walked, 3 exempted".- Full-population lint: 6196 files, 0 errors, 0 warnings; ⛔ no narrowing claimed because none was taken.
- Honest
NO ABLATIONdeclaration: the deliverable is prose, there is no guard to delete and no behaviour a mutation could flip, so the projection control is this card's equivalent evidence. ⭐ Correct — ⛔ manufacturing an ablation here would have been theatre. - The dev also corrected its own
mcp_callscount (4 stated in the posted comment, 5 actual, because the comment predated its own read-back). Small, and exactly the right instinct.
⚠️ One channel note for the record: the dev reports the repo-scoped REST probe returned 403 in its container, so the card body and triage comment reached it through the public-repo page payload rather than REST. I read the same card and triage independently through MCP and the content matches, so nothing in the delivery rests on an unverified channel.Landing posture — ⛔ NOT enqueued, and the reason is not this PR
CI started 07:33:47Z. Already green:
No other open PR may claim the same single-writer path,Check Changeset,Spec property liveness,Governed Surface Queue Guard,Type Check · source gates,Check PR Size,Check Documentation Links,Flag docs affected by code changes.Test Core (1/6)is in progress.⚠️ This PR is expected to join the shard-timeout hold (#16173 / amplifier #16157) — it will be the seventh consecutive measurement on this lane, and like the other six it touches nopackages/clipath. This seat enqueues only on the gate job's ownsuccess, ⛔ never on the aggregate rollup, so:- if
Test Core (1/6)returns genuinelysuccess⇒ flip ready, arm auto-merge, track to MERGED, stroke this card; - if it returns
cancelled⇒ held with docs(spec): record DeleteDataRequestSchema's consumer and why the DELETE data door carries no requestSchema #16191 / docs(spec):publicSharing.enabledis a standing policy held at redemption;resolveTokennames the switched-off block among itsnullcauses #16196 / docs(spec): lead the deletion-gate anchor description with what it must NOT be used for #16199, ⛔ no re-run, ⛔ no timeout change, ⛔ no edit toscripts/test-shard-timings.json.
⚠️ That hold is open-ended, not "waiting for a fix": PR #16220 established that the timings refresh cannot be delivered at all (it reds both balancing pins, and no six-way split helps because@objectstack/clialone is 68% of the wall), so it waits on a maintainer decision. Cost recorded at5557677433. ⛔ Nothing about that is this card's fault or this card's to fix.domain:specPM dispatch seat · seat post #6017 · R3 · card stayspm:dispatcheduntil the PR merges
Generated by Claude Code
- lit control —
Hold confirmed — PR #16246 is fully green except the shard, which was killed at 30m11s. ⛔ Not enqueued; the card stays
pm:dispatchedFollowing the ACCEPT at
5557781279. The verdict I said this card was waiting on has arrived, and it went the way the previous six did.Test Core (1/6)07:33:47Z → 08:03:58Z = 30m11s, cancelledTest Core(required aggregate)08:04:00Z → 08:04:15Z, ⚠️ success— 2 seconds after the killevery other check (32) ✅ green — all four Type Check legs, Lint & Repo Gates,Check Changeset,Spec property liveness,Governed Surface Queue Guard, the single-writer gate, both Dogfood aggregates,Temporal Conformancesibling shards 2/6–6/6 ✅ 11m24s – 15m03s ⇒ This PR is one shard away from landable, and that shard is measuring
@objectstack/cli— a package a ten-line docblock change inpackages/specdoes not touch. ⛔ I enqueue on the gate job's own conclusion, never the aggregate, socancelledholds it.⚠️ Worth stating plainly because it is the sharpest version of the defect this lane has hit: a documentation-only PR with 24 additions, 0 deletions and no executable change cannot land. Held with #16191 / #16196 / #16199 — the pile is now four.⛔ No re-run (a 30m11s kill is not a flake to confirm, and the one sanctioned re-run is spent), ⛔ no timeout change, ⛔ no edit to
scripts/test-shard-timings.json. The full within-run measurement is posted to #16173 (5557950…), where it is now the load-bearing evidence: five siblings at 11–15 minutes against one at 30m11s, same commit, same run, same runner pool — every confounder held constant.⚠️ The hold is open-ended: PR #16220 established the timings refresh is undeliverable (it reds both balancing pins, and no six-way split helps because@objectstack/clialone is 68% of the wall), so it waits on a maintainer decision about shard granularity, ⛔ not on the devx lane finishing.Nothing is owed by this card or its PR.
pm:dispatchedand the assignee stay until it merges.domain:specPM dispatch seat · seat post #6017 · R3
Generated by Claude Code
Observation filed from work on #14254 (resolver half, PR #14707). No behaviour is broken by this; it is an authoring-discoverability gap in a docblock.
ObjectTranslationDataSchema._actionsinpackages/spec/src/system/translation.zod.ts(around line 334) documents the convention as:Placeholders written
OBJECT/ACTION/PARAMrather than in angle brackets, since the body sanitizer eatsa-shaped fragments.The same file's
actionTranslationSchemafactory (around line 110) declares two more keys the list does not mention:description, andparams.PARAM.{label, helpText, placeholder, options}. So the authoring-side doc, which is what an author reads to learn which keys exist, is a strict subset of what the schema accepts.That is the same shape as the defect in #14254, one layer up: there the resolver silently ignored declared keys, here the documentation silently omits them. The resolver half is fixed in PR #14707, which also refreshed the equivalent list in
i18n-resolver.ts's own file header; this docblock was deliberately left alone because that card's ruling scoped it to the resolver and forbade edits totranslation.zod.ts.Suggested fix: add the two missing lines to the
_actionsconvention list, and check the siblingglobalActionsdocblock for the same omission. Docs-only, one file.Generated by Claude Code
Generated by Claude Code