Skip to content

feat(spec,lint): a field declares conditionalFormatting cell rules over value and record, in the list view's own rule element - #22546

Merged
objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-22228-field-conditional-formatting
Oct 9, 2026
Merged

objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-22228-field-conditional-formatting

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #22228
Clause-②: yes (widening)

The maintainer-directed field-level formatting block from the decision discussion on objectstack-ai/objectui#11815 (the director seat's proposal, answered 「同意」). Spec and authoring gate only; objectui's cell renderer is its own card after the spec publishes.

What

  • FieldSchema.conditionalFormatting (optional): an ordered list of { condition, style } rules. The first rule whose CEL condition holds applies its CSS style map to that field's cell, wherever the field renders. A list view's row rules still style the row, and both may apply.
  • One rule element, no second copy. The { condition, style } element was declared inline in ListViewSchema. A field definition cannot import it from there (ui/view.zod.ts imports data/field.zod.ts, so the reverse import is a cycle and a data-on-UI dependency). It moved to packages/spec/src/shared/conditional-formatting.zod.ts as ConditionalFormattingRuleSchema, and both ListViewSchema.conditionalFormatting and FieldSchema.conditionalFormatting mount it. The object-grid / object-kanban blocks still reuse the list view member by identity, so they mount it too. Not exported from the shared barrel: no public export is added.
  • The scope is value and record, nothing else. @objectstack/lint's field walk (validate-expressions.ts) judges each conditionalFormatting[i].condition at os build, os validate and the object save door: it must parse, read fields as record.FIELD (never bare), name declared fields, and read no root but value and record. value reaches the shared validator through ExprSchemaHint.roots for this one call; SCOPE_ROOTS is not widened. A new root verdict, fieldFormattingRootIssue, refuses previous, parent, the user roots and every other judged root, in its own words (the field-rule family's message names previous / parent as bound, and here they are not).
  • No new style vocabulary. The style map is the row block's, by the identity above.

Zone 2 mechanism assumptions, measured

  1. Where the rule item schema lives on ee8751d41e: inline in ListViewSchema (ui/view.zod.ts, a strictObject inside z.array), with ListViewSchema.shape.conditionalFormatting the single definition the grid and kanban blocks reuse by identity. data/field.zod.ts cannot import ui/view.zod.ts (view imports field). So: the second form the dispatch allowed, a rule schema both members share. Pinned by identity in field-conditional-formatting.test.ts (FieldSchema...element and ListViewSchema...element are both toBe(ConditionalFormattingRuleSchema)).
  2. The precedent 215e66204a was read. Its field-rule family binds FIELD_RULE_BOUND_ROOTS = record, previous, parent and refuses value as a bare reference, so this slot could not join that loop: it is a sibling pass beside it, with its own bound set (FIELD_FORMATTING_BOUND_ROOTS = value, record) and the family's membership-test shape.
  3. ADR-0087 entry: none owed, none added. On ee8751d41e FieldSchema is a strictObject (field.zod.ts:1053) and authorable-surface/data.json has no data/Field:conditionalFormatting row, so the key was refused before and no stored source can carry a legacy spelling. git grep conditionalFormatting ee8751d41e -- examples hits only the showcase field-zoo LIST VIEW. node scripts/check-adr-0087-registration.mjs --base origin/main: "this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)", exit 0.
  4. PR fix(lint): a declared read attachment resolves only where record is a served row, so os validate refuses a flow condition that reads one #22509 landed (c76edeb8c6) and was merged in (5b81e00352; one hand conflict in the check closure, both parameters stacked: servedRowSite then extraRoots). The new call site passes no servedRowSite, so it is fields-only by construction. The SERVED_ROW_SITES docblock names it beside a field's visibleWhen.

Liveness ledger

packages/spec/liveness/field.json row conditionalFormatting: planned, drilled (condition, style) because check:liveness refuses an undeclared container verdict. The in-repo reader is the authoring gate, which validates and does not make the key do anything, so the ledger's live ("has a runtime consumer") does not apply. Cross-repo, measured: objectui main 2a48bd4 and the .objectui-sha pin f0268ad78 read conditionalFormatting only off list views, the grid and kanban blocks and reports (control: plugin-grid/src/ObjectGrid.tsx carries 8 hits at the pin); no field-level read. No authorWarn (the settledWhen precedent). The flip condition is written into the row.

