Repository navigation
fix(spec): the submitBehavior property help carries no ruling date - #22322
Conversation
The form view `submitBehavior` describe — the one row the reference table
and the published react-blocks skill render for this key — ends on the
fact ("URL-escaped.") instead of "(ruled 2026-08-11)". The ruling's date
and tracker reference move into the code comment above the describe, and
that comment's deferral paragraph goes. The pin reads the describe from
the published JSON Schema projection, the way the generators do.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848
…protocol) `pnpm --filter @objectstack/spec check:generated --fix` after the describe edit: exactly two artifacts were stale (check:react-blocks, check:docs). The react-blocks row now fits the generator's 160-char clip whole, so it reads the full sentence instead of a truncation. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
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 9c36a38bd1fc2f2745f1a51aa8c4091b82fcbbaf && git checkout 9c36a38bd1fc2f2745f1a51aa8c4091b82fcbbaf
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4e4111ca054fe995b5a08a07d273cad18a749ef7 acba7086d75db27dd45ce54d156ea219cd6ff9fe && git checkout -B drift-repro 4e4111ca054fe995b5a08a07d273cad18a749ef7 && git merge --no-ff acba7086d75db27dd45ce54d156ea219cd6ff9fe
node scripts/docs-audit/affected-docs.mjs --json 4e4111ca054fe995b5a08a07d273cad18a749ef7
|
维护者速读
改了什么: 表单视图 为什么改: 这是 #22093 的最后一项。作者看到的帮助文案里不该出现内部裁决日期。其余各项已由 PR #22125、#22309 落地。这一行会投影进受管的发布技能目录 风险与代价(含回滚): 只改文案,不改接受或拒绝行为,也不改键、类型和导出。 席位意见: 建议批准。
你要做的: 在本 PR 上批准(approve)一次,之后由席位落地。你也可以直接合并。 Generated by Claude Code |
…ince ADR-0096 D5 (objectstack-ai#22382) Fixes objectstack-ai#22372 Clause-②: no The published `objectstack-query` skill taught `{ flowRunId }` "for provenance alone" as a valid execution context. Since ADR-0096 D5 strict mode (PR objectstack-ai#22297) the security plugin refuses a non-system context that carries no principal with `403 PERMISSION_DENIED`, so an AI author following that line wrote a context the engine refuses. This PR rewrites that one paragraph in place so it names the two shapes the engine admits and the refusal otherwise, and records the `skills/**` enumeration the card asked for. ## The paragraph Before (`skills/objectstack-query/SKILL.md:65-69` on `main` at `16096e8d7`): ``` Pass any SUBSET of the execution envelope (identity, tenant, transaction): `{ isSystem: true }` for a system read, `{ flowRunId }` for provenance alone. On the READ methods it may sit in the query bag (above) OR in the trailing options argument, `engine.find(obj, query, { context })`; the trailing one wins when both are given. Writes take only the trailing argument. ``` After (`:65-70`): ``` Pass any SUBSET of the execution envelope (identity, tenant, transaction): `{ isSystem: true }` for a system read, otherwise the caller's own context (user, position or permission set), or `403 PERMISSION_DENIED` (ADR-0096 D5). On the READ methods it may sit in the query bag (above) OR in the trailing options argument, `engine.find(obj, query, { context })`; the trailing one wins when both are given. Writes take only the trailing argument. ``` Lines 68-70 are the original 67-69 re-wrapped, text unchanged. ## The contract the sentence follows — `main` at `16096e8d7`, verbatim at each site - `packages/plugins/plugin-security/src/security-plugin.ts:383-387`, `isPrincipalLessContext`: `positions.length === 0 && explicitPermissionSets.length === 0 && !context?.userId`. That is the "(user, position or permission set)" gloss. - `:398-404`, `principalLessDenial`, the refusal text: "was called with a context that carries no principal (no user, no position, no permission set) and is not a system context. Pass the caller's execution context, or, for platform plumbing whose own door already authorized the caller, the explicit system opt-in (isSystem: true)." — `PermissionDeniedError`, `403 PERMISSION_DENIED`. - `:349-367`, the predicate's docblock: "A non-system context of this class is REFUSED, at every layer that used to hand it through: the engine middleware throws principalLessDenial before it resolves anything, the object-admission probes (`canReadObject` / `canWriteObject` / `canExport`) answer `false`, and its row scope is the deny sentinel (`getReadFilter`)." and "The two ways to reach the engine are explicit, never a missing field: carry the caller's principal, or … the explicit system opt-in (`isSystem: true`)." - `docs/adr/0096-execution-surface-identity-admission.md`, the D5 note dated 2026-10-08: "An engine context that carries no principal (no user, no position, no permission set) and is not a system context is refused with `PermissionDeniedError` (`403 PERMISSION_DENIED`) wherever the security plugin used to hand it through". **Wording and PR objectstack-ai#22327.** PR objectstack-ai#22327 (card objectstack-ai#22302) is still an open draft as of this PR (read via REST: `state: open`, `draft: true`; it edits `packages/spec/src/contracts/security-service.ts`, `packages/spec/src/kernel/execution-context.zod.ts`, `packages/spec/src/data/data-engine.zod.ts`, `content/docs/kernel/contracts/data-engine.mdx` and `content/docs/permissions/access-recipes.mdx`). So the sentence here follows the D5 contract as it stands on `main` — the plugin's refusal text and predicate above — not that PR's draft text; the two agree on substance (principal or `isSystem: true`, else `403 PERMISSION_DENIED`, ADR-0096 D5). On `main` the old sentence still stands at `execution-context.zod.ts:335-336` and `:501`, `data-engine.zod.ts:67-70` and `data-engine.mdx:127-128`; those are objectstack-ai#22327's files and are not touched here. ## Enumeration pin — `git grep -n -i` over `skills/**` on `main` at `16096e8d7` **Class 1, a `{ flowRunId }`-only context.** `flowRunId`: 1 hit, `objectstack-query/SKILL.md:66`, fixed here. `provenance`: 1 hit, the same line. `runId` / `run id`: 2 hits, the same line plus `objectstack-automation/references/state-machines-and-approvals.md:208`, a `:runId` URL path parameter, not a context. **Class 2, a principal-less context that is admitted, keeps its scope or falls open.** Zero hits for each of: `no principal`, `without a principal`, `principal-less`, `principalless`, `anonymous context`, `context: {}` (fixed-string, and the regex `context:\s*\{\s*\}`), `empty context`, `fall open`, `falls open`, `fall-open`, `fail open`, `fail-open`, `hand(ed|s)? (it )?through`, `keeps its scope`, `skips? (the )?(permission |security )?checks`, `no identity`, `without identity`, `resolves no identity`, `without (a )?context`, `contextless`, `no context`, `RLS-on`, `sees-nothing`, `SYSTEM_CTX`, `passes only`. Non-zero query words, each hit read and dispositioned: - `unauthenticated` (4): `objectstack-api/SKILL.md:162` — `authRequired: false` opens an anonymous HTTP entry point (ADR-0121 D6 pairing); that is the door's authentication, not an engine context. The public-form endpoints at `:87-88` run under a synthetic `{ permissions: ['guest_portal'], anonymous: true }` context, which carries a named permission set and so is not principal-less under the predicate. `objectstack-data/references/data-hooks.md:361`, `:602`, `:629` — `ctx.user` is `undefined` for system / unauthenticated writes: the ctx shape, no admission claim. - `no user` (2): `objectstack-automation/SKILL.md:192-194` — a `'user'` hook whose trigger resolved no user has its `ctx.api` refused (`HOOK_UNSCOPED_DATA_ACCESS`, 403) "rather than run unscoped": fail-closed, consistent with D5. `data-hooks.md:930` — "system operations carry no user", a system context. - `context-less` (1): `objectstack-ui/rules/actions.md:127` — `ctx.user` is `undefined` for a context-less / self-invoked call (the `ScopedRepo.execute()` path, `packages/runtime/src/sandbox/body-runner.ts:1188-1198` says that path carries no caller identity). It describes `ctx.user`; it does not say the engine admits such a context. Left alone. - `carries no` (5), `sees nothing` (1), `anonymous` (11), `bypass` (7), `elevat` (12), `runAs` (20), `system context` (3), `resolve[sd] no` (3), `no resolvable` (1), `unscoped` (4): every hit is either an explicit elevation the engine admits (`isSystem`, `runAs: 'system'`: `objectstack-automation/SKILL.md:183`, `references/examples-flows.md:23`, `:88` teach `runAs: 'system'` for a run with no trigger user, which is the D5-correct prescription) or a different subject (anonymous records, sharing `bypass`, public forms, a search axis, a time dimension). Control: `isSystem` hits 3 files (`data-hooks.md`, `objectstack-query/SKILL.md`, `objectstack-query/evals/filters-pagination-search.json`), matching the seat's reading. No hit lands in `skills/objectstack-formula/SKILL.md` (PR objectstack-ai#22347) or `skills/objectstack-ui/references/react-blocks.md` (PR objectstack-ai#22322); neither file is touched. ## Evals `skills/objectstack-query/evals/filters-pagination-search.json`: the 2 `isSystem` hits are both in case `id: 5`, whose `expected_output` prescribes `context: { isSystem: true }` and whose `must_contain` is `["context", "isSystem: true", "limit: 1"]`. `flowRunId` / `provenance` / `envelope`: 0 hits across `evals/`. The old line is not asserted; the eval is unchanged and stays true. ## Budget — net-line budget 0, cap +1: spent +1 | reading | before (`16096e8d7`) | after (`f4e5170b`) | |---|---|---| | `skills/objectstack-query/SKILL.md`, lines | 400 | 401 (+1) | | whole package, all `skills/*/SKILL.md`, lines | 4409 | 4410 (+1) | | `SKILL.md` tokens, `ceil(bytes/4)` (ceiling 5552) | 4109 | 4128 (headroom 1424) | Why not 0: the replaced clause (`{ flowRunId }` for provenance alone) was 37 characters; the replacement that states the admitted shape, the refusal code and the ADR is 112. A 0-net fit required deleting content — the envelope's "(identity, tenant, transaction)" or the principal gloss — and re-wrap is not a currency, so the one line the cap allows was spent instead. `scripts/pm/check-skill-line-ratchet.mjs` does not cover the published root (its header says so); the token ratchet is the binding one and it is green with headroom. ## Changeset `skills/**` is in no released package's `files[]`: no `package.json` under `packages/` names a `skills` path and none carries an entry that escapes its own directory (both measured over every `packages/**/package.json`); the catalog reaches customers through `npx skills add objectstack-ai/objectstack/skills` from this repository, which `packages/create-objectstack` invokes at scaffold time rather than bundling (`src/created-summary.ts:31`). Positive control: the same old sentence in `packages/spec/src/kernel/execution-context.zod.ts:501` IS inside spec's `files[]` (`src/**/*.zod.ts`), which is why objectstack-ai#22327 carries a changeset and this PR does not. No `.changeset/*.md`; `skip-changeset` is the repo's skip form. ## Gates — merge base `16096e8d7`, final commit `f4e5170b` `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 24 families from the worktree change set (1 path); each ran with its exit code captured before any pipe; `--ran` reconciliation: "24 derived, 24 run, 0 NOT-MEASURED, 0 UNRUN", with every line carrying its exit code ("a DERIVED zero — all 24 recorded an exit code and none of them is 3"). All 24 exit 0: - `node scripts/check-skills-token-ratchet.mjs` (+ `--self-test`): "skills/objectstack-query/SKILL.md is 4128 tokens (ceiling 5552; headroom 1424)". - `pnpm --filter @objectstack/lint run check:doc-formula-expressions`, after building its prerequisite under the verify lock (`pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint`, `VERDICT command-exit 0`, held 105s, waited 0s). - `pnpm --filter @objectstack/spec run check:skill-docs` (reads frontmatter only; unchanged), `pnpm check:doc-authoring`, `pnpm check:skill-identifier-liveness`, `pnpm check:skill-frame-sync`, `pnpm check:skill-compatibility`, `pnpm check:corpus-claim-drift`, `pnpm check:cross-package-test-inputs`, `pnpm check:agent-test-spelling`, `pnpm check:role-word`, `pnpm check:gitlink-declared`, `pnpm check:driver-memory-census`, `pnpm check:refd-timer-probe`, `pnpm check:watch-hint-literal`, `pnpm check:pm-governed-merges`, `pnpm check:nul-bytes`, `node scripts/check-ci-filter-parity.mjs`, `node scripts/check-closing-keyword-parity.mjs` (+ `--self-test`), `node scripts/check-comment-mask-corpus.mjs`, `node scripts/check-doc-route-spelling.mjs --advisory` (+ `--self-test`). Beyond the derivation: `pnpm check:pm-skill-ratchet` exit 0 (the published root is outside its map, as its header states); `pnpm --filter @objectstack/spec run check:skill-refs` exit 0 ("9 generated files in sync", nothing to regenerate); a control-byte scan over the edited file finds none. The 52 artifact-roster families, the 11 declared wide-population families and the type-check lanes the derivation lists outside the derived total are CI's runs on this PR; `pnpm lint` (repo-level eslint) was not run locally — the diff is one Markdown file. ## Acceptance notes - Governed surface, Tier H (`skills/**`): this PR stays draft; landing waits for an authorized approval, and the dispatch names the contract-review tier as mandatory on this path. - Commit identity: this cloud container cannot mint the fleet identity (`OS_FLEET_APP_ID` / `OS_FLEET_PRIVATE_KEY` are unset and the relay hands out no token for `git`), so the one commit carries the worktree's harness identity; every later commit on this branch keeps that same identity. - Observed, not filed (code comments are objectstack-ai#22345's lane, PR objectstack-ai#22357): `packages/runtime/src/sandbox/body-runner.ts:979-984` still says "A caller that has no context to give gets the same identity-less behavior as before", a pre-D5 reading in a code comment. - Historical `CHANGELOG.md` entries ("A run with no principal now passes provenance alone.") are release-owned records of what shipped and are not edited. ## 维护者速读(草稿) - **改了什么:** 已发布的 `objectstack-query` 技能里,"Execution Context" 一节的一段话。原来教"只传 `{ flowRunId }` 做溯源"也是合法的执行上下文;现在改为:系统读传 `{ isSystem: true }`,否则传调用者自己的上下文(带用户、岗位或权限集),两者都没有则引擎拒绝(`403 PERMISSION_DENIED`,ADR-0096 D5)。只改这一段,净增 1 行(预算 0、上限 +1)。 - **为什么改:** ADR-0096 D5 严格模式(PR objectstack-ai#22297)落地后,`plugin-security` 在全部站点拒收"无 principal 且非 system"的上下文。按旧句写出来的上下文会被引擎直接拒绝,而这份技能是通过 `npx skills add` 装进客户项目的,AI 作者先读到它、再撞上 403。同时按卡片要求对全部 `skills/**` 做了枚举:只有这一行教旧读法,其余命中都是 `isSystem`/`runAs:'system'` 这类显式提权或别的主题;评测文件没有断言旧句。 - **风险与代价(含回滚):** 纯文本改动,不碰代码、不发包、无 changeset(`skills/**` 不在任何已发布包的 `files[]` 内)。措辞按 `main` 上的 D5 契约原文写;PR objectstack-ai#22327 仍是 draft,它落地后两边说法一致。回滚即还原这一个文件的一次提交。 - **席位意见:** - **你要做的:** 看一眼第 65-70 行这一段表述是否认可,认可就给一个批准。 --- _Generated by [Claude Code](https://claude.ai/code/session_01JmWtcHfGbC4ncw4GFKWuRA)_ 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>
Fixes #22093
Clause-②: no
The card's last remainder, (a): the form view
submitBehaviordescribe inpackages/spec/src/ui/view.zod.tsno longer ends in a ruling date. Help text only: no key, type, accept/reject or export change, with apatchchangeset for@objectstack/spec. The diff reachesskills/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 comment6047193742).What changed
FormViewSchema.submitBehaviordescribe (view.zod.ts), the property help that Studio, the reference docs and the published skill show for this keyredirectarm,urlis relative-only and interpolates only declared record fields as{{record.field_name}}, URL-escaped (ruled 2026-08-11)."redirectarm,urlis 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 (#7496) now live in the code comment directly above the describe, in the form PR #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:generatedafter the edit named exactly two stale artifacts,check:react-blocksandcheck:docs.check:generated --fixregenerated those two and nothing else, and a re-run reads all 15 artifacts up to date.skills/objectstack-ui/references/react-blocks.mdsubmitBehaviorrow)content/docs/references/ui/view.mdxsubmitBehaviorrowscontent/docs/references/api/protocol.mdxsubmitBehaviorrowsPublished-skill readings (
skills/**is token-ratcheted bycheck-skills-token-ratchet, countingceil(utf8 bytes / 4)), at4e4111ca0→ this head:react-blocks.md: 115 → 115 lines; 13,633 → 13,624 bytes; 3,409 → 3,406 tokens.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.tsgains section 6. It reads thesubmitBehaviorhelp from the published JSON Schema projection (projectPublishedJsonSchema, the projectiongen:react-blocksandgen:docsrender from) and asserts: the positive control (the sentence still namesPost-submit behavior, theredirectarm,relative-only,{{record.field_name}}, and ends on "URL-escaped."), that it matches neither the file'sRULING_DATEpattern nor a tracker id, and that the source node's.descriptionequals the projection.Reverse verification (the fix committed first; one-off, no file kept):
view.zod.tsrestored to its4e4111ca0bytes withgit restore --source(tree only; blobe60abeaa9== 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 withgit checkout HEAD --(blob48b21f8d9== HEAD;git diff HEADempty; porcelain clean), 54 passed. The pin imports the schema fromsrcby relative path, so no dist leg applies.Verification (at
acba7086d, underos-verify-lockon a shared box)pnpm --filter @objectstack/spec build: exit 0 (held 128s).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 jsonon the two changed TS files: 2 files, 0 errors, 0 warnings. Population:eslint.config.mjsfiles: **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}minusNEVER_LINTED; noparserOptions.project(no type-aware linting), so this diff cannot move the verdict on any untouched file.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackatacba7086dderived 114 commands (6 paths, +67/−16 vs merge base4e4111ca0). Each ran with its exit code captured before any pipe: 110 exit 0 (three docs gates,check:doc-formula-expressions,check:doc-security-postureandcheck:docs-transcript-drift, first refused on an unbuilt@objectstack/lint; afterturbo run build --filter='@objectstack/lint...'under the lock they ran green); 3 exit 3 PREREQUISITE NOT MET, read from CI (check:skill-examplesneeds the client-react dist,check:dual-build-cjs-loadsandcheck:lean-entry-closureneed every package's dist);check:type-check-debtclaimed NOT-MEASURED (its re-measure is a whole-workspace build; CI).--ranreconciliation: 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 withPR_BODYset to this body: exit 0);check:published-readme-exportsexit 3 (every package's dist; CI).维护者速读(草稿)
改了什么: 表单视图
submitBehavior属性的帮助文案(Studio 属性面板、参考文档、发布的objectstack-ui技能里显示的是同一句)不再以「(ruled 2026-08-11)」结尾;规则本身一字未改。裁决日期与 #7496 进了代码注释。react-blocks.md、view.mdx、protocol.mdx的对应行由生成器重生成,react-blocks.md那一行因此不再被截断。为什么改: 这是 #22093 的最后一项:作者看到的帮助文案里不该出现内部裁决日期。其余 22 行已在 PR #22125 落地;这一行投影到受管的发布技能目录
skills/**,所以单独成 PR、等维护者批准。风险与代价(含回滚): 纯文案,不改接受/拒绝行为、键、类型或导出;
@objectstack/specpatch 变更集。react-blocks.md行数不变、净减 9 字节。回滚 = revert 本 PR。席位意见: (席位填写)
你要做的: 批准(approve)本 PR 一次;之后由席位落地。
Acceptance notes
packages/spec/scripts/lib/generated-output.tsrecords that the governed-merge register carves generator-owned files insideskills/**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