Repository navigation
feat(spec,lint): a date or datetime field declares its deadline: dueLike and settledWhen - #22503
Conversation
The deadline semantic on the field definition: `dueLike` declares the date a deadline, `settledWhen` is a per-record CEL predicate in the visibleWhen family that settles it. Both refused off the date/datetime types, and settledWhen refused without dueLike: true. Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…n the field-rule pass (wip) Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
… liveness, FLS and expression ledgers (wip) Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…s; teach dueLike and settledWhen; changeset (wip) Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…e two deadline rows, generator output unedited (wip) Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…and es-ES (wip) Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…ledWhen; type the form-row read in the pin (wip) Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…65 to 667 for the two deadline rows (wip) Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 5 package(s): 23 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 140 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 5a3bceee79af297af99c0de01e45217e93219840 && git checkout 5a3bceee79af297af99c0de01e45217e93219840
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 446c8b2a6420a61a2862e6f5140dda71a53316d1 c73e779f9a04e78461220274f2d0b5356f1d99aa && git checkout -B drift-repro 446c8b2a6420a61a2862e6f5140dda71a53316d1 && git merge --no-ff c73e779f9a04e78461220274f2d0b5356f1d99aa
node scripts/docs-audit/affected-docs.mjs --json 446c8b2a6420a61a2862e6f5140dda71a53316d1
|
Contract reviewServed-tier: Inputs: card #22227 (body and all four comments, the dev report included), PR #22503 (body, 24-file list, the net diff against ① Derived judgmentsThe accept set of
② Semver level
③ Boundary flags
Prescription for the next head. Add Check-runs on this head, read at 2026-10-09T15:49Z: 34 check-runs, 29 completed (24 success, 2 skipped, 3 failure: Implemented-by: VERDICT: FAIL |
…TORAGE_CLASS; settledWhen joins the docs gate's field-rule slots and the consumer census's behaviour segments FIELD_KEY_STORAGE_CLASS is pinned against Object.keys(FieldSchema.shape) (builtin-column-collision.test.ts): both keys are presentation, read by the renderer's date cells and never by createColumn. The docs-corpus gate now root-judges a settledWhen example (slot list and text tripwire together, three self-test cases), and validate-field-consumers counts a settledWhen read as behaviour beside its three siblings. Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…-level properties; the field-level alias verdict covers settledWhen protocol.meta-types-degenerate-derivation.test.ts holds the per-type served property counts as a positive control; `field` moves 75 to 77 with the two declared keys, the `picklist` precedent. The #6585 user-alias table over the field-rule slots gains settledWhen, which binds the same roots. Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…s as four, settledWhen included Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs: card #22227 (body and all six comments: the claim The four commits since ① Derived judgmentsThe accept set of
② Semver level
③ Boundary flags
Check-runs on this head, read at 2026-10-09T17:29Z: 35 check-runs, all 35 completed — 33 success, 2 skipped ( Implemented-by: VERDICT: PASS Generated by Claude Code |
…-written and field-level regex keys; geolocation leaves the union (objectui#12242) (#12293) Part of #12242 Clause-②: yes Round two of the field metadata family close-out. It executes maintainer ruling A on the card (comment 6135661503: Q1 A, Q2 A, Q3 B), from `main` `74964ad6`. The authoring face of a field now describes only what a published object can carry. One position is **held, not executed**: `dueLike`, because the ruling's fork clause fired on it (see "The fork" below). That is why the first line is `Part of`, not `Fixes`: the card stays open for that one position. ## What changed - **`@object-ui/types`** (`field-types.ts`, `form.ts`): - **Q1, moved off the authoring face.** `BaseFieldMetadata.mobile_fullscreen`, `LookupFieldMetadata.idField`, and `UserFieldMetadata.picker` / `subtitle` are no longer declared. Each spot carries a "there is deliberately no X here" note that names where the key lives now. - `FormField` keeps `idField` / `picker` / `subtitle` at the same types. They were declared by reference to the removed members; the entry now declares them itself (`string`, `'search' | 'default'`, `string[]`). The ruling keeps the faces that already typed them. - **Q2, `never` tombstones.** `TextFieldMetadata.pattern` and `pattern_message`, and `BaseFieldMetadata.validate`, are now `never` tombstones. Each docblock names the spec's object-level `format` validation rule, `{ type: 'format', name, field, regex, message }`. `FieldValidationFunction` and `FieldConstraints` are marked `@deprecated` because no member declares them now, the same treatment round one gave `VisibilityCondition`. - **Q3, out of the union.** `GeolocationFieldMetadata` leaves `FieldMetadata`. The interface stays exported, marked `@deprecated`, and the `field:geolocation` widget registration is untouched. - `FieldValidationRules.pattern`'s docblock no longer says `buildValidationRules` compiles a string pattern. - **`@object-ui/fields`**: - `buildValidationRules` no longer reads `pattern` / `pattern_message` / `validate`. A `location` field's rule is now the stored-value check alone. No client pre-check of the object-level rule is added (the ruling did not take B). - New package-local hand-off: `LongTextCarrierHandoff` (`widgets/longTextCarrierHandoff.ts`, not exported from the index, following the `renderHandoff.ts` / `chartRenderHandoff.ts` pattern). `TextAreaField` and `RichTextField` read `mobile_fullscreen` through it, intersected into their existing declared casts. Its producer is still `ObjectForm`, unchanged. - `LookupField` reads `idField` / `picker` / `subtitle` through an annotated alias, typed as `Pick` of `FormField` over those three keys. `FormField` is the existing face that declares them. The reads were untyped (`any`) before; behaviour is unchanged. - **`@object-ui/app-shell`**, `FlowReferenceField` (the flow designer's record-lookup cell). It builds `LookupField`'s carrier with `idField` and the people-picker keys, taken from its own package-local `RecordLookupBinding`. The literal was typed as `LookupFieldMetadata` through the widget prop, so removing `idField` turned `app-shell` `type-check` red (TS2353). The literal is now typed as `LookupFieldMetadata` intersected with `Pick` of `FormField` over the three keys. This is the producer side of the hand-off, fixed on the producer. - **`@object-ui/plugin-grid`** (`relationalMetaKeys.ts`): the `widget-contract` verdict's docblock and the `idField` row's note now name `FormField` instead of `LookupFieldMetadata`. This is internal data, not exported, with no behaviour change. The derivation test's alias control now expects `LookupField`'s second alias (`pickerConfig`). - **Docs**: - `text.mdx` teaches the object-level `format` rule, written as a prose list (see Acceptance notes). - `lookup.mdx` and `user.mdx` drop the moved keys from their field metadata snippets and say where `idField` / `picker` / `subtitle` live now. `user.mdx` shows them on a typed `FormField`. - `blocks/forms.mdx`, `plugins/plugin-form.mdx` and the plugin-form README no longer say a field-level string pattern is compiled. ## The fork: `dueLike` is held The ruling's premise for Q1 is that the spec refuses each key and no published object can carry it. For `dueLike` that is no longer true on `@objectstack/spec`'s own `main`: - objectstack-ai/objectstack#22503 (merged 2026-10-09, commit `215e66204`, pending changeset `22227-field-deadline-due-like.md`) declares `dueLike` and `settledWhen` on `FieldSchema`, for `date` / `datetime` only. Its changeset names objectui's date cells as the reader. - The spec installed here (17.7.0) still refuses `dueLike` (`unrecognized_keys`), so the adoption arrives with the next spec release. - Three example apps in objectstack write `dueLike: true` on published object definitions: `app-crm`'s `activity`, `app-showcase`'s `task` and `app-todo`'s `task`. - Its readers take it off the object's field definition: `resolveDueLike` in `@object-ui/fields`, which the date and datetime cells call, and the record-detail enrichment copy. That is an authored key, which a runtime hand-off type cannot honestly carry. So `dueLike` is a reader no hand-off type can carry, and the key is moving toward the authoring face, not away from it. Per the fork clause, this run reports it and does not decide it. `DateFieldMetadata.dueLike`, `DateTimeFieldMetadata.dueLike`, `date.mdx`, `datetime.mdx`, `DetailViewFieldSchema.dueLike` and `schema-reference.md` are all unchanged. No silent C. ## Census of the kept rows (starting from PR objectui#12257's table) | Interface | Member / value | Installed `FieldSchema` (17.7.0) | Spec `main` | Non-test readers and producers | This PR | |---|---|---|---|---|---| | Base | `mobile_fullscreen` | `unrecognized_keys` | not declared | produced by `ObjectForm`; read by `TextAreaField`, `RichTextField`, and the form renderer's built-in `textarea` branch (through its own props type) | moved to `LongTextCarrierHandoff` | | Base | `validate` | `unrecognized_keys` | not declared | `buildValidationRules` | `never`; read removed | | Text | `pattern`, `pattern_message` | `unrecognized_keys` | object-level `format` rule only | `buildValidationRules` | `never`; reads removed | | Date, DateTime | `dueLike` | `unrecognized_keys` | **declared** (objectstack#22503) | `resolveDueLike` (date and datetime cells), `DetailViewFieldSchema.dueLike`, the detail enrichment copy | **held** (fork) | | Lookup | `idField` | `unrecognized_keys` | not on `FieldSchema` (only on `InlineGridColumnSchema`) | `LookupField`; produced by `FormField` entries, action parameters, `FlowReferenceField` and `GridField` inline columns | moved; `FormField` declares it | | User | `picker`, `subtitle` | `unrecognized_keys` | not declared | `LookupField`, and `UserField` (defaults); produced by `FormField` entries and `FlowReferenceField` | moved; `FormField` declares them | | Geolocation | type `geolocation` | `invalid_value` at `type` | not a `FieldType` | the `field:geolocation` widget | out of the union | `field-metadata-family-retired-12242.test.ts` re-derives the installed-spec column on every run, each row behind an accepting control. The other columns are a reading taken once. ### Fork-clause measurements - **`type: 'geolocation'` producers.** The query was the regex `type['"]?\s*:\s*['"]geolocation['"]`, excluding tests, specs, `content/**`, `docs/**`, markdown and changelogs: - objectui `74964ad6`: 1 hit, `GeolocationFieldMetadata`'s own `type` member. The control, the same query for `text`: 221 files. - objectstack `origin/main` `c9b5b24b0`: 0 hits; control: 80 files. - hotcrm `0f84c9c`: 0 hits; control: 16 files. - **Writers of the other Q1 keys** in objectstack (`packages/**`, `examples/**`, `apps/**`) and hotcrm. Every hit was read: - `mobile_fullscreen`: 0 in both. - `picker` / `subtitle`: none on a field definition. The hits are related-list `add.picker`, page-header `subtitle` and chart `subtitle`. - `idField`: only `InlineGridColumnSchema` and unrelated identifiers. - `dueLike`: the fork above. ## Export closure (Clause-②) Measured on the built `.d.ts`: base `74964ad6` from a scratch worktree, head from this branch. The comparison is per top-level declaration, as a token multiset with comments excluded, so the type printer's union-member order cannot read as a change. `@deprecated` is compared separately. - **Export names:** identical for every `exports` entry of `@object-ui/types`, `@object-ui/fields` and `@object-ui/plugin-grid`. No name is added or removed. - **`@object-ui/types` declarations changed:** - `BaseFieldMetadata`, `TextFieldMetadata`, `LookupFieldMetadata` and `UserFieldMetadata` (the members above). - `FieldMetadata` (one union member fewer). - `FormField`: the same three member types, now spelled out. `form-field-channel-keys-12130.test.ts` pins them `Equal` to `string` / `'search' | 'default'` / `string[]`, each with `undefined`. - `@deprecated` added on `FieldValidationFunction`, `FieldConstraints` and `GeolocationFieldMetadata`. - **`@object-ui/fields`:** one added file, `widgets/longTextCarrierHandoff.d.ts`. No emitted declaration file references it, and no export entry reaches it. Every other declaration is unchanged. - **`@object-ui/plugin-grid`:** no declaration changed. ## Tests and gates (at `31da2084`, from the repo root; heavy runs under the shared verify lock) - `type-check`, with the script name echoed and after the dependency-closure build (turbo, 28 of 28 tasks), exit 0 for each of `@object-ui/types`, `fields`, `core`, `components`, `plugin-form`, `plugin-detail`, `plugin-grid` and `app-shell`. `app-shell` was red once (the `FlowReferenceField` literal above) and green after that fix. `types` was re-run at `31da2084`: exit 0. - `vitest run`: - `packages/types/`, plus the two new `fields` pins: 375 files, 10239 tests passed (at `31da2084`). - `packages/fields/`: 252 files passed (1 skipped), 4031 tests passed (at `bf418ddc`; no `fields` source changed after it). - A targeted set of 28 files, 354 tests passed (at `bf418ddc`): `plugin-grid` `relationalMetaCopySet.derivation` and `lookupPickerKeys-7154`; the `plugin-form` location suites and `ObjectForm.mobileFullscreen`; `app-shell` `FlowReferenceField*` and `paramToField`; the `components` fullscreen-textarea suites; `schema-catalog` `form-field-channel-census-12130`, `form-field-channel-effects-12130` and `form-builtin-branch-dom-allowlist-12155`; `plugin-detail` `fieldEnrichment`. - `scripts/__tests__/` (at `bf418ddc`): 186 files passed, 1 red: `check-doc-component-types`, "this repository is green", on this branch's first `text.mdx`. That page now teaches the rule in prose, and the re-run of that file is green (at `155bd4e1`, with the `types` suite). - **Gates, each exit 0 at `31da2084`, with its own verdict line:** - `check:doc-types`: "Every documented component type is registered". - `check:doc-snippets`: "Every covered documentation snippet compiles against the built types" (after the scoped build it names, 37 of 37 tasks). - `check:doc-examples`: "Every covered `@example` compiles, or fails exactly as its ledger row declares". - `check:doc-json`, `check:doc-fences`, `check:doc-example-ids`, and `check-doc-links` ("Links are valid across 17 scan roots"). - `check:readme-exports`: OK, 0 fabricated. - `check:spec-symbols`, `check:self-import`, `check:phantom-deps`, `check:unreferenced-sources`, `check:handler-key-reads`, `check:esm-specifiers` (specifier leg), and `check:designer-field-key-parity` ("OK"). - `check:new-line-citations`: "0 new citation(s)". `check:control-bytes`: OK. - `check:changeset-claims`: report-only. It names round one's types changeset, whose paragraph about the family pin stays true: this PR adds rows and changes none. - `check:pending-changeset-literals`. - `check-changeset-presence`: "20 source file(s) of 5 released package(s) changed, and this change declares 2 changeset(s)". Also `check-changeset-no-major` and `check-changeset-fixed`. - `check:eager-closure`: 3131.3 KB of 3204.6 KB. - `check-governed-queue-guard --test` over the 28 changed paths: NOT GOVERNED. - **Targeted eslint** on the 19 changed `.ts` / `.tsx` files: 0 errors and 233 warnings. The 16 files that exist at base carry the identical per-file counts there, and the 3 new files carry 0 of each. - Population: `eslint.config.js`'s TS block (`**/*.{ts,tsx}`). - The count is read from `--format json`. - Invariance: the config enables no type-aware linting (no `project` / `projectService`), and no rule in `eslint-rules/` reads another file. So this diff cannot move any untouched file's verdict. ## Ablations (each from committed state, through objectstack's `ablation-replace.mjs`, wrap mode, under the lock) Each mutation hit its anchor, and the blob changed. Each restore proved blob equal to `HEAD` and an empty `git diff HEAD`. - **A1**, `pattern?: never` to `string | RegExp`: `tsc -p tsconfig.test.json` red, TS2344 on the tombstone assertion and TS2578 (unused `@ts-expect-error`) on `_pattern`. - **A2**, `idField?: string` put back on `LookupFieldMetadata`: red, TS2578 on `_idField` and TS2344 on the moved-off assertion. - **A3**, `GeolocationFieldMetadata` put back in the union: red, TS2578 on `_geolocation`. - **A4**, `buildValidationRules`' field-level `pattern` read put back: 2 rows of the new `fields` pin red ("expected { pattern: … } to be undefined", "expected [ 'maxLength', 'pattern' ] to deeply equal [ 'maxLength' ]"). - **A5**, the field-level `validate` read put back: 1 row red ("expected { validate: [Function validate] } to be undefined"). The `location` control row stayed green, as expected: that branch replaces `rules.validate` wholesale. The first attempt at A2 to A5 was a no-op: each replacement contained its own anchor, so the tool refused it, and the command never ran. Those readings were discarded and re-run with replacements that consume the anchor. ## Changesets - `12242-types-kept-positions.md`: `@object-ui/types` minor, BREAKING, FROM → TO for every member and the union. Patch round 1 corrects the pending objectui#12130 changeset's bullet about these keys in place. - `12242-fields-validation-rules.md`: `@object-ui/fields` minor, BREAKING for hosts that hand-write `pattern` / `validate` in code. Those hosts lose the client-side check; the server enforces the object's `format` rule on save. - `plugin-grid` (internal note text) and `app-shell` (the same carrier object, now built in a typed const before the render branch) carry no behaviour change and get no release entry; the presence gate counts their files under these two declarations. ## Acceptance notes (observed, not filed) - `text.mdx` teaches the `format` rule as a prose list, not a code fence. `check:doc-types` reads every fenced `type` value as a node type. Its per-file ledger (where round one exempted `object.mdx`'s `json_schema`) is outside this claim's file surface, and prose is outside that gate's question. - `ENRICHED_FIELD_METADATA_KEYS` (`plugin-detail`) still copies `idField`, `picker`, `subtitle` and `dueLike` off the served object definition. The installed spec refuses all four on a field, so for spec-compliant data those copies reach nothing. The ruling says behaviour is unchanged, so they stay. Carrier: none. - No in-repo path emits the form renderer's `validation.pattern` default message any more; `buildValidationRules` was its only producer. `check:i18n-dead-keys` (report-only, exit 0) already listed the pack key as needs-review before this change. Carrier: none. - `buildValidationRules` still reads `required_message`, `min_length_message`, `max_length_message`, `min_message` and `max_message`, which no type declares. The spec's answer on them was not measured here. Carrier: none. - `GeolocationFieldMetadata` stays exported, marked `@deprecated`, rather than removed. That follows the `VisibilityCondition` precedent, and the barrel (`index.ts`) is outside this claim's file surface. Removing it would be a separate decision. --- _Generated by [Claude Code](https://claude.ai/code/session_015YrgVw7e65cs1ndbebt46G)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #22227
Clause-②: yes
The protocol half of objectstack-ai/objectui#11815's ruling D (declared-only overdue). The maintainer's words there: 「每个状态不是可以自己配置颜色吗?done 配置为红色不是元数据的 bug 吗?为什么平台要兜一圈」, then 「同意」 to option D.
What
On the
dateanddatetimefield definitions (packages/spec/src/data/field.zod.ts):dueLike: boolean(optional, no default) declares the date a deadline. Absent means not a deadline, and nothing is inferred from the field's name.settledWhen(optional,EvaluatedExpressionInputSchema, the same schema asvisibleWhen/readonlyWhen/requiredWhen) is a per-record CEL predicate. While it holds, the deadline is settled.Declared = enforced (ADR-0049), at two seams:
FieldSchemasuperRefine, themaxLength/valueDomain/rowstemplate)dueLikeon any type outsideDEADLINE_FIELD_TYPES(date,datetime),settledWhenon any such type, andsettledWhenunlessdueLike: truefield.zod.tsos build,os validate, the object save door)settledWhenthat does not parse, reads a bare field, names an undeclared field, or reads a root the field-rule family does not bindpackages/lint/src/validate-expressions.ts, the field-rule passNo select option carries a terminal marker. Nothing changes in objectui here: objectui#11815 is
Blocked-by:this card and reads the keys after this spec publishes.Mechanism hypotheses, measured
git grep -n "dueLike\|settledWhen"over this tree atdee7692f0bfinds 3 hits, allCHANGELOG.mdprose (packages/console,packages/platform-objects,packages/spec), and no source. The view schemas refuse the key:ListColumnSchema.safeParse({ field: 'due_date', dueLike: true })answersunrecognized_keyswith keysdueLike, while the control{ field: 'due_date' }parses. So no spec-validated source can spell it, and no ADR-0087 entry is added. The changeset declares no break, andcheck-adr-0087-registrationjudges only breaking declarations (breakingDeclaration: "wideningadds NO signal"). It exits 0.FIELD_RULE_BOUND_ROOTS=record,previous,parent.today()is a stdlib function in every CEL env, not a scope name, so it applies.valueis not a root inSCOPE_ROOTS, so it reads as a bare field reference and is refused.settledWhenjoins the same loop asvisibleWhenand gets exactly that verdict. No new scope name is added. The pins: the three bound roots are admitted,current_usergets the family's root verdict namingsettledWhen,today()passes, andvalueis refused. LikevisibleWhen, the slot is display only. The server-side gates further down (theparentgate, the null guard, the traversal refusal) stay withrequiredWhen/readonlyWhen.field.zod.tsscopes a type-specific key with a superRefine over aReadonlySetof types (BOUNDED_STRING_FIELD_TYPES,VALUE_DOMAIN_FIELD_TYPES,MULTILINE_EDITOR_FIELD_TYPES), not with a discriminated variant.DEADLINE_FIELD_TYPESfollows it, sodueLikeon atextfield is a locatedcustomissue at[dueLike].showcase_task: status options includedone, sosettledWhen: record.status == 'done'. app-todotodo_task:completed(also the app's own documented overdue definition,due_datepast ANDstatusnotcompleted). crmcrm_activity:completed. All three follow the card.Files beyond the claim's declared surface (each one demanded by a gate)
The claim fenced the surface to
field.zod.tsand its tests, the lint field-rule gate and its tests, generated baselines and forms, the docs page, the three example objects and one changeset. A newFieldSchemakey and a new CEL slot are also counted by these classification ratchets. Each one is red until the key is classified:packages/spec/liveness/field.json(check:livenessUNCLASSIFIED) and its generatedstate-counts/field.md.dueLikeislivecross-repo: objectuiresolveDueLikereads it, and the detail surfaces copy it throughENRICHED_FIELD_METADATA_KEYS, at main55e90fdand at the.objectui-shapinf0268ad78.settledWhenisplanned: no reader in either repo yet, with noauthorWarn.packages/qa/dogfood/test/expression-conformance.ledger.ts: a newsettledWhenCEL slot is unclassified otherwise. The new row isexperimentalandunevaluated, and it says NO EVALUATOR FOUND.packages/metadata-core/src/object-schema-fls-references.ts:FIELD_REFERENCE_POSITIONSis pinned against the liveFieldSchemashape.dueLikeiskeepandsettledWhenisexpression.packages/lint/src/validate-predicate-path-refs.test.ts: the shipped-form predicate census moves 85 → 87 for the two new form rows.packages/platform-objects/src/apps/translations/*.metadata-forms.generated.ts: generator output plus six hand-authored leaves, in two commits. The*.source-hashes.generated.tsfiles are byte-identical to base.object-lifecycle-panel-echo-decisions.test.tsmoves its translated-label control 665 → 667.packages/drivers/driver-sql/src/builtin-column-collision.ts(the tenth ratchet),packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts(the eleventh),packages/lint/scripts/check-doc-formula-expressions.mjsandpackages/lint/src/validate-field-consumers.ts(the field-rule family lists).None of them is touched by any of the 12 open PRs (file lists read while this PR was being built).
Patch round 1 (HEAD
c73e779f9a; REWORK6084352738on #22227, contract review FAIL6084330967, then PASS6085939799)a520b4eea5and fixed:driver-sqlFIELD_KEY_STORAGE_CLASSclassifiesdueLikeandsettledWhenaspresentation(c064f4b335);metadata-protocolprotocol.meta-types-degenerate-derivation.test.tsserves 77fieldproperties, up from 75 (d0af72c6f8).settledWhenjoins the field-rule family lists:FIELD_RULE_SLOTS/FIELD_RULE_TEXT_REincheck-doc-formula-expressions.mjs(three self-test cases; ablation turns exactly those red),BEHAVIOUR_SEGMENTSinvalidate-field-consumers.ts, and the 字段级*When的用户根拒绝只认current_user—— ADR-0068 的两个别名user/ctx.user静默放行,写哪个拼写决定拿不拿得到诊断 #6585 user-alias table.origin/main2e10c9abe0merged (23c192bfad), clean; no regeneration owed. PR feat(spec)!: PROTOCOL_VERSION 17 → 18 in an ordinary PR — regenerated spec-changes.json and upgrade guide, ^18 handshakes, pre-mode lockstep exception (#22085 Q1 → B) #22215 (protocol 18) landed after it; this PR adds no ADR-0087 entry, so no re-sync is owed.c73e779f9a: metadata-protocol 223 files / 28,347; driver-sql 218 / 3,635; lint 130 / 5,958; metadata-core 17 / 387; service-messaging 48 / 534; platform-objects 68 / 1,078; spec local 631 / 18,829, repo 54 / 915; objectql local 391 / 7,716; cli unit 274 / 4,053; the three example apps' suites andvalidategreen. 118 derived gate families run, 0 not measured. CI: 35 runs, 33 success, 2 expected skips.Tests, round 0 (HEAD
a520b4eea5, all locked runs throughos-verify-lock)@objectstack/spec,--project local: 631 files, 18824 passed, 1 todo.--project repo: 54 files, 915 passed. Both ran at832743b836; the one later commit touches only aplatform-objectstest.@objectstack/lint130 files / 5956 ·@objectstack/metadata-core17 / 421 ·@objectstack/objectql(--project local) 390 / 7698 ·@objectstack/cli(--project unit) 274 / 4037 · example-crm 5 / 45 · example-todo 7 / 238 · example-showcase 33 / 408 · dogfoodexpression-conformance.test.ts7 / 7. All green at832743b836.@objectstack/platform-objects68 files / 1078 passed ata520b4eea5.objectstack validatepasses for all three example apps.typecheckis green for spec (includingcheck:test-typecheck, which caught one error in the new pin before this head), lint, metadata-core, platform-objects, dogfood and the three example apps, ata520b4eea5.@objectstack/cliintegration tier: not run locally, declared to CI. The diff touches no spawn entry.Ablation, three runs, each committed first and mutated through
scripts/ablation-replace.mjs. Each anchor hit once, the blob changed, and each restore was proven with blob == HEAD and an emptygit diff HEAD. The subjects resolve tosrcthrough relative imports, so nodistleg applies.settledWhendropped from the lint field-rule loopcurrent_user,value, object door, slot list), 386 greendueLike !== truebranch →falsefalse, ondateanddatetime)dueLiketype refusal →falsedueLike: falseoff-type)All three turned red, which was the expected direction.
Gates:
dispatch-gates --commandsre-derived on the actual diff (--repo objectstack-ai/objectstack) is unioned with the dispatched list: 119 commands, all exit 0 ata520b4eea5. The re-derivation added 10 families:check:generated,check:engine-double-contract,check:i18n,check:i18n-stale-fill,check:objectql-double-limit,check:platform-checklist,check:quick-reference-counts,check:type-check-coverage,check:type-check-debtandcheck:where-matcher.dispatch-gates --ranreports 114 derived families, 114 run, 0 NOT-MEASURED, all with a recorded exit code.Acceptance notes
crm_activityhas a second finished state.cancelledexists besidecompleted. Under the ruled predicate a cancelled activity past due keeps the overdue affordance.record.status in ['completed', 'cancelled']is a one-line change if that is wanted; the card's text was followed. app-todo'sdeferredis in the same position, and the app's own overdue definition counts onlycompleted.previous/parenton a display predicate. The family's bound roots are admitted forsettledWhenas they are forvisibleWhen. A display-row evaluator may have neither bound. That is objectui#11815's to honour or a later narrowing card's to decide (carrier: objectui#11815).dueLikereaches the cell only on the detail surfaces today. objectui's grid copy set does not carry it, and objectui still also guesses due-likeness from the field name. Both retire with objectui#11815 (carrier: objectui#11815).settledWhenis a 35th evaluated-expression position.packages/spec/src/shared/evaluated-slot-population.test.tshand-lists 34 (pinnedtoBe(34)), andevaluated-slot-union.ts:10says 34. No gate is red, and the three-spelling refusal holds by composition. Carrier: spec(ui): field-level conditionalFormatting — a field declares[{ condition, style }]cell rules (CEL overvalueandrecord), the row block's own grammar, for presentational rules that carry no semantic (maintainer-directed) #22228, which adds the next evaluated position on the field definition and adds both rows (contract review6085939799③).Generated by Claude Code