Files beyond the claim's declared surface (each one demanded by a gate or by the shared element)

  • packages/spec/src/shared/conditional-formatting.zod.ts and packages/spec/src/ui/view.zod.ts: the shared element and the list view mounting it.
  • Re-keyed declaring position ui/view.zod.ts:ListViewShapeSchema.condition to shared/conditional-formatting.zod.ts:ConditionalFormattingRuleSchema.condition: packages/qa/dogfood/test/expression-conformance.ledger.ts (still the cel-ui row, with a note that the position now has two mounts and the field mount has no evaluator yet) and packages/spec/src/shared/evaluated-slot-population.test.ts (count stays 34).
  • packages/spec/src/system/metadata-form-zod-reconciliation.test.ts: a root omit row for field.conditionalFormatting, the page.print "planned, not yet rendered" precedent, instead of a form control nothing would paint.
  • packages/metadata-core/src/object-schema-fls-references.ts (+ test): conditionalFormatting: ruleEntries, so a rule whose condition reads a denied field is dropped whole (a bare style would read as always-on) and an emptied list is deleted.
  • packages/drivers/driver-sql/src/builtin-column-collision.ts: presentation.
  • packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts: field 77 to 78.
  • packages/spec/llms.txt: check:llms-txt counts (shared 15 to 16, total 202 to 203).
  • Generated: authorable-surface/data.json, liveness/state-counts/field.md, content/docs/references/**, the strictness-ledger counts, and three skills/*/references/_index.md lines (generator-owned: check-governed-merges.mjs --test reports "0 of 28 path(s) hit the register after 3 generated-artifact lift(s)", NOT governed).

Docs

content/docs/data-modeling/fields.mdx: a row in the Conditional Logic table and a "Cell formatting" subsection with the card's worked example (an amount below zero in red), the authoring checks, the presentation-only boundary (a deadline stays dueLike / settledWhen), and one closing status paragraph: the console reads the rules in a later objectui release. That paragraph is dropped on the ledger flip.

Tests (all locked runs through os-verify-lock)

Every suite below ran on the merged tree at 14b85354d3. The one later commit, ffce73a2ac, changes only packages/spec/llms.txt: check:llms-txt passes at ffce73a2ac, and the one spec test that names that file (in a comment, filter-array-declaration.test.ts) re-ran green there with the new pin file (2 files, 32 passed). Filter direction: per package, --filter PKG itself (no consumer sweep); the consumer radius named by the dispatch (packages/spec, packages/lint, packages/metadata*, the examples) is run package by package.

Suite Result
@objectstack/spec --project local 632 files, 18,894 passed, 1 todo
@objectstack/spec --project repo 54 files, 915 passed
@objectstack/lint (whole package) 132 files, 6,047 passed
@objectstack/metadata-core 17 files, 388 passed
@objectstack/metadata-protocol (whole package) 223 files passed, 3 skipped; 28,347 passed, 19 skipped
@objectstack/metadata 58 files, 871 passed
@objectstack/driver-sql 218 files passed, 11 skipped; 3,635 passed, 206 skipped
@objectstack/dogfood test/expression-conformance.test.ts 7 passed
example-showcase / example-crm / example-todo 33 / 408, 5 / 45, 7 / 238 passed
objectstack validate (showcase, crm, todo) Validation passed, all three
typecheck: spec (incl. check:test-typecheck), lint (incl. check:test-typecheck), metadata-core, driver-sql, metadata-protocol, dogfood all exit 0

