Skip to content

feat(spec,lint): a form's thank-you panel names the record's engine-generated fields, and the public submit answer has a declared shape - #28440

Merged
objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/project-thread-i0l9sb
Oct 31, 2026
Merged

objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/project-thread-i0l9sb

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Part of #28005

Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up owed — content/docs/ui/public-data-collection.mdx and content/docs/ui/forms.mdx (the thank-you panel's {{record.field_name}} slot, taught once the server fills the answer and the Console fills the tokens; teaching it now would advertise a panel that still prints the token text); no skills/** page names the thank-you panel's text (git grep -n "thank-you" -- skills/ is empty)

domain:spec#1 · os-tesla · session_01L9qibwKTredm7FjB4Z6DeX

This is the spec half of ruling B (ruling record 6545210989). The server half (POST /forms/SLUG/submit fills fields) and the ui half (FormPage.tsx fills the tokens) follow on their own cards, which the seat files once this PR is up.

维护者速读

  • 改了什么:公开表单的「提交成功」面板(thank-you 的 title / message)可以用 {{record.字段名}} 引用刚提交的那条记录的字段,但只允许引擎在插入时自动生成的字段(目前就是自动编号 autonumber,比如案件号)。同时在 spec 里声明了公开表单提交接口的返回形状:id,加上面板引用到的那几个字段,别的什么都不带。
  • 为什么改:web-to-case 的访客提交后看不到自己的案件号。按裁决 B,只放引擎生成的字段,因为其它字段的值可能是 hook 从匿名访客无权读取的记录里算出来的,不能回显给匿名页面。
  • 风险与回滚:低。引用写法和 redirect 的 url 是同一个记号;写错形状({record.x}、{{ record.x }}、{{case_number}})在解析时被拒并说明正确写法;引用了非自动编号字段,在 os validate / os build / os lint 和 view、page 的发布门被拒,并点名字段和类型。现有示例、hotcrm 里没有任何面板文字带花括号(实测为零)。回滚即 revert。
  • 先看:packages/lint/src/validate-thank-you-record-fields.ts(判定规则,只读 RUNTIME_OWNED_FIELD_TYPES);packages/spec/src/ui/view.zod.ts(THANK_YOU_TEXT_RE、checkThankYouText、thankYouRecordFieldNames);packages/spec/src/api/protocol.zod.ts(PublicFormSubmitResponseSchema)。
  • 两分钟验证:pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/validate-thank-you-record-fields.test.ts,预期 16/16 通过:自动编号字段放行,文本字段、公式字段、未声明字段、系统列各被拒并点名类型;把类型集合在测试里放宽一种类型即放行,无需改规则。
  • 需要你做的:批准三个新名字(见下文 Names for approval),其余无,席位落地。

What changes

1. The slot (@objectstack/spec, ui/view.zod.ts)

The thank-you member's title and message stay z.string(): the renderer receives the text the author wrote. Each now carries a .regex(THANK_YOU_TEXT_RE): every brace in the text must belong to a well-formed {{record.field_name}} token. That is the same token the redirect member's url takes (SUBMIT_REDIRECT_URL_TOKEN_RE), so there is one grammar and no second dialect.

  • A malformed token is refused on its own terms, in the style of checkSubmitRedirectUrl. checkThankYouText words the refusal from the input: it names the member (title or message), quotes the offending text, and gives the one spelling ({{record.case_number}}). It also says braces belong to that token alone, and that only an engine-generated field may be named.
  • A regex, not a refine. A .regex() projects into the published JSON Schema as a pattern, so the file carries the shape too. That means no new x-dropped-refinements site and no dropped-refinements.baseline.json row. The two alternatives in the pattern start on disjoint characters, so matching is linear.
  • thankYouRecordFieldNames(submitBehavior) is the one reading of which fields a panel names: title then message, each name once, in order of first appearance, and [] for any other kind. It is a pure function of the block. The lint rule judges exactly these names, and the server half fills exactly these fields.
  • The two members gain a .describe() that states the slot and its limit. The union's own describe, which is projected into the governed skills/objectstack-ui/references/react-blocks.md, is unchanged, so this PR touches no governed surface.

2. The authoring check (@objectstack/lint, validate-thank-you-record-fields.ts)

  • Where it lives. I measured whether the redirect token's "declared record fields" rule is cross-checked anywhere. It is not: no lint rule reads submitBehavior.url, and the view.zod.ts docblock only names the reference-integrity family as the right layer. The nearest existing site that resolves a form's object fields is validate-public-form-file-fields.ts. The new rule sits beside it, with the same registration: gating, all three commands, the runtime publish gate. It resolves the form's object with the same anonymousFormObjectName the anonymous doors use.
  • One source. A name is admitted only when its field's type is in RUNTIME_OWNED_FIELD_TYPES (data/field.zod.ts, today autonumber). The rule spells no field type of its own. It takes the set as a defaulted parameter only so a test can widen it as a double: the spec dist is externalised in lint's tests, so it cannot be module-mocked.
  • What is refused, naming the field, its type and the reason (a value a hook may have set from records the anonymous grant cannot read):
    • a declared field of any other type (text, formula, …);
    • a registry-injected column (created_at, owner_id, id), which is injected, not engine-numbered;
    • a name the object does not declare (the panel would print nothing).
  • Which forms. Every form that carries a thank-you panel, public or not: the bare form-view shape, the nested form, every formViews entry, the flattened config of a viewKind: 'form' item, and a page object-form component (properties.submitBehavior, object from properties.objectName, else the page walk's binding). The slot is one slot with one admission. A form that is internal today becomes public by flipping its sharing, and that is a write this rule would otherwise never see again.
  • Runtime publish gate: runtimeTypes: ['view', 'page']. Both snapshots carry objects as context, which is every input the rule reads. object is not declared, for the reason the file-field rule gives: an object write's snapshot carries no views or pages.
  • A form whose object does not resolve in the stack is not judged, the same boundary the file-field rule draws.

3. The answer shape (@objectstack/spec, api/protocol.zod.ts)

I measured whether any spec schema names the POST /forms/:slug/submit answer. None does: rest-route-ledger.ts lists the route with no responseSchema. PublicFormSubmitResponseSchema now sits next to CreateDataResponseSchema:

  • id: string;
  • fields (optional): a map from field name to a string or number value, holding exactly the named fields' stored values, present only when the panel names at least one;
  • .strict(), so a key beyond these two is a conformance failure rather than a silent widening of what an anonymous caller is shown.

The server half fills it and names it as the route's responseSchema. ⛔ packages/rest/src/form-routes.ts is untouched here.

Names for approval

The order asks for Tesla's approval of every new name. These are proposals; renaming before merge is a mechanical edit.

Name Kind Where
PublicFormSubmitResponseSchema (type PublicFormSubmitResponse) REST answer schema @objectstack/spec/api
fields key on that answer the map of named field values
thankYouRecordFieldNames pure helper @objectstack/spec/ui
thank-you-record-field / validateThankYouRecordFields lint rule id / function @objectstack/lint

THANK_YOU_TEXT_RE and checkThankYouText are module-private.

Tests

  • Parse (packages/spec/src/ui/view-submit-thank-you-record-slot.test.ts, 15):
    • A message with {{record.case_number}} parses, and the text arrives verbatim. Title tokens, several tokens, token-free text and an empty panel all parse.
    • Refused, each naming the offender and the one spelling, at the member's path: {record.x}, {{ record.x }}, {{case_number}}, an uppercase segment, a dotted path, and a stray }.
    • The refusal carries no tracker id or date. The published JSON Schema carries the shape as a pattern.
    • thankYouRecordFieldNames reads title then message, each once, and reads only well-formed tokens. It answers [] for other kinds and for non-objects.
  • Answer schema (packages/spec/src/api/public-form-submit-response.test.ts, 4): it accepts { id } and { id, fields: { case_number } }. It refuses another top-level key (unrecognized_keys), a missing id and a non-scalar value.
  • Authoring check (packages/lint/src/validate-thank-you-record-fields.test.ts, 16):
    • On a fixture object, an autonumber token passes. text and formula tokens are refused, and the message names the field and its type. An undeclared name and the injected created_at / id are refused too.
    • Every form shape is judged: form, formViews.*, the viewKind: 'form' config, the bare form view, a public form, and a page object-form. A list-shaped fields is read, and the object resolves from the view.
    • One source: widening RUNTIME_OWNED_FIELD_TYPES in a test double (+ 'formula') admits the formula token with no other edit. The rule source imports the set from @objectstack/spec/data and spells no 'autonumber'.
    • Registration is pinned: gating, three commands, ['view', 'page']. The runtime publish gate refuses a view write naming a text field and admits the same write naming the autonumber field.
  • Existing pins updated:
    • The publish-gate rule rosters name the new rule in authoring-rule-wiring.test.ts, runtime-gate.test.ts (3 sites) and runtime-gate.page-visibility-predicate-writes.test.ts (2 sites).
    • type-alias-convention.pin.test.ts pins PublicFormSubmitResponseSchema as isomorphic (805 → 806, with the reason in the count's ledger comment).
Ablation (one-time, restored)

Done through node scripts/ablation-replace.mjs on packages/lint/src/validate-thank-you-record-fields.ts. The suite imports the rule from src/, so no build sits between mutation and run. The type gate is the anchor if (type !== undefined && admitted.has(type)) return undefined;, which went x1 → x0, replaced by an unconditional return undefined;.

  • Blob 014ba032cba7 → e040658efab9.
  • validate-thank-you-record-fields.test.ts: 9 failed | 7 passed. The text and formula refusals, every form-shape case, the page case, the one-source widening case and the runtime gate's refusal all went red.
  • Restored: blob 014ba032cba7 = HEAD, git diff HEAD empty.

Acceptance notes

  • A brace in panel text is now refused unless it is the token. Before this PR, title / message accepted any text. Text with a brace that is not a well-formed token (a literal {, {{ x }}) now fails to parse. The order asked for this, mirroring the redirect refine. I measured the corpus: no authored thank-you title or message in examples/, the non-test packages/ sources or hotcrm contains a brace. I copied the claim's declaration lines (yes (widening), added) unchanged. The seat decides whether this face changes the Clause-② arm.
  • {{record.id}} is refused. id is not a RUNTIME_OWNED_FIELD_TYPES type. The answer already carries id at the top, so admitting it would be a separate decision.
  • Translations are not judged. The i18n checks (validate-translation-references.ts) read only the key address of …submitBehavior.title/message, never its text, so a token in the text breaks no translation check (measured). A translated panel could name a field the base text does not name. The server half fills only the base text's names, so such a token would print as written; it never leaks. Whether to judge translated text is the ui/server cards' question.
  • An object write is not re-judged. An object write's snapshot carries no views or pages, so retyping a named field under a published panel is caught by the CLI commands and by the form's next publish. This is the same boundary as the file-field rule.
  • Until the server and ui halves land, a panel that names a field shows the token text verbatim. The changeset says so.

Verification

The branch head is eeb6adb2b; each line names the commit it ran on where that differs. Heavy steps ran under scripts/pm/os-verify-lock.sh, and every exit code was captured before any pipe.

  • Package suites:
    • pnpm --filter @objectstack/spec test on 9d1b0b28: 774 of 775 files passed (22847 tests, 1 todo). The one red was the isomorphic-pin count (805 → 806), fixed in this PR, and that file then passed 3/3.
    • pnpm --filter @objectstack/lint test on 9d1b0b28: 183 files, 7698 passed, 5 skipped.
  • Typecheck on 47c5f03c4: pnpm --filter @objectstack/lint typecheck && pnpm --filter @objectstack/spec typecheck exit 0.
  • Generated artefacts on 1bc2dd670: pnpm --filter @objectstack/spec check:generated --fix rebuilt spec and regenerated api-surface, export-origins, declaration-map, the reference docs and the strictness ledger. It then re-checked all five green. A later check:generated exit 0 is in the rerun below.
  • Derived gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 103 commands on 9d1b0b28, and 104 on eeb6adb2b (+ check:cli-examples-parity, from the docs edit).
    • First sweep on 9d1b0b28: 91 exit 0.
    • One real finding: check-comment-mask-adoption flagged a private comment stripper in my new lint test. I removed it.
    • Eleven reds read built output and had no build to read: ten exit 3 (prerequisite) and check:dts-closure / check:generated exit 1 (missing or stale dist).
    • After a full turbo run build of ./packages/** and ./examples/**, I re-ran all twelve on b96858477. One more real finding: check:docs-transcript-drift said four CLI transcripts print author-time rules (56), and the registry now derives 57. I updated those four pages, and the gate exits 0 on eeb6adb2b, as does the new check:cli-examples-parity.
    • The final union of all 104 on eeb6adb2b is in the os-dev-report comment on Public form thank-you text cannot name the record it created: no template slot, and the submit answer carries the id only #28005, reconciled with dispatch-gates.mjs --ran.
First sweep, 9d1b0b2 (103)
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-adr-links.mjs :: exit 0
node scripts/check-adr-links.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 1
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dev-prereqs.mjs --self-test :: exit 0
node scripts/check-doc-route-spelling.mjs --advisory :: exit 0
node scripts/check-doc-route-spelling.mjs --self-test :: exit 0
node scripts/check-docs-section-name.mjs :: exit 0
node scripts/check-docs-section-name.mjs --self-test :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/check-section-landing-index.mjs :: exit 0
node scripts/check-section-landing-index.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 3
pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 3
pnpm --filter @objectstack/spec run check:api-surface :: exit 0
pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0
pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0
pnpm --filter @objectstack/spec run check:docs :: exit 0
pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 3
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm --filter @objectstack/spec run check:empty-state :: exit 0
pnpm --filter @objectstack/spec run check:entry-nameability :: exit 3
pnpm --filter @objectstack/spec run check:export-origins :: exit 0
pnpm --filter @objectstack/spec run check:exported-any :: exit 3
pnpm --filter @objectstack/spec run check:generated :: exit 1
pnpm --filter @objectstack/spec run check:liveness :: exit 0
pnpm --filter @objectstack/spec run check:llms-txt :: exit 0
pnpm --filter @objectstack/spec run check:skill-examples :: exit 3
pnpm --filter @objectstack/spec run check:skill-refs :: exit 0
pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0
pnpm --filter @objectstack/spec run check:variant-docs :: exit 0
pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:corpus-claim-drift :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-anchors :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:doc-frontmatter :: exit 0
pnpm check:docs-audit-scope :: exit 0
pnpm check:docs-redirects :: exit 0
pnpm check:docs-single-h1 :: exit 0
pnpm check:docs-spec-enumerations :: exit 0
pnpm check:docs-transcript-drift :: exit 3
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 1
pnpm check:dual-build-cjs-loads :: exit 3
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 3
pnpm check:logger-receiver-detach :: exit 0
pnpm check:merge-driver :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:pm-prior-rulings :: exit 0
pnpm check:published-files :: exit 0
pnpm check:published-readme-links :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:react-page-adapter-contract :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:role-word :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:spec-docblock-symbol-anchors :: exit 0
pnpm check:spec-parsed-alias :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:vendor-version-stamps :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
Rerun after the full build: twelve on b968584, the last two on eeb6adb
node scripts/check-comment-mask-adoption.mjs :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0
pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0
pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0
pnpm --filter @objectstack/spec run check:exported-any :: exit 0
pnpm --filter @objectstack/spec run check:generated :: exit 0
pnpm --filter @objectstack/spec run check:skill-examples :: exit 0
pnpm check:docs-transcript-drift :: exit 1
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:docs-transcript-drift :: exit 0
pnpm check:cli-examples-parity :: exit 0
  • CI: the full farm, and test:repo, are CI's; Test Core runs the latter on the PR.
  • Dogfood: not run. The title carries no ! and the Clause-② arm is not (narrowing).

Generated by Claude Code

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation protocol:ui tests tooling labels Oct 31, 2026
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @objectstack/spec, touching 24 documentable anchor(s). ⚠️ 8 changed file(s) yielded no anchor (packages/lint/src/index.ts, packages/spec/api-surface/api.json, packages/spec/api-surface/ui.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/automation/queues-and-assignment-rules.mdx (via case_intake (literal, a string literal in a comment in ThankYouRecordFieldFinding))
  • content/docs/deployment/validating-metadata.mdx (via AUTHORING_RULES (symbol, a top-level const object), runtimeTypes (symbol, a field of const object AUTHORING_RULES))
  • content/docs/protocol/objectui/actions.mdx (via submitBehavior (symbol, a field of interface PanelSite), submitBehavior (literal, a string literal in viewForms))
  • 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))
  • content/docs/ui/forms.mdx (via submitBehavior (symbol, a field of interface PanelSite), submitBehavior (literal, a string literal in viewForms), /forms/:slug/submit (route, a path literal in a comment on a changed line))
  • content/docs/ui/public-data-collection.mdx (via submitBehavior (symbol, a field of interface PanelSite), submitBehavior (literal, a string literal in viewForms), /forms/:slug/submit (route, a path literal in a comment on a changed line))
  • content/docs/ui/translations.mdx (via submitBehavior (symbol, a field of interface PanelSite), submitBehavior (literal, a string literal in viewForms))

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

  • content/docs/releases/v17/17-1.mdx (via submitBehavior (symbol, a field of interface PanelSite), submitBehavior (literal, a string literal in viewForms))
  • content/docs/releases/v17/17-3.mdx (via FormViewSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-7.mdx (via submitBehavior (symbol, a field of interface PanelSite), submitBehavior (literal, a string literal in viewForms), /forms/:slug/submit (route, a path literal in a comment on a changed line))
  • content/docs/releases/v17/index.mdx (via submitBehavior (symbol, a field of interface PanelSite), submitBehavior (literal, a string literal in viewForms))
  • content/docs/releases/v18/18-2.mdx (via /forms/:slug/submit (route, a path literal in a comment on a changed line))

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
  • 8 changed file(s) yielded no anchor (packages/lint/src/index.ts, packages/spec/api-surface/api.json, packages/spec/api-surface/ui.json, …) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: objectName (symbol, 55 pages)
  • 8 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 212 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 — 168 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 52306d40bdd943a5d55c2396c0985aa4c55fbabb → packageMentionDocs.

Which tree this was computed on

This run read content/docs from d735f366cf73cd1fe06655ba533b5ca555f44542 — the merge of head 8332f94c8e19e2abaac891843bc85d155c970515 into base 52306d40bdd943a5d55c2396c0985aa4c55fbabb, 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 d735f366cf73cd1fe06655ba533b5ca555f44542 && git checkout d735f366cf73cd1fe06655ba533b5ca555f44542
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 52306d40bdd943a5d55c2396c0985aa4c55fbabb 8332f94c8e19e2abaac891843bc85d155c970515 && git checkout -B drift-repro 52306d40bdd943a5d55c2396c0985aa4c55fbabb && git merge --no-ff 8332f94c8e19e2abaac891843bc85d155c970515

node scripts/docs-audit/affected-docs.mjs --json 52306d40bdd943a5d55c2396c0985aa4c55fbabb

⚠️ 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 52306d40bdd943a5d55c2396c0985aa4c55fbabb → 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

director#2 · os-tesla · session_01L9qibxdiybU8ehPKBroYE3

Brief-claimed-by: session_01L9qibxdiybU8ehPKBroYE3 · batch #435 · 2026-10-31T16:38Z

Director seat (the maintainer's decisions thread, 2026-10-31). The four names in this PR's "Names for approval" table are presented to the maintainer in batch #435 (two items: these names, and the connection-door agent-caller finding from #28427's report 6545987804). The maintainer's answer follows in a ruling comment here; renaming before merge stays a mechanical edit.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head eeb6adb2b1e13c5fd583e4d9bde9e6dd2755486a · 2026-10-31T16:41Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: eeb6adb2b1e13c5fd583e4d9bde9e6dd2755486a
Local-runs: none
Scope: packages/spec/src/ui/view.zod.ts (THANK_YOU_TEXT_RE, checkThankYouText, thankYouRecordFieldNames, thank-you title/message regex + describe), packages/spec/src/api/protocol.zod.ts (PublicFormSubmitResponseSchema, type), packages/lint/src/validate-thank-you-record-fields.ts (new rule thank-you-record-field), authoring-rules.ts registration, lint index exports, 3 new test files + 5 roster/pin tests, generated spec surfaces (api-surface, authorable-surface, declaration-map, export-origins, json-schema.manifest), references pages, the strictness ledger count, 4 CLI transcript pages (56 → 57 author-time rules), the changeset. 29 files, +797/−18. Part of #28005 (ruling B 6545210989; claim 6545269751; dev report 6546169219; seat ruling on the brace refusal 6546213062; split: server #28443, ui #28444).
Inputs: card #28005 body + ruling + claim + dev report + split note, the PR body (incl. the Names-for-approval table and the deviations), the PR diff at this head, git grep/git show on origin/main at the merge-base 5da3a8ce (main has since touched only one generated line of component.mdx; merge clean), the hotcrm checkout, check-runs (read 2026-10-31T16:43Z: all 50 latest-per-name check-runs completed, 39 success + 11 skipped, none failed, none running) — 50 checks. Nothing built or run here; the seat's numbers (spec 15/15 + 4/4, lint 16/16, suites, ablation 9 failed / 7 passed, 104 derived gates) are the dev report's.

① Derived judgments

  • The slot is one grammar. THANK_YOU_TEXT_RE admits any text whose braces all belong to {{record.<snake_case>}}; the token is the same SUBMIT_REDIRECT_URL_TOKEN_RE the redirect behavior's url reads (main view.zod.ts L4389, /g), so thankYouRecordFieldNames (matchAll, slice(9, -2) = the 9 characters of {{record.) and the regex cannot disagree on a well-formed token. The .regex() projects as a JSON Schema pattern, so no dropped-refinements row — consistent with the baseline file not moving. The error callback words the refusal from the input (member, offender, the one spelling), the pattern checkSubmitRedirectUrl set.
  • The lint rule reads the spec's one set. validateThankYouRecordFields admits a name only when the object's declared field type is in RUNTIME_OWNED_FIELD_TYPES (main field.zod.ts L304: Set(['autonumber'])), spelling no type of its own (the set is a defaulted parameter only so the test can widen it — the ablation and the "one source" test cover it). Injected system columns (id, created_at, owner_id) and undeclared names are refused with distinct reasons; a form whose object does not resolve is not judged (the file-field rule's boundary). Sites: bare form view, form, every formViews.*, viewKind: 'form' config, and page object-form components via walkPageComponents with properties.objectName else the walk's binding; the object is resolved through anonymousFormObjectName (main anonymous-form-intake.ts L86), the doors' own reading. Registered gating, all commands, CLI_AND_RUNTIME, runtimeTypes: ['view', 'page'] — the five roster pins and the transcript count move accordingly.
  • The answer shape is strict and minimal. PublicFormSubmitResponseSchema = { id: string, fields?: Record<string, string | number> }.strict(), beside CreateDataResponseSchema; the describe says fields holds exactly the named engine-generated fields and is present only when the panel names one. Nothing on main named the POST /forms/:slug/submit answer before, so this is a new declaration (Metadata-protocol: added); the route's responseSchema binding and the fill are the server half (server: the public form submit answer carries the record fields its thank-you panel names (#28005 server half) #28443), packages/rest/src/form-routes.ts untouched here as the claim ordered.
  • Declarations. Clause-②: yes (widening) with @objectstack/spec + @objectstack/lint minor is right for the face's main change (a new slot, a new exported schema, a new helper, a new rule). The one narrowing face — any brace in title/message that is not the token now fails to parse — is declared in the PR body and ruled to stay by the seat (6546213062); verified corpus: no thank-you title/message in examples/, non-test packages/, or hotcrm carries a brace (hotcrm has no thank-you panel at all). So no shipped document changes meaning; the refusal is actionable and names the one spelling.
  • Isomorphic-pin and surfaces. The type-alias pin moves 805 → 806 with the new PublicFormSubmitResponse; api-surface/export-origins carry the new schema, type, and thankYouRecordFieldNames; lint index exports the rule, its id and finding type. The generated references pages carry the two new describes.

② Rulings cited

  • Public form thank-you text cannot name the record it created: no template slot, and the submit answer carries the id only #28005 ruling 6545210989 (Tesla 「同意」 15:10Z, item 1): B — slot admits only engine-generated fields; answer = id + exactly the named fields; order spec → server → ui. Seat ruling 6546213062: the brace refusal stays, declared as a narrowing face in the PR body. Open with Tesla (not blocking this record): the four proposed names (PublicFormSubmitResponseSchema/PublicFormSubmitResponse, fields, thankYouRecordFieldNames, thank-you-record-field/validateThankYouRecordFields) await his approval per the order; a rename before merge is mechanical and would need a delta record. Tier S, the seat lands after that approval.

③ Narrowing / widening

  • Widening: the slot, the exported schema and helper, the lint rule. Narrowing: non-token braces in thank-you title/message are refused at parse (corpus 0, declared). {{record.id}} is refused by the rule since id is injected, not autonumber — the answer carries id at the top anyway; a separate decision if wanted.

④ Behaviour / flags

Implemented-by: claude/project-thread-i0l9sb
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz
VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

director#2 · os-tesla · session_01L9qibxdiybU8ehPKBroYE3

Ruling: batch #435 item 1 · letter A · maintainer 「同意」 2026-10-31T20:30Z

Director seat (the maintainer's decisions thread). Presented in batch #435 (brief-claimed here at 6546190901; detailed letter in the project's decision doc, batch 4). The maintainer answered the batch at 2026-10-31T20:29Z, verbatim: 「同意」 = every item at the seat's recommendation, which for this PR is A: all four proposed names approved as written.

What the ruling binds: PublicFormSubmitResponseSchema (type PublicFormSubmitResponse) in @objectstack/spec/api; the fields key on that answer (a map of the panel-named fields' stored values, .strict() beside id); thankYouRecordFieldNames in @objectstack/spec/ui; the lint rule id thank-you-record-field / validateThankYouRecordFields in @objectstack/lint. The record-for-fields alternative (B) was put to the maintainer and not taken: the key holds named fields, not a record. The server half and the ui half of #28005 reuse these four names.

Execution: the dispatching seat (spec seat 1) proceeds — contract review PASS 6546248260 already stands on this head — and lands; Ruling-ref: names this comment on the follow-up cards. Label strokes are the seat's.

…-i0l9sb

# Conflicts:
#	packages/spec/src/type-alias-convention.pin.test.ts
check:generated --fix on the merged tree: references, api/ui surfaces,
export origins, declaration map, json-schema manifest and the api
strictness-ledger count.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibwKTredm7FjB4Z6DeX
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head 8332f94c8e19e2abaac891843bc85d155c970515 · 2026-10-31T21:02Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review (delta)

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 8332f94c8e19e2abaac891843bc85d155c970515
Local-runs: none
Scope: the delta from the reviewed head eeb6adb2 (PASS 6546248260) to 8332f94c: the merge commit c1d493ce (origin/main in) and the regeneration commit 8332f94c (12 generated files, +301/−52). Ruling 6548758413 (batch #435 item 1, letter A: all four names approved as written) binds the names the reviewed head already carried; no rename was made.
Inputs: git show --cc c1d493ce (one conflict-resolution hunk: packages/spec/src/type-alias-convention.pin.test.ts, 806 → 807 in the count, the prose and the toHaveLength, with the [#28005] note kept), git show --stat 8332f94c (references index.mdx / ui/component.mdx / ui/view.mdx, the strictness ledger counts/api.md, api-surface, authorable-surface, declaration-map, export-origins, json-schema manifest: generated by repo tooling), the PR-only interdiff (this PR's diff against its own merge-base before and after, hunk headers ignored: 25 lines each way, every one a rebased count — schema total 1575 → 1576, API Protocol 453 → 454, api/ 463 → 464, "140 more" → "141 more", pins 806 → 807 — or a generated import / table line that now also names main's PublishPackageDraftsRequest (#28447); no byte of view.zod.ts, protocol.zod.ts, the lint rule, the tests or the changeset moved), merge-tree against origin/main clean, check-runs (read 2026-10-31T21:04Z: all 50 latest-per-name check-runs completed, 39 success + 11 skipped, none failed, none running) — 50 checks.

Delta judgment

  • Merge of main plus regeneration only. The two hand-rebased files are count carriers: the pin test's alias count follows feat(spec): declare the publish-drafts request body, so the change note is part of the published API contract #28447's 806 on main (this PR's PublicFormSubmitResponseSchema is the 807th), and the strictness ledger's api/ row follows main's 463. Neither changes what the PR declares or refuses.
  • Every judgment of 6546248260 (one thank-you grammar sharing SUBMIT_REDIRECT_URL_TOKEN_RE, the lint rule reading RUNTIME_OWNED_FIELD_TYPES, PublicFormSubmitResponseSchema strict with id + fields, Clause-②: yes (widening) + Metadata-protocol: added, spec + lint minor) stands on this head unchanged, and the four names now carry the maintainer's letter A.

Implemented-by: claude/project-thread-i0l9sb
Reviewed-by: triage#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

VERDICT: PASS (delta)

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm

维护者速读

席位核对 · ACCEPT · domain:spec seat 1 (#6017) · 2026-10-31T21:05Z

Head 8332f94c8e19e2abaac891843bc85d155c970515. Card #28005: ruling B 6545210989, claim 6545269751, os-dev-report 6546169219, seat ruling on the brace refusal 6546213062, names ruling A 6548758413. The maintainer's ruling, verbatim: 「按 pm-dispatch 审过的pr你可以自主落地」.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 31, 2026 21:05
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 31, 2026 21:05
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 31, 2026
Merged via the queue into main with commit 9b1dc99 Oct 31, 2026
59 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-i0l9sb branch October 31, 2026 21:27
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant