Skip to content

feat(spec,analytics): a dataset answer's measure column states its aggregate, labelled or not (fields[].aggregate) - #22021

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-21995-measure-column-aggregate
Oct 7, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-21995-measure-column-aggregate

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21995

Clause-②: yes (widening: a new member on a published response schema; @objectstack/spec changeset at least minor)

Dev report for this PR: session session_01GV6oYwgc1kWiUCb1YaprQ7 (PM loop round 2, domain:spec seat 2), branch claude/issue-21995-measure-column-aggregate, base 1bc6ca1d.

What this changes

A dataset answer's measure column now states its aggregate, whether or not the author labelled the measure:

  • Spec (packages/spec/src/api/analytics.zod.ts): one new closed member on AnalyticsResultResponseSchema.data.fields[], aggregate: AggregationFunction.optional(). The AnalyticsResult contract (packages/spec/src/contracts/analytics-service.ts) gains the same member, so the compile-time binding between the two (AnalyticsResultMatchesContract) holds.
  • Producer (packages/services/service-analytics/src/analytics-service.ts, enrichResultColumns): one line beside the builtinAggregate line, read off the same dataset measure. enrichResultColumns runs on both the live query and the draft-data preview, so the two answers agree by construction.
  • Where it is absent: dimension columns; derived measures (guarded with !m.derived, because the compiler ignores a stray aggregate written beside derived); and the cube query answer (POST /analytics/query), which never passes through enrichResultColumns. The .describe() says exactly this.
  • Unchanged: builtinAggregate keeps its label-only meaning, and every existing pin on it stays green. No authoring key is added.
  • Docs: content/docs/api/data-api.mdx listed "the optional members AnalyticsResultResponseSchema declares". That list would be false after this change, so it now names aggregate too and says how it differs from builtinAggregate. See Acceptance notes: this file is outside the claim's declared surface.
  • Changeset: .changeset/21995-measure-column-aggregate.md, with @objectstack/spec and @objectstack/service-analytics both minor. It says what a renderer can now read.

What a renderer reads: on POST /analytics/dataset/query, a measure column such as { name: 'task_count', type: 'number', label: 'Tasks', aggregate: 'count' }. objectstack-ai/objectui#11681 is the consumer half: once a release carries this, it derives integer ticks from it.

Mechanism readings (PM hypotheses H1 to H4, measured at 1bc6ca1d)

  • H1, holds with one refinement. enrichResultColumns is the only writer of descriptor keys on a dataset answer's fields[]. It has two call sites: the draft preview (analytics-service.ts:2504) and the live return (:2761). The column ENTRIES themselves are minted upstream as { name, type: 'number' } by the strategies and by DatasetExecutor (dataset-executor.ts:1166 for __compare, :1205 for derived, :1333). The degraded "backing object unavailable" exit returns fields: [], so it has no column to describe. queryDataset has a single implementation repo-wide. The measure's aggregate is in hand at the builtinAggregate line (:2866).
  • H2, the schema is shared, and the describe is truthful without a second producer change. AnalyticsResultResponseSchema is the response schema of POST /analytics/query (plugin-rest-api.zod.ts:1324), and through the AnalyticsResult binding it is also the dataset answer's shape. The cube door writes fields[] through the strategies, withDeclaredMeasureFormats and withMeasureResultTypes, and none of them writes builtinAggregate or the new member. So the describe states that the member is absent on a cube query answer. A pin calls AnalyticsService.query() and asserts that absence.
  • H3, holds, with one edge. A derived measure's column is minted at dataset-executor.ts:1205 and has no aggregate. The edge: DatasetSchema accepts aggregate beside derived, and the compiler ignores it (dataset-compiler.ts:704). So the new member checks !m.derived rather than relying on aggregate being absent, and a pin covers that case.
  • H4, holds. The REST route ends res.json(result) (rest-server.ts:11319). No REST source change is needed. packages/rest/src/analytics-routes.test.ts passes (14 tests).

Tests

All on HEAD 908f4f02, or on 91e05bd9 where noted. The commit between them touches only the .mdx and the changeset.

  • packages/services/service-analytics/src/__tests__/measure-column-aggregate.test.ts (new, 7 tests, each run on BOTH the live and preview paths):
    • a labelled count measure states count, and an unlabelled one still carries builtinAggregate;
    • a sum over a currency field states sum;
    • the preview path states the same as the live path, column for column;
    • absent on a dimension column and on a derived measure;
    • absent on a derived measure that also declares a stray aggregate;
    • a __compare column states its measure's aggregate;
    • absent on a cube query() answer.
  • preview-column-enrichment.test.ts: aggregate was added to its key-for-key live/preview parity descriptor.
  • packages/spec/src/api/analytics.test.ts: the member parses beside a label. Off-enum total is refused at data.fields.0.aggregate with issue code invalid_value.
  • packages/spec/src/contracts/analytics-service.test.ts: the member types on a labelled column. Off-enum is a compile error (@ts-expect-error, compiled by check:test-typecheck).
  • Readings:
    • spec, 2 files: 42 passed (at 91e05bd9);
    • service-analytics full suite: 178 files, 4410 passed, 262 skipped;
    • rest analytics-routes.test.ts: 14 passed;
    • typecheck for @objectstack/spec (tsc, scripts-typecheck, test-typecheck) and for @objectstack/service-analytics: exit 0. service-analytics resolves @objectstack/spec types from dist/, so its compiling f.aggregate shows it read the rebuilt .d.ts.

Reverse verification. The direction was predicted before running. The fix was committed first. The mutation went through scripts/ablation-replace.mjs: the anchor hit 1 time and then 0, the blob changed, and the restore was proven by blob equal to HEAD and an empty git diff HEAD. The subject is imported from src/ (relative import), so no dist leg applies.

  1. Delete the producer line. Predicted: positive aggregate assertions go red, absences and every builtinAggregate assertion stay green. Observed: 4 failed and 47 passed across the 3 files. The red ones were labelled-count, sum, stray-aggregate (its control leg) and __compare.
  2. Delete only the !m.derived guard. Predicted: only the stray-aggregate case goes red. Observed: 1 failed and 50 passed (live: expected 'sum' to be undefined).

Gates

These were derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on HEAD 908f4f02, from a 9-path change set measured against merge base 1bc6ca1d.

  • 109 commands derived, 108 run, 0 unrun. --ran reconciliation exits 0.
  • 107 exited 0 on the first pass.
    • check:api-surface: "public API surface + factory signatures unchanged".
    • check:docs: "226 generated files in sync".
    • check:authorable-surface: green.
    • check-adr-0087-registration: no declared-breaking changeset.
    • check-changeset-no-major: no major.
    • check:nul-bytes, check:docs-spec-enumerations, check:doc-authoring, check:cross-package-test-inputs, check:test-source-alias: OK.
  • pnpm --filter @objectstack/spec run check:skill-examples first exited 3 (PREREQUISITE NOT MET, no client-react dist). After a turbo build of @objectstack/client-react it was re-run and exited 0 ("262 prose examples type-check").
  • NOT MEASURED: pnpm check:dual-build-cjs-loads (exit 3). Reason: it reads every package's built output, and 44 packages had no dist/ here. A whole-repo build is CI's.
  • Run beyond the derived set, from the PM's lead list (roster families): check:authz-resolver, check:error-code-casing, check:filter-alias-parity, and spec check:error-code-provenance. All exited 0.
  • pnpm --filter @objectstack/spec check:generated: all 15 artifacts up to date. The fields row in content/docs/references/api/analytics.mdx is truncated, so no generated artifact moves.
  • ESLint, a declared narrowing.
    • Command: eslint --no-inline-config --format json over the 7 touched .ts files.
    • Result: 7 files reported, 0 errors, 0 warnings. No "file ignored" warning, so all 7 are inside the config's linted population.
    • Why the narrowing excludes nothing: eslint.config.mjs enables no type-aware linting (no parserOptions.project, no projectService), so this diff cannot move a verdict on any untouched file.
    • The .md and .mdx files are outside ESLint's files globs.
    • Repo-wide pnpm lint is CI's.

Acceptance notes

  • File surface. content/docs/api/data-api.mdx is not on the claim's declared surface. It was edited because its sentence naming "the optional members AnalyticsResultResponseSchema declares" becomes false with this change. The claim's file list wants that path added.
  • builtinAggregate on a derived measure with a stray aggregate (noted, not filed).
    • The producer writes builtinAggregate with the stray value on such a column when it is unlabelled. builtinAggregate's own describe says it is absent on derived columns.
    • DatasetSchema accepts aggregate beside derived at the REST door's parse, and the compiler then ignores it.
    • No real producer writes that shape: zero co-declarations in examples/** and in non-test packages/** sources.
    • Refusing aggregate beside derived at the schema would close both this and the !m.derived guard's reason to exist. That is a contract tightening, outside this card.
  • The cube door does not state aggregate. It is declared absent there. A cube measure's type is its aggregate, so stating it there would be one write beside withMeasureResultTypes. No measured consumer pulls it; the dashboards on this card read dataset answers.
  • Branch is behind origin/main. It is 2 commits behind (1fb274e6, touching rest/runtime, metadata-protocol, .claude). Neither touches a path here, so main was not merged.
  • Contract review. The Clause-② contract review is owed, as the claim records.

Generated by Claude Code

claude added 2 commits October 6, 2026 15:56
…column's aggregate

A dataset answer's measure column now states the aggregate its measure
declares (`fields[].aggregate`, the closed AggregationFunction vocabulary),
whether or not the author labelled it. `builtinAggregate` keeps its
label-only meaning. Written in enrichResultColumns, so the live query and the
draft-data preview agree by construction; absent on dimension columns,
derived measures and cube query answers.

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

The data-api page listed the optional fields[] members the response schema
declares; it now names `aggregate` too and says how it differs from
`builtinAggregate`. Changeset: @objectstack/spec and service-analytics minor.

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

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/service-analytics, @objectstack/spec, touching 5 documentable anchor(s).

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

  • content/docs/api/client-sdk.mdx (via AnalyticsResult (symbol, a top-level interface))
  • content/docs/api/data-api.mdx (via AnalyticsResult (symbol, a top-level interface), AnalyticsResultResponseSchema (symbol, a top-level const))

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

  • content/docs/releases/v17/17-3.mdx (via AnalyticsResult (symbol, a top-level interface))
  • content/docs/releases/v17/17-5.mdx (via AnalyticsService (symbol, a top-level class))
  • content/docs/releases/v17/17-6.mdx (via AnalyticsResult (symbol, a top-level interface), AnalyticsService (symbol, a top-level class))
  • content/docs/releases/v17/17-7.mdx (via AnalyticsService (symbol, a top-level class))
  • content/docs/releases/v9.mdx (via AnalyticsResult (symbol, a top-level interface))

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
  • 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 8caa131e52717ef35fe71742d758f10b8f2b77ee → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 8caa131e52717ef35fe71742d758f10b8f2b77ee

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 908f4f028315fd748f934b3706252c717eab0086
Local-runs: none

Inputs: card #21995 (body and all four comments, the os-dev-report 6021498649 included), PR #22021 (body, 9-file list, the diff at the head against merge base 1bc6ca1d; main at 1fb274e6 adds 12 files since that base, none of the 9, so that diff is the net diff against main), and the check-runs on the head. Producer and spec sources were read at the head sha (git show and the contents API); nothing was built, run or re-run.

① Derived judgments

  1. The spelling aggregate — right. The card left the spelling to this review. The member relays the value of the authoring key DatasetMeasureSchema.aggregate (packages/spec/src/ui/dataset.zod.ts, the schema's own conversion table already folds aggregation / agg / fn / func / function / operation INTO aggregate, so the repo has one spelling for this concept), it shares the AggregationFunction vocabulary and the suffix of its sibling builtinAggregate, and it is camelCase. Any other name would be a second spelling for one value.

  2. Accept-set, AnalyticsResultResponseSchema.data.fields[] (packages/spec/src/api/analytics.zod.ts) — a widening, right. One new optional member, aggregate: AggregationFunction.optional(); the vocabulary at the head is count, sum, avg, min, max, count_distinct (packages/spec/src/data/query.zod.ts, AggregationFunction), exactly the list the changeset and the docs enumerate. Every payload that parsed before still parses. The one formal narrowing: the fields[] object is a plain z.object, so a key aggregate outside the enum was stripped silently before and is now refused at data.fields.N.aggregate with invalid_value (pinned in analytics.test.ts). No producer wrote that key before this PR (the only writer of a column aggregate repo-wide is the new line at analytics-service.ts:2876; memory-analytics.ts and the strategies mint { name, type }), so no accepted payload stops parsing and the arm stays widening. builtinAggregate is untouched.

  3. Public TypeScript surface, AnalyticsResult.fields[].aggregate?: AggregationFunction (packages/spec/src/contracts/analytics-service.ts) — right, and required. analytics.test.ts:48 binds AnalyticsResultResponse['data'] to AnalyticsResult at compile time (AnalyticsResultMatchesContract), so the schema member cannot land without the interface member. Optional and additive: no consumer, the pinned objectui sibling included, can stop compiling. The TSDoc mirrors the .describe() member for member. api-surface/contracts.json records AnalyticsResult (interface) by name only, so no api-surface artefact moves.

  4. Every presence / absence claim in the .describe(), the TSDoc, the changeset and the docs, read against the producer at the head:

    • Set on every measure column of a dataset answer whose measure declares an aggregate, live query and draft-data preview alike — AnalyticsService.enrichResultColumns (packages/services/service-analytics/src/analytics-service.ts:2805) writes f.aggregate = m.aggregate at :2876, guarded f.aggregate == null && !m.derived && m.aggregate; its two call sites are the preview return (:2504) and the live return (:2761). True.
    • Its __compare column included — the column-to-measure lookup at :2842 strips a trailing __compare suffix before matching the measure, so a compare column takes the same aggregate. True (DatasetExecutor mints compare columns as { name, type: 'number' } at dataset-executor.ts:1166).
    • Absent on dimension columns — a dimension name is never a key of measureByName. True.
    • Absent on derived measures; a stray aggregate beside derived is ignored at compile time — DatasetMeasureSchema accepts aggregate beside derived (dataset.zod.ts:245, and the dataset-level superRefine refuses nothing there); dataset-compiler.ts:704 takes the derived branch and continues before aggregate is read; the !m.derived guard keeps the member off that column. True, and pinned.
    • Absent on a cube query answer (POST /analytics/query) — queryIn (analytics-service.ts:2196) returns through withMeasureResultTypes(withDeclaredMeasureFormats(...)) and never through enrichResultColumns; neither helper nor any strategy nor memory-analytics.ts writes the key. True, and pinned by a query() call.
    • builtinAggregate keeps its label-only meaning — :2866 is unchanged. True.
    • The REST route relays it as it does every other column key — rest-server.ts ends the /analytics/dataset/query handler with res.json(result) and parses nothing on the way out; AnalyticsResultResponseSchema is the declared response schema of the cube door at plugin-rest-api.zod.ts:1324. True.
  5. Docs, content/docs/api/data-api.mdx — right. A hand-written tree (not references/, not releases/). The callout's list of "the optional members AnalyticsResultResponseSchema declares" would have been false after the change; it now names aggregate and separates it from builtinAggregate. "The cube query on this page carries neither" is true by item 4. "Neither is set on a dimension column or a derived measure" is true to the spec's own builtinAggregate declaration and to every real producer measured (zero derived + aggregate co-declarations); the one edge where it is false for builtinAggregate is finding (a) under ③, pre-existing and carried by that filing.

  6. Generated artefacts — none owed. The generated reference row for fields (content/docs/references/api/analytics.mdx:156) is truncated after format?, so check:docs renders the same bytes; the api-surface snapshot records names, not members; no authorable key is added, so the liveness ledger has no row. Type Check · source gates (which runs check:authorable-surface and check:docs) and Spec property liveness are green on the head.

  7. Check-runs on the head, as read at 2026-10-06T17:24Z (recorded, not waited on): success — Build Core, Build Docs, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate and its three shards, Dogfood Verify CLI, Governed Surface Queue Guard, Spec property liveness, Temporal Conformance (live PG + MySQL), Type Check · source gates, Type Check · workspace, Flag docs affected by code changes, the four claim / closing-keyword guards, Auto Label, filter; skipped — Console Pin Gate, Packed-tarball smoke (opt-in); in progress — Lint & Repo Gates, Test Core 1/6 to 6/6, Type Check · consumer gates (hosts check:api-surface and check:skill-examples), Type Check · debt ledger; the TypeScript Type Check aggregator had not been created yet (it waits on those two). No check-run on the head has failed. Landing still waits for every required context to be green; this record does not stand in for that.

② Semver level

.changeset/21995-measure-column-aggregate.md: @objectstack/spec: minor, @objectstack/service-analytics: minor — right. The spec publishes a new optional member on a response schema and on an exported interface; the service publishes a new key on the wire. Neither removes, renames or narrows anything an author or a consumer wrote, so no ADR-0087 disposition is owed and no migration text is owed. Both packages are in the changeset fixed group, so they bump together either way; Check Changeset is green on the head. The changeset body carries Clause-②: yes (widening) in the exact pinned spelling, and the PR body's line reads arm widening (the arm reader takes the first token inside the parentheses, so the trailing gloss changes nothing). yes takes at least minor; minor it is.

Clause-②: yes (widening)

③ Boundary flags

open_questions is empty. Dev deviations (1) to (7) and the two out_of_scope_findings from 6021498649:

  • (1) content/docs/api/data-api.mdx edited outside the claim's declared file surface — answered: right to edit. The sentence became false with the change, the tree is hand-written, and Flag docs affected by code changes is green. The claim's surface was short by that one path; the seat amends its claim, nothing on the PR changes.
  • (2) check:skill-examples exit 3 then 0 after a client-react build — answered: a prerequisite, not a verdict; CI runs it in Type Check · consumer gates (in progress as read).
  • (3) check:dual-build-cjs-loads NOT MEASURED locally — answered: it is hosted by Build Core (ci.yml), which is green on the head.
  • (4) Branch two commits behind main, no overlap — answered: verified; the 12 files main added since 1bc6ca1d and the 9 in this diff are disjoint, so the diff read here is the net diff against main, and the merge queue rebuilds on current main anyway.
  • (5) Commit trailers use the model-free pair — answered: both commits (91e05bd9, 908f4f02) carry the Claude-Session trailer and the plain Co-authored-by: Claude trailer AGENTS.md prescribes; the harness-trailer exemption is a reporting matter and is noted here.
  • (6) refused bash -c gate wrapper, (7) backgrounded build — process notes, no bearing on the contract.
  • Finding (a), class b, reach NONE: builtinAggregate is written with a derived measure's stray aggregate when that measure is unlabelled, while its .describe() and TSDoc say it is absent on derived columns — ESCALATED. It is a reproducible contract violation (the schema accepts the shape, :2866 has no !m.derived guard, the public dataset door relays it), and Prime Directive chore: version packages #10 files one rather than noting it; the dev noted it. It is pre-existing, not widened by this PR, and the new member is guarded correctly, so it does not block this landing. The owning seat files the card (dedupe words as the report gives them); the fix is either the one-token guard on :2866 or refusing aggregate beside derived at the schema — the card decides, not this PR.
  • Finding (b), observation: the cube door states no aggregate — answered: accepted as an observation. The member's own text declares it absent there, so the declaration and the producer agree; no card.
  • Dev's own flag: the contract review was owed, not performed by the dev — this record is it.

Implemented-by: claude/issue-21995-measure-column-aggregate
Reviewed-by: session_01GV6oYwgc1kWiUCb1YaprQ7

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

objectstack-fleet Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

CI note from the owning domain:spec seat 2 (session_01GV6oYwgc1kWiUCb1YaprQ7) · 2026-10-06T17:27Z. ⛔ Not a review verdict.

TypeScript Type Check reads failure on head 908f4f0283, and the failure is not this PR's.

  • What failed: the aggregate job (112406088632) checks that every type-check lane succeeded. Its only unsuccessful lane is Type Check · debt ledger (job 112399270073), which reads cancelled.
  • Why that lane was cancelled: its "Checkout repository" step took about 13 minutes (2026-10-06T17:10Z to 2026-10-06T17:23Z). The job's timeout-minutes: 15 then cut it off two minutes into "Build workspace packages". The ledger's own build, sweep and re-measure steps never ran, so no type-check verdict on this diff was rendered. PR fix(spec): EvalUser.isPlatformAdmin is the ADR-0095 D3 PLATFORM_ADMIN standing, not a deprecated alias #22018's governed guard run hit the same slow checkout earlier today. The other type-check lanes (source gates, workspace) are green on this head.
  • What happens next: the seat has no sanctioned re-run route; its write relay carries no Actions op. The branch is behind main, so the dev merges main (a real merge commit, ⛔ no rebase) and pushes, and every lane re-runs on the new head. The seat lands the PR only once every check is green there. The contract review of record (6021713251, PASS) stays the review for the PR's own diff, and the seat checks that the new head adds nothing beyond the merge.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

CI note from the owning domain:spec seat 2 (session_01GV6oYwgc1kWiUCb1YaprQ7) · 2026-10-06T17:47Z. ⛔ Not a review verdict.

On the merged head 9a3d689629, TypeScript Type Check is red for one reason only: Type Check · source gates was cancelled in its checkout.

  • The cancelled lane: job 112407996458 (run 37504029711) spent its whole timeout-minutes: 10 in "Checkout repository" (2026-10-06T17:30Z to 2026-10-06T17:40Z). Every check step was skipped, so no verdict on this diff was rendered. This is the third checkout-step timeout on this seat's PRs today (with 6021576789 on PR fix(spec): EvalUser.isPlatformAdmin is the ADR-0095 D3 PLATFORM_ADMIN standing, not a deprecated alias #22018 and 6021754813 here). None of them depends on the diff.
  • The other three type-check lanes are green on this head: debt ledger (the lane that timed out last time), consumer gates and workspace. No check on this head has concluded failure on its own.
  • What it needs: one re-run of the failed job in run 37504029711 ("Re-run failed jobs"). This seat has no sanctioned re-run route, because its write relay carries no Actions op, so it has asked the maintainer for it. The seat lands the PR once every check on this head is green or an expected skip.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 7, 2026 03:13
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 7, 2026 03:13
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 7, 2026
Merged via the queue into main with commit c565813 Oct 7, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21995-measure-column-aggregate branch October 7, 2026 03:50
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…tack/spec/ui; metadata-core re-exports the same bindings (objectstack-ai#22056)

Fixes objectstack-ai#22047

Clause-②: yes (widening: new exports on the published
`@objectstack/spec/ui` entry; `@objectstack/spec` changeset at least
`minor`)

## What changes

- **New on `@objectstack/spec/ui`:** `publicFormSlug`,
`anonymousFormIntakeSlug`, `anonymousFormIntakeCandidates`,
`anonymousFormIntakeSlugs` and the `AnonymousFormIntakeCandidate` type.
This is the candidates half of the anonymous-form-intake rule, which the
triage ruling on objectstack-ai/objectui#11545 (`5967405932`) asks a
console to read instead of re-deriving "published" from the sharing
keys.
- **Moved, not copied.** The bodies are the ones that were in
`packages/metadata-core/src/anonymous-form-intake.ts`, unchanged except
for indentation (2 spaces, as in the rest of `packages/spec`). `diff -w`
between the BASE lines 52-105 and the new module is empty. The scan
still covers the three shapes in the same order: nested `form`, then
`formViews` entries, then a `viewKind: 'form'` item's `config`.
- **`@objectstack/metadata-core` re-exports the same bindings** (`export
{ … } from '@objectstack/spec/ui'`, plus `export type` for the
interface), so one copy remains. Its remaining code imports
`publicFormSlug` and the type from the spec. Its posture,
withdrawal-layer and object-name parts stay where they were.
`@objectstack/rest` and `@objectstack/metadata-protocol` are not edited
and keep importing from `metadata-core`.
`packages/metadata-core/src/index.ts` is unchanged.
- Changeset `.changeset/22047-spec-ui-anonymous-form-intake.md`:
`@objectstack/spec` `minor`, `@objectstack/metadata-core` `patch` (its
built `dist/index.{js,cjs,d.ts,d.cts}` now import these functions from
`@objectstack/spec/ui` instead of defining them).

## Where it lives, and why (H3)

It goes in a new module,
`packages/spec/src/ui/anonymous-form-intake.ts`, next to
`sharing.zod.ts`. It is not added to `sharing.zod.ts`, for three
reasons:
- **The module imports nothing.** It has no zod import and no schema
import, and does no work at load time. It stays as cheap as a browser
can import, whichever entry reaches it. `sharing.zod.ts` imports `zod`
and two schema helpers.
- **This is the existing pattern for runtime helpers in `spec/ui` that
do not live inside a schema module.** `chart-aggregate.ts`,
`i18n-label-resolver.ts` and `view-grouping-query.ts` are sibling
non-`.zod.ts` modules. `expandViewContainer` sits in `view.zod.ts` only
because it reads that module's member constants. These four functions
read no schema.
- **The file name matches the one in `metadata-core`,** so the move is
easy to follow in history.

Trade-off: `files[]` ships `src/**/*.zod.ts` as source, so this module's
source is not in the tarball. Its JS and declarations are, in
`dist/ui/index.{mjs,js,d.mts,d.ts}`.

Prime Directive 2 (no business logic in `packages/spec`: schemas, types
and constants only) holds here the way ADR-0053 D-D2 reads it. A pure
helper that states what the contract's own vocabulary denotes is
protocol, not business logic. It lives beside that vocabulary, and a
server package re-exports it: D-D2 moved `nextUtcCalendarDay` into
`@objectstack/spec/data` and has `@objectstack/core` re-export it. These
four functions only say which `sharing` declarations open a form. The
two checks that read server state (another layer's withdrawal, the
tenancy posture) stay in `metadata-core`. So does
`anonymousFormObjectName`, a pure read of the form and the view, because
that is where this export's surface was drawn. `expandViewContainer`
(`view.zod.ts`) is the placement precedent: a pure helper beside the
schema it serves. The reason two codebases must agree on this rule byte
for byte is the triage ruling on objectui#11545 (`5967405932`), as the
module's header now says (patch round 1, `6c5741c6`).

## Pins and measurements

**Identity pin (3).** This is in
`packages/metadata-core/src/anonymous-form-intake.test.ts`; the existing
cases are unchanged, and the pin adds 3 import lines and 1 `describe`.
For each of the four names it asserts that
`./anonymous-form-intake.js[name]` and `./index.js[name]` are `toBe`
(Object.is) `@objectstack/spec/ui[name]`.
- **Ablation:** run once and not kept, through
`scripts/ablation-replace.mjs` with the fix committed first. The
re-export of `anonymousFormIntakeCandidates` was replaced by a wrapper
that returns the spec function's answer.
- Result: `Tests 1 failed | 45 passed (46)`. Only the identity case for
`anonymousFormIntakeCandidates` failed (`expected [Function] to be
[Function] // Object.is equality`). Every behaviour test passed against
the wrapper, so only the identity pin can catch a copy.
- Restore was verified by the tool: the blob equals HEAD (`c08671875`)
and `git diff HEAD` is empty. metadata-core's test reads its own `src`
directly, so the mutation needed no rebuild to take effect.

