Repository navigation
fix(analytics): make organization timezone drive date-dimension bucketing (#1982) - #2018
Merged
Merged
Conversation
…ting (#1982) Date-bucketed analytics silently ignored the reference timezone end-to-end. Three independent seams were broken; all three are needed for an org's localization timezone to actually change a date-bucketed chart: 1. service-analytics — NativeSQLStrategy (priority 10) won every cube/dataset query on a SQL driver, but it groups by the raw column (no date_trunc) and ignores `timezone`, so date dimensions never bucketed (one row per raw timestamp) and a non-UTC zone was dropped. It now declines queries carrying a timeDimensions[].granularity, handing them to ObjectQLStrategy → engine.aggregate (native bucketing UTC-safe, uniform in-memory when non-UTC). 2. objectql — the in-memory `count` aggregation treated the `*` count-all sentinel (Cube `count` measure / fieldless dataset `count`, both compiled to sql:'*') as a column name → counted 0 for every bucket. The driver COUNT(*) masked it; the in-memory path (non-UTC date buckets, driver-rest/-memory) returned zeros. `*` is now counted as all rows. 3. rest — resolveExecCtx never resolved the localization timezone/locale, so /analytics/dataset/query always ran with timezone:'UTC'. It now resolves them through the settings service (4-tier cascade incl. the OS_LOCALIZATION_TIMEZONE env override), mirroring the dispatcher path. Verified end-to-end against example-crm: an org tz of America/Los_Angeles now buckets a 03:5x UTC lead into 2026-06-17 (cnt 5) vs UTC's 2026-06-18 (cnt 5). Regression tests added for seams 1 and 2; full suites green (objectql 660, service-analytics 127, rest 121). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
added a commit
that referenced
this pull request
Jun 18, 2026
Static gates (build, unit tests, spec-liveness, CodeQL) verify each layer in isolation, usually against mocks, and cannot catch a break that only appears when the real engine + strategies + services + HTTP context run together. The #2018 tz-bucketing regression is the canonical case: green on every static gate (900+ unit tests included) because each of its three broken seams was individually correct and individually mocked. This adds `@objectstack/dogfood` (private), a harness that boots real example apps in-process against in-memory SQLite — wired with the same service plugins `objectstack dev` loads — and exercises them through the real HTTP surface via Hono request-injection (no ports, no sockets, CI-stable). Tests act as a browser client: sign in, hit /api/v1/..., assert on real responses. - src/harness.ts — bootDogfoodStack(config) → { kernel, api, raw, signIn, apiAs, stop } - test/analytics-timezone.dogfood.test.ts — golden regression for #1982/#2018: org timezone (set via the real settings route) must shift a boundary-crossing instant's date bucket (UTC 2024-03-01 → America/Los_Angeles 2024-02-29) with the correct count. Verified to FAIL when any #2018 seam is reverted. - CI: new "Dogfood Regression Gate" job (also runs under turbo run test). Boot-to-assert ~2s. Private package — no changeset. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang
added a commit
that referenced
this pull request
Jun 18, 2026
…2024) * docs(adr): ADR-0054 prove-it-runs gate for the authorable surface Extends ADR-0049 (enforce-or-remove) with a third leg. The liveness ledger (#1919) classifies every authorable property live/experimental/dead, but "live" means only a static file:line consumer pointer — proof that something reads the property, not that authoring it produces correct runtime behavior. #2018 (tz bucketing: live at every layer, broken in integration) and the field-type fidelity gaps (#2022: rating/slider/toggle read back wrong-typed) fell through that gap — call it "unproven liveness". For a platform whose authors are AI emitting metadata across a combinatorial space the examples never cover, unproven liveness ships silently into third-party apps. ADR-0054 upgrades a `live` classification to optionally carry a `proof` — a @objectstack/dogfood test that authors the property against the real in-process stack and asserts the runtime outcome. Required as a ratchet (not a retrofit) for a high-risk authorable class on change, and for any property implicated in a shipped regression (the fix carries its proof). Generative testing is explicitly deferred (Phase 3, evidence-gated). Proposed — for architect review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(adr): accept ADR-0054 (prove-it-runs gate) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang
pushed a commit
that referenced
this pull request
Jun 19, 2026
…0054) Unlocks the analytics binding that was previously blocked because its authorable surface wasn't governed. - Govern the `dataset` metadata type: new liveness/dataset.json classifies all 19 authorable props with file:line evidence from the analytics service consumer audit (26 live, 1 dead — measures.certified has no runtime consumer). `dataset` added to GOVERNED in check-liveness.mts. - Bind the analytics class: dataset.dimensions.dateGranularity carries the tz-bucketing proof (#1982/#2018) — the property whose org-timezone day-bucket behavior the proof asserts. proof-registry.mts flips analytics to bound. - Four high-risk classes now CI-enforced: field types, RLS, flow nodes, analytics. - README governed-types + high-risk tables updated; changeset description updated. Verified: liveness gate green (dataset 27 classified); 17 registry tests; the analytics proof runs green; full dogfood suite 59/59. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx
os-zhuang
added a commit
that referenced
this pull request
Jun 19, 2026
…h-risk classes (ADR-0054 #1 + Phase 2) (#2045) * feat(spec): add prove-it-runs proof field + ratchet to liveness gate (ADR-0054 #1) ADR-0054 follow-up (1): the liveness ledger's `live` meant only a static consumer pointer — necessary but not sufficient, since a property can be live at every layer yet broken end-to-end. This adds the third leg: high-risk authorable properties must carry a `proof` (a dogfood test reference) that asserts the runtime outcome. - proof-registry.mts: the authoritative high-risk-class list (field types, analytics, RLS, flow nodes, form widgets) + which classes the ratchet enforces this phase. Field types and RLS are bound (matrix exists AND surface governed); analytics/flow/form are listed-but-blocked with honest reasons (their surface isn't governed yet / no proof yet — Phase 2). - check-liveness.mts: a bound `live` entry must carry a valid `proof` of its own class. Validation is STATIC (file exists + declares the `@proof: <id>` tag) so the gate stays seconds-cheap; running the proof remains the dogfood gate's job. Reverse check flags unregistered `@proof:` tags. - Ledger: field.type → field-zoo proof; permission.rowLevelSecurity.using → #1994 RLS proof. Dogfood proofs self-declare their `@proof:` tag. - Gate now also triggers on packages/dogfood/** so deleting/renaming a proof re-runs the check and the dangling reference is caught. - 15 unit + wiring tests; README documents the contract and ratchet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx * chore: add changeset for the liveness prove-it-runs gate change Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx * feat(verify,spec): bind flow-node high-risk class with a runtime proof (ADR-0054 Phase 2) Phase 2 extends the prove-it-runs ratchet to the flow-node class. - verify: bootStack gains an opt-in `automation` flag that registers @objectstack/service-automation, so authored flows are pulled from the registry and POST /automation/:name/trigger runs their nodes. Without it, flow execution was unreachable through the harness (the dispatcher's automation routes resolved no service). Mirrors the existing `multiTenant`/`security` opt-ins; default off. - dogfood: a self-contained flow fixture (one object + one autolaunched flow whose update_record node stamps a record) + the flow-node proof. It authors the flow, triggers it over HTTP, and asserts both directions — the targeted record is stamped (node executed) AND a bystander is untouched (the input variable wired into the node filter, not a blanket update). Runs green end-to-end. - spec: flow-node class is now `bound` in proof-registry.mts; flow.nodes.type carries the proof; the liveness gate enforces it. Three classes now bound: field types, RLS, flow nodes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx * feat(spec): govern dataset + bind the analytics high-risk class (ADR-0054) Unlocks the analytics binding that was previously blocked because its authorable surface wasn't governed. - Govern the `dataset` metadata type: new liveness/dataset.json classifies all 19 authorable props with file:line evidence from the analytics service consumer audit (26 live, 1 dead — measures.certified has no runtime consumer). `dataset` added to GOVERNED in check-liveness.mts. - Bind the analytics class: dataset.dimensions.dateGranularity carries the tz-bucketing proof (#1982/#2018) — the property whose org-timezone day-bucket behavior the proof asserts. proof-registry.mts flips analytics to bound. - Four high-risk classes now CI-enforced: field types, RLS, flow nodes, analytics. - README governed-types + high-risk tables updated; changeset description updated. Verified: liveness gate green (dataset 27 classified); 17 registry tests; the analytics proof runs green; full dogfood suite 59/59. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx --------- Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang
added a commit
that referenced
this pull request
Jun 27, 2026
feat(console): "Edit with AI" — open the build agent pre-scoped to an app's package (#2018) objectui@2021b5f4a89c1940b2b3aa751ce4e33fe971974a
os-zhuang
added a commit
that referenced
this pull request
Jul 30, 2026
… validate /analytics bodies at the entry (#3878) (#4010) The spec's AnalyticsQueryRequestSchema described a {cube, query, format} ENVELOPE — the dialect of the retired degraded shim (#3891) that the real engine never understood: an envelope body inferred a column-less cube and died as a driver SQL syntax error (SELECT FROM ...) instead of a shape error, and a non-contract `filters` key was silently ignored. That mismatch sent #3867's first investigation to a wrong conclusion. Spec: - AnalyticsQueryRequestSchema = bare AnalyticsQuery + required top-level `cube`, `.strict()`. `query` and `format` are retiredKey-tombstoned (tsc `never` + parse-time prescription); `format` was never implemented (declared != enforced). - Registered as two step-17 semantic migrations (an HTTP-wire change with no stored metadata to rewrite); spec-changes.json, upgrade guide, authorable-surface, JSON schemas, OpenAPI, reference docs regenerated. Runtime: - POST /analytics/query and /analytics/sql validate the body at the entry and raise the duck-typed VALIDATION_FAILED shape both dispatcher error exits already map to 400 + fields[] (#3918) — the envelope answers with the tombstone's migration text, `filters` gets a bespoke hint at `where`. The domain throws through, matching its existing error contract; the wire-level 400 is pinned in dispatcher-validation-error.real.test.ts. - A valid body is forwarded byte-identical (validation only): parsing would inject the schema's timezone 'UTC' default and override org-timezone resolution (#1982/#2018). - Service-absent still answers 404 before body inspection (#3891). Tests: 5 new entry-validation cases + wire-level 400/200 pins; 9 legacy fixtures updated to contract-valid bodies. Refs #3878, #3891, #3867, #3918. Part 1 of the #3878 follow-through; route-mount conditionality is the next PR. Claude-Session: https://claude.ai/code/session_017WZBR9XyhXoqKCU6qQVyjx Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Aug 2, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 2, 2026
…es (objectstack-ai#4538) (objectstack-ai#4568) Each of the 11 baseline rows judged individually against a three-repo import-level scan (framework, cloud, objectui): - Converged onto the domain zod declaration, re-exported from ./contracts: NotificationChannel (system), ValidationResult / HealthStatus / StartupOptions / PluginStartupResult (kernel), JobExecution (system, after renaming the schema's dead `duration` to the runtime-true `durationMs`), AnalyticsQuery (data, after the schema dropped the `timezone` .default('UTC') the /analytics entry always refused to apply - absence means the engine resolves org timezone, objectstack-ai#1982/objectstack-ai#2018). IStartupOrchestrator.orchestrateStartup now takes StartupOptionsInput. - Renamed: contracts DriverCapabilities -> AnalyticsDriverCapabilities (two live concepts shared one name; the data domain's driver feature-flag record keeps it). @objectstack/service-analytics re-export renamed in lockstep. - Removed dead domain-side declarations (zero consumers in all three repos; last of the objectstack-ai#4411 family): system MetadataExportOptions(Schema) / MetadataImportOptions(Schema) - the contracts IMetadataService parameter interfaces own the names now - and the system `JobSchedule = Schedule` back-compat alias (authoring tier keeps its real name, Schedule). @objectstack/metadata now re-exports the two Metadata*Options names from ./contracts, the shape its own manager implements. All 11 dual-source-exports.baseline.json rows deleted; baselines (api-surface, authorable-surface, json-schema.manifest, reference docs) regenerated via check:generated --fix; the authorable-surface lines for the removed runtime option bags deleted by hand per the objectstack-ai#4458 precedent (not authorable metadata - no tombstone, no D2 conversion). Out-of-scope finding filed unassigned as objectstack-ai#4567 (defineJob cron envelope vs croner). Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang
added a commit
that referenced
this pull request
Aug 20, 2026
…ly (#10221) CI caught the first version's regression: it cached every outcome (including a successful read) for 30s, which broke packages/qa/dogfood/test/analytics-timezone.dogfood.test.ts (#1982/#2018) — that dogfood test writes a new org timezone via the real settings route and expects the very next analytics read to bucket under it. Narrow the cache to memoize ONLY the case where the underlying sys_setting read genuinely throws (a backend fault, e.g. "no such table") — the case that actually produces the log spam #10221 reports. A successful read, including a legitimate empty result, is never cached and always re-reads, so a settings write is visible on the very next call, matching pre-existing behavior. Functional fallback (UTC/en-US on failure) is unchanged. Added two tests pinning the never-cache-a-success guarantee directly (value change and first-write-after-empty, both visible on the next call with no TTL advance), and updated the existing failure-path tests' description accordingly.
This was referenced Aug 20, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 23, 2026
…epeated sys_setting read noise (objectstack-ai#10301) * fix(core): cache resolveLocalizationContext across requests (objectstack-ai#10221) A fresh environment's sys_setting table doesn't exist yet, so every authenticated request re-issued the same localization read, and every one failed the same way — driver-sql's backendStatementFault logs a [sql-driver] DATABASE_ERROR warning on every failed read, burying real errors between the noise. resolveLocalizationContext now memoizes its result (including the missing-table fallback to UTC/en-US) for 30s per (ql, tenantId, userId), mirroring the TTL cache plugin-audit/audit-writers.ts already applies to this same read. Functional behavior is unchanged; only the repeated per-request query is eliminated. * fix(core): narrow resolveLocalizationContext cache to failed reads only (objectstack-ai#10221) CI caught the first version's regression: it cached every outcome (including a successful read) for 30s, which broke packages/qa/dogfood/test/analytics-timezone.dogfood.test.ts (objectstack-ai#1982/objectstack-ai#2018) — that dogfood test writes a new org timezone via the real settings route and expects the very next analytics read to bucket under it. Narrow the cache to memoize ONLY the case where the underlying sys_setting read genuinely throws (a backend fault, e.g. "no such table") — the case that actually produces the log spam objectstack-ai#10221 reports. A successful read, including a legitimate empty result, is never cached and always re-reads, so a settings write is visible on the very next call, matching pre-existing behavior. Functional fallback (UTC/en-US on failure) is unchanged. Added two tests pinning the never-cache-a-success guarantee directly (value change and first-write-after-empty, both visible on the next call with no TTL advance), and updated the existing failure-path tests' description accordingly. * fix(core): drop unnecessary as-any cast in the inlined sys_setting read check:query-options-erasure caught it: the previous commit's inlined ql.find() call (added to observe read failure for the objectstack-ai#10221 cache) cast its options literal to any, a NEW counted erasure site distinct from tryFind's existing grandfathered one. ql is already typed any, so the cast was redundant — tsc doesn't need it, and dropping it restores the ratchet to its pre-existing count. No behavior change. --------- Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…e run, so the shards stop scattering it (objectstack-ai#17867) Clause-②: no Closes objectstack-ai#16886 The `dogfood:` job carried the same run-level Turbo passthrough defect that PR objectstack-ai#16868 fixed in the `test` job for objectstack-ai#16395. It was outside that card's declared surface, so it was still there. This applies objectstack-ai#16868's landed diff as the template rather than a second working spelling — the two jobs are now spelled the same way. ## The site, measured on this branch (⛔ not the card's line number) The card said `.github/workflows/ci.yml:1283`; found by content, the run sits at **`:1520`** on the merge base `c88fa2ccd` and at `:1579` after this diff. The job header is `:1423` and `name:` is `:1431` — the card's `:1186` / `:1209` are 2026-09-08 readings and this file moves constantly. ``` 1520: pnpm turbo run test --filter=@objectstack/dogfood --log-order=stream -- --shard=${{ matrix.shard }}/3 ``` ## The change 1. A dedicated, guard-wrapped, **passthrough-free** `pnpm turbo run build --filter=@objectstack/dogfood --concurrency=4 --log-order=stream` step ahead of the sharded run, so the closure is built once and reaches the shared, content-addressed `build` cache. 2. `--only` on the sharded `turbo run test`, so the passthrough hash covers only the `test` task it is meant for. It is **its own step**, not a second guarded run inside the existing one, for the reason objectstack-ai#16868 states: a guarded SITE is the triple (file, job, step), and `measure-stall-guard-headroom` refuses a verdict when two guarded runs share one. ⛔ `name:` is untouched (required-status-check context) and `timeout-minutes: 30` is untouched — the card names that budget as the instrument that shows the fix working, so it may not be raised to buy room. ## Cache reading — before / after, `--dry=json` task hashes turbo 2.10.10, `--filter=@objectstack/dogfood`, on this branch. The plain test plan is **67 tasks: 66 `build` + 1 `test`**. **Before** — every task in the run re-hashed per shard: ``` plain vs plain (control, fires) tasks 67/67 identical=67 changed=0 plain vs -- --shard=1/3 tasks 67/67 identical=0 changed=67 -- --shard=1/3 vs 2/3 tasks 67/67 identical=0 changed=67 -- --shard=2/3 vs 3/3 tasks 67/67 identical=0 changed=67 ``` So the three shards hashed one 66-package build closure three ways, and none could reach the `build` cache every other job in this workflow populates — that cache is written with no passthrough in the hash. **After** — the two runs the new spelling issues: ``` turbo run build --filter=@objectstack/dogfood 67 tasks, every one a #build task vs the plain test plan's ^build closure (66) hash-identical=66 differing=0 missing=0 (the 67th node is @objectstack/dogfood#build itself, whose command is NONEXISTENT in the dry output because the package declares no `build` script — it executes nothing and is only the root its 66 `^build` dependencies hang from) turbo run test --filter=@objectstack/dogfood --only -- --shard=k/3 k=1: 1 task k=2: 1 task k=3: 1 task -> ['@objectstack/dogfood#test'] --only shard=1/3 vs itself (control, fires) identical=1 changed=0 --only shard=1/3 vs --only shard=2/3 identical=0 changed=1 ``` The 66 closure builds now **replay** instead of re-executing per shard: their hashes are identical to the passthrough-free test plan's, which is what the shared cache holds. The one remaining per-shard hash difference is the `test` task itself, which is correct and intended — that is the task the passthrough is for. ## ⭐ The sweep: `--filter` plus a run-level passthrough, whole tree Triage asked for the count before closing, so here is the probe and the count rather than a claim. **Probe shape.** A tokenizing scan (`shlex`, quote-stripped, backslash continuations joined first) over all 37 files in `.github/workflows/`. For each `turbo run` occurrence it tokenizes **from the `turbo` token onward** and asks three questions: does it carry `--filter`/`-F`; is there a bare `--` token *after* `turbo run` with at least one argument behind it; does it carry `--only`. Starting at the `turbo` token is the load-bearing part — a naive `-- ` grep reports `set -- pnpm turbo run …` (the shell builtin) and `node run-with-stall-guard.mjs … -- pnpm turbo …` (the wrapper's own separator) as passthroughs. That first, naive grep over this same tree produced five hits, of which **three were the wrong `--` entirely**; the classification below is from the tokenizing probe. 37 files, 40 `turbo run` textual matches — **23 executable**, 17 prose in comments (classified, not dropped). **Count: 3 sites carry `--filter` plus a run-level passthrough. 2 of them were the defect; both are now fixed; there is no third.** | site | filter | run-level passthrough | `--only` | verdict | |:---|:---:|:---:|:---:|:---| | `ci.yml:738` job `test`, slice leg | yes | `-- "--shard=$SLICE"` | **yes** | same defect, fixed by objectstack-ai#16868 — the template | | `ci.yml:1577` job `dogfood` | yes | `-- --shard=k/3` | **yes** (this PR) | same defect, fixed here | | `test-nightly-tiers.yml:281` job `tiers`, slice leg | yes | `-- "--shard=$SLICE" $REPORTER` | no | **same defect, outside this card's fence** — filed as objectstack-ai#17866 | **Zeros, reported as asked.** The other 20 executable `turbo run` invocations: 14 carry a `--filter` and **no** passthrough (the plain `turbo run build` legs in `ci.yml`, `lint.yml`, `cut-rc.yml`, `showcase-smoke.yml`); 4 carry neither; and 2 (`rerun-safety-nightly.yml:99`/`:130`) carry neither a filter nor a passthrough. **Zero sites outside the table above carry both.** **One blind spot in the probe, declared.** A filter arriving through a shell variable is invisible to a token-level scan: `test-nightly-tiers.yml:276` is `turbo run test $FILTERS … -- $REPORTER`, where `$FILTERS` is built as `--filter=$PKG` in the loop immediately above it. The probe scores it `filter=0`; read by hand it is a **fourth** instance of the same class, and the only one whose *whole-package* leg carries a passthrough (`ci.yml`'s corresponding leg carries none). Both nightly sites are in objectstack-ai#17866 with their own measurement; they are not fixed here because that workflow is `schedule`-triggered, outside the required set, so a fix would land unexercised by any PR's CI — and the `:276` half needs a derivation the pinned shape does not supply. ## ⭐ Reverse-read: which existing sentence does this make false? **Sentences made false: zero.** The two present-tense claims in range, both in this job: - `Restore Turbo cache`, still on the tree: *"the turbo test hash differs per shard (pass-through args are part of the task hash)"* — still **true**, and still the reason its key is shard-scoped: the `test` task keeps the passthrough. **Left alone.** - Same comment: *"turbo's cache is content-addressed per task, so another shard's entries replay the shared build closure even when the test slice differs"* — this one ran the **other** direction. It was **false before this diff** (the closure was hashed three ways, so another shard's entries could not replay it) and this change makes it **true**. Left alone, now correct. - The test step's *"The `--` args reach the package's `vitest run` and are hashed into the turbo task, so each shard caches independently"* — not false, but no longer the whole story under `--only`. **Extended in place**, not deleted: it now says the passthrough is hashed into that one task and that the closure it used to scatter is built once by the step above. **Sentence kinds, distinguished as asked:** nothing carrying a rev or a date was touched (`objectstack-ai#2018`, `objectstack-ai#4250`, `objectstack-ai#4859`, `objectstack-ai#4928`, the objectstack-ai#16868 measurement block — all history). The named-direction zeros elsewhere in the file (`check:required-contexts` pins, the `!@objectstack/dogfood` exclusion rationale, the "NOT the dogfood job's vitest `--shard` passthrough, deliberately" note at `:295`) are unaffected: the passthrough stays, it only stops being hashed into 66 other tasks. **No bare present-tense magnitude in the tree became stale** — the 66/67 counts this diff introduces are new, and each is written with the tree and the turbo version it was taken against. **Pin tests on the current behaviour: zero found.** `--only` appears in no test or gate assertion about this job; `check:stall-guard-budget` reads the step *structurally* and re-derived it green (below), rather than pinning a step list. ## Gates `scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived **44 commands** from the real change set (1 path, vs merge base `c88fa2ccd`). Ran all 44; reconciled the ran-list back against the deriver. - **39 green.** The ones that read this diff: `check:stall-guard-budget`, `check:stall-guard-headroom`, `check:required-contexts`, `check:agent-test-spelling`, `check-ci-filter-parity`, `check:shard-attestation`, `check-step-collectors`, `check:workflow-step-name-quoting`, `check:workflow-status-functions`, `check:pnpm-filter-targets`, `check:nul-bytes`. - `check:stall-guard-budget` verdict line, which proves the new site was measured rather than skipped: ``` check-stall-guard-budget: OK (37 workflow file(s), 62 job(s), 692 step(s), 10 guard-wrapped step(s); every effective cap clears its job budget by at least one stall window). .github/workflows/ci.yml:1538 job `dogfood` step `Build the dogfood package's dependency closure` window 10m (explicit) · cap 20m (2x window) · budget 30m (job timeout-minutes) · slack 10m (1 of 2 guarded steps in this job; they share one timeout clock) ``` — the same window/cap/budget shape as `test`'s pair, and 10 guard-wrapped steps where the tree had 9. - **4 NOT MEASURED, exit 3 (`PREREQUISITE NOT MET`), not failures:** `check:dts-closure`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`, `check:sourcemap-no-sources-content`. All four read built `dist/` trees and say so in their own refusal text. They are placed by a whole-tree declaration, not by this diff's path, and a workflow-YAML-only diff moves no input of theirs. CI builds and runs them. - **1 NOT MEASURED, incomplete:** `check:pm-dispatch-gates` exceeded a 560-second foreground budget inside its own self-test at 1507 passing assertions and **zero** failures. Left to CI. **Lint scope — a measured narrowing, not a skipped run.** Repo-level `pnpm lint` (`eslint . --no-inline-config`) is CI's run. The narrowing here is that this diff contains **zero files eslint judges**, and all three pieces of evidence: (1) eslint's own answer for the one file, from its own config — `File ignored because no matching configuration was supplied`; (2) `--format json` over the diff — 1 file, **0 errors**, 1 warning, that notice; (3) invariance for untouched files — this repo runs one `eslint.config.mjs` which never enables type-aware linting for any file (`eslint.config.mjs:326-329`, measured there with a positive control), so nothing in this diff can move any untouched file's verdict. Taken at `21d717d5`, the final commit. ## Changeset: skipped, and measured first `skip-changeset` label applied. Nothing published moves: of the **70** packages declaring a `files[]`, **zero** ship any `.github/workflows` path; positive control — `@objectstack/spec`'s `files[]` reads back non-empty with its real shipped paths. The one other `.github` directory in the tree (`packages/create-objectstack/src/templates/blank/.github`) is a different path and is untouched. This is also the fast-track case: a repo-root CI config. ## Clause-② `Clause-②: no`, declared line-initial at the top of this body and self-verified against `readClause2Line()` read fresh from `scripts/pm/check-clause2-carriers.mjs:909` — whose `CLAUSE2_KEY_LINE` now has three capture groups. The substantive answer matches the diff: one CI workflow file, no schema key, no closed-set member, no published export, no registry entry, so no C5 widening tell either. --- _Generated by [Claude Code](https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU)_ Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Oct 7, 2026
…gfood's files outside src to the commits and ADR that decided them (objectstack-ai#20898) Part of objectstack-ai#20594 Clause-②: no ## What changed This is stage 15 of the `domain:cli` lane's dead-citation sweep: **`packages/qa/dogfood`'s dead citations in files outside `src/**`**, the largest package left after stage 14 (claim `5914651671`). The package has no `src/` at all, so the surface is its `test/**`, `README.md`, `tsconfig.json` and `vitest.config.ts`. Every comment site on that surface whose tracker number answers 404 now cites the object that decided what the line describes, in ruling C+D's form C (comment `5749154545` on objectstack-ai#19123): the ADR clause when one records the ruling, otherwise the commit in this repository's history. Stages 1 to 14 of this card are the precedents; the latest is PR objectstack-ai#20883. - **94 sites on 93 lines in 27 files**, covering **24 numbers**, now cite **24 distinct commits and one ADR clause** (ADR-0029 D9.2a). - The 27 files are `vitest.config.ts` and 26 under `test/**`: 21 test files, 4 helper modules and 1 fixture. - **Comments only.** 93 lines out and 93 in, and every file keeps its line count. A token guard (below) shows zero non-comment tokens changed. - **String literals, `describe` / `it` titles and messages are untouched.** They belong to objectstack-ai#20752 (form D, fold `5911936313`); 28 such sites stay on this file list, listed in the acceptance notes. - **No tracker number is added.** The live numbers that share a changed line stay as they were: objectstack-ai#4757, objectstack-ai#6915, objectstack-ai#7987, objectstack-ai#8074, objectstack-ai#8284, objectstack-ai#8408, objectstack-ai#9719, objectstack-ai#11373 and objectstack-ai#17978 all answer 200. - **Sites left without a deciding commit: none.** Every one of the 24 numbers has one. - Where an earlier stage anchored a number and the line here describes the same decision, the same anchor is reused: 16 of the 24 numbers. The other 8 anchors are new (marked below). - A trailing box rule (`── … ─────`) is shortened by the characters its heading gained, never below one dash. Three of the five section headers keep their width; `admin-route-nonadmin-refusal:339` grows by 2 characters and `two-doors-permission:81`, which already ended in one dash, by 11. ## Census **Instrument.** The card's gate does not read these files: its declared surface is `packages/**/src/**`, and `surfaceFor` answers null for all 27 touched paths (the control `packages/cli/src/commands/init.ts` answers `package-docblocks`). So the census is taken by hand, with the gate's own grammar, as stage 14 took it: - **Files:** every tracked file of `packages/qa/dogfood` outside `src/**`, with `CHANGELOG.md` (claim) and `package.json` (not on the claim's list) left out. That is 178 files. - **Extraction:** `extractCitations` from `scripts/check-issue-citations.mjs`, with its comment-prose projection for code and JSONC files and the whole file for `README.md`. The whole-file extraction minus the comment projection gives the string sites. - **Numbers kept:** only those naming this repository (`namesThisRepository`). - **Probe:** each distinct number, `GET /repos/objectstack-ai/objectstack/issues/N`; 404 means dead. | | tree | probe window (UTC) | numbers | answer 200 | answer 404 | dead comment sites | comment sites | dead string sites | |---|---|---|---|---|---|---|---|---| | before | base `4edb61449b` | 2026-09-30 15:46:43 to 15:49:29 | 386 | 359 | 27 | **94** (24 numbers, 27 files) | 1,145 | 28 (13 files) | | after | head `57ffb83b00` | 2026-09-30 15:59:37 to 16:02:15 | 375 | 359 | 16 | **0** | 1,051 | 28 (13 files) | - The before count matches stage 14's census at `660a9b247`: 94 comment sites and 28 string sites in 13 files. - No number present in both probes changed its answer. The 11 numbers that left the census were only on the rewritten lines. - Comment sites fell by exactly 94, the rewritten sites. String sites did not move. - `README.md` cites objectstack-ai#2018 and objectstack-ai#1994, and `package.json`'s description cites objectstack-ai#2018, objectstack-ai#1994 and objectstack-ai#2004. All answer 200. ## Per-number anchors Each anchor was checked by blame on the site, and in the anchor's own message or diff. "Reused" names an earlier stage that gave the number the same anchor. | number | sites | anchor | what it decided | | |---|---|---|---|---| | `objectstack-ai#6293` | 9 | `c39a911ae` | the build stand-in: `build-shaped-artifact.ts` runs the real lowering, and no published surface grows (its subject names the card) | reused (cli `src`) | | `objectstack-ai#6483` | 15 | `ee58392e1` | the ADR-0005 rollback of `permission` (and eight more types) to `allowOrgOverride: false`; `allowRuntimeCreate` stays open | reused (spec, plugin-security, runtime) | | `objectstack-ai#8460` | 4 | ADR-0029 D9.2a | the ruling itself, 2026-08-13, option A "tenant wins"; implemented in `01a7337fc`, which amends the ADR | reused (spec stage 6) | | `objectstack-ai#8676` | 9 | `d6e80b28b` | flags `sys_account.password` and `previous_password_hashes` `internal` | reused (plugin-auth) | | `objectstack-ai#8711` | 3 | `2ce1eb41b` | half (2): the ruled narrowing of the matrix's completeness claim to routes (its message records the maintainer ruling) | **new** | | `objectstack-ai#8711` | 1 | `60ade586e` | half (1): the two `active` rows with no `covers`, and why (`matrix.ts:393`) | **new** | | `objectstack-ai#8811` | 1 | `d6e793507` | adds the `grant-validity-window` matrix row (its subject names the card) | **new** | | `objectstack-ai#8839` | 7 | `c25b2d52a` | comment moderation stops being dead behind the delete floor; ruling of 2026-08-15, reading 1 | reused (plugin-security) | | `objectstack-ai#8919` | 1 | `b5378550e` | gates `/meta` publish and rollback on `manage_metadata`, with the enumeration pin | reused (rest, runtime, cloud-connection) | | `objectstack-ai#9797` | 7 | `1258dcaee` | the PR itself: the opt-in whole-operation dispatch that restores the unscoped multi-delete refusal | **new** | | `objectstack-ai#9934` | 2 | `79c46da90` | the producer-side `userMessage` marking, including the QuickJS side-channel | reused (types, rest, client, runtime, plugin-hono-server, spec) | | `objectstack-ai#10243` | 1 | `266436a7f` | the toggle ruling: `POST /automation/:name/toggle` joins the `manage_metadata` write set (`automation-toggle-tenant-scope:4`) | reused (runtime, service-automation) | | `objectstack-ai#10243` | 1 | `02b41232d` | the measured cross-organization toggle leak ADR-0126 §7.2 retires (`packaged-activation-ledger-reach:291`) | reused (runtime, service-automation) | | `objectstack-ai#10943` | 1 | `46d34ab7c` | `fallbackImport` becomes a caller-supplied parameter | reused (types, cli, verify) | | `objectstack-ai#10996` | 1 | `02b41232d` | the PR itself, the first landing of this file as a measurement | **new** | | `objectstack-ai#11477` | 7 | `6dd3e6968` | `/admin/remove-user` authorizes before the break-glass guard (its message records "Ruled option A on" the card) | reused (plugin-auth, verify) | | `objectstack-ai#11530` | 1 | `033a34c7c` | the PR itself: retires the `set_user_role` console action | **new** | | `objectstack-ai#11686` | 1 | `7131f12bf` | the PR itself: `hasPlatformAdminStanding` as the one authority | **new** | | `objectstack-ai#12176` | 3 | `7986d973f` | stage 3 of the ruled retirement: un-mounts the compound arities, `PUT /meta/:type/:section/:name` among them. The ruling's `D3` ordinal is kept beside it | reused (rest, client, runtime, spec) | | `objectstack-ai#12194` | 1 | `311433f6b` | declares the metadata item-name grammar | reused (rest, client, runtime, spec) | | `objectstack-ai#13214` | 4 | `cc837dbfe` | the ruled ownership gate at `GET /ui/view/:object/:type`; its diff writes these census lines and its message the 2026-08-30 ruling | reused (rest) | | `objectstack-ai#16589` | 4 | `555a89cbd` | `driver-memory` refuses a tenant-scoped call; its diff names the card at every seam | reused (trigger-schedule) | | `objectstack-ai#16659` | 6 | `ecdfc9411` | a time-triggered flow declares its acting organization; its diff adds these pins and the fixture. The `F2` ordinal is kept, as the trigger-schedule stage kept it | reused (trigger-schedule, service-automation, spec, lint) | | `objectstack-ai#16687` | 1 | `779710213` | the PR itself; its squash carries the contract-review patch round the line describes | **new** | | `objectstack-ai#17853` | 1 | `08f5f0e5a` | a vitest filter that selects nothing says so (the same sentence stage 14 rewrote in cli's config) | reused (qa, cli) | | `objectstack-ai#19306` | 2 | `f9e16d856` | a packaged permission set's DELETE stops reporting a deletion; its diff adds both pins | **new** | **ADR and ruling records.** A grep of `docs/adr` and `scripts/adr-anchors` for the 24 numbers finds three: objectstack-ai#8460, objectstack-ai#6483 and objectstack-ai#10243. ADR-0029 D9.2a is the ruling for objectstack-ai#8460 (it carries the number in its heading), so it is cited. The objectstack-ai#6483 hits (ADR-0086, ADR-0094, ADR-0126 and two adr-anchors entries) and the objectstack-ai#10243 hits (ADR-0126, ADR-0131) mention those landings as history; none records the ruling itself, so those two stay on the commit every earlier stage anchored them to. The control number `7329` finds 1 file in the same tree. **Anchor checks:** - **Each sha is unambiguous:** `git rev-parse --disambiguate` gives count 1 for each of the 24. - **Each is a plain commit** with one parent. - **Each is on the base:** `merge-base --is-ancestor` against `4edb61449b` exits 0 for all 24. - **The history is complete:** the checkout is not shallow. Control leg: `255588bd36`, the parent of the oldest anchor `ee58392e1` (2026-08-09), exits 0. Negative control: the base as an ancestor of `ee58392e1` exits 1. ## Verification All at head `57ffb83b00`. Heavy runs went through `scripts/pm/os-verify-lock.sh` with `OS_VERIFY_LOCK_SLOT=issue-20594-dogfood`. - **Build (dependency closure):** `pnpm exec turbo run build --filter='@objectstack/dogfood^...' --concurrency=2` → 63 successful, 63 total (32 cached), lock verdict exit 0. - **Every touched test file ran, by name.** Dogfood has two vitest projects (`shared-showcase` and `isolated`) and no tier split, so one run named all 21 touched test files plus the unit tests of the two touched helpers no touched test imports (`authz-probe-blind-spot.test.ts`, the census module's only importer, and `enterprise-organizations.test.ts`): `pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 FILES` → **Test Files 23 passed (23), Tests 317 passed (317)**, lock verdict command-exit 0. `vitest.config.ts` is loaded by that run. - **Typecheck:** `pnpm --filter @objectstack/dogfood typecheck` (`tsc --noEmit`) → lock verdict command-exit 0. `--listFilesOnly` shows 26 of the 27 touched files in the program; the 27th is `vitest.config.ts`. - **Token guard** (TypeScript leaf tokens via `getChildren`, JSDoc nodes skipped), base `4edb61449b` against head, 27 files, 52,502 base tokens: **0 files differ**. Controls, in memory only: a comment inserted into each file, 0 of 27 differ; a statement appended, 27 of 27; one character flipped inside each file's first `StringLiteral`, 27 of 27. - **Control bytes:** a scan of the 27 files finds 0 (positive control, a scratch file holding U+0001: 1). `pnpm check:nul-bytes` exits 0. - **Derived gates and lint:** listed under "Gates". ## Gates All at head `57ffb83b00`, exit codes captured before any pipe. - **Derivation:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands`, no paths, derives **53 commands** for this change set (27 paths against merge base `4edb61449b`). Re-derived after two fetches of `origin/main` (to `00a92e18da`, then `33b6e8bece`): the same 53, and none of the upstream commits touches a derivation input or a file here. - **All 53 exit 0.** One needed a second run for a reason outside the diff: `pnpm check:dual-build-cjs-loads` first exited 3 (`PREREQUISITE NOT MET`, no `dist/` for 8 packages outside dogfood's build closure); after `pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2` (71 of 71, all cached) it exits 0. - **Reconciliation:** `dispatch-gates --ran` over the recorded `COMMAND :: exit CODE` list → "53 derived, 53 run, 0 NOT-MEASURED, 0 UNRUN", exit 0. - **`pnpm lint`** (repo-wide `eslint . --no-inline-config`, the family the derivation does not name) → exit 0, 2026-09-30T16:13:49Z to 16:18:00Z. - `node scripts/check-issue-citations.mjs` (diff mode, in the 53) reads 0 files, because none of these paths is on its declared surface; the hand census above is the measurement for this surface. - A local `git merge-tree --write-tree` of this head against `origin/main` `33b6e8bece` is clean. **The 53 derived commands:** - `node scripts/check-ci-filter-parity.mjs` - `node scripts/check-closing-keyword-parity.mjs` - `node scripts/check-closing-keyword-parity.mjs --self-test` - `node scripts/check-comment-mask-adoption.mjs` - `node scripts/check-comment-mask-adoption.mjs --self-test` - `node scripts/check-comment-mask-corpus.mjs` - `node scripts/check-issue-citations.mjs` - `node scripts/check-keyed-text-bounds.mjs` - `node scripts/check-keyed-text-bounds.mjs --self-test` - `node scripts/check-platform-object-tenancy-census.mjs` - `node scripts/check-platform-object-tenancy-census.mjs --self-test` - `node scripts/check-plugin-teardown-shape.mjs` - `node scripts/check-plugin-teardown-shape.mjs --self-test` - `node scripts/check-registry-log-declared.mjs` - `node scripts/check-registry-log-declared.mjs --self-test` - `node scripts/check-rest-log-spy-declared.mjs` - `node scripts/check-rest-log-spy-declared.mjs --self-test` - `node scripts/check-system-context-census.mjs` - `node scripts/check-system-context-census.mjs --self-test` - `node scripts/check-undeclared-dep-imports.mjs` - `node scripts/check-undeclared-dep-imports.mjs --self-test` - `node scripts/docs-audit/check-affected-docs.mjs` - `node scripts/docs-audit/check-drift-comment.mjs` - `pnpm --filter @objectstack/spec run check:empty-state` - `pnpm --filter @objectstack/spec run check:liveness` - `pnpm --filter @objectstack/spec run check:strictness-ledger` - `pnpm --filter @objectstack/spec run check:variant-docs` - `pnpm check:cross-package-test-inputs` - `pnpm check:dispatcher-error-vocabulary` - `pnpm check:doc-authoring` - `pnpm check:driver-memory-census` - `pnpm check:dts-closure` - `pnpm check:dual-build-cjs-loads` - `pnpm check:engine-double-contract` - `pnpm check:gitlink-declared` - `pnpm check:issue-citations` - `pnpm check:lean-entry-closure` - `pnpm check:logger-receiver-detach` - `pnpm check:nul-bytes` - `pnpm check:objectql-double-limit` - `pnpm check:org-identifier` - `pnpm check:page-declaration-shape` - `pnpm check:published-files` - `pnpm check:query-options-erasure` - `pnpm check:refd-timer-probe` - `pnpm check:slot-lookup` - `pnpm check:sourcemap-no-sources-content` - `pnpm check:test-source-alias` - `pnpm check:tier-file-adoption` - `pnpm check:type-check-coverage` - `pnpm check:type-check-debt` - `pnpm check:watch-hint-literal` - `pnpm check:where-matcher` ## Acceptance notes - **Changeset:** none. `@objectstack/dogfood` is `private: true`, so nothing here publishes; the PR carries `skip-changeset`. - **28 dead string sites stay on this file list** (16 numbers in 13 files: objectstack-ai#6293, objectstack-ai#6483, objectstack-ai#8676, objectstack-ai#8710, objectstack-ai#8711, objectstack-ai#8811, objectstack-ai#9934, objectstack-ai#10243, objectstack-ai#11477, objectstack-ai#11530, objectstack-ai#11686, objectstack-ai#11757, objectstack-ai#12176, objectstack-ai#13260, objectstack-ai#16589, objectstack-ai#16659). They are `describe` / `it` titles and string values such as the matrix rows' `note:` text. They belong to objectstack-ai#20752 (form D). - **What remains on this card after this stage** (stage 14's census at `660a9b247`, report `5914100460`): 17 comment sites in the other lane packages outside `src/**` (`plugin-hono-server` 4; `plugin-dev`, `client` and `qa/vitest-filter-preflight` 2 each; `cloud-connection`, `mcp`, `qa/downstream-contract`, `rest`, `runtime`, `types` and `verify` 1 each), the five `cli` sites with no deciding commit, and the 55 off-list sites stage 14 listed. None of them is touched here. - **No open PR touches `packages/qa/dogfood`** (all 10 open PRs' file lists read at 2026-09-30T15:56:00Z). The in-flight branch for objectstack-ai#20862 adds one new file there, `automation-authoring-doors-durable.dogfood.test.ts`, which is not among these 27. --- _Generated by [Claude Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_ Co-authored-by: Claude <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A follow-up to ADR-0053 Phase 2 (#1982) browser verification: setting a non-UTC organization timezone (localization manifest, #2006) did not change a date-bucketed analytics chart. The unit tests for the tz bucketing engine passed, but the result never reached the API — the classic "passes build / silent at runtime" gap.
Tracing it revealed three independent broken seams, all required for an org timezone to actually drive a date bucket:
@objectstack/service-analytics— wrong strategy won.NativeSQLStrategy(priority 10) handled every cube/dataset query on a SQL driver, but it groups by the raw column (GROUP BY <col>, nodate_trunc) and referencestimezonenowhere. So date dimensions never bucketed (one row per raw timestamp) and a non-UTC zone was silently dropped. It now declines queries carrying atimeDimensions[].granularity, handing them to the lower-priorityObjectQLStrategy→engine.aggregate(native bucketing when UTC-safe, uniform in-memory bucketing when non-UTC).@objectstack/objectql— count-all returned 0 in-memory. The Cubecountmeasure and a fieldless datasetcountboth compile tosql: '*'. The in-memorycountbranch treated'*'as a column name and counted non-null of a non-existent property → 0 for every bucket. The driver'sCOUNT(*)masked it, but the in-memory path (non-UTC date buckets,driver-rest/driver-memory) returned zeros.'*'is now counted as all rows.@objectstack/rest— context had no timezone.resolveExecCtxnever resolved the localization timezone/locale, so/analytics/dataset/queryalways ran withtimezone: 'UTC'. It now resolves them through thesettingsservice (honouring the 4-tier cascade incl. theOS_LOCALIZATION_TIMEZONEenv override), mirroring the dispatcher path'sresolveLocalization.Verification
End-to-end against
example-crmwith a lead created at…T03:5x:…Z(UTC day06-18, LA day06-17):06-18cnt 506-18cnt 5America/Los_Angeles(settings)06-18cnt 5 ❌06-17cnt 5 ✅selection.timezone=America/LA06-17×… cnt 0 ❌ (uncollapsed)06-17cnt 5 ✅Tests
native-sql-granularity-decline.test.ts— pins the production capability shape (nativeSql: trueANDobjectqlAggregate: true); a granularity query must route toexecuteAggregate, not raw SQL. (The pre-existing granularity test forcednativeSql:false, which masked the bug.)in-memory-aggregationcases for the'*'count-all sentinel (ungrouped + per tz bucket).🤖 Generated with Claude Code