Skip to content

feat(spec): AnalyticsResult declares object, the dataset answer's base object - #20687

Merged
os-justin merged 3 commits into
mainfrom
claude/issue-20647-analytics-result-object
Sep 29, 2026
Merged

os-justin merged 3 commits into
mainfrom
claude/issue-20647-analytics-result-object

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Closes #20647

AnalyticsResult and AnalyticsResultResponseSchema.data declare an optional object: string, the base object of the dataset an answer was computed from. This is the declaration only; setting it on every dataset answer is #20644's service change.

Clause-②: yes (widening)

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1

…e object

AnalyticsResult (contracts) and AnalyticsResultResponseSchema (api) both
gain the optional member `object: string`: the base object of the dataset
an answer was computed from. The contract asks every queryDataset answer
to carry it, whatever dimensions are selected and whether or not rows came
back; a cube query answer has no dataset behind it and carries none.

Declaration only. Pins: the response schema preserves data.object on a
dimension-less, zero-row answer and refuses a non-string; a queryDataset
implementation returns it against the plain AnalyticsResult.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
Generated by `pnpm --filter @objectstack/spec gen:docs`, the one artifact
`check:generated` named stale after the response schema gained `object`.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
Clause-② is yes (widening): AnalyticsResult and its response schema gain
one optional member.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests tooling labels Sep 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 2 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))

⛔ 2 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/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
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 137 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 1a75e39d4a0cd177b6fbef4348da52c5a67407b0 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 61bb4c1d87b6cc284c1eca41eb8ffff36dfe2ae7 — the merge of head 9810aebb1391d05d70adc8b17d7a7dccdc07ffa9 into base 1a75e39d4a0cd177b6fbef4348da52c5a67407b0, 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 61bb4c1d87b6cc284c1eca41eb8ffff36dfe2ae7 && git checkout 61bb4c1d87b6cc284c1eca41eb8ffff36dfe2ae7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1a75e39d4a0cd177b6fbef4348da52c5a67407b0 9810aebb1391d05d70adc8b17d7a7dccdc07ffa9 && git checkout -B drift-repro 1a75e39d4a0cd177b6fbef4348da52c5a67407b0 && git merge --no-ff 9810aebb1391d05d70adc8b17d7a7dccdc07ffa9

node scripts/docs-audit/affected-docs.mjs --json 1a75e39d4a0cd177b6fbef4348da52c5a67407b0

⚠️ 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 1a75e39d4a0cd177b6fbef4348da52c5a67407b0 → 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: 9810aebb1391d05d70adc8b17d7a7dccdc07ffa9
Local-runs: none

Inputs: card #20647 (body and all 3 comments: triage 5891768391, claim 5893775645, os-dev-report 5895355316), PR #20687 (body, 6-file list, net diff against main at the merge base 6bff748bbd), the head's check-runs, git show / git grep on origin/main and the branch, and the bodies of #20644 and objectui#11095 to source two sentences.

① Derived judgments

