feat(spec): Field.valueDomain, one closed standard-domain vocabulary and one membership predicate shared by settings specifiers and object fields - #15133
Conversation
…d predicate (#14168) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
… and one membership predicate shared by settings and fields (#14168) Maintainer ruling 2026-09-02 (option A, spec half): FieldSchema gains a `valueDomain` slot whose vocabulary is exactly SpecifierValueDomainSchema's three members; the vocabulary and the membership predicate move to shared/value-domain.zod.ts and both the settings specifier and the field slot reference them. The write-path refusal (engine) and the settings door's re-point (services) are the follow-up cards. - shared/value-domain.zod.ts: ValueDomainSchema, ValueDomain, isValueDomainMember, ISO_3166_ALPHA2_CODES (+ test) - settings-manifest.zod.ts: SpecifierValueDomainSchema is now an alias of the shared schema (same name, same three members, same shape) - field.zod.ts: the slot, VALUE_DOMAIN_FIELD_TYPES (text only — measured narrower than the maxLength family), the superRefine applicability refusal - api/errors.zod.ts: FieldErrorCode member `value_domain` (ADR-0114 D1/D2) - system/validation-message.ts: four-locale templates for value_domain and its three per-domain variants - liveness/field.json: `planned` row for the new authorable key - generated: api-surface, export-origins, declaration-map, authorable-surface, json-schema manifest, reference docs, liveness counts, skill reference indexes (pure regeneration) - hand-written docs: fields, field-types, validation-rules, objectql schema, error-catalog, quick-reference count, llms.txt inventory count - changeset: @objectstack/spec minor Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
… merging main (#14168) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
📓 Docs Drift CheckThis PR changes 2 package(s): 17 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 1 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 131 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 e45d15e6bcb50431a3115f98ee2c5f31a73c362c && git checkout e45d15e6bcb50431a3115f98ee2c5f31a73c362c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin fcc42e6c1de9df5c65ab60166bf15c3aeea5bfe2 b6f10508ad27d3447d160e8339f1b58e7d4a7d2f && git checkout -B drift-repro fcc42e6c1de9df5c65ab60166bf15c3aeea5bfe2 && git merge --no-ff b6f10508ad27d3447d160e8339f1b58e7d4a7d2f
node scripts/docs-audit/affected-docs.mjs --json fcc42e6c1de9df5c65ab60166bf15c3aeea5bfe2
|
|
CI on Generated by Claude Code |
…KEY_STORAGE_CLASS (#14168) The builtin-column-collision pin requires every FieldSchema key to be classified storage or presentation. `valueDomain` is write-time membership validation of the written string against a standard — never read by `createColumn`; the column stays the string `maxLength` sizes — so it joins `minLength` on the presentation side. Widening ruled by the domain:spec seat (comment 5534967364 on #14168); the test file is untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
…t census on the merged tree (#14168) Regeneration commit after merging origin/main (merge commit ef67726): the os-regen driver deferred content/docs/references/index.mdx and content/docs/permissions/system-context.mdx; both are re-derived here from the merged tree by the repo's own generators (gen:schema + gen:docs via check:generated --fix; gen:system-context-census). No behaviour change. Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i Co-authored-by: Claude <noreply@anthropic.com>
Contract review (Clause ②) — PASS · ACCEPTReviewer of record: the Head and window (readings at 2026-09-04T03:17Z)
The widening, verified in the diff
Report checklist
Seat rulings on the report's open questions (open to the maintainer's veto)
Out-of-scope group (sweep criterion: copies of the membership predicate and consumers of the key outside the spec)
Follow-up cards this seat files at landing
LandingAt all-green on
Generated by Claude Code |
|
Landing provenance — Landing window readings, all on head
Sequence: ready → squash auto-merge → Generated by Claude Code |
…s liveness row live Seat ruling 1 on PR #15133: the engine card adds the two form rows in the same stroke that flips the liveness row `planned` to `live` — declared = enforced = shown. Both rows' `visibleWhen` mirrors `VALUE_DOMAIN_FIELD_TYPES`; the object form's choices are derived from the vocabulary rather than re-typed, so the control cannot become a second opinion on what the closed vocabulary is. The liveness row now cites the record-validator seam beside `maxLength`'s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
Fixes #14168
Spec half of maintainer ruling A (comment 5507503059, director seat, 2026-09-02, verbatim reply 「同意」). Dispatched by the domain:spec PM seat (session
session_0174WZTU6XcFcS7g2kykC53i, seat post #6017); Clause ②: yes (a published field schema widens its accept set), contract review at tier.The ruling, quoted where it is the operative criterion
This PR delivers the contract: the slot, the shared vocabulary, the shared predicate, the declared refusal (catalog member + translatable templates), the generated products and the docs. The write-path refusal itself (engine) and the settings door's re-point onto the shared predicate (services) are the follow-up cards the PM files at ACCEPT; #14238 sequences after this PR.
What changed
packages/spec/src/shared/value-domain.zod.ts(new):ValueDomainSchema(the three members, in the same order),ValueDomain,isValueDomainMember(domain, value),ISO_3166_ALPHA2_CODES. Exported from@objectstack/spec/shared.SpecifierValueDomainSchema/SpecifierValueDomaininsystem/settings-manifest.zod.tskeep their names and are now an alias of the shared schema (expect(SpecifierValueDomainSchema).toBe(ValueDomainSchema)is pinned — identity, not a copy). No member added or removed;check:api-surface/check:export-origins/check:declaration-mapregenerated (moved origin, unchanged shape).FieldSchema.valueDomain?: ValueDomainSchemawith a.describe()stating what it constrains (membership of the WRITTEN value, themin/max/maxLengthtransition-gate class; a stored value is not re-read) and the applicability rule. Refused at parse on every other type with a locatedcustomissue at[valueDomain]naming the type, through the same superRefine doormaxLength/minLengthuse (VALUE_DOMAIN_FIELD_TYPES, the message enumerating the set itself).isValueDomainMember:iana_time_zoneis theIntl.DateTimeFormatprobe;iso_4217_currencyis the key set of the package's own CLDR snapshot;iso_3166_alpha2is the explicit 249-code list (exact uppercase). Details and the duplication window below.FieldErrorCodegainsvalue_domain(ADR-0114 D1: the code is the property's own snake_case name, likemax_length; D2: a new constraint KIND adds a catalog member), andvalidation-message.tsgainsvalue_domainplus three per-domain rendering variants in all four platform locales (the completeness pins require every locale to carry every key).data/Field:valueDomain,shared/ValueDomain), json-schema manifest, reference docs (incl. the newreferences/shared/value-domain.mdx), liveness state counts, and the eightskills/*/references/_index.mdpointer files (one generated line each, see Governed surface below).check:generatedsecond run exits 0.data-modeling/fields.mdx,data-modeling/field-types.mdx,data-modeling/validation-rules.mdx(text table + summary),protocol/objectql/schema.mdx,api/error-catalog.mdx(field-level catalog table),getting-started/quick-reference.mdx(Shared Protocol total 7 to 8 — the curated table did not gain a row),packages/spec/llms.txt(206 to 207 schemas; shared 12 to 13, "Value Domain" named in the prose).content/docs/permissions/system-context.mdxwas re-anchored by the census--fix(field.zod.ts line 1540 to 1580 —field.zod.tssits above anchored declarations).liveness/field.jsongains aplannedrow forvalueDomain(evidence: the applicability door and the shared predicate; note states honestly that the record validator does not read the key yet and names the engine card as the flip tolive).@objectstack/specminor, additive, no BREAKING banner. Measured:check-adr-0087-registrationjudges only declared-breaking changesets (this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)), so it expects no disposition marker on an additive key; the changeset carries the house-stylenot-required (accept-set expansion)comment anyway, as thecarryOverprecedent does.Measurements
Applicability (the choice).
BOUNDED_STRING_FIELD_TYPES(themaxLength/minLengthfamily) has twelve members: text, textarea, email, url, phone, password, markdown, html, richtext, code, signature, qrcode.valueDomaintakes the NARROWER family,textonly. A domain member is one short identifier and the whole stored value is that identifier; of the twelve, onlytextstores a single plain string. The other eleven store a multi-line body (textarea, markdown, html, richtext, code), a value that already carries its own shape family (email, url, phone — a currency code is never an email), a masked credential (password, ADR-0100) or a data URI (signature, qrcode).selectis deliberately excluded: its membership boundary is its exhaustiveoptionstable; the settings specifier's "options degrade to a suggestion list" semantics would be a second ruling, not a widening of this set. #14238's two timezone columns aretextcolumns, so the narrower family serves the first consumer. Pinned: the three values accepted ontext; a stranger (iso_8601_date) refused withinvalid_valueat[valueDomain], first sentenceInvalid option, the three members listed;valueDomainonnumber(and on 18 other types, the eleven bounded-string siblings included) refused withcustomat[valueDomain]naming the type; positive controls (absent key stays absent; domain composes withmaxLength/minLength); the derived JSON schema carries the three-member enum.Predicate home and definitions.
packages/specalready ships pure shared verdicts over checked-in data (currencyPrecisionContradictionover the CLDR table,filterVerdict, the comparand-shape door), and the ruling names one predicate shared by two doors, so it lives beside the vocabulary. Prime Directive #2 is addressed in the module header: the predicate is pure, dependency-free, holds no state and reads no service. Definitions, each re-measured on node v22.22.2 by the shared test:iana_time_zone= theIntl.DateTimeFormatprobe. Pinned:UTC,Asia/Kolkata,Europe/Kyivaccepted (and all three absent fromIntl.supportedValuesOf('timeZone'), which returns 418 and listsAsia/Calcuttainstead);Europe/MunichandMars/Olympusrefused;europe/zurichaccepted (the probe is case-insensitive — that is the pinned definition, unchanged from the settings door).iso_4217_currency= the key set ofdata/currency-fraction-digits.ts(CURRENCY_FRACTION_DIGITS, CLDR 48.0 snapshot). Measured on the baseline: 162 keys, equal in both directions toIntl.supportedValuesOf('currency')(162) — the exact set the settings door enforces today. The test pins that equality as the drift detector, and pins the definition's known gaps (VED,XAUabsent) rather than papering over them. Exact uppercase (usdrefused),Object.prototypekeys refused.iso_3166_alpha2= the explicit 249-code list, exact uppercase. Pinned: 249, each two uppercase letters;US/GB/CN/CHaccepted;ZZ,UK,XX,AA,QM,QZ,EU,usrefused;Intl.DisplayNamesre-measured as NOT an oracle.Duplication window and the services re-point. Until the services follow-up card lands,
packages/services/service-settings/src/value-domains.tscarries its own copies. Not "fixed" here — spec must not import services, and services stays untouched by this card. The symbol-by-symbol re-point that card makes (value-domains.tsto@objectstack/spec/shared):isIanaTimeZone(value)(module-private) →isValueDomainMember('iana_time_zone', value)iso4217Codes()/ theIntl.supportedValuesOf('currency')cache (module-private) →isValueDomainMember('iso_4217_currency', value)(same 162-code set; the cast for the ES2020libgoes with it)ISO_3166_ALPHA2(module-private) andISO_3166_ALPHA2_CODES(exported for its pins) →ISO_3166_ALPHA2_CODESfrom spec (same 249 codes; the structural pins invalue-domains.test.tsre-point unchanged)DOMAIN_MEMBERSHIP(the private record) →(value) => isValueDomainMember(domain, value)per member, orfirstRejectedDomainMembercalling the predicate directlyknownValueDomain,firstRejectedDomainMember,valueDomainPhrasingstay in services (door behaviour and prose, not membership);SpecifierValueDomainkeeps resolving through@objectstack/spec/system.Whether the settings door's own refusal code moves from
invalid_valueto the newvalue_domainmember is that card's question (open question 2 below).Refusal contract (item 4 measurement). The family declares its codes in spec:
FieldErrorCodeinpackages/spec/src/api/errors.zod.ts(min_length,max_length,min_value,max_value,max_scale), a closed catalog with no ledger tier, and the objectql write seam emits exactly those (packages/objectql/src/validation/record-validator.ts:570-571,fail('max_length', { maxLength: def.maxLength, actual: s.length }), buildingFieldValidationError { code: FieldErrorCode, message, label, constraint, value }). The settings door spells its domain refusalinvalid_valuewithconstraint: { valueDomain }(settings-service.ts:1808-1832docblock,:1024-1035env path). Per ADR-0114 D2 ("a new kind of constraint is a type-system change and deserves a line in the spec"), this PR declaresvalue_domainbeside the family, so the engine card implements a declared contract:fail('value_domain', { valueDomain: def.valueDomain, actual: s }, 'value_domain_' + def.valueDomain, undefined, s)renders through the shipped templates.Governed surface — certified regeneration only
The diff touches
skills/*/references/_index.md(eight files, one generated pointer line each: every skill index enumerates theshared/modules, so the new module appears in each). Pure regeneration bypnpm --filter @objectstack/spec gen:skill-refs, byte-exact against this tree (check:skill-refsgreen insidecheck:generated); no hand-authored file underskills/**. Per Prime Directive #14 and the maintainer's 2026-09-01 ruling the queue guard lifts a certified pure regeneration with zero approvals; the PM decides the landing.Deviations and things to know
system/validation-message.ts(+ test), the ADR-0122 pin file (type-alias-convention.pin.test.ts:Iso867for the new bare alias; the pin count 829 to 830 with its recorded reason),settings-manifest.test.ts,packages/spec/llms.txt,quick-reference.mdx,error-catalog.mdx,field-types.mdx, and the census re-anchor — each forced by a gate named in the verification table. Not touched:packages/services/**,packages/objectql/**,packages/core/**, objectui,content/docs/releases/**.data/field.form.ts,data/object.form.ts) are hand-writtenvisibleWhenlists that showmaxLength/minLengthfor exactly the enforced set. They are NOT edited here: showingvalueDomainin Studio's field form before the write path enforces it would advertise an inert capability (ADR-0078 / Prime Directive chore: version packages #10). The engine card adds the two form rows in the same stroke it flips the ledger row tolive(open question 1).origin/mainwas merged before opening (one overlapping file,system-context.mdx, regenerated by the census on the merged tree; the deferred regeneration was discharged in its own commit aspre-commitrequires).describe()text names no issue ids (check:doc-authoringclean: 14725 customer-facing strings); ADR ids are cited where a decision is load-bearing.field.zod.tsis a declared trigger file of the v18 hold currency: no exchange-rate / home-currency conversion — dynamic multi-currency amounts cannot be reconciled or aggregated across currencies #8345; no rider taken. The hot-file diff is the import, the applicability set, the slot and the superRefine block.Read-and-report for the follow-up cards (no edits made)
(a) Engine.
packages/objectql/src/validation/record-validator.ts:FieldDefinterface:156-172(addvalueDomain?: string); the bounded-string branch:568-583readsdef.maxLength/def.minLengthand returnsfail('max_length', { maxLength, actual })/fail('min_length', ...);failis:486-492(code: FieldErrorCode, constraint?, messageKey?, options?, value?) building throughbuildFieldError(:292-310,valuerides the interpolation namespace so a template can say{{value}}). ThevalueDomaincheck belongs in that branch after the length checks, judged only whent === 'text'(the applicability set), callingisValueDomainMember(def.valueDomain, s)from@objectstack/spec/shared.packages/coreenforces nothing here;packages/core/src/security/resolve-authz-context.ts:936carries a module-privateisValidTimeZonewith the same probe (a later re-point candidate, not this ruling's).(b) Services. The symbol-by-symbol re-point is listed above under Duplication window.
(c) objectui. Not generic. The Studio field editor is the hand-written inspector
packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx(:626-641:isTexty(type)gates twoInspectorNumberFields bound todef.minLength/def.maxLength, i18n keysdesigner.field.minLength/.maxLengthinmetadata-admin/i18n.ts:1403,3297); the generic JSON-schema formSchemaForm.tsx(:4-9, renders/meta/typesschema) explicitly does not cover Object/Field designers (:20-23), andmergeServerFields.ts:30-32states "SchemaForm renders ONLY the fields a form declares". So the new key reaches OpenAPI and the/meta/typesschema without a UI change, but a Studio control for it is an objectui edit (a select bound todef.valueDomain, shown whentype === 'text'), sequenced with the engine card. Measured at objectuia27d153c.Patch round — surface widened by one source module (seat ruling, comment 5534967364)
CI on
18c6c7f49was red inTest Core (5/6)andTemporal Conformance (live PG + MySQL)with one failing test:packages/drivers/driver-sql/src/builtin-column-collision.test.ts› "classifies EVERY FieldSchema key, and invents none" — the pin that requires everyFieldSchemakey to be classified storage or presentation inFIELD_KEY_STORAGE_CLASS. The domain:spec seat ruled (comment 5534967364, with its in-place correction) to widen this card's surface by exactly that one SOURCE module,packages/drivers/driver-sql/src/builtin-column-collision.ts, on the four-condition adjacent-fix path; the test file is untouched (PR #11336 touches it) and nothing else underpackages/drivers/**moves.The entry:
valueDomain: 'presentation', placed besideminLengthwith the reason in its trailing comment. The table's docblock draws the line at what the DDL reads:maxLengthis'storage'becausecreateColumnsizes the column (varchar(n) vs TEXT),minLengthis'presentation'as write-time validation.valueDomainis write-time membership validation of the WRITTEN string against a standard;createColumnnever reads it and the column stays the stringmaxLengthsizes, so the DDL has nothing to discard — theminLengthclass. The docblock makes no case for'storage'. No sibling landing onorigin/maintouches the module since this branch's base (git log f594e70d..origin/main -- <module>is empty).Before / after readings of that test file (same worktree, driver-sql closure built, under the lock, exits captured by redirect):
18c6c7f49)Tests 1 failed, 10 passed (11)·AssertionError: unclassified FieldSchema key(s): expected [ 'valueDomain' ] to deeply equal []750d24603)Tests 11 passed (11)·VERDICT command-exit 0· verbose reporter:✓ … classifies EVERY FieldSchema key, and invents none·✓ … puts the honoured half on the presentation side and the column shape on the storage side(and✓ … puts \required` on the PRESENTATION side`)pnpm --filter @objectstack/driver-sql typecheck(tsc --noEmit)--no-inline-configover the modulepnpm --filter @objectstack/spec check:generatedat750d24603All 15 generated artifacts are up to date.750d24603Test Files 7 passed (7)·Tests 416 passed (416)·VERDICT command-exit 0node scripts/check-system-context-census.mjsat750d24603check-system-context-census: OK — 106 elevation read sites in 20 packages across 45 files, all anchored; 140 anchors resolve, 27 declared non-read.Final head after the patch round:
750d24603. Merge round (GitHub read the PRmergeable_state: dirtyagainstorigin/mainfcc42e6c1, so no pull_request CI ran on750d24603):origin/mainwas merged as merge commitef6772673viascripts/pm/os-regen-merge.sh, and the two artifacts the os-regen driver deferred —content/docs/references/index.mdx(schema totals 1590 to 1593, automation 69 to 72, main's additions) andcontent/docs/permissions/system-context.mdx(one anchor re-derived,protocol.ts:1747to:1795, main's move) — were regenerated on the merged tree by the repo's own generators (check:generated --fix=gen:schema+gen:docs;gen:system-context-census) in regeneration commitb6f10508a, whose staged diff was exactly those two files; secondcheck:generatedrun exits 0 (All 15 generated artifacts are up to date.). Final head:b6f10508a(pushed fast-forward; no rebase, amend or force;origin/mainis an ancestor). Atb6f10508a: spec build34/34d.ts present; spec typecheck exit 0 (check:test-typecheck: OK — 54 file(s) / 261 error(s) / 145 pinned signature(s)); spec pin setTest Files 7 passed (7)/Tests 416 passed (416); driver-sql classification testTests 11 passed (11);check:strictness-ledgerexit 0 (counts.md is current — 440 site(s) measured); census check exit 0 (106 elevation read sites ... 140 anchors resolve);check-governed-merges.mjs --testover the eightskills/*/references/_index.mdpaths exit 0 —governed-surface predicate: 0 of 8 path(s) hit the register/✅ NOT governed, all eight lifted under thegenerated-surface exception (#11705). No behaviour changed in either round.needs:contract-reviewstays hung on both carriers.Verification (head
18c6c7f49for the spec half — the table below was taken there; the patch round above cites750d24603; exits captured before any pipe; verdict lines quoted)pnpm --filter @objectstack/spec build(lock)check-dts-emitted: @objectstack/spec - 34/34 declared declaration file(s) present.·VERDICT command-exit 0shared/value-domain.test.ts,data/field.test.ts,system/settings-manifest.test.ts,api/errors.test.ts,system/validation-message.test.ts,type-alias-convention.pin.test.ts,data/currency-precision-iso4217.test.ts(lock)Test Files 7 passed (7)·Tests 416 passed (416)·VERDICT command-exit 0pnpm --filter @objectstack/spec typecheckcheck:test-typecheck: OK — ... 54 file(s) / 261 error(s) / 145 pinned signature(s)(ledger unchanged);tsc --listFiles -p tsconfig.test.jsonlists all six edited/new test filespnpm --filter @objectstack/spec check:generated(clean, second run)All 15 generated artifacts are up to date.(liveness:field 93 classified (live 89, planned 3, dead 1))--no-inline-configover the 12 edited TS filespnpm check:doc-authoring14725 customer-facing string(s) across 724 spec sources clean — no internal issue-id referencespnpm check:nul-bytescheck-nul-bytes: OK (scanned 8229 text file(s) ...)check-adr-0087-registration --base 2cc4610/check-changeset-no-major --base 2cc4610this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)·This diff introduces no major bump.@objectstack/lintclosure build + typecheck + vitest (lock)Test Files 94 passed (94)·Tests 2900 passed, 5 skipped (2905)·VERDICT command-exit 0check:error-code-provenance,check:exported-any,check:dual-source-exports,check:browser-reachable-entries,check:entry-nameability,check:empty-state,check:strictness-ledger,check:variant-docs,check:yaml-examples,check:llms-txt,check:quick-reference-counts,check:spec-parsed-alias,check:error-code-casing,check:system-context-census,check:type-check-coverage, lintcheck:doc-formula-expressions/check:doc-security-posturenode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(89 commands, derived at18c6c7f49, identical to the pre-merge derivation)check:dual-build-cjs-loads(PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/ ... nothing was measured),check:type-check-debt(PREREQUISITE NOT MET ... --re-measure cannot run: 30 workspace dependenc(ies) ... have no built type entry point),check:skill-examples(packages/client-react/dist holds no .d.ts declarations — the package is not built),check:react-declaration-parity(needs MANIFEST=sdui.manifest.json). All four need the full monorepo build or objectui's manifest — CI's run.ab05a825a, restore leg proven)valueDomain:slot line (grep -c1 to 0;git diff --stat1 deletion);vitest src/data/field.test.ts -t valueDomain:Tests 23 failed, 2 passed, 211 skipped(direction: red, as expected; the two survivors are the positive controls that do not read the slot). Restore:git checkout HEAD -- path;git hash-object=HEAD:blob5b6f87db72c19cb856e1e980a8f05c4e3ac6e7f0both sides;git diff HEAD --statempty;git status --porcelainempty. No build in the resolution path:field.test.tsimports./field.zodas relative source, not throughdist/.Generated by Claude Code
Generated by Claude Code
Generated by Claude Code