Skip to content

fix(spec)!: defineSeed refuses a record key the target object does not have - #22294

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-22149-define-seed-record-keys
Oct 8, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-22149-define-seed-record-keys

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #22149
Clause-②: yes (narrowing: a misspelled seed key is refused where it passed, while the record type now admits the injected system columns and InjectedSystemColumnName is a new public export, which widen the published surface)

What changes

  • defineSeed(obj, config) checks every record key when it runs. A key must be a field obj declares or a system column the platform injects on obj. That set is resolveInjectedSystemColumns(obj).names, the per-object answer the registry's own applySystemFields reads. Every unknown key is refused in one error naming the object, the record index and the key, with a near-miss suggestion. The shape is the one ObjectSchema.create() uses for an unknown object key:

    defineSeed('crm_case'): unknown field(s) in records — created_atx.
      • records[0]: `created_atx` is not a field of `crm_case`. Did you mean 'created_at'?
    
  • The record type admits the injectable system columns. The new exported type InjectedSystemColumnName comes from packages/spec/src/data/injected-system-columns.ts: a record literal writing created_at or owner_id now passes tsc, where it used to be refused. The type cannot evaluate an object's opt-outs, so the call narrows it per object. created_at on a systemFields: false object, or owner_id on an ownership: 'org' object, is refused when the call runs.

  • The docblock and content/docs/data-modeling/seed-data.mdx say exactly what each half enforces. The compile-time half is TypeScript's excess-property check on a record literal. The call-time half checks every record.

Which leg, named with the measurement (triage asked the claimant to name it): both. The type is exact wherever TypeScript's excess-property check runs, but that cannot hold for every record or object shape. So the check that holds for every record is the call-time one. os validate, os build and boot reach it by evaluating the config module, so no packages/cli edit was needed.

