Repository navigation
analytics: a configured cube or dataset over an apiEnabled: false object registers silently and lists in GET /analytics/meta, then every query of it answers 404 — an authoring trap with no registration-time signal #22663
Description
Activity
- addedbugSomething isn't workingSomething isn't workingarea:workflowApprovals and automation — the work that runs without a person driving itApprovals and automation — the work that runs without a person driving itand removed
on Oct 10, 2026 objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-10-10T10:06Z
Session:session_013j5gkUCpqQiti4GgPqqmnt
Account:zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-22663-analytics-unexposed-cube-registration
Worktree:objectstack-issue-22663
Domain:domain:services
Seat:domain:services#1(seat post #6021)
File surface, read onorigin/main156ddfaee4(PR #22645 landed):- Step 1, a reading before any edit: count the authored cubes and datasets in this repo, the examples and the platform objects that sit over an object whose
enablethe spec'sapiExposureDenialReasondenies. Record where cubes enter the registry: config cubes at plugin start,registerDataset, and any other door. packages/services/service-analytics/src/**: the registration doors refuse a cube or dataset whose base or joined object is denied. The refusal is loud and names the object and the declaration. It reuses the door PR fix(service-analytics)!: the analytics door judges the generic-exit declarations — an object's enable block and a field's internal flag (#22634) #22645 added (api-exposure-door.ts), ⛔ never a second rule. This is the card's preferred shape (a). Shape (b), omitting the cube fromgetMetawhile keeping it registered, is taken only if step 1 measures a producer that (a) would break; stop and report first.- Tests in
service-analytics: per cube and per dataset. Control: an exposed object's cube is unchanged. .changeset/22663-*.md: at the level the diff publishes, FROM → TO.- ⛔ No new gate. ⛔ No
packages/spec, nopackages/lint. ⛔ Nocontent/docs/releases/. - Stop on breach; explain in the report.
Container & model:S,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; the default tier builds; a narrowing owes oneCONTRACT_REVIEW_TIERreview before the queue)
Clause-②: no (narrowing) - Registration stops accepting a cube that no query can ever serve.
Responsibility:this repository's service-analytics: registration accepts and lists a cube over an unexposed object that every query then refuses | the exposure door PR #22645 added, which this reuses | any author who registers such a cube; no in-repo producer measured so far
Thread-read: none
Serial constraints cleared: - PR fix(service-analytics)!: the analytics door judges the generic-exit declarations — an object's enable block and a field's internal flag (#22634) #22645 (security(analytics): the ad-hoc analytics query serves objects that declare
apiEnabled: falseand columns declaredinternal: true, which every other generic exit refuses or withholds #22634) has landed. No open PR touchesservice-analytics(2026-10-10T10:06Z).
Generated by Claude Code
- Step 1, a reading before any edit: count the authored cubes and datasets in this repo, the examples and the platform objects that sit over an object whose
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22663,
"status": "done",
"branch": "claude/issue-22663-analytics-unexposed-cube-registration",
"pr": "#22675",
"session": "session_013j5gkUCpqQiti4GgPqqmnt — harness-stamped (the Claude-Session line); this subagent inherits the dispatching seat's id",
"premise_still_valid": true,
"summary": "Implemented shape (a), the card's preferred shape. The registration doors now ask the decision PR #22645 put at the query door: apiExposureDenialReason(enable, 'aggregate'), through the existing api-exposure-door.ts (new export assertDefinitionExposed; no second rule). They ask it over the definition's base object and declared joins (cubeObjects, the set the query door's first check reads). Each door answers as follows. (1) A configured cube (AnalyticsServiceConfig.cubes, the plugin's cubes, os serve analyticsCubes) over a denied object gets one warn naming the cube, the object and the declaration, and is skipped. The other cubes register, and the boot continues. This reuses the constructor's existing per-definition channel, which a pre-registered dataset that cannot compile already takes (#5115). (2) registerDataset throws a located error carrying the query door's envelope: OBJECT_API_DISABLED/404, or OBJECT_API_METHOD_NOT_ALLOWED/405 with allowed. It registers nothing, and the constructor's datasets take the warn-and-skip channel. (3) A direct write to the service's public cubeRegistry (register/registerAll/inferFromObject) is refused by a new optional CubeRegistry admission hook, which is the single choke point. Refused definitions never reach getMeta. Step 1 census on origin/main ee3ae03: 1 authored cube (showcase_delivery: showcase_task joined to showcase_project; neither declares enable) and 11 example/platform datasets (all request-scoped, never registered). None of them sits over a denied object. There are 0 non-test registerDataset/cubeRegistry writers, so (a) breaks no real producer and (b) was not taken. The only producers were PR #22645's fixtures hidden_cube/nolist_cube, re-triaged: that suite now inits the plugin before declaring its subjects, so its configured-cube rows pin the query door in the window. Timing: a throwaway LiteKernel + real ObjectQLPlugin probe measured a window. An object declared by a plugin whose init() precedes analytics is judged. One declared by a later plugin's init(), or in any start(), is not yet known at registration. It takes the #5115 tier (cannot answer, do not block), and the query door still refuses it. The seat's call on this window is open question 1. Deviations: two lock queue-timeouts (exit 99) behind sibling #22642's long holder; I re-queued under slot issue-22663 and took no reading from a 99. Harness attribution: the harness reminder asked for a commit trailer naming a model and a different PR footer. Per AGENTS.md (which that reminder defers to) commits carry the model-free Claude-Session/Co-authored-by pair, and the PR body carries the session-URL footer.",
"tests": "Final HEAD dcc38d4 (after merging origin/main, 4 incoming commits, clean merge, pushed); all heavy runs went through os-verify-lock with OS_VERIFY_LOCK_SLOT=issue-22663, NODE_OPTIONS=--max-old-space-size=3072, turbo --concurrency=1 and vitest --maxWorkers=2. (1) pnpm turbo run build --filter=@objectstack/service-analytics: Tasks 15 successful, 15 total. (2) pnpm --filter @objectstack/service-analytics exec vitest run --maxWorkers=2: Test Files 182 passed (182); Tests 4485 passed, 262 skipped (4747). (3) pnpm --filter @objectstack/service-analytics typecheck: tsc --noEmit clean (tsconfig include src covers src/tests). The same three were green pre-merge at d60bf4e. VERDICT command-exit 0. (4) New pins in src/tests/unexposed-definition-registration.test.ts, 12 cases: config cube refused (base and join), the envelope, direct registry write, registerDataset (base 404/405 and join), constructor datasets, the plugin over a real ObjectQL+SqlDriver engine (cubes refused at init and absent from getMeta, registerDataset refuses), the control, and three tiering pins. api-exposure-door.test.ts was reordered; 43 tests across the targeted files passed at d60bf4e. (5) Ablation at c8dae0a via node scripts/ablation-replace.mjs (wrap mode, under the lock). Anchor: the assertDefinitionExposed call in assertRegistrable, hit x1 as declared, x1 to x0; replacement x0 to x1; blob 07cb9454e93f to ea44bf4cd373. Result: Tests 8 failed, 4 passed (12). All refusal pins went red; the dataset control and the 3 tiering pins stayed green, the predicted direction. Restore: blob after restore 07cb9454e93f == HEAD, git diff HEAD empty. Independent re-check: ABLATED marker 0, anchor 1. The pins import the service from src by relative path, with no dist hop, so no rebuild leg was owed. (6) Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 65 at dcc38d4 (identical to the pre-merge set). I ran all 65 plus 4 implicated: check:startup-registry-verdict, check:durability-log-level, check:adr-anchors, and the spec check:error-code-provenance. 69/69 exit 0, each exit captured before any pipe; the dist-reading five ran after the rebuild at this HEAD. --ran: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero: all 65 recorded an exit code). Verdict lines: startup-registry-verdict reports 45 seams, none recording a verdict the boot can contradict; error-code-provenance OK, every registered-code stamp site listed; nul-bytes OK; the adr-0087 no-migration-prescription exemption accepted; empty-changeset reports 1 declaring changeset added. (7) Timing probe (throwaway, deleted, not committed): at analytics init, early_init true, late_init false, early_start false. Before the change, getMeta listed all 3 cubes; after it, late_init_obj_cube and early_start_obj_cube (the window). Declared to CI and not run locally: repo-wide pnpm lint, and other packages' suites. The public surface change is one additive optional CubeRegistry constructor parameter, and no other workspace package constructs CubeRegistry.",
"mcp_calls": "0 — no MCP GitHub tool called; card, claim and PR reads went through gh api single-resource REST reads",
"api_writes": "3 — each through the fleet-write relay (scripts/pm, as objectstack-fleet[bot]), one repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches per write, each executing one op: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, opened #22675 as a draft; read-back shows the body stored byte-identical (14388 bytes); (2) label-write --assign zhuangjianguo, POST /repos//issues/22675/assignees; read-back matches the target; (3) this os-dev-report comment, POST /repos//issues/22663/comments. No label written: the dispatch named none, and the diff publishes, so skip-changeset does not apply. Plus 4 git pushes (not REST): the empty branch probe, d60bf4e, c8dae0a, dcc38d4.",
"open_questions": [
{
"question": "The registration window. A configured cube over an object declared AFTER the analytics plugin's init() still registers and lists in getMeta, and every query of it is refused by the query door. Such objects include: a capability provider that follows analytics in requires order, any start()-time registration, kernel:ready installs, and runtime-authored objects. Is a second judgment owed beyond the #5115 registration-time tiering the dispatch asked to reuse?",
"options": [
"A: Leave it as this PR does: judge at registration, with cannot-answer-do-not-block. Cost: none. The window remains for late-declared objects, and the query door stays the fail-closed backstop.",
"B: Re-judge the registered configured cubes once at kernel:ready (or kernel:bootstrapped), and evict a denied one with the same warn. Cost: a boot-phase hook in plugin.ts, an eviction path on the shared registry, and ordering tests. It is still blind to post-ready installs.",
"C: An authoring-time lint in packages/lint, analyticsCubes against the stack's objects, in the style of validate-nav-object-servability. Cost: a new rule (fenced off this claim). It sees only the stack's own objects, not plugin-contributed ones, and a new rule defaults to no."
],
"recommendation": "A, on the four axes. Real business need: 0 measured producers fall in the window; the one authored cube sits over app objects, which os serve registers before analytics inits, so they are judged. Long-term soundness: B adds a boot-phase eviction path whose payoff today is hypothetical, while A keeps one judgment point and the query door already fails closed. AI-error prevention: the cube an AI app author typically writes, over its own app's objects, is refused loudly at boot under A, and the window case still fails loudly at every query. Startup focus: no new hook or rule without a measured pull. Take B if a producer over a plugin-contributed or late-installed unexposed object appears."
}
],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed. An authoring-time check could refuse an analyticsCubes entry whose base or join names an object with enable.apiEnabled false, or a whitelist without list. This would be a packages/lint rule in the style of validate-nav-object-servability, run at os validate / os build, and would catch the pair before boot, inside the registration window too. No producer is measured, the claim fences packages/lint, and a new rule defaults to no; it is recorded in PR #22675 Acceptance notes item 5. Dedupe words: analytics cube lint unexposed object; analyticsCubes apiEnabled false validate; cube registration window kernel:ready; getMeta lists cube over hidden object"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsClaim amendment (file surface) ·
domain:servicesseat 1 (#6021) ·session_013j5gkUCpqQiti4GgPqqmnt· 2026-10-10T11:43ZAmends
6096404246.- Added:
packages/spec/liveness/analytics_cube.json, value-only. Its 16 rows that anchor their producer atservice-analytics/src/analytics-service.ts#registerAllare re-anchored on the symbol that now registers configured cubes. PR fix(service-analytics)!: a configured cube or registered dataset over an object the API does not serve is refused at registration, and GET /analytics/meta no longer lists it (#22663) #22675 removedregisterAllfrom the constructor, andTest Core (1/6)fails on that rot.- It is a
packages/specfile (domain:spec), taken under the cross-domain exception, as security(analytics): the ad-hoc analytics query serves objects that declareapiEnabled: falseand columns declaredinternal: true, which every other generic exit refuses or withholds #22634's ledger row was. It is declared on [PM seat] domain:spec — 🟢 os-project-manager · session_01S3aAf11JjbW1mSGL1EhfFj #6017 in this act. - ⛔ No other spec file. No status, key or row is added or removed. Only the anchor's symbol moves.
- It is a
- Everything else in the claim stands, including
Clause-②: no (narrowing)and the contract-review record owed on the patch head.
Generated by Claude Code
- Added:
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22663,
"round": 1,
"status": "done",
"branch": "claude/issue-22663-analytics-unexposed-cube-registration",
"pr": "#22675",
"head": "3a646f5ffb",
"session": "session_013j5gkUCpqQiti4GgPqqmnt — harness-stamped (the Claude-Session line); this subagent inherits the dispatching seat's id",
"premise_still_valid": true,
"summary": "Round 1 patch. Cause: 16 rows in packages/spec/liveness/analytics_cube.json anchored their producer at service-analytics/src/analytics-service.ts#registerAll. Round 0 replaced the constructor's registerAll call, so Test Core (1/6) failed the spec liveness gate. I reproduced it on dcc38d4 before editing (24 failed, 1 failed, and check:liveness exit 1). Fix: the 16 anchors moved to analytics-service.ts#register, the symbol of the per-cube this.cubeRegistry.register(cube) call that now registers configured cubes. No code changed to fit the anchor. The contract-review addendum (6097218872) is folded in. The 16 rows carry verifiedAt 2026-10-10, and their shared producer string now quotes the code at head; I corrected both quotes, because the serve.ts one (a ?? [] fallback, now resolveStackCollection) was stale since #22288 and a verifiedAt stamp attests the whole producer. The _note no longer says registerAll. The changeset declares Clause-② yes (narrowing) and stays minor. The value-only fence holds: same key paths, 0 status changes, and changed leaves of _note 1, producer 16, verifiedAt 16. No other spec file was touched. I did not PATCH the PR body; the Round 1 text is in pr_body_text, and line 2 is the seat's to update. One main-side red to route: check:platform-checklist exits 1 at head and identically on a clean origin/main 243dd3c tree (control leg), on symbol anchors in files this diff does not touch. No behaviour change was made for the registration window or the dispatch-gates blind spot, as instructed.",
"tests": "Reproduction at dcc38d4, before any edit, under the lock: pnpm --filter @objectstack/spec exec vitest run --project local scripts/liveness/check-liveness.test.ts gave Tests 24 failed, 47 passed (71), exit 1. vitest run --project repo scripts/liveness/evidence.test.ts gave Tests 1 failed, 41 passed (42), exit 1. pnpm --filter @objectstack/spec run check:liveness gave exit 1 with 16 anchors naming a symbol the file does not contain. After the re-anchor, at 393b179, the same three passed: 71/71, 42/42, exit 0. Spec build exit 0, spec local 642 files / 19180 passed + 1 todo, test:repo 54 files / 915 passed. Final head 3a646f5 (after the addendum commit dabf17b and a clean merge of origin/main 243dd3c). check-adr-0087-registration --base origin/main exit 0, with [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription). check-changeset-no-major --base origin/main exit 0, measured at dabf17b and re-run inside the derived set at 3a646f5. Build closure 15/15. Liveness check test 71/71 exit 0; evidence test 42/42 exit 0; check:liveness exit 0. Spec test local 642 files / 19159 passed + 1 todo, exit 0. test:repo 55 files / 971 passed, exit 0. service-analytics vitest 182 files / 4485 passed, 262 skipped, exit 0. service-analytics typecheck (tsc --noEmit) exit 0. Every VERDICT line reads command-exit 0. Gates: dispatch-gates --commands --repo objectstack-ai/objectstack derived 72 at 3a646f5, 7 new versus round 0: check-dev-prereqs self-test, spec check:empty-state, check:liveness, check:strictness-ledger, check:variant-docs, check:merge-driver, check:platform-checklist. All ran, plus 4 implicated: startup-registry-verdict, durability-log-level, adr-anchors and spec error-code-provenance. Each exit was captured before any pipe; 75 exited 0 and 1 exited 1. --ran reports 72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN, a derived zero. check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET, no dist on 64 packages), which is NOT MEASURED. After the full turbo build (72/72, 71 cached) it exited 0: 107 entry points across 66 packages load. check:platform-checklist exited 1 with 7 ABSENT SYMBOL problems in access-security.json and attachments-storage.json. On a control worktree at origin/main 243dd3c it exits 1 with the identical 7, so the red is main-side. Ledger fence proof: a JSON diff of 393b179 against the edit gives same key paths, 0 status changes, and changed leaves {_note 1, producer 16, verifiedAt 16}. The control-byte scan is clean.",
"mcp_calls": "0 — no MCP GitHub tool called; card, amendment and PR reads went through gh api single-resource REST reads",
"api_writes": "1 — this round-1 os-dev-report comment, through the fleet-write relay as objectstack-fleet[bot]: one repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches executing the comment op, POST /repos//issues/22663/comments. No PR-body PATCH and no label write. Plus 3 git pushes (not REST): 393b179, dabf17b, 3a646f5.",
"pr_body_text": "## Round 1 (patch)\n\nHead:3a646f5ffb. That is393b179be6(re-anchor), thendabf17b4b8(contract-review addendum), then a clean merge oforigin/main243dd3c625with no regeneration debt.\n\nCause.Test Core (1/6)atdcc38d4413failed in@objectstack/spec's liveness gate. Sixteen rows inpackages/spec/liveness/analytics_cube.jsonanchored their producer atpackages/services/service-analytics/src/analytics-service.ts#registerAll. This PR replaced the constructor'sregisterAllcall, so the anchored symbol left the file. Reproduced ondcc38d4413before any edit:\n\n-check-liveness.test.ts(local): 24 failed, 47 passed, exit 1.\n-evidence.test.ts(repo): 1 failed, 41 passed, exit 1.\n-check:liveness: exit 1, with "16 anchored citation(s) name a symbol the cited file does not contain".\n\nRound 0's local set missed it. The derived set held no spec family for a diff outsidepackages/spec, and nothing that ran read the ledger's anchors intoservice-analyticssource.\n\nRe-anchored symbol:analytics-service.ts#register. The constructor now registers configured cubes throughthis.cubeRegistry.register(cube), one call per cube.registeris the symbol that call uses, the direct successor ofregisterAllin the same position. Nothing inanalytics-service.tschanged to fit the anchor.\n\nContract-review addendum (6097218872), value-only in the same ledger. No status, key or row moved, and no other spec file was touched. The before/after JSON has the same key paths and 0 status changes; the changed leaves are_note1,producer16 andverifiedAt16.\n\n-verifiedAt: 2026-10-10on the 16 rows. Their shared producer string now quotes the code at this head. This covers theserve.tsfallbackresolveStackCollection(config, 'analyticsCubes'), which had been stale since #22288 and was a?? []quote, and the constructor's per-cuberegisterbehind the exposure admission. AverifiedAtstamp attests the whole producer, so a known-stale quote could not stay under it.\n-_note. It now reads "registered one cube at a time byregister".\n- ChangesetClause-②: yes (narrowing).CubeRegistryis re-exported from the package entry, and its optionaladmitparameter enlarges the published surface. The bump is stillminor, and the ADR-0087 marker still discloses the additive parameter.check-adr-0087-registration --base origin/mainexits 0 ([BREAKING+bang+clause-②-narrowing],not-required (no-migration-prescription)), andcheck-changeset-no-major --base origin/mainexits 0. Line 2 of this body is the seat's to update.\n\nGate exits at3a646f5ffb. All runs went through the verify lock with slotissue-22663, a 3 GB heap, turbo--concurrency=1and vitest--maxWorkers=2.\n\n- Liveness pair.check-liveness.test.ts: 71/71, exit 0.evidence.test.ts: 42/42, exit 0.check:liveness: exit 0.\n- Spec suites.test(local): 642 files, 19159 passed and 1 todo, exit 0.test:repo: 55 files, 971 passed, exit 0.\n-@objectstack/service-analytics. 182 files, 4485 passed and 262 skipped, exit 0. Typecheck exit 0.\n- Builds. The service-analytics closure built 15/15. A fullturbo run build --filter=!@objectstack/docsbuilt 72/72 (71 cached) for the dist-reading gate.\n- Derived set.dispatch-gates --commands --repo objectstack-ai/objectstackderived 72 commands, 7 of them new spec/repo families (includingcheck:liveness). All 72 ran, pluscheck:startup-registry-verdict,check:durability-log-level,check:adr-anchorsand the speccheck:error-code-provenance. 75 exited 0 and 1 exited 1.--ranreports 72 derived, 72 run, 0 NOT-MEASURED and 0 UNRUN, a derived zero.\n-check:dual-build-cjs-loads. It first answered exit 3,PREREQUISITE NOT MET(64 packages had nodistin the fresh worktree), which is not a measurement. After the full build it exited 0: 107 require entry points across 66 packages load.\n-check:platform-checklistexit 1, main-side. It reports 7 problems, all ABSENT SYMBOL anchors. Five are inareas/access-security.json, againstmetadata-protocol/src/protocol.ts(anonymousFormIntakeOrgScopeRefusal,anonymousFormIntakeReopenRefusal,envWideRawViewRows). Two are inareas/attachments-storage.json:#canEdit, and the symbol-anchor floor at 27 of 28. None of those files is in this diff. Control leg: the same gate on a cleanorigin/main243dd3c625tree exits 1 with the identical 7 problems.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: the seat (main-side red; not in this diff) · noted, not filed. pnpm check:platform-checklist exits 1 on origin/main 243dd3c, measured on a clean control worktree, with 7 problems. Five are in docs/qa/platform-checklist/areas/access-security.json: ABSENT SYMBOL packages/metadata-protocol/src/protocol.ts#anonymousFormIntakeOrgScopeRefusal, #anonymousFormIntakeReopenRefusal and #envWideRawViewRows (each 0 occurrences in protocol.ts at origin/main). Two are in areas/attachments-storage.json: packages/services/service-storage/src/attachment-access-hooks.ts#canEdit is present as text but not as a declaration, and SYMBOL ANCHORS LOST 27 against a floor of 28. Not bisected. Dedupe words: platform-checklist absent symbol protocol.ts; anonymousFormIntakeReopenRefusal anchor; attachment-access-hooks canEdit anchor floor"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22663,
"round": 2,
"status": "done",
"branch": "claude/issue-22663-analytics-unexposed-cube-registration",
"pr": "#22675",
"head": "0dd4426835",
"session": "session_013j5gkUCpqQiti4GgPqqmnt — harness-stamped (the Claude-Session line); this subagent inherits the dispatching seat's id",
"premise_still_valid": true,
"summary": "Round 2: two value-only fixes to stale leaves under the round-1 verifiedAt stamp, in packages/spec/liveness/analytics_cube.json only (review 6097791315, flag ③.4). (1) The first evidence citation of joins.children.name quoted a joinTable line that is gone from native-sql-strategy.ts. It is re-sited at hop-object.ts#resolvePathHops, quoting const joined = joins?.[alias]?.name; verbatim, with native-sql-strategy.ts#qualifyAndRegisterJoin kept as the LEFT JOIN emitter (object: hop.object, verbatim). The other three citations are unchanged. (2) In the 16 producer strings, the configKey declaration clause is re-sited at packages/core/src/capability-providers.ts#CAPABILITY_PROVIDERS. The serve.ts threading quote stays at serve.ts#CAPABILITY_PROVIDERS, which is the resolver's own declared handle (const CAPABILITY_PROVIDERS = Serve.CAPABILITY_PROVIDERS, line 4593) that the loop indexes; I chose it over the enclosing run() method, a 3,000-line method whose name would anchor nothing specific. That clause now also quotes spec.configKey === 'analyticsCubes'. Every quote occurs exactly once at head, and every anchor names a declaration in its cited file. Fence against origin/main: same 91 key paths, 0 status changes, changed leaves _note 1, verifiedAt 16, producer 16, evidence 1. Nothing else was touched. origin/main had not moved (243dd3c is an ancestor of head), so no merge was needed. I did not PATCH the PR body; the round-2 section is in pr_body_text.",
"tests": "Head 0dd4426; all heavy runs went through os-verify-lock with OS_VERIFY_LOCK_SLOT=issue-22663, NODE_OPTIONS=--max-old-space-size=3072, turbo --concurrency=1 and vitest --maxWorkers=2, each exit recorded before any pipe. Full turbo run build --filter=!@objectstack/docs: Tasks 72 successful (72), exit 0. pnpm --filter @objectstack/spec exec vitest run --project local scripts/liveness/check-liveness.test.ts: Tests 71 passed (71), exit 0. vitest run --project repo scripts/liveness/evidence.test.ts: Tests 42 passed (42), exit 0. pnpm --filter @objectstack/spec run check:liveness: exit 0. Spec test:repo: Test Files 55 passed, Tests 971 passed, exit 0. dispatch-gates --commands --repo objectstack-ai/objectstack derived 72 at 0dd4426 (the same set as round 1). All 72 ran, plus startup-registry-verdict, durability-log-level, adr-anchors and spec error-code-provenance: 75 exited 0 and 1 exited 1. check:dual-build-cjs-loads exit 0 (107 entry points across 66 packages load). check:platform-checklist exit 1 with 7 problems, a line-for-line diff identical to round 1's clean origin/main 243dd3c control, so the red is main-side and in no file of this diff. --ran: 72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN, a derived zero. Fence proof by JSON walk: against origin/main, same key paths (91), status changes 0, changed leaves {_note 1, verifiedAt 16, producer 16, evidence 1}; against 3a646f5, {producer 16, evidence 1}. The edit used literal splits with exact hit counts (evidence x1, producer x16) and no JSON re-serialisation. The blob moved a780de2e24c7 to 79771f0a0700. The control-byte scan is clean.",
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "1 — this round-2 os-dev-report comment, through the fleet-write relay as objectstack-fleet[bot]: one repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches executing the comment op, POST /repos//issues/22663/comments. No PR-body PATCH and no label write. Plus 1 git push (not REST): 0dd4426.",
"pr_body_text": "## Round 2 (ledger)\n\nHead:0dd4426835(one commit on3a646f5ffb).origin/mainis still243dd3c625, which round 1 merged, so no merge was owed. Onlypackages/spec/liveness/analytics_cube.jsonchanged, and only values. The contract review of3a646f5ffb(6097791315, flag ③.4) named two pre-existing stale leaves sitting under the round-1verifiedAt: 2026-10-10stamp.\n\n1.joins.children.name, firstevidencecitation. It quotedconst joinTable = cube?.joins?.[alias]?.name ?? aliasatnative-sql-strategy.ts#qualifyAndRegisterJoin.joinTablehas 0 occurrences in that file at head. The citation is re-sited atpackages/services/service-analytics/src/hop-object.ts#resolvePathHops, quotingconst joined = joins?.[alias]?.name;verbatim (tier 1 of the one hop resolver). It keepsnative-sql-strategy.ts#qualifyAndRegisterJoinas the emitter of the LEFT JOIN against that object, quotingobject: hop.object, which is verbatim at head. The row's other three citations are unchanged.\n\n2. The 16producerstrings.\n\n- TheconfigKeydeclaration clause is re-sited atpackages/core/src/capability-providers.ts#CAPABILITY_PROVIDERS, which declaresconfigKey: 'analyticsCubes'on theanalyticsentry.\n- Theserve.tsthreading quote stays where it lives, atpackages/cli/src/commands/serve.ts#CAPABILITY_PROVIDERS. That is the resolver's own declared handle on the table (const CAPABILITY_PROVIDERS = Serve.CAPABILITY_PROVIDERS), which the resolver loop indexes. The clause now quotes the test it applies,spec.configKey === 'analyticsCubes', and the existingconst cubes = … ; arg = { cubes }quote.\n- Every anchor names a declaration in its cited file, and every quote occurs exactly once at head.\n\nFence proof. The JSON walk againstorigin/mainfinds the same 91 key paths and 0 status changes. Changed leaves by field:_note1,verifiedAt16,producer16,evidence1. Against the round-1 head, onlyproducer16 andevidence1 moved.\n\nGates at0dd4426835. All runs went through the verify lock with slotissue-22663, a 3 GB heap, turbo--concurrency=1and vitest--maxWorkers=2, with each exit captured before any pipe.\n\n-check-liveness.test.ts(local): 71/71, exit 0.\n-evidence.test.ts(repo): 42/42, exit 0.\n-pnpm --filter @objectstack/spec run check:liveness: exit 0.\n- Spectest:repo: 55 files, 971 passed, exit 0.\n- Fullturbo run build --filter=!@objectstack/docs: 72/72 tasks, exit 0.\n-dispatch-gates --commands --repo objectstack-ai/objectstackderived 72 commands, the same set as round 1. All 72 ran, plus 4 implicated ones. 75 exited 0 and 1 exited 1.--ranreports 72 derived, 72 run, 0 NOT-MEASURED and 0 UNRUN, a derived zero.\n-check:platform-checkliststill exits 1 with the identical 7 main-side ABSENT SYMBOL problems that round 1's cleanorigin/main243dd3c625control showed. None of them is in this diff.",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsACCEPT (seat review, patch round 2): PR #22675 at head
0dd4426835. The analytics registration doors refuse a definition over an object the API does not servedomain:servicesseat 1 (#6021) ·session_013j5gkUCpqQiti4GgPqqmnt· 2026-10-10T13:47Z.- Claim
6096404246, amended6097143716(onepackages/spec/livenessfile, value-only). - Reports: round 0
6097085931, round 16097648815, round 26098050717. - Read against GitHub, and the PR head fetched into a seat-owned ref, not the reports.
Review route. The PR declares
Clause-②: yes (narrowing):- it narrows the accept set at three registration doors;
CubeRegistry, re-exported from the package entry, gains one optional constructor parameter, which widens the public surface.
The contract-review-tier records are on the PR:
6097218872ondcc38d4413: FAIL, a red liveness shard and the undeclared widening;6097791315on3a646f5ffb: PASS, with flag ③.4 naming two stale ledger leaves under the freshverifiedAt. The seat folded the flag into the branch (6097801200).6098145097on0dd442683534bf37b941ae9d4b2050e668f397f9: PASS.Served-tier: CONTRACT_REVIEW_TIER,Local-runs: none, an isolated at-tier subagent adopted by this seat. ③.4 is discharged, and nothing is owed before the queue.
Shape. Draft, base
main. Line 1 isFixes #22663; line 2 isClause-②: yes (narrowing). 7 files, +589 / −59. NOT governed:check-governed-merges --pr 22675finds 0 of 7 paths, 648 changed lines. The changeset grades@objectstack/service-analyticsminor, BREAKING as an accept-set narrowing; no major.The change, as read in the diff
- Configured cubes. A cube whose base object or declared join the spec's
apiExposureDenialReason(enable, 'aggregate')denies is warned once and skipped, through the constructor's per-definition channel (dataset 跨 datasource JOIN 应在编译/发布期就被拒绝,而不是留到查询期才响亮失败(#5033 的后续) #5115), now oneregisterper cube. The other cubes register, andGET /analytics/metanever lists the refused one. registerDataset. It throws the query door's envelope and registers nothing:404 OBJECT_API_DISABLED, or405 OBJECT_API_METHOD_NOT_ALLOWEDwithallowed, plusobject.- Direct writes to the public
cubeRegistry. They are admitted atregister, the one choke point every write reaches. - One decision, not a second rule.
assertDefinitionExposedasks the same decision as PR fix(service-analytics)!: the analytics door judges the generic-exit declarations — an object's enable block and a field's internal flag (#22634) #22645's query door, overcubeObjects(cube). - The liveness ledger.
packages/spec/liveness/analytics_cube.jsonis edited value-only. The 16 producers are re-anchored atanalytics-service.ts#register, and the declaration clause atpackages/core/src/capability-providers.ts#CAPABILITY_PROVIDERS. Oneevidencecitation is re-sited athop-object.ts#resolvePathHops.verifiedAtand_noteare refreshed. Same key paths, 0 status changes.
Evidence read. The dev's derived set at the head: 72 derived, 72 run, 0 NOT-MEASURED. The one non-zero is
check:platform-checklist, main-side, below.check:liveness, the liveness pair and spectest:repoall exit 0.service-analytics: 4485 passed, typecheck exit 0.CI at
0dd4426835: 30 success and 5 skipped, read latest-run-per-check-name. The skips areAuto Label,Build Docs,Check PR Size,Console Pin GateandPacked-tarball smoke (opt-in), all rostered. The seven required contexts are allsuccess.mergeable_state: clean.The PR's Acceptance notes and the dev's findings, disposed
- The registration window: an object declared after the analytics plugin's
init()is not judged at registration, and the query door still refuses it. Filed analytics: a configured cube over an object declared after the analytics plugin'sinit()still registers and lists inGET /analytics/meta, though every query of it is refused — the registration window #22663 leaves #22679 (priority:p3,pm:queue). - A relationship hop one member walks is judged per member at query time: Acceptance notes. Refusing the cube for it would over-refuse.
- A lookup that throws at registration is not logged: Acceptance notes. The plugin already warns at
init(), and the query door logs the same failure aterror. CubeRegistry.registerAllstops at the first refused cube: Acceptance notes. This is documented on the method, and the service no longer calls it.- The authoring-time lint rule: rides analytics: a configured cube over an object declared after the analytics plugin's
init()still registers and lists inGET /analytics/meta, though every query of it is refused — the registration window #22663 leaves #22679 as its option C. A new rule defaults to no unless the maintainer names it. - The derivation blind spot that let round 0 ship a red liveness shard: filed pm tooling:
dispatch-gates.mjsdoes not derivecheck:livenessfor a diff that changes a file apackages/spec/liveness/*.jsonproducer anchor cites, so a consumer-package PR can remove an anchored symbol with every derived gate green #22680, which triage closednot_planned. check:platform-checklistred onmain, 7 ABSENT SYMBOL anchors in no file of this diff: dropped as a duplicate of check:platform-checklist is red on main #22594. Triage closed that cardnot_plannedwith its re-entry conditions (6095872178), and the daily watchdog files it again.
Landing: ready plus auto-merge through the queue, in this act. After this lands, the seat moves to serial dispatch, one card at a time, on the maintainer's instruction.
Generated by Claude Code
- Claim
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsLanded ·
domain:servicesseat 1 (#6021) ·session_013j5gkUCpqQiti4GgPqqmnt· 2026-10-10T14:19Z- PR fix(service-analytics)!: a configured cube or registered dataset over an object the API does not serve is refused at registration, and GET /analytics/meta no longer lists it (#22663) #22675 merged through the queue as
73990802d3. Read onorigin/main:analytics-service.tsregisters configured cubes throughassertRegistrable.api-exposure-door.tscarriesassertDefinitionExposed.analytics_cube.jsoncitespackages/core/src/capability-providers.ts#CAPABILITY_PROVIDERSon its 16 producer rows..changeset/22663-analytics-unexposed-cube-registration.mdis present.
- Its
Fixes #22663closed this cardcompleted.pm:dispatchedand the assignee were cleared in this act. - What landed (
@objectstack/service-analytics, BREAKING accept-set narrowing, gradedminor):- A configured cube, or a dataset
registerDatasetregisters, over an object whoseenablethe API does not serve foraggregateis refused at registration. GET /analytics/metano longer lists such a cube.- The query door's refusal of every query of such a cube is unchanged.
- A configured cube, or a dataset
- Carried forward: the registration window is analytics: a configured cube over an object declared after the analytics plugin's
init()still registers and lists inGET /analytics/meta, though every query of it is refused — the registration window #22663 leaves #22679. It is this lane's next card, dispatched serially per the maintainer's instruction.
Generated by Claude Code
- PR fix(service-analytics)!: a configured cube or registered dataset over an object the API does not serve is refused at registration, and GET /analytics/meta no longer lists it (#22663) #22675 merged through the queue as
Filing class: ③ an authoring trap (Prime Directive 10): the author is told nothing until a query fails. Escalated by PR #22645's contract review (
6096186384, boundary flags 5 and 6), from #22634's dev report6095724958(finding 3 and theregisterDatasetAcceptance note). Derived from the in-flight #22634 bydomain:servicesseat 1 (seat post #6021,session_013j5gkUCpqQiti4GgPqqmnt), and it inherits that lane.Reader: the
domain:servicesseat that claims it. Priority: p3. The disclosure is bounded to the author's own naming:getMetaemits cube and member names and titles, never the base object name or rows. The cost is a cube that can never answer.The gap (read on
origin/main5fb1746611, after PR #22645 if landed)enablethe spec'sapiExposureDenialReasondenies (404 OBJECT_API_DISABLED/405).registerDataset, whose base or joined object is such an object is still accepted at registration and listed byGET /analytics/meta. Every query of it then answers 404, with no authoring-time signal.Ask
apiEnabled: falseand columns declaredinternal: true, which every other generic exit refuses or withholds #22634's dev measured none).meta. This is a consumer-side narrowing; prefer (a) unless (a) breaks a measured producer.Dedupe: MCP
search_issues, this repo,analytics meta lists cube over object with apiEnabled false, registerDataset does not refuse …→ no card on this gap. The nearest are #20381 and #20356 (closed, registry pollution by ad-hoc requests) and #21082 (closed, lint reads no cube).Generated by Claude Code