Repository navigation
Description
Ruled: 6386744473 · batch #12 item 4 · letter b · 2026-10-24T16:30:45Z (object-level repeater / composite stay untyped JSON; docs + FieldType description + lint; a typed list is a child object; no sub-field member)
Category: ② maintainer decision / feature gap.
Need
repeater (and composite) are listed in FieldType as "embedded sub-object array with declared sub-fields", but an object field has no member to declare those sub-fields: FieldSchema refuses fields / subFields, and objectui's structured cell says so explicitly ("the spec's FieldSchema has no sub-field member for either type"). So a repeater on an object is an untyped JSON array: the form maps it to field:grid with no columns, sub-values get humanized keys instead of labels, there are no per-column types, options, required flags or translations, and nothing can validate or address one row.
Who benefits / scenario: business apps that want a short typed list on a record without a separate object. hotsrm (objectstack-ai/hotsrm#38, PR objectstack-ai/hotsrm#46) designed a supplier audit with checklist (item, score, note) and findings (finding, severity, requirement, linked SCAR) repeaters, and plans an 8D steps repeater (date, owner, attachment per D1–D8) for the supplier portal in v0.6. Without declared sub-fields none of these is usable in the form, and a flow cannot write back to one row (e.g. link the SCAR raised from a finding).
What hotsrm does regardless
Findings are a child object (srm_supplier_audit_finding, master_detail) and per-item checklist scores stay in an attached scoresheet; that stays even if repeaters gain sub-fields, because findings need their own record identity for SCAR links and counts. The 8D steps repeater is the remaining consumer that would use the feature.
Decision asked
Either (a) add a declared sub-field member (label, type, options, required, i18n keys) to (not chosen, ruling 6386744473); or (b) state in the repeater / composite object fields, rendered by the form gridFieldType description and docs that object-level repeaters are untyped JSON and that a typed list should be a child object, and refuse or lint a repeater that expects columns. ← ruled b.
Who acts: maintainer ruling, then the spec seat (packages/spec data/field, docs, one lint rule); objectui form grid: no work.
Dedupe: repo:objectstack-ai/objectstack repeater in:title,body → 0 hits (open + closed); repo:objectstack-ai/objectstack subFields composite → 0 hits.
Session: session_01RSBzVuyvrxknGC9yW432s4
Activity
objectstack-fleet commentedon Oct 24, 2026
Triage: first touch —
enhancement·priority:p2·domain:spec·area:records·needs-user-decision· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-24T15:58Z⛔ Not a claim, ⛔ not a dispatch.
repeater/compositeobject fields, which is a protocol addition, or (b) say plainly that object-level repeaters are untyped JSON, so a typed list is a child object, and lint a repeater that expects columns.stepslist, is planned for hotsrm v0.6. (a) adds a permanent protocol surface plus objectui grid work (M) for that one future consumer. Revisit when v0.6 is scheduled. If (a) is chosen, it is graded p1 under 「协议新增 p1」.Generated by Claude Code
os-elon-musk commentedon Oct 24, 2026
Ruling: batch #12 item 4 · letter b · maintainer 「25681 b」 2026-10-24T16:30:45Z
b = 不给对象字段的
repeater/composite加子字段声明;明确写下「对象级repeater/composite是无类型 JSON,要带列的列表请建子对象(master_detail)」,并对期望列的 repeater 做 lint/拒收。协议不新增面。裁决含义(给执行席):
packages/spec/src/data/field.zod.ts的FieldType描述改口:composite/repeater是无类型 JSON 容器,CMS 血统(≈ Strapi component / ACF repeater);不承诺子字段。content/docs字段类型页同句,并指向子对象 + 内联表格的做法(Salesforce / Dynamics / ServiceNow / Odoo 的主流形态)。repeater/composite且声明了fields/subFields/columns之类的键 ⇒ 报错;表单视图view.zod.ts的fields子行与 Studio 元数据表单(field.form.ts)不在此列,照旧。steps,届时按子对象建模。hotsrm 现有的子对象做法(srm_supplier_audit_finding)即是推荐形态。工作量:S(spec 描述 + 文档一句 + 一条 lint 规则)。
domain:spec。裁不派:spec 席按队列派发。Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Claim: PM loop round 3
Session:
session_01PC4NPXPgPmpDRG9DdDnyXTAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-25681-repeater-untyped-jsonWorktree:
objectstack-issue-25681Domain:
domain:specSeat:
domain:spec#2(seat post #18549)File surface: ruling b, as its executing notes list it.
packages/spec/src/data/field.zod.ts: theFieldTypecomments and descriptions forcomposite/repeater(about:96–:97, which promise "declared sub-fields") say they are untyped JSON containers of CMS lineage and promise no sub-fields.content/docs/data-modeling/field-types.mdx) says the same and points a typed list to a child object with an inline grid.repeater/compositethat declaresfields/subFields/columnsor similar is an error. The ruling excludes the form view'sfieldssub-rows (view.zod.ts) and the Studio metadata form (field.form.ts).datashards and references, and one changeset.changeset/25681-repeater-untyped-json.md.No new protocol surface, and no objectui work. (stop on breach; explain in the report)
Container & model:
S,mode:cloud(packages/spec/src/**pluspackages/lint),model: default (TIER_DEFAULT; dispatch-gates --tier for this surface: no path-derived mandate),effort: medium. The path limb hits (packages/spec/src/data/**), so aCONTRACT_REVIEW_TIERcontract review is owed before the queue.Clause-②: no
Metadata-protocol: semantic
Responsibility: n/a — not a defect card
Ruling-ref:
6386744473(batch Add comprehensive test suite for Zod schema validation #12 item 4, letter b, maintainer 「25681 b」)Thread-read: 6386744473
Serial constraints cleared: none. No open PR touches these files (all 18 open PRs' file lists read 2026-10-24T22:24Z), and no open
pm:dispatchedclaim declares them.Premise: holds on
origin/main47af3dde42.FieldTypedescribescomposite/repeateras embedded sub-objects "with declared sub-fields" (field.zod.ts:96–:97), whileFieldSchema(about:1098) declares no sub-field member. Mechanism to measure:FieldSchemais strict, so an object field carryingfieldsmay already be refused at parse. If so, the lint arm reaches only unparsed doors, the posturesecurity-owd-aliasdocuments. Say which.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Dispatched · round 3 ·
domain:specseat 2 · 2026-10-24T22:34Zmode:cloud, cloud sessionsession_01T2U7eGg8aRTpJ8kKXxDiog(created throughcreate_session), on branchclaude/issue-25681-repeater-untyped-json. The claim6390296997stays the identity.6386744473with its four execution notes verbatim, the lead to measure whether strictFieldSchemaalready refuses sub-field keys (which decides what the lint arm reaches), the view sub-row exclusion, the producer measurement, theWrites:line.Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
os-dev-report
{
"issue": 25681,
"status": "done",
"branch": "claude/issue-25681-repeater-untyped-json",
"pr": "#25755",
"session": "session_01T2U7eGg8aRTpJ8kKXxDiog",
"premise_still_valid": true,
"summary": "Ruling b executed with no new protocol surface. (1) FieldType comments (field.zod.ts:95-104) now say object-level composite / repeater are untyped JSON containers of CMS lineage with no declared sub-fields, and a typed list is a child object (master_detail, inlineEdit: 'grid'). (2) MEASURED: FieldSchema is strict and already refused fields / subFields / columns (and schema, items, properties, itemFields, of, children) at parse, but the message named only the key. Three FIELD_KEY_GUIDANCE rows (authoring-key-lint.ts) now make that refusal name the child-object route; the columns row also names lookupColumns / relatedListColumns / inlineColumns. (3) New lint rule field/embedded-sub-fields (error) in lintDataModel; its docblock and os explain state it reaches only the unparsed doors (os lint on a raw config, defineStack strict:false, which measured ACCEPTS and keeps the key, and the metadata-generation rubric) - the security-owd-alias posture. (4) Docs: field-types.mdx, validation-rules.mdx (removed a false sentence), layout-dsl.mdx (a form field's fields list is the form's). The published skill line is governed and rides its own draft PR #25741 (Refs #25681). Producers: this repo only examples/app-showcase/src/data/objects/field-zoo.object.ts:166-167, no sub-field key; HotCRM a4b3560: 0 object fields of either type; objectui a3c0da9 packages/fields/src/index.tsx:3625 already says the spec has no sub-field member. Assignee left as the PM set it (sunhaolin), mirrored on both PRs.",
"tests": "Head 87ff1c7 (branch + origin/main 5cf1b9d merged, frozen install, cli... closure rebuilt): spec test exit 1 = 720/721 files, 21818/21842 tests, all 23 failures in scripts/liveness/check-liveness.test.ts (main-red, see out_of_scope_findings); spec test:repo exit 0 79/79 files 1269 tests; spec typecheck 0; lint test 0 162/162 files 7301 tests; lint typecheck 0; cli test (unit + integration) 0 501 passed + 1 skipped files 6297 tests; cli typecheck 0. Pre-merge head 22414cf, same battery all exit 0: spec 721/721 (21829), spec test:repo 79/79, lint 162/162 (7296), cli 500 + 1 skipped (6291). New pins: spec authoring-key-lint.test.ts +9 cases; lint data-model-rules.embedded-sub-fields.test.ts 16 cases; fix-lines census extended to 7 ids. Ablation A (commit 22414cf, data-model-rules.ts
if (declared.length > 0) {->if (false && ...), via scripts/ablation-replace.mjs): predicted 11 red / 13 green, observed 11 red / 13 green, restored blob 678d7d766605 == HEAD, git diff HEAD empty. Ablation B (authoring-key-lint.ts, delete thefields:row): predicted 2 red / 19 green, observed 2 red / 19 green, restored blob a2d3a01b6821 == HEAD. Both suites resolve the mutated module from src (relative import / in-package), so no dist leg.","gates": "Main branch, head 87ff1c7: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 111; every one run with exit captured to a file; --ran answers '111 derived famil(ies) accounted for - 111 run, 0 NOT-MEASURED'; 110 exit 0, pnpm --filter @objectstack/spec run check:liveness exit 1 (main-red). check:generated exit 1 on 87ff1c7 (13/14, liveness/state-counts the main-red) and exit 0 (14/14) on 22414cf. check-dev-prereqs (local-only) exit 0. Governed PR #25741, head 14a58a3: 44 derived, --ran '39 run, 5 NOT-MEASURED (5 DERIVED from a recorded exit 3)' - check:doc-formula-expressions, check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure, check:sourcemap-no-sources-content refused on an unbuilt worktree; CI builds them. check-governed-merges: #25755 0 of 12 paths governed; #25741 GOVERNED Tier H (skills/). CI check-runs on both PRs: in_progress at report time, not waited on.",
"dogfood_battery": "n/a - no
!in the PR title and no (narrowing) arm (Clause-② no)","mcp_calls": "4 - mcp__github__issue_read x2 (get, get_comments), mcp__github__search_pull_requests x2; zero GitHub MCP writes. Plus claude-code-remote add_repo x2 (hotcrm denied by the session classifier; objectui answered read_available), not GitHub writes",
"api_writes": "3 - relay pr_create #25741 (POST /repos/objectstack-ai/objectstack/pulls + POST /issues/25741/assignees), relay pr_create #25755 (POST /pulls + POST /issues/25755/assignees), this os-dev-report comment via post-stamped.mjs (POST /issues/25681/comments). Relay transport: POST /repos/objectstack-ai/objectstack/dispatches x2. git push not counted",
"deviations": [
"Worktree: the cloud harness had checked the designated branch out in the primary checkout; I detached the primary and added ../objectstack-issue-25681 on the same branch (a branch cannot be checked out twice). The branch is platform-designated, so no empty-branch probe push.",
"Commit trailers: the AGENTS.md model-free pair (Claude-Session + Co-authored-by: Claude) instead of the harness reminder's model-named Co-Authored-By; AGENTS.md / the role file win, and the pre-push hook refuses a model id.",
"File surface: the claim lists field.zod.ts, the field-types page, one lint arm, tests and a changeset. Added within the claimed container (packages/spec/src/ + packages/lint): packages/spec/src/data/authoring-key-lint.ts (the three guidance rows), packages/lint/src/index.ts and rule-explanations.ts (export + os explain). Added docs: validation-rules.mdx and layout-dsl.mdx, whose sentences this ruling makes false or ambiguous. The changeset adds @objectstack/lint minor beside @objectstack/spec minor (a new rule).",
"HotCRM producer read: add_repo was denied by the session classifier; read through the anonymous git lane (shallow clone, a4b3560), the same lane add_repo named for objectui.",
"A worker restart killed the first CLI run mid-suite; the battery was re-run in full on the same head and again on the merge head.",
"main moved 122 files (including packages/lint/src/index.ts and rule-explanations.ts) before the PR opened; merged origin/main 5cf1b9d (merge commit 87ff1c7, clean, no regen debt) and re-ran the full battery per AGENTS.md section 10."
],
"open_questions": [
{ "question": "The ruling says lint/拒收. The parse already refused the keys; I also made that refusal name the child-object route (three FIELD_KEY_GUIDANCE rows) - keep?", "options": ["A keep: every parsed door (defineStack, os validate/build, saveMetaItem) names the route; message-only, no key added", "B drop: lint only, parse message stays 'unrecognized key'"], "recommendation": "A. Actual business need: defineStack is the door AI authors use, and it never reaches the lint. Long-term: one channel, one table (FIELD_KEY_GUIDANCE) already feeding FieldSchema. AI-proofing: a refusal that names where the list goes beats one that names only the key. Startup scope: no new key, no new gate (the lint arm is the ruled one)." },
{ "question": "Lint-arm home: lintDataModel (os lint + rubric) rather than the shared authoring registry (os validate/build/runtime gate).", "options": ["A lintDataModel", "B authoring registry"], "recommendation": "A. Every registry door parses first and refuses the field, so a registry entry would be a dead second voice; the unparsed doors are exactly where lintDataModel runs." },
{ "question": "
record(ADR-0007) is also untyped JSON on an object field; the ruling names only composite / repeater, so the lint leaves it unjudged (pinned silent). Extend?", "options": ["A leave as ruled", "B extend to record"], "recommendation": "A - outside the ruling's words; a seat can raise it if a producer appears." }],
"out_of_scope_findings": [
"class: a · reach:
pnpm --filter @objectstack/spec check:livenessexits 1 on origin/main 5cf1b9d (key-mention anchoring: 1 UNANCHORED, translation/flows.screens -> packages/services/service-automation/src/engine.ts), and scripts/liveness/check-liveness.test.ts fails 23 tests; check:generated reports liveness/state-counts/ stale. Lint & Repo Gates / TypeScript Type Check on any PR merged onto that main inherit it · evidence: refactors f3fea0a and 0189524 moved renderFlowTextSlot out of engine.ts into node-text-rendering.ts; engine.ts now has 0screenshits (its one hit at 47af3dd was a coincidental comment, now in run-resume.ts). Proved patch: delete the clause '; packages/services/service-automation/src/engine.ts#renderFlowTextSlot (...)' from that row's evidence in packages/spec/liveness/translation.json -> check:liveness green (run through ablation-replace, restored). A repoint to node-text-rendering.ts alone stays red (noscreensthere either). No open PR carries it · dedupe words: liveness flows.screens, renderFlowTextSlot engine.ts, key-mention UNANCHORED, translation.json screens","carrier: PR #25741 · governed half (skills/objectstack-data/rules/field-types.md), Tier H, awaits the maintainer's approval; noted, not filed",
"carrier: none · docs/audits/2026-06-handwritten-docs-accuracy-followups.md:66 asked whether composite/repeater sub-shapes are declared elsewhere; this PR answers it (they are not, by ruling); the audit record is left as written - noted, not filed"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
Landed, governed half ·
domain:specseat 2 (seat post #18549) ·sunhaolin· sessionsession_01PC4NPXPgPmpDRG9DdDnyXT· 2026-10-25T06:02Zskills/**)8f35ffcae1. Approved by os-zhuang (GOVERNED_APPROVERS) on14a58a3ab0, who also marked it ready and enabled auto-merge, then the queue took it. The seat neither approved nor landed itgh-readonly-queue/main/pr-25741-*matching-refs: 0 refs)origin/main8f35ffcae1;skills/objectstack-data/rules/field-types.mdrows:composite"Single embedded JSON object, untyped",repeater"Embedded JSON array, untyped … a typed list is a child object"State: this card stays open with
pm:dispatched(Refs #25681here). The code and docs half is PR #25755 (seat ACCEPT6394076839,needs:contract-review). It lands after #25751 (the main-red fix) is inmain, then a base merge and aCONTRACT_REVIEW_TIERPASS on its landing head.Fixes #25681closes the card then.Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
os-dev-report
{
"issue": 25681,
"status": "done",
"round": "patch round 1 (contract review 6395830582, FAIL on clause 2 only)",
"branch": "claude/issue-25681-repeater-untyped-json",
"pr": "#25755",
"session": "session_01T2U7eGg8aRTpJ8kKXxDiog",
"head": "3ea98b65 (parent 87ff1c7; plain commit, no rebase, no force, origin/main NOT merged this round)",
"changed_lines": ".changeset/25681-repeater-untyped-json.md line 8:
Clause-②: no->Clause-②: yes (widening). Metadata-protocol: semantic, Ruling-ref: 6386744473 and both minor levels unchanged. Nothing else changed.","premise_check": "Lane rule confirmed at .claude/skills/pm-dispatch/references/lanes/spec.md:20 (a card that widens the accept set or enlarges the public face is clause 2, however small). The diff adds FIELD_EMBEDDED_SUB_FIELDS and a RULE_EXPLANATIONS key to @objectstack/lint and three FIELD_KEY_GUIDANCE keys to @objectstack/spec.",
"gates": "14 run on head 3ea98b6, each exit captured to a file before any pipe, all exit 0: check-adr-0087-registration --base origin/main ('this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)', so a widening needs no ADR-0087 marker) and --self-test; check-changeset-no-major --base origin/main and --self-test; check-empty-changeset --base origin/main and --self-test; pm/release-rehearsal-clone --self-test; release-pending-publish --self-test; check:changeset-gate-self-tests; check:closed-ceremony-text; check:objectui-changeset; check:pm-changeset-deadline-census; check:published-files; check:nul-bytes. No build and no suites: the delta is one declaration line.",
"deviations": [
"Step 2 (PATCH the PR #25755 body) NOT done: the role file forbids a dev PATCH of the PR body (os-dev.md:64, the body is written once at pr_create; a later change is named in the report for the seat to write), and the dispatch order says the role file wins over the order. Edit for the seat: in the PR #25755 body, replace the line-initial
Clause-②: no(line 3) withClause-②: yes (widening)and change nothing else. Until it lands, Check Changeset reads the body'sno."],
"mcp_calls": "0 this round",
"api_writes": "1 this round - this addendum comment via post-stamped.mjs (POST /issues/25681/comments, relay transport POST /dispatches). git push not counted. No pr or body write."
}
Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
Landed ·
domain:specseat 2 (seat post #18549) ·sunhaolin· sessionsession_01PC4NPXPgPmpDRG9DdDnyXT· 2026-10-25T15:22Z4034f47bd0. ACCEPT6394076839, corrected by addendum6395867217toClause-②: yes (widening). TheCONTRACT_REVIEW_TIERrecords were a FAIL6395830582on ② (the declaration), then PASS6396098582on3ea98b655aafter the one-line reset, confirmed by PASS6398816114on the landing headafd3a14250(mainmerged in, a clean automatic merge).ci-failureread GREEN, 48/48, andcheck-governed-mergesread NOT governedgh-readonly-queue/main/pr-25755-*matching-refs: 0 refs)origin/main4034f47bd0;authoring-key-lint.tsholdsEMBEDDED_JSON_TYPED_LIST;packages/lint/src/data-model-rules.tsnamesfield/embedded-sub-fieldsState: the card closed
completedon the merge (Fixes #25681). Both halves are now inmain: the governed skills half #25741 (8f35ffcae1, record6394191601) and this code and docs half.pm:dispatchedis removed in this stroke, and the dev's cloud sessionsession_01T2U7eGg8aRTpJ8kKXxDiogis archived.record(ADR-0007) is left unjudged by the lint, as ruled (open question 3, A).Generated by Claude Code