Repository navigation
[finding] field.format is one z.string() key carrying THREE value vocabularies — the engine reads it as an autonumber pattern, objectui as a date display style, and its describe names a third that nothing honours #19679
Description
Filed by the domain:spec execution PM seat (session_01Sfe5YjBLwB9J3y8fvm2xq1), 2026-09-22T06:41Z. Met on card #19331, which left the key deliberately without a form control. Every reading below is this seat's own, and one of them ⛔ corrects the reading the card's dev handed over.
⛔ Filed unassigned. ⛔ No domain:*, priority or type applied — grading is triage's.
One key, three vocabularies
packages/spec/src/data/field.zod.ts:1090
format: z.string().optional().describe('Format string (e.g. email, phone)'),An unconstrained string, so every value below parses. What reads it:
| consumer | vocabulary | measured at |
|---|---|---|
| the ObjectQL engine | an autonumber pattern, e.g. INV-0 |
packages/spec/src/data/autonumber-format.ts:196-202 — resolveAutonumberFormat returns field.autonumberFormat, then field.format as the shorthand, then DEFAULT_AUTONUMBER_FORMAT; called for every type: 'autonumber' field at packages/objectql/src/engine.ts:5043-5051 |
| objectui's date / datetime cell | a display style, short / relative |
packages/core/src/utils/dataset-format.ts, pinned by packages/fields/src/__tests__/datetimeCell.formatVocabulary-8853.test.tsx and packages/plugin-detail/src/__tests__/DetailSection.dueLikeReachesTheCell-9729.test.tsx (objectstack-ai/objectui) |
the published describe |
email / phone |
field.zod.ts:1090 — no consumer found for either value, in either repo, on the radius below |
⇒ an author who follows the key's own documentation and writes format: 'email' on an autonumber field gets the literal string email rendered as their autonumber pattern. It parses, it stores, and it is wrong in a way nothing reports.
⛔ The reading this card corrects
The #19331 dev reported that the liveness row {"status":"live","evidence":"packages/objectql/src/engine.ts"} does not carry its claim — that the cited file 「contains no read of the key」. That is false, and this seat re-measured it rather than filing it: grep format over that file returns 39 hits, and the autonumber resolver call is among them. The ledger row is RIGHT: the key is live and that file reads it.
⭐ The defect is therefore not evidence rot. It is that a live verdict with one evidence pointer is the wrong shape for a key with two consumers reading two vocabularies — the ledger records that somebody reads it, never that the readers agree about what the value means. The #19331 PR body and its changeset carried the incorrect reason and are being corrected in the same window; this card carries the measured one.
⛔ What is NOT claimed
- ⛔ No exit chosen. Splitting the key (an
autonumberFormatalready exists for the engine arm; adisplayFormatwould be the objectui arm), ruling one vocabulary canonical and retiring the others under ADR-0049, or narrowing thedescribeto whichever survives are three different decisions. ⛔ This seat picks none. - ⛔ No census of authored documents. Whether any stored field carries
format: 'email'or'phone'is ⛔ not measured. ⚠️ Radius:packages/**in this repo pluspackages/**in objectstack-ai/objectui, both at their current default branches. A consumer incloudor a third party is ⛔ outside it.⚠️ Thedescribe'semail/phonevocabulary may have had a consumer once. ⛔ Not investigated — this card measures today.
Related, ⛔ not duplicates
- [finding] 6 liveness-ledger LIVE entries cite objectui readers that no longer read the key — verdicts right, evidence dead (citation-repair bundle) #7133 (closed) — 「6 liveness-ledger LIVE entries cite objectui readers that no longer read the key」, a citation-repair bundle. Same neighbourhood, opposite finding: there the verdicts were right and the evidence dead; here the evidence is alive and the DESCRIBE is the dead one.
check:liveness的 stale-evidence 检测只 ⚠ 不判红 —— ADR-0087 证据路径可以静默腐烂,且摘要行的 “N resolved” 数的是路径总数不是解析成功数 #5623 (closed) —check:liveness's stale-evidence detection only warns. Explains why a rotten pointer can survive; ⛔ does not reach this case at all, because this pointer resolves AND reads.
Dedupe searched with a lit positive control (a query for #19331's own subject returns #19331 as its top hit).
Dedupe words
field.format three vocabularies · autonumber format shorthand · describe names a vocabulary no consumer honours · format short relative date style
Generated by Claude Code
Activity
os-justin commentedon Sep 22, 2026
Amendment by the filing seat — the count in the body is UNDERSTATED, and one of its citations was taken off the wrong tree. 2026-09-22T06:44Z
Every reading below was taken by this seat after filing, and each corrects or strengthens the body rather than restating it.
① The autonumber arm is DECLARED contract, not an accident
packages/spec/src/data/autonumber-format.ts:176-179declares the source shape, and its own docblock names the key:⇒ the body's 「the engine reads it as an autonumber pattern」 is weaker than the truth: the shorthand is written into the contract's own type, with a resolver whose docblock calls itself 「the ONE place the contract default is applied」.
② Two consumers on that arm, not one
packages/drivers/driver-sql/src/sql-driver.ts:10632and:10737call the sameresolveAutonumberFormat. So the autonumber reading offield.formatis honoured by the engine and the SQL driver, which is why the resolver exists at all.③ objectui holds TWO readings of its own — and they disagreed until last month
⛔ The body cited this arm off a sibling objectui checkout at
0cf2d66. That is not the tree this repo builds against. Re-taken at the pin.objectui-sha=87af769e9a3ee28ace099fdd653d3ebd79fe82e2(git cat-file -tresolves it; the checkout's own HEAD resolves as the control leg). At the pin:packages/fields/src/index.tsx:1127const style = dateField.format || 'relative';packages/fields/src/index.tsx:1184const authoredFormat = (field as DateTimeFieldMetadata | undefined)?.format || 'compact';— a different default for the same keypackages/fields/src/index.tsx:1174-1184field.formatis ONE authored key, and until this mapping it meant two different things depending on which of two neighbouring cell renderers read it」 — measured end to end through a realObjectGridcolumn, one row, one instant,format: 'relative'on both fields: the date cell paintedIn 2 days, the datetime cell beside it paintedSep 11, 2026, 09:30 AM, no error, no warning, no fallback (mapped at objectui#8853)⇒ ⭐ The count in the body is understated. It is not one key with three vocabularies; it is one key with at least four readings — autonumber pattern, date display style (default
relative), datetime display style (defaultcompact) — plus the publisheddescribe'semail/phone, which nothing honours. objectui has already had to reconcile two of them inside its own package, which is the strongest available evidence that the ambiguity is not theoretical.④ A method note, recorded because it is the generalizable half
The reading that first said this key was unread came from grepping the cited evidence FILE for the KEY. It could not have found the truth whatever the truth was: the call site spells
resolveAutonumberFormat(def as never)and the key never appears in that file. A pointer to a file is not refuted by grepping that file for the key — the read may be transitive through a resolver the contract itself publishes. Worth having on the record for whoever next audits a liveness evidence pointer, and it is a reason aliveverdict carrying one file path is a thin instrument for a key like this one.Generated by Claude Code
objectstack-fleet commentedon Sep 22, 2026
Triage:
domain:spec(packages/spec/src/data/field.zod.ts) · p2 · Bug ·area:records— scoped to thedescribe; rationale: one published description names a format vocabulary nothing honours, while two readers read the key two other waysPath: 业务对象与记录 | records-forms.field-type-matrix(P1) | P② | p2Graded by the triage seat (
session_01Tw7jnJinGHvoGSi8aFkhPJ) at 2026-09-22T19:12Z. ⛔ ThePath:line lives here, not in the body: no instrument reads it, and rewriting a filer's body risks the platform's citation rewrite.Class (b), scoped deliberately. The one arm with a quoted published line is the description:
packages/spec/src/data/field.zod.ts:1090declaresformat: z.string().optional().describe('Format string (e.g. email, phone)'), and that sentence ships —content/docs/references/data/field.mdx:59renders it verbatim to a customer. Nothing honoursemailorphone. NORTH-STAR rule 4: 「写给 AI 的文档与 skills 说错一句,等于产品缺陷」.z.string()key carrying several vocabularies (an autonumber pattern in the engine, a date display style in objectui). That half is a contract-shape decision and is ⛔ NOT in this card's scope: splitting or retiring the key widens or removes a published surface and belongs in the decision box as its own card. This round fixes the false sentence and ⛔ nothing else.⛔ Not (c): the card takes no census and names no producer, and (c) 须具名生产者.
Dedupe — semantic search returned only this card;⚠️ that zero is a semantic result, ⛔ not a literal grep, so treat it as a floor.
Generated by Claude Code
os-support-ai commentedon Sep 23, 2026
Claim: PM loop round 8 — the published⚠️ ONE conditional fence:
formatdescription names a vocabulary nothing honours, dispatched at 2026-09-23T00:21ZSession:
session_013RDBh5DqXd2xnLwvHLgLFrBranch:
claude/issue-19679-format-describe-vocabularyWorktree:
objectstack-issue-19679Domain:
domain:specSeat:
domain:spec#1File surface:
packages/spec/src/data/field.zod.ts— the ONEdescribestring at theformatkey — plus whatever the repo's own generators rewrite from it (packages/spec/json-schema/**,content/docs/references/data/field.mdx), regenerated and ⛔ never hand-edited, plus.changeset/. ⛔ The key's shape, its type, its optionality and every consumer are out of this round.Container & model:
M,mode:subagent,model: opus (default judgment tier)— the exit is triage's and the edit is one sentence, but the sentence has to be true about three readers, so the judgment tier applies rather than the floor.Clause-②: no
Thread-read: 5782431902
Serial constraints cleared: census over all 22 open PRs at 2026-09-23T00:21Z (375 file rows, 0 unreadable; lit control 93 → 94
packages/spec/rows across the same PRs).packages/spec/src/data/field.zod.tsCLEAR,content/docs/references/data/field.mdxCLEAR,packages/spec/json-schema/field.jsonCLEAR,packages/spec/liveness/field.jsonCLEAR.packages/spec/authorable-surface/data.jsonis held by PR #19618. Adescribeedit should not move that file — it lists keys, not descriptions, and the #19677 round measuredcheck:authorable-surfacegreen across a sibling change — but the round is told to STOP and report if regeneration moves it rather than resolving a hunk under another claim.The premise, re-measured first-hand before the claim
origin/main2bdb81f8ad610472b7cb48fc7b3669039f3c1f61, read at 2026-09-23T00:21Z:packages/spec/src/data/field.zod.ts:1090—format: z.string().optional().describe('Format string (e.g. email, phone)')content/docs/references/data/field.mdx:59—| **format** |string| optional | Format string (e.g. email, phone) |, the same sentence rendered verbatim to a customer.Both halves of triage's class (b) are on the tree: a published line, and nothing honouring
emailorphone.On-hold trigger index — intersected, and it does NOT fire
Two open
pm:on-holdcards name this round's write file in their prose, and both were read rather than assumed:Restart-when:the v18 cycle opens —.changeset/pre.jsononorigin/main,packages/spec/package.jsonat^18., or a v18 milestone/refRestart-touch:file triggerRestart-when:#2657 closed — one Studio appetite call⇒ neither hold declares this file as a trigger, so ⛔ no rider obligation fires and nothing is owed to either card. Named here so the next seat does not re-derive it.
The scope line is triage's, and this claim keeps it
Triage graded this 「scoped to the
describe」 and said so twice: 「This round fixes the false sentence and ⛔ nothing else」. The wider half — onez.string()key carrying an autonumber pattern for the engine and the SQL driver, a date display style for objectui (two different defaults at the pin), and a third vocabulary in its own description — is a contract-shape decision, because splitting or retiring the key widens or removes a published surface.Generated by Claude Code
os-support-ai commentedon Sep 23, 2026
os-dev-report
{ "issue": 19679, "status": "done", "branch": "claude/issue-19679-format-describe-vocabulary", "pr": "https://github.com/objectstack-ai/objectstack/pull/19763", "session": "session_013RDBh5DqXd2xnLwvHLgLFr", "premise_still_valid": true, "summary": "Re-verified the premise first-hand on origin/main before editing: `packages/spec/src/data/field.zod.ts:1090` carried `format: z.string().optional().describe('Format string (e.g. email, phone)')` and `content/docs/references/data/field.mdx:59` rendered the same sentence to a customer. Replaced it with a description that is true about the readers that exist: on an `autonumber` field the key is the record-number pattern resolved by `resolveAutonumberFormat` (canonical `autonumberFormat` first, then this shorthand, then `{0000}`), read by the ObjectQL engine and driver-sql; on a `date`/`datetime` field objectui's grid cells read it as a display style with their own words and their own per-type default; nothing else reads it and it never reaches storage, coercion or write validation. The new sentence also states that the spec declares no vocabulary and validates nothing, and redirects value constraints to the field `type` (`email`/`url`/`phone` are TYPES) and to the `format` validation rule (closed set email|url|phone|json) — the two places the false sentence's words actually live. Scope held to the sentence: no key split, no `displayFormat`, no retirement, no type narrowing, no consumer moved, `autonumber-format.ts` untouched. Docs regenerated with `gen:docs` (three mdx projections), never hand-edited. The conditional fence did NOT trip: `check:authorable-surface` stayed green and `packages/spec/authorable-surface/data.json` (held by PR #19618) did not move. assignee was set by the PM dispatch as expected and I never wrote it; the claim comment 5786758430 names this branch and carries `Clause-②: no`, copied verbatim to the PR body. COORDINATOR LEAD ANSWERED (measured, not edited — hand-written docs are outside this claim's file surface): the email/phone/url shape validation is driven by `field.type`, NOT by `field.format`. `packages/objectql/src/validation/record-validator.ts:628` binds `const t = def.type`, and :746/:749/:752 are `if (t === 'email' ...)`, `if (t === 'url' ...)`, `if (t === 'phone' ...)`. That file never reads `def.format`. So both pages carry the same defect as the describe, and my new describe is the correct side.", "tests": "Final commit 5a837c9ed0 (includes a merge of origin/main dc1b98680b via scripts/pm/os-regen-merge.sh; spec rebuilt and everything below re-verified on that commit). Heavy runs went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-19679. (1) BUILD: `pnpm --filter @objectstack/spec build` -> `os-verify-lock: VERDICT command-exit 0`. packages/spec has no workspace dependencies, so the ^... closure is empty; the package itself was built because the dist-reading gates need it. (2) GENERATED ARTIFACTS: `pnpm --filter @objectstack/spec check:generated` -> EXIT=0, 'All 15 generated artifacts are up to date' (covers check:docs, check:authorable-surface, check:api-surface). Before regeneration it named exactly one stale artifact, content/docs/references/**, and `gen:docs` moved exactly three files and nothing else. (3) TYPECHECK + TEST: `pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/spec test` -> VERDICT command-exit 0; 'Test Files 516 passed (516) / Tests 15056 passed | 1 todo (15057)'. Public surface bytes do not move (a description is not an export), so no importer owes tests. (4) BEHAVIOUR CLAIMS IN THE NEW SENTENCE, run against this branch's build (packages/spec/dist/data/index.js, seq 1): format:'email' -> 'email1'; format:'phone' -> 'phone1'; format:'INV-{0000}' -> 'INV-0001'; {autonumberFormat:'A-{000}',format:'email'} -> 'A-{000}'; {} -> '0001'. One-off verification, no test file left behind. (5) GATES, all exit 0 at 5a837c9ed0, each exit code captured before any pipe (`cmd > log 2>&1; EXIT=$?`): check:nul-bytes, check:doc-authoring, check:issue-citations, check:published-files, check:keyed-text-bounds, check:merge-driver, check:docs-transcript-drift, check:corpus-claim-drift, check-changeset-no-major --base origin/main, check-adr-0087-registration --base origin/main, check:doc-anchors, check:docs-single-h1, check:docs-spec-enumerations, check:quick-reference-counts, check:docs-redirects, check:docs-audit-scope, check:published-readme-links, check-doc-frontmatter, check-docs-section-name, check-spec-docblock-symbol-anchors, check-affected-docs, check-closing-keyword-parity, and the spec source audits check:llms-txt, check:variant-docs, check:yaml-examples, check:empty-state, check:liveness, check:duration-unit-keys, check:objectui-pin-citations, check:exported-any, check:dual-source-exports, check:spec-parsed-alias. (6) NOT MEASURED: check:skill-examples exits 3 'PREREQUISITE NOT MET' without a built @objectstack/client-react — not a pass and not a finding; this diff touches no skills/** file and moves no export, so it is declared to CI. check:docs-transcript-drift first exited 3 for the same class of reason (@objectstack/lint unbuilt); I built that closure and re-ran it to a real exit 0. (7) LINT, narrowed with the narrowing proven: the repo-wide `eslint . --no-inline-config` sweep is CI's. (i) population read from eslint's own config — eslint.config.mjs:327-329 records that this repo runs one config which 'never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file', and grep confirms no parserOptions.project and no projectService in it; (ii) count from --format json: 1 file linted (packages/spec/src/data/field.zod.ts), 0 errors, 0 warnings — 100% of the diff's lintable surface, since eslint answers 'File ignored because no matching configuration was supplied' for both the .mdx projections and the .changeset/*.md; (iii) invariance — with no type-aware linting enabled this diff cannot move the verdict on any file it does not contain. Run after the final commit. (8) GATE-FAMILY RECONCILIATION: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran RANFILE` reports 78 of 101 derived families unrun — repo-wide scans, declared to CI. (The run record undercounts slightly: check:api-surface / check:authorable-surface / check:docs / check:export-origins / check:strictness-ledger / check:liveness / check:skill-refs and check:generated itself were all exercised through the check:generated aggregate.) No dispatch-named gate family was skipped — the dispatch named none. (9) ABLATION: none run and none owed — this round adds no gate and no test, so there is nothing whose ability to fail needs proving. (10) BYTES: `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` over the edited source, the changeset and the regenerated field.mdx -> 0 hits, beyond check:nul-bytes passing. PR body read back after the write: stored byte-identical to what was sent apart from GitHub stripping the trailing newline, exactly one footer, the session-URL form preserved, no angle-bracket fragment anywhere in it. (11) COORDINATOR LEAD, measured at source: reader search `git grep -nE \"format\\s*===\\s*'(email|phone|url|json)'\"` over every packages/*/src and packages/*/*/src returns ZERO hits outside the validation-RULE path (packages/objectql/src/validation/rule-validator.ts:2796, a different key on a different schema). The default claim is refutable too: `FieldSchema.parse({name:'x', type:'email'})` on this branch's build returns {name, type, required:false, searchable:false, multiple:false, unique:false, hidden:false, readonly:false, sortable:true, externalId:false} — every defaulted key materializes and there is NO `format` key at all, so `format` has no default, let alone 'email'.", "mcp_calls": "0 — no MCP GitHub tool was called; all GitHub traffic went through the REST proxy", "api_writes": "2 — POST /repos/objectstack-ai/objectstack/pulls (draft), POST /repos/objectstack-ai/objectstack/issues/19679/comments (this report). Zero label writes: the dispatch named no label and `skip-changeset` is inapplicable (this PR ships a changeset), so the write scope is empty. Reads (not writes): GET /issues/comments/5786758430, GET /pulls/19763. Four git pushes on the feature branch, the first an empty-branch write probe before any edit.", "open_questions": [ { "question": "Should the spec's `format` description enumerate objectui's date/datetime style words (`relative`, `short`, `compact`) and the two per-type defaults, or only name that arm and point at the renderer as the authority for its vocabulary?", "options": [ "A — name the arm, say the words and the per-type default belong to the renderer and are declared there (what this PR ships). Nothing in the published spec string can rot when objectui moves; an author is told exactly where to look and that the spec validates nothing.", "B — enumerate the words and both defaults in the spec description. Maximally concrete for an author, but it pins another repository's vocabulary inside a published spec string, with no gate holding the two in step and no way to carry the `.objectui-sha` the reading was taken at." ], "recommendation": "A, because the defect being repaired is precisely a published spec sentence that asserted a vocabulary the spec does not own and cannot enforce. Spelling objectui's current word list into the same slot repeats the shape at a smaller radius: the words were measured at pin 87af769e9a3ee28ace099fdd653d3ebd79fe82e2 and nothing would go red when that pin moves. If a reviewer wants the words in the published text, the durable version of B is a gate that holds the description against the pinned renderer — which is decision-card work, not this round's." }, { "question": "The PR body's Acceptance notes were written before this lead arrived, so they do not name the six hand-written rows. The PR body is a dev's one write by lane rule (no PATCH after the opening call). Should the seat amend it?", "options": [ "A — the seat appends an Acceptance note to PR #19763's body naming the six rows and the record-validator reader, so the carrier set travels with the PR that fixed the sibling sentence.", "B — leave PR #19763's body as is and carry the six rows only on the card and in whatever docs PR picks them up." ], "recommendation": "A. The describe repair and these six rows are one defect with one root cause (a `format` key nothing reads, credited with behaviour that comes from `type`), and a reviewer reading #19763 should see the full blast radius. Exact text to append is in the finding above; I did not PATCH the body myself." } ], "out_of_scope_findings": [ "class: b · The key-vocabulary decision card triage said was owed does not exist yet — triage graded this card 'scoped to the describe' and said the wider problem 'belongs in the decision box as its own card', and nothing has been filed. Evidence recorded in the PR's Acceptance notes so whoever files it starts from measurements: one z.string() key carries two unrelated vocabularies selected by field type, with no declared value set and no diagnostic in either arm. Autonumber arm — packages/spec/src/data/autonumber-format.ts:196 resolveAutonumberFormat, AutonumberFormatSource declaring `format` as the pre-autonumberFormat shorthand, call sites packages/objectql/src/engine.ts#applyAutonumbers and packages/drivers/driver-sql/src/sql-driver.ts (two); a pattern-less value renders as literal text plus the bare counter (measured: format:'email' -> 'email1') and nothing refuses it. Display-style arm at pinned .objectui-sha 87af769e9a3ee28ace099fdd653d3ebd79fe82e2 — packages/fields/src/index.tsx:1127 `const style = dateField.format || 'relative'` (date cell) and :1176 `const authoredFormat = (field as DateTimeFieldMetadata | undefined)?.format || 'compact'` (datetime cell): two different defaults for one key, and two different vocabularies, since formatDate (packages/core/src/utils/date-display.ts) honours `short`/`relative` and silently paints its default locale face for anything else while the datetime cell maps `relative`/`short` onto its own faces and falls through for everything else. Blast radius today is documentation, not data: no field anywhere in this repository authors `format` (measured over packages/*/src/objects, examples/*/src/data and the published skills/ corpus, which never teaches the key), so what the false sentence reached was the reference docs and the JSON Schema — exactly what an AI authoring agent reads before writing a field. I did NOT file it: per the claim comment a decision card owes the four-facet block and a maintainer-facing 速读, which is the triage seat's build. Dedupe words: format, autonumberFormat, displayFormat, field vocabulary, date display style.", "carrier: the key-vocabulary decision card above (not yet filed) · noted, not filed — packages/spec/liveness/field.json's `format` row is a bare { status: 'live', evidence: 'packages/objectql/src/engine.ts' }: no verifiedAt, no function anchor, no note, and it records only the autonumber reader. Its own siblings are richer on exactly the two axes it lacks — `autonumberFormat` names the consuming function, and `rows` carries evidenceScope: 'cross-repo' for a key objectui reads — so the objectui display-style arm is invisible in this row. Not one of the three filing classes (it is a ledger understatement, not a reproducible defect, a declared-contract violation or an authoring trap), and it is work the decision card has to redo anyway, so it stays in the PR's Acceptance notes.", "class: b · COORDINATOR LEAD — ANSWERED, and both hand-written pages are the SAME defect as the describe. THE QUESTION: is the email/phone shape validation driven by `field.format` or by `field.type`? THE ANSWER, named at source: `field.type`. `packages/objectql/src/validation/record-validator.ts:628` binds `const t = def.type;` and the three checks are per-TYPE conditions — :746 `if (t === 'email' && !EMAIL_RE.test(s)) return fail('invalid_email');`, :749 `if (t === 'url' && !URL_RE.test(s)) return fail('invalid_url');`, :752 `if (t === 'phone' && !PHONE_RE.test(s)) return fail('invalid_phone');`. The regexes are fixed constants in that file, not author input: :91 EMAIL_RE = /^[^\\s@]+@[^\\s@.]+(?:\\.[^\\s@.]+)+$/ (exactly the 'local@domain' shape the page describes), :107 URL_RE (the 'protocol required' shape), :108 PHONE_RE = /^[+()\\-\\s\\d.]{5,}$/ (exactly 'digits, + ( ) - . and spaces, minimum 5'). THE FILE NEVER READS `def.format`: the word occurs twice, both in prose — a header-comment axis label at :38 ('- format email / url / phone (lightweight RFC-aware regex)') and a comment at :691 ('The email/url/phone format checks below stay per-type conditions inside the branch') — and that :38 line is itself a small instance of the same mislabelling. Repo-wide confirmation: zero sites compare `format` to 'email'/'url'/'phone'/'json' anywhere outside the validation-RULE path (rule-validator.ts:2796), which you correctly ruled out. SO MY NEW DESCRIBE IS THE CORRECT SIDE and the pages are wrong. THE CARRIER SET IS SIX ROWS, NOT THREE — your grep missed the `url` row and both `text` rows. content/docs/data-modeling/field-types.mdx :24 (under ### text) '| `format` | `string` | — | Validation format pattern |' and :71 (under ### phone) '| `format` | `string` | — | Phone format pattern |'. content/docs/data-modeling/validation-rules.mdx :46 (under ### text) '| `format` | `string` | — | Validates against format pattern (e.g., regex) |', :64 (### email) '| `format` | `string` | `email` | Validates a basic `local@domain` shape |', :72 (### url) '| `format` | `string` | `url` | Validates URL format (protocol required) |', :80 (### phone) '| `format` | `string` | `phone` | Validates a permissive phone-number character set |'. TWO SEVERITIES, and the second is worse than the describe I just repaired. The two `text` rows and the `phone` row in field-types.mdx promise a regex/pattern meaning NO reader implements at all — `format` on a `text` or `phone` field is read by nothing, so an author writing `format: '(000) 000-0000'` is silently ignored (Prime Directive #10, declared-not-enforced). The email/url/phone rows in validation-rules.mdx are worse because THE BEHAVIOUR THEY DESCRIBE IS REAL and only the attribution is wrong: the local@domain check really does happen, so a reader has every reason to believe `format` caused it, and the habit carries onto an `autonumber` field where `format: 'email'` mints `email1`. Those three rows also declare a `Default` column value ('email'/'url'/'phone') that provably does not exist: `FieldSchema.shape.format` has no `.default()`, and `FieldSchema.parse({name:'x', type:'email'})` on this branch's build returns no `format` key at all while every genuinely defaulted key (required, searchable, multiple, unique, hidden, readonly, sortable, externalId) materializes. NOT EDITED, per your instruction and per my claim's file surface (field.zod.ts + its generator output + .changeset/). Both pages are hand-written under content/docs/data-modeling/, so they are neither auto-generated references/** nor release-owned releases/** — an ordinary docs PR can carry them once the seat amends the claim. THE HONEST REPAIR for the validation-rules.mdx rows is to move the property column from `format` to `type` (the behaviour is real and correctly documented apart from which key causes it) and delete the invented Default; for the field-types.mdx rows it is to delete them outright, since the key means nothing on `text` or `phone`. Dedupe words: format, field format, email validation, type-driven validation, declared not enforced." ] }Generated by Claude Code
os-support-ai commentedon Sep 23, 2026
Round ACCEPTED for review — the describe is fixed, the lead is answered (and it went the other way from a guess), six sibling rows filed as their own card
domain:specexecution seat 1 (seat post #6017,session_013RDBh5DqXd2xnLwvHLgLFr), 2026-09-23T01:07Z. The card stayspm:dispatchedwith the assignee kept. ⛔ No label written. PR #19763 stays draft.What landed in the PR, against the claim
Head
5a837c9ed0: the onedescribeinpackages/spec/src/data/field.zod.ts(+4/−1), threecontent/docs/references/**pages regenerated bygen:docs(data/field.mdx,data/object.mdx,system/migration.mdx) and a changeset. That answers the question this seat left open when the PR opened:object.mdxandmigration.mdxeach carriedFormat stringexactly 2 times onorigin/main, and each moved +2/−2 — they are generator projections of the same sentence, ⛔ not scope creep. ✅ The conditional fence did not trip:packages/spec/authorable-surface/data.json, held by PR #19618, did not move. The round mergedorigin/mainthroughscripts/pm/os-regen-merge.sh, the correct ritual for themerge=os-regenroster.The lead, answered at source — and verified by this seat before anything was filed
The question sent mid-round: is email/phone shape validation driven by
field.formator byfield.type? Bytype. Re-read by this seat onorigin/maindc1b98680b:packages/objectql/src/validation/record-validator.ts:628bindsconst t = def.type, the checks at:746/:749/:752aret === 'email'/'url'/'phone', anddef.formatappears in that file 0 times against a same-file lit controldef.typeof 7. ⇒ the new describe, which sends an author totypeand to theformatvalidation rule, is the correct side.email|phonenearformat, it found three hand-written rows; there are six — it missed theurlrow and bothtextrows. The instrument's radius was smaller than the proposition. Recorded on the new card as 「six is a floor, not a census」.Six hand-written rows → filed as #19764, ⛔ not folded into this PR
The rows in
content/docs/data-modeling/field-types.mdxandvalidation-rules.mdxcreditformatwith validation it never performs, and three declare aDefaultofemail/url/phonethat does not exist (FieldSchema.parse({ name: 'x', type: 'email' })returns noformatkey). The worse half is the one where the behaviour is REAL and only the key is wrong, because the habit carries onto anautonumberfield — where the round measuredformat: 'email'mintingemail1.Why a separate card rather than a widened round, stated because this seat widened #19752 two rounds ago and a reader could expect the same here:
describe」, 「nothing else」.content/docs/**, which the domain table routes todomain:devx, while what they misdescribe isFieldSchema. Routing that is triage's, ⛔ not an execution seat's.18605-enable-on-install-one-authoritychangeset ships two sentences that are false at today's main — andFixes #19339is about to close the only open tracker naming it #19735). Widening past a scope line triage wrote in words is the same move.#19764 is bare
finding, ungraded and unrouted; the PR body's acceptance notes now name it, so a reviewer of #19763 sees the whole blast radius.The round's two open questions
os-dev.md:56): body 11774 → 13097 bytes, every byte stored.Still unfiled, still not this seat's to build
Triage named a decision card for the key-vocabulary question 「as its own card」 on this card's grading; the round re-raised it with measurements in the PR's acceptance notes. ⛔ Still unfiled — a decision card owes the four-facet block and a maintainer-facing 速读, which is the triage seat's build.
Owed next
An isolated at-tier review is dispatched on
5a837c9ed0; its first instruction is to verify every clause of the new sentence at source, since a published sentence that promises something unverifiable is this card's defect in a new spelling. ⛔ No landing until it returns and the seat takes its own CI reading at the landing moment.Generated by Claude Code
os-support-ai commentedon Sep 23, 2026
At-tier review FAILED the new sentence — round 2 dispatched, and two lines of this seat's acceptance record are corrected
domain:specexecution seat 1 (session_013RDBh5DqXd2xnLwvHLgLFr), 2026-09-23T01:24Z. The card stayspm:dispatched, assignee kept. PR #19763 stays draft.The finding (full record on PR #19763): the new
describesays 「nothing else reads this key」 and 「email,urlandphoneare field TYPES, not formats」. At the pinned objectui87af769e9a…,packages/fields/src/index.tsx:2941-2944readsfield.formatand, on a textual base type, promotes the cell renderer by a small word set (phone,email,urland aliases,currency,percent) — pinned end-to-end byformatHintedColumnRenderer-8920.test.tsx({ type: 'text', format: 'phone' }→ atel:anchor). ⇒format: 'phone'on a text field is honoured, as a display hint. Re-verified at source by this seat before adopting it.Corrected in this seat's acceptance record (
5787…on this card, 2026-09-23T01:07Z)record-validator.tsreadsformat. As a statement about the whole key it was wrong, and this seat endorsed it without checking the objectui side beyond the date arm.field.formatwith validation it never performs — email/url/phone shape checks are driven byfield.type, and three rows declare aformatdefault that does not exist #19764 carried a lead built on the same gap (「thefield-types.mdxrows describe nothing and could simply go」); corrected there — thetextrows describe a live renderer hint.Unchanged: Q1's answer — name an arm and point at the renderer as the authority for its words, ⛔ never copy another repository's word list into a published spec string. It now applies to two objectui arms instead of one.
Round 2
Dispatched on the same branch and PR. Either route the reviewer allowed — name the textual renderer-promotion reader, or drop the absolute 「two consumers / nothing else / not formats」 claims — ⛔ without deciding the vocabulary question triage reserved, and with every surviving negative claim carrying its radius and a firing control.
Generated by Claude Code
os-support-ai commentedon Sep 23, 2026
os-dev-report
{ "issue": 19679, "status": "done", "branch": "claude/issue-19679-format-describe-vocabulary", "pr": "https://github.com/objectstack-ai/objectstack/pull/19763", "session": "session_013RDBh5DqXd2xnLwvHLgLFr (the parent seat session; this round ran as its subagent)", "premise_still_valid": true, "summary": "Round 2 rewrote the `FieldSchema.format` describe (`packages/spec/src/data/field.zod.ts:1090-1094`) and the changeset body so that each clause states what a measured reader does. The clauses cover the autonumber pattern and its precedence, the server acting on the key only on that arm, the Studio UI reading it as a display hint whose words its renderers own (the date/datetime style and the plain-text renderer hint are named as examples, with no word list copied), and value checks keyed on `type`. `two consumers`, `nothing else` and `types, not formats` are gone, and the vocabulary question is left undecided. Every negative kept in the text has a stated radius and a firing control (see tests). The census also found a fourth objectui reader, plugin-dashboard's `renderFieldValue`, which has its own display-pattern vocabulary and hides a `format: 'email'` text value (finding 1). The red at `66cb68d947` was an intermediate head: the describe was pushed before its regenerated pages. `a8c7f17751` is the final head. The PR body needs the seat's edit, and the replacement text is in `pr_body_replacements`.", "tests": "All readings are at a8c7f17751 unless marked. (1) `pnpm --filter @objectstack/spec build` under os-verify-lock: `VERDICT command-exit 0`. (2) `pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/spec test` in one locked run: `VERDICT command-exit 0`, `Test Files 516 passed (516)`, `Tests 15056 passed | 1 todo (15057)`. (3) `gen:schema && gen:docs` rewrote exactly field.mdx (1 row), object.mdx (2) and migration.mdx (2). `check:docs` exit 0: `225 generated files in sync with packages/spec`. `check:generated` exit 0: `All 15 generated artifacts are up to date`. `git grep` census: the new sentence appears 1/2/2 times in field.mdx/object.mdx/migration.mdx, and the round-1 and original sentences appear 0 times in each. `git diff dc1b98680b HEAD -- content/docs/references` is the five format rows only. (4) All 101 families that `dispatch-gates.mjs --commands` derives were run from its own list with exits recorded. `--ran` answers `101 derived, 98 run, 3 NOT-MEASURED, 0 UNRUN`. NOT MEASURED: check:skill-examples, check:dual-build-cjs-loads and check:lean-entry-closure, reason `PREREQUISITE NOT MET` (exit 3). They read built output of client-react, the whole workspace and objectql, none of which this diff touches, so they are declared to CI. Derivation caveat: origin/main is 1 commit ahead (ed4b655e5b, scripts/pm only), and the tool flags its derivation as read from those files' old copies. (5) eslint narrowed to the diff: 1 file linted (field.zod.ts), 0 errors, 0 warnings from `--format json`. The mdx and changeset files are ignored by config. eslint.config.mjs:327-329 records that no type-aware linting is enabled, so untouched files are invariant. (6) Clause re-verification at source. Spec: 49 FieldType members x 6 format values = 294 cells through FieldSchema and ObjectSchema give 0 issues on a format path. Firing control: format 123 gives 1 issue on each. Autonumber, from the built dist: `INV-{0000}` gives INV-0001, `email` gives email1, `{autonumberFormat:'A-{000}', format:'email'}` gives A-001, and `{}` gives 0001. `lintAutonumberFormats` returns no finding for format 'email'. Control: `{nope}{000}` gives error autonumber-references-unknown-field. Server negative: this repo's packages/** non-test sources were grepped for non-call `.format` reads. Of 107 hits, the only field-definition reads are autonumber-format.ts:199 and lint-autonumber-formats.ts:62, both on the autonumber arm. A word census over packages/drivers and objectql/src shows every field read gated on the autonumber type (sql-driver.ts:10638 and :10743, engine.ts:5044). Firing control: the instrument returns the two known readers. Blind spots: multi-line destructuring, computed keys and whole-object forwarding. Validator: record-validator.ts has 0 `def.format` reads against lit controls `def.type` 7 and `def.maxLength` 2. `t = def.type` is at :628, and the checks are at :746/:749/:752. objectui at pin 87af769e9a3e: the date style is at fields/src/index.tsx:1127 and the datetime style at :1176, with the fallback to formatDateTime at :1278-1282. The textual hint map is at :2915, TEXTUAL_BASE_TYPES at :2929, and the promote/else at :2943-2946. The resolver is called from grid, detail, related list, kanban, gallery, report and dashboard. formatDate (core date-display.ts:198) and formatDateTime (:300) contain no warn or throw. No ablation: there is no new test or guard to ablate, because this is a prose repair (the round-1 reasoning in the PR body stands, amended by R3).", "mcp_calls": "0", "api_writes": "4 in total. 3 are `git push` runs through `scripts/pm/write-pace.mjs --run` to claude/issue-19679-format-describe-vocabulary (66cb68d947, 875c198d2c, a8c7f17751). 1 is a REST `POST /repos/objectstack-ai/objectstack/issues/19679/comments`, the os-dev-report, sent through `scripts/pm/post-stamped.mjs` on the direct route with the session token. No PR body PATCH, no labels, no draft flip.", "open_questions": [ { "question": "Should the two objectui arms that the describe now names get a dependency edge, so that a pin bump forces someone to re-read them? Today nothing in this repo goes red if objectui removes an arm.", "options": [ "A: add a read-point docblock above `format` in field.zod.ts with the asserting pin spelling and the anchors index.tsx:1127, :1176 and :2915-2946. check:objectui-pin-citations then reds on the next `.objectui-sha` bump. Cost: every bump re-reads these anchors. Quoted-line assertions would also need `ASSERTED_ANCHOR_FLOOR` raised in the gate script, which is outside this card's file surface.", "B: leave it as it is and accept the named residual risk (PR body R3).", "C: fold it into the reserved vocabulary decision card, because that card may declare, move or retire these arms." ], "recommendation": "C. Anchoring the arms now would harden a shape that the decision card is meant to rule on. B is the state until that card exists." } ], "out_of_scope_findings": [ "class: a · objectstack-ai/objectui plugin-dashboard `renderFieldValue` (packages/plugin-dashboard/src/recordFields.tsx:424, the same at pin 87af769e9a3e and at objectui main 3bfcf442) treats a field's `format` as a date pattern whenever it contains any of the letters Y M D H m s. The field's own `format` reaches it at :334. So `{ type: 'text', format: 'email' }`, a word `resolveCellRendererType` honours as a mailto hint, sends the value to `formatDate('ada@example.com', 'email')`, which returns an em dash, and the value is hidden in the dashboard ObjectDataTable and RecordDetailDrawer. Probe: the real `formatDate` from the pinned core/src/utils/date-display.ts returned the em dash, with branch selection read from recordFields.tsx:382-427 (replicated, not imported). A text value `format: 'money'` takes the same branch. Failing probe to write in objectui: `renderFieldValue('ada@example.com', buildFieldMeta({ accessorKey: 'email_addr', def: { type: 'text', format: 'email' } }))` must not return the em dash. The fix lands in objectui. Dedupe words: `renderFieldValue format date pattern`, `recordFields YMDHms`, `dashboard format email em dash`, `format hint collides with date pattern`.", "class: a · packages/lint/src/lint-autonumber-formats.ts:60-62 re-implements format precedence with a `typeof === 'string'` test instead of calling `resolveAutonumberFormat`, whose docblock (autonumber-format.ts:183-190) counts only a NON-EMPTY string as declared. `{ type: 'autonumber', autonumberFormat: '', format: '{nope}{000}' }` parses through FieldSchema. On it, the lint stops at the empty canonical key and reports nothing, while engine and driver-sql resolve to `{nope}{000}`, so every create throws on the unknown `{nope}` field. Probe: `lintAutonumberFormats` gives no finding; control `{ format: '{nope}{000}' }` gives error autonumber-references-unknown-field; `resolveAutonumberFormat` gives `{nope}{000}`. Dedupe words: `lint-autonumber-formats precedence`, `resolveAutonumberFormat empty autonumberFormat`, `autonumber lint misses shorthand`, `autonumber lint own copy`.", "carrier: 承接者:无 · noted, not filed — acceptance note only: packages/drivers/driver-sql/src/builtin-column-collision.ts:97 comments `format: 'presentation', // display/validation hint`. The classification is right, but the `validation` in the comment is not: no validator reads the key. It is comment-only, so it is not filed." ], "pr_body_replacements": [ { "where": "the paragraph that starts `One published sentence named a format vocabulary nothing honours.`", "replace_with": "One published sentence described `FieldSchema.format` as `Format string (e.g. email, phone)`: two example words, with no field type and no reader named. It shipped verbatim into `packages/spec/json-schema/`, into `dist`, into the published `src/**/*.zod.ts`, and to a customer in `content/docs/references/data/field.mdx`. On an `autonumber` field the same key is the record-number pattern, so following the documented example there minted `email1`." }, { "where": "the whole section from `## What actually reads the key — measured on this tree, not recalled` up to, but not including, `## Write surface`. This covers the reader table with its `nothing else` row, the probe block, and the `Where email / phone really live` paragraph", "replace_with": "## What reads the key, measured and stated positively\n\nRound 2 rewrote this section. Round 1 listed two readers and a `nothing else` row. The at-tier review found a third reader at the pinned objectui, so this table lists what each measured reader does and makes no claim that the list is complete.\n\n| reader | field types | what the value means there |\n|:--|:--|:--|\n| `resolveAutonumberFormat` (`packages/spec/src/data/autonumber-format.ts:196`), minted through by `packages/objectql/src/engine.ts` `applyAutonumbers` (`:5051`) and twice by `packages/drivers/driver-sql/src/sql-driver.ts` (`:10639`, `:10744`) | `autonumber` | the record-number **pattern**: canonical `autonumberFormat` first, then this key, then the default `{0000}` |\n| `lintAutonumberFormats` (`packages/lint/src/lint-autonumber-formats.ts:60-62`), wired into `os lint`, `os compile` and `os validate` through `packages/lint/src/authoring-rules.ts` | `autonumber` | the same pattern, linted at build time for unrecognised `{...}` tokens and bad `{field}` references |\n| objectui `DateCellRenderer` and `DateTimeCellRenderer` (`packages/fields/src/index.tsx:1127`, `:1176`) at the pinned `.objectui-sha` `87af769e9a3ee28ace099fdd653d3ebd79fe82e2` | `date`, `datetime` | a display **style**, with its own words and a different default per type |\n| objectui `resolveCellRendererType` (`packages/fields/src/index.tsx:2915-2947`), same pin; called from the grid (`plugin-grid/src/cellRendererResolution.ts:114`), the detail views (`DetailSection.tsx:411`, `DetailView.tsx:1298`, `HeaderHighlight.tsx:137`, `RelatedList.tsx:1282`), and the kanban, gallery, report and dashboard views | textual base types (`text`, `textarea` and the rest of `TEXTUAL_BASE_TYPES`) | a cell-renderer **hint**: a small word set promotes the cell to a richer renderer. Pinned end to end by `packages/plugin-grid/src/__tests__/formatHintedColumnRenderer-8920.test.tsx`, where `{ type: 'text', format: 'phone' }` renders a `tel:` anchor |\n| objectui `renderFieldValue` (`packages/plugin-dashboard/src/recordFields.tsx:382-427`), same pin, fed the object field's `format` at `:334` | any | a display **pattern**: a leading currency symbol, a `%`, or any of the letters `Y`, `M`, `D`, `H`, `m`, `s` selects a formatter, and anything else falls through to `resolveCellRendererType` |\n\nRun against this branch's build of `@objectstack/spec` (`dist/data/index.mjs`), seq 1:\n\n```text\nformat: 'INV-{0000}' -> INV-0001\nformat: 'email' -> email1\n{ autonumberFormat: 'A-{000}', format: 'email' } -> A-001\n{} -> 0001\n```\n\nSo an author who followed the key's own documentation onto an `autonumber` field got `email1` as a business identifier. It parsed, it stored, and neither the runtime nor the build-time lint reported it: `lintAutonumberFormats` returns no finding for `format: 'email'`, while a `{nope}{000}` control draws `autonumber-references-unknown-field`.\n\n**The negatives the description keeps, each with its radius:**\n\n- *On any non-`autonumber` field the server does not act on the key.* Radius: this repo's `packages/**` non-test sources at `a8c7f17751`. Instrument: `git grep` over every non-call property read of `format` (107 hits). Triaged, the only field-definition reads are `autonumber-format.ts:199` and `lint-autonumber-formats.ts:62`, both on the autonumber arm. A word census over `packages/drivers` and `packages/objectql/src` finds every field-reading line gated on the autonumber type (`sql-driver.ts:10638`, `:10743`; `engine.ts:5044`). Firing control: the same instrument returns the two known readers. Named blind spots: multi-line destructuring, computed keys, and whole-object forwarding. Corroboration: `driver-sql`'s own `FIELD_KEY_STORAGE_CLASS` classifies `format` as `presentation` (`packages/drivers/driver-sql/src/builtin-column-collision.ts:97`).\n- *The write-time record validator never reads it.* Radius: `packages/objectql/src/validation/record-validator.ts`. `def.format` has 0 reads, against `def.type` 7 and `def.maxLength` 2 as lit controls. `const t = def.type` is at `:628`, and the email, url and phone checks at `:746`, `:749` and `:752` test `t`.\n- *The spec checks nothing but that it is a string.* 49 `FieldType` members times 6 values (`email`, `phone`, `url`, `relative`, an arbitrary string, `INV-{0000}`) gives 294 cells, run through both `FieldSchema` and `ObjectSchema`: 0 issues on a `format` path. Firing control: `format: 123` draws 1 issue on each.\n- *The two named objectui arms fall back silently.* Read at the pin: the date cell hands the word to `formatDate` (`packages/core/src/utils/date-display.ts:198`), which honours `short` and `relative` and otherwise paints its default face. The datetime cell handles `relative`, `short` and `compact` and hands any other word to `formatDateTime`, which paints its default face (`index.tsx:1243-1282`). `resolveCellRendererType` returns the base type's renderer for a word outside its map (`:2943-2946`). None of the three warns or throws.\n\n**Where a value check lives**, which the description now states: the record validator's email, url and phone checks key on the field **type** (`type: 'email'`), and the closed `email | url | phone | json` vocabulary belongs to a **`format` validation rule** (`FormatValidationSchema`, `packages/spec/src/data/validation.zod.ts:204`, its `format` key at `:214`). The description no longer says `email`, `url` and `phone` are \"not formats\". On a plain-text field objectui reads them as renderer hints, and whether that reading should become a declared vocabulary is the decision this card leaves open." }, { "where": "in `## Is a regression test owed?`, the bullet that starts `Every behaviour claim the new sentence makes is already pinned.`", "replace_with": "- **Every behaviour claim the new sentence makes is pinned, but not all of it in this repository.** The autonumber claims are pinned here by `packages/spec/src/data/autonumber-format.test.ts`: canonical-over-shorthand precedence, the `{0000}` default rendering as `0001`, and the no-slot branch. The two objectui arms are pinned in objectui at the pin: `packages/fields/src/__tests__/datetimeCell.formatVocabulary-8853.test.tsx` for the date and datetime styles, and `packages/plugin-grid/src/__tests__/formatHintedColumnRenderer-8920.test.tsx` for the plain-text hint. The description names those arms without copying their words, so an objectui change to a word list cannot make the spec sentence false. An objectui change that removes an arm could, and nothing in this repository would go red. That residual risk is why the arms are introduced as examples." }, { "where": "the whole `## Verification` section, up to but not including `## Acceptance notes`", "replace_with": "## Verification\n\nFinal commit `a8c7f17751`. No merge of `origin/main` this round: `origin/main` is one commit ahead (`ed4b655e5b`, `scripts/pm/**` only) and touches none of this PR's five files, so `os-regen-merge.sh` was not needed. The branch still carries round 1's merge of `dc1b98680b`.\n\n| check | result at `a8c7f17751` |\n|:--|:--|\n| `pnpm --filter @objectstack/spec build` (under `scripts/pm/os-verify-lock.sh`) | `VERDICT command-exit 0` |\n| `pnpm --filter @objectstack/spec typecheck`, then `test`, in one locked run | `VERDICT command-exit 0`; 516 test files, 15056 tests passed, 1 todo |\n| `gen:schema`, then `gen:docs` | rewrote exactly `data/field.mdx` (1 row), `data/object.mdx` (2 rows) and `system/migration.mdx` (2 rows) |\n| `pnpm --filter @objectstack/spec check:docs` | exit 0: `225 generated files in sync with packages/spec` |\n| `pnpm --filter @objectstack/spec check:generated` | exit 0: `All 15 generated artifacts are up to date` |\n| sentence census | the new sentence appears in `field.mdx` 1 time, `object.mdx` 2 times and `migration.mdx` 2 times; the round-1 sentence and the original sentence appear 0 times in each. `git diff dc1b98680b HEAD -- content/docs/references` is the five `format` rows and nothing else |\n| every family `scripts/pm/dispatch-gates.mjs` derives for this path set, run from its own `--commands` list with each exit recorded | `--ran`: 101 derived, 98 run at exit 0, 3 NOT MEASURED, 0 unrun |\n| eslint, narrowed | 1 file linted (`field.zod.ts`): 0 errors, 0 warnings, read from `--format json`. The `.mdx` pages and the `.changeset/*.md` answer `File ignored because no matching configuration was supplied`. `eslint.config.mjs:327-329` records that type-aware linting is enabled for no file, so this diff cannot move the verdict on an untouched file |\n\n**NOT MEASURED, declared to CI:** `check:skill-examples`, `check:dual-build-cjs-loads` and `check:lean-entry-closure` exited 3 (`PREREQUISITE NOT MET`). Each reads built output of packages this diff does not touch: `client-react`, the whole workspace, and `objectql`. Not a pass and not a finding.\n\n**The red at `66cb68d947` was an intermediate head.** That push carried the new `describe` before its regenerated pages. `875c198d2c` added them, and `check:docs` is green from there on." }, { "where": "in Acceptance note 1, replace the sentence `The shape of the decision: one z.string() key carries two unrelated vocabularies ...` with the first paragraph below, and insert the bullet below after the `Display-style arm` bullet", "replace_with": "The shape of the decision: **one `z.string()` key is read by at least four readers with four different vocabularies (the autonumber pattern, the date and datetime style, the textual renderer hint, and the dashboard display pattern), with no declared value set and nothing that checks the readers agree.**\n\n- **Renderer-hint and display-pattern arms** (objectui, same pin), found by round 2's at-tier review and census. `resolveCellRendererType` promotes a textual field by a word set (`packages/fields/src/index.tsx:2915-2946`), and `plugin-dashboard`'s `renderFieldValue` reads the same key as a display pattern (`recordFields.tsx:382-427`). The two disagree on at least one word. `format: 'email'` on a `text` field is a `mailto:` hint to the resolver, but the dashboard's pattern test matches its `m` and hands the value to `formatDate`, which answers an em dash. That defect is reported separately in the round-2 report." }, { "where": "in the #19764 paragraph, the sentence that starts `⇒ this PR's new describe, which sends an author to type and to the format validation rule, is the correct side`", "replace_with": "⇒ on **validation**, this PR's `describe`, which sends an author to `type` and to the `format` validation rule, is the correct side. On display it is not the whole story: the `text` row (`field-types.mdx:24`) describes a live renderer hint at the pinned objectui (see the reader table), which the seat recorded on #19764." } ], "deviations": "The report comment was posted through post-stamped with the session token (direct route; `fleet-write/dispatch.mjs --route` answered direct, OS_FLEET_SESSION absent), the same identity as the round-1 report, not through with-fleet.sh. No other deviation." }Generated by Claude Code
os-support-ai commentedon Sep 23, 2026
✅ Landed —
Release:and the landing record. Card cleared to its four non-state labels and no assignee.Release:session_013RDBh5DqXd2xnLwvHLgLFr—domain:specexecution seat 1 (seat post #6017), 2026-09-23T02:56Z. PR #19763 is MERGED, squash041c8cf62b05e5ab0d7c1270a17565cc2da6f5fd, and the card closedcompletedon its ownFixes #19679. Same stroke:pm:dispatchedremoved and the assignee cleared, read back clean.bug,priority:p2,domain:specandarea:recordsstay, because none of them is a state.Judged on the tree, ⛔ never on the API's
mergedfieldInstrument A — the commit message on fetched
origin/main:(#19763)⇒ 1, lit control(#19759)⇒ 1, dark control(#99999)⇒ 0. Squash title:fix(spec): describe \format` by the readers that exist, not by email/phone (#19763)`.Instrument B — the subject itself, parent
2005a558de→ squash041c8cf62b, scoped to the two carriers of the sentence:field.zod.tsreferences/data/field.mdxFormat string (e.g. email, phone)record-number PATTERN)autonumberFormatinfield.zod.ts.changeset/19679-format-describe-vocabulary.mdThe lit control is present on both sides, so the instrument is live at the parent and at the squash.
The landing path, for the record
formatby the readers that exist, not by email/phone #19763 at heada8c7f17751. Tier was measured by the seat from the reviewer's own transcript: 101 / 101claude-fable-5-1, dark control empty, and ⛔ no fallback notice.skills/corpus does teach the key on the autonumber arm;check:docswas not measured at875c198d2c) were re-verified at source by this seat and corrected in the body before landing. The head did not move.check-governed-merges --pr 19763⇒ NOT governed.check-clause2-carriers --pair 19763⇒ a review of record names this head.added_to_merge_queue02:24:16Z → merged 02:56:01Z. ⛔ Never merged directly, ⛔ never approved, ⛔ never bypassed.What stays open, ⛔ not by this card
z.string()key has at least four readers with four vocabularies, and the published skill still teaches the autonumber shorthand the describe now tells a new field to replace. A decision card owes the four-facet block and a maintainer quick-read; that is the triage seat's build. 承接者: the triage seat.field.formatwith validation it never performs — email/url/phone shape checks are driven byfield.type, and three rows declare aformatdefault that does not exist #19764 (six hand-written doc rows), [finding]lint-autonumber-formatskeeps its own copy of the format precedence and disagrees withresolveAutonumberFormaton an emptyautonumberFormat— the lint goes silent on a pattern the engine then throws on for every create #19772 (the lint's own precedence copy), [finding] dashboardrenderFieldValuereads anyformatcontaining Y/M/D/H/m/s as a date pattern — a text field hintedformat: 'email'goes toformatDateand its value is hidden in the dashboard table and record drawer objectui#10220 (the dashboard's date-pattern test swallowingformat: 'email').Generated by Claude Code
fix(spec): describe `format` by the readers that exist, not by email/…