New pins: packages/spec/src/data/field-conditional-formatting.test.ts (19: identity of the element on both members, accept, order, any field type, control without the key, refusals at the rule with issue path plus the message's first sentence) and packages/lint/src/validate-expressions.field-conditional-formatting.test.ts (41: value and record accepted, today(), a non-parsing condition refused naming the field and the rule, bare field, unknown field, every judged root other than record refused with this surface's verdict and only that one, control on the field-rule family's own slots, os build and the runtime object door under expression-invalid), plus one FLS case in object-schema-fls-references.test.ts. @objectstack/cli integration tier: not run locally, declared to CI (no spawn entry touched).

Ablation (each committed first, mutated through scripts/ablation-replace.mjs, restored to blob == HEAD with an empty git diff HEAD; subjects resolve to src through relative imports, so no dist leg)

Expected direction for each: red. Observed: red.

Mutation Suite Result
the cell-rule check call passes no value root lint pin file 4 red, 37 green
fieldFormattingRootIssue returns null always lint pin file 29 red, 12 green
FieldSchema.conditionalFormatting mounts a loose z.object instead of the shared element spec pin file 9 red, 10 green
FLS scrub drops only the condition (arrayOf(block(...))) instead of the whole rule metadata-core FLS file 1 red, 51 green

Gates

At ffce73a2ac: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 116 families (the same 116 as before the last commit); with the 51 artifact-roster commands, 166 commands ran. --ran: "116 derived, 115 run, 0 NOT-MEASURED, 1 UNRUN" — the one is pnpm check:dual-build-cjs-loads, NOT MEASURED by dispatch (no whole-workspace build), left to CI. Of the 115: 114 exit 0, and pnpm check:platform-checklist exit 1, which is red identically on a pristine origin/main faf6348508 worktree (see Acceptance notes). Roster: 48 exit 0; the three PR-context guards (check-closing-target-claim, check-partof-closing-keyword, check-single-claim-paths) answer exit 2 without PR context; they are re-run with this PR's context after it opens, and their verdicts are recorded in the os-dev-report comment on #22228. check:generated (spec, after a build): all 15 artifacts current.

Acceptance notes

  • The list view's unknown-rule-key refusal reads differently. One element, one message: the history sentence is now true for both members, and the color guidance leads with "put it in style" before pointing a list view at rowColor. The accept set is unchanged; no test or doc pinned the old wording (git grep of both sentences: 0 hits outside the schema).
  • One expression-ledger row speaks for two mounts. The conformance ledger's unit is the declaring position, and the shared element is one position. The cel-ui row keeps enforced for the list-row mount and its note records that the field mount has no evaluator yet; the liveness row carries the field mount's planned state.
  • The docs-corpus gate does not judge these conditions. check:doc-formula-expressions admits only the field-level *When slots; a condition nested in conditionalFormatting is outside its arms, so the new docs example is judged by nothing at the corpus level (it is the same predicate the lint pins accept). Noted, not extended.
  • No traversal refusal, null-guard or parent gate on this slot, like visibleWhen: those are write-path gates and nothing on the write path reads it. Whether a cell renderer hydrates record.REFERENCE.FIELD is for objectui's card to declare (carrier: the objectui renderer card).
  • No authorWarn. Until objectui reads the key an authored rule styles nothing; the docs status paragraph and the changeset say so. A warned field row would reopen the lint's field walk for one display key (the settledWhen precedent).
  • check:platform-checklist is red on origin/main itself (faf6348508, a pristine worktree: areas/attachments-storage.json ABSENT SYMBOL attachment-access-hooks.ts#canEdit, anchors 27 of floor 28). This diff touches neither file.

Generated by Claude Code

claude added 9 commits October 9, 2026 19:54
…st view's own rule element

The rule element { condition, style } moves to shared/conditional-formatting.zod.ts
so FieldSchema and ListViewSchema mount one declaration.

Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF
Co-authored-by: Claude <noreply@anthropic.com>
…nd strictness counts

Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF
Co-authored-by: Claude <noreply@anthropic.com>
…tadata-core and driver-sql

Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF
Co-authored-by: Claude <noreply@anthropic.com>
…eld-conditional-formatting

# Conflicts:
#	packages/lint/src/validate-expressions.ts
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/driver-sql, @objectstack/lint, @objectstack/metadata-core, @objectstack/spec, touching 9 documentable anchor(s). ⚠️ 4 changed file(s) yielded no anchor (packages/spec/authorable-surface/data.json, packages/spec/liveness/field.json, packages/spec/liveness/state-counts/field.md, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/concepts/metadata-driven.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/data-modeling/external-datasources.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/data-modeling/field-types.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/data-modeling/fields.mdx (via conditionalFormatting (symbol, a field of const object FIELD_KEY_STORAGE_CLASS; a field of const object FIELD_REFERENCE_POSITIONS))
  • content/docs/data-modeling/validation-rules.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/deployment/troubleshooting.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/deployment/validating-metadata.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/getting-started/quick-reference.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/kernel/contracts/data-engine.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/protocol/backward-compatibility.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/protocol/objectql/types.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/protocol/objectui/concept.mdx (via FieldSchema (symbol, a top-level const))

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

  • content/docs/releases/v17/17-0.mdx (via FieldSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-1.mdx (via FieldSchema (symbol, a top-level const), conditionalFormatting (symbol, a field of const object FIELD_KEY_STORAGE_CLASS; a field of const object FIELD_REFERENCE_POSITIONS))
  • content/docs/releases/v17/17-7.mdx (via conditionalFormatting (symbol, a field of const object FIELD_KEY_STORAGE_CLASS; a field of const object FIELD_REFERENCE_POSITIONS))

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
  • 4 changed file(s) yielded no anchor (packages/spec/authorable-surface/data.json, packages/spec/liveness/field.json, packages/spec/liveness/state-counts/field.md, …) — pages documenting those are invisible to this run
  • 6 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 140 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 faf6348508519197c6047b46fb30b6ae8910f6b2 → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 faf6348508519197c6047b46fb30b6ae8910f6b2 → 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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ffce73a2acdd4bc3202cc963f3fb9db1890452e5
Local-runs: none

Reviewed on the net diff of refs/pm/pr-22546 against its merge-base with origin/main (40a6ee50ac): 28 files, +699 / -36 = 735 changed lines, the same 28 paths the PR's file list names. Inputs: card #22228 (body and its 3 comments), PR #22546 (body, 1 bot comment, 0 reviews), and the head's 39 check-runs. Head repo equals base repo; the PR is a draft labelled needs:contract-review.

① Derived judgments

Every accept-set and public-surface change the diff implies, each named right or wrong:

  1. FieldSchema widens by one optional key — conditionalFormatting: z.array(ConditionalFormattingRuleSchema).optional(). On the merge-base FieldSchema is a strictObject (field.zod.ts:1053) with no such key, so the spelling was refused before and is accepted now on every field type; a bare-string condition normalises to the { dialect: 'cel', source } envelope like every CEL slot; an empty list is kept; a malformed rule is refused at the rule's own path with the strict-object prescription. RIGHT — exactly the card's one key, in the row block's grammar.
  2. ListViewSchema.conditionalFormatting accept set unchanged. The element moved from an inline strictObject in ui/view.zod.ts to shared/conditional-formatting.zod.ts with the same two keys (condition, style), the same six refusal aliases (when, expression, visibleWhen, rule, styles, css) and the same color guidance key; object-grid and object-kanban reuse the member by identity and are unchanged. Only refusal prose moved: the history sentence (was the view-wide VIEW_HISTORY) now describes a rule and holds on both members; the color guidance leads with "put it in style" and still points a list view at rowColor. Element identity on both members is pinned in field-conditional-formatting.test.ts. RIGHT — one grammar, no second style vocabulary, no narrowing.
  3. No public export added to @objectstack/spec. The new module is not re-exported from the shared barrel and no api-surface/ shard moves in the diff (check:api-surface sits inside the green TypeScript Type Check). authorable-surface/data.json gains data/Field:conditionalFormatting, the widening's own baseline row. RIGHT.
  4. @objectstack/lint judges a new slot. The field walk reads f.conditionalFormatting literally (the validate-expressions / validate-security-posture 也有同形的 spec 不声明键的 ?? 别名读法(#5009 建议 3 的核对结果) #5017 source scan now expects it) and, per rule, calls the record-scoped check with fieldRuleVerdictIssued = verdict !== null, no traversalHydration, no servedRowSite (fields-only by construction) and extraRoots = ['value'] through ExprSchemaHint.roots (formula/src/validate.ts:167); SCOPE_ROOTS is not widened (pinned). fieldFormattingRootIssue refuses every root of FIELD_RULE_JUDGED_ROOTS other than record, user roots first, one verdict per rule. fieldFormattingRootIssue and FIELD_FORMATTING_BOUND_ROOTS are module exports not re-exported from packages/lint/src/index.ts, so the package's export surface is unchanged; its behaviour widens at all three doors (os build, os validate, the object save door — each pinned, 41 cases). RIGHT — the authoring gate the card requires, enforced now.
  5. @objectstack/metadata-core FLS — FIELD_REFERENCE_POSITIONS.conditionalFormatting = ruleEntries: a rule whose condition mentions a denied field is dropped whole (not only its condition, which would leave an always-on style), and arrayOf removes an emptied list. Internal table, not on the package index. RIGHT — the one consumer read landing now, and it is a protection, not the style.
  6. @objectstack/driver-sql — FIELD_KEY_STORAGE_CLASS.conditionalFormatting = 'presentation': the key never reaches column DDL. RIGHT.
  7. Ledgers re-keyed, not widened. The expression-conformance row and the evaluated-slot-population position move from ui/view.zod.ts:ListViewShapeSchema.condition to shared/conditional-formatting.zod.ts:ConditionalFormattingRuleSchema.condition (count stays 34); the cel-ui row stays enforced for the list-row mount and its note records that the field mount has no evaluator. CARD_PROPERTY_COUNTS.field 77 → 78; strictness-ledger counts shared/ 20 → 21 and ui/ 210 → 209 (one site moved, net zero); llms.txt 202 → 203. All generated or count-pinned, all green. RIGHT, with one caveat named: one conformance row now speaks for two mounts and only its note says which mount is evaluated — the liveness row is where the field mount's state lives.
  8. Studio form — metadata-form-zod-reconciliation.test.ts carries a root omit for field.conditionalFormatting (the page.print precedent): no form control for a key nothing paints. RIGHT, and consistent with the planned row.
  9. ADR-0087: no entry owed, none added. The key was refused by the strict schema on the base, nothing stored can carry a legacy spelling, and check:adr-0087-registration is inside the green Lint & Repo Gates. The card's landing shape listed an entry; the dispatch's Claim: made it conditional on measurement, and the measurement shows none. RIGHT.
  10. Contract-first held. No ?? fallback and no lenient alias in any consumer; the six aliases are refusal prescriptions on a strict object, carried over unchanged.
  11. Governed surfaces. Three skills/*/references/_index.md paths hit the skills/** register row. The Governed Surface Queue Guard on this head (success) recomputed gen:skill-refs on the tree and LIFTED all three as byte-equal generator output on both commits that touched them (5b81e00352, 572dff32f2): verdict CLEAR, zero review lookups. After the lift, zero of the 28 paths is a governed surface and nothing hand-authored sits under a governed root. This PR owes no Tier H or Tier S landing record on governance grounds; it lands through the queue on green.

② Semver level

Changeset .changeset/22228-field-conditional-formatting.md, four entries, Clause-②: yes (widening) in its body and on the PR's second line — one arm, the widening one: the field accept set grows by one key and nothing narrows anywhere. Check Changeset green.

  • @objectstack/spec minor — RIGHT. Accept-set widening, no removal or rename, no export change; Clause-②: yes takes at least minor and this is exactly minor.
  • @objectstack/lint minor — RIGHT. A new judged slot with a new verdict family at three doors is a feature, not a fix; the export barrel is unchanged, so nothing above minor.
  • @objectstack/metadata-core patch — RIGHT. One scrub position on an internal table so a rule naming a denied field cannot serve through FLS: correctness-sized, nothing exported.
  • @objectstack/driver-sql patch — RIGHT. One classification row on an internal ratchet table; the package ships changed code, so an entry is owed, and nothing larger is.
  • No entry owed elsewhere: metadata-protocol and qa/dogfood change test or ledger files only; the docs, strictness counts and skill indexes publish from no released package.
  • No breaking marker and no ADR-0087 disposition line — RIGHT, nothing breaks.
  • One non-blocking defect in the text, the PM's nit confirmed: the example sits in one inline code span holding backslash-escaped backticks; CommonMark does not escape inside a code span, so the published CHANGELOG.md renders that one sentence broken. The raw text is intact and greppable, and the schema describe and the docs carry the example correctly. A double-backtick span fixes it on the next push, if one is ordered; it is not a contract change and not a reason to withhold the verdict.

③ Boundary flags

open_questions: none declared. The dev's six deviations, its three out-of-scope notes, the PM's nit and the sequencing question, each answered or escalated:

  • Files beyond the claim's surface — each judged in ① as demanded by the shared element or by a gate. ANSWERED.
  • Two extra changeset entries — judged in ②. ANSWERED.
  • List view refusal text — accept set unchanged; the new history sentence is true of both members; no pin or doc held the old prose. ANSWERED.
  • Base drift — origin/main is 4 commits and 37 files past the merge-base; the intersection with this PR's 28 paths is empty; mergeable_state is clean. No base sync is owed before enqueue; the queue rebuilds on current main. ANSWERED.
  • Attribution — the model-free trailer pair and the session-URL PR footer are AGENTS.md's form, which outranks the harness reminder in this repo. ANSWERED.
  • check:platform-checklist red on pristine main — touches neither of this PR's files; the watchdog is the carrier. ANSWERED.
  • record.REF.FIELD through a reference field is not refused — the same as visibleWhen; whether a cell host hydrates the related record is the renderer card's declaration. ANSWERED, carrier objectui's cell-renderer card.
  • check:doc-formula-expressions does not judge a nested condition — a coverage gap in a corpus gate, not a defect or an authoring trap, so an acceptance note is the right carrier; the docs example is the predicate the lint pin accepts. ANSWERED, with one request: widen that gate's arms in the same PR that flips the row to live.
  • Declared means enforced, against the card's sequencing. Enforced now: the rule shape at parse (strict element, non-blank condition, string-to-string style), CEL parse, the record.FIELD form, declared field names, and the value plus record root allowlist at os build, os validate and the object save door; the FLS scrub; the DDL exclusion. Not delivered yet, and honestly marked: no cell renderer reads the key in either repo (measured by the dev on objectui main and the pin, recorded in the PR body and the ledger row). The planned liveness row carries cross-repo evidence, the enforcement half, no authorWarn (the settledWhen precedent) and a written flip; the docs subsection closes with the status paragraph; the changeset's "Who reads it" says the same; the form omit row and the conformance note agree. The schema describe — and so the generated reference pages — states the cell semantics without a planned marker, as dueLike and settledWhen do on the same schema; its own enforcement claim (objectstack validate refuses) is true today. ANSWERED: consistent with the card, which places the renderer in a later objectui card and ships no objectui change and no pin bump here.
  • ESCALATED to the PM, not blocking: the planned row's flip has no filed carrier yet — the card defers objectui's renderer card to after the spec publishes. File it at publish time and name it in the row, so the flip has an owner the way settledWhen's did.

Required contexts on the head, all success: Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard. Also green: Spec property liveness, Check Changeset, Check PR Size (735 lines, under the 3,000-line landing threshold), and the three claim guards. Console Pin Gate and the packed-tarball smoke are skipped by design (no removal; opt-in). The Docs Drift Check comment is advisory and lists pages that name FieldSchema generically; the one page this diff falsifies nothing on and extends is fields.mdx, edited here.

Implemented-by: claude/issue-22228-field-conditional-formatting
Reviewed-by: session_01KNKBCRDJCu5tGy3TEbvtrF

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Landing pre-checks at ffce73a2ac, by the owning seat

domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-09T22:44Z.

  • The review: the contract review PASS 6090502579 names this head.
  • CI: 34 success and 5 skipped. check-expected-skips --pr 22546 exits 0, and every skip is in the roster.
  • The governed surface: three generated skills/*/references/_index.md paths hit the skills/** register row.
    • The Governed Surface Queue Guard on this head recomputed pnpm --filter @objectstack/spec gen:skill-refs with the toolchain installed. It LIFTED all three on both commits that touch them (5b81e00352a5, 572dff32f21d): byte-equal generator output. Its verdict is CLEAR, with 0 governed PRs and 0 review lookups.
    • The seat's own check-governed-merges.mjs --pr 22546 ran in a checkout with no installed dependencies. There gen:skill-refs --check exits 254 and declares no output set, so the audit fails closed and prints GOVERNED (exit 3). That reading is an environment fact, not a measurement of these paths.
    • The measured answer is the guard's, and the dev's own toolchain run agrees: "0 of 28 path(s) hit the register after 3 generated-artifact lift(s)". The merge group re-runs the same guard, and a lift it cannot reproduce there refuses the entry.
  • Size: 735 changed lines, generated files included, under 3,000.
  • Closing keywords: the body carries Fixes #22228 alone.
  • main drift: main moved no path of this PR, and none of its os-regen artifacts, since the merge base. mergeable_state reads clean.

needs:contract-review comes off, and the PR goes ready with auto-merge armed.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 9, 2026 22:46
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 9, 2026 22:46
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 3d886ee Oct 9, 2026
44 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22228-field-conditional-formatting branch October 9, 2026 23:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants