Skip to content

feat(lint): form layout, visibility and page-sort advisories carry a localized user sentence - #33143

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-168lt2
Nov 13, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-168lt2

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Fixes #32840

Clause-②: no
Metadata-protocol: none

维护者速读

What changes

Layer 1 of #32654 for the page, form, navigation and translation advisories, in the pattern #32655 landed (validate-security-posture.user-text.ts, finding-user-text.test.ts), under ruling record 6819749037 (option C).

Re-measured first. The card's table (9 ids in 8 files, a static-scan upper bound) was checked on origin/main 5b2ec820: each advisory was planted in a whole stack, confirmed by calling its rule, and then every item of that stack was written through runRuntimeAuthoringRules (types object, view, page, app, flow as the stack held them) with the snapshot context the door carries.

Rule file Advisory ids the rule emits Reach the door (write)
validate-form-layout.ts form-field-unknown, form-section-group-unknown (card: 1) both (view)
validate-visibility-predicates.ts visibility-root-mislayered yes (view, page)
validate-page-field-bindings.ts page-field-unprovisioned, page-field-unknown (its skipped grade), page-section-group-unknown (card: 1) page-field-unprovisioned only at the page-sort site in validate-sortable-fields.ts (page); none at this rule's own sites
validate-react-page-props.ts react-page-source-unparseable, react-prop-deprecated, react-prop-typo, react-chart-aggregate-invalid (its warning arm), react-chart-field-unprovisioned (card: 2) none
validate-nav-access.ts nav-object-ungranted none
validate-nav-target-refs.ts nav-target-unresolved none
validate-translation-references.ts translation-option-key-unknown none
validate-translatable-sections.ts translation-section-name-missing none

So 4 ids reach the door and get a sentence; the seat agreed to cover those and name the rest (see Acceptance notes).

packages/lint.

  • Three catalogs, new, beside the rules that own the ids, each in all 11 PLATFORM_LOCALE_TAGS and in the console's terms from ui/packages/i18n (view, page, form, section, field, field group, object, external object, sort, the visibleWhen rule as the record form names it):
    • validate-form-layout.user-text.ts: form-field-unknown (view, object, field) and form-section-group-unknown (view, object, group).
    • validate-visibility-predicates.user-text.ts: visibility-root-mislayered (surface, name, root). One arm per root: data on a runtime view or page (write record.), record on a metadata-editing form (write data.), as the rule's message does; surface picks the noun (view or page).
    • validate-page-field-bindings.user-text.ts: page-field-unprovisioned (page, object, field), addressed to the page whose sort is ignored.
  • Push sites:
    • validate-form-layout.ts sets userTextParams at the section-field site, and adds them to checkSectionGroupRefs' findings at its call site (keyed by each finding's path, which is its reference's). The shared helper in object-field-groups.ts is untouched: its other host emits page-section-group-unknown, which the door never returns.
    • validate-visibility-predicates.ts: checkElement takes the element's owner ({ surface, name }, passed by its three callers) and sets the params at the mislayered-root site only.
    • validate-sortable-fields.ts: checkPageSortDeclaration takes the page name (passed by all five page-sort callers) and sets the params at its page-field-unprovisioned site.
    • Each finding interface gains userTextParams?. Rule ids, message and hint are unchanged. No registry adapter drops the field (validateFormLayout, validateVisibilityPredicates and the reference-integrity suite pass findings through), so authoring-rules.ts is untouched.
  • finding-user-text.ts: FINDING_USER_TEXT_CATALOG merges the three catalogs via mergeUserTextCatalogs.
  • No new export: the catalogs stay internal, as SECURITY_POSTURE_USER_TEXT is.

Changeset. .changeset/32840-advisory-user-text.md, @objectstack/lint patch, with Clause-②: no and Metadata-protocol: none. Precedent followed: #32837 (PR #33118), the sibling sub-card, declared none and patch for the same lint-only change, reading #32790 (the #32655 PR): its added came only from the @objectstack/spec key RuntimeAuthoringIssueSchema.userMessage, and it shipped the lint half as patch. This PR moves no spec key and no accepted value.

