Skip to content

fix(spec): the submitBehavior property help carries no ruling date - #22322

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-22093-remainder-a-submit-behavior
Oct 9, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/issue-22093-remainder-a-submit-behavior

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #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 (#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: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)」结尾;规则本身一字未改。裁决日期与 #7496 进了代码注释。react-blocks.md、view.mdx、protocol.mdx 的对应行由生成器重生成,react-blocks.md 那一行因此不再被截断。

为什么改: 这是 #22093 的最后一项:作者看到的帮助文案里不该出现内部裁决日期。其余 22 行已在 PR #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

claude added 2 commits October 8, 2026 15:30
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
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 1 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/objectui/concept.mdx (via FormViewSchema (symbol, a top-level const))
  • content/docs/protocol/objectui/index.mdx (via FormViewSchema (symbol, a top-level const))
  • content/docs/protocol/objectui/layout-dsl.mdx (via FormViewSchema (symbol, a top-level const))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-3.mdx (via FormViewSchema (symbol, a top-level const))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 4e4111ca054fe995b5a08a07d273cad18a749ef7 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 9c36a38bd1fc2f2745f1a51aa8c4091b82fcbbaf — the merge of head acba7086d75db27dd45ce54d156ea219cd6ff9fe into base 4e4111ca054fe995b5a08a07d273cad18a749ef7, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 4e4111ca054fe995b5a08a07d273cad18a749ef7 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

domain:spec seat 3 (#18883) · os-justin · session session_01RPo7FUd6bSnAfkWMAKi848 · 2026-10-08T16:49Z · 席位对照 acba7086d 的 diff 校正了正文草稿。

改了什么: 表单视图 submitBehavior 属性的帮助文案去掉了结尾的「(ruled 2026-08-11)」,规则本身一字未改。Studio 属性面板、参考文档和发布出去的 objectstack-ui 技能显示的都是这一句。裁决日期和 #7496 挪进了代码注释。三份投影文件都由生成器重新生成,每处改动都只是删掉这个括号:react-blocks.md 1 行(原先在 160 字处被截断,现在完整)、view.mdx 5 行、protocol.mdx 2 行。

为什么改: 这是 #22093 的最后一项。作者看到的帮助文案里不该出现内部裁决日期。其余各项已由 PR #22125、#22309 落地。这一行会投影进受管的发布技能目录 skills/**,所以单独成 PR,等你批准。

风险与代价(含回滚): 只改文案,不改接受或拒绝行为,也不改键、类型和导出。@objectstack/spec 发 patch。react-blocks.md 行数不变,净少 9 字节。新增的钉子测试从已发布的 JSON Schema 投影读这句帮助文案,在旧字节上反向验证为红。回滚就是 revert 本 PR。

席位意见: 建议批准。

  • 席位 ACCEPT 见 6064160628。
  • 契约复审 PASS,见 6064355753。
  • acba7086d 上 39 项检查已全部结束:34 项成功,5 项按预期跳过,没有失败。

你要做的: 在本 PR 上批准(approve)一次,之后由席位落地。你也可以直接合并。


Generated by Claude Code

akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 9, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 9, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: author-visible describe/refusal strings carry internal references (II18nService.getDefaultLocale(), a ruling date, manifest.id, com.steedos.crm)

3 participants