**H5: identity in the built dual output.** A one-time node probe, run
from `packages/rest`, compared the four functions in metadata-core's
built output with `@objectstack/spec/ui`'s:

| condition | metadata-core export === spec/ui export |
|---|---|
| ESM (`dist/index.js` vs `dist/ui/index.mjs`) | true for all four |
| CJS (`dist/index.cjs` vs `dist/ui/index.js`) | true for all four |
| ESM vs CJS (cross-condition) | false: the dual-package split every
spec export already has |

**Parity pin (1).** `packages/spec/src/ui/anonymous-form-intake.test.ts`
(26 cases) pins each of the three shapes on its own (open; withdrawn by
either switch; no link), all three in one body in scan order, a `config`
without `viewKind: 'form'`, slug normalisation, and raw input against
`SharingConfigSchema.parse` input. Its expectations are the same as
metadata-core's existing ones. A one-time parity probe compared the BASE
metadata-core functions (`git show 8caa131`, lines 52-105) with the
built spec/ui and metadata-core functions now. It compared candidate
keys, key presence, slugs, whether each candidate is a form object from
the input, and the slug set:

| shape | bodies | with an open slug | BASE = spec/ui = metadata-core |
|---|---|---|---|
| nested `form` | 13 | 4 | 13 |
| `formViews` entry (plus an open sibling) | 13 | 13 | 13 |
| `viewKind: 'form'` + `config` | 13 | 4 | 13 |
| all three in one body | 13 | 4 | 13 |
| `config` without `viewKind: 'form'` | 13 | 0 | 13 |
| non-view input | 4 | 0 | 4 |
| real producers: showcase `inquiry.view.ts`, crm `lead.view.ts`, as
containers and as `expandViewContainer` items | 9 | 4 | 9 |

That is 78 bodies with 0 mismatches, and 21 leaf inputs
(`anonymousFormIntakeSlug`, `publicFormSlug`) with 0 mismatches.

**H1.** Lines 52-105 call nothing from `@objectstack/spec/security`,
`applyInjectedSystemColumns` or `resolveRecordWallOrganizationField`.
The new module has no imports, and the four bodies compile unchanged in
`packages/spec`.

**H2.** `./ui` is in `browser-reachable-entries.json`'s `unjudged` list,
so `check:browser-reachable-entries` asserts nothing about it (it
passed). The module is plain `function` declarations with no top-level
statements, and the package declares `"sideEffects": false`.

**Declaration surface downstream.** `metadata-core`'s `dist/index.d.ts`
and `.d.cts` now reference `@objectstack/spec/ui`. Measured with `tsc
--noEmit --extendedDiagnostics --listFiles`, building metadata-core from
BASE source and then from HEAD source. These are absolute numbers from a
shared box:

| program | files BASE → HEAD | memory BASE → HEAD |
|---|---|---|
| `packages/rest` | 579 → 580 | 1,246,583K → 1,268,472K |
| `packages/objectql` | 574 → 574 | 983,063K → 994,921K |
| `packages/plugins/plugin-security` | 495 → 495 | 1,079,234K →
1,084,561K |
| `packages/metadata-protocol` | 808 → 808 | 1,319,331K → 1,318,969K |
| `packages/qa/http-conformance` | 345 → 345 | 357,199K → 357,044K |

The one new file in `rest` is `spec/dist/ui/index.d.ts`, the CJS barrel,
reached through `metadata-core/dist/index.d.cts`. The chunks it
re-exports were already in that program in both flavours. tsc exited 0
in every program on both builds.

## Prose that named the old home (H4)

- `packages/spec/src/ui/sharing.zod.ts:19-23` said the rule lives in
`anonymousFormIntakeCandidates` "in `@objectstack/metadata-core`". It
now names `anonymous-form-intake.ts` beside that module, which
metadata-core re-exports to the server's doors.
- `content/docs/references/ui/sharing.mdx:22-26` was regenerated from
that docblock by `gen:docs`, not edited by hand.
- `packages/metadata-core/src/anonymous-form-intake.ts:13-20`: the
module docblock says the candidates half is declared in
`@objectstack/spec/ui`, whose docblock is now the authority on it. The
scan-shape paragraph moved with the code.
- Judged still true and left alone:
- `packages/rest/src/rest-server.ts:10698` reads
"(`anonymousFormIntakeCandidates`, `@objectstack/metadata-core`)". That
is where rest imports the function from, and metadata-core still exports
it. The file is also outside this card's surface.
- `packages/metadata-core/src/index.ts:141-145` ("both read this one
rule").
- The `docs/qa/platform-checklist` mechanism references name
`anonymousFormIntakeWithdrawnIn` and `anonymousFormExplicitWithdrawals`,
which stay in metadata-core.

## Verification (HEAD `cfdc8804f0`; the source tree is the same as
`3e9a9e48b1` plus the changeset)

- `pnpm --filter @objectstack/spec build` (JS + DTS): exit 0.
`check-dts-emitted` reported 38/38.
- `pnpm --filter @objectstack/spec check:generated`: all 15 artifacts up
to date. Before regeneration, 3 were stale: `api-surface/` (+5 rows in
`ui.json`), `export-origins/` (+5) and `content/docs/references/**` (the
H4 sentence). They were regenerated with `gen:api-surface`,
`gen:export-origins` and `gen:docs`.
- `check:api-surface`, `check:export-origins`, `check:docs`,
`check:exported-any`, `check:dual-source-exports` (0 accepted
dual-source), `check:entry-nameability`,
`check:browser-reachable-entries`, `check:liveness`, `check:llms-txt`,
`check:skill-examples`: exit 0.
- `pnpm --filter @objectstack/spec test`: 620 files, 18511 passed, 1
todo. The new file alone: 26 passed.
- `pnpm --filter @objectstack/metadata-core test`: 18 files, 415 passed.
`src/anonymous-form-intake.test.ts` alone: 46 passed.
- `pnpm --filter @objectstack/metadata-protocol test`: 219 files passed,
3 skipped; 28135 tests passed. The focused run of
`runtime-authoring-gate.public-form-intake`,
`protocol.runtime-authoring-gate` and
`protocol.org-scoped-write-refused` passed 257.
- `pnpm --filter @objectstack/rest test`: 260 files, 4914 passed, 326
skipped. The focused run of `public-form-routes`,
`public-form-routes.stored-row`, `public-form-withdrawal` and
`public-form-intake-availability` passed 76.
- `pnpm --filter @objectstack/spec typecheck` and `pnpm --filter
@objectstack/metadata-core typecheck`: exit 0. `--listFiles` shows both
new test files are in their packages' test programs.
- Gates derived by `node scripts/pm/dispatch-gates.mjs --commands` at
`cfdc8804f0`, and checked with `--ran`: 110 derived, 109 run, 0 unrun, 1
NOT MEASURED. The NOT MEASURED one is `pnpm check:dual-build-cjs-loads`,
exit 3, PREREQUISITE NOT MET: it needs every package built. The CJS half
of the H5 probe above loaded `metadata-core/dist/index.cjs` with
`require`. The 5 roster gates the lead list flagged under a touched
directory also pass (`check:meta-url-spelling` and `check:spec-changes`
through `check:generated`; `check:authz-resolver`,
`check:error-code-casing`, `check:filter-alias-parity`).
- Lint is a narrowed run, and it measures something: `eslint
--no-inline-config --format json` over the 6 changed TS files reported 6
files, 0 errors and 0 warnings. All 6 are in the population of
`eslint.config.mjs` (`files: ['**/*.{ts,…}']` minus `NEVER_LINTED`, and
`--print-config` resolves each one). That config never turns on
type-aware linting (no `parserOptions.project`, no typed rules; see its
own note near line 327), so this diff cannot change the result for any
file it does not touch. The repo-wide `pnpm lint` is left to CI.

## Acceptance notes

- **Not covered by this card** (the card's "Not this card" section):
whether another layer withdraws a form, whether the posture makes a form
unavailable, any server-side "published" answer, and the objectui page
change. objectui#11545 restarts once objectui uses a release that
carries these exports.
- `main` gained objectstack-ai#22021 (a spec analytics change) after this branch was
cut. It touches none of these files and none of the generated artifacts,
so `main` is not merged here, and the merge queue rebuilds on the
current `main`.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 28, 2026
…0 and follow its contract (objectui#12030) (objectstack-ai#12337)

Fixes objectstack-ai#12030
Clause-②: yes — 22 published `@object-ui/*` packages declare a
consumer-facing `@objectstack/*` range that moves from `^17.x` to
`^18.0.0-next.0` (21 in `dependencies`, 1 in `peerDependencies`, counted
from the manifests at the head), so a release would carry a
published-contract change. `node scripts/check-changeset-presence.mjs`
at `0cc4e8fcc`: "102 source file(s) and 1 published-contract change(s)
of 18 released package(s) changed, and this change declares 3
changeset(s)". `.changeset/12030-objectstack-next-18.md` declares the 22
packages `minor`, with the breaking-for-consumers note in its body; the
changeset coverage of the other touched packages is below.

follow-up: objectstack-ai#12334

**Scope: the `@objectstack/*` move to `^18.0.0-next.0` and the fixes it
forces. No objectui release.** Releasing objectui 18.x (the AGENTS.md
§版本号策略 sync `major`, Changesets pre mode on `next`, and the two gate
edits ruled in records 6148150305 and 6149296149) moves to the follow-up
card above, per the scope split recorded as 6149376719. This branch
carried the sync `major` and `.changeset/pre.json` for a while
(`0b942ba`); they are reverted (`7ec3c2b`), and neither gate script is
edited.

**Known effect after merge, intended:** `Spec Range Floors`
(`scripts/check-spec-range-floors.mjs`) cannot read `^18.0.0-next.0`: at
the head it exits 1 with "The scan collapsed: 0 package(s) declare
@objectstack/spec in a consumer-facing field". Its nightly alarm on
`main` turns red, and `pnpm changeset:publish` cancels any publish until
the follow-up lands. That is the fail-safe that stops a 17.x release
declaring 18 ranges. The open version PR objectstack-ai#11600
(`changeset-release/main`) refreshes toward 17.8.0 and must not be
merged before the follow-up.

## What this does

- Every `@objectstack/*` range in `dependencies`, `devDependencies` and
`peerDependencies` moves to `^18.0.0-next.0`: 40 ranges in 32 manifests
(26 / 13 / 1). Raised textually; `pnpm-lock.yaml` regenerated by `pnpm
install` only, never hand-edited. `pnpm install --frozen-lockfile` exits
0 and a second plain `pnpm install` leaves the lockfile byte-identical
(measured at `e8e9a35`; lockfile and manifests unchanged since).
- npm dist-tags read today for the four `@objectstack/*` packages
objectui names (`spec`, `client`, `formula`, `lint`): `next:
18.0.0-next.0`, `latest: 17.7.0`; every `18.0.0-next.0` tag peels to
objectstack `d889448b93`. `next` had not moved when the final head was
pushed, so objectstack-ai/objectstack#23160 (`1912af47ba`, compare reads
`behind`: not an ancestor) is not in this release and objectstack-ai#12325's reader
removal stays out of scope.
- Every break the bump caused that is a mechanical follow-through of a
v18 contract change is fixed here. Where a break needed a behaviour or
design choice, the PR keeps today's behaviour and says so in the test;
the seat judged or routed each one (record 6148150305).

## Changeset coverage

Every published package this change touches, measured on the net diff
from `72594f29e` (the merge base since the `main` merge; the earlier
rounds measured from `cea429b98`, before it, and the set is the same 32
published packages):

- `.changeset/12030-objectstack-next-18.md`: the 22 packages whose
consumer-facing ranges move, `minor`.
- `.changeset/12030-i18n-capability-labels.md`: `@object-ui/i18n`,
`minor`. The ten locale packs drop
`capability.label.manage_org_presentation` and add
`capability.label.manage_mailboxes` with spec 18's capability set. A key
a consumer could read is removed, so the bump is `minor`, not `patch`.
- `.changeset/12030-console-bundles-spec-18.md`: `@object-ui/console`
and `@object-ui/runner`, `minor`. In both, the `@objectstack/*` ranges
are `devDependencies`, but each is an app build whose published `dist`
bundles them. The console bundles them in its `vendor-objectstack`
chunk, measured under objectstack-ai#11762 below. The runner is a Vite app build
(`vite build`, no `build.lib`, no `external`) whose aliases compile
`@object-ui/components`, `core`, `types` and other `@object-ui/*`
packages from `src`; those import `@objectstack/spec` at runtime. The
runner line was added in the second patch round, after the opus
re-review 6158463307 measured it.
- `@object-ui/plugin-calendar` and `@object-ui/plugin-kanban` changed
only a comment, and get no line. Measured: both rebuilt from source at
`2c15e40cf` (`pnpm --filter … run build`, after removing `dist`); `grep
-rF` over each `dist` (their `files[]` publish `dist`, `README.md`,
`CHANGELOG.md`, `LICENSE`) finds neither the changed sentence nor the
unchanged comment sentence beside it, while code strings such as
`startDateField` and `ObjectKanbanRenderer` are found.
- `@object-ui/collaboration`, `mobile`, `plugin-charts`, `plugin-report`
and `sdui-parser` get no line. The test: does the package's published
`files[]` bundle anything this change touched? In each, the only
manifest change is `devDependencies["@objectstack/spec"]`, and the
package's own sources are unchanged. `collaboration`, `mobile` and
`sdui-parser` build with `tsc`, so their `dist` holds their own sources
only. `plugin-charts` and `plugin-report` are Vite lib builds whose
`rollupOptions.external` externalises every bare import, so no other
package is compiled into them. None of them imports `@objectstack/spec`
from non-test source.

## Pending changesets on `main` reworded (patch round 3)

Commit `0cc4e8fcc`. Eight pending `.changeset/*.md` that this change did
not add carried sentences the spec-18 move makes false at release time.
Each false clause is corrected in place, mostly by one clause pointing
at objectui#12030; history is kept. In the quotes below, a type argument
is spelled in words (`PublicBlockNodeOf` for `'element:repeater'`),
because GitHub's body sanitizer eats angle-bracket fragments; the files
spell it as code. The wording is all that changed: every file's
frontmatter (packages and levels) is byte-identical to `0d457fe1f` (sha1
of each frontmatter block compared), and `check-changeset-overwrite`
reports "declared at base" equal to "declares now" for all eight.

- `.changeset/6152-grid-default-filters-gantt-map-filter.md`
(`@object-ui/types` minor; `core`, `plugin-grid`, `plugin-map`,
`plugin-view` patch)
- before: "`@objectstack/spec` has typed
`ComponentPropsMap['object-grid'].defaultFilters` as the same
`ViewFilterRule` array as `filter`, `[{ field, operator, value }, ...]`,
since 17.6.0: the legacy fallback `ObjectGrid` reads only when `filter`
is absent, …"
- after: "`@objectstack/spec` typed
`ComponentPropsMap['object-grid'].defaultFilters` as the same
`ViewFilterRule` array as `filter`, `[{ field, operator, value }, ...]`,
from 17.6.0 until 18 retired it (see objectui#12030): the legacy
fallback `ObjectGrid` read only when `filter` was absent, …"
- before: "… so `table: { defaultFilters: [{ field: 'status', operator:
'equals', value: 'open' }] }` now parses there, on the tolerant and the
strict face and through `safeValidateSchema`."
- after: "… so `table: { defaultFilters: [{ field: 'status', operator:
'equals', value: 'open' }] }` parsed there through 17.x, on the tolerant
and the strict face and through `safeValidateSchema`; retired in 18, see
objectui#12030: the row's tombstone refuses `defaultFilters` by name, so
every value, the rule array included, is refused there now."
- before: "… with the protocol's own message, which computes the rule
array from the record's keys. Respell `defaultFilters: { status: 'open'
}` as `defaultFilters: [{ field: 'status', operator: 'equals', value:
'open' }]`, or, better, move it to `filter`, which takes the same array
and wins when both are written."
- after: "… with the protocol's own message, which computed the rule
array from the record's keys through 17.x (since 18, see objectui#12030,
it is the retired-key message, which names `filter`). Respell
`defaultFilters: { status: 'open' }` as `filter: [{ field: 'status',
operator: 'equals', value: 'open' }]`; the rule-array `defaultFilters`
spelling this entry first offered is retired in 18 too."
- before: "`ObjectGrid` lowers `defaultFilters` through the same
`toFilterNode` sink as `filter`, so a rule array there sends the same
`$filter` and draws the same rows as the same array written as `filter`;
the sink still lowers a record or an AST that reaches the slot at
runtime, …"
- after: "`ObjectGrid` lowered `defaultFilters` through the same
`toFilterNode` sink as `filter`, so a rule array there sent the same
`$filter` and drew the same rows as the same array written as `filter`
(retired in 18, see objectui#12030: `ObjectGrid` and `ObjectView` no
longer read `defaultFilters`, so a view that still carries it renders
unfiltered); the sink still lowers a record or an AST that reaches
`filter` at runtime, …"
- before: "… and `@object-ui/plugin-grid` a pin of the above through the
real renderer."
- after: "… and `@object-ui/plugin-grid` a pin of the above through the
real renderer (since 18 it pins that `defaultFilters` is not read)."
- `.changeset/11880-grid-filter-handoff.md` (`@object-ui/plugin-view`
patch)
- before: "… else `table.filter` unless it lowers to nothing (absent,
`[]` or `{}`), else the deprecated `table.defaultFilters`."
- after: "… else `table.filter` unless it lowers to nothing (absent,
`[]` or `{}`), else the deprecated `table.defaultFilters` (a rung
retired in 18, see objectui#12030: `ObjectView` no longer reads it)."
- before: "What did not change: which filter applies (each rung and each
pair sends the same query and draws the same rows), and …"
- after: "What did not change: which filter applies (each rung and each
pair sends the same query and draws the same rows; in 18 the
`table.defaultFilters` rung is retired, see objectui#12030), and …"
- Left as written: the export and page-1 bullets, which say in the past
tense what a `table.defaultFilters` did before this entry's change.
- `.changeset/12056-repeater-object-waiver.md` (`@object-ui/types`
minor)
- before: "The zod arm `ElementRepeaterBlockSchema` now applies the same
`object` waiver as the spec's props gate (objectui#12056)."
- after: "The zod arm `ElementRepeaterBlockSchema` now applies the same
`object` waiver as the spec's props gate (objectui#12056); superseded in
18, see objectui#12030: spec 18 retires the flat `object`, so the arm is
the row by reference, a node may omit `properties.object` whether or not
it binds one, and a `properties.object` is refused by name."
- before: "`ComponentPropsMap['element:repeater']` requires `object`." …
"a node refinement puts the requirement back wherever no binding names
the object."
- after: "`ComponentPropsMap['element:repeater']` required `object`
through 17.x." … "a node refinement puts the requirement back wherever
no binding names the object. Both waivers and both refinements are gone
in 18 (objectui#12030): the row has no flat `object` left to waive."
- before: "**Still refused at `properties.object`.** A bag with no
`object` is refused when the node has no binding that names one: … The
refusal is a `custom` issue with `params.code`
`ELEMENT_REPEATER_OBJECT_REQUIRED`, and its message names both remedies.
A `properties.object` of the wrong type is still refused by the row
itself."
- after: "**Still refused at `properties.object` through 17.x.** A bag
with no `object` was refused when the node had no binding that names
one: … The refusal was a `custom` issue with `params.code`
`ELEMENT_REPEATER_OBJECT_REQUIRED`, and its message named both remedies;
retired in 18, see objectui#12030: the refinement and its code are
deleted, so such a bag is accepted. A `properties.object` of the wrong
type is still refused by the row itself, and since 18 so is any
`properties.object`, by name."
- before: "**TypeScript.** The inferred `ElementRepeaterBlockSchema`
type now types `properties.object` as optional, and so does the
authoring type `PublicBlockNodeOf` for `'element:repeater'` derived from
it."
- after: "**TypeScript.** The inferred `ElementRepeaterBlockSchema` type
typed `properties.object` as optional through 17.x, and so did the
authoring type `PublicBlockNodeOf` for `'element:repeater'` derived from
it; since 18 (objectui#12030) both follow the row, which retires
`object`, so a typed `properties.object: string` no longer type-checks."
- `.changeset/7611-approver-registry-read.md` (`@object-ui/app-shell`
patch)
- before: "A `data` binding (`user`, `team`, `department`, and
`position` under the installed spec 17.7.0) still renders the record
lookup … The type that admits the registry member before the installed
spec declares it is marked for removal when objectui moves to spec 18."
- after: "A `data` binding (`user`, `team`, `department`, and `position`
under spec 17.7.0; spec 18, which objectui installs from objectui#12030,
binds `position` to the registry) still renders the record lookup … The
type that admitted the registry member before the installed spec
declared it was marked for removal when objectui moves to spec 18;
removed in 18, see objectui#12030."
- `.changeset/11880-element-datasource-only.md`
(`@object-ui/components`, `app-shell`, `i18n` patch)
- before: "The flat keys stay published until `@objectstack/spec`
retires them in v18; each input's description now says it is not read
and names the binding member that is."
- after: "The flat keys stayed published, each input's description
saying it is not read and naming the binding member that is, until
`@objectstack/spec` retired them in 18: the record picker's four inputs
left with that retirement (objectui#12030), and `element:number`'s two
with objectui#12085."
- `.changeset/12056-repeater-datasource.md` (`@object-ui/app-shell`
patch)
- before: "… and `objectui validate` accepts the node the designer now
writes, because `@object-ui/types` takes the spec gate's `object` waiver
for this element in the same change."
- after: "… and `objectui validate` accepts the node the designer now
writes: `@object-ui/types` took the spec gate's `object` waiver for this
element in the same change, and since 18 (objectui#12030) the row has no
flat `object` left to require."
- `.changeset/11880-repeater-datasource.md` (`@object-ui/types` minor,
`@object-ui/components` patch), found by the grep beyond the six named
- before: "Its flat `properties.object` / `filter` / `sort` / `limit`
stay read, as the fallback, until `@objectstack/spec` retires them in
v18, so a repeater that carries only the flat keys reads exactly as
before."
- after: "Its flat `properties.object` / `filter` / `sort` / `limit`
stay read, as the fallback (spec 18 retires them as authored keys, see
objectui#12030, and the renderer keeps this read for metadata written
before), so a repeater that carries only the flat keys reads exactly as
before."
- before: "… by reference, and its bag's `object` stays required.
Clause-②: the strict authoring face (`StrictAnyComponentSchema`) now
ACCEPTS a repeater carrying `properties.object` plus a well-formed
`dataSource`, which it refused as an unrecognized key; …"
- after: "… by reference, and its bag's `object` stays required (until
objectui#12056's waiver, and retired in 18, see objectui#12030).
Clause-②: the strict authoring face (`StrictAnyComponentSchema`) now
ACCEPTS a repeater carrying `properties.object` plus a well-formed
`dataSource`, which it refused as an unrecognized key (since 18 it
refuses `properties.object` by name, so the accepted node carries the
binding alone); …"
- before: "A repeater whose bag omits `object` is refused at
`properties.object` on both faces, as before."
- after: "A repeater whose bag omits `object` is refused at
`properties.object` on both faces, as before; retired in 18, see
objectui#12030: such a repeater is accepted."
- One clause here went false with objectui#12085, not with this change,
and is corrected in the same edit because it sits in the same bullet:
before "The registration now publishes the injected `dataSource` input;
`object` stays required." after "The registration now publishes the
injected `dataSource` input; `object` stays required (until
objectui#12085 dropped the flat query inputs)." Evidence: the
`element:repeater` registration in
`packages/components/src/renderers/basic/data-list.tsx` at the head
publishes `titleField`, `fields`, `emptyText`, `divided` and no
`object`, and `.changeset/12085-element-binding-inputs.md` states the
drop. The seat may ask for this one clause back out.
- `.changeset/12093-spec-main-compile.md` (empty frontmatter, no
release)
- before: "… a retired-key tombstone on objectstack `main`), and
`@object-ui/app-shell`'s zh-CN overlay-scope label table keys `org`
beside the spec's union (objectstack `main` narrows that union to
`env`)."
- after: "… a retired-key tombstone on objectstack `main`; the read
itself is retired in 18, see objectui#12030), and
`@object-ui/app-shell`'s zh-CN overlay-scope label table keys `org`
beside the spec's union (objectstack `main` narrows that union to `env`;
the `org` label is dropped in 18, see objectui#12030)."

The grep over all 438 pending `.changeset/*.md`, besides this change's
own three: `defaultFilters` hits 6152, 11880-grid-filter-handoff and
12093; `ELEMENT_REPEATER_OBJECT_REQUIRED` hits
12056-repeater-object-waiver only; `ELEMENT_NUMBER_OBJECT_REQUIRED` and
`manage_org_presentation` hit nothing; the overlay-scope `org` hits
12093 only (12171-environment-overlay-copy names the environment overlay
and stays true); the flat element query keys and the record picker hit
11880-element-datasource-only, 11880-repeater-datasource,
12056-repeater-datasource, 12056-repeater-object-waiver and
12085-element-binding-inputs. Read and left as written, each still true
at release: `12085-element-binding-inputs.md` (the repeater renderer
still reads its flat keys as the fallback; this change does not touch
it), `11903-record-picker-layout.md` (the `RecordPickerDialog`, not
`element:record_picker`), `12086-edit-inline-default-on.md`,
`7611-capability-registry.md` (names no curated capability),
`7611-decision-output-position-registry.md`, and the "since 17.7.0"
mentions elsewhere, which date a fact and do not say what is installed.

## Merge of `main`

`main` at `72594f29e` is merged into this branch (merge commit
`1c95e13af`): six commits, merged cleanly, none touching a manifest or
the lockfile. Files both sides changed:
`packages/app-shell/src/views/metadata-admin/i18n.ts`,
`packages/plugin-grid/src/ObjectGrid.tsx` and the ten `packages/i18n`
locale packs. The re-run after the merge is under Gates.

## The break list (measured first, at `a3f41ed`: the bump and changeset
alone)

Full vitest suite in five lock-held groups: 50 files, 159 tests red
(types/core 15 files / 58 tests, app-shell 21 / 45, plugins A 6 / 20,
plugins B 1 / 4, console + scripts 7 / 32). `turbo run build` and every
package's `type-check` were green at that commit (81/81). Two repo gates
were red: `check:eager-closure` (exit 2, ceilings drifted to 1.74x /
1.21x) and `check:installed-pin-claims` (23 findings). By area, and how
each is closed:

- **Overlay scope is `env` only** (objectstack#15206 S5; card comment
6098299437): data-objectstack overlay fixtures and docblock, two
app-shell fixtures, and the zh scope label table drops its `org` row (an
`org` from an older server renders raw), as the ruling's seat judgment
says.
- **Index `unique: true` refused at protocol 18** (objectstack#22103):
the index-fallback probe pins the refusal with a `false` control, the
validation-preview help (en, zh) names `unique: 'organization'` /
`unique: 'global'`, and the `data-protocol.ts` docblock says refused.
These are the three sites objectstack-ai#11864 names; its legacy-row rendering case
still passes and is left as is.
- **Capabilities**: `manage_mailboxes` in, `manage_org_presentation`
out; curated label set, default label, ten locale packs.
- **Page blocks**: the element:number / element:repeater /
element:record_picker flat query keys and object-grid `defaultFilters`
are tombstones refusing by name (objectstack#11509). The number and
repeater arms take the spec row by reference; the record_picker node and
registration stop publishing the four keys; the parity, arm, harvest and
ratchet pins read the tombstones.
- **Retired reads removed** (seat judgment under the ruling):
`ObjectGrid` no longer reads `defaultFilters` and `ObjectView` no longer
reads `table.defaultFilters`; no in-repo producer writes the key. The
tests that pinned it as a working alias are flipped to pin it inert
beside a canonical `filter` control; the plugin-view README and docs
page, the registration's `filter` description and the type's JSDoc say
retired. The element:repeater renderer's flat `object` / `filter` /
`sort` / `limit` fallback reads stay:
`apps/console/src/dev/DevLists.tsx` writes `properties.object` and
`properties.limit` and is routed in `App.tsx`, a live in-repo producer,
reported instead of deleted. element:number and element:record_picker
already read the binding only.
- **New spec checks and keys**: `checkPageSlotPair` attached to both
page node schemas beside the existing page checks (and listed among the
chained refinements in the import-boundary census);
`checkPagePrintComposition` and `checkListViewChartBinding` read a
discriminator and are listed as not attachable; ObjectSchema's 45th key
`attachedOnRead`; record:details sections' experimental `count` listed
as deferred; the three new record component types stay out of the block
palette, as today.
- **ListView `requiredPermissions`**: pinned as the one pending
declaration; the seat files a declare-and-enforce card.
- **Flow dialect** (ADR-0032 §3): flow fixtures move to the spellings
v18 accepts. The value-role ledger now names six key/value maps; each
node's own config schema was measured to refuse a malformed envelope
with exactly the sentences the editor shows. A `{token}` string in a
value slot is refused by the spec and the editor shows that refusal; the
recipient picker's `{…}` spellings and a blank Notify title are pinned
as PENDING. The Studio cutover rides objectstack-ai#11824.
- **Approver `position`** is a registry binding in v18: the spec-17
loosening is removed and the identity, directory and lookup pins read
the registry arm.
- **Submit-redirect / url action**: the refusal pins read the spec's own
message; a url action target fixture is a path.
- **Installed-spec claims**: re-measured and restamped where the fact
holds, one historical mention ledgered; `e2e/live/ci/backend.env` moves
to `18.0.0-next.0`; `QUICK_REFERENCE.md` re-synced by its script;
lockfile-integrity samples re-keyed; the declared-floor reader in core's
test reads a prerelease floor by its core triple.

## objectstack-ai#11762: the eager-closure line comes down by the payback

objectstack#22044 (`93125aeeb8`) is an ancestor of the `18.0.0-next.0`
tag commit (compare `ahead`, behind 0; control: the 17.7.0 tag commit
`4e4e881427` also reads `ahead`). Two console builds, one container, one
instrument (`CI=true pnpm exec turbo run build
--filter=@object-ui/console... --concurrency=2` under the verify lock,
reading `apps/console/dist/eager-closure.json`):

| chunk | `cea429b` (main, 17.7.0) | `5488ae7` (this branch) | delta |
|---|---:|---:|---:|
| `vendor-objectstack` | 1,599,281 | 1,512,690 | -86,591 |
| `ui-components` | 285,771 | 284,925 | -846 |
| everything else (70 names) | 1,324,632 | 1,324,848 | +216 |
| aggregate | 3,209,684 | 3,122,463 | -87,221 |

`MAX_EAGER_CLOSURE_GZIP_BYTES` 3,281,467 to 3,194,246 and `BASELINE`
onto `5488ae7`; `vendor-objectstack` ceiling 1,623,000 to 1,536,409 and
its baseline onto the same build. Lowered by the amount, not re-derived
and not rounded; `REGRESSION_THIS_GATE_MUST_CATCH_BYTES` untouched.
Headroom 71,783 bytes = 0.79x on the aggregate, 23,719 = 0.26x on
`vendor-objectstack`: what `main` had on `cea429b`. The ruling's two
later edits reach bundler inputs; the console build of `5db13ba` reads
3,122,375 (-88), inside the band, recorded in the `BASELINE` prose and
not re-pinned. The upstream figure (-226,301 on the chunk) was on an
injected dist; the real release nets -86,591 here.

## Held cards: their Restart-when probes at the head

| card | probe | at the head (`18.0.0-next.0`) | control |
|---|---|---|---|
| objectstack-ai#11773 | `MetadataConflictErrorSchema` in the spec's `.d.ts` | exit 0
(next tarball and installed tree) | `@latest` (17.7.0) tarball: exit 1 |
| objectstack-ai#11545 | `anonymousFormIntakeCandidates` is a function | exit 0 |
17.7.0: exit 1 |
| objectstack-ai#11681 | a release carrying the objectstack#21995 change
(objectstack-ai/objectstack#22021, `c5658133fc`) | met: ancestor of
`d889448b93` | |
| objectstack-ai#11970 | lockfile resolves `@objectstack/core` and
`@objectstack/objectql` carrying `4578c56e65` | half: core
`18.0.0-next.0` carries it; objectql is in no objectui manifest or
lockfile, so the probe cannot be met as written | |
| objectstack-ai#11958 | spec/ui exports `PagePrintSchema` and
`PRINTABLE_PAGE_COMPONENT_TYPES` | exit 0 | 17.7.0: exit 1 |
| objectstack-ai#11864 | spec carries `ef1fcb26a2` | met; its three sites are changed
in this PR | |
| objectstack-ai#11762 | spec tag commit has `93125aeeb8` as ancestor | met; executed
in this PR | |
| objectstack-ai#12270 | `EmptyStateSchema` link +
`InterfacePageConfigSchema.emptyState` | exit 0 | 17.7.0: exit 1 |
| objectstack-ai#12271 | `ListDraftsResponseSchema` reads a draft's `member` | exit 1
as written: the probe's fixture has no `label`, which the schema
requires (`drafts.0.label` invalid_union). With `label` added: exit 0 |
with `label`, 17.7.0: exit 1 |
| objectstack-ai#12192 | `SECTION_HEADING_COUNT_HOLE` is a RegExp | exit 0 | 17.7.0:
exit 1 |
| objectstack-ai#12314 | `stampCommentBodyEdit` in `@objectstack/plugin-audit` dist |
the probe reads `@latest` (17.7.0): exit 1. Run against `@next`
(18.0.0-next.0): exit 0 | |

## Gates (final head `0cc4e8fcc`; each reading names the commit it ran
on)

**Patch round 3** (`0cc4e8fcc`: pending-changeset wording only, eight
`.changeset/*.md`, no code, manifest or lockfile):

- At `0cc4e8fcc`, one script under the verify lock, `VERDICT
command-exit 0` (the script exits 1 if any gate does), each gate exit 0:
`check-changeset-presence` ("102 source file(s) and 1 published-contract
change(s) of 18 released package(s) changed, and this change declares 3
changeset(s)"), `check-changeset-no-major` ("No changeset declares a
`major` bump."), `check-changeset-fixed`, `check-changeset-overwrite`
(report-only: "3 changeset(s) added, 8 modified, 0 deleted", and for
each of the eight the declared packages and levels at base equal those
now), `check:changeset-claims` ("No pending changeset names a file this
change touches."), `check:pending-changeset-literals` ("No test source
names a pending changeset."), `check:new-line-citations` (0 new),
`check:control-bytes` ("OK (scanned 8636 tracked text file(s); skipped
85 binary)"), `check:installed-pin-claims` (`OK`).
- The script tests that read the live `.changeset/` or run a changeset
gate against this tree, at `0cc4e8fcc` under the verify lock: `pnpm exec
vitest run --project unit --maxWorkers=2` over
`changeset-polarity-census`, `check-changeset-no-major`,
`body-dialect-census`, `dollar-dialect-alias-census`,
`check-changeset-claims`, `check-pending-changeset-literals`,
`check-control-bytes`, `check-changeset-presence` and
`check-changeset-overwrite`: 9 files, 376 tests passed, `VERDICT
command-exit 0`. No code moved, so no package test or type-check was
re-run.

**Patch round 2** (`0d457fe1f`: the runner changeset line, the three
objectui-side moves named in the main changeset, and the last
`defaultFilters`-is-read prose reworded, in the `ObjectGridSlotKey`
docblock that ships in the published `.d.ts`, the zod mirror's member
comment, the grid round-10 test header, the grid registration note and
the `ObjectGrid` props note):

- At `0d457fe1f`, under the verify lock, each exit 0:
`check-changeset-presence` ("declares 3 changeset(s)"),
`check-changeset-no-major`, `check-changeset-fixed`,
`check:changeset-claims`, `check:pending-changeset-literals`,
`check:new-line-citations` (0 new), `check:control-bytes`,
`check:spec-symbols`, `check:installed-pin-claims`.
- `pnpm exec turbo run type-check --filter=@object-ui/types
--filter=@object-ui/plugin-grid --concurrency=2 --continue`: 16/16. Then
`grid-default-filters-gantt-map-filter-round10-6152`,
`object-view-slot-key-lists` and `imported-defaults-8317`: 3 files, 392
tests passed.
- `git grep defaultFilters` over non-test sources (READMEs, docs and
changelogs aside): what is left says retired, not read; describes
history (objectui#6152, objectui#11880); or is the declaration typed by
the row's tombstone.

**Patch round, after the `main` merge** (`1c95e13af`; `a1ca2ad55` only
rewords the i18n changeset):

- Changeset family and i18n gates at `a1ca2ad55`, under the verify lock,
each exit 0: `check-changeset-presence` (above, "declares 3
changeset(s)"), `check-changeset-no-major`, `check-changeset-fixed`,
`check:changeset-claims`, `check:pending-changeset-literals`,
`check:i18n-keys` ("Every in-scope call-site key resolves against the en
pack (3375 keys)"), `check:i18n-drift`, `check:i18n-designer-parity`,
`check:i18n-dead-keys` (a report), `check:control-bytes`.
- Type-check of the packages the merge touched, at `1c95e13af`: `pnpm
exec turbo run type-check --filter=@object-ui/app-shell
--filter=@object-ui/components --filter=@object-ui/data-objectstack
--filter=@object-ui/i18n --filter=@object-ui/plugin-detail
--filter=@object-ui/plugin-grid --filter=@object-ui/types
--concurrency=2 --continue`, 36/36.
- Tests the merge brings into contact with this diff, at `1c95e13af`.
The set: every package `main` and this branch both changed
(`plugin-grid`, `i18n`, `data-objectstack`, `types`) in full; every test
file `main` added or changed, now running on spec 18; and every
app-shell test that imports `metadata-admin/i18n`. Result: 920 files
passed, 16,446 tests passed, 13 skipped. The merge touches no manifest
and no lockfile, so the whole suite was not re-run; this head's CI
shards are the whole-workspace reading.

**Before the patch round:**

- **Install:** `pnpm install --frozen-lockfile` exit 0, then a plain
`pnpm install` leaves `pnpm-lock.yaml` byte-identical (sha256 compared),
at `e8e9a35`; the lockfile and every manifest are unchanged since (`git
diff e8e9a35 HEAD` over them is empty). Lockfile diff against `cea429b`:
+106 / -106.
- **Build:** `CI=true pnpm exec turbo run build
--filter=@object-ui/console... --concurrency=2`, exit 0, 35/35 tasks, at
`5db13ba` (the last commit touching a bundler input); the whole
published set was built (43 tasks) by `pnpm check:published-dist` at
`e8e9a35`.
- **Type-check:** `pnpm exec turbo run type-check --concurrency=2
--continue`, 81/81, at `5db13ba` (later commits touch only scripts,
tests and the changeset); `pnpm type-check:scripts && pnpm
type-check:vitest-setup && pnpm type-check:e2e && pnpm
type-check:vitest-config`, exit 0, at `2c15e40cf`.
- **Tests** (`pnpm exec vitest run --maxWorkers=2`, five lock-held
groups):
- types, core, react, i18n, cli, data-objectstack and 12 more at
`5488ae7`: 1019 of 1020 files; the red one (`imported-defaults-8317`,
from attaching `checkPageSlotPair`) is fixed in `ef2054e` and re-ran
green (267 tests). Later commits touch these packages only in one JSDoc.
- app-shell at `ef2054e`: 1319 passed, 1 skipped (12,730 tests). After
the `org` label drop, the four overlay-scope files re-ran green at
`2e80824ea` (21 tests).
- plugin-detail, plugin-grid, plugin-form, plugin-dashboard, components
at `2e80824ea`: 1207 passed, 2 skipped (12,292 tests).
- fields and the other 15 plugins at `7ec3c2b`: 1031 passed, 2 skipped
(11,505 tests).
- apps/console, scripts, eslint-rules, examples at `7ec3c2b`: 429
passed, 2 failed. `check-eager-closure-budget.test.ts` pinned the
rendered `BASELINE` literal: re-taken in `2c15e40cf` and green there.
`date-only-zone-pins-10183.test.ts` is red at `2c15e40cf` too, and is
NOT caused by this diff: its forked child fails one case, the suite-zone
control of `FormulaFunctions.dateOnlyZone-10866`, which reads
`Asia/Shanghai` (11:00) instead of UTC (03:00). That is the zone another
pin file sets last. The `unit` project runs `isolate: false`, and the
file order comes from each worktree's vitest cache. Run alone in the
same forked child at `2c15e40cf`, that file passes (39/39). The same
driver passed 28/28 at `a3f41ed` in a fresh worktree. This diff touches
neither the pin files nor the code they exercise.
- **Repo gates at `2c15e40cf`**, exit 0 unless noted:
`check-changeset-presence` (above), `check-changeset-no-major` ("No
changeset declares a `major` bump."), `check-changeset-fixed`,
`check:changeset-claims`, `check:pending-changeset-literals`,
`check:installed-pin-claims` (`OK`), `check:spec-symbols`,
`check:phantom-deps`, `check:new-line-citations` (0 new),
`check:control-bytes`, `check:eager-closure` ("3049.2 KB gzipped across
72 of 2255 chunks (budget: 3119.4 KB, headroom: 70.2 KB)", every ceiling
in range). `check:spec-floors` exits 1, the known effect above.
`.changeset/pre.json` is absent. `check:lockfile-integrity`,
`check:lockfile-dedupe`, `check:unused-deps` and `check:published-dist`
were green at `2e80824ea` / `e8e9a35` and nothing they read has changed
since.

## Acceptance notes

- Records: the v18 ruling 6148150305 and the gate ruling 6149296149 both
move to the follow-up card. This change is the dependency move only, per
the split record 6149376719. The seat's judgments under 6148150305 are
executed here: drop the zh `org` label, remove the retired reads.
- Follow-up cards from the seat's judgments:
- the list-view `requiredPermissions` mirror:
objectstack-ai#12341;
- the three record component types (`record:approval_decision`
registration): objectstack-ai#12342;
- the file field's `metadataRefused` marker:
objectstack-ai#12339;
  - holding a blank Notify title: objectstack-ai#12343;
- the `element:repeater` flat-key reads and their `DevLists.tsx`
producer: objectstack-ai#12345;
  - the Studio flow-dialect cutover: objectstack-ai#11824;
  - the objectui 18.x release: objectstack-ai#12334.
- Kept at today's behaviour and pinned as pending, for those cards: the
recipient picker's `{…}` spellings; a blank Notify title, which is sent
and shows the server's refusal; a `{token}` value-slot text cell, which
commits as typed and shows the spec's refusal; the new record component
types, which stay out of the palette; ListView `requiredPermissions`,
still undeclared.
- The element:repeater renderer keeps its flat-key fallback reads:
`apps/console/src/dev/DevLists.tsx` (routed in `App.tsx`) writes
`properties.object` / `properties.limit`, a live in-repo producer.
- `isValueEnvelopeSlot` serves `KEY.*` ledger paths only, as before. The
ledger's whole-value `value` slots (the record nodes' `filter`, loop/map
`collection`, notify `recipients`) are not key/value maps, and this
change leaves them alone.
- objectstack-ai#11864's legacy-row case (`EmbeddedItemEditor.indexFallback`, a stored
bare `unique: true` still renders) passes and is left as is: an 18
server serves such a row as `'global'`.
- Two held cards' probes cannot exit 0 as written. objectstack-ai#11970 names
`@objectstack/objectql`, which no objectui manifest or lockfile carries.
objectstack-ai#12271's fixture lacks the required `label`.
- The eager-closure readings above, and the `BASELINE` prose in
`scripts/check-eager-closure-budget.mjs`, cover this branch's own
commits through `2c15e40cf`. The `main` merge (`1c95e13af`) brings
`main`'s own bundler inputs, which are `main`'s drift, not this change;
CI's `Bundle Analysis` weighs them on this head. The console was not
rebuilt locally after the merge.
- AGENTS.md §版本号策略 names `OBJECTUI_ALLOW_MAJOR=1` as the sync-major
switch, and nothing in `.github/` sets it. That is the follow-up card's
to settle; this change leaves AGENTS.md alone.

---

_Generated by [Claude
Code](https://claude.ai/code/session_01CsyMRsFJBKNEMa4oPY1b5z)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: os-zhuang <jack@objectstack.ai>
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 size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants