Skip to content

fix(spec)!: the Field.select string shorthand keeps what it does not document transforming, so a hyphenated option is refused instead of declared as another value - #25122

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-25013-select-shorthand-refusal
Oct 21, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-25013-select-shorthand-refusal

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #25013

Clause-②: no (narrowing: a Field.select shorthand string the helper used to rewrite silently, e.g. 'no-overlay' → nooverlay, is refused instead)
Metadata-protocol: narrowed

What changed

  • packages/spec/src/data/field.zod.ts selectWithOptions: the string shorthand's transform is now only what its JSDoc documents: lower-case, and whitespace runs become _. It no longer deletes every other character outside a-z0-9_. A shorthand string that is no system identifier ('no-overlay') therefore reaches SelectOptionSchema.value unchanged and is refused by the rule an explicit { value: 'no-overlay' } already met. No new error code, no parallel validator, and SelectOptionSchema.value / SystemIdentifierSchema accept and refuse exactly what they did before. The explicit-object branch keeps its lower-casing (documented, and not a strip). The Field.select JSDoc states the rule and the explicit spelling to use.
  • packages/metadata-core sys_metadata_audit.lock_state: the one in-tree shorthand the strip altered. It declared none, nooverlay, nodelete, full, while its writer (metadata-protocol protocol.ts, lock_state: entry.lockState ?? 'none') stores the MetadataLock spelling (no-overlay, no-delete). A select option value cannot hold a hyphen, so the column is now Field.text({ maxLength: 255 }), read-only as before, like the sibling code column whose closed set also lives with its writer. Writer and reader unchanged, no stored row rewritten.
  • Translations (platform-objects, regenerated with node scripts/check-i18n-bundles.mjs --write): the four lock_state option labels drop out; the field label stays.
  • ADR-0087: a semantic entry field-select-shorthand-keeps-written-value (major 18), so the upgrade guide and os migrate meta carry the TODO for a TypeScript author whose shorthand now refuses. The changeset's disposition is registered.
  • Engine-double ledger: scripts/engine-double-contract.pinned.json records the new save-door test's fake engine (check-engine-double-contract.mjs --write).

