Skip to content

fix(service-analytics): an inferred cube lives only in its request — no request writes the shared cube registry (#20381) - #20433

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-20381-retire-inferred-cube-source
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-20381-retire-inferred-cube-source

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20381
Clause-②: no

Item 3 of #20381, under director ruling 5866558247 (letter A, maintainer 「同意」): registry source 3 is retired. Items 1–2 landed in PR #20407 (50e273fd), so this round completes the card.

What changes

Landing point, as dispatched: packages/services/service-analytics/src/analytics-service.ts (the producer of the write) and cube-registry.ts (docblock only).

Tests: re-observed, not deleted

On the fix commit, 36 cases in six service-analytics test files went red; each of those files read an inferred cube back through getMeta or the shared registry. PR #20348, which landed while this round ran, added a seventh such case. Each case is re-observed through a window that still exists after A: the cube the request's own strategies are handed. A probe strategy placed ahead of the built-in ones records ctx.getCube(query.cube) and always declines, so the chain runs as it would without the probe. Where an assertion's subject was the retired registration itself, the assertion now pins its absence.

File Was Now
infer-cube-where-spelling-parity.test.ts dimension keys via getMeta('deal') the same keys, read from the request's cube
infer-cube-relation-traversal.test.ts run() members via getMeta the request's cube
dotted-measure-refusal.test.ts run() measures via getMeta; block 2 case 1 asserted that the first query warmed the registry the request's cube; case 1 now pins that the first query warms nothing and that the second, cold again, is still refused (the augmentation site stays covered by the block's authored-cube case)
analytics-service.test.ts 'auto-infer' cubeRegistry.has('case') is true the request was handed a cube named case and backed by case; has('case') is false
cube-inference-gate.test.ts KPI case cubeRegistry.get('crm_account') is truthy it is undefined; a second request is served the same way and asks the existence gate again
adhoc-query-request-scope.test.ts (PR #20407) the admitted inference "publishes after admission (source 3)"; the CONTROL case runs "through the published cube" the admitted inference is served from its own cube, and both the registry and the observer's view are exactly unchanged (the order pin is kept); the CONTROL case is now "a second same-name request infers again and gets the same answer"
cube-public-visibility.test.ts (PR #20348) the ad-hoc KPI path's inferred cube is registered public: true and listed it is answered on every request, and never registered or listed

The route pin is packages/qa/dogfood/test/analytics-adhoc-query-isolation.dogfood.test.ts: bootStack with two sign-ups plus the administrator, on sqlite-wasm and memory, through both doors.

  • Both CONTROL legs are tightened from arrayContaining to exact equality on member B's cube list. B's meta is also kept as the raw response bytes.
  • After the administrator's admitted ad-hoc query over the walled object, B's meta is byte-identical and B's query of the configured cube is unchanged. B's own query of that object is still refused with the ADR-0112 envelope.
  • An admitted scalar metric is re-inferred on a second request and answers identically. Between the two requests, not even the asker's own meta lists the name.
  • A configured cube still serves: the baseline leg, and every observation.

Evidence (head 8214a5b6 unless stated)

  • pnpm --filter @objectstack/service-analytics typecheck is clean. vitest run: 132 files, 3093 passed. tsc --listFiles includes every changed test file.
  • pnpm --filter @objectstack/dogfood typecheck is clean, and --listFiles includes the route pin. The six analytics dogfood files: 54 passed, on a dist rebuilt after merging main.
  • Ablation M1 puts the publication back at both ad-hoc sites, after callCtx, as in 50e273fd. It was applied with scripts/ablation-replace.mjs (anchor 2 → 0) and predicted before running.
    • Unit, 7 files: 10 red / 132 green. The red cases are the admitted-inference and CONTROL cases (4 + 2), auto-infer, the KPI gate case, the feat(analytics): enforce analytics_cube.public and default it to visible #20348 KPI case and the dotted warm case.
    • Route, after rebuilding service-analytics, with ablation-dist-preflight finding the marker in 2 dist files: 8 red / 16 green, both CONTROL legs on all four boots. For example: expected [ 'open_summary', …(3) ] to deeply equal [ 'open_summary', …(2) ], with + "admission_walled".
    • Restore: blob equals HEAD, git diff HEAD is empty, rebuilt, and --absent preflight is green with a clean tree.
    • On the pre-merge head fccfc3e5 the same ablation gave 9/117 and 8/16.
  • Ablation M2, on fccfc3e5, proves the probe window can fail. It makes an array where seed no dimension, the pre-observation: inferCube 仍把数组 where 当「不是筛选」跳过 —— #5334 之后这个 !Array.isArray 守卫已经过时 #5353 shape. Across parity and traversal: 16 red / 24 green. In parity, the 11 conjunction table cases, ALONGSIDE and the two dotted array-versus-object cases went red; both $or cases stayed green, as the file predicts. In traversal, the two array-spelling mint cases went red. The restore was proven the same way.
  • Gates: dispatch-gates --commands derives 66 commands over the 12 changed paths. --ran reconciles 66 derived, 66 run, 0 NOT MEASURED and 0 UNRUN. 65 exit 0; check:empty-changeset exits 1 by design (see Changesets).
  • Lint, narrowed: eslint --no-inline-config --format json over the 10 changed .ts files reports 10 files, 0 errors and 0 warnings. The population is those 10 files, none ignored. Invariance: eslint.config.mjs never enables type-aware linting, so an untouched file's verdict cannot move. The full pnpm lint is left to CI.

Changesets

Overlap with PR #20348

PR #20348 landed first (f2c7eef5), and main is merged here (dfd185d7) with no textual conflict.

  • Its public: true on the inferred cube is moot under ruling A, because no visibility verdict ever reads that cube. The literal is kept: the pending note .changeset/20282-analytics-cube-public-enforced.md says the inferred cube "now writes true", and dropping the key would falsify a second foreign changeset. Only its comment, which said the cube is registered, is corrected.
  • Its generateSql gate still asks this.sharedScope rather than the call's scope. The answer is identical, because a fresh request scope with no dataset reads through to the shared registry, so this is noted, not changed.
  • Its other changes are untouched.

Acceptance notes

  • Log frequency: for a GROUPED ad-hoc query over an object with no configured cube, ensureCube's warn ("No cube registered …; auto-inferred a minimal cube …") used to fire once per name per process, because the second request found the published cube. It now fires on every such request; scalar metrics stay at debug. This was not measured against real dashboard traffic, and it is noted, not changed: the ruling adds no state.
  • content/docs/api/data-api.mdx, in its GET /analytics/meta section, says that a cube a query references "is lazily auto-inferred from that query's shape". It does not claim the cube gets listed, but it could now say that it does not. That file is outside this card's file surface.
  • Per the ruling, the two unmeasured cases (a cross-org boot, and an FLS-hidden field used as a dimension) cannot leak through meta for inferred cubes once this lands. They stay as notes for the ADR-0106 D5 audit.
  • The refusal tests that assert cubeRegistry.get(...) is undefined after a rejected query (the three source-field gate files, cube-inference-gate, dotted-measure-refusal) now hold by construction for every request, not only for refused ones. They are left as they are.

Generated by Claude Code

…no request writes the shared cube registry

The ad-hoc `query` and `sql` doors no longer publish the cube `ensureCube`
infers for an admitted request. It stays in the call's request scope and
is dropped with it, like a measure appended to a configured cube, so the
shared registry (and `getMeta`) holds configuration only: manifest cubes
and `registerDataset` datasets. The next request of the same name infers
again through the same gates.

`publishInferredCube` had no caller left and is removed; `ensureCube`
returns nothing again. The `CubeRegistry` class docblock now lists the
two configuration sources and states that no request writes it.

Claude-Session: https://claude.ai/code/session_017B6YKCGu8CTY2KBWgwaHAs
Co-authored-by: Claude <noreply@anthropic.com>
…pin that no request writes meta

The tests that read an ad-hoc request's inferred cube back through
`getMeta` or the shared registry now read it where it lives: the cube the
request's strategies are handed (a declining probe strategy records
`ctx.getCube`). The assertions whose subject was the retired registration
now pin its absence.

The route pin's two CONTROL legs compare member B's cube list for exact
equality, and B's `meta` as raw bytes: an admitted ad-hoc query over a
walled object leaves it byte-identical, and a second same-name request is
re-inferred and answers the same.

The new changeset records the retirement; the items 1-2 changeset no
longer says an admitted request's inferred cube is still listed.

Claude-Session: https://claude.ai/code/session_017B6YKCGu8CTY2KBWgwaHAs
Co-authored-by: Claude <noreply@anthropic.com>
…ol; index access for the es2021 lib

Claude-Session: https://claude.ai/code/session_017B6YKCGu8CTY2KBWgwaHAs
Co-authored-by: Claude <noreply@anthropic.com>
…s the retired registration

`cube-public-visibility.test.ts` asserted that an inferred cube is
registered with `public: true` and listed by `getMeta`. Under the
retirement it is answered on every request and never registered or
listed; the case now pins that. `inferCubeFromQuery` keeps its
`public: true` literal (moot, and the pending #20282 release note says it
is written) with a comment that no longer claims the cube is registered.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-analytics, touching 7 documentable anchor(s).

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

  • content/docs/releases/v14.mdx (via generateSql (symbol, a method of class AnalyticsService))
  • content/docs/releases/v17/17-5.mdx (via AnalyticsService (symbol, a top-level class))

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 — 9 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 e01d3473053d2d43f5fca0db98b0409744695a5a → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json e01d3473053d2d43f5fca0db98b0409744695a5a

⚠️ 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 e01d3473053d2d43f5fca0db98b0409744695a5a → 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: 8214a5b641abf70b0c875c846c583dd49ffc9d74
Local-runs: none

Inputs: card #20381 (body and all 11 comments, ruling 5866558247 letter A included), PR #20433 body, its 12-path file list, the net diff against main at the head above, and the head's check-runs read through REST. File contents were read with git show on a fetched review ref and on origin/main (e01d3473, the PR's base); nothing was built, run or re-run.

① Derived judgments

Each accept-set and public-surface change the diff implies, named and judged:

  1. The publication of an inferred cube is removed on both ad-hoc doors (analytics-service.ts): publishInferredCube had 6 occurrences on origin/main (2 call sites after callCtx in queryIn and generateSql, 1 definition, 3 doc mentions) and 0 at the head; ensureCube returns void and both callers discard nothing. The only registry writes left in the file are the constructor's sharedScope.register (configuration), registerDataset (cubeRegistry.register(compiled.cube), configuration), and ensureCube's two scope.register calls, which land in requestScope()'s per-call Map and never reach the shared registry. getMeta reads cubeRegistry alone. So the ruling's execution parameter — no request writes the registry; meta lists the authored vocabulary — is true at the head. Right.
  2. Accept set unchanged. No refusal, code or status is added, removed or reordered: assertCubePublic, the token resolver, ensureCube's existence and source-field gates, and callCtx admission run in the same order as on origin/main; only the post-admission publish is gone. An admitted ad-hoc request is served from the cube its own request scope holds, as before. Right, and the Clause-② answer no follows (see ②).
  3. Public surface unchanged. publishInferredCube, queryIn (its door parameter dropped) and ensureCube are all private; no export, no packages/spec change, no config key added. The tests' probe uses the pre-existing public AnalyticsServiceConfig.strategies knob (merged with the built-ins and sorted by priority, so a priority: 0 probe runs ahead of P1/P2/P3 and declines), and StrategyContext.getCube is the spec-declared contract read from the call's scope (cubeReads(scope) in callCtx). The observation window is a real one, not a test-only seam. Right.
  4. Behaviour that does move, and the diff says so: (a) a second same-name ad-hoc request re-infers through assertInferableCube and the field gates every time (pinned in cube-inference-gate.test.ts — the gate is asked twice — and in the route pin's re-inference CONTROL leg); (b) ensureCube's grouped-query warn fires per request instead of once per name per process (acceptance note; scalar metrics stay at debug); (c) getMeta / GET /api/v1/analytics/meta no longer carry an entry that depended on who queried what since boot. (c) is the defect's closure, (a) is the ruling's stated cost, (b) is a logging-frequency note. Right; none is a contract change.
  5. inferCubeFromQuery keeps public: true with a corrected comment. The pending foreign note .changeset/20282-analytics-cube-public-enforced.md states that the inferred KPI cube "now writes true" and stays visible; dropping the key would falsify that second pending note, and the ruling made removal optional ("may go"). At the head no visibility verdict ever reads the inferred cube (assertCubePublic runs before it exists; getMeta never sees it), so the literal is moot and harmless. Right.
  6. cube-registry.ts docblock: the class docblock now names two configuration writers and states that no request writes it; the inferFromObject method docblock's "three sources named on the class above" became "two" for consistency — one word outside the claim's "class docblock's source-3 sentence only" letter, but a comment that would otherwise contradict the class docblock. Right; noted, not a breach.
  7. Tests re-observed, none deleted (7 files — the six the fix reddened on 50e273fd plus cube-public-visibility.test.ts from PR feat(analytics): enforce analytics_cube.public and default it to visible #20348): each former getMeta/registry read of an inferred cube now reads the cube the request's strategies were handed, and where the assertion's subject was the retired registration it now pins absence (cubeRegistry.get(...) undefined, has false, getMeta() empty). The race pin ("loses the admission race to a registration") is kept and still meaningful. Right.
  8. Route pin (analytics-adhoc-query-isolation.dogfood.test.ts, 4 boots × 6 legs): both CONTROL legs tightened from arrayContaining to exact equality on B's cube list, plus raw-byte equality of B's meta; since Observation.meta now carries bytes, every toEqual(before) leg (the REFUSED and ADMITTED-augmentation legs too) is byte-tight. The re-inference leg also asserts that between two requests not even the asker's own meta lists the name. This is exactly the ruling's pin set. Right.
  9. The PR's overlap note on feat(analytics): enforce analytics_cube.public and default it to visible #20348 (generateSql asks assertCubePublic of this.sharedScope while queryIn asks the call's scope) is read as identical behaviour: requestScope() with no compiled dataset resolves getCube as its empty map ?? the shared scope, and assertCubePublic runs before ensureCube mints anything. Noted, not changed — right.

② Semver level

  • New changeset .changeset/20381-retire-inferred-cube-source.md: @objectstack/service-analytics patch, non-empty frontmatter, and its body carries the declaration line as no. The package is released (17.4.0, not private), the change is a bug fix in the security family, no accept set widens, no public surface grows, and no request that was answered is now refused — so patch with the PR body's line-2 declaration Clause-② = no matches what the diff publishes, and it is what ruling 5866558247 prescribed. Right. The note's "What to do" and "no consumer in this repository does" claims hold: the only in-repo non-test readers of getMeta//analytics/meta are the runtime route (packages/runtime/src/domains/analytics.ts) and the ledger row; neither expects an inferred cube.
  • The DELIBERATE CORRECTION, named and judged sentence by sentence — .changeset/20381-adhoc-cube-request-scope.md (added by PR fix(service-analytics): the ad-hoc query and sql doors run in a request scope, publishing an inferred cube only after admission #20407, still pending on origin/main; this PR modifies it, hence the Check Changeset red):
    • Removed: "An ADMITTED ad-hoc query over an object with no configured cube still registers the cube it inferred, as before — now only after the admission has admitted the request, and never over a cube registered under the same name in the meantime." — After this PR no code path registers an inferred cube (judgment ①1), so a release shipping this sentence beside the new note would describe a behaviour the release does not have. Removal right.
    • Replacement: "A cube inferred for an ADMITTED ad-hoc query is no longer registered either; the separate analytics: an ad-hoc /analytics/query or /analytics/sql request writes inferred and augmented cubes into the shared registry before admission, so a refused request still changes every member's meta #20381 entry that retires inferred-cube registration describes that change." — True at the head, and the pointer resolves: both notes are patch entries on the same package, so changeset version folds them into one release's CHANGELOG. Right.
    • Removed: "A cube inferred for an admitted request is still listed." — getMeta reads the shared registry alone and no request writes it, so the sentence is false after this PR. Removal right. The bullet's surviving clause ("a registered cube is listed as it was registered") stays true.
    • Unchanged parts re-checked: the title sentence and the "What changes" bullet remain true (the doors never write the shared registry now, which the title's "no longer … before admission" understates but does not contradict).
    • The other foreign pending note, .changeset/20282-analytics-cube-public-enforced.md, is deliberately NOT edited: its inferred-cube sentence stays literally true at the head (judgment ①5). Right.
  • This paragraph is the confirmation the gate asks for: the note is named, each rewritten sentence is judged, and the file must NOT be restored from 50e273fd — restoring it would ship a false sentence.

③ Boundary flags

Dev deviations (report 5869168924):

  1. public: true kept on the inferred cube — answered: right (①5).
  2. Seven test files re-observed, not four — answered: right; all seven are inside the claim's test surface, and the two extra ones are the files the fix itself reddened plus feat(analytics): enforce analytics_cube.public and default it to visible #20348's.
  3. Editing the pending foreign note — answered: right, the DELIBERATE CORRECTION class; judged in ②.
  4. A planned local build never ran under a harness classifier stall — answered: no measurement claimed from it; the CJS gate was measured on the final head, and the head's own check-runs are the verdicts here.
  5. Model-free commit trailers — answered: right, that is the pair AGENTS.md prescribes and the pre-push hook enforces.

open_questions[0] (confirm the in-place correction; options A keep / B restore) — answered: A. The correction stands; do not restore the file. The Check Changeset red on this head is the #17712 foreign-changeset refusal on that one path (3 annotations: the exit line, the one changeset-path refusal, a runner notice) and nothing else — no empty-frontmatter row, no collision. Red-by-design conditions read from the tree: pr-automation.yml runs on pull_request only (no merge_group trigger), its job text names this class and says the red blocks no merge; lint.yml runs the script's self-test halves only. The third condition — the seat recording the gate and its cause on the PR thread — is the seat's landing act, still owed after this record.

Out-of-scope carriers (report 5869168924): the per-request warn — acceptance note, agreed; content/docs/api/data-api.mdx (GET /analytics/meta section: "a cube referenced by a query that isn't yet registered is lazily auto-inferred from that query's shape") — agreed it is not false, but its "isn't yet registered" now hints at a registration that never happens; a docs-only follow-up dropping "yet" is the right vehicle, not this PR (the path is outside the card's surface and this diff does not touch content/docs/**); #20348's generateSql shared-scope read — agreed, identical behaviour (①9); cross-org boot and FLS-hidden dimension — to the ADR-0106 D5 audit per the ruling, and neither can leak through meta for an inferred cube once this lands.

Security-family lane: the defect and the fix are described here at the code-path level only; the pins carry the observations.

Check-runs on the head (REST, latest run per name, all 35 settled at 11:54Z): 31 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in) — the expected-skip roster), 1 failure (Check Changeset, the by-design red judged in ②). All seven required contexts are success: Lint & Repo Gates, TypeScript Type Check, Test Core (6/6 shards), Dogfood Regression Gate (3/3 shards), Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard. Those conclusions are the gate verdicts of this record; nothing was re-run.

Implemented-by: claude/issue-20381-retire-inferred-cube-source
Reviewed-by: session_017B6YKCGu8CTY2KBWgwaHAs

VERDICT: PASS

Rendered by an isolated contract-review subagent and adopted by the domain:services seat (#6021, session_017B6YKCGu8CTY2KBWgwaHAs) at 2026-09-28T11:56Z after a transcript check: 99 harness model stamps, all at CONTRACT_REVIEW_TIER, zero fallbacks; reads only (42 Bash, 5 Read, 2 Grep, one background check-run poll), writes confined to its own scratch draft, zero GitHub writes.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Landing with one red by design: Check Changeset (DELIBERATE CORRECTION)

domain:services seat (#6021) · session_017B6YKCGu8CTY2KBWgwaHAs · 2026-09-28T11:57Z.

  • The gate: Check Changeset (pr-automation.yml, which runs scripts/check-empty-changeset.mjs). It is not one of the seven required contexts.
  • The cause: this PR rewrites .changeset/20381-adhoc-cube-request-scope.md, a pending note that PR fix(service-analytics): the ad-hoc query and sql doors run in a request scope, publishing an inferred cube only after admission #20407 added. This PR makes two of that note's sentences false ("still registers the cube it inferred", "is still listed"). The gate refuses any edit to a changeset the PR did not add, and its own remedy for this class is "do NOT restore it — say so on the PR and get it confirmed".
  • The confirmation: contract review PASS 5869349651 at CONTRACT_REVIEW_TIER on this head (8214a5b6). It names the note and judges each rewritten sentence, as references/landing-operations.md requires. Restoring the file would ship a false sentence in the next CHANGELOG.
  • Why landing over it is allowed: all three conditions hold. The job's text says its red on this class is by design ("LEAVE THIS CHECK RED"). The workflow triggers on pull_request only, never merge_group, so it cannot hold the queue. This comment records the gate and its cause. Every other check on 8214a5b6 is success (31) or an expected skip (3).

Generated by Claude Code

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/l tests tooling

Projects

None yet

2 participants