Repository navigation
fix(spec): author-facing describes and refusals drop service-interface names, ruling dates and foreign example ids - #22125
Conversation
…rface names, ruling dates or foreign example ids Studio renders spec describes as form help (/meta/types schema + form) and the notify node's Template help from the service-automation descriptor; refusals are read at the save and install doors. The rationale those strings carried (II18nService/IEmailService names, "maintainer ruling <date>", "ruled <date>") moves into the code comments beside them; MANIFEST_ID_EXAMPLES becomes com.acme.crm / org.example.help-desk, and the package-id refusal leads with a product-words headline and names the key as a locator after it. WIP: generated artifacts not yet regenerated. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
…itBehavior describe for a governed change The top-level `submitBehavior` describe is projected into skills/objectstack-ui/references/react-blocks.md by gen:react-blocks, a governed surface, so its ruling date stays in this wording sweep and the deferral is recorded beside it. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
…the email-template Identity help `pnpm i18n:extract` output; the zh-CN / ja-JP / es-ES values were re-translated by hand in the first commit of this branch. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 3 package(s): 33 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 7bd05e7d0dd0bccd68fc6e9c13c1e1a6c7637d9d && git checkout 7bd05e7d0dd0bccd68fc6e9c13c1e1a6c7637d9d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 54ace18c669a776c7e849708039c7876ac534cee 0262046319a13c871d5427ac8cc4181ce54a3c5e && git checkout -B drift-repro 54ace18c669a776c7e849708039c7876ac534cee && git merge --no-ff 0262046319a13c871d5427ac8cc4181ce54a3c5e
node scripts/docs-audit/affected-docs.mjs --json 54ace18c669a776c7e849708039c7876ac534cee
|
…thor-strings-internal-refs
…rence left behind "(one dialog, not two —)" read as a truncated sentence in Studio's action inspector; the ruling it once cited stays in the docblock above the key. Pinned on the authoring-shape JSON Schema Studio renders: no dash left dangling before a close paren, no ruling date, no service interface, no tracker id. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
gen:schema && gen:docs after merging origin/main (54ace18): the only delta is the action description row. Main's regenerated docs reproduce byte-for-byte from the merged sources. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Read-only review of PR #22125 against Check-runs on the head. First read at 2026-10-07T23:31:32Z: 46 runs, 45 completed (38 success, 7 skipped), 1 ① Derived judgmentsAccept-set: unchanged, and the PR says so correctly. Every edit is to a
Refusal ② Semver level
Clause-②: no — correct. No parse accepts or refuses anything it did not before, so neither ③ Boundary flagsDev flags and open questions, each with its answer:
Escalated to the seat (none blocks this verdict):
Implemented-by: VERDICT: PASS Generated by Claude Code |
…e; QA clause requotes the package-id refusal (objectstack-ai#22309) Part of objectstack-ai#22093 Clause-②: no This PR delivers remainders (b) and (c) of objectstack-ai#22093, as the seat's partial-landing record (`6049519522`) names them. Remainder (a), the governed `submitBehavior` describe, is not in this PR and stays on the card. Wording only: no field, option, key, type or export changes. It ships a patch changeset for `@objectstack/platform-objects`. The `docs/qa` change publishes nothing. ## (b) The `sys_email` field help no longer names a service interface Studio shows a field's `description` as its help in the object forms: the `help` leaf in each locale's `objects` bundle. Two `sys_email` fields named the `IEmailService.send` service interface there. Each one now says what the field holds and when it is set, in product words, and keeps every fact it stated before. The implementation reference moves into the code comment beside each field (`sys-email.object.ts`), in the same form PR objectstack-ai#22125 used for the other 22 rows. | Field | Locale | Before | After | | :--- | :--- | :--- | :--- | | `headers_json` | en (source) | "Custom headers supplied to IEmailService.send, as a JSON object of name → value." | "Custom headers supplied with the message when it was sent, as a JSON object of name → value." | | `headers_json` | zh-CN | "传给 IEmailService.send 的自定义邮件头,…" | "发送邮件时随附的自定义邮件头,…" | | `headers_json` | ja-JP | "IEmailService.send に渡されたカスタムヘッダーを、…" | "メッセージの送信時に指定されたカスタムヘッダーを、…" | | `headers_json` | es-ES | "Cabeceras personalizadas facilitadas a IEmailService.send, como un objeto JSON …" | "Cabeceras personalizadas facilitadas con el mensaje al enviarlo, como un objeto JSON …" | | `status` | en (source) | "Lifecycle state — queued by IEmailService.send before transport call" | "Lifecycle state — queued when the message is submitted for sending, before the first delivery attempt" | | `status` | zh-CN | "生命周期状态——在调用传输层之前由 IEmailService.send 排队" | "生命周期状态——邮件提交发送时即进入排队,早于首次投递尝试" | | `status` | ja-JP | "ライフサイクル状態 — トランスポート呼び出し前に IEmailService.send がキューに入れます" | "ライフサイクル状態 — 送信が要求された時点で、最初の配信試行の前にキューに入ります" | | `status` | es-ES | "Estado del ciclo de vida; se pone en cola mediante IEmailService.send antes de la llamada al transporte." | "Estado del ciclo de vida; queda en cola al solicitarse el envío del mensaje, antes del primer intento de entrega." | For `headers_json`, only the first sentence changes. The rest of each leaf is unchanged. The `status` wording uses the vocabulary of the neighbouring `attempt_count` help in each locale ("delivery attempts", 投递尝试, 配信試行, intentos de entrega). ### How the bundles were produced - **en:** regenerated by the package's documented generator, `pnpm i18n:extract`, after the source edit. It rewrote exactly the two `en` leaves. - **zh-CN, ja-JP, es-ES:** the translated leaf is itself where the translated text lives. The generator's merge mode never overwrites an existing translated leaf. The bundle header ("Edit translations in place … Do not hand-edit the structure — only the leaf string values") and AGENTS.md's Documentation Guardrails row both name re-translating the leaf in place as the maintenance path. So I re-translated the two leaves in place and then ran the generator. It wrote all 11 files and left the three translated bundles and their `*.source-hashes.generated.ts` tables byte-identical. That shows the edit changed leaf values only, never structure. Neither leaf had a recorded source hash, so no provenance entry moves. A second `pnpm i18n:extract` at the head leaves `git status` empty. - The dispatch named es-ES and ja-JP. zh-CN carried the same phrase, so it is rewritten too. ### Pin `packages/platform-objects/src/apps/translations/objects-help-no-service-interface.test.ts` walks every string leaf of the four `objects` bundles. It asserts that none matches the service-interface pattern the notify Template help pin already uses (`\bI[A-Z]\w*Service\b`). It also checks the two `sys_email` source descriptions. A positive control asserts that the walk reaches both rewritten leaves in every locale, so a walk that reaches nothing cannot pass. Today no other leaf in the four bundles matches, so the pin covers the whole class for this package's object help, not just these two rows. Ablation (one-off, no file kept): `scripts/ablation-replace.mjs` put the old zh-CN `status` leaf back, landing confirmed on disk (anchor 1 → 0, blob `5cf8f2e7e81d` → `64f05c216782`). The pin went red as 1 failed and 5 passed, naming `sys_email.fields.status.help` in zh-CN only. The file was restored with the blob equal to HEAD and `git diff HEAD` empty. ## (c) The QA clause quotes the current package-id refusal `docs/qa/platform-checklist/areas/cli.json`, item `cli.plugin-manifest-build-contract`, acceptance clause A2: | Before | After | | :--- | :--- | | "a located Zod error ('Invalid package id '' on `manifest.id`. Expected reverse-domain notation …')" | "a located Zod error ('Invalid package id ''. A package id (`manifest.id`) is written in reverse-domain notation, like …')" | The new quote is taken from `manifestIdRefusal` (`packages/spec/src/kernel/manifest.zod.ts`) at `6729e107e`. I checked it against the live refusal: `ManifestSchema.safeParse` with `id: ''` answers on path `id` with "Invalid package id ''. A package id (`manifest.id`) is written in reverse-domain notation, like 'com.acme.crm' or 'org.example.help-desk' — …". The item's `revision` moves from 2 to 3, with one `history` entry in the file's convention (`revision`, `date`, `change`, `ref`). The scored property is unchanged, and so is A2's `verify` text: probe (d) still reads a located reverse-domain refusal that names `manifest.id` and echoes the received `''`. ## Verification (at `acc26751b`) - **Build:** the extract closure under the verify lock, `pnpm turbo run build --filter='@objectstack/lint...' --filter='@objectstack/platform-objects...' --concurrency=2`: 8 of 8 tasks, `VERDICT command-exit 0`. - **Tests:** `pnpm --filter @objectstack/platform-objects exec vitest run --maxWorkers=2 src/apps/translations src/audit/sys-email`: 27 files, 443 tests passed. - **Typecheck:** `pnpm --filter @objectstack/platform-objects typecheck`, all three legs including `check:test-typecheck`: exit 0. - **Lint:** `eslint --no-inline-config --format json` on the 6 changed TS files: 6 files, 0 errors, 0 warnings. The population comes from `eslint.config.mjs`: `**/*.{ts,…}` minus `NEVER_LINTED`. Type-aware linting is not enabled, so this diff cannot move the verdict on any untouched file. - **Gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 65 commands. I ran them together with the 52-row artifact-roster block, 115 unique commands. 107 exited 0. - Three are PR-context gates that print NOT WIRED (exit 2) without `PR_BODY` or `PR_NUMBER`. `check:partof-closing-keyword` was re-run against this body. - Four exited 3 with PREREQUISITE NOT MET, because they read a whole-workspace build this run did not make: `check:dual-build-cjs-loads`, `check:i18n` (the built CLI), `check:lean-entry-closure` and `check:published-readme-exports`. For `check:i18n`, the documented command it runs for this package (`pnpm i18n:extract`) was run directly, and it is idempotent at the head. - `check:type-check-debt` was not run locally: its re-measure is a workspace build outside the verify lock. CI reads it. ## Acceptance notes - The zh-CN, ja-JP and es-ES `headers_json` help leaves omit the last English sentence ("Never returned on the generic data path — …"). This translation lag predates this PR, and the source-hash mechanism treats a leaf with no recorded hash as legacy-trusted, so nothing flags it. This PR re-translates only the sentence that named the interface. Noted, not filed. Carrier: none. --- _Generated by [Claude Code](https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…bjectstack-ai#22322) Fixes objectstack-ai#22093 Clause-②: no The card's last remainder, (a): the form view `submitBehavior` describe in `packages/spec/src/ui/view.zod.ts` no longer ends in a ruling date. Help text only: no key, type, accept/reject or export change, with a `patch` changeset for `@objectstack/spec`. The diff reaches `skills/objectstack-ui/references/react-blocks.md`, a Tier H governed surface, through the generator alone, so this PR stays a draft and lands on the maintainer's approval (route A of the seat's comment `6047193742`). ## What changed | Where | Before | After | | :--- | :--- | :--- | | `FormViewSchema.submitBehavior` describe (`view.zod.ts`), the property help that Studio, the reference docs and the published skill show for this key | "Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped (ruled 2026-08-11)." | "Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped." | Every fact the sentence stated stays. The ruling's date and its tracker reference (objectstack-ai#7496) now live in the code comment directly above the describe, in the form PR objectstack-ai#22125 used for the other 22 rows, and that comment's paragraph that deferred this edit to this card is gone. ## Regenerated projections (generator output, not hand-edited) `pnpm --filter @objectstack/spec check:generated` after the edit named exactly two stale artifacts, `check:react-blocks` and `check:docs`. `check:generated --fix` regenerated those two and nothing else, and a re-run reads all 15 artifacts up to date. | File | Rows changed | Reading | | :--- | :--- | :--- | | `skills/objectstack-ui/references/react-blocks.md` | 1 (the `submitBehavior` row) | The row used to be clipped at the generator's 160-char limit ("… URL-escaped (ruled…"). The new sentence is 153 chars, so the row now reads whole: "… URL-escaped." | | `content/docs/references/ui/view.mdx` | 5 `submitBehavior` rows | "(ruled 2026-08-11)" dropped; nothing else moves | | `content/docs/references/api/protocol.mdx` | 2 `submitBehavior` rows | same | Published-skill readings (`skills/**` is token-ratcheted by `check-skills-token-ratchet`, counting `ceil(utf8 bytes / 4)`), at `4e4111ca0` → this head: - `react-blocks.md`: 115 → 115 lines; 13,633 → 13,624 bytes; 3,409 → 3,406 tokens. - the whole `skills/` package (65 files): 13,343 → 13,343 lines; 627,340 → 627,331 bytes; 156,835 → 156,833 tokens. Net −9 bytes, no new content, no re-wrap. ## Pin `packages/spec/src/ui/view-submit-redirect-url.test.ts` gains section 6. It reads the `submitBehavior` help from the published JSON Schema projection (`projectPublishedJsonSchema`, the projection `gen:react-blocks` and `gen:docs` render from) and asserts: the positive control (the sentence still names `Post-submit behavior`, the `redirect` arm, `relative-only`, `{{record.field_name}}`, and ends on "URL-escaped."), that it matches neither the file's `RULING_DATE` pattern nor a tracker id, and that the source node's `.description` equals the projection. Reverse verification (the fix committed first; one-off, no file kept): `view.zod.ts` restored to its `4e4111ca0` bytes with `git restore --source` (tree only; blob `e60abeaa9` == the base blob; old-marker count 1, new-marker count 0), the file ran **2 failed / 52 passed**, the two new cases by name. Restored with `git checkout HEAD --` (blob `48b21f8d9` == HEAD; `git diff HEAD` empty; porcelain clean), **54 passed**. The pin imports the schema from `src` by relative path, so no dist leg applies. ## Verification (at `acba7086d`, under `os-verify-lock` on a shared box) - `pnpm --filter @objectstack/spec build`: exit 0 (held 128s). - The eight spec test files that pin `submitBehavior` (incl. `scripts/nested-shape.test.ts`): 8 files, 700 tests passed. - `pnpm --filter @objectstack/spec typecheck` (tsc, `check:scripts-typecheck`, `check:test-typecheck`, which compiles the test layer): exit 0. - `check:generated`: 15 of 15 artifacts up to date. - `eslint --no-inline-config --format json` on the two changed TS files: 2 files, 0 errors, 0 warnings. Population: `eslint.config.mjs` `files: **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` minus `NEVER_LINTED`; no `parserOptions.project` (no type-aware linting), so this diff cannot move the verdict on any untouched file. - Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `acba7086d` derived 114 commands (6 paths, +67/−16 vs merge base `4e4111ca0`). Each ran with its exit code captured before any pipe: 110 exit 0 (three docs gates, `check:doc-formula-expressions`, `check:doc-security-posture` and `check:docs-transcript-drift`, first refused on an unbuilt `@objectstack/lint`; after `turbo run build --filter='@objectstack/lint...'` under the lock they ran green); 3 exit 3 PREREQUISITE NOT MET, read from CI (`check:skill-examples` needs the client-react dist, `check:dual-build-cjs-loads` and `check:lean-entry-closure` need every package's dist); `check:type-check-debt` claimed NOT-MEASURED (its re-measure is a whole-workspace build; CI). `--ran` reconciliation: 114 derived, 110 run, 4 NOT-MEASURED, 0 UNRUN. The 49-row artifact-roster block also ran: 45 exit 0; 3 exit 2 NOT WIRED without PR context (`check-closing-target-claim`, `check-partof-closing-keyword`, `check-single-claim-paths`; the Part-of/closing-keyword gate re-run with `PR_BODY` set to this body: exit 0); `check:published-readme-exports` exit 3 (every package's dist; CI). ## 维护者速读(草稿) **改了什么:** 表单视图 `submitBehavior` 属性的帮助文案(Studio 属性面板、参考文档、发布的 `objectstack-ui` 技能里显示的是同一句)不再以「(ruled 2026-08-11)」结尾;规则本身一字未改。裁决日期与 objectstack-ai#7496 进了代码注释。`react-blocks.md`、`view.mdx`、`protocol.mdx` 的对应行由生成器重生成,`react-blocks.md` 那一行因此不再被截断。 **为什么改:** 这是 objectstack-ai#22093 的最后一项:作者看到的帮助文案里不该出现内部裁决日期。其余 22 行已在 PR objectstack-ai#22125 落地;这一行投影到受管的发布技能目录 `skills/**`,所以单独成 PR、等维护者批准。 **风险与代价(含回滚):** 纯文案,不改接受/拒绝行为、键、类型或导出;`@objectstack/spec` patch 变更集。`react-blocks.md` 行数不变、净减 9 字节。回滚 = revert 本 PR。 **席位意见:** (席位填写) **你要做的:** 批准(approve)本 PR 一次;之后由席位落地。 ## Acceptance notes - `packages/spec/scripts/lib/generated-output.ts` records that the governed-merge register carves generator-owned files inside `skills/**` out of the human-merge fork when the queue leg reproduces them byte-exact. Whether that applies to this PR is the seat's read; this PR follows the dispatch route (draft, Tier H). Noted, not filed. --- _Generated by [Claude Code](https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
… cited decision in words instead of a tracker number (stage 29) (objectstack-ai#22376) Part of objectstack-ai#20749 Clause-②: no Stage 29 of this card: the next area of class (e), the test strings shipped under `packages/spec/src`, as ruled in `5902360492` on objectstack-ai#20513. This stage takes the second name-ordered group of the test files directly in `packages/spec/src/`: 15 files, `root-entry-migrations-split.pin.test.ts` through `stack.test.ts`. 12 of them carried 57 tracker ids in test titles (59 ids), one of which sits in a declared describe label rather than a literal title. All 57 now either state what their record decided, in words (form D), or drop the number where the title already says it. Text only: no assertion, identifier, test count, code comment, file name or non-test file changes. No file directly in `src/` carries an id in a string any more (`type-alias-convention.pin.test.ts`, which sorts after the group, reads 0); the card stays open for the subdirectory files named under Acceptance notes. ## Census (re-taken first) The instrument is stage 28's `census28.cjs`, byte-identical (md5 `31d8488b5194b8d3048e3fcaec0efaed`, the value stage 28 published): an AST walk over the `packages/spec/src` test files, one message per folded string (a lone literal, a template, or a plus chain) that matches the gate's id pattern, a title when the folded root is argument 0 of a describe / it / test / suite / bench call, comments never read. It was run against the three published readings before it was trusted, and all three reproduce exactly: 128 messages / 130 ids in 37 files at stage 27's landing `f7b8a5932b`, 191 / 200 in 53 files (titles 162 / 168, other 29 / 32) at `aa09db58c9`, and 73 / 76 in 24 files at stage 28's landing `b7e01fbbd`. | reading | messages / ids | files | |:--|--:|--:| | the group, base `43fc50051` | 57 / 59 (titles 56 / 58, other 1 / 1) | 12 of 15 | | class (e) whole, base `43fc50051` | 73 / 76 (titles 61 / 64, other 12 / 12) | 24 | | class (e) whole, this head | 16 / 17 (titles 5 / 6, other 11 / 11) | 12 | | the group, this head | 0 / 0 | 0 of 15 | The group reads 57 / 59, stage 28's landing figure: no re-cut. The census at `3f80f1716` (the dispatch base) and at stage 28's landing read the same per file as at `43fc50051`. Per file, messages at base: root-entry-migrations-split.pin 1, stack-artifact-crossref 11 (12 ids), stack-artifact-packages 3, stack-cross-reference-envelope 2 (one title, one declared label), stack-dev-logins 4, stack-email-template-locale-floor 6, stack-inline-action-crossref 13, stack-json-stage-package-body 4, stack-refusal-envelopes 2, stack-requires 4 (5 ids), stack-top-level-strict 5, stack 2. `stack-conversions-record`, `stack-duplicate-action-key` and `stack-provenance` carry no id in any literal. Controls: - Pathspec: the 15 named paths hit the control word `describe(` in 15 of 15 files, and a nonsense word in none; the census scanned 15 of 15. - Lit outside the group, at base and head: `ai/build-progress.test.ts` 2 / 2 and `ui/dashboard.test.ts` 2 / 2. - Lit at base and dark at head, inside the group: `stack-artifact-crossref` 11 / 12, `stack-inline-action-crossref` 13 / 13, `stack-requires` 4 / 5 and `stack-cross-reference-envelope` 2 / 2 each read 0. - Dark at both ends: `stack-conversions-record` has no id anywhere; `stack-duplicate-action-key` and `stack-provenance` carry an id in one comment line each and read 0. The 15 group files read 0 at head while 55 of their comment lines still carry ids (112 id-bearing lines at base, comments included, 14 files). - Planted, in a scratch tree: an id in a describe title, a plus-chain title, an expect message, an it.each row name, a template title and a declared string each read once (6 / 6); a comment, a six-digit number, an HTML entity and a markdown heading read 0. ## What changed 57 literals, one line each, in 12 files: +57 / -57. Every file keeps its line count. - 6 state the decision in words: "a modal target names a page, only" for the inline and the embedded modal-target refusals (stack-inline-action-crossref 133 and 330); "it was never built by a stack producer" for the hand-built stack object composeStacks refuses before either artifact-scoped rule runs (stack-artifact-crossref 380); "compatibility with the multi-package artifact" (stack-artifact-crossref 270); "unchanged from before the dangling-`objectName` refusal" (stack-inline-action-crossref 399); and the declared label "hooks[].object (the hook-ownership rule)", the rule ADR-0130 states as "the split must follow hook ownership" (stack-cross-reference-envelope 91). - 4 get their subject back where the number was the only subject: "the no-floor report: warn-once bookkeeping" and "the floor guard's declared scope boundary" (stack-email-template-locale-floor 159 and 179), "the strict top-level door: the accept side does not move" and "defineStack surfaces the unknown-top-level-key refusal" (stack-top-level-strict 173 and 225). - 47 drop a number the title already explains, with its connector: a `#N — ` or `#N ` prefix, a `[#N] ` prefix, or a trailing `(#N)`. That includes the twin title `stack-artifact-packages.test.ts:355`, which read `does not warn about an undeclared composition rule (objectstack-ai#5005 rule 3)` and now reads `does not warn about an undeclared composition rule`, the same text stage 28 gave its copy at `compose-stacks-manifest-preserve.test.ts:308`. ## Cited records 26 distinct ids. 25 answer 200 and were read: the body and the comment thread as the API serves it, with the rulings and landings that the rewritten titles state read in full. objectstack-ai#10485 answers 404; its decision was read from its landing commit `35ad101bc` (the `themes` carrier retired under ADR-0049 enforce-or-remove; `app.branding` is the one colour surface) and from the unknown-key strictness ledger, which records the maintainer's 2026-08-21 disposition as retiring the authorable surface, the phrase the title keeps verbatim. The comments counter of four records differs from what the comments endpoint serves (objectstack-ai#14122 serves 21 of 22, objectstack-ai#17614 5 of 6, objectstack-ai#16449 0 of 5, objectstack-ai#18056 5 against a counter of 4); everything served was read, and objectstack-ai#16449's decision is in its body. What each record decided, as the titles now say it: - objectstack-ai#6739, ruling A (2026-08-09): a `type: 'modal'` target names a page, only; the object fallback is consumer leniency and enters retirement. - objectstack-ai#20367, ruling B: one authoring shape; `defineStack` and `composeStacks` stamp a provenance mark and `composeStacks` refuses an unmarked input with `STACK_PROVENANCE_MISSING` / 422. - objectstack-ai#7456, Option A: the embedded `objectName` gets the registered rule's existence check, and only a dangling name newly refuses. - objectstack-ai#19926, ruling A: `packages: null` is malformed, not absent. - objectstack-ai#19784, ruling C: a non-array concat key is refused with the envelope. - objectstack-ai#5005 (2026-08-04): same value passes, a conflict throws a prescriptive error, an undeclared top-level key warns. - objectstack-ai#8687, Shape B: the top-level stack door is strict, and the lint yields to it. - objectstack-ai#14122 / objectstack-ai#18202: one artifact carries N packages; the artifact-scoped permission and seed references resolve against the artifact, a stack that does not opt in is untouched. - objectstack-ai#17518, ruling A prime: the artifact-stage and record-stage package bodies are declared beside the assembled one. - objectstack-ai#6889 and objectstack-ai#7397: inline page-element actions and object-embedded actions get the registered action rule's target cross-references. - objectstack-ai#17063: the `type: 'page'` list-view mount is retired. objectstack-ai#17556: an app declares its own `devHint` / `devLogins`. objectstack-ai#17614 and objectstack-ai#18056: the no-`en-US`-floor report and its declared scope. objectstack-ai#14552 and objectstack-ai#15963: every `defineStack` refusal carries an ADR-0112 envelope; objectstack-ai#16449 registers each shipped code in the ledger. objectstack-ai#3265 and objectstack-ai#3308: `requires` is validated at the producer and the camelCase aliases are removed. objectstack-ai#14153 and objectstack-ai#20332: trigger capability validation and its `triggers` without `automation` pair. objectstack-ai#20646: the root entry stops carrying the migration registries. objectstack-ai#2611: named import mappings are wired into the import path. ## Readers 173 needles (each old literal, a 24-character window around each id, and the text left and right of each id) were searched with `git grep -F` over the tracked tree at the base outside the 15 files, with a lit control (`composeStacks` in `stack.zod.ts`, hit) and a dark control (a nonsense string, no hit). One needle hit, one line: a `//` comment in `packages/qa/dogfood/test/fixtures/override-composite-fixture.ts` that quotes the tail of one title with its id. It is a comment, not a reader. The only reader of the declared label is the file's own `describe(row.label, ...)`. No group file reads a test name (`currentTestName`, `expect.getState`, snapshots: 0). The 15 file names appear elsewhere only in prose and comment lines, in `packages/spec/test-typecheck-debt.json` (keyed by file and error text, not by title) and in two `dispatch-gates.mjs` / `check-changeset-no-major.mjs` fixtures that name a path or a changeset file. Every old literal occurs exactly once across the 15 files (its own site). ## Text-only proof - `textonly28.cjs` (stage 28's, md5 `957eff6b3837d762b8e03d070155930a`), per file, base text against head text: both texts with every string token masked are byte-equal (code and comments), the string-token counts are equal, and every changed token sits in a describe / it / test title position or on a declared line, carried an id at base and carries no `#` plus digits at head. 15 / 15 files SAME; 57 changed tokens, 56 titles and 1 declared label (run with `--declared 91`); per-file counts exactly as predicted in writing at 2026-10-08T22:57Z, before the edit. The 3 untouched files read SAME with 0 changed. - 17 / 17 controls on scratch copies of the head files behave as predicted: an identifier rename, a numeric literal, a comment edit, a non-title string given an id, a rewritten title given a new id, a title that was id-free at base edited, one rewritten title reverted to base (SAME, 0 changed), an it.each title given an id, an expect message changed, a title re-split into a plus chain, a template title given an id, a test added, an id appended to a rewritten title in a many-title file, an untouched file (SAME, 0 changed), the declared label run without `--declared` (refused), the declared line plus another changed string (refused) and the declared line alone (SAME, 2 changed). - `git diff -U0`: 57 plus and 57 minus lines, each the planned line; 0 added lines carry `#` plus digits; control-byte scan of the 12 files and of this body: 0 hits. ## Tests - The 15 files with `--project local --project repo` and the JSON reporter, at the base (same worktree, before the edit) and at the head `65b42b7d1`: 405 / 405 passed in 15 files, 108 suites each side; per-file test count and status sequence identical in 15 / 15; 229 full names changed, 0 mismatches against the plan (each head name equals the base name with the planned replacement applied); 0 duplicate full names either side; names with `#` plus digits 229 at base, 0 at head. - spec `vitest run --project local`: 626 files passed, 18740 tests passed | 1 todo, exit 0. - spec `typecheck`: `VERDICT command-exit 0`; `check:test-typecheck` 52 files / 246 errors / 135 pinned signatures held, unchanged; `tsc --listFilesOnly -p tsconfig.test.json` holds 15 of 15 group files. - turbo build of all packages before the gates: `Tasks: 71 successful, 71 total`. Heavy runs went through `scripts/pm/os-verify-lock.sh` (slot `dev-20749-s29`). - No reverse verification or ablation applies: no assertion, gate or reader moved. ## Gates All at `65b42b7d1`. The dispatch list (77 commands, derived at `3f80f1716`) equals the list re-derived from the real change set at this head, 12 paths against merge base `43fc50051` (114 changed lines): nothing added, nothing dropped. All 77 exit 0 and `dispatch-gates --ran` reads "77 derived, 77 run, 0 NOT-MEASURED, 0 UNRUN". Also run, exit 0: the five roster families that share a directory with the diff (`check:meta-url-spelling`, `check:spec-changes`, `check:authz-resolver`, `check:error-code-casing`, `check:filter-alias-parity`) and `check:generated` ("All 15 generated artifacts are up to date", measured against the dist the full build wrote at this head). ESLint, narrowed and proven: 15 files linted with `--no-inline-config`, 0 errors, 0 warnings; the population read from ESLint's own config is 15 configured, 0 ignored; no file has `parserOptions.project` or `projectService`, so type-aware linting is not enabled and this diff cannot move the verdict of a file it did not touch. ## Packaging and landing - No changeset: `skip-changeset`. `npm pack --dry-run --json --ignore-scripts` in `packages/spec` lists 2069 files, none of the 12 changed files and no `*.test.ts`; controls `src/stack.zod.ts`, `dist/index.mjs` and `package.json` are present; an old and a new title phrase read 0 files in `dist/`, the control `Unrecognized key` reads 42. - Not governed: `check-governed-merges --test` on the 12 paths reads 0 of 12; 114 changed lines. - `git merge-tree` onto `origin/main` `6a53564b9`: clean. None of the 17 open PRs touches any of the 15 files (file lists read; 6 of them touch `packages/spec/src`, so the scan sees spec files). ## Acceptance notes - Class (e) is not finished: 16 messages / 17 ids remain in 12 files, all in subdirectories. By directory: `ui/` 9 / 9 in 7 files, `automation/` 2 / 3 in 1, `ai/` 2 / 2 in 1, and `api/`, `contracts/` and `kernel/` 1 / 1 in 1 each. Their plan is the cutting seat's: stage 27's landing sets the needles stage apart (one stage, with an at-tier review) and keeps `ui/`'s four colour literals as decided, and this stage measured neither. - Regrowth for stage 30's plan: five subdirectory test files carry 6 messages / 7 ids that were added after stage 27's landing. The files are older; the ids entered their strings in three commits. `api/meta-item-response-shapes.test.ts` 1 / 1 (a title, from objectstack-ai#22126); `automation/builtin-node-config.test.ts` 2 / 3 (two titles, from objectstack-ai#22259, which re-added ids to a title stage 10 had rewritten); `kernel/manifest.test.ts` 1 / 1, `ui/action-description.test.ts` 1 / 1 (titles) and `ui/view-submit-redirect-url.test.ts` 1 / 1 (an `other` string, to be needle-checked there), all three from objectstack-ai#22125. Nothing regrew since stage 28's landing: the per-file census at `b7e01fbbd` equals the one at `43fc50051`. Test files are outside `check:doc-authoring`'s ledgered leg, so nothing refused these on arrival. Not edited here. - Code comments in the 15 files still carry live ids (55 lines). Comments are not this card's share. Carrier: none. - `origin/main` moved from `43fc50051` to `6a53564b9` while this ran; one commit touches `packages/spec` (`api/error-code-ledger.zod.ts`, none of the 15 files), so nothing was merged into the branch. --- _Generated by [Claude Code](https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn)_ Co-authored-by: Claude <noreply@anthropic.com>
…n in words instead of a tracker number (stage 30) (objectstack-ai#22414) Part of objectstack-ai#20749 Clause-②: no Stage 30 of this card: the class (e) remainder, the test strings shipped under the `packages/spec/src` subdirectories, as ruled in `5902360492` on objectstack-ai#20513. The census at the base reads 17 messages / 18 ids in 12 files. This stage rewrites 7 of them (6 titles and 1 expect message, 8 ids) in 5 files: each now states what its record decided, or drops the number where the title already says it. The other 10 messages / 10 ids stay, 4 because earlier stages decided they are not citations and 6 because an assertion matches the string against text this claim does not let the stage edit; both groups are named under "What stays". Text only: no assertion, identifier, test count, code comment, file name or non-test file changes. No file is deferred. ## Census (re-taken first) The instrument is stage 28's `census28.cjs`, byte-identical (md5 `31d8488b5194b8d3048e3fcaec0efaed`, the value stages 28 and 29 published): an AST walk over the `packages/spec/src` test files, one message per folded string (a lone literal, a template, or a plus chain) that matches the gate's id pattern, a title when the folded root is argument 0 of a describe / it / test / suite / bench call, comments never read. It was run against the three published readings before it was trusted, and all three reproduce exactly: 128 messages / 130 ids in 37 files at `f7b8a5932b`, 191 / 200 in 53 files at `aa09db58c9`, 73 / 76 in 24 files at `b7e01fbbd`. | reading | messages / ids | files | |:--|--:|--:| | base `11d119ab1` | 17 / 18 (titles 6 / 7, other 11 / 11) | 12 | | stage 29's landing `0ef9029da` | 17 / 18, per file equal to the base | 12 | | this head | 10 / 10 (titles 0 / 0, other 10 / 10) | 7 | | the 5 edited files, this head | 0 / 0 | 0 of 5 | Stage 29's ACCEPT carried 16 / 17 (ui 9 / 9 in 7 files, automation 2 / 3, ai 2 / 2, api 1 / 1, contracts 1 / 1, kernel 1 / 1). The base reads 1 / 1 more, all in `ui`: the title `carries no ruling date and no tracker id (objectstack-ai#22093)` at `view-submit-redirect-url.test.ts:341`, which PR objectstack-ai#22322 (`ad381fd94`, landed 2026-10-09T00:35Z) added after stage 29's head. So `ui` reads 10 / 10 in 7 files, and nothing else moved. The census at `origin/main` `e75dceddd` (8 commits past the base, none touching the 12 files) reads the same 17 / 18 in the same 12 files, so nothing regrew while this stage ran. The reading is within one message of the claim's, so there was no re-cut. Per file, messages at base: `ai/build-progress` 2, `api/meta-item-response-shapes` 1, `automation/builtin-node-config` 2 (3 ids), `contracts/approval-service` 1, `kernel/manifest` 1, `ui/action-description` 1, `ui/component-props-unknown-members.pin` 1, `ui/dashboard-chart-structure-refusal` 2, `ui/dashboard` 2, `ui/notification` 1, `ui/strictness-batch14` 1, `ui/view-submit-redirect-url` 2. Controls: - Pathspec: the 12 named paths hit the control word `describe(` in 12 of 12 files and a nonsense word in none; the census scanned 12 of 12. - Planted, in a scratch tree: an id in a describe title, a plus-chain title, an expect message, a template literal, a cross-repo spelling and a ledger-style string each read once (6 / 6); a comment, a six-digit colour, an HTML entity, a two-digit number and a hex colour with a letter read 0. - Lit and dark inside the group: the 5 edited files read 1, 2, 1, 1 and 2 messages at base and 0 at the head; the 7 untouched files read the same at both ends. ## Deferral At the census (2026-10-09T03:03Z) 17 PRs were open; at the re-scan before opening this PR (04:13Z), 13. Every file list was read through REST (605 and 593 rows). None touches any of the 12 files: lit control `api/protocol.test.ts` (PR objectstack-ai#22323) found, dark control 0. Of the four PRs the claim named, objectstack-ai#22380 has landed and objectstack-ai#22315, objectstack-ai#22323 and objectstack-ai#22215 are open; none of them touches a file in this group. Deferred files: none. ## What changed 7 literals, one line each, in 5 files: +7 / -7. Every file keeps its line count. - `api/meta-item-response-shapes.test.ts:226`: the `[objectstack-ai#22114] ` prefix goes; the title already says what objectstack-ai#22126 landed, that the read serves the version token and the 409 carries the current one as data. - `automation/builtin-node-config.test.ts:434`: "a CEL envelope beside literals; the `{token}` dialect retired". objectstack-ai#14149's ruling A made an assignment value a CEL envelope beside literals, and objectstack-ai#19939 retires the `{token}` dialect in flow value slots; the title already stated both, so only the two numbers go. - `automation/builtin-node-config.test.ts:485`: the `[objectstack-ai#19939] ` prefix goes from the REFUSES title. - `kernel/manifest.test.ts:681` and `ui/action-description.test.ts:235`: the trailing `(objectstack-ai#22093)` goes. objectstack-ai#22093 decided that author-visible help and refusals carry no service-interface name, ruling date or foreign example id, and both titles already say what their bodies pin. - `ui/view-submit-redirect-url.test.ts:341`: the trailing `(objectstack-ai#22093)` goes from the title. - `ui/view-submit-redirect-url.test.ts:118`: the expect message `states the rule, not its ruling date (objectstack-ai#22093)` drops the number. It is an assertion's failure message, so it was needle-checked first (below) and is the one declared non-title string. All seven are "drop a number the title already explains". None needed a rewrite in new words, because each title already carried the decision. ## What stays, and why 10 messages / 10 ids in 7 files, none edited. Four are CSS hex colours, not citations. `colors: ['objectstack-ai#111', 'objectstack-ai#222']` at `ui/dashboard-chart-structure-refusal.test.ts:94` and `palette: ['objectstack-ai#111', 'objectstack-ai#222']` at `ui/dashboard.test.ts:124` are fixture input the schema under test reads. Stage 21's ACCEPT (`6001279159`, decision A) kept them by file and line, and every later stage carried them forward. Six are strings that an assertion matches against text outside this stage's edit surface. Moving one at the same strength means editing a non-test source docblock (and, for the first two, its generated reference page) or the assertion that matches it. The claim forbids both and says to stop and report, so none is touched; `open_questions` in the report carries the decision. - `ai/build-progress.test.ts:236` `'cloud#2172'` and `:237` `'objectui#7388 block 2'`: `toContain` over the source text of `ai/build-progress.zod.ts` (docblock lines 8, 27 and 85), which `content/docs/references/ai/build-progress.mdx` renders. - `contracts/approval-service.test.ts:274` `'objectstack-ai#16495'`: `toContain` over the docblock above `continueRestoredRun` in `contracts/approval-service.ts` (line 999). - `ui/notification.test.ts:123` `'// [objectstack-ai#4610]'`: the locator of the tombstone note in `ui/notification.zod.ts:94`; the file's own `toMatch(/^\[objectstack-ai#4610\]/)` at `:134` reads the same note. - `ui/strictness-batch14.test.ts:395` `'objectstack-ai#5015'`: `toContain` over `ui/notification.zod.ts` and `ui/sharing.zod.ts`. - `ui/component-props-unknown-members.pin.test.ts:322` `ruling: 'decision card objectstack-ai#21704, fork 4, letter B (record 5979239990)'`: the file's own assertion at `:417` matches the value with `/objectstack-ai#21704/`. Stage 20's ACCEPT (`5998488373`) kept it for this reason and sent it to the needles' stage. Readers of the seven rewritten strings: none. `git grep -F` at HEAD over the tracked tree outside the 12 files, with the full literal, a 24-character window around each id, and the text on each side of each id (29 needles over all 17 sites): the only hits are the readers of the kept strings named above, the lit control (`composeStacks` in `stack.zod.ts`) hits and the dark control does not. The same needles searched inside the 12 files, outside each literal's own span: the only hits are two code comments beside `:322`. The five short needles (`cloud#2172`, `objectstack-ai#16495`, `// [objectstack-ai#4610]`, `objectstack-ai#5015`, `objectstack-ai#21704`) fall under the script's 12-character floor, so their readers were confirmed by direct `git grep -F` with a dark control. ## Cited records Read with their comments as the API serves them: objectstack-ai#22114 (8 of 8 comments; landed as PR objectstack-ai#22126), objectstack-ai#14149 (12 of 12; ruling A `5507504961`, landed as PR objectstack-ai#15113), objectstack-ai#19939 (15 of 15; pass 1 landed as PR objectstack-ai#22259, the card stays open), objectstack-ai#22093 (17 of 17; PRs objectstack-ai#22125, objectstack-ai#22309 and objectstack-ai#22322), and the four PRs themselves. objectstack-ai#19939 is still open: its pass 1 refuses the `{token}` dialect in flow value slots and keeps two spellings (the date macros and `{$User.*}`) until CEL can write them. The describe's PRESERVATION test still accepts those two, and the title keeps the record's own verb, "retired", as PR objectstack-ai#22259 wrote it. The title and the test body say the same thing the record says. ## Verification At head `8885dbf1c` (one commit on base `11d119ab1`): - **Text only.** `textonly28.cjs` (stage 28's, md5 `957eff6b3837d762b8e03d070155930a`) on all 12 base copies against their heads: 12 / 12 SAME. 7 changed tokens, as predicted in writing at 2026-10-09T03:08Z before any edit or test run: 6 titles and 1 declared string (`--declared 118`). Every other string token, identifier, number, punctuation mark and comment is byte-equal. 16 controls, expectations written in the script before the first run, 16 / 16 as predicted: an identifier rename, a numeric literal, a comment edit, an undeclared expect message, a rewritten title given a new id, an id-free title edited, one title reverted to base (SAME, 0 changed), a declared label without `--declared`, a declared line plus another changed string, the declared line alone (SAME, 2 changed), a title re-split into a plus chain, a test added, an untouched file (SAME, 0 changed), an id appended to a rewritten title, a kept needle rewritten, a kept hex colour rewritten. The two controls that mutate a string beside the declared line fail at the mutated line, not at 118. - **Tests, 12 files, base and head.** `--project local --project repo` with the JSON reporter, 618 tests in 88 suites each side, all passed. Per-file test count and status sequence identical in 12 / 12. 23 full names changed (4 + 14 + 1 + 3 + 1), 0 mismatches against the plan. Names carrying `#` plus digits: 23 at base, 0 at head. Duplicate full names: 3 and 3, the same three `[object Object]` it.each rows at both ends. - **Full spec unit tier at the head**, under the verify lock: `Test Files 626 passed (626)`, `Tests 18743 passed | 1 todo (18744)`. - **Build and typecheck**, under the verify lock: `turbo run build` over `packages/*` and `packages/*/*`, `Tasks: 71 successful, 71 total`; `@objectstack/spec` `typecheck` exit 0 with `check:test-typecheck` holding 52 files / 246 errors / 135 pinned signatures, the same figures as stage 29; the 12 files are all in the `tsconfig.test.json` program. - **Gates.** `dispatch-gates.mjs --commands` at the head derives 79 (stage 29's 77 plus `check:authorable-surface` and `check:yaml-examples`); all 79 exit 0, and `--ran` reconciles 79 derived, 79 run, 0 NOT-MEASURED. The five artifact-roster families that keep their roster in a directory one of the paths is in (`check:meta-url-spelling`, `check:spec-changes`, `check:authz-resolver`, `check:error-code-casing`, `check:filter-alias-parity`) and `check:generated` (all 15 artifacts up to date) also exit 0. - **ESLint**, `--no-inline-config`, 12 files: 0 errors, 0 warnings. Population from ESLint's own config: 12 configured, 0 ignored, 0 with a type-aware parser option, so this diff cannot move the verdict of a file it does not touch. - **Skip-changeset.** `npm pack --dry-run --json --ignore-scripts` in `packages/spec`: 2069 files, 0 `*.test.ts`, 0 of the 5 edited files; controls `src/stack.zod.ts`, `dist/index.mjs` and `package.json` present. The rewritten expect message occurs in 0 files of `dist/`; the control `Unrecognized key` occurs in 42. Nothing published changes. - **Governed.** `check-governed-merges.mjs --test` on the 5 paths: 0 of 5, not governed; 14 changed lines. - **Merge.** `git merge-tree --write-tree` onto `origin/main` `e75dceddd`: clean. - **Bytes.** 0 added lines carry `#` plus digits; 0 control bytes in the changed files. Declared narrowing: the 12-file base and head comparison ran outside `os-verify-lock.sh`, after three queue turns (about 28 minutes) ended without a grant. It is a 12-file run with two workers; the workspace build, the typecheck and the full unit tier all ran under the lock. The gates are `check:*` runs, which do not use the lock. ## Acceptance notes - **Regrowth continues.** Since stage 27's landing, four PRs (objectstack-ai#22125, objectstack-ai#22126, objectstack-ai#22259 and objectstack-ai#22322) added 7 messages / 8 ids to test strings in files that already existed, one of them to a title objectstack-ai#22322 wrote while stripping a ruling date from a describe. Test files sit outside `check:doc-authoring`'s ledgered leg, and the ruling adds no gate, so the per-stage census is the only instrument. An observation about the burn-down's denominator, not a class a / b / c finding. - **Comments are untouched.** Code comments in these files still cite ids (for example `// ─── assignment (objectstack-ai#14149) ───` at `builtin-node-config.test.ts:432`); comments are objectstack-ai#20234's share. --- _Generated by [Claude Code](https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn)_ Co-authored-by: Claude <noreply@anthropic.com>
Part of #22093
Clause-②: no
Wording only: no schema, key, type, export or error-code change. Patch changeset for
@objectstack/spec,@objectstack/service-automationand@objectstack/platform-objects. objectui#11785 is the Studio half (splitting an author message from the detail); this PR changes the server-side strings only. The card keeps two named remainders, listed under "Remainder of the card and its route" below.What changed
Form help and refusals that an author reads no longer carry service-interface names (
IXxxService.method()), ruling dates ("maintainer ruling DATE", "ruled DATE") or another product's example ids. The rationale they carried moves into the code comment beside each string. Each describe and refusal still states the rule, why it exists and the repair.How "author-facing" was measured (instrument and radius)
Studio reaches these strings through five routes. I measured each one at
a543e244f:/meta/typesschema: everyDEFAULT_METADATA_TYPE_REGISTRYtype, derived asmetadata-protocolderives it (z.toJSONSchema,unrepresentable: 'any', with theio: 'input'retry)descriptionin all 27 type schemas/meta/typesform: all 17METADATA_FORM_REGISTRYlayoutsdescription/helpText/label/placeholder/hintManifestSchemaclient-side (objectuipackage-schema.ts)descriptionconfigSchemafromGET /api/v1/automation/actionsconfigSchema(13 files)packages/spec/src(1284 files, 80190 string parts, comments excluded)The class patterns were: interface
\bI[A-Z]\w*(Service\|Driver\|Engine\|…)\b; ruling\bruling\b\|\bruled\b\|\b20\d\d-\d\d-\d\d\b; foreignsteedos\|superset\|salesforce\|apache\|airtable\|….Positive control: both strings the filer measured are found.
service-automation/src/builtin/notify-node.ts:203, as both an interface hit and a ruling hit. The spec copy is found by R5 atio-node-config.zod.ts:250.com.steedos.crmis found by R5 atmanifest.zod.ts:283.R1 to R3 found neither string (needle count 0). So the Studio-rendered Template help is produced by the service-automation descriptor, not by the spec describe. That is why the producer side is changed too.
After the change, R1 to R3 hold no interface, ruling or foreign-id hit. The one exception is the "Airtable parity" wording (see Acceptance notes). The needles
II18nService,IEmailService,com.steedos.crmandmaintainer rulingcount 0. The R5 hit count drops by 22.Every string changed
Lines are at
a543e244f. Before is the internal reference that was removed; After is what replaced it. No changed string carried a tracker number, so no tracker number moved.services/service-automation/src/builtin/notify-node.ts:203, notifyconfigSchema.properties.template.descriptiontemplate:in the descriptorspec/src/automation/io-node-config.zod.ts:250,NotifyConfigSchema.templatedescribetemplate(it already named both; one sentence added)spec/src/kernel/manifest.zod.ts:283,MANIFEST_ID_EXAMPLESPOST /api/v1/packages)'com.steedos.crm', 'org.apache.superset''com.acme.crm', 'org.example.help-desk'manifest.zod.ts:305,manifestIdRefusalmanifest.id. Expected reverse-domain notation ('com.steedos.crm', 'org.apache.superset') — …"manifest.id) is written in reverse-domain notation, like 'com.acme.crm' or 'org.example.help-desk' — …", with the rule and suggestion arm unchangedmanifestIdRefusalmanifest.zod.ts:360-361,@exampleonidcom.steedos.crm,org.apache.supersetcom.acme.crm,org.example.help-deskspec/src/data/field.zod.ts:1142,requireddescriberequiredfield.zod.ts:1170,multipledescriberadioby the narrower 2026-08-22 ruling … (maintainer ruling 2026-08-18)"radioincluded, is REFUSED at parse …"multiple(new Declarability paragraph)spec/src/ui/view.zod.ts:1175/:1221,GROUPING_FIELD_RULINGin the padded grouping-field refusalview.zod.ts:3444, form-view fieldvisibleWhendescribeview.zod.ts:3655, sectioncollapsibledescribeview.zod.ts:3663, sectioncollapseddescribecollapsed: truealone is a correct way to say "collapsed by default""view.zod.ts:3715, form-view sectionvisibleWhendescribeview.zod.ts:3959,SUBMIT_REDIRECT_RULINGin sevensubmitBehavior.urlrefusals (:3988,:3993,:4003,:4010,:4018,:4029,:4039)view.zod.ts:4097/:4123,FORM_VIEW_FEATURES_RULINGin thefeatures.*predicate refusalview.zod.ts:4444, redirect-armurldescribecheckSubmitRedirectUrlcommentspec/src/ui/page.zod.ts:941(kind) and:990(source) describeskind(new) andsource(already)spec/src/system/email-template.form.ts:19, Identity section help. Its three translated values inplatform-objects/src/apps/translations/{zh-CN,ja-JP,es-ES}.metadata-forms.generated.ts:2183are hand-written, andenis regenerated bypnpm i18n:extractdescription:spec/src/data/filter.zod.ts:475, null ordering comparand refusalfilter.zod.ts:564,{ $field }in a list position refusalfilter.zod.ts:589, null list member refusalfilter.zod.ts:801, blank$betweenbound refusalspec/src/ui/action.zod.ts:985, actiondescriptiondescribe (added in patch round 1)io: 'input') derivation; the action Description help objectui#11785 measureddescription(already cites the ruling; one sentence added)The regenerated
content/docs/references/**files follow from rows 2, 6, 7, 9 to 12, 15, 16 and 22.Remainder of the card and its route
This PR delivers the card minus two named rows. It is therefore
Part of #22093, and the card stays open for both.submitBehaviorrow.view.zod.ts:4478, the top-levelsubmitBehaviordescribe, keeps "(ruled 2026-08-11)".gen:react-blocksprojects this describe intoskills/objectstack-ui/references/react-blocks.md, which is a governed Tier H surface, so changing it here would make this whole sweep a Tier H landing. Measured: the regenerated skill file differs in exactly that one row.react-blocks.mdrow and its reference-docs row, landed on the maintainer's approval.sys_emailfield help namingIEmailService.send.packages/platform-objects,en.objects.generated.ts:2500and:2508. The source is in thesys_emailobject definitions.*.objects.generated.tsbundles, so folding it here would put two PRs on those bundles at once.Pins
Refusal
codeis asserted with the sentence:manifest.test.tsassertsinvalid_format.view-form-features-root.test.tsassertscustom.Negative pins on what the author reads. No interface name, ruling date or foreign id appears in:
io-node-config.test.tsnotify-node.test.ts, on the Studio-served descriptor textmanifest.test.ts. Its headline sentence carries nomanifest.id, the key is still named as a locator, and there is no steedos/superset/apache.filter.test.tsview-form-features-root.test.tsview-submit-redirect-url.test.tsPatch round 1:
action-description.test.ts. It derivesActionSchemathe way/meta/typesserves it (io: 'input') and asserts three things about thedescriptionhelp:Pins that changed direction. These earlier pins asserted that the date was present:
view-submit-redirect-url.test.ts, 7 assertions: "cites the ruling so the refusal is traceable"view-form-features-root.test.ts:68filter.test.ts:644io-node-config.test.ts:347and its2026-09-01pinThey are now negative pins.
filter.test.ts:661used the date as its discriminator between two messages. It now discriminates on the blank-bound headline.No whole-prose pin was added.
Where the ruling dates in refusals come from (lineage)
This PR reverses no ruling.
Tests and gates
Patch round 1, on the merged head
026204631.54ace18c6withbash scripts/pm/os-regen-merge.sh.75cec8cd1, parents34bf72933and54ace18c6. There were no conflicts.flow-edge-unresolved-or-repeated-refused,sys-presence-organization-column-retiredandsys-job-organization-column-retired.pnpm --filter @objectstack/spec buildpassed.gen:schemaandgen:docspassed.service-automation^...closure build (spec excluded) passed.pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2over 26 targeted files (round 0's 25 plusaction-description.test.ts): 26 files, 2475 tests passed. This is a declared narrowing; CITest Coreruns the full suite.notify-node.test.ts, 16 passed.src/apps/translations/*.test.ts, 24 files, 430 passed.@objectstack/spectypecheckpassed.pnpm --filter @objectstack/spec check:generatedreports all 15 generated artifacts up to date (react-blocks and api-surface included).action.zod.tswent back to the54ace18c6bytes; landing confirmed bygrep -c= 1.action-description.test.tswent red: "carries no residue of a stripped reference".34bf72933):a543e244fbytes.notify-node.test.ts. Every red is a pin this PR added or flipped.pnpm exec eslint --no-inline-config --format jsonover the 20 changed lintable files at026204631: 20 files, 0 errors, 0 warnings.eslint.config.mjsmatches**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}minusNEVER_LINTED.parserOptions.project, see its note near line 328), so this diff cannot move the verdict on any file it does not touch.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat026204631derived 113 families (30 paths against merge base54ace18c6).check:skill-examples,check:dual-build-cjs-loads,check:i18nandcheck:type-check-debt. In round 0 each exited 3 (PREREQUISITE NOT MET).check:type-check-debt's re-measure is a 45-task workspace build and is not run outside the verify lock.--ranreconciliation: 113 accounted, 109 run, 4 NOT MEASURED (claimed), 0 unrun.Acceptance notes
stack.zod.ts:458-459(IJobService,IEmailService.sendTemplate):defineStackcollection describes. R1 to R3 do not serve them, and they are not refusals.data-engine.zod.ts:165,190(IDataEngine.find())hook.zod.ts:1269(IScopedContext)query.zod.ts:429driver.zod.ts:287-388refusals (IDataDriver)automation-api.zod.ts:452execution-context.zod.ts:441("the 2026-09-03 ruling"):preserveAuditis server-constructed and never authored.plugin-rest-api.zod.ts:138("ruling record, 2026-09-01"): a tombstone onRestApiEndpointSchema. Its own comment records that nothing parses that schema outside its unit tests, so no door reaches it.component.zod.ts:365("Since the console release of 2026-08-21 (c86185eb5)"): a release date and a commit sha, not a ruling.action.zod.ts:1139and:2157page.zod.ts:93view-grouping-query.ts:433context.zod.ts:50protocol.zod.ts:1681endpoint-publish-gate.ts:368,416error-code-ledger.zod.tsnotespage.zod.ts:911,page.form.ts:113,view.form.ts:164, plusview.zod.ts:1706,page.zod.ts:647andcomponent.zod.ts:1766. It is a design note naming a competitor, not an example id.export.zod.ts:292andobject.zod.ts:1528are comparisons, not ids.src/migrations/**, about 1,240 dated or "ruling" hits: upgrade-guide records, not a form or a door.manifest.zod.ts:546, TSDoc@exampleondependencies(@steedos/plugin-auth): TSDoc only.packages/core/src/artifact-packages.ts:114cites that exact example, so changing it moves a comment in core.sys_emailfield help namingIEmailService.sendis the second named remainder; see "Remainder of the card and its route".9990f9e(grep forII18nService,IEmailService,steedos.crm,Invalid package id).Generated by Claude Code