Premises verified before writing code (ruling part ②)

  • (a) lock_state is the only shorthand the strip alters. A TypeScript-AST census of every Field.select(...) call in all 9,792 tracked .ts/.tsx/.mts/.js/.mjs files at ae58b26f9 (140 calls, 189 shorthand strings), comparing the old strip with the documented transform and with the identifier rule: 2 hits, both sys-metadata-audit.object.ts:190 (no-overlay, no-delete). The non-literal option lists it could not read statically (BUILTIN_MEMBERSHIP_ROLE_OPTIONS, InvitationStatus.options, SYS_ACTIVITY_BUILTIN_TYPES, and the .map(...) builders that write explicit values) were read by hand: all identifiers. A regex census of the 1,016 tracked .md/.mdx files (published skills, docs, templates; changelogs excluded): 51 shorthand strings, 0 hits. Positive control: the same AST census at ba244f1^ (before PR fix(platform-objects)!: sys_oauth_application.type declares only web and native, the two application types its writer produces #25099) reports the third hit, sys-oauth-application.object.ts:449 'user-agent-based'.
  • (b) lock_state can hold what its writer writes without widening the option-value accept set and without rewriting rows. Field.text holds the hyphenated spelling; no option value is involved. Schema drift was measured with diffManagedTable on a varchar(255) column (the width the select column was created as) on postgres, mysql and sqlite: select 0 entries; text with no maxLength 1 entry on postgres and mysql (manual_widen_varchar_to_text, needs_confirm); text with maxLength: 32 a destructive narrow_varchar; text with maxLength: 255 0 entries on all three. Hence maxLength: 255.

Where the refusal fires (H3, measured)

  • TypeScript authoring, module load: ObjectSchema.create (the shape every platform object and os init template uses) throws a ZodError with one issue, invalid_format at fields.FIELD.options.N.value, message System identifier must be lowercase, starting with a letter, …. Before the lock_state fix, importing sys-metadata-audit.object.ts would have thrown exactly this.
  • The object save door: saveMetaItem answers 422 INVALID_METADATA with the issue at fields.status.options.1.value, code invalid_format, and stores nothing; the shorthand and the explicit spelling produce identical issues.

Tests

  • packages/spec/src/data/field.test.ts: the hyphen is kept; shorthand and explicit meet the same issue at the same path; ObjectSchema.create refuses at module load (invalid_format at fields.lock_state.options.1.value); the documented transform still parses (In Progress → in_progress, and v1.2 now declares v1.2).
  • packages/metadata-protocol/src/protocol.select-shorthand-refusal.test.ts: the save door's code + status + issue path, the explicit spelling's identical issues, and a control that the documented transform saves.
  • packages/metadata-core/src/objects/sys-metadata-audit-lock-state.test.ts: lock_state is text, maxLength: 255, no options, read-only; exactly no-overlay and no-delete of MetadataLockSchema are values a select option cannot declare; the object parses.
  • Ablation (one, on the behaviour the spec tests pin): scripts/ablation-replace.mjs re-inserted the strip into field.zod.ts (anchor 1 → 0, blob 9d7a024c0fbc → 3162adcde7d2); field.test.ts went 4 failed / 280 passed, the four new cases; restored, git diff HEAD empty.
  • Suites (on 88440a148, before the ledger entry and merge; neither touches these packages' sources): @objectstack/spec 706 files / 21,463 tests passed; @objectstack/metadata-core 19 / 791; @objectstack/metadata-protocol 252 passed + 3 skipped files / 20,835 passed + 19 skipped tests; @objectstack/platform-objects 86 / 1,250. After the ledger entry: spec src/migrations + field.test.ts 6 files / 512 tests, and typecheck green for spec, metadata-core, metadata-protocol and platform-objects.
  • Full-repo pin sweep: git grep for nooverlay, nodelete, useragentbased and toSnakeCase outside changelogs finds only the regenerated translation bundles, the earlier OAuth changeset and the CLI's own unrelated toSnakeCase. No fixture asserted the stripped values, so no pin flips.

Acceptance notes

  • The translated lock_state option labels (禁止叠加, …) never displayed: they were keyed nooverlay / nodelete, which no stored row holds. Dropping them changes nothing a user saw; the column now shows the stored spelling.
  • 'v1.2' used to declare v12 and now declares v1.2, a valid identifier: a value change, not a refusal. The census found no in-tree shorthand with a dot.
  • The explicit-object branch still lower-cases value ({ value: 'Active' } → active). That is documented and not a strip, so it is left alone here; recorded in the report as an open question.

Generated by Claude Code

…document transforming, so a hyphenated option is refused rather than rewritten

sys_metadata_audit.lock_state becomes a text column holding the MetadataLock
spelling its writer stores.

Claude-Session: https://claude.ai/code/session_0123qkvzKLQfRRRnuR8wanHJ
Co-authored-by: Claude <noreply@anthropic.com>
…t declaration; regenerate translation bundles

Claude-Session: https://claude.ai/code/session_0123qkvzKLQfRRRnuR8wanHJ
Co-authored-by: Claude <noreply@anthropic.com>
…t column was created with

Claude-Session: https://claude.ai/code/session_0123qkvzKLQfRRRnuR8wanHJ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata-core, @objectstack/platform-objects, @objectstack/spec, touching 5 documentable anchor(s).

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

  • content/docs/releases/v17/17-0.mdx (via sys_metadata_audit (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v18/18-0.mdx (via sys_metadata_audit (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 144 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json f2b6cf230b4cddd795b854b044bbcd95e37b6f55 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 5bf6094fc142f20e373ba5aa0ec645152053a641 — the merge of head 84a283fe4f22129c9ff20d2c259454391edda7f3 into base f2b6cf230b4cddd795b854b044bbcd95e37b6f55, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 5bf6094fc142f20e373ba5aa0ec645152053a641 && git checkout 5bf6094fc142f20e373ba5aa0ec645152053a641
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f2b6cf230b4cddd795b854b044bbcd95e37b6f55 84a283fe4f22129c9ff20d2c259454391edda7f3 && git checkout -B drift-repro f2b6cf230b4cddd795b854b044bbcd95e37b6f55 && git merge --no-ff 84a283fe4f22129c9ff20d2c259454391edda7f3

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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs f2b6cf230b4cddd795b854b044bbcd95e37b6f55 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 84a283fe4f22129c9ff20d2c259454391edda7f3
Local-runs: none
Scope: packages/spec/src/data/field.zod.ts, packages/spec/src/migrations/entries/semantic/18.field-select-shorthand-keeps-written-value.ts + packages/spec/src/shared/identifiers.zod.ts (SystemIdentifierSchema), packages/metadata-core/src/objects/sys-metadata-audit.object.ts, packages/metadata-protocol/src/protocol.ts (the lock_state writer at :19453 and reader at :12666), packages/spec/src/kernel/metadata-protection.zod.ts (MetadataLockSchema), packages/spec/src/api/protocol.zod.ts (the audit response's lockState), packages/drivers/driver-sql/src/sql-driver.ts (createColumn) and packages/drivers/driver-sql/src/schema-drift.ts (diffManagedTable); plus the narrowing-face floor, every diff path outside the prose faces: packages/spec/src/data/field.test.ts, packages/metadata-core/src/objects/sys-metadata-audit-lock-state.test.ts, packages/metadata-protocol/src/protocol.select-shorthand-refusal.test.ts, packages/platform-objects/src/apps/translations/en.objects.generated.ts, packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts, packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts, packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts, scripts/engine-double-contract.pinned.json; and .changeset/25013-select-shorthand-keeps-written-value.md, read as the prose face it is.

Inputs: card #25013 (body and all three comments: claim 6316146057, dispatch 6316248493, os-dev-report 6318297750); PR #25122 body, its 12-file list, and the net diff origin/main...84a283fe4 (merge-base f2b6cf230, 12 files, +304/-36, read with git diff on the shared checkout); the check-runs on the head. Nothing built, run or re-run.

① Derived judgments

  1. The shorthand transform (selectWithOptions, field.zod.ts:3052-3058). The strip .replace(/[^a-z0-9_]/g, '') is gone; the transform is lower-case plus whitespace runs to _. Accept-set reading: a shorthand string loads now only if it loaded before (the old strip only ever deleted characters, so whatever passes SystemIdentifierSchema after the change passed it before), with one degenerate exception named in item 3. The hyphen class ('no-overlay', 'user-agent-based') moves from "loads as a different value" to "refused at options.N.value, invalid_format, the SystemIdentifier message", and the refusal is the existing one: field.test.ts pins the shorthand's issues equal to the explicit { value: 'no-overlay' } issues at FieldSchema, ObjectSchema.create throws at module load, and saveMetaItem answers 422 INVALID_METADATA at fields.status.options.1.value with nothing stored. Right — a pure narrowing of the loading set, no new error code, no parallel validator.

  2. SelectOptionSchema.value / SystemIdentifierSchema. The diff of packages/spec/src/shared/ and packages/spec/src/kernel/ between origin/main and the head is empty, and the SelectOptionSchema block (field.zod.ts:350-362) is untouched; the regex stays ^[a-z][a-z0-9_.]*$ with the 2-character floor. Right — byte-identical, as the claim fenced it.

  3. The dot case. 'v1.2' declared v12 and now declares v1.2, a valid identifier: a value change inside the loading set, not a refusal. It is stated — the changeset's FROM/TO ("A dot is now kept"), the ledger entry's reason, the PR's acceptance notes — and pinned (field.test.ts: ['in_progress', 'closed_won', 'v1.2']). Right, stated. One edge the prose does not name, judged immaterial: a shorthand of one letter followed only by dots ('a.') was refused before (the strip left a, under the 2-character floor) and loads now as a.. No published accept set grows — the explicit form { value: 'a.' } always loaded — so it is not a widening the Clause-② line owes; recorded so nobody re-derives it.

  4. The explicit-object branch ({ ...o, value: o.value.toLowerCase() }) is unchanged. Lower-casing is the documented transform, not a deletion, and the card's defect class is silent deletion. Right to leave (③ OQ1).

  5. lock_state. The writer at protocol.ts:19453 stores entry.lockState ?? 'none', a MetadataLock (none | no-overlay | no-delete | full, metadata-protection.zod.ts:50); the reader at :12666 casts the raw column to MetadataLock | null; the response schema at api/protocol.zod.ts:1869 types it MetadataLockSchema.nullable(). None of the three moved. Field.text({ maxLength: 255, readonly: true }) holds all four spellings; the old select declared nooverlay / nodelete, which no row ever held, and the new test pins exactly those two as the members a select option cannot declare. No row rewritten, no reader fallback. Right. Column shape, read off the driver rather than re-measured: lock_state sits on no index (occurred_at and (type, name, occurred_at)), so createColumn's unkeyed text-family arm makes a FRESH table's column TEXT, while an EXISTING varchar(255) — the width the select column was created at; packages/qa/dogfood/test/upgrade-harness/fixture-18.0.0/database.sql:601 shows it — diffs clean under diffManagedTable: with maxLength: 255 the varchar-length branch sees declared === col.maxLength (or emitterWouldVarchar false when the mirror is threaded), and the [finding] An UNDECLARED signature/qrcode column on a pre-existing table stays varchar(255) and no drift op reports it — the #11875 repair reaches new tables only #12121 unbounded-text branch requires no maxLength at all. The dev's three readings (no bound → manual_widen_varchar_to_text; 32 → narrow_varchar; 255 → zero) are what schema-drift.ts:1220-1330 says they must be. Right. A nit, not a finding: the object comment says 255 "is the varchar(255) the select column was created as" — true of an upgraded database; a fresh one lands TEXT with the bound enforced at the write seam (max_length over the bounded string types). Both hold every value the writer writes.

  6. Served metadata. GET /api/v1/meta/object/sys_metadata_audit serves lock_state as type: 'text', maxLength: 255, no options, where it served select with four options. A public-surface change, declared and carried at minor on @objectstack/metadata-core. Not a widening at the write seam: the column was readonly, so option membership never bound a write (the card's own stated limit), and text with a 255 bound is at most as wide. Right.

  7. Translations. The four lock_state option labels drop from all four bundles, regenerated by check-i18n-bundles.mjs --write; the Lint lanes that run that check are green on the head. Right. The acceptance note "never displayed" is over-broad: it holds for nooverlay / nodelete; the none / full keys did match stored rows, so a generic grid over sys_metadata_audit that rendered select labels showed 无 / 完全锁定 and now shows the raw value. The changeset states the drop plainly, which is the published statement; the note's sentence is prose, not a defect.

  8. The ADR-0087 ledger entry 18.field-select-shorthand-keeps-written-value.ts: a D3 structured TODO with no D2 conversion and no conversionIds — correct, since no stored artifact carries the shorthand (it runs in the author's TypeScript), and migrations.test.ts's ruling-B pin runs from D2 to D3, not the reverse. Major 18 is the current spec major (18.0.0) with a semantic:18 region in registry.ts.template; the registry is generated at build and never committed, and since spec(changes): delete the committed spec-changes per-major projection and the upgrade guide copy, with their two merge=os-regen routes, once generation at publish has landed (#22449 B′) #22485 spec-changes.json and the upgrade guide are generated witnesses, so no committed projection is owed in this diff. The entry's runtime strings carry ADR ids only, no tracker number. Right.

  9. scripts/engine-double-contract.pinned.json: three rows for the new save-door test's fake engine, written by check-engine-double-contract.mjs --write; the double asserts its dispatch shape through assertEngineFindOnePredicate / assertEngineUpdateDispatch / assertEngineDeleteDispatch as the sibling harness does. Right.

  10. In-tree producers. The dev's AST census (140 calls, 189 shorthand strings, 2 hits, both lock_state; positive control at ba244f1^ finds the OAuth hit) is not re-runnable under this review's read-only shape. Corroboration read instead: Build Core and the Dogfood Regression Gate are green on the head (a hyphenated shorthand now throws at module load through ObjectSchema.create), and a single-line git grep over Field.select([...]) literals at the head finds no hyphenated string outside comments. Right, with that limit named.

  11. Widened anything published? No new export, no new error code, no schema or accept set widened; the only gain is the helper's output range now including dotted identifiers the explicit form always accepted. Right.

② Semver level

  • .changeset/25013-select-shorthand-keeps-written-value.md: @objectstack/spec minor, @objectstack/metadata-core minor, @objectstack/platform-objects patch. The summary is fix(spec)!:, the body carries the BREAKING banner, the FROM → TO mapping with the one-line fix (write the option explicitly, or declare the column Field.text({ maxLength: 255 })), the ADR-0087 marker registered field-select-shorthand-keeps-written-value in the comment form the gate prescribes, and the two line-initial declarations.
  • Level under ruling A: the kind decides. Metadata-protocol: narrowed (a value refused) takes at least minor on a moved package — spec and metadata-core both move published source and both are minor; platform-objects' regenerated bundles ride patch; major stays refused in the launch window. @objectstack/metadata-protocol gains only a test file, so no changeset is owed there. Check Changeset on the head is green, the level axis' own verdict. Matches the diff.
  • Clause-②: no (narrowing: ...) — identical on the claim, the PR body and the changeset; matches the diff (no published accept set widens; the shorthand's loading set narrows on the hyphen class; the dot case is a value change inside the set, stated in the body). narrowed beside the (narrowing) arm is the pair the gate expects — no contradiction. Right.
  • Metadata-protocol: narrowed — the highest-breaking kind applies (narrowed over semantic: the dot case and the lock_state type change are semantic faces of the same diff). Right.
  • ADR-0087 disposition: registered with the D3 entry above; check:adr-0087-registration runs in the green Lint lanes. not-required (no-migration-prescription) would contradict the changeset's own fix line. Right.

③ Boundary flags

  • Dev flag — file surface beyond the claim. (a) The semantic ledger entry: owed by the ADR-0087 gate once the changeset carries a prescription; the claim's "stop on a breach and explain it in the report" was met by the report, and the addition is accepted here. (b) engine-double-contract.pinned.json: a generated artifact written by its own tool, inside the claim's generated-artifacts clause. No breach.
  • Dev flag — check:dual-build-cjs-loads NOT MEASURED locally (dist missing): Build Core on the head is green. Answered.
  • Dev flag — suites ran on 88440a148, before the ledger entry and the merge: Test Core on the head is the carrier (the check-run reading below). Answered by CI.
  • OQ1 (explicit { value: 'Active' } still lower-cased): A, keep. Lower-casing is the documented transform and lands on an identifier the author can read back in the served options; SelectOptionSchema.value already refuses anything else. B is a second narrowing with no measured producer and outside this card's claim — a new card if anyone wants it, not this PR.
  • OQ2 (lock_state as text, the closed set with MetadataLockSchema at the writer): A, accept. It is the claim's own cross-domain path; B needs a reader mapping or a ?? fallback (Prime Directive Add comprehensive test suite for Zod schema validation #12) or a row rewrite, all of which the ruling fenced out.
  • OQ3 (registered + D3 entry versus not-required): A. The changeset prescribes a TypeScript-author rewrite, so no-migration-prescription would be false on substance; a D3 structured TODO is what ADR-0087 D3 is for when no artifact carries the shape.
  • Out-of-scope finding 1 (the dot case, noted not filed): accepted — stated in FROM/TO and the ledger entry, pinned by test, no in-tree dot shorthand found. No card owed.
  • Out-of-scope finding 2 (translated labels never displayed): accepted with the correction in ① item 7 (the none / full keys did match rows). The changeset states the drop. No card owed.
  • The Docs Drift Check comment: two release-owned pages name sys_metadata_audit by symbol only; git grep lock_state over content/docs at the head is empty, so no page restates the option list. Nothing owed.
  • Check-runs on the head, read at 2026-10-21T14:36Z: 41 runs — 31 success, 7 skipped (opt-in and path-filtered lanes), 3 in progress (Test Core 2/6, 3/6, 4/6). Green among them: Check Changeset, Governed Surface Queue Guard, Lint & Repo Gates and every Lint lane, Build Core, Dogfood Regression Gate, Spec property liveness, Type Check · source gates / debt ledger / workspace / consumer gates, TypeScript Type Check, Test Core 1/6, 5/6 and 6/6, the card-claims-branch and single-writer checks. An earlier reading at 2026-10-21T14:28Z had Test Core 1-6/6, the two Type Check lanes and TypeScript Type Check still open; all but the three named have since completed green. Commit-status API: 0 statuses. This verdict is the contract reading; the landing still waits for every check green, which the queue guard holds on its own.

Implemented-by: session_0123qkvzKLQfRRRnuR8wanHJ
Reviewed-by: session_01VsqhiSrBjB1wJfRNcHPeXN

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

Seat record of domain:spec seat 1 (seat post #6017) · session session_01VsqhiSrBjB1wJfRNcHPeXN · 2026-10-21T14:41Z · verdict ACCEPT · head 84a283fe4f22129c9ff20d2c259454391edda7f3.

  1. Shape. Draft, base main, first line Fixes #25013 and no other closing keyword; Clause-②: no (narrowing: …) and Metadata-protocol: narrowed at line start; 12 files, +304 / −36; check-governed-merges.mjs --pr 25122 exit 0 (no governed path, under 3000 lines), so no Tier H; assignee huangyiirene.
  2. Premises measured. The conditional ruling's premises (claim 6316146057): (a) the dev's AST census (140 Field.select calls, 189 shorthand strings, 2 hits, both sys-metadata-audit.object.ts:190) with a positive control at the parent of PR fix(platform-objects)!: sys_oauth_application.type declares only web and native, the two application types its writer produces #25099 (the third hit, 'user-agent-based'); (b) lock_state as Field.text({ maxLength: 255 }) holds the MetadataLock spelling its writer stores, with writer :19453 and reader :12666 unchanged and no row rewritten. The seat read the field.zod.ts, sys-metadata-audit.object.ts, changeset and ADR-0087 entry hunks against the diff.
  3. Gate check-runs (head-pinned): 42 runs, 34 success, 7 skipped, 1 in progress (Test Core), 0 failed at this record; readied only once every check is green or an expected skip. Contract review of record: 6318757920, Served-tier: CONTRACT_REVIEW_TIER, schema and narrowing faces, PASS on this head, adopted by this seat. Dogfood battery on this head: VERDICT command-exit 0, 2387 passed, 0 failed (the dev's report).
  4. Accepted deviations. Two files beyond the claim's list, both owed by gates: packages/spec/src/migrations/entries/semantic/18.field-select-shorthand-keeps-written-value.ts (the ADR-0087 registered disposition the gate required for the changeset's author-facing rewrite; a structured TODO, no D2 conversion) and scripts/engine-double-contract.pinned.json (one row written by the gate's own --write). One merge of origin/main (f2b6cf230, PR feat(gates): the changeset level follows the metadata protocol — Metadata-protocol: line, level axis, console refresh at patch, release notes by reader action (ruling A) #25114: no package source).
  5. Open questions and out-of-scope dispositions.
    • The dev's three open questions are answered A by the seat, within the ruling: the explicit branch keeps lower-casing (a documented transform, not a strip); lock_state as text (option B fails premise ②(b)); ADR-0087 registered (the gate's refusal was right on substance).
    • The dot case ('v1.2' now declares v1.2, not v12): stated in the changeset FROM → TO, the ledger entry and the Acceptance notes; zero in-tree producers. No card.
    • The dropped lock_state option translations: no card.
    • Two wording nits from the review (the Acceptance note says the dropped labels "never displayed", which holds for nooverlay / nodelete only; the object comment's varchar(255) holds for an upgraded database, a fresh one stores TEXT) do not change a published statement. They ride a next code push if one comes, else nothing.
  6. Landing clause. Once every check on the then-current head is green or an expected skip and mergeable_state is not dirty: relay pr_ready + automerge_enable; confirm added_to_merge_queue; confirm MERGED on origin/main; remove pm:dispatched from spec: Field.select's string-array shorthand silently strips hyphens from option values, so a written 'no-overlay' or 'user-agent-based' is not the declared value #25013 in the closing act; archive the cloud session session_0123qkvzKLQfRRRnuR8wanHJ.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/m tests tooling

Projects

None yet

2 participants