Not in this PR. The save door composing userMessage (#32656) and the console rendering it (#32657). Until #32656 lands, every save and publish response is byte-identical.

Tests

Counts from the merged head 40ff8f91 (branch tip d2634c67 on main 5b2ec820, then main dd4ce50b merged through scripts/pm/os-regen-merge.sh once #33140 landed). The merge conflicted in two files only, both resolved by keeping both sides: finding-user-text.ts (the catalog import and merge lists now carry #33140's four and this PR's three) and validate-sortable-fields.ts (one userTextParams field on SortableFieldFinding, its docblock now naming both the list-view and the page-sort sites; the duplicate type import dropped). #33140's own pin that asserted the page-sort page-field-unprovisioned warning carries no params is replaced by a pointer to this PR's pin, and its catalog note in validate-sortable-fields.user-text.ts now names the catalog that site feeds. No generated artifact left to regenerate; pnpm --filter @objectstack/spec check:generated exit 0 after a full pnpm turbo run build (120/120 tasks).

  • packages/lint/src/finding-user-text.pages-forms.test.ts (new, 26 tests):
    • exact coverage: each of the three catalogs holds exactly its door-reaching ids, each in exactly the 11 platform locales; all are the entries FINDING_USER_TEXT_CATALOG serves; the four ids are pinned by value;
    • the 11 ids the door never returns have no catalog entry;
    • every push site rendered from the rule's real findings (13 cases: the form field site from a simple form and from a Studio form item's config; the section-group site from both the sections and the legacy groups bucket; the mislayered root from a view section, a view field, a page component and a metadata-editing form; the page-sort site from all five callers: interfaceConfig, record, a block's sort, a block's dataSource and a related list's parentDataSource): severity warning, the exact params, every locale truthy, no undefined or template residue, every interpolated name present, and no English stand-in in any non-English locale;
    • the two root arms and the view and page nouns render apart in every locale;
    • zh-Hans and en spot checks on the form sentence;
    • no params where the finding is not door advice: the page-field-bindings rule's own sites, the two navigation advisories, a gating visibility syntax error;
    • from the runtime gate to the wire field: two view writes (the section-group advisory, whose params come from the call-site mapping, and the unknown field) and one page write (the unstored sort and the mislayered root) come out of runRuntimeAuthoringRules with their params, and the rendered userMessage (de-DE, ja-JP, zh-CN, fr-FR requests) parses under RuntimeAuthoringIssueSchema beside the unchanged rule id and message.
  • The four user-text pin files together (security, dashboards, data-model, pages-forms): Test Files 4 passed (4), Tests 172 passed (172), VERDICT command-exit 0.
  • pnpm --filter @objectstack/lint test: Test Files 205 passed (205), Tests 8762 passed (8762), VERDICT command-exit 0.
  • pnpm --filter @objectstack/lint typecheck: VERDICT command-exit 0.
  • Consumer reading (@objectstack/metadata-protocol, which runs the gate on every save; after the full build): Test Files 337 passed, 7 skipped (344); Tests 22799 passed, 36 skipped (22835); VERDICT command-exit 0.
Ablations (committed first, mutated through scripts/ablation-replace.mjs, restored to the HEAD blob)
# Mutation Observed
1 validate-form-layout.ts: the call-site mapping sets group: '' instead of the reference's key 3 failed, 23 passed (both section-group render cases and the view-write gate case)
2 validate-sortable-fields.ts: drop userTextParams at the page-sort site 6 failed, 20 passed (the five page-sort render cases and the page-write gate case)

Each mutation landed (anchor 1 → 0, blob changed) and each restore read blob == HEAD and git diff HEAD is empty.

Acceptance notes

  • Eleven advisory ids of the card's rule files stay on the English fallback, by measurement (parent lint: every save-door advisory rule carries a localized user sentence (67 rule ids in 36 rule files remain after the security posture), per ruling C on #32440 #32835's Done-when alternative). react-page-source-unparseable, react-prop-deprecated, react-prop-typo, react-chart-aggregate-invalid, react-chart-field-unprovisioned, nav-object-ungranted, nav-target-unresolved, page-field-unknown (skipped grade), page-section-group-unknown, translation-option-key-unknown and translation-section-name-missing, plus page-field-unprovisioned at validate-page-field-bindings.ts's own sites. Their rules are reference-integrity members on the frozen flow default (reference-integrity-suite.ts, no runtimeTypes), so the door runs them only on a flow write, and a flow write's snapshot carries no pages, apps or translations (RuntimeStackContext holds objects, permissions, books, datasets, flows). translation-section-name-missing on an object's own sections is context on both sides of the differential and cancels. A sentence the door can never render would be declared but not delivered. A pin asserts each of them has no catalog entry.
  • The card's table counts differ. validate-react-page-props.ts emits 5 advisory ids, not 2 (react-prop-typo is a literal, not a constant, and two chart ids warn on one arm); validate-form-layout.ts emits 2, not 1 (form-section-group-unknown is pushed through the shared helper); validate-page-field-bindings.ts emits 3, not 1. None of the extra ids of the react and bindings rules reaches the door either.
  • page-field-unprovisioned's catalog lives with its owner. The constant and its own push sites are validate-page-field-bindings.ts's, so the catalog is validate-page-field-bindings.user-text.ts, and its sentence speaks of a sort: the page-sort site in validate-sortable-fields.ts is the only one that carries params. lint: the the data model: objects, fields, sorting, search, record titles and autonumbers advisories carry a localized user sentence (10 rule ids in 6 rule files), per ruling C on #32440 #32838 (PR feat(lint): data-model advisories carry a localized user sentence #33140) left this id to this card by name.
  • Agent-written non-English sentences, for the native-review round. All non-English sentences in the three new catalogs are agent-written: 10 locales (zh-Hans, zh-Hant, es, ar, fr, pt-BR, ru, de, ja, ko) × 4 entries, 5 sentences per locale counting the two root arms (form-field-unknown, form-section-group-unknown, visibility-root-mislayered ×2, page-field-unprovisioned), each with the view or page noun. Points worth a native reader's eye: zh-Hant writes "區段" for section, where the console's own string reads "分割槽" (a conversion of the Simplified "分区" that means partition); ko uses the console's "보기" for view; ar uses "العرض" for view; Korean particles are written in the dual form (이(가), 은(는)) because the name is interpolated; the external-object sort sentence follows the wording of the landed sort-field-unprovisioned and dashboard-filter-field-unprovisioned sentences; visibleWhen, data. and record. stay untranslated, as the console's own visibleWhen refusal keeps the key.
  • API names, not labels, as the security catalog does (its N3, accepted): the rule is a pure function of the stack with no label lookup in the request's language.
  • New test file, not finding-user-text.test.ts. The pins sit in finding-user-text.pages-forms.test.ts, as lint: the dashboards, charts and widgets advisories carry a localized user sentence (11 rule ids in 3 rule files), per ruling C on #32440 #32837's sit in finding-user-text.dashboards.test.ts, so parallel sibling cards do not collide; the shape is the same.
  • Ruling grep over the surface (ruling|Ruling-ref|maintainer|⛔): validate-visibility-predicates.ts 27 (the parse-only, unknown-function and has() rulings, all on the gating ids), validate-sortable-fields.ts 3 (type-list notes), validate-form-layout.ts 1 (the colSpan note), validate-page-field-bindings.ts 1 (ruling B on a related list's object), finding-user-text.ts 2 (ruling record 6819749037 and the one-locale-list rule); none constrains a user sentence or the push-site params.

Verification

Local subset at the merged head 40ff8f91, derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (62 families, 11 paths against merge base dd4ce50b), every line run with stdin closed and its exit code captured before any pipe, then reconciled with --ran: 62 derived, 62 run, 0 NOT-MEASURED (a derived zero: all 62 recorded an exit code, none is 3). All 62 exit 0. The record is spec-seat-2/r2026-11-13/out/32840-gates-ran-40ff8f91.list in the project files. The pre-merge head d2634c67 read the same (61 derived, 61 exit 0).

Prerequisites before some readings
  • node scripts/check-plugin-teardown-shape.mjs --self-test: its pinned fixture commit 621a4876 was fetched first (shallow clone).
  • pnpm check:docs-transcript-drift, pnpm check:dual-build-cjs-loads, pnpm check:lean-entry-closure, pnpm check:type-check-debt: read after a full pnpm turbo run build (before it, the first answered exit 3 PREREQUISITE NOT MET, no lint dist).

node scripts/check-cli-command-ids.mjs (run after editing the catalogs, and again on the merged head): exit 0. node scripts/check-nul-bytes.mjs: exit 0.

CI runs the rest of the farm on this PR; its check-runs are the reading for every family outside the local subset.


Generated by Claude Code

…isories carry a localized user sentence

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibyb4HJHsnssCRLUw4F
… from real findings and the publish door

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibyb4HJHsnssCRLUw4F
…-168lt2

# Conflicts:
#	packages/lint/src/finding-user-text.ts
#	packages/lint/src/validate-sortable-fields.ts
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 16 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/lint/src/validate-sortable-fields.user-text.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/releases/v12.mdx (via validateFormLayout (symbol, a top-level function))

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
  • 1 changed file(s) yielded no anchor (packages/lint/src/validate-sortable-fields.user-text.ts) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 65 of 247 client-bound route-ledger rows — the other 182 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 182: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 123 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 — 4 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 bf02d33bd64f496aa8166e1a235ef10977927947 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json bf02d33bd64f496aa8166e1a235ef10977927947

⚠️ 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 bf02d33bd64f496aa8166e1a235ef10977927947 → 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#2 · os-tesla · session_01L9qibvfX5bawZKtfa6u7j9

维护者速读

席位意见:可以落地。

席位核对

ACCEPT · seat domain:spec#2 · head 40ff8f91 · 2026-11-13T19:04Z

  1. Shape: Fixes #32840, draft, base main, 11 files, +783/−36; changeset .changeset/32840-advisory-user-text.md (@objectstack/lint patch). Clause-②: no, Metadata-protocol: none, matching claim 6859188621 and dev report 6861706026.
  2. Premise: spec: user-facing refusal and advisory text is in the request locale (userMessage locale rule, advisory user text field), per ruling C on #32440 #32655's pattern, in the catalog list lint: the automation writes: approvals, flows, hooks, actions and email templates advisories carry a localized user sentence (27 rule ids in 11 rule files), per ruling C on #32440 #32836, lint: the dashboards, charts and widgets advisories carry a localized user sentence (11 rule ids in 3 rule files), per ruling C on #32440 #32837 and lint: the the data model: objects, fields, sorting, search, record titles and autonumbers advisories carry a localized user sentence (10 rule ids in 6 rule files), per ruling C on #32440 #32838 already use. The dev re-measured through runRuntimeAuthoringRules before writing catalogs: 4 ids reach the publish door (view and page writes). 11 never do, because those rules run at the door only on a flow write, whose snapshot carries no pages, apps or translations; they are named in the changeset and Acceptance notes. The card's counts were off and the PR names the differences: react-page-props 2 → 5 advisory ids, form-layout 1 → 2, bindings 1 → 3.
  3. Review: dispatch-gates --tier: no path-derived mandate (packages/lint only, no narrowing), so this is the seat's own review of record. It read the code diff: the three catalog imports and the merge list; form-section-group-unknown's params, mapped at the form-layout call site so the shared checkSectionGroupRefs helper stays untouched; visibility-root-mislayered's surface, name and root, where root picks the sentence; pageName threaded through checkPageSortDeclaration for the page-sort page-field-unprovisioned site lint: the the data model: objects, fields, sorting, search, record titles and autonumbers advisories carry a localized user sentence (10 rule ids in 6 rule files), per ruling C on #32440 #32838 left to this card; the SortableFieldFinding docblock now names both sites. No registry adapter drops userTextParams. feat(lint): data-model advisories carry a localized user sentence #33140's page-sort no-params pin is replaced by a pointer to this card's pins.
  4. Gates / check-runs: dev at 40ff8f91: 62/62 derived gates exit 0, lint 205 files, typecheck 0, metadata-protocol 337 passed / 7 skipped; 2 ablations red as expected (the group call-site mapping, the page-sort params). CI on 40ff8f91: 66 runs, 55 success, 11 skipped, 0 failed. K = 4; mergeable_state clean, and git merge-tree against main bf02d33b is clean.
  5. Accepted deviations and out-of-scope findings: the 11 unreachable ids stay on the English message. Agent-written non-English sentences are listed for the native-review round. The one-line edit to finding-user-text.data-model.test.ts (feat(lint): data-model advisories carry a localized user sentence #33140's pin) is a test under packages/lint, inside the surface.
  6. Landing: not governed, 819 changed lines. The seat readies the PR and arms auto-merge under the maintainer's standing words 「车道经理应该使用 pm-dispatch 技能审核和落地pr」. On MERGED: lint: the pages, forms, navigation and translations advisories carry a localized user sentence (9 rule ids in 8 rule files), per ruling C on #32440 #32840 closes via Fixes; pm:dispatched comes off.

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 size/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lint: the pages, forms, navigation and translations advisories carry a localized user sentence (9 rule ids in 8 rule files), per ruling C on #32440

2 participants