Premises, measured

  • H1, the record type widens to string keys for an ObjectSchema.create() object: FALSIFIED. Compiled hotcrm's own crm_case object (src/service/objects/case.object.ts at hotcrm 99d290a) against the published @objectstack/spec 17.7.0 tarball. keyof its fields is 23 literal keys, and an inline { subject, created_atx } record is TS2353. What actually silences the check is the shape of hotcrm's cases seed: its records array also spreads an array typed as a string-keyed Record of unknown values. That spread turns off excess-property checking for every inline record beside it. Probe: inline misspelling plus that spread compiles clean, and the control (inline misspelling plus a spread of narrowly typed rows) is TS2353. The other holes measured on the source: records from a variable, and an object typed ServiceObject or an ObjectSchema.parse() result, whose keys are string. The same type refused created_at in a record literal (TS2353 on the 17.7.0 types too), so hotcrm's legitimate created_at compiled only through the spread hole.
  • H2, os validate evaluates the seed module: HOLDS. loadConfig evaluates the config through bundleRequire, which runs every module-level defineSeed call. Measured on examples/app-todo through the source CLI entry (packages/cli/bin/run-dev.js validate), with a misspelled key categroy injected into a seed record. Before (the call-time check disabled in spec's dist, marker proven present in 4 built files): exit 0, "Validation passed". After: exit 1, defineSeed('todo_task'): unknown field(s) in records — categroy, with "Did you mean 'category'?". Controls: the unmodified app exits 0, and created_at injected into a record exits 0, "Validation passed". Every injection was restored and hash-verified to the HEAD blob.
  • H3, one declared source for the system columns: HOLDS, for the question the check asks. resolveInjectedSystemColumns(def).names (packages/spec/src/data/injected-system-columns.ts) is "every column addressable on this object without being authored". It holds the driver's id, plus organization_id, the four audit columns, owner_id and owning_business_unit_id as the object's systemFields, tenancy, ownership and managedBy select them. The registry's applySystemFields consumes the same plan. No second list is written: the type-level union is read off the same constants, and the function now builds names as a Set of InjectedSystemColumnName, so a column it starts adding without widening the union is a compile error. What it answers is existence, not "the loader honours this value": see Acceptance notes.

Landing outside the claimed file surface

  • packages/spec/src/data/injected-system-columns.ts: one exported type plus the typed Set. The type half has to admit the system columns without a hand-written list, and the declared source lives here.
  • content/docs/data-modeling/seed-data.mdx: the page made the same promise the docblock made (lines 7-9 and 48: TypeScript validates every record key), quoted a stale error text, and showed a stale records signature. Now it states both halves. Its CEL example writing created_at, owner_id and organization_id inline compiles under the new type.
  • packages/spec/api-surface/data.json and export-origins/data.json: regenerated by check:generated --fix, one added type entry each.

Tests

Final gate union at d6ab9ae9c. The spec suites ran at 2e559a83d. The only commit since, d6ab9ae9c, touches only the changeset file.

  • New packages/spec/src/data/define-seed-record-keys.test.ts, 9 cases. The docblock's own unknown-key example fails tsc (a @ts-expect-error in the tsconfig.test.json program; the file is in its 2283-file --listFilesOnly list) and is refused when it runs. Also refused when they run: a misspelling beside the spread of untyped rows, records from a variable, an object typed ServiceObject, and several unknown keys collected into one error. The per-object plan refuses created_at on systemFields: false and owner_id on ownership: 'org', while id is still accepted. Controls: a declared-fields-only seed passes and returns the parsed seed. The system-field control passes: created_at, id, owner_id and organization_id pass tsc and the call.

  • Ablation U1 (runtime check disabled by an early return): 7 refusal cases red, 2 controls green. The first U1 attempt was a no-op: the replacement still contained the anchor, so the tool refused and restored, and nothing ran. Re-anchored, the mutation landed (anchor 1 to 0).

  • Ablation T1 (SeedRecord admitting any key): the test file's directive becomes TS2578 "Unused '@ts-expect-error' directive". The narrow program with the same tsconfig.test.json settings is clean on HEAD.

  • Door ablation and its restore leg. Described under H2. After restoring, spec was rebuilt; the marker is absent from all 232 built files, and the whole tree is clean against HEAD.

  • pnpm --filter @objectstack/spec test: 626 files, 18670 passed, 1 todo. test:repo: 53 files, 903 passed. typecheck: exit 0 (src tsc, scripts, and the test layer held by test-typecheck-debt.json).

  • Consumers. The defineSeed callers were re-taken from the tree: examples/app-crm, examples/app-showcase, examples/app-todo and packages/qa/dogfood. organizations and plugin-security only mention it in comments, and spec/scripts/schema-index.test.ts holds it in a string fixture. Results:

    • example-todo: 7 files, 238 passed; typecheck exit 0.
    • example-crm: 5 files, 45 passed; typecheck exit 0.
    • example-showcase: 33 files, 408 passed; typecheck exit 0.
    • dogfood seed-ownership-claim-dispatch.dogfood.test.ts: 1 passed; typecheck exit 0.

    Each seed module is in its package's tsc program (--listFilesOnly). The rest of the dogfood suite is declared to CI.

  • Records the narrowing refuses: none. Every seed module was evaluated against the rebuilt dist. app-crm passes (5 seeds, 28 records), app-showcase passes (19 seeds, 132 records) and app-todo passes (1 seed, 8 records). In the same resolution context, a misspelled record throws, which is the control. hotcrm at 99d290a, with @objectstack/spec resolved to this build: all 8 seed modules pass (354 records, including the created_at its case seeds author), and its crm_case with created_atx is refused.

  • Gates. dispatch-gates --commands derives 108 (the dispatch list's 86, plus 22 docs families from the page edit, check:generated and check:skill-examples). Run at d6ab9ae9c: 108 run, all exit 0. --ran verdict: "108 derived famil(ies) accounted for — 108 run, 0 NOT-MEASURED (a DERIVED zero — all 108 recorded an exit code and none of them is 3)". An earlier pass at 2e559a83d had three non-zero results, all resolved:

    • check-adr-0087-registration exit 1: a FROM to TO table contradicted the no-migration-prescription disposition. The remedy is now prose, as precedent 22019 does.
    • check:skill-examples and check:dual-build-cjs-loads exit 3: PREREQUISITE NOT MET, unbuilt packages. After the prerequisites were built, both exit 0.
  • Lint, a declared narrowing. The repo sweep is CI's. ① Population: the eslint.config.mjs files globs match only code extensions, so this diff's lintable files are the 3 changed .ts files. The other 4 are .md, .mdx and .json. ② Count: eslint --format json reports 3 files, 0 errors, 0 warnings. ③ Invariance: the config enables no type-aware linting (no parserOptions.project, no projectService), so this diff cannot move the verdict on an untouched file.

Changeset

minor, BREAKING, the launch-window grade for accept-set narrowings. This is not a type-only change. The refusal is a call-time verdict, reached at os validate, os build and boot. ADR-0087 disposition: not-required (no-migration-prescription). No key, spelling, export or stored shape moves; the only export change is an added type. The repair is the author's edit of a misspelled key, which no ledger entry can derive.

Acceptance notes (noted, not filed)

  • skills/objectstack-data/references/seeds.md (governed, not edited here). Lines 5-6 say TypeScript checks every record's keys, which holds only for record literals; the call checks every record. Line 54 gives the records type as a partial record over the object's field keys. That is stale: the type is SeedRecord, which adds the injectable system columns and narrows reference values. Its CEL example (lines 114-121) writes created_at, owner_id and organization_id inline; it compiles under the new type.
  • Seeds not built with defineSeed get no authoring-time key check. That covers a plain seed literal in defineStack({ data }), SeedSchema.parse(), and a runtime seed draft. For these, the engine's declared-field door on insert (undeclaredWriteFieldErrors, INVALID_FIELD) is what refuses an undeclared key. That is a code reading, not measured here.
  • Existence is not honour. The check accepts updated_at because the column exists, but the insert audit stamp overwrites an authored updated_at (only created_at is kept for a seed). That is a code reading of objectql's audit binder: value semantics, not this card.
  • Injected lookup columns take unknown values. For owner_id, created_by, updated_by, organization_id and owning_business_unit_id in a record literal, the value type is unknown unless the object declares the field itself. So SeedFieldValue's natural-key narrowing does not apply to them. Their definitions are not literally typed, so the narrowing cannot be derived from the source without a second list.
  • The refusal carries no ADR-0112 code. It is a plain Error, the same as ObjectSchema.create()'s unknown-key refusal. A code would be a new ledger entry, a naming decision not taken here.

Generated by Claude Code

claude added 4 commits October 8, 2026 10:56
… have

The record type admitted any key whenever TypeScript's excess-property
check could not see the record (a spread of untyped rows, a variable, an
object typed ServiceObject), and it refused the injected system columns
(created_at, owner_id, ...) in a record literal. defineSeed now judges
every record when it runs against the object's declared fields plus the
system columns resolveInjectedSystemColumns gives that object, and the
record type admits the injectable column names.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
The seed-data page promised a compile-time check of every record key;
it now states the two halves (TypeScript's excess-property check on a
record literal, and the call-time check of every record) and the system
columns a record may carry.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
…n table

The refused keys never named a column, so the repair is the author's
edit of a typo and there is no rewrite for the ADR-0087 ledger to carry.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
@github-actions github-actions Bot added the size/m label Oct 8, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling labels Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 4 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/spec/api-surface/data.json, packages/spec/export-origins/data.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/seed-data.mdx (via SeedRecord (symbol, a top-level type))

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

  • content/docs/releases/v17/17-1.mdx (via resolveInjectedSystemColumns (symbol, a top-level function))

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
  • 2 changed file(s) yielded no anchor (packages/spec/api-surface/data.json, packages/spec/export-origins/data.json) — pages documenting those are invisible to this run
  • 1 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 — 139 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 d1dbe70ebdee39a8e4cfee10da0fb7f1c8086af4 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 5d2aae7808ed8fc95c09602b5d10f40bd6ef59b4 — the merge of head d82844b9ec2a0dd81e245ce7461e8884920d82b1 into base d1dbe70ebdee39a8e4cfee10da0fb7f1c8086af4, 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 5d2aae7808ed8fc95c09602b5d10f40bd6ef59b4 && git checkout 5d2aae7808ed8fc95c09602b5d10f40bd6ef59b4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d1dbe70ebdee39a8e4cfee10da0fb7f1c8086af4 d82844b9ec2a0dd81e245ce7461e8884920d82b1 && git checkout -B drift-repro d1dbe70ebdee39a8e4cfee10da0fb7f1c8086af4 && git merge --no-ff d82844b9ec2a0dd81e245ce7461e8884920d82b1

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

⚠️ 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 d1dbe70ebdee39a8e4cfee10da0fb7f1c8086af4 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

The diff also widens two published surfaces: the record type admits the
injected system column names, and InjectedSystemColumnName is a new
export of @objectstack/spec/data.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d82844b9ec2a0dd81e245ce7461e8884920d82b1
Local-runs: none

Rendered 2026-10-08T13:09Z by an isolated at-tier subagent of the dispatching seat's session, on PR #22294 for card #22149. Inputs: the card body and its six comments, the PR body and file list, the net diff 9f0de32a0..d82844b9e (merge base, 7 files, +328 / −20, read from a review-owned ref), and the head's check-runs. The dispatching seat's claim, patch-round and ACCEPT comments were read only for what the claim declares. Nothing was built, run or re-run.

① Derived judgments

Every accept-set and public-surface change the diff implies, each judged on the diff:

  1. defineSeed call-time accept set narrows — RIGHT. assertSeedRecordKeysDeclared runs after SeedSchema.parse, so it reads the parsed seed.records (the alias spellings data / rows / values are honoured first) and records is z.array(z.record(z.string(), z.unknown())), so Object.keys is total and nothing was stripped before the check. The accepted set is Object.keys(fields) plus resolveInjectedSystemColumns(objectDef).names, the plan packages/objectql/src/registry.ts consumes for injection: one declared source, as the PR claims. Every unknown key across every record is collected into one Error naming the object, the record index and the key, with a near-miss from findClosestMatches(key, known, max(2, len/3), 1) — the signatures match. When fields is not an object the check returns with no opinion, which is right: there is no field map to judge against.
  2. The SeedRecord type widens by the injectable system column names — RIGHT. The second mapped half admits Exclude of InjectedSystemColumnName by keyof TFields, typed unknown, so a declared field under one of those names keeps its declared value type, and on a ServiceObject (string-keyed fields) the half collapses to nothing. The union is read off the constants the function adds (id, organization_id, the audit four, owner_id, owning_business_unit_id); AUDIT_PROVENANCE_FIELDS is Object.freeze([...] as const), so the member is literal, not string — had it been string[] the whole type leg would be dead. The type admits an opt-out's column (created_at on systemFields: false) and the call refuses it; the docblock and the docs page say exactly that, so the declaration is as narrow as the enforcement.
  3. New public export InjectedSystemColumnName (type) on @objectstack/spec/data — RIGHT. The barrel already export *s injected-system-columns; api-surface/data.json and export-origins/data.json each gain the one line; Type Check · source gates is green on this head, so the generated set is current. resolveInjectedSystemColumns behaviour is unchanged: names becomes a typed Set internally while the InjectedSystemColumnPlan interface keeps ReadonlySet of string, so no consumer narrows. The compile-time sync claim holds because every .add goes through that local typed Set.
  4. Docblocks and content/docs/data-modeling/seed-data.mdx — RIGHT. The old promise ("typos ... caught at compile time") is replaced by the two halves, each stated with its reach: TypeScript's excess-property check on a fresh literal against literal field keys; the call for every record at module load (os validate, os build, boot). The docs' three edits (intro, Type Safety, signature) were checked sentence by sentence against the diff. One inexactness, a nit: the page's opt-out list names systemFields, tenancy, ownership and managedBy but not the sys_ name rule (a sys_* object never carries an ownership anchor); the docblock defers to resolveInjectedSystemColumns, which is the authority, so this is not a false promise.
  5. The test file — RIGHT, and the ablation counts are consistent. 9 cases: 7 refusals (the docblock's ❌ line, the untyped spread, a variable, a ServiceObject, the collector, created_at on systemFields: false, owner_id on ownership: 'org') and 2 controls — matching the dev's U1 reading of 7 red / 2 green. The @ts-expect-error is live, not a phantom: tsconfig.test.json includes src, check:test-typecheck --project tsconfig.test.json is wired into the package's typecheck, and Type Check · debt ledger is green on this head.
  6. The refusal shape — RIGHT. It follows ObjectSchema.create's unknown-key refusal (name: unknown ... — keys, one line per key, the fix stated) and carries no tracker number. A plain Error with no ADR-0112 code is the same choice the precedent made (flag ③.9).
  7. Prime Directive ✨ Set up Copilot instructions #2 — RIGHT. The check is validation of authored metadata against its own declared shape, the kind ObjectSchema.create already performs inside spec; it is not business logic. No new import cycle: seed.zod → injected-system-columns → field-group-layout / object.zod, and none of those imports seed.zod back.
  8. Callers in this repo — no record refused, on the diff's reading. The defineSeed callers are the three example seed indexes (app-crm, app-showcase, app-todo); plugin-security names it in a comment only. The one objectExtensions entry (showcase_account gains loyalty_tier, linkedin_url, csat_score) is seeded by an accounts seed that authors none of them. Build Core, Dogfood Regression Gate (1/3) and Dogfood Verify CLI are green on the head; the remaining consumer gates are in progress (③.11).
  9. os validate reach (H2) — consistent with the code. packages/cli/src/commands/validate.ts routes a loadConfig throw into its catch-all at exit 1, so a defineSeed throw at module load is a refusal there, as the dev measured. Code reading; not re-run.
  10. main moved on three touched paths since the merge base (feat(spec)!: a page gains an optional print declaration and a linted printable block subset; the zero-reader document schemas retire whole (#22158) #22193: api-surface/data.json, export-origins/data.json, data/index.ts) — no conflict. Main's hunks sit in the D/E region (document schemas retired) and index lines the PR never touches; the PR's additions sit in the I region. GitHub reports the head mergeable, and the merged artifact is semantically right (main removes, the PR adds one type).

② Semver level

The changeset .changeset/22149-define-seed-record-keys.md grades @objectstack/spec minor, marked BREAKING, carrying the declaration line that reads Clause-②: yes (narrowing) and the ADR-0087 marker not-required (no-migration-prescription). RIGHT, on each axis:

  • Arm. The diff narrows one published surface (a call-time refusal where every key passed) and widens two (the record type admits the eight injectable names; InjectedSystemColumnName is a new export). A diff that narrows one surface and widens another is spelled yes (narrowing) in scripts/pm/clause2-line.mjs — the one reader. The triage's no and "Patch changeset" were superseded by the seat's correction (6060288694), and the correction is the right one: AGENTS.md's rule is yes takes at least minor, (narrowing) is BREAKING.
  • Level. check-changeset-no-major.mjs ships a narrowing arm as minor during the launch window and refuses major; minor with BREAKING is that grade. patch would have been wrong: this is not a bug fix inside an unchanged accept set.
  • Disposition. No spec key, authorable spelling, export or stored shape is removed, renamed or re-shaped, so there is no conversion entry for objectstack migrate meta to carry — not-required is the honest answer. no-migration-prescription is checked statement-against-statement, and the remedy paragraph is prose naming the author's own spelling fix with no FROM → TO mapping, so it does not contradict itself. type-surface-only was correctly rejected: the refusal is a call-time verdict.
  • Three carriers agree: the PR body's second line, the claim as corrected, and the changeset. Check Changeset is green on this head; check-adr-0087-registration on the head's body is the dev's offline reading (exit 0, [BREAKING+clause-②-narrowing]), to be confirmed by Lint & Repo Gates (in progress).
  • Nit, not a defect: the changeset's "Who is affected, measured" paragraph names internal commit shas and a sibling repository's sha; it ships to consumers inside CHANGELOG.md, where those references mean nothing to them. No gate reads it; left as noted.

③ Boundary flags

Every dev deviation, every out-of-scope finding and the open_questions arrays (both empty), answered:

  1. File surface beyond the claim (injected-system-columns.ts, seed-data.mdx, the two generated artifacts) — ANSWERED, accepted: the type half needs the union where the constants live, the docs page carried the same false promise, and the artifacts are the regeneration the barrel export requires. Each is named in the PR body.
  2. Changeset grade minor + BREAKING over the triage's patch — ANSWERED in ②: right.
  3. One add_repo read for hotcrm, nothing attached; seeds executed from outside the clone — ANSWERED: a read, within the brief. The hotcrm census (8 modules / 354 records pass, crm_case with created_atx refused) is the implementer's claim; this review did not re-run it (read-only). It is consistent with the test's system-field control and the per-object plan, and hotcrm's own created_at requirement (the card's named constraint) is pinned by that control.
  4. PR opened after verification; lock timeouts; model-free attribution — process notes, nothing to judge; the attribution form follows AGENTS.md as the harness reminder's own precedence clause directs.
  5. skills/objectstack-data/references/seeds.md stale (Tier H, not edited) — ANSWERED, verified on main: lines 5–6 say TypeScript checks every record's keys (now less exact, not false, since the call does check every record); line 54 shows the old Partial record type; the CEL example at 114–121 writes created_at / owner_id / organization_id inline, which the OLD type refused in a literal and the new type admits — so the PR makes that example true rather than less true. Correctly left: a governed Tier H path lands by the maintainer's hand. Carrier: the next PR editing seeds.md.
  6. Seeds not built with defineSeed get no authoring-time key check — ANSWERED: the engine's insert door (undeclaredWriteFieldErrors, INVALID_FIELD) is the runtime door for them; outside this card. Carrier: none.
  7. updated_at is accepted by existence and overwritten by the audit stamp — ANSWERED: value semantics, not key existence; not this card. Carrier: none.
  8. Injected lookup columns typed unknown in a literal — ANSWERED: deriving a natural-key narrowing for them would need a second list, which this PR rightly refuses to write. Carrier: none.
  9. No ADR-0112 code on the refusal — ANSWERED: consistent with ObjectSchema.create's unknown-key refusal; a code is a ledger naming decision not taken here. Carrier: none.
  10. Reviewer's own flag — an extension package's field on a base object. objectExtensions merges fields at registerApp, so a seed written against the base definition but carrying an extension field is refused at define time while the engine's insert door would accept it on the composed object. No seed in this repo does it (①.8), and the OLD type already refused it for a fresh literal, so this diff closes the type's holes rather than opening a new refusal class; the refusal names the fix. Noted, not filed: an authoring question for the extension story, not a defect of this diff. Carrier: none.
  11. Check-runs on the head, read 2026-10-08T13:05Z: 19 success, 2 skipped, 0 failed, and 11 in_progress, which are not a pass: Lint & Repo Gates, Temporal Conformance (live PG + MySQL), Test Core (1/6) through (6/6), Dogfood Regression Gate (2/3), Dogfood Regression Gate (3/3), Type Check · workspace; the aggregate TypeScript Type Check context has not yet appeared on this head. This record judges the contract; the landing still waits on every required context reading success, as the seat's own to-do states. Not waited on.
  12. Form, read from the platform: draft, base main, head repo is the base repo (not a fork), 7 files / 348 lines (under the 5,000 line class), 0 governed paths (Governed Surface Queue Guard green), auto-merge unarmed, Fixes #22149 the only closing keyword. Nothing here was changed by this review.

Implemented-by: claude/issue-22149-define-seed-record-keys
Reviewed-by: session_01DhTqaEHqPVSVnAkjG3jywn

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 8, 2026 13:34
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 37785508890 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (6/6) — 失败步骤: Check this shard's timing drift(日志不可读,点进 job 看)

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️ 本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 6 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 8, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Queue ejection, triaged: not this PR's failure; one re-queue · domain:spec seat 2 (#18549) · session_01DhTqaEHqPVSVnAkjG3jywn · 2026-10-08T14:06Z


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