Accept-set and public-surface changes the diff implies

  1. AnalyticsResult (@objectstack/spec/contracts; api-surface row AnalyticsResult (interface)) gains object?: string. Type-level widening: every implementer and literal on main still compiles, and a reader gets a typed object on any AnalyticsResult, the shared query() (cube) return included. Right. The api-surface / declaration-map / export-origins artifacts list names only, so nothing else was owed there.
  2. AnalyticsResultResponseSchema.data (@objectstack/spec/api; registered as the response of POST /api/v1/analytics/query in DEFAULT_ANALYTICS_ROUTES, plugin-rest-api.zod.ts) gains object: z.string().optional(). Accept set, measured against strip mode (BaseResponseSchema is a plain z.object at contract.zod.ts:312, .extend keeps strip, data carries no passthrough): absent, unchanged; a string, was accepted-and-stripped and is now accepted-and-preserved; a non-string, was accepted-and-stripped and is now refused at data.object. That last movement is the one refusal the diff adds. It reaches only a payload no producer emits: AnalyticsService.query goes queryIn then strategy.execute, and neither strategy file, driver-memory/src/memory-analytics.ts, nor analytics-service.ts outside its two drill sites writes an object key. The repo's own precedent for this exact shape (changeset e22158f, which declared the formerly-stripped fields[].label / format / currency / percentScale / totals) graded it a widening. Right under yes (widening); named here because the arm is a repo convention for a formerly-stripped key rather than a literal reading of the accept set.
  3. The compile-time equality AnalyticsResultMatchesContract (api/analytics.test.ts:48) is the only pin that enumerates either shape (no keyof AnalyticsResult or satisfies AnalyticsResult anywhere in packages/), and it holds because both sides moved in one commit. Right.
  4. AnalyticsResultResponse / AnalyticsResultResponseParsed gain data.object? by derivation. Additive. Right.
  5. content/docs/references/api/analytics.mdx is the AUTO-GEN row of the ownership table, regenerated by gen:docs (commit 159d84d8b0); the data summary cell's new trailing … is the generator's own truncation once a fifth member exists. check:generated sits inside the green TypeScript Type Check job. Right.
  6. Liveness: neither shape is an authorable metadata type, so no ledger row; Spec property liveness green. Right.
  7. Two new tests. api/analytics.test.ts: preservation of data.object on a dimension-less, zero-row payload, absence still parses, a non-string is refused at data.object. contracts/analytics-service.test.ts: a queryDataset implementation returns object against the plain AnalyticsResult, and a @ts-expect-error on object: 42. The file is inside tsconfig.test.json's include: ["src/**/*"], so that marker is compiled (the debt ledger holds exactly one pre-existing TS6133 for the file under the exact ratchet; Type Check · debt ledger green). Note the marker pins the member's TYPE only, since an excess property on a removed member would also error; the existence pin is the answer.object read in the same test, which is TS2339 without the member. Right.

Every author-shown or AI-facing sentence, tested against the tree

Contract TSDoc (contracts/analytics-service.ts:135-148):

Schema .describe() (api/analytics.zod.ts:145-151), which is also the generated mdx row:

Test comments: "this schema strips an undeclared key, so an undeclared object would parse green and vanish" — true (strip mode, item 2). "declared on the answer itself and not on a drill-through side type" — true after this PR; on main it lives only on the local AnalyticsResultWithDrill (analytics-service.ts:96; the card says :91, a line drift since triage, immaterial).

Changeset .changeset/20647-analytics-result-object.md:

PR body: "declare an optional object: string, the base object of the dataset an answer was computed from" — true. "This is the declaration only; setting it on every dataset answer is #20644's service change." — true, and it is triage 5891768391's ⛔ declaration-only line restated. Clause-②: yes (widening) matches the changeset. The card claims this branch (The card this PR closes must claim this branch green). The dev report's deviation (1) describes a Verification section that the body read at review time does not carry; nothing to act on.

② Semver level

The diff moves published source in one package only, @objectstack/spec (packages/spec/src/**); content/docs and .changeset publish nothing. One changeset, @objectstack/spec: minor. Clause-②: yes (widening): yes because two exported shapes change; (widening) because nothing an implementer or a producer emits today is refused, and the one added refusal (a non-string data.object, item ① 2) hits a payload no producer emits, on the precedent the repo already set for a formerly-stripped key. yes takes at least minor and this is minor; Check Changeset green; the dev reports check:adr-0087-registration asked for no marker (a non-breaking changeset), inside the green Lint & Repo Gates. No skip-changeset. The changeset matches what the diff publishes, and the Clause-②: line in the changeset and the PR body agree.

③ Boundary flags

Dev report 5895355316 on the card:

Check-runs on 9810aebb1391d05d70adc8b17d7a7dccdc07ffa9, read last: 46 raw, 35 after dedupe by name keeping the newest started_at. 31 success; 4 skipped (Auto Label, Check PR Size, Console Pin Gate, Packed-tarball smoke (opt-in)); 0 failure; 0 in progress or queued. Legacy status: Vercel success. None still running.

Implemented-by: claude/issue-20647-analytics-result-object
Reviewed-by: session_01Sfe5YjBLwB9J3y8fvm2xq1

VERDICT: PASS

Adopted and posted by domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1) · 2026-09-29T18:26Z · rendered by the seat's at-tier review subagent on this head. The seat read its served tier family from the subagent transcript before posting.


Generated by Claude Code

@os-justin
os-justin marked this pull request as ready for review September 29, 2026 18:32
@os-justin
os-justin enabled auto-merge September 29, 2026 18:32
@os-justin
os-justin added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 35587f7 Sep 29, 2026
51 checks passed
@os-justin
os-justin deleted the claude/issue-20647-analytics-result-object branch September 29, 2026 18:56
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/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants