You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{
"issue": 14492,
"status": "done",
"branch": "feat/analytics-builtin-aggregate-14492",
"pr": "https://github.com/objectstack-ai/objectstack/pull/15017",
"premise_still_valid": true,
"summary": "Ruling B implemented: AnalyticsResult.fields[] gains optional builtinAggregate (reusing the closed AggregationFunction enum from data/query.zod.ts), mirrored in AnalyticsResultResponseSchema (api/analytics.zod.ts — required by the spec's own compile-time parity guard), populated by queryDataset's measure enrichment exactly when the dataset measure declares an aggregate and NO authored label (judged on the authored key, so an unresolved locale-map label is still an author's label), and the dataset create seed drops its hardcoded label: 'Count' so a Studio-created dataset is a built-in default on the wire. Premise PARTIALLY refuted and stated in the PR body: fields[].label has exactly one producer (that enrichment, from the dataset measure), so the three Cube-metric sites (inferMeasure x2, cube-registry inferFromObject) never reach fields[] — they surface only via getMeta().title where CubeMeta.measures[].type already carries the aggregate — and populating them would mean widening the strict authorable MetricSchema with a key nothing reads; left untouched. The downstream-contract fixture is a Cube fixture with nothing to mirror (suite run green). The AI dashboard's actual 'Count' producer is cloud's dataset-derive.ts:206 (authored label), filed as cloud#1933 under epic #1841. Footer conflict: dispatch asked for the claude.com line, the role file for a session-URL footer — PR body carries both (bare form; session id not known to this seat). Verify lock reports no flock on this macOS host, so heavy runs went direct.",
"tests": "Built closures first: pnpm --filter '@objectstack/service-analytics^...' build (exit 0) and '@objectstack/rest^...' build (exit 0). spec: check:generated exit 0 ('All 15 generated artifacts are up to date'); tsc --noEmit exit 0; check:test-typecheck exit 0 (54 files / 261 errors ledger held — the @ts-expect-error reverse pin compiled); vitest api/analytics + contracts/analytics-service + kernel/metadata-create-seeds: 3 files, 50 passed. service-analytics: typecheck exit 0; vitest query-dataset.test.ts 32 passed (+5 new: label-less count => 'count' and no label; authored 'Tasks' => no discriminator; locale-map label unresolved for locale 'fr' => no discriminator; sum/avg/count_distinct verbatim; derived => none). rest: typecheck (incl. test layer) exit 0; vitest analytics-routes.test.ts 14 passed (+1 passthrough). downstream-contract: typecheck exit 0, test 2 files / 19 passed. Reverse verification AFTER commit 105ec6c1b: scratch file in service-analytics assigning builtinAggregate: 'total' against the REBUILT dist d.ts => tsc exit 1, TS2322 naming the closed union; file removed, git diff HEAD empty. Gates derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (60 commands, all accounted for): spec check:api-surface/authorable-surface/docs/export-origins/liveness/strictness-ledger/skill-refs via check:generated exit 0; spec exported-any, dual-source-exports, empty-state, variant-docs, yaml-examples, llms-txt, entry-nameability, browser-reachable-entries, liveness all exit 0; root nul-bytes, cross-package-test-inputs, test-source-alias, type-check-coverage, spec-parsed-alias, changeset-gate-self-tests, objectui-changeset, published-files, doc-authoring, query-options-erasure, dispatcher-error-vocabulary, engine-double-contract, merge-driver, page-declaration-shape, slot-lookup, where-matcher, watch-hint-literal, refd-timer-probe, logger-receiver-detach, objectql-double-limit, type-source-resolution all exit 0; node scripts adr-0087-registration, changeset-no-major, ci-filter-parity, closing-keyword-parity, comment-mask-adoption, comment-mask-corpus, empty-changeset, keyed-text-bounds, plugin-teardown-shape, shard-attestation, system-context-census, tenant-audit-census, undeclared-dep-imports, docs-audit/check-affected-docs, docs-audit/check-drift-comment, pm/release-rehearsal-clone --self-test, lint check:doc-formula-expressions all exit 0. NOT MEASURED by their own verdict text (PREREQUISITE NOT MET, need whole-tree build / turbo test log): check:type-check-debt (3), check:dual-build-cjs-loads (3), check-dev-prereqs (1), check-test-completeness (3). Not run locally (network board sweeps): check:pm-half-states, scripts/pm/check-half-states.mjs. Head for every run: 105ec6c1b. CI: ALL TERMINAL on head 105ec6c1b — pass=30, skipping=5; 0 fail/cancel (skipped = Console Pin Gate, Build Docs, Packed-tarball smoke and the two other path-filtered jobs). Watched via a single until-condition wait (harness Monitor; foreground sleep is blocked here and macOS has no timeout binary); the monitor exited on ALL TERMINAL. Draft NOT flipped, nothing merged. Worktree cleaned up after the report (node_modules removed, git worktree remove without force).",
"mcp_calls": "0 — REST (gh api) and git only for the whole run",
"open_questions": [
{
"question": "Should the Cube metric (MetricSchema, strict + authorable via defineCube/StackSchema) ALSO carry builtinAggregate so inferMeasure x2 and cube-registry inferFromObject populate it literally as the ruling's site list reads?",
"options": [
"A — No (implemented): the discriminator lives only on the response, derived from 'aggregate present and label absent' on the dataset measure; the Cube-metric sites never reach fields[] and getMeta already exposes CubeMeta.measures[].type.",
"B — Yes: widen MetricSchema with an optional builtinAggregate, populate the three Cube sites, and add a query()-path enrichment that copies cube metric label + builtinAggregate onto fields[] (new wire behaviour on /analytics/query, plus gen:schema/docs/authorable-surface regen)."
],
"recommendation": "A. Long-term soundness: item 1 of the ruling names AnalyticsResult.fields[] as the contract change and a response-only member cannot be authored wrong; B declares an authorable key that no wire path reads today (check:liveness territory) and lets a metadata author or an AI stamp builtinAggregate beside a custom label, exactly the consumer-side tolerance the ruling refused. Real need: the measured producer of the reported chart is a dataset measure (cloud dataset-derive.ts), served by A. Scope: A is the smaller change. If the maintainer wants B, it is a separate card on top of this one, not a rework of it."
}
],
"out_of_scope_findings": [
"filed as objectstack-ai/cloud#1933 (sub-issue of cloud#1841, Blocked-by objectstack#14492 + pin bump): dataset-derive.ts:206 authors label: 'Count' on the AI-derived count measure, so the reported chart still carries an authored English label until that one line is dropped.",
"filed as #15019 (finding): CubeRegistry.inferFromObject has no runtime caller on origin/main (only a unit test), so ruling site 3 (cube-registry.ts:80) is dead code and its composite LABEL (Sum) / LABEL (Avg) English defaults are unreachable; delete-vs-wire left to the maintainer."
]
}
PM ruling on the seat's open question: A (as implemented). The discriminator is a response-only member derived from "aggregate declared, no authored label" on the dataset measure; the three Cube-metric sites never reach fields[] and CubeMeta.measures[].type already exposes the aggregate on the meta path. Widening the authorable MetricSchema (B) would declare a key no wire path reads and let an author stamp it beside a custom label — the consumer-side tolerance ruling B refused. If the maintainer later wants the cube query() path to carry it, that is a separate card on top of this one. objectstack#15019 (dead inferFromObject) stays with the maintainer.
Activity
hotlong commentedon Sep 3, 2026
Claim: epic cloud#1841 lane — spec seat taken over cross-lane by the epic PM per the owner's 2026-09-04 authorization (「你可以跨车道处理 epic 所有任务」)
Session: wonderful-driscoll-ac0895 (cloud PM session, Fable 5.1)
Branch: feat/analytics-builtin-aggregate-14492
Worktree: ~/Documents/GitHub/objectstack-issue-14492 (linked worktree off origin/main; primary checkout untouched)
Domain: spec (contracts/analytics-service) + service-analytics + kernel seeds + qa/downstream-contract fixtures
File surface: packages/spec/src/contracts/analytics-service.ts (+ zod/tests), packages/services/service-analytics/src/analytics-service.ts (inferMeasure ×2), packages/services/service-analytics/src/cube-registry.ts (inferFromObject), packages/spec/src/kernel/metadata-create-seeds.ts, packages/qa/downstream-contract/src/additional-domains.fixtures.ts, .changeset/*
Container & model: general-purpose subagent reading .claude/agents/os-dev.md, inherit tier
Clause-②: yes
Clause-② rationale: public spec surface grows by one optional field; contract review in-seat at fable tier (cloud scripts/pm/check-clause2-carriers.mjs --pair) before ACCEPT; consumer half = objectui#7258
Serial constraints: none on shared pin files; downstream cloud pin bump follows after merge (cloud#1841 lane)
Generated by Claude Code
feat(spec,analytics): AnalyticsResult.fields[].builtinAggregate discr…
hotlong commentedon Sep 3, 2026
ACCEPT — PR #15017 verified in-seat: 9 files, all inside the Claim's surface (spec contract + zod mirror + seeds + service-analytics queryDataset + tests + changeset); all check runs terminal with 0 failures at 105ec6c; clause-② review PASS posted on the PR and the
needs:contract-reviewgate cleared on both carriers (predicate: legible, carriers agree). Marked ready and enqueued (--auto --squash). Downstream: cloud.objectstack-shabump (third in epic cloud#1841) → cloud#1933 label drop → objectui#7535 ready → cloud.objectui-shabump._Generated by Claude Code
hotlong commentedon Sep 3, 2026
os-dev-report
{ "issue": 14492, "status": "done", "branch": "feat/analytics-builtin-aggregate-14492", "pr": "https://github.com/objectstack-ai/objectstack/pull/15017", "premise_still_valid": true, "summary": "Ruling B implemented: AnalyticsResult.fields[] gains optional builtinAggregate (reusing the closed AggregationFunction enum from data/query.zod.ts), mirrored in AnalyticsResultResponseSchema (api/analytics.zod.ts — required by the spec's own compile-time parity guard), populated by queryDataset's measure enrichment exactly when the dataset measure declares an aggregate and NO authored label (judged on the authored key, so an unresolved locale-map label is still an author's label), and the dataset create seed drops its hardcoded label: 'Count' so a Studio-created dataset is a built-in default on the wire. Premise PARTIALLY refuted and stated in the PR body: fields[].label has exactly one producer (that enrichment, from the dataset measure), so the three Cube-metric sites (inferMeasure x2, cube-registry inferFromObject) never reach fields[] — they surface only via getMeta().title where CubeMeta.measures[].type already carries the aggregate — and populating them would mean widening the strict authorable MetricSchema with a key nothing reads; left untouched. The downstream-contract fixture is a Cube fixture with nothing to mirror (suite run green). The AI dashboard's actual 'Count' producer is cloud's dataset-derive.ts:206 (authored label), filed as cloud#1933 under epic #1841. Footer conflict: dispatch asked for the claude.com line, the role file for a session-URL footer — PR body carries both (bare form; session id not known to this seat). Verify lock reports no flock on this macOS host, so heavy runs went direct.", "tests": "Built closures first: pnpm --filter '@objectstack/service-analytics^...' build (exit 0) and '@objectstack/rest^...' build (exit 0). spec: check:generated exit 0 ('All 15 generated artifacts are up to date'); tsc --noEmit exit 0; check:test-typecheck exit 0 (54 files / 261 errors ledger held — the @ts-expect-error reverse pin compiled); vitest api/analytics + contracts/analytics-service + kernel/metadata-create-seeds: 3 files, 50 passed. service-analytics: typecheck exit 0; vitest query-dataset.test.ts 32 passed (+5 new: label-less count => 'count' and no label; authored 'Tasks' => no discriminator; locale-map label unresolved for locale 'fr' => no discriminator; sum/avg/count_distinct verbatim; derived => none). rest: typecheck (incl. test layer) exit 0; vitest analytics-routes.test.ts 14 passed (+1 passthrough). downstream-contract: typecheck exit 0, test 2 files / 19 passed. Reverse verification AFTER commit 105ec6c1b: scratch file in service-analytics assigning builtinAggregate: 'total' against the REBUILT dist d.ts => tsc exit 1, TS2322 naming the closed union; file removed, git diff HEAD empty. Gates derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (60 commands, all accounted for): spec check:api-surface/authorable-surface/docs/export-origins/liveness/strictness-ledger/skill-refs via check:generated exit 0; spec exported-any, dual-source-exports, empty-state, variant-docs, yaml-examples, llms-txt, entry-nameability, browser-reachable-entries, liveness all exit 0; root nul-bytes, cross-package-test-inputs, test-source-alias, type-check-coverage, spec-parsed-alias, changeset-gate-self-tests, objectui-changeset, published-files, doc-authoring, query-options-erasure, dispatcher-error-vocabulary, engine-double-contract, merge-driver, page-declaration-shape, slot-lookup, where-matcher, watch-hint-literal, refd-timer-probe, logger-receiver-detach, objectql-double-limit, type-source-resolution all exit 0; node scripts adr-0087-registration, changeset-no-major, ci-filter-parity, closing-keyword-parity, comment-mask-adoption, comment-mask-corpus, empty-changeset, keyed-text-bounds, plugin-teardown-shape, shard-attestation, system-context-census, tenant-audit-census, undeclared-dep-imports, docs-audit/check-affected-docs, docs-audit/check-drift-comment, pm/release-rehearsal-clone --self-test, lint check:doc-formula-expressions all exit 0. NOT MEASURED by their own verdict text (PREREQUISITE NOT MET, need whole-tree build / turbo test log): check:type-check-debt (3), check:dual-build-cjs-loads (3), check-dev-prereqs (1), check-test-completeness (3). Not run locally (network board sweeps): check:pm-half-states, scripts/pm/check-half-states.mjs. Head for every run: 105ec6c1b. CI: ALL TERMINAL on head 105ec6c1b — pass=30, skipping=5; 0 fail/cancel (skipped = Console Pin Gate, Build Docs, Packed-tarball smoke and the two other path-filtered jobs). Watched via a single until-condition wait (harness Monitor; foreground sleep is blocked here and macOS has no timeout binary); the monitor exited on ALL TERMINAL. Draft NOT flipped, nothing merged. Worktree cleaned up after the report (node_modules removed, git worktree remove without force).", "mcp_calls": "0 — REST (gh api) and git only for the whole run", "open_questions": [ { "question": "Should the Cube metric (MetricSchema, strict + authorable via defineCube/StackSchema) ALSO carry builtinAggregate so inferMeasure x2 and cube-registry inferFromObject populate it literally as the ruling's site list reads?", "options": [ "A — No (implemented): the discriminator lives only on the response, derived from 'aggregate present and label absent' on the dataset measure; the Cube-metric sites never reach fields[] and getMeta already exposes CubeMeta.measures[].type.", "B — Yes: widen MetricSchema with an optional builtinAggregate, populate the three Cube sites, and add a query()-path enrichment that copies cube metric label + builtinAggregate onto fields[] (new wire behaviour on /analytics/query, plus gen:schema/docs/authorable-surface regen)." ], "recommendation": "A. Long-term soundness: item 1 of the ruling names AnalyticsResult.fields[] as the contract change and a response-only member cannot be authored wrong; B declares an authorable key that no wire path reads today (check:liveness territory) and lets a metadata author or an AI stamp builtinAggregate beside a custom label, exactly the consumer-side tolerance the ruling refused. Real need: the measured producer of the reported chart is a dataset measure (cloud dataset-derive.ts), served by A. Scope: A is the smaller change. If the maintainer wants B, it is a separate card on top of this one, not a rework of it." } ], "out_of_scope_findings": [ "filed as objectstack-ai/cloud#1933 (sub-issue of cloud#1841, Blocked-by objectstack#14492 + pin bump): dataset-derive.ts:206 authors label: 'Count' on the AI-derived count measure, so the reported chart still carries an authored English label until that one line is dropped.", "filed as #15019 (finding): CubeRegistry.inferFromObject has no runtime caller on origin/main (only a unit test), so ruling site 3 (cube-registry.ts:80) is dead code and its composite LABEL (Sum) / LABEL (Avg) English defaults are unreachable; delete-vs-wire left to the maintainer." ] }Generated by Claude Code
hotlong commentedon Sep 3, 2026
PM ruling on the seat's open question: A (as implemented). The discriminator is a response-only member derived from "aggregate declared, no authored label" on the dataset measure; the three Cube-metric sites never reach
fields[]andCubeMeta.measures[].typealready exposes the aggregate on the meta path. Widening the authorableMetricSchema(B) would declare a key no wire path reads and let an author stamp it beside a custom label — the consumer-side tolerance ruling B refused. If the maintainer later wants the cubequery()path to carry it, that is a separate card on top of this one. objectstack#15019 (deadinferFromObject) stays with the maintainer.Generated by Claude Code
feat(spec,analytics): AnalyticsResult.fields[].builtinAggregate discr…
fix(scripts): check-issue-citations reads a qualifier only when it na…