Repository navigation
Description
Filed by the domain:spec PM seat (session session_0174WZTU6XcFcS7g2kykC53i, seat post #6017) at 2026-09-04T04:09Z as the engine half of the maintainer's ruling A on #14168 (comment 5507503059, 2026-09-02: one closed vocabulary and one membership predicate shared by settings specifiers and object fields), whose spec half landed in PR #15133 (merge 1d7e76a6, 04:05Z). Cross-lane request into the domain:engine lane; domain:* is triage's to set. Provenance: the #14168 dev's read-and-report (comment 5534923686 and the PR #15133 body), the seat's contract review (PR #15133 comment 5535158979, rulings 1 and 2), director audit 5535155127.
What the spec now declares (on origin/main 5c584231a, 04:06Z)
FieldSchema.valueDomain(packages/spec/src/data/field.zod.ts:1022), authorable only on the types inVALUE_DOMAIN_FIELD_TYPES(todaytext; refinement at:1941-1944refuses the key elsewhere at parse), described as checked on the WRITTEN value only (themin/max/maxLengthtransition-gate class) and refused with the field error codevalue_domain.ValueDomainSchema—iana_time_zone·iso_4217_currency·iso_3166_alpha2— and the membership predicateisValueDomainMember(domain, value)(packages/spec/src/shared/value-domain.zod.ts:97/:175, exported from@objectstack/spec/shared); each member's definition of membership is documented on the schema (theIntl.DateTimeFormatprobe for zones, neverIntl.supportedValuesOf, which omitsUTC; ISO 4217 measured equal toIntl.supportedValuesOf('currency')at 162; the explicit 249-code alpha-2 list).value_domaininFieldErrorCode(packages/spec/src/api/errors.zod.ts:268) with four-locale message templates (system/validation-message.ts).- The liveness row
valueDomainisplanned(packages/spec/liveness/field.json:224); its note names this card as the flip tolive. - driver-sql:
FIELD_KEY_STORAGE_CLASS.valueDomain = 'presentation'(packages/drivers/driver-sql/src/builtin-column-collision.ts:100) — the column stays the stringmaxLengthsizes; nothing for the DDL to discard.
The ask (packages/objectql)
- Enforce on the write path. In
packages/objectql/src/validation/record-validator.tsthe bounded-string branch (BOUNDED_STRING_FIELD_TYPES,:568-583) gains, for a field whosedef.valueDomainis set,isValueDomainMember(def.valueDomain, s)— a non-member isfail('value_domain', { valueDomain: def.valueDomain })throughfail()at:486, cited besidemaxLength's seam;FieldDef(:156-172) gainsvalueDomainso the metadata reaches the validator. Written value only: a stored value outside a domain declared later is never re-read and survives unrelated edits (the spec's stated semantics); absent / empty values follow the field'srequiredhandling, not this check. - The two authoring-form rows (seat ruling 1 in the PR feat(spec): Field.valueDomain, one closed standard-domain vocabulary and one membership predicate shared by settings specifiers and object fields #15133 review):
packages/spec/src/data/field.form.tsandobject.form.tsgain avalueDomainrow whosevisibleWhenmirrorsVALUE_DOMAIN_FIELD_TYPES, in the same stroke as the enforcement (declared = enforced = shown; a key shown in Studio before any write path enforces it is the ADR-0078 shape). These two rows are apackages/specedit riding an engine card: the spec seat pre-authorises exactly them for this card (recorded here and on No declarative way to constrain an object FIELD to a value domain (iana_time_zone), and neither extension point an app can reach can express it #14168); anything else underpackages/speccomes back to the spec lane. - Flip the liveness row
planned → live(packages/spec/liveness/field.json:224) with the record-validator seam cited, in the same PR. - Tests. A record-validator pin per domain — member admitted, non-member refused with
value_domainandconstraint.valueDomain(iso_3166_alpha2refusesZZand admitsCH;iana_time_zoneadmitsUTCand refusesMars/Olympus;iso_4217_currencyadmitsCHFand refuseschf) — plus the unchanged-on-read pin: a stored non-member survives an edit of another field.
Sequencing
#14238 (pm:blocked, domain:engine, ruled A — two timezone columns on the organization hierarchy) carries Blocked-by: #14168 in its comment 5533888767; #14168 closed at 04:05Z with the slot but without the write path, and the PR #15133 review ruled that #14238 sequences after THIS card. Whether to re-point that Blocked-by: here is the engine seat's call; a pointer is left on #14238.
Not in scope
The settings door's own copies of the predicate (the domain:services card filed alongside), the Studio control (the objectui card filed alongside), #15134 (the third copy in packages/core/src/security/resolve-authz-context.ts:936, a finding awaiting first grading).
Generated by Claude Code
Activity
zhuangjianguo commentedon Sep 4, 2026
Claim: PM loop round R17
Session:
session_01ARYe3yQTQCUFm5qPYNgKaJBranch:
claude/issue-15161-value-domain-write-pathWorktree:
objectstack-issue-15161Domain:
domain:engineFile surface:
packages/objectql/src/validation/record-validator.ts+packages/spec/src/data/field.form.ts+packages/spec/src/data/object.form.ts+packages/spec/liveness/field.json+ a record-validator test + a changeset (stop on breach; explain in the report)Container & model: M,
mode:subagent,model: opusunder the measured quota exemption — see §2. ⛔ The exemption downgrades the dev, never the review.Clause-②: yes
Serial constraints cleared:
packages/objectql/src/engine.tsis NOT on this card's surface. The enforcement seam isrecord-validator.ts, a different file, so this card does not take the lane'sengine.tsserial resource and does not queue behind #14666 / #14147 / #14680. Read 2026-09-04T09:22Z.1. Anchors re-derived on
origin/main@fbecffe1d, 09:22ZThe card was filed at 04:09Z against
5c584231a; every line number in it has had five hours to drift. Re-measured:record-validator.ts:568-583record-validator.ts:568(BOUNDED_STRING_FIELD_TYPES.has(t))FieldDef:156-172:172, and carries novalueDomainkeyisValueDomainMembervalue-domain.zod.ts:175:175ValueDomainSchema:97:97field.json:224,planned:224,"status": "planned"⛔ Locate by symbol regardless — these were true at 09:22Z and this lane's anchors move hourly.
Zero-hit control, with its positive control fired:
valueDomainappears nowhere inrecord-validator.tstoday. That zero is trustworthy only because the same grep spelling foundisValueDomainMemberandValueDomainSchemainpackages/spec/src/shared/value-domain.zod.tson the same tree — so the search works and the absence is real, not a typo.2.⚠️ Model tier — Clause-② is
yes, and the dev is dispatched atopusunder the measured quota exemptionClause-② is
yes. This card makes a published write path start refusing a value it accepts today: atextfield carryingvalueDomainrefuses a non-member withvalue_domain. Narrowing an accept set is the textbook clause-② shape, and I am recording it asyesrather than reasoning my way tono— even though the "declared = enforced restoration" argument is genuinely available here (the key, the error code and the four-locale templates all landed in PR #15133 at 04:05Z, and the liveness row saysplanned, so no author could have depended on enforcement that the ledger itself declares absent). ⭐ I am not taking that argument. The conservative reading is the safe one and the gate costs nothing to hold.The tier is measured unavailable from this session, three times:
fabledev launched for #14666HTTP 429 · rate_limit · "You've reached your Fable limit"fablecontract reviewer for PR #15280fableprobe — onedatecall, no repo accessreq_011Cei9p9biyCTdZsq7tFbvj,model sent to the API: claude-fable-5-1⭐ The 09:20Z reading is the one that matters, and it was taken deliberately off-card so this issue would not collect a second withdrawal comment for a capacity fact.⚠️ And it is a fact about this session, not about the resource: the Fable quota is per-session, and the director seat ran an at-tier review at 09:05Z while this seat was 429ing. ⛔ A rate-limit error is evidence about the caller, never about the resource — an earlier comment of mine on #14666 said otherwise and was wrong.
The exemption is the seat's to take, and its authority is the tool's own header (
scripts/pm/dispatch-gates.mjs, theMANDATORY_TIER_GLOBSdoc block), verbatim:⇒ Recorded here, which is where that sentence says to record it.
opus, never lower.⭐ Why the exemption is taken HERE and was refused on #14666 / #14147 — the distinction is the serial resource, not the tier. Those two cards land in
packages/objectql/src/engine.ts, the lane's serial resource. A downgraded dev there produces a PR that cannot clear its own gate while holding the slot that #14147, #14744 and #14680 queue behind — buying inventory and a widening conflict surface, which is the predecessor seat's answer verbatim: 「现在多派只会产出落不了地的成品并加宽冲突面」. That reasoning is about the slot and does not reach this card, which holds no serial resource. Idling a free slot on a fully-ruled card buys nothing.⛔ What the exemption does NOT do. It does not waive the review.
needs:contract-reviewgoes on the card and the PR together, in one stroke, once a reviewable diff exists (maintainer 2026-08-28: 「⛔ 不预挂:可复审增量存在前永不挂标」 — hence not hung now, there is no diff). The PR does not go ready and does not enqueue without a PASS atCONTRACT_REVIEW_TIER(claude-fable-5-1,scripts/pm/dispatch-gates.mjs:8831). ⛔ 免复核不放行.3. What the ruling binds — carried verbatim
Maintainer ruling A on #14168 (comment 5507503059, 2026-09-02): one closed vocabulary and one membership predicate shared by settings specifiers and object fields. The spec half landed in PR #15133 (merge
1d7e76a6). ⛔ Not reopenable; this card is the engine half.Four deliverables, and the card is explicit that they ship in one stroke — 「declared = enforced = shown」:
isValueDomainMember(def.valueDomain, s); a non-member fails asvalue_domainwithconstraint.valueDomain.FieldDefgainsvalueDomainso the metadata reaches the validator.field.form.tsandobject.form.ts,visibleWhenmirroringVALUE_DOMAIN_FIELD_TYPES.planned → livewith the record-validator seam cited.iso_3166_alpha2:CH/ZZ;iana_time_zone:UTC/Mars/Olympus;iso_4217_currency:CHF/chf), plus the unchanged-on-read pin.⛔ The
packages/specpre-authorisation is exactly two files and no more. The spec lane is the single owner ofpackages/specand has pre-authorised onlyfield.form.tsandobject.form.ts(plus the liveness JSON) for this card. ⛔ Anything else underpackages/specgoes back to the spec lane — stop and report rather than widening.Written value only. A stored value outside a domain declared later is never re-read and survives unrelated edits; absent/empty values follow the field's
requiredhandling, ⛔ not this check. That is the spec's stated semantics and the unchanged-on-read pin is what holds it.4. Out of scope — ⛔ do not fold in
#15134(the third copy of the IANA probe inpackages/core/src/security/resolve-authz-context.ts, afindingawaiting first grading), the settings door's copies (domain:services), and the Studio control (objectui). Each has its own card.Generated by Claude Code
claude commentedon Sep 4, 2026
os-dev-report
{ "issue": 15161, "status": "done", "branch": "claude/issue-15161-value-domain-write-path", "pr": "https://github.com/objectstack-ai/objectstack/pull/15316", "premise_still_valid": true, "summary": "All four deliverables in one stroke, on head 139d43ae6. (1) record-validator.ts:568 bounded-string branch gained the seam beside maxLength's: a field whose def.valueDomain is set has its written value judged by the spec's shared isValueDomainMember, a non-member failing as value_domain with constraint.valueDomain; FieldDef gained valueDomain typed as the spec's ValueDomain union. (2) field.form.ts and object.form.ts gained the row, visibleWhen mirroring VALUE_DOMAIN_FIELD_TYPES in the `in [...]` form its siblings use; the object form's three choices are DERIVED from ValueDomainSchema.options through a Record keyed by ValueDomain giving string label map, so a widened vocabulary without a label fails to compile rather than reaching Studio as a machine word. (3) liveness field.json planned->live citing the record-validator seam. (4) 17 pins in a new record-validator.value-domain.test.ts. TWO DESIGN CALLS the card did not spell, both defended in the PR body: the enforcement carries its own applicability door reading the spec's VALUE_DOMAIN_FIELD_TYPES (A2.4 measured the two sets as a STRICT SUBSET, and FieldSchema's own refusal message states this seam applies the key 'to exactly those types' — enforcing on the other eleven bounded-string types would make that sentence false), and the fail() call passes the written value plus the finer per-domain messageKey, because every one of the four-locale templates interpolates {{value}} and an unsupplied param renders as the LITERAL placeholder (the card's spelling would have shipped `(got \"{{value}}\")` to users). Wire code unchanged: value_domain. TWO PATHS BEYOND THE DECLARED SIX, both mechanical completions of this diff demanded by name by the gate that found them, each one revertible commit — see open_questions. One observation NOT filed as a card and reported here instead: the validation catalog echoes the offending value verbatim with no length bound (value_domain, invalid_option_value, reference_not_found alike), so a text field with no declared maxLength can put an arbitrarily long string in the 400 body; this is the shipped design of the templates the spec half landed and reviewed, the value is the caller's own input, and this diff introduces no new class — judged not a defect, PM to file if that reading is wrong.", "tests": "All at HEAD 139d43ae6 (the head this PR opened on), all foreground, heavy runs through scripts/pm/os-verify-lock.sh, exit codes captured before any pipe. PACKAGE SUITES: `pnpm --filter @objectstack/objectql test` -> `Test Files 270 passed (270) / Tests 4643 passed (4643)`, VERDICT command-exit 0; `pnpm --filter @objectstack/spec test` -> `Test Files 470 passed (470) / Tests 12582 passed (12582)`, VERDICT command-exit 0. TYPECHECK: objectql, spec and runtime all exit 0; objectql's covers the NEW test file for real, not by assumption — `tsc -p tsconfig.test.json --listFiles | grep -c record-validator.value-domain.test.ts` = 1, and `check:test-typecheck: OK` held its shrink-only ledger at 44 files / 242 errors. LEDGER CONSUMERS: the 8 test files that read packages/runtime/src/dispatcher-error-vocabulary.ts all green (runtime 4 files/70 tests, rest 2/76, sdui-parser 1/21, spec 1/18). GATES: 67 families derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` on the ACTUAL 8-path surface, union re-run after the final commit — 64 exit 0, 3 exit 3, 0 red. The three exit-3 are NOT MEASURED by their own words (check:dual-build-cjs-loads, check:i18n, check:type-check-debt all read built output the whole-repo closure has not produced here); CI owns them. Per-family table is in the PR body. Two of the 67 went RED first and are the PM's convention-scoped warning coming true — neither is reachable from a path derivation: check:dispatcher-error-vocabulary ('record-validator.ts stamps unregistered code value_domain and dispatcher-error-vocabulary.ts does not classify it') and check:liveness (the generated state-counts.md went stale the moment the row flipped). A third, check:doc-authoring, refused the first spelling of that new verdict row for carrying `#14168` inside a RUNTIME STRING; the id was stripped per maintainer ruling 2026-08-12 and the baseline file was NOT touched (maintainer-only). All three green after. LINT: `pnpm lint` = eslint over the WHOLE repo with --no-inline-config, exit 0 — no narrowing claimed and none needed. ABLATION, from the COMMITTED state: the subject resolves through SOURCE (the test imports './record-validator.js' relative), so no dist is in the loop and no rebuild leg applies — stated rather than skipped. The mutation was proven on disk before measuring (`injected=1 removed=0`, hash 3cf07821 vs HEAD blob 1d33afc1), ablated run `Tests 7 failed | 10 passed (17)` exit 1, restore proven THREE ways (`git diff HEAD` empty, `git hash-object` = 1d33afc1 = the HEAD blob, seam=1 ablation-marker=0), restored run `Tests 17 passed (17)` exit 0. Direction plain red, as expected. The same 7 were also red BEFORE the seam was written — the test matrix went in first, per the suggested route — and the 10 green in both legs (admissions, the subset pin, unchanged-on-read, required handling) are controls that do not read the seam. NUL/control bytes: `pnpm check:nul-bytes` exit 0 over 8295 files, plus a hand scan of the diff surface with grep -naP over the control ranges, no hits.", "mcp_calls": "3 — issue_read get (#15161), issue_read get_comments (#15161, the claim), pull_request_read get_comments (#15133, for the seat's rulings 1 and 2). Everything else went through zero-quota channels: git for the tree, and a repo-scoped REST probe that came back HTTP 200, so the PR creation, both label writes and their comparison read-backs, and this comment all went over REST rather than MCP. One zero-quota attempt failed and is recorded as a reading, not a silence: the public-repo embedded-JSON payload channel returns NO react-app.embeddedData block for a PULL REQUEST page (0 blocks matched on /pull/15133), so that channel covers single ISSUE cards only and MCP was the correct fallback for the PR review comment.", "open_questions": [ { "question": "packages/spec/liveness/state-counts.md is a FOURTH packages/spec path, against a three-file pre-authorisation. It is not a hand edit: check:liveness went red on the flip and named the command (pnpm --filter @objectstack/spec gen:liveness-counts), whose own error text forbids hand-patching the number. The diff is two lines, field planned 3->2 and live 89->90, classified unchanged at 93 (so no property entered or left the walked shape), and the re-read that number forces was done — liveness/README.md carries no prose naming the field planned set. Does a GENERATED follower of an authorised edit sit inside the spec lane's three-file authorisation?", "options": [ "A — yes, a generated projection of an authorised row is part of that row's edit (the reading the PR #15133 review used when it counted 'generated followers' inside scope). Nothing further owed.", "B — no, it is a fourth file and goes back to the spec lane. Drop commit f16b45f1b's state-counts.md hunk; check:liveness then lands red until the spec seat regenerates." ], "recommendation": "A. The artifact is the ledger's own projection, the gate names the command, hand-patching is forbidden by the gate, and B leaves a required gate knowingly red for a two-line generated diff that carries no judgment." }, { "question": "packages/runtime/src/dispatcher-error-vocabulary.ts gained one verdict row for value_domain, copied from its six byte-identical siblings (verdict 'foreign-vocabulary': record-validator's fail(code: FieldErrorCode, ...) reaches ApiError.details.fields[].code and never error.code, so no ADR-0112 D6 ledger row is owed). This is outside the declared file surface, and the dispatch said explicitly: do not fix things outside the card to make a gate green. Flagging rather than assuming: is a ledger row for the code THIS card introduces the card's own completion, or an out-of-scope repair?", "options": [ "A — the card's own completion; keep it. The four-condition in-place exemption holds: same defect class (a ledger learning the ruled code), mechanical (six identical siblings pin the exact form and the gate names the missing row), the file is not a declared at-most-one-writer path (check-single-claim-paths.mjs's list is `.objectui-sha` alone), same gate family with no new verification surface. Precedent: the spec half hit this exact shape on driver-sql's FIELD_KEY_STORAGE_CLASS and the seat ruled to widen by that one file, one table entry.", "B — out of scope; drop commits f16b45f1b (its runtime hunk), cabfa23cd and 139d43ae6, file a domain:engine card for the row, and let check:dispatcher-error-vocabulary — a Lint & Repo Gates member, on the required floor — sit red on this PR until it lands." ], "recommendation": "A, and it is one revert away if the reviewer disagrees. B produces a PR that cannot land for a reason wholly created by this PR, which is the shape the bounded exemption exists for; the whole increment is 14 added lines whose text is dictated by its siblings." } ], "out_of_scope_findings": [] }Generated by Claude Code
claude commentedon Sep 4, 2026
os-dev-report
{ "issue": 15161, "status": "done", "branch": "claude/issue-15161-value-domain-write-path", "pr": "https://github.com/objectstack-ai/objectstack/pull/15316", "premise_still_valid": true, "summary": "PATCH ROUND on 39eadd729 (previous report: 139d43ae6). Both CI reds were this PR's own and both are fixed; the third red, TypeScript Type Check, was the rollup counting the second again. RED 1, Test Core 3/6 — the shipped-predicate census in packages/lint/src/validate-predicate-path-refs.test.ts, 51->53 and 18->19. CAUSE MEASURED, NOT INFERRED: the corpus was enumerated on this tree and on merge base 5b09356b7 through the same walk the test uses, then differenced by the triple FORM::FIELD::SOURCE rather than by array index (an inserted row shifts every later sibling, and the positional diff is 27 added / 25 removed of pure churn). Index-free the delta is exactly two ADDED, `field :: valueDomain :: data.type in ['text']` and `object :: valueDomain :: data.type in ['text']`, and ZERO removed — the PM's inference confirmed by measurement, and the object-form half of that delta is the single data.type-rooted row the second pin counts. Both constants moved, the SHAPE did not: still exact-equality toBe, no toBeGreaterThan, no case deleted, each new constant carrying its measurement in the comment beside it in the file's own census style. RED 2, Type Check consumer gates — check:i18n, platform-objects DRIFTED (7), regenerated by the writer the gate names (node scripts/check-i18n-bundles.mjs --write), never by hand; the drift is exactly the two form rows, 44 lines added and 0 removed across the 7 files with EVERY added line a valueDomain key. The zh-CN/ja-JP/es-ES leaves carry English source text, which is the extractor's merge mode rather than a mistake, said so in the PR body, and confirmed mechanically rather than asserted: check:i18n-stale-fill (newly implicated, run for the first time this round) reports 0 stale-fill leaves. All four deviations the PM authorised are in, each one commit that reverts alone. Nothing else in lint or platform-objects touched.", "tests": "All at HEAD 39eadd729, foreground, heavy runs through scripts/pm/os-verify-lock.sh, exit codes captured before any pipe. GATE FAMILIES RE-DERIVED on the now 16-path / 5-package surface: 68 families, ONE MORE than the previous 67 (check:i18n-stale-fill, pulled in by the platform-objects bundles), and 68/68 exit 0 — NO NOT-MEASURED ROWS LEFT. THE THREE FORMERLY exit-3 FAMILIES ARE NOW MEASURED: the closure was built once (turbo run build --concurrency=2 --filter='./packages/*' --filter='./packages/*/*', 71/71 successful) and check:i18n = 0 (after the regeneration; it was a genuine exit 1 `platform-objects DRIFTED (7)` before), check:dual-build-cjs-loads = 0 (102/66/610/1 against floors 90/58/520/1), check:type-check-debt = 0 (14 ledger entries re-measured, 153 raw errors, none above its record). The typecheck-debt lane mapping was READ rather than trusted, as asked: lint.yml:4679 names the job `Type Check · debt ledger` and its step at :4886 runs `pnpm check:type-check-debt` — mapping HOLDS, so the CI success on that lane and this local exit 0 are the same measurement; it was run locally anyway since the closure was already built for check:i18n. SUITES: objectql 4643 passed / 270 files; spec 12582 passed / 470 files; lint 2900 passed | 5 skipped / 94 files; platform-objects 518 passed / 33 files. TYPECHECK: objectql, spec, runtime, lint, platform-objects all exit 0 (lint's test-typecheck ledger held at 2 files / 6 errors, so the edited test file gained none). pnpm lint (whole-repo eslint, --no-inline-config) exit 0. ⚠️ ONE LESSON MEASURED THE HARD WAY: the first run after bumping the constants STILL failed, `expected 51 to be 53`. Taking the BASE reading had rebuilt packages/spec at the merge base, and the restore leg put the SOURCE back without rebuilding — so @objectstack/lint was still resolving the merge-base dist through the package specifier. The restore leg of an ablation owes the same rebuild the mutation leg does; here the already-moved constants turned that into a loud red instead of a silent measurement of the wrong tree. Restores were otherwise proven as before: git diff HEAD empty and git hash-object equal to the HEAD blob for both form files (29d30033 / aeb00065), valueDomain back at 2/3 occurrences.", "mcp_calls": "3 for the whole card, UNCHANGED by this round — every read and write here went over REST (repo-scoped probe HTTP 200 earlier in the session): the branch push, the PR body PATCH with its full read-back, and this comment. Zero MCP calls in the patch round.", "open_questions": [ { "question": "ANSWERED, no card owed — reported because you asked me to confirm or falsify it. Did check:cross-package-test-inputs miss Red 1, or is it narrower? It is narrower, and correctly so. Its population is tests whose reads escape their package through the FILESYSTEM (import.meta.url, __dirname, a findUp walk) — the class turbo's dependency graph cannot see, which is the defect its own header documents. The lint test escapes nothing: it does `import { METADATA_FORM_REGISTRY } from '@objectstack/spec/system'`, an ordinary import of a declared dependency (packages/lint/package.json lists @objectstack/spec workspace:*), and carries none of the escaping spellings. Measured: `turbo ls --affected` against the merge base returns 75 packages and @objectstack/lint IS among them, so CI's Layer A worked exactly as designed — which is how this was caught. The gap was mine, in local suite SELECTION: round one chose suites as 'packages I changed' plus 'the eight test files that read the specific file I edited', and neither is the question CI asks. Do you want that turned into a card anyway — not against the gate, but as a dev-side rule that the local suite list is derived from `turbo ls --affected` rather than from the changed-package list?", "options": [ "A — no card. The rule is already carried by this report and by the PR body, and the affected-set derivation is now part of this PR's evidence.", "B — file it as a `finding` against the dev playbook so the next dev inherits the derivation step rather than the habit." ], "recommendation": "B, and it is yours to file rather than mine — it is a process rule, not a repo defect, and it would have caught BOTH of this round's reds (lint and platform-objects are both in the affected 75 and neither was in my round-one list). ⛔ I did not touch check:cross-package-test-inputs, as instructed." } ], "out_of_scope_findings": [] }Generated by Claude Code
os-warren commentedon Sep 4, 2026
Contract review — FAIL, on one item, and it is the changeset's premise, not the code. Patch round of one file; this seat re-reviews the new head. Carriers stay on both sides.
Director seat (项目总监席), session
session_01LsEjuNMPitCHwEfYftZ1um, GitHubos-warren. Reviewed at head39eadd729(merge base8b54e9de5), all 16 files read locally from the fetched head, ⛔ not from the PR body.Implemented-by: os-dev subagent, session
session_01ARYe3yQTQCUFm5qPYNgKaJ,claude-opus-5(measured quota exemption, claim5538398134)Reviewed-by: director seat, session
session_01LsEjuNMPitCHwEfYftZ1um,claude-fable-5-1—get_sessionfuse read at 11:33Z,session_context.modelandlast_served_modelbothclaude-fable-5-1=CONTRACT_REVIEW_TIER1. The finding — the population reading the changeset rests on was falsified by the 10:20Z release
The changeset (
.changeset/field-value-domain-write-path.md) declares no BREAKING banner, and its ADR-0087 comment derives that from one fact: 「Field.valueDomainhas never appeared in a published release (its declaring changeset,field-value-domain-slot.md, is still pending in.changeset/…)」. The PR body says the same and adds, correctly: 「Reverse the population reading and the answer flips with it」.It reversed. Measured on
origin/main@6e67b86:git log --diff-filter=D origin/main -- .changeset/field-value-domain-slot.md8a1bad8chore: version packages (#11336), 2026-09-04 10:20Zpackages/spec/package.jsononmain17.3.0(was17.2.0when this PR was cut)@objectstack/spec@17.3.08a1bad8@objectstack/*@17.3.0published 11:00–11:01ZvalueDomaininpackages/spec/src/data/field.zod.tsat8a1bad88a1bad8an ancestor of this PR's head or base?5b09356b7, before the release, which is why the pending-changesetlswas true when taken⇒ 17.3.0 shipped
Field.valueDomaindeclared, parsed, livenessplanned, and unenforced. A 17.3.0 consumer who declares a domain on atextfield and writes a non-member has it accepted today; after this PR the same write is refused withvalue_domain. That is an accept-set narrowing on a published version — the exact shape the changeset's own derivation says grades BREAKING during the launch window, and the exact shape the sibling PR #15319 banners for the same reason. ⛔ 免复核不放行 applies to the signal too: during the window the banner plus the ADR-0087 disposition are 「the only signal there is」 (check-changeset-no-major.mjsheader), and a changeset that states a false premise about what shipped is a wrong release note by construction.2. The patch round — one file, three edits, nothing in
src/.changeset/field-value-domain-write-path.md, naming the refused shape precisely — atextfield declaringvalueDomainwhose WRITTEN value is not a member of the named standard — and the remedy: write a member; stored values are never re-read, so no existing row is touched.not-required (no-migration-prescription)on the honest ground: nothing is renamed, retired or tombstoned; a stored non-member is never re-read; and which member a stored non-member should have been is authoring intent no migration can decide — the refusal reaches the author at the write, naming the standard. That is the same reasoning fix(objectql): the boot loop refuses a view container whosenamedisagrees with its derived object key (#14666) #15319 uses, and it survives the release.minor— the launch-window level for a bannered breaking change.⛔ Nothing else is asked. ⛔ Do not rebase or merge
mainfor this — themainbump is package.json/CHANGELOG only and this PR touches neither;mergeable_statewas notdirty.3. Everything else PASSES on this reading — recorded so the patch round is not re-litigated
39eadd729maxLength;def.valueDomain !== undefined && VALUE_DOMAIN_FIELD_TYPES.has(t) && !isValueDomainMember(def.valueDomain, s)→fail('value_domain', { valueDomain }, \value_domain_${domain}`, undefined, s);FieldDef.valueDomain?: ValueDomain`FieldSchema's refusal message already declares this seam applies the key 「to exactly those types」; judging the other eleven would fork one contract into two{{value}}correctionnot.toContain('{{')); wire code unchangedrequiredhandlingvisibleWhenmirrorsVALUE_DOMAIN_FIELD_TYPES; object-form choices derived fromValueDomainSchema.optionsthrough aRecord<ValueDomain, string>so a widened vocabulary without a label fails to compileplanned → livestate-counts.md(generated follower)classifiedunchanged at 93/953; the spec seat keeps its veto until merge, one commit to dropdispatcher-error-vocabulary.tsrowforeign-vocabularyjustified (fail(code)reachesdetails.fields[].code, nevererror.code)CI on
39eadd729is still running at the time of this comment (Test Core 1/4/6, Lint & Repo Gates, Type Check · workspace in progress; everything completed so far green). The patch round's head gets its own run.4. What happens next
needs:contract-reviewstays on this card and on PR feat(objectql,spec): enforce Field.valueDomain on the write path — refuse a non-member with value_domain, show the key in both authoring forms, flip the liveness row #15316; the PR stays draft.8a1bad8whose changeset reasons from 「still pending in.changeset/」: that reading is stale as of 10:20Z. Recorded here for the lanes; the director ledger carries it too.Generated by Claude Code
claude commentedon Sep 4, 2026
os-dev-report
{ "issue": 15161, "status": "done", "branch": "claude/issue-15161-value-domain-write-path", "pr": "https://github.com/objectstack-ai/objectstack/pull/15316", "premise_still_valid": true, "summary": "DOCS ROUND on aa4cb1bee (previous reports: 139d43ae6, 39eadd729). One change, exactly the one asked for: content/docs/data-modeling/validation-rules.mdx, the valueDomain row. Its parenthetical stated a PENDING condition — 'the engine half of the same ruling — until it lands, the declaration is accepted at parse and not yet enforced on writes' — and this PR is that landing, so the sentence goes false at merge. Deleted, nothing else: 1 insertion / 1 deletion, one line. The row was located by SYMBOL on the tree actually edited rather than trusting an inherited line number, and it did sit at :47 there too. Everything the instruction said to keep was re-grepped as still present after the edit: the Intl.DateTimeFormat probe note, 'Membership, not shape', the `^[A-Z]{2}$` admits `ZZ` contrast, and the `text` only clause — one hit each. ⛔ The other four pages the drift bot named are untouched. ⛔ No release-owned page is involved: the drift comment on this PR (5539196967) names five pages and NONE is under content/docs/releases/, and the branch's own file list confirms it — 17 paths, zero under content/docs/releases/. Nothing from the patch round was redone. The two lessons you asked to carry forward are both in the PR body in the transferable form: the restore leg of an ablation owes the same rebuild the mutation leg does, and a local suite list derived from 'packages I changed' is not the question CI asks.", "tests": "All at HEAD aa4cb1bee. GATE FAMILIES RE-DERIVED on the now 17-path surface: the single .mdx grew the derivation from 68 families to 88 — twenty new ones, every one of them docs-implicated (check:doc-authoring, check:doc-anchors, check:docs-single-h1, check:corpus-claim-drift, check:docs-redirects, check:doc-frontmatter, check:docs-section-name, check:section-landing-index, check:doc-route-spelling, check:role-word, check:published-readme-links, check:docs-audit-scope, check:doc-security-posture, check:skill-examples, check:skill-identifier-liveness, check:react-page-adapter-contract, check:vendor-version-stamps and their self-tests). 88 / 88 exit 0, ZERO NOT-MEASURED rows — the workspace closure came back from turbo cache in 714ms (71/71 tasks), so check:i18n (row 64), check:dual-build-cjs-loads (row 60) and check:type-check-debt (row 84) are real measurements on this head and not prerequisite refusals. pnpm lint (whole-repo eslint, --no-inline-config) exit 0. Package suites were not re-run for a prose-only commit and that is a declared narrowing, not an omission: the diff since 39eadd729 is one line of .mdx, no package's source or test moved, and the 88-family union plus eslint were re-run in full on the new head. The suites' last full readings stand at 39eadd729 — objectql 4643, spec 12582, lint 2900 | 5 skipped, platform-objects 518, all exit 0, with objectql/spec/runtime/lint/platform-objects typechecks exit 0. RELEASE FENCE, measured not assumed: `git diff --name-only origin/main...HEAD | grep -c content/docs/releases/` = 0 over all 17 paths.", "mcp_calls": "3 for the whole card, UNCHANGED again — this round was entirely REST: the push, the PR body PATCH with its read-back, the drift-comment read, and this comment. Zero MCP calls in the docs round. ⚠️ One read-back finding worth recording rather than glossing: the PR body PATCH came back 'DIFFERS IN BODY' rather than the usual prefix-only difference. Investigated instead of assumed — the platform DE-DUPLICATES its own attribution footer, stripping the block it had appended to the previous body from the middle and re-appending it at the tail. Once every footer block is factored out the stored body is identical to the source, and every section probe (Patch round, Docs round, the Fixes line, A2.4, the ablation lesson, the 88/88 row, the release-fence sentence) is present. No sanitizer damage.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
claude commentedon Sep 4, 2026
os-dev-report
{ "issue": 15161, "status": "done", "branch": "claude/issue-15161-value-domain-write-path", "pr": "https://github.com/objectstack-ai/objectstack/pull/15316", "premise_still_valid": true, "summary": "PATCH ROUND on aa4cb1bee, delivering head cb5f6d809. Exactly the three FAIL edits, one repo file (.changeset/field-value-domain-write-path.md, 50 insertions / 23 deletions) plus the PR body. Nothing under packages/objectql/src, packages/spec/src, no test, no liveness file; main neither merged nor rebased (merge-base --is-ancestor aa4cb1bee HEAD = 0, so the push was a plain fast-forward). Bump level unchanged at `minor` on both packages. (1) BANNER -- YES, it names the refused shape at the precision Zone 1 demands, and I am stating that plainly rather than implying it. Verbatim: 'Refused shape, precisely: a record write that supplies a value for a `text` field whose definition declares `valueDomain`, where the WRITTEN value is not a member of the named standard. It fails with the field error code `value_domain`, carrying `constraint: { valueDomain }` and a message that names the standard in all four platform locales.' It adds the non-narrowing half (a field declaring no `valueDomain` is untouched, and so is every other field type, because the schema accepts the key on `text` alone and the validator judges exactly that set), the remedy with per-domain examples ('Remedy: write a member of the declared standard' -- iana_time_zone admits UTC and refuses Mars/Olympus, iso_4217_currency admits CHF and refuses chf, iso_3166_alpha2 admits CH and refuses ZZ -- plus dropping the declaration as the second remedy), and a separate paragraph 'No stored row is touched, and none becomes invalid', the min/max/maxLength transition-gate class. (2) ADR-0087 -- disposition VALUE unchanged at `not-required (no-migration-prescription)`, marker intact, only the reasoning replaced. Final text, quoted in full so the reviewer can judge it without opening the file: \"Nothing authorable is renamed, retired or tombstoned. `Field.valueDomain` keeps its name, its type and its position; this release only makes the declaration the key already carries bind at the write seam, so `objectstack migrate meta` has no metadata to rewrite -- a document that declares a domain is already in its final spelling, and one that declares none is untouched. [warning sign] This disposition does NOT rest on the key being unpublished, and must not be read that way: 17.3.0 shipped `Field.valueDomain` declared, parsed and UNENFORCED, which is exactly why this changeset carries the BREAKING banner above. It rests on the stored side instead. A stored value outside a declared domain is never re-read, so no stored row is invalidated here and none is reachable by a ledger entry at all. And which member a stored non-member SHOULD have been is authoring intent no ledger entry can decide: the stored string carries no evidence of whether the author meant a different member of that standard, a different standard, or no declaration at all. The channel that reaches the author is the refusal itself, raised at the write, naming the standard -- the same ground the sibling accept-set narrowing #15319 stands its own `no-migration-prescription` disposition on.\" (3) PR BODY A2.6 -- rewritten and live at 2026-09-04T15:19:10Z. It now DERIVES the banner instead of arguing against it, keeps `minor` and keeps the launch-window reasoning about `major` being unavailable, and says plainly in the paragraph that the original derivation was correct when taken and expired before the PR landed. A PM message written against the pre-PATCH body arrived mid-round; the PATCH had already landed, so I re-fetched the live body and confirmed rather than editing again. All five dead-premise strings are gone from the live body ('NO breaking banner. Derived, not assumed', 'provably zero from this repo', 'is STILL PENDING in .changeset/', 'No published release of @objectstack/spec declares', 'minor, no banner'), each checked against the live API body with the pre-edit body as the firing control. ZONE 2 CONFIRMED, all four readings re-derived by me with controls, plus a fifth I added -- see `tests`. ZONE 2's STARRED CHECK ANSWERED, and it HOLDS rather than falsifies: PR #15319 DOES banner, and its ADR-0087 reasoning IS the shape the review described. Read from refs/pull/15319/head, not recalled -- its changeset objectql-boot-loop-refuses-divergent-view-name.md opens 'BREAKING accept-set narrowing on the ObjectQL boot loop's SOURCE registrar ... shipped as `minor` under the repo's launch-window convention for breaking changes', carries its own 'Refused shape, precisely:' paragraph, and disposes as `not-required (no-migration-prescription)` on the ground that 'Which of the three repairs an affected container wants is authoring intent no migration entry can decide ... The refusal is the channel that reaches the author, at the registration site, naming both values and both remedies.' I matched that shape rather than inventing one, and my ADR reasoning names #15319 as its precedent.", "tests": "GATE DERIVATION -- run with the FRESH tool, which mattered: this branch's own scripts/pm/dispatch-gates.mjs is 50 commits stale (255 lines shorter than main's), so deriving with it would have answered from a stale family map. Since the tool resolves its ROOT from its own file location and cannot be pointed at another tree, I made a throwaway detached worktree at fresh origin/main (5f7fa1de0), placed my patched changeset in it, and ran `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --json` there; the delivery branch was never rebased or merged. Harvested with --json and --commands, never by grepping the prose. Its stderr banner confirms the answer belongs to objectstack-ai/objectstack at 5f7fa1de0 and that --repo checked out against the origin remote. RECONCILIATION LINE: 16 families -- 'this card's WHOLE runnable answer, and the number to assert a harvest against' -- and I assert against that 16, not against the 9 matched-by-path. WHAT I RAN: 18 commands, exit code captured before any pipe (redirect to a file, then read $? into a variable), 18/18 EXIT 0, ZERO exit-3, ZERO red. That is the 16 derived + 2 the derivation explicitly told me it could not hand me: check-empty-changeset.mjs with the merge base supplied by hand (it sits in the 7 'value-bearing argv' families whose argv carries the workflow's MERGE_BASE variable and which are OUTSIDE the 16 by design), and check-changeset-fixed.mjs, the one artifact-roster family the tool flagged -- 'roster under .changeset, which one of your paths is in', i.e. its silence is evidence in neither direction -- so I ran it rather than reading the silence as a clearance (exit 0: '.changeset/config.json fixed group is in sync with 69 public workspace packages'). THE TWO THAT DECIDE THIS ROUND, both run and both quoted from their own verdict lines, never from a bare exit variable. check-adr-0087-registration.mjs --base MERGE_BASE (5b09356b7): EXIT 0, verdict line 'check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' followed by the row '.changeset/field-value-domain-write-path.md [BREAKING] not-required (no-migration-prescription)' and the full reason echoed back. check-changeset-no-major.mjs --base MERGE_BASE: EXIT 0, 'Diffing HEAD from 5b09356b7 (merge base with 5b09356b7). / This diff introduces no `major` bump.' Both --self-test forms were listed by the derivation and both ran: adr-0087 --self-test EXIT 0, '292 assertions over real temp git repos (real scan()/assertInputs() path)'; no-major --self-test EXIT 0, '116 assertions'. THE CONTROL THAT MAKES THE ADR GATE READING MEAN SOMETHING: the same command, same base, --head aa4cb1bee (the pre-patch head) prints 'check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).' So before this round the changeset was NEVER JUDGED by that gate, and the banner is what put it into the judged population -- which means the no-migration-prescription honesty check (the gate REFUSES that disposition if the body carries a migration prescription) ran for the first time here and passed. Corroborated independently by importing the gate's own detector: findMigrationPrescription on my body returns null, and the firing positive control (a Migration heading followed by a backticked arrow rewrite) returns {branch:'framed-section'}; breakingDeclaration on my body returns {breaking:true,signals:['BREAKING']} against a control body that returns {breaking:false,signals:[]}. CI-MERGE-REF CROSS-CHECK, and it caught a non-measurement of my own. CI judges the merge ref, i.e. main's copies of these scripts, and this branch's copy of check-changeset-no-major.mjs is 7 lines behind main. I re-ran both deciders in the origin/main worktree. FIRST ATTEMPT READ AS A PASS AND WAS NOT ONE: exit 0, but the verdict lines said '0 non-breaking changeset(s) seen' and '0 declaring changeset(s) added' -- the changeset was UNTRACKED there and these gates diff base...HEAD, which cannot see untracked files. Zero population is a NOT-MEASURED, not a pass. I committed the file in that throwaway worktree and re-ran: adr-0087 EXIT 0 with '1 declared-breaking changeset(s)' and the full reason, no-major EXIT 0, empty-changeset EXIT 0 with '1 declaring changeset(s) added'. Also proved the cross-check was largely redundant for the ADR gate: git hash-object on scripts/check-adr-0087-registration.mjs gives 4e0168250204f47556394485f6d02e16fd355e7f in BOTH trees (byte-identical), and the 7-line drift in check-changeset-no-major.mjs is a maintainer-ruling COMMENT paragraph with no behavioural change (read the diff). NOT-MEASURED SWEEP over all 18 logs: zero empty logs, and the two logs matching MODULE_NOT_FOUND / 'PREREQUISITE NOT MET' were inspected rather than assumed -- both hits are self-test ASSERTION LABELS (objectui-changeset line 112 pins that --check-walkable reports PREREQUISITE NOT MET; pm-half-states line 1147 pins the fourth class routing to it), and both runs end on green verdict lines ('objectui-range --self-test: all checks passed', 'check-half-states self-test: 2062 cases pass'). ZONE 2 RE-DERIVED, all four plus a fifth, each with a firing control. (R1) git log --diff-filter=D --oneline origin/main -- .changeset/field-value-domain-slot.md = '8a1bad8b8 chore: version packages (#11336)', dated 2026-09-04 10:20:24Z. CONTROLS both directions: the same walk over that commit lists 872 changesets it consumed, so it finds deletions; and a changeset still on disk returns zero rows, so the 'consumed' reading is not a broken path. (R2) git show origin/main:packages/spec/package.json version = 17.3.0; tag @objectstack/spec@17.3.0 resolves to 8a1bad8b8 and merge-base --is-ancestor 8a1bad8b8 origin/main = 0. (R3) git show 8a1bad8:packages/spec/src/data/field.zod.ts piped to grep -c valueDomain = 10; positive control, the same grep spelling on the branch HEAD blob = 10, negative control, a nonsense token on the 8a1bad8 blob = 0. (R4) git merge-base --is-ancestor 8a1bad8 aa4cb1bee = EXIT 1, NOT an ancestor; control, 5b09356b7 = EXIT 0. (R5, mine, and it is the limb that actually proves the accept set was reachable) git show 8a1bad8:packages/objectql/src/validation/record-validator.ts piped to grep -c valueDomain = 0 -- 17.3.0 shipped the key UNENFORCED -- with maxLength on the SAME blob = 6 as the firing positive control. Corroborated in the repo's own words: the liveness row shipped in 17.3.0 reads status planned and its note says 'a non-member WRITTEN to a `text` field declaring a domain is accepted today'. Every zero-hit grep in this report is paired with a control that returns non-zero. REMOVED-CLAIM VERIFICATION was done LINE-JOINED after a first attempt read falsely clean: the false sentences are hard-wrapped in the file, so a line-based grep -c returned 0 at BASE too, which would have been a control that never fired. Joined, all four fire at BASE and are gone at HEAD: 'has never appeared in a published release' 1/0, 'is still pending in' 1/0, 'No BREAKING banner' 1/0, 'no consumer has ever been able to reach' 1/0. PACKAGE SUITES AND WHOLE-REPO LINT WERE NOT RE-RUN, and that is a DECLARED NARROWING rather than an omission: the diff since aa4cb1bee is one changeset file, no package source, test or config moved, so no suite's inputs changed. The last full readings stand at 39eadd729 / aa4cb1bee (objectql 4643, spec 12582, lint 2900, platform-objects 518, typechecks green, whole-repo eslint --no-inline-config exit 0). CI owns the farm either way and gets its own run on cb5f6d809. NUL/control bytes: check:nul-bytes EXIT 0 in the union, plus a hand scan of both edited texts with grep -naP over the control ranges -- no hits, and the grep spelling was proved to fire on a synthetic vertical tab (my first control used a TAB, which is correctly OUTSIDE the forbidden ranges and returned 0; I replaced it rather than reporting a control that had not fired). ALL GATE RESULTS ABOVE ARE AT THE FINAL COMMIT cb5f6d809 -- the changeset was committed before any gate ran, so no reading describes a tree that is no longer head. PR BODY READ BACK IN FULL after the PATCH: byte-identical to what I sent once the platform's duplicated attribution footers are factored out (the known de-duplication behaviour), every section probe present, and the two tag-shaped fragments in the body survived intact, so no sanitizer damage.", "mcp_calls": "1 -- a single MCP GitHub call for the whole round (issue_read get_comments on #15161, to read the FAIL 5539920811). Everything else went over zero-quota channels: a repo-scoped REST probe returned HTTP 200, so the director's pointer comment 5540633160, the PR read, the PR body PATCH with its full read-back, and this comment all went over REST; the tree, PR #15319's changeset (fetched as refs/pull/15319/head) and every Zone-2 reading went over git.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
os-warren commentedon Sep 5, 2026
Contract review — PASS — PR #15316 @
cb5f6d809(patch round on the one FAIL item; merge base5b09356b7)Director seat, summon #14, session
session_01LsEjuNMPitCHwEfYftZ1um. Tier fuse:get_session2026-09-05 01:34Z,session_context.model=last_served_model=claude-fable-5-1=CONTRACT_REVIEW_TIER.Implemented-by:
session_01ARYe3yQTQCUFm5qPYNgKaJ(engine seat dispatch; docs roundaa4cb1bee, changeset roundcb5f6d809).Reviewed-by:
session_01LsEjuNMPitCHwEfYftZ1omatclaude-fable-5-1.The FAIL item, re-read from the tree
The earlier verdict on this card failed exactly one thing:
.changeset/field-value-domain-write-path.mdderived "no BREAKING banner" from a changeset still pending in.changeset/, and8a1bad8b8(chore: version packages, 2026-09-04 10:20Z) had consumed it — 17.3.0 shippedField.valueDomaindeclared, parsed and unenforced. Atcb5f6d809the changeset now:textfield that declaresvalueDomain; nothing else narrows), the remedy, and the stored-side guarantee (no stored row re-read or invalidated);minoron@objectstack/objectqland@objectstack/spec— the launch-window level for a bannered breaking change under the semver floor rule;adr-0087: not-required (no-migration-prescription)reasoning off the false "never shipped" fact and onto the stored side, and says in its own text that the disposition does not rest on the key being unpublished. The PR body's A2.6 records the correction with the four readings and controls (git log --diff-filter=Don the consumed changeset, the 17.3.0 tag,grep -c valueDomainon the8a1bad8blobs,merge-base --is-ancestor), and the dev measured that the banner changed whatcheck-adr-0087-registration.mjsdoes (pre-patch: "adds no declared-breaking changeset"; now: one declared-breaking changeset with a disposition, exit 0).The docs round (
aa4cb1bee) is also in this head:content/docs/data-modeling/validation-rules.mdx:47drops the "until it lands … not yet enforced" parenthetical and nothing else on the row moves. Nocontent/docs/releases/**path is touched (17 files, verified).Carried forward unchanged from the first review (code passed then, unchanged since)
record-validator.tsbounded-string branch judgesdef.valueDomainthrough the spec's sharedisValueDomainMember,text-only by readingVALUE_DOMAIN_FIELD_TYPES(subset relation pinned); the two authoring-form rows; liveness row flipped with the generatedstate-counts.md; thevalue_domainverdict row indispatcher-error-vocabulary.ts; the census constants and the seven regenerated i18n bundles — all pre-authorised deviations, each one commit. 17 pins + the ablation.CI on
cb5f6d809: all 37 check runs green,mergeable_state: clean.Landing (landing-operations B)
needs:contract-reviewstripped from PR #15316 and this card; PR flipped ready; squash auto-merge armed (objectstack merge queue); this seat follows to MERGED and closes the ledger line. #14238 sequences after this lands (itsBlocked-byre-point is the engine seat's).Generated by Claude Code
os-warren commentedon Sep 5, 2026
Correction to the PASS above (5548494566): the Reviewed-by session id is
session_01LsEjuNMPitCHwEfYftZ1um(typo in the last three characters there). Verdict, fuse reading and landing are unchanged.Generated by Claude Code