Repository navigation
feat(spec,lint): object-level composite / repeater are untyped JSON — a typed list is a child object - #25755
Conversation
…ld keys refused with the child-object route Claude-Session: https://claude.ai/code/session_01T2U7eGg8aRTpJ8kKXxDiog Co-authored-by: Claude <noreply@anthropic.com>
…g sub-fields is an error on the unparsed doors Claude-Session: https://claude.ai/code/session_01T2U7eGg8aRTpJ8kKXxDiog Co-authored-by: Claude <noreply@anthropic.com>
…ist to a child object with inlineEdit grid Claude-Session: https://claude.ai/code/session_01T2U7eGg8aRTpJ8kKXxDiog Co-authored-by: Claude <noreply@anthropic.com>
…peater-untyped-json
📓 Docs Drift CheckThis PR changes 2 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 144 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin dad82094c7b3cf272778a2c80414aa7a809f5b2e && git checkout dad82094c7b3cf272778a2c80414aa7a809f5b2e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6679644a1a56b552d6864b125900221823ec78b3 afd3a1425027a1d9ce67bafc4ef145327fd701e8 && git checkout -B drift-repro 6679644a1a56b552d6864b125900221823ec78b3 && git merge --no-ff afd3a1425027a1d9ce67bafc4ef145327fd701e8
node scripts/docs-audit/affected-docs.mjs --json 6679644a1a56b552d6864b125900221823ec78b3
|
席位核对ACCEPT · #25681 ·
|
Contract reviewServed-tier: Rendered 2026-10-25T09:15Z · PR #25755 · card #25681 · ruling ① Derived judgmentsAccept set, per door — each named right or wrong against the code:
② Semver level
③ Boundary flagsDev deviations (report
Open questions:
Out-of-scope findings:
Reviewer's own flags: none beyond ② and the two notes in ① (items 3 and 6). PR author is Implemented-by: VERDICT: FAIL FAIL on ② alone: the |
|
ACCEPT addendum (corrects item 3 of Item 3 was wrong. The seat read
By the lane rule (
Generated by Claude Code |
…lic exports Claude-Session: https://claude.ai/code/session_01T2U7eGg8aRTpJ8kKXxDiog Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Rendered 2026-10-25T09:44Z · PR #25755 · card #25681 · ruling What moved since the prior record. One commit, ① Derived judgmentsEvery accept-set and public-surface change the diff makes, each judged against the code at the PR's tree:
② Semver level
③ Boundary flagsDev deviations (report
Patch-round addendum ( Open questions (all three carried unchanged from the first report):
Head binding and landing:
Reviewer's own flags:
Implemented-by: VERDICT: PASS PASS on ①②③: the prior record's one finding is resolved in both carriers, the accept set of every gate door is unchanged, the public-face widening is declared, and the three boundary flags are answered. The check-runs are not green at render time (1 shard in progress; |
Contract reviewServed-tier: Rendered 2026-10-25T14:22Z · PR #25755 · card #25681 · ruling What moved since the prior record. One commit, the head itself: ① Derived judgments
② Semver level
③ Boundary flagsDev deviations (report Open questions (carried unchanged from the first report): (1) keep the three guidance rows — A, right (① items 2–3); (2) lint home Head binding and landing:
Reviewer's own flags, carried: (1) the Implemented-by: VERDICT: PASS PASS on ①②③: the PR's 12 hunks are byte-identical to the reviewed head's save one hunk-header offset, the head is the clean automatic merge of that head with |
Fixes #25681
Clause-②: yes (widening)
Metadata-protocol: semantic
Ruling-ref: 6386744473
Ruling 6386744473 (batch #12 item 4, letter b), verbatim:
What changed
FieldType(packages/spec/src/data/field.zod.ts). The comments oncomposite/repeaterused to promise "declared sub-fields". They now say these are untyped JSON containers of CMS lineage (Strapi component, ACF group or repeater), thatFieldSchemahas no sub-field member, and that a typed list is a child object with amaster_detailfield (inlineEdit: 'grid'edits it inline on the parent's form). No schema change: the enum,FieldSchemaand every generated artifact are byte-identical (check:generated: all 14 up to date).packages/spec/src/data/authoring-key-lint.ts). ThreeFIELD_KEY_GUIDANCErows,fields/subFields/columns, fed into the strictFieldSchemathrough its existingguidancechannel. The parse already refused all three before this PR, but its message named only the key, so an author learned the key was wrong but not where the list goes. Thecolumnsrow also names the three real column keys (lookupColumns,relatedListColumns,inlineColumns). This changes a message, not what parses.field/embedded-sub-fields(error) inlintDataModel(packages/lint). It fires when acomposite/repeaterobject field carriesfields,subFieldsorcolumns. It has anos explainentry and a one-linefix. Its docblock's intake table states which doors reach it (below).data-modeling/field-types.mdxgets the same sentence plus a child-object example, the shape Salesforce, Dynamics, ServiceNow and Odoo use.data-modeling/validation-rules.mdxloses the false "Sub-field shapes are declared on the field".protocol/objectui/layout-dsl.mdxnow says that a form field'sfieldslist belongs to the form, not to the object field.fieldssub-rows (view.zod.ts),field.form.ts, objectui. No member was added toFieldSchema.The published skill
skills/objectstack-data/rules/field-types.mdrepeated "declared sub-fields".skills/**is a governed surface (Tier H), so that fix is its own draft PR onclaude/issue-25681-skills-repeater-untyped(Refs #25681). This PR carries no governed path.Measured before building (on
origin/main47af3dde)field.zod.ts:96–:97promised "declared sub-fields", and so didfield-types.mdx:515/:522and the skill file.FieldSchemadeclares no sub-field member. TheFieldTypeenum has no.describe(), so the generated references never carried the claim.FieldSchema.safeParseon arepeater/compositecarryingfields,subFields,columns,schema,items,properties,itemFields,oforchildrenanswersunrecognized_keyson every one. Before this PR the message wasUnrecognized key(s) on this field: `fields`. Until this shape was closed these were dropped silently — …: it names no route.defineStack(x)(strict) throws the same message.defineStack(x, { strict: false })ACCEPTS the field and keepsfieldsverbatim.security-owd-aliasdocuments. Its docblock andos explainboth say so:field/embedded-sub-fields?defineStack(x)(strict), henceos validate/os build/os linton such a configsaveMetaItem(Studio / REST/meta/ MCP)os linton a raw object-literal config (lintConfigruns before any parse)defineStack(x, { strict: false })scoreMetadata:lintConfigon the normalized input, beside its own schema verdict)examples/app-showcase/src/data/objects/field-zoo.object.ts:166–:167(f_composite,f_repeater), and neither carries a sub-field key. Every other hit is a metadata FORM row (*.form.ts, excluded by the ruling). HotCRM ata4b3560: 0 object fields of typerepeater/composite(control:type: 'lookup'hits insrc/sales/actions/activity-actions.ts). objectui ata3c0da9:packages/fields/src/index.tsx:3625–:3626already states that "the spec'sFieldSchemahas no sub-field member for either type and refusesfields/subFields". No producer is refused by this PR that was not already refused.Ablations (commit first, mutate through
scripts/ablation-replace.mjs, self-restore)data-model-rules.ts:if (declared.length > 0) {→if (false && …)embedded-sub-fields.test.ts, 2 infix-lines.test.ts), 13 greenauthoring-key-lint.ts: delete thefields:guidance rowrepeater + fields,composite + fields), 19 greenBoth restored blob-identical to HEAD (
678d7d76,a2d3a01b), andgit diff HEADwas empty after each. Both suites resolve the mutated module fromsrc/, never through adist/export, so no rebuild leg applies.Verification
Every reading below is on head
87ff1c79(this branch withorigin/main5cf1b9d2merged in), afterpnpm install --frozen-lockfileand a rebuild of the@objectstack/cli...closure. Each exit code was captured to a file before any pipe.pnpm --filter @objectstack/spec testscripts/liveness/check-liveness.test.ts, themain-red belowpnpm --filter @objectstack/spec test:repopnpm --filter @objectstack/spec typecheckpnpm --filter @objectstack/spec check:generatedliveness/state-counts/is themain-red below. On the pre-merge head22414cf1: 14/14pnpm --filter @objectstack/lint testpnpm --filter @objectstack/lint typecheckpnpm --filter @objectstack/cli test(unit and integration)pnpm --filter @objectstack/cli typechecknode scripts/pm/dispatch-gates.mjs --ran …✓ 111 derived famil(ies) accounted for — 111 run, 0 NOT-MEASURED. 110 exit 0;check:livenessexit 1 is themain-red belownode scripts/check-dev-prereqs.mjs(local-only)Pre-merge head
22414cf1, the same battery, all green: spectest721/721 (21829 tests), spectest:repo79/79, lint 162/162 (7296 tests), cli 500 + 1 skipped files (6291 tests), and 111/111 families (110 run green,check:dual-build-cjs-loadsNOT MEASURED there and green on87ff1c79).Not this PR's:
check:livenessis red onmain5cf1b9d2. Thetranslationledger rowflows.screenscitespackages/services/service-automation/src/engine.ts#renderFlowTextSlot.engine.tsno longer namesscreensin any spelling: the refactorsf3fea0a0and0189524amovedrenderFlowTextSlotintonode-text-rendering.ts. Its one earlier hit was a coincidental comment ("states two screens down"), which moved torun-resume.ts. Neither file of this PR touches it. Proposed patch, proved but not applied here: delete the clause; packages/services/service-automation/src/engine.ts#renderFlowTextSlot (…)from that row'sevidenceinpackages/spec/liveness/translation.json. Thescreen-nodes.ts#registerScreenNodespointer beside it already cites the caller and names the key. With that deletion,pnpm check:livenessanswers ✓ (run throughscripts/ablation-replace.mjs, then restored blob-identical). Repointing tonode-text-rendering.tsalone would stay red: that file does not namescreenseither. No open PR carries a fix (searched 2026-10-25).Acceptance notes
shared/suggestions.zod.ts):subfieldsorsub_fieldsstill gets the bare refusal. The three spellings are the ones the ruling names.record(ADR-0007) is also stored as untyped JSON on an object field, but the ruling names onlycomposite/repeater, so the lint does not judgerecord(pinned silent).docs/audits/2026-06-handwritten-docs-accuracy-followups.md:66asked "whether sub-shapes are declared elsewhere". This PR answers it (they are not, by ruling); the audit record is left as written.🤖 Generated with Claude Code
https://claude.ai/code/session_01T2U7eGg8aRTpJ8kKXxDiog
Generated by Claude Code