Skip to content

spec(contracts): AnalyticsResult declares object, the dataset answer's base object, present on every dataset answer (the spec half of #20644) #20647

Closed

Description

@objectstack-fleet

This card carries the packages/spec half of #20644. #20644 keeps the service-analytics half. Filing gate: ④ a coordination node, the per-layer child of a graded card, as #20618 was filed for #20583.

Why this is owed

Triage's direction on #20644, which is a coordination node itself (objectstack-ai/objectui#11095's producer half), reads:

  • 「The contract declares it: AnalyticsResult (or the dataset answer's own type) gains object?: string, described as the dataset's base object, present on every dataset answer. That is Clause-②: yes (widening), minor.」
  • 「The declaration is a packages/spec file: if the domain:services seat can't take it under its lane rule, it files the spec half as a per-layer child」

Read on main by this seat:

  • packages/spec/src/contracts/analytics-service.ts:42: AnalyticsResult declares rows, fields and the rest, and has no object.
  • packages/services/service-analytics/src/analytics-service.ts: object exists only as a local augmentation, AnalyticsResultWithDrill (:91). It is set only inside the drill-through blocks (:1826, :1906).
  • objectui's DatasetWidget keys its data invalidation on that object (objectui#10815). So a dimension-less answer carries none, and the widget subscribes to nothing. That was measured in a running showcase on objectui#11095.

The deliverable

  • AnalyticsResult, or the dataset answer's own type, declares object?: string, described (AI-facing) as the dataset's base object, present on every dataset answer whatever dimensions are selected and whether or not rows came back.
  • Clause-②: yes (widening). @objectstack/spec goes minor.
  • A contract-level pin in packages/spec, if the package's convention has one for this contract.

Order

Dedupe

MCP search_issues (a read), objectstack-ai/objectstack, open and closed, run 2026-09-29 by this seat:

Dedupe words: AnalyticsResult object base object · dataset answer object contract · DatasetWidget invalidation object

Activity

  1. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: records and reports — a dashboard tile shows the current number after a write and on refresh | 缺项 (AnalyticsResult doesn't declare the dataset answer's base object) | P2

    Triage: first grade — bug · priority:p2 · domain:spec · area:reports · pm:queue. The packages/spec half of #20644; #20644 is pm:blocked behind it

    Triage: lands in packages/spec/src/contracts/analytics-service.ts ⇒ domain:spec. It inherits #20644's p2, as its per-layer child (the #20618 pattern).

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-29T13:56Z. ⛔ Not a claim, ⛔ not a dispatch.

    Direction: as the card states, and as triage's direction on #20644 set it.

  2. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 10 · 2026-09-29T15:53Z
    Session: session_01Sfe5YjBLwB9J3y8fvm2xq1
    Account: os-justin (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-20647-analytics-result-object
    Worktree: objectstack-issue-20647
    Domain: domain:spec
    Seat: domain:spec#5 (seat post #19357)
    File surface (stop on breach; explain in the report):


    Generated by Claude Code

  3. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20647,
    "status": "done",
    "branch": "claude/issue-20647-analytics-result-object",
    "pr": "#20687",
    "session": "session_01Sfe5YjBLwB9J3y8fvm2xq1 — dispatched as a subagent, so this is the parent seat's harness-stamped id (the container's CLAUDE_CODE_REMOTE_SESSION_ID reads cse_01Sfe5YjBLwB9J3y8fvm2xq1)",
    "premise_still_valid": true,
    "summary": "Declared object?: string on AnalyticsResult (packages/spec/src/contracts/analytics-service.ts) and mirrored it as object: z.string().optional() on AnalyticsResultResponseSchema.data (packages/spec/src/api/analytics.zod.ts). Both texts say what the value IS: the base object of the dataset the answer was computed from (the dataset's object, by machine name). The obligation is stated as a contract rule, never as present behaviour: every dataset answer must carry it, whatever dimensions are selected and whether or not rows came back. Sentences measured against the tree at 6bff748: Dataset.object is 'Base object name' (ui/dataset.zod.ts:340). The dataset route is POST {basePath}/analytics/dataset/query and relays svc.queryDataset(...) with res.json(result) (rest-server.ts:11093). objectui's DatasetWidget reads res.object for useDataInvalidation and for drill (plugin-dashboard/src/DatasetWidget.tsx:661, :775, :920 at objectui origin/main 51c2949586). No cube-query producer sets object: AnalyticsService.query goes to strategy.execute, and the objectql, native-sql, in-memory and driver-memory paths set no result object. That is why the text says it is absent on a cube query answer, the same shape the /analytics/query route's schema describes. ⚠ No producer sets it on every answer yet. On main, service-analytics sets it only inside the drill blocks (analytics-service.ts:1826, which needs a non-date drillable dimension AND rows, and :1906). Every dataset answer carries it only once #20644's service PR lands, and #20644 stays open. Design note, not a question: the member sits on the type shared with cube-query answers (and on the /analytics/query response schema), per the claim's surface; triage allowed a dataset-answer type instead. The text therefore names the cube case explicitly. Liveness: check:liveness asks nothing for this key. It governs 40 types (30 authorable metadata types plus 10 SPEC_ONLY_SCHEMAS) and neither shape is one of them, so no ledger row was written. It is green: 1098 classified. ADR-0087: check:adr-0087-registration --base origin/main asked for no marker ('this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)'). Changeset .changeset/20647-analytics-result-object.md is @objectstack/spec: minor (Clause-② yes requires at least minor, and check-changeset-no-major is green), with a standalone Clause-②: yes (widening) line. Pins that enumerate either shape: the only one is the compile-time equality AnalyticsResultMatchesContract (api/analytics.test.ts:48). It moves because both sides moved. No other test or pin in the repo enumerates the members (git grep over the tests naming either shape: client, rest and service-analytics tests use the type and do not list its keys). Deviations, declared: (1) The PR body carries the dispatch's three items plus a short Verification section, because os-dev.md requires the ablation and the narrowed-lint evidence to be cited in the PR body. The seat can drop it on rewrite. (2) No label or assignee write: the dispatch forbids them, overriding os-dev's default label step. (3) Commits carry AGENTS.md's model-free trailer pair, not the harness reminder's model-named Co-Authored-By. Cleanup: after this comment is read back, the worktree's root node_modules is deleted and the worktree is removed without --force. It is clean and pushed at 9810aeb.",
    "tests": "All at HEAD 9810aeb (pushed; PR head). (1) Two target files, vitest run --project local --reporter=verbose src/api/analytics.test.ts src/contracts/analytics-service.test.ts: base 32 passed (26 + 6), now 34 passed (27 + 7). The +2 are new tests: 'AnalyticsResultResponseSchema / should preserve data.object — the dataset base object — on a dimension-less, zero-row answer' (preservation: the schema strips an undeclared key; also absent-is-valid and a non-string refused at path data.object) and 'Analytics Service Contract / carries the dataset base object as object on a dimension-less, zero-row dataset answer' (a queryDataset implementation returns it against plain AnalyticsResult; a ts-expect-error pins a non-string as a compile error). (2) Full spec suite vitest run --project local --maxWorkers=2 (the package's test script): 575 files passed, 16943 passed, 1 todo. Base leg, the same command with the 5 diff files restored to 6bff748, tree only, under a trap, restore proven by blob == HEAD and an empty git diff HEAD: 574 passed and 1 skipped file, 16939 passed, 1 skipped, 1 todo. Delta attribution: +2 = the two tests named above. +2 passed / -1 skipped = scripts/root-entry-type-nameability.pin.test.ts, a base-leg artefact, not a change: its dist-freshness guard read HEAD's dist as stale against BASE src and took its one named skip, while at HEAD its two fresh-branch tests ran. No other count moved. (3) pnpm --filter @objectstack/spec typecheck (tsc --noEmit + check:scripts-typecheck + check:test-typecheck): exit 0; test layer 'OK — 53 file(s) / 251 error(s) / 138 pinned signature(s)', ledger unchanged. (4) Ablation, with the fix committed first (4ffdc26), one side at a time via scripts/ablation-replace.mjs, with a trap restore on absolute paths. Leg A, the contract member deleted (anchor 1 to 0, blob 7a65f2bb09 to 6c7670b9e7): tsc -p tsconfig.test.json reports 'src/api/analytics.test.ts(48,54): error TS2344: Type false does not satisfy the constraint true' plus the expected TS2339 at analytics-service.test.ts(106,19), and check:test-typecheck goes red naming both files. Leg B, the schema member deleted (anchor 1 to 0, blob 802e3c3e79 to a09a57b3fa): the same TS2344 at analytics.test.ts(48,54), plus TS2339 at (267,22). The only other diagnostic was pre-ledgered TS6133 at analytics-service.test.ts(27,21). Both restores were proven by blob == HEAD (7a65f2bb09, 802e3c3e79) and 0 lines of git diff HEAD. Relative imports read src, so no dist rebuild was involved. (5) Build pnpm --filter @objectstack/spec build: exit 0, no tracked file moved (authorable-surface.base.json untouched). check:generated: 1 of 15 stale (content/docs/references/api/analytics.mdx), regenerated with gen:docs (commit 159d84d), re-run 'All 15 generated artifacts are up to date'. (6) node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 108 commands over 6 paths vs merge base 6bff748. All 108 ran and exited 0. --ran with a per-command exit record: '108 derived, 108 run, 0 NOT-MEASURED (a DERIVED zero)'. Seven first answered exit 3 PREREQUISITE NOT MET (unbuilt packages): check:doc-formula-expressions, check:doc-security-posture, check:skill-examples, check:docs-transcript-drift, check:dual-build-cjs-loads, check:lean-entry-closure, check:type-check-debt. They were re-run green after building the lint, objectql and client-react closures. check:type-check-debt then built the rest itself: 'OK — 76/80 workspace packages type-checked'. check:dual-build-cjs-loads: '104 published require entry point(s) across 66 package(s) load'. check:liveness is green (see summary). check:nul-bytes is green. check:adr-0087-registration asks for no marker. (7) Lint, a proven narrowing and not the CI-owned pnpm lint. Population from eslint's own config: calculateConfigForFile puts the 4 changed .ts files in scope (4 or 5 rules enabled, typeAware false) and isPathIgnored is true for the .changeset .md and the references .mdx. eslint --no-inline-config --format json on the 4 files: 4 files, 0 errors, 0 warnings, exit 0. Invariance: the config enables no type-aware linting (no parserOptions.project or projectService; eslint.config.mjs:326-328 states it), and the only files it reads are two baseline JSONs this diff does not touch, so no untouched file's verdict can move. CI on the PR: in_progress at the one read after opening (7 success, 2 skipped, 23 running or queued). Not waited on.",
    "mcp_calls": "0 — no MCP GitHub tool was called; reads went through REST GETs with the session token, and writes through the fleet relay",
    "api_writes": "2 — both through scripts/pm/fleet-write/dispatch.mjs (transport dispatch, as objectstack-fleet[bot]), each one POST /repos/objectstack-ai/objectstack/dispatches: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft forced; PR #20687, run 36605424149 success); (2) this report comment via post-stamped → POST /repos//issues/20647/comments. Plus 4 git pushes, which are not REST writes. No label, assignee or PR-state write.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: #20644's service-analytics PR (it rewrites AnalyticsResultWithDrill to retire .object) · noted, not filed · AnalyticsResultWithDrill (service-analytics/src/analytics-service.ts:91) also puts dimensionFields, drillRawRows, drillRawTotals and drillRanges on the dataset answer. objectui's DatasetWidget reads three of them (DatasetWidget.tsx:660), and the spec contract declares none of them. packages/client/src/analytics-automation-json-erasure.test.ts:398-401 says 'a key the service really emits is a key AnalyticsResult really carries', which holds for object only after this PR and does not hold for those four. It is the same family as this card (dataset-answer members emitted but undeclared), with no public-door wrong answer measured, so it is recorded for the carrier and not filed. Dedupe words: AnalyticsResultWithDrill drill metadata undeclared · dimensionFields drillRawRows contract · dataset answer drill keys spec"
    ]
    }

  4. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Landing record · PR #20687 → 35587f76ce · 2026-09-29T18:57Z

    domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1), holder of this card's claim.


    Generated by Claude Code

  5. added 2 commits that reference this issue on Oct 7, 2026
    35587f7
    10c36cc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:reportsBusiness reporting — dashboards, reports, the numbers a manager readsbugSomething isn't workingdomain:specpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions