Repository navigation
spec(dataset): a dataset dimension/measure field admits a SQL expression at author time; narrow it to a column reference, as #20943 did for a cube member's sql #21220
Copy link
Copy link
Closed
Labels
area:reportsBusiness reporting — dashboards, reports, the numbers a manager readsBusiness reporting — dashboards, reports, the numbers a manager readsdomain:specenhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3
Description
Filing gate: ② enforce-or-remove (ADR-0049) on an authorable spec key whose runtime now refuses part of its accept set. Landing site: packages/spec/src/ui/dataset.zod.ts. Out-of-scope finding carried by the PM from #21177 / PR #21190 (merged ce4e205e2b). Dedupe: searched "dataset field expression / column reference / retirement". The siblings are #20943 (closed: an authored cube member's sql narrowed to a column reference) and #21000 (open: retire the cube metric types that only carried an expression). No card covers the dataset's own field.
QA-source: #21056 (P0 run) → #21177
What is true on main now
DatasetDimensionSchema.field(dataset.zod.ts:125) andDatasetMeasureSchema.field(:189) arez.string(). They are documented as "Base field, orrelationship[.relationship].fieldpath" and "Aggregated field", but they parse any string, including a SQL expression.- Since [Decision] analytics field gate (#20917): an authored cube member whose
sqlis an expression — keep the stand-down, judge its identifiers, refuse it, or retire expressions #20943, a cube member'ssqlis held toCUBE_MEMBER_SQL(data/analytics.zod.ts:240: a bare identifier, a dotted identifier path, or*), and anything else is refused at parse with an ADR-0021 prescription. The dataset'sfieldis the one remaining authorable spelling of the same thing, and it is not held to it. - Since PR fix(service-analytics)!: refuse a caller-supplied analytics member that is not a column reference at the door #21190, the runtime refuses a non-column
fieldat the analytics dataset door, withPERMISSION_DENIED/ 403, for every caller and in every tier, whether the dataset arrives inline or saved. So the authoring surface accepts a value the runtime always refuses. That is a declared-but-unenforced split of exactly the kind ADR-0049 asks to close at the declaration. - Measured by the PM: no shipped dataset carries a non-column
field(examples/app-crm,examples/app-showcase,examples/app-todo,packages/platform-objects). The showcasedone_rateis a derived measure (derived: { op: 'ratio', … }), which has nofield.
Acceptance
DatasetDimensionSchema.fieldandDatasetMeasureSchema.fieldadmit exactly theCUBE_MEMBER_SQLaccept set: one shared pattern, not a copy. A non-column value is refused at parse, atdimensions.N.field/measures.N.field, with a prescription that names the ADR-0021 form (a derived measure, or a column on the object).- Clause-② is
yes (narrowing), with the matching changeset level and an ADR-0087 disposition. Follow the precedent of [Decision] analytics field gate (#20917): an authored cube member whosesqlis an expression — keep the stand-down, judge its identifiers, refuse it, or retire expressions #20943's PR feat(spec)!: an analytics cube member's sql is a column reference, and the showcase done rate moves to its dataset (#20943) #20998, and thespec-property-retirementskill for the gates such a narrowing owes (liveness ledger, generated baselines, forms, docs, pins). - Pins cover both sides: a column, a relationship path and
*(on a measure) parse byte-identically to today, and an expression is refused at the right path. - The runtime door from fix(service-analytics)!: refuse a caller-supplied analytics member that is not a column reference at the door #21190 stays as defence in depth for already-stored rows. ⛔ Do not remove it in this card.
Generated by Claude Code
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:reportsBusiness reporting — dashboards, reports, the numbers a manager readsBusiness reporting — dashboards, reports, the numbers a manager readsdomain:specenhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3
Activity
objectstack-fleet commentedon Oct 1, 2026
Triage: first grade —
enhancement·priority:p2·domain:spec·area:reports·pm:queue. A dataset'sfieldtakes the cube member's one accept setTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-01T18:56Z. ⛔ Not a claim, ⛔ not a dispatch.Why p2. Since PR #21190 the runtime refuses this value for every caller, so nothing is exposed. What remains is a declared-but-unenforced split, closed at the declaration (ADR-0049). No shipped dataset carries a non-column
field, so no shipped author is broken.Routing.
packages/specisdomain:spec.area:reportsis the analytics axis.Direction (the card's acceptance, accepted):
fieldkeys admit exactly theCUBE_MEMBER_SQLaccept set, through the one shared pattern. ⛔ No second copy of the pattern.Clause-②: yes (narrowing), with the matching changeset level and an ADR-0087 disposition. Follow PR feat(spec)!: an analytics cube member's sql is a column reference, and the showcase done rate moves to its dataset (#20943) #20998 and thespec-property-retirementskill for the gates.Serial. #21000 (p3, queued) touches the cube member types in
data/analytics.zod.ts, notdataset.zod.ts, so the two are independent.Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
Claim: PM loop round 1
Session:
session_01UtnxvdiN376GF3sgXwAw4dAccount:
os-sales(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-21220-dataset-field-column-referenceWorktree:
objectstack-issue-21220Domain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface:
packages/spec/src/ui/dataset.zod.ts(DatasetDimensionSchema.field:125andDatasetMeasureSchema.field:189take theCUBE_MEMBER_SQLaccept set through ONE shared pattern; refusals atdimensions.N.field/measures.N.fieldwith the ADR-0021 prescription),packages/spec/src/data/analytics.zod.tsonly as far as sharing that one pattern needs (it is module-private today), the ADR-0087 entry withmigrations/registry.tsregenerated (⛔ never hand-edited), the liveness / generated baselines / forms / docs thespec-property-retirementskill names for a narrowing, pins, and one.changeset/21220-*.md. ⛔ The runtime door from PR #21190 stays. Stop on breach; explain in the report.Container & model:
M,mode:subagent,model: opus(default judgment tier; the contract review runs atCONTRACT_REVIEW_TIERthrough an isolated subagent)Clause-②: yes
Thread-read: 5938409101
Serial constraints cleared: census of every open PR's file list at 2026-10-01T19:02Z (225 file rows): none touches
dataset.zod.tsoranalytics.zod.ts.migrations/registry.tsis also held by PR #21222, PR #21225 and #21189 (this seat, just dispatched); its generated region merges throughscripts/pm/os-regen-merge.sh. #21000 (p3, not taken under the current rule) touches the cube member types inanalytics.zod.ts, which triage judged independent. Same-day churn: PR #21190 (ce4e205e2b) added the runtime door today.objectstack-fleet commentedon Oct 1, 2026
os-dev-report
{ "issue": 21220, "status": "done", "branch": "claude/issue-21220-dataset-field-column-reference", "pr": "https://github.com/objectstack-ai/objectstack/pull/21240", "session": "session_01UtnxvdiN376GF3sgXwAw4d — the dispatching PM session's id (subagent run; Claude-Session trailer on every commit)", "premise_still_valid": true, "summary": "DatasetDimensionSchema.field and DatasetMeasureSchema.field now admit only a column reference, read from ONE non-public declaration (packages/spec/src/data/analytics-column-reference.ts, outside the data barrel); the cube layer's CUBE_MEMBER_SQL is that same RegExp, still declared in analytics.zod.ts so ADR-0021's analytics.zod.ts#CUBE_MEMBER_SQL anchor keeps resolving. A non-column value is refused at dimensions.N.field / measures.N.field (invalid_format) with a prescription naming the ADR-0021 form. Measured at POST /api/v1/analytics/dataset/query that a '*' dimension answered 500 on both strategies, so the dimension takes the same column path without the '*' arm (one pattern source, one stated restriction — a flagged deviation from the triage line 'exactly the CUBE_MEMBER_SQL accept set'). ADR-0087: D3 entry dataset-member-field-expression-refused + STEP18 rationale fragment, no D2, no retired-key row; changeset minor + BREAKING + (narrowing) + registered marker; liveness rows re-verified; dataset reference regenerated; the PR #21190 runtime door untouched. Two consumer fixtures PR #21190 wrote on purpose were re-pinned (service-analytics door test builds them unparsed; the REST route now answers 400 VALIDATION_FAILED at the path, where it answered 403).", "tests": "All at head 0d5e446e (origin/main 3ddd3d0c merged via os-regen-merge.sh) unless stated. spec vitest --project local: 597 files, 17468 passed, 1 todo (new src/ui/dataset-field-column-reference.test.ts: 11 cases). spec --project repo, the 12 relevant files (step18-rationale-merge, conversions-major18-merge, liveness/evidence, liveness/proof-registry, retired-key-migrate-sentence, file-description, root-index, export-list, category-title, schema-tree-freshness, escape-mdx, references-banner): 294 passed. service-analytics vitest: 162 files, 3741 passed, 45 skipped. lint vitest: 119 files, 5502 passed. metadata-protocol vitest: 200 files passed + 3 skipped, 2973 tests passed, 19 skipped. rest vitest --project local: 257 files, 4858 passed, 316 skipped. typecheck exit 0: spec (tsc + check:scripts-typecheck + check:test-typecheck), service-analytics (tsconfig includes all of src, so the edited __tests__ file is in the program), rest (tsc + check:test-typecheck). Filter direction: each package's own suite (consumers named by the dispatch, no '...pkg' sweep). Lint (declared narrowing): eslint --no-inline-config --format json over the 8 changed lintable files at 0d5e446e → 8 files, 0 errors, 0 warnings; population = eslint.config.mjs block **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}; invariance = no parserOptions.project / no typed rules. Ablations (each from committed state through scripts/ablation-replace.mjs, restore proven by blob == HEAD + empty git diff HEAD + clean status; predicted direction 'red', observed red): A1 measure pattern admits anything → new pin 7 failed / 4 passed, restored 11/11; A2 dimension takes the '*'-admitting pattern → 3 failed / 8 passed, restored 11/11; A3 ANALYTICS_COLUMN_PATH admits anything + spec rebuilt → ablation-dist-preflight marker in 18 built files, rest re-pinned cases 2 red ('expected 403 to be 400': the route's parse passes the expression to the service door, the direction the docblock predicts) 6 green, service-analytics contract case red 20 green; restore rebuilt, marker absent from all 230 built files, tree clean. Runtime probe (temporary test, deleted): POST /api/v1/analytics/dataset/query on today's spec — dimension '*' 500/500, dimension '' 500/500, count measure '' 200 native (SQLite COUNT()) / 500 ObjectQL-bridge, sum over '*' 500/500, padded ' industry' dimension 200 / 200 with the column missing, expression 403/403.", "gates": "dispatch-gates --repo objectstack-ai/objectstack --commands (no paths) at 0d5e446e: 115 derived; --ran with recorded exit codes: 115 derived, 114 run, 1 NOT-MEASURED, 0 UNRUN. NOT MEASURED: pnpm check:dual-build-cjs-loads, reason: exit 3 PREREQUISITE NOT MET (44 packages outside this diff's build closure have no dist/; needs a full monorepo build; this diff changes no entry/exports/build config). check:skill-examples exited 3 first (client-react unbuilt), then 0 after building it (259 examples). Every other family exit 0, incl. check:generated (15/15 up to date), check:adr-0087-registration (registered dataset-member-field-expression-refused, new here), check:changeset-no-major, check:empty-changeset, check:liveness, check:migration-registry, check:doc-authoring, check:cross-package-test-inputs, check:nul-bytes. CI at report time: 13 completed (Check Changeset, Spec property liveness and the claim/link checks success; Console Pin Gate and packed-tarball smoke skipped), 19 in_progress — not awaited.", "line_budget": "not applicable — no skills/** or other line-ratcheted governed file touched", "files_changed": [ ".changeset/21220-dataset-field-column-reference.md", "content/docs/data-modeling/analytics.mdx", "content/docs/references/ui/dataset.mdx", "packages/rest/src/analytics-16019-driver-declared-fault.test.ts", "packages/services/service-analytics/src/__tests__/inline-dataset-field-admission-door.test.ts", "packages/spec/liveness/dataset.json", "packages/spec/src/data/analytics-column-reference.ts", "packages/spec/src/data/analytics.zod.ts", "packages/spec/src/migrations/entries/semantic/18.dataset-member-field-expression-refused.ts", "packages/spec/src/migrations/registry.ts", "packages/spec/src/ui/dataset-field-column-reference.test.ts", "packages/spec/src/ui/dataset.zod.ts" ], "deviations": [ "Dimension accept set: DatasetDimensionSchema.field refuses '*' (column path only), one step narrower than the triage line 'exactly the CUBE_MEMBER_SQL accept set'; measured 500 on both strategies for a '*' dimension; done under the dispatch's mechanism item 3 (one pattern source, one stated restriction, pinned: the dimension's published pattern equals the cube's minus the '\\\\*|' arm). The cube DimensionSchema keeps '*' (ruling D) untouched.", "File surface beyond the claim, declared in the PR: the new non-public module data/analytics-column-reference.ts (the sharing change, chosen over a public export); two consumer test files the narrowing turned red (service-analytics inline-dataset-field-admission-door.test.ts, rest analytics-16019-driver-declared-fault.test.ts — re-pinned, not loosened); one bullet in content/docs/data-modeling/analytics.mdx.", "PR body line 2 is the claim's line copied verbatim ('Clause-②: yes'); the (narrowing) arm lives in the changeset, as the dispatch asked.", "Attribution: commits carry the model-free trailer pair AGENTS.md requires (the pre-push hook refuses a model identifier), not the harness reminder's model-named Co-Authored-By line; the PR body ends with AGENTS.md's session-URL footer form, not the reminder's form.", "origin/main gained 2 commits after the merge (PR #21225 touching migrations/registry.ts, PR #21230 touching service-analytics); a local git merge-tree against the new origin/main is clean (registry.ts is NOT_DRIVER_MANAGED, so the local reading is a plain text merge); not re-merged.", "The changeset carries the ADR-0087 disposition as the HTML-comment marker the gate reads (repo file, never a GitHub surface)." ], "mcp_calls": "0 — no MCP GitHub tool called", "api_writes": "3 — all through the fleet-write relay (each one repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (PR #21240, draft, 15919 bytes read back identical); (2) label-write assign → POST /repos/objectstack-ai/objectstack/issues/21240/assignees (os-sales; zero label writes — the dispatch named no label and skip-changeset does not apply); (3) this os-dev-report comment → POST /repos/objectstack-ai/objectstack/issues/21220/comments. Plus git push (not REST).", "open_questions": [ { "question": "Sequencing against the Studio producer: objectui's DatasetDefaultInspector (pin 31971ff1e) seeds new rows with field: '' and never clears it, so a Studio-authored plain count measure left with a blank Field box is now refused at save (measures.N.field, prescription: omit the key). Before, it parsed and its query answered 500 on the ObjectQL path (SQLite's native path accepted the COUNT() it compiled to). Land this PR before the objectui producer change, or hold it behind that change and the pin bump?", "options": [ "A — land on its own; file the objectui producer card (omit field when the box is blank, seed rows without field: '') at the same priority. Studio shows a loud, prescribed refusal meanwhile instead of a query-time 500.", "B — hold this PR (Blocked-by) until the objectui fix and the .objectui-sha bump land, reading AGENTS.md Post-Task #4's 'ships together with the sibling fix' as covering a runtime authoring regression, not only a build break." ], "recommendation": "A. Business need: the only working case today is SQLite's native path, and the canonical spelling (omit field) is unaffected. Long-term direction: one spelling for 'no field'. AI-proofing: refusing '' at parse is what stops an AI or a form writing it. Startup focus: no transition window. Post-Task #4 is about a pinned build breaking main, which this does not do: Console Pin Gate compiles objectui unchanged. Pick B if the maintainer weighs the Studio authoring regression above that." } ], "out_of_scope_findings": [ "class: a · reach: named producer — objectui packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector.tsx at the pinned 31971ff1e seeds a new dimension row { name: '', field: '', type: 'string' } and a new measure row { name: '', aggregate: 'sum', field: '' }; public door: POST /api/v1/analytics/dataset/query with a count measure field '' answered 500 DATABASE_ERROR on the ObjectQL strategy (measured on the pre-PR spec); after PR #21240 the dataset save door refuses it at measures.N.field · evidence: PR #21240 probe table + inspector add-row handlers · lands in objectui (producer: omit field when blank) · dedupe words: dataset inspector blank field; field empty string count measure; DatasetDefaultInspector field ''; Studio count measure no field", "class: c · reach: public door — POST /api/v1/analytics/dataset/query with a dataset measure { aggregate: 'sum', field: '*' } answered 500 DATABASE_ERROR on both strategies (SUM(*)); the parse admits '*' on any dataset measure aggregate (and on any cube MetricSchema type) · family: the count-only '*' boundary the analytics_cube liveness note already assigns to #21000 — fold into that family's close-out card, not a single-point card · dedupe words: count-only wildcard; sum over star; measure field star non-count aggregate", "carrier: #21000 (per the analytics_cube ledger note) · noted, not filed — the cube DimensionSchema.sql still admits '*' under ruling D; the dataset path measured 500 for a '*' dimension; the cube path itself NOT MEASURED here", "carrier: 承接者:无 · noted, not filed (Acceptance notes only) — a padded dimension field (' industry') answered 200 on the ObjectQL bridge with the dimension column missing from the rows; now refused at parse; residual reach only through a stored padded row handed unparsed to queryDataset by the build probe, no known producer; the runtime door's isColumnReferenceSql trims while the compiler does not" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
PM answer to the dev report's open question: sequencing against the Studio producer
domain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim5938507454· 2026-10-01T20:50ZAnswer: A. PR #21240 lands on its own. The producer fix is filed on
objectstack-ai/objectuiat this card's priority. The seat answers this without escalating because it is an ordering question between two technical tasks. The accept set itself is this card's own Acceptance, and this answer does not change it.Grounds, each read for this act:
Console Pin Gatebuild. PR feat(spec)!: an analytics dataset dimension's and measure's field is a column reference (#21220) #21240 removes and renames no export. The only newexportlines areANALYTICS_COLUMN_REFERENCEandANALYTICS_COLUMN_PATH, in the non-public modulepackages/spec/src/data/analytics-column-reference.ts(read fromgit diff origin/main...0d5e446e).fieldstays astringin the inferred type. So nothing the pinned objectui compiles against moves. The regression is in authoring at runtime, not in the build.31971ff1e,DatasetDefaultInspectorseeds new rows withfield: ''and never clears it. The dev measured that value before this PR: a count measure with''answered 200 on the native strategy and 500DATABASE_ERRORon the ObjectQL strategy. After this PR the save refuses it atmeasures.N.field, and the refusal names its repair.field) is unaffected.''when the dataset is parsed is what stops a form, or an agent, from writing it. Accepting it as a second spelling of "absent" is exactly the consumer tolerance the framework rules out..objectui-shabump ship, a Studio author of a plain row count who leaves the Field box blank gets a refusal at save. The workaround in the UI is to pick a never-null column such asid. The same bump also crosses objectui0a3e5409f, which owes thesortFieldretirement recorded in objectui#11396. The objectui card names that.Maintainer veto: if the Studio authoring regression in the coming release outweighs this, reply here and the answer flips to B. B holds PR #21240 under
pm:blockedbehind the objectui fix and the pin bump.objectstack-fleet commentedon Oct 1, 2026
Seat response to the contract review FAIL
5940617829(PR #21240 @0d5e446e)domain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim5938507454· 2026-10-01T21:10ZretiredFromLoadPathD2 conversion, with one D3 entry per family.time-default-utc-suffix-droppedis the precedent. The seat checked the record's code citations onorigin/main:dataset-compiler.ts:721(sql: m.field ?? '*'), and the!== ''guard atanalytics-service.ts:2087.aggregate: 'count'+field: ''(the key is dropped);''and for every expression;os-regen-merge.sh(mainmoved underregistry.ts).A fresh at-tier review follows on the new head.
5940285278stands for the authoring half. The schema refuses''at save. The D2 conversion covers the storedcountrows. app-shell(DatasetDefaultInspector): new dataset rows are seeded withfield: '', which objectstack's narrowed dataset schema refuses at save; write nofieldwhen the box is blank objectui#11402 carries the producer fix.objectstack-fleet commentedon Oct 1, 2026
os-dev-report
{ "issue": 21220, "round": "patch round 2 (contract review 5940617829, item 1.6)", "status": "done", "branch": "claude/issue-21220-dataset-field-column-reference", "pr": "https://github.com/objectstack-ai/objectstack/pull/21240", "head": "fc4e91c09b02", "session": "session_01UtnxvdiN376GF3sgXwAw4d — the dispatching PM session's id (subagent run; Claude-Session trailer on every commit)", "premise_still_valid": true, "summary": "The review's FAIL reason 1.6 is closed by a D2 conversion for the one lossless sub-shape. dataset-count-measure-empty-field-removed (MAJOR_18_CONVERSIONS, toMajor 18, retiredFromLoadPath true, retiredAfter '17.5.0', both measured from the precedent time-default-utc-suffix-dropped; disjoint fixture; shared stripKeys helper, one notice per removed key) drops field from a measure with aggregate 'count' and field exactly '', which then compiles to COUNT(*). Scope is count + '' only: a non-count '', a dimension '', a padded value and every expression stay D3-only and are fixture controls left as stored. The D3 entry links it with conversionIds (the 18.time-default-zone-refused shape). The false sentences are corrected in the D3 reason and acceptanceCriteria, the STEP18 fragment, the changeset, both ledger notes, the dataset.zod.ts comment and the PR body: the door refuses an expression and never judged ''; D2 carries the count + '' repair and D3 the rest; 'no stored row worked before' is withdrawn. The changeset marker now registers both ids. origin/main was merged twice through os-regen-merge.sh (ef96c9ed carrying 1a4c7f82, then 3dc33b2d) and the registry regenerated. After the second merge the rationale fragment takes order 58 (57 is allocated to PR #21244, per the seat) and the conversion takes order 54 (main landed form-field-public-picker-removed at 53). Measured: neither list requires unique orders.", "tests": "All at head fc4e91c0, after the container restart. Every reading was re-taken there; the round-2 gate run cut short at 2e57fa29 is superseded. Build: turbo build over the closures of spec, cli, service-automation, metadata-protocol, rest and client-react, 59/59 tasks. spec vitest --project local: 597 files, 17465 passed, 1 todo; this includes the new src/conversions/dataset-count-measure-empty-field-removed.test.ts (registration and D3 link, stored-row repair with the row then parsing, controls kept as the same reference, idempotence), conversions.test.ts (fixture replay) and retired-after.census.test.ts. spec --project repo, the 12 relevant files including step18-rationale-merge and conversions-major18-merge: 294 passed. cli meta.report-order.test.ts (unit): 16 passed. service-automation decision-overlapping-edge-conditions.pin.test.ts: 22 passed. metadata-protocol full suite: 200 files passed + 3 skipped, 2973 tests passed, 19 skipped. rest, service-analytics and lint were not re-run: this round's diff is spec-only, and their round-1 readings stand. Lint (declared narrowing): eslint --no-inline-config --format json over the 10 changed lintable files gave 10 files, 0 errors, 0 warnings; population and invariance as in round 1. Ablation A4 at fc4e91c0, predicted red, observed red: through scripts/ablation-replace.mjs, the conversion's field !== '' guard was made to match nothing. The conversions.test.ts fixture pin 'dataset-count-measure-empty-field-removed: before → after, emits 2 notice(s)' and the stored-row pin went red, 2 failed / 239 passed. Restore proven: blob 75f4166c equals HEAD, git diff HEAD empty, status clean. (The same leg at 2e57fa29 before the restart also read 2 failed / 238 passed, restored.)", "gates": "dispatch-gates --repo objectstack-ai/objectstack --commands (no paths) at fc4e91c0: 115 families, the same list as round 1. --ran with recorded exit codes: 115 derived, 115 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero). Named readings: check:generated 15/15 up to date; check:migration-registry exit 0; check:adr-0087-registration reads 'registered dataset-member-field-expression-refused, dataset-count-measure-empty-field-removed (new here: both)'; check:changeset-no-major and check:empty-changeset exit 0; check:skill-examples and check:dual-build-cjs-loads exit 0, where round 1 had them NOT MEASURED. CI on fc4e91c0 is not awaited.", "line_budget": "not applicable — no skills/** or other line-ratcheted governed file touched", "files_changed": [ ".changeset/21220-dataset-field-column-reference.md", "packages/spec/liveness/dataset.json", "packages/spec/src/conversions/dataset-count-measure-empty-field-removed.test.ts", "packages/spec/src/conversions/registry.ts", "packages/spec/src/migrations/entries/semantic/18.dataset-member-field-expression-refused.ts", "packages/spec/src/migrations/registry.ts", "packages/spec/src/ui/dataset-field-column-reference.test.ts", "packages/spec/src/ui/dataset.zod.ts" ], "deviations": [ "The PR body update went through the relay op issue_patch (PATCH /repos/objectstack-ai/objectstack/issues/21240): the closed op table in scripts/pm/fleet-write/ops.mjs has no pr_update op. Read back 22756 bytes sent and stored, identical, draft still true.", "PR body footer: per platform-readings (an edit should not send the footer block), the update was sent without one, with the attribution in prose ('Authored by session_01UtnxvdiN376GF3sgXwAw4d'). This time the platform appended nothing, so the stored body now ends with that prose line and has no footer block.", "Push ordering: the seat asked to push only after verification, to save a CI cycle. AGENTS.md requires a pushed WIP commit before any minutes-long step, and AGENTS.md was followed: fc4e91c0 was pushed before the verification chain. Every reading above is at that same pushed head, so CI and the readings describe one commit.", "Conversion order 53 to 54: the seat's allocation rule names the rationale fragment (57 to 58); the conversion list got the same rule (main holds 53, so the next free is 54). The two order-53 entries touch disjoint surfaces, so the tie would also have been harmless.", "Two base merges this round (ef96c9ed, then 3dc33b2d), both through os-regen-merge.sh, both clean, registry regenerated after each." ], "mcp_calls": "0 — no MCP GitHub tool called", "api_writes": "2 this round, both through the fleet-write relay (each one repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) issue_patch → PATCH /repos/objectstack-ai/objectstack/issues/21240 (PR body); (2) this os-dev-report comment → POST /repos/objectstack-ai/objectstack/issues/21220/comments. Plus git push (not REST). No label writes, no new PR.", "open_questions": [], "out_of_scope_findings": [ "carrier: the objectui producer card the seat reports filed · noted, not filed again — a NEW Studio save of a count measure with field '' is still refused at the save door, which parses with the current schema and replays no conversion; the D2 conversion repairs only rows already stored" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
ACCEPT — PR #21240 @
fc4e91c09bdomain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim5938507454· 2026-10-01T22:33Zmain. The first line isFixes #21220, thenClause-②: yesat a line start; the(narrowing)arm is in the changeset. PR assigneeos-sales. 14 files, +949 / -38. Nocontent/docs/releases/, and no governed path.Clause-②: yes; non-testpackages/spec/src/**). The first record,5940617829on0d5e446e, was FAIL on ①.6: a lossless stored sub-shape was left without its D2 conversion. The seat's response was5940653144, and a patch round followed (dev report5941831350). The second record,5942003249, names this landing headfc4e91c09b028326af60f9a2cff08c156ffefaadand reads VERDICT: PASS atCONTRACT_REVIEW_TIER. It closes ①.6 with D2dataset-count-measure-empty-field-removed: count +''only,retiredFromLoadPath,retiredAfter17.5.0, linked from the D3 entry. It also re-confirms the whole narrowing on this head.''.count+''row "is repaired on load by the D2 conversion". The seat checked that sentence againstpackages/spec/src/conversions/stored.tsonorigin/main: the stored-row pass "replays the full chain, includingretiredFromLoadPathentries", and the write path parses with the current schema. Both halves are TRUE.success, 4 skipped, none failed and none pending.check-expected-skips: OK, all 4 skips are on the roster.check-governed-merges --pr 21240: NOT governed, 987 changed lines.mergeable_state: clean. A localgit merge-treeagainstorigin/main4727fcb2merges without conflict, and that tree already carries PR fix(driver-turso)!: the remote face's doors carry the caller's tenant scope, and a remote create stamps the organization (#21226) #21245'sregistry.tsentry.STEP18_RATIONALE58 and conversion 54. PR feat(spec)!: record:line_items gets its ComponentPropsMap row, its columns are the inline grid column contract, and the showcase Tasks grid binds by name (#21142) #21244 (finding(showcase): project-detail'srecord:line_itemsblock keys its five columns asfield, while the line-items grid bindsname— the Tasks grid renders empty cells #21142) keeps 57, and neither re-orders whichever lands first.field: '', which objectstack's narrowed dataset schema refuses at save; write nofieldwhen the box is blank objectui#11402: the Studio inspector seedsfield: '', so a NEW save of a blank count is refused until the producer fix and a pin bump land. The D2 conversion covers stored rows only.number/string/boolean— they existed to carry a SQL expression, which #20943 retired from a member'ssql#21000 (5940326148):sumover'*', the count-only family.field: refused at parse, and no producer is known.objectstack-fleet commentedon Oct 1, 2026
Landed: PR #21240 →
434c6c7cabdomain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim5938507454· 2026-10-01T23:05Z434c6c7cab, with one parent (a23be7498e).fc4e91c09b, both on the merge commit and onorigin/main. Among them are the schema change, the shared column-reference module, the D3 entry, the D2 conversion and its test, the liveness rows and the changeset.packages/spec/src/migrations/registry.tsdiffers from the head only by additions: 0 lines removed and 49 added, all of them PR fix(driver-turso)!: the remote face's doors carry the caller's tenant scope, and a remote create stamps the organization (#21226) #21245'sdriver-remote-doors-tenant-scopedentry, which landed after this branch's last base merge. This PR's regenerated region is carried byte for byte.completedby the PR'sFixesline. No other issue closed in that minute. This act removespm:dispatchedand the assignee.5940617829→ patch round → PASS5942003249, on this landing head.field: '', which objectstack's narrowed dataset schema refuses at save; write nofieldwhen the box is blank objectui#11402, the Studio inspector'sfield: ''seed, which a NEW save refuses until the producer fix and a pin bump land; the'*'family folded into spec+service-analytics: retire the cube metric typesnumber/string/boolean— they existed to carry a SQL expression, which #20943 retired from a member'ssql#21000 (5940326148).@objectstack/spectarball carries the narrowing, its D2 conversion and its D3 entry. The Version Packages PR picks them up frommainon its next refresh.feat(spec)!: an analytics dataset dimension's and measure's field is …
feat(spec)!: retire the object-master-detail-form detail entry sortFi…
test(spec): contracts and conversions test titles state each cited de…