@happyvertical/smrt-reports 0.55.20 → 0.55.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,14 +3,15 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-reports",
6
- "packageVersion": "0.55.20",
6
+ "packageVersion": "0.55.22",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "e3eb8d45b550dd972bd95775e14b73e6c671951ba3f8572a693248b59b4408b1",
11
- "packageJson": "f09066c3d34bb12fe677a5c263dfa82468477c42a51be6449e002d04c3fb00c9",
12
- "agents": "b7d07b1b9812cdc09f3e34f086f1ec33dc9d8ca53eb6dd070e91bd4783642956",
13
- "moduleDoc:agents/runtime-reports.md": "0a4349b36e09dd02fda091f172973d767581e6201d486ea79a5be484e65ef18a"
10
+ "manifest": "30855b693fef8a0b50ed89036d5dd3e4ff0db5f578d7ddd21ff22d4e022b7a24",
11
+ "packageJson": "c45c1d2d7fd13012e5e9abed0ce325d126bd97340b4646a4ef7b4bb1a3732a62",
12
+ "agents": "16b24ae6ad9b15f768befb198807635a9c21d2274ef496cce296eb7383588565",
13
+ "moduleDoc:agents/runtime-reports.md": "cc54255d814cf12ba9e7d2daf44926b2a18825ed5ac9470792afd60e14666a6f",
14
+ "moduleDoc:README.md": "c3f1c14edf5fbf3ef65df1081b83837401c3f9fcc5dcf5bc9a735bde879463d5"
14
15
  },
15
16
  "exports": [
16
17
  ".",
@@ -1154,12 +1155,17 @@
1154
1155
  "polymorphicAssociations": 0,
1155
1156
  "uuidColumns": 24
1156
1157
  },
1157
- "agentDoc": "# @happyvertical/smrt-reports\n\nMaterialized aggregate report models for SMRT.\n\n## Key Pieces\n\n| Module | Purpose |\n| --- | --- |\n| `SmrtReport` | Abstract report row base with `refreshedAt`, `isStale()`, and manual `refresh()` |\n| decorators | `@report`, grouping decorators, time buckets, and aggregate measure decorators |\n| compiler | Pure `ReportDefinition -> AggregateSpec` compiler and ObjectRegistry adapter |\n| aggregate | Compatibility re-export of the SDK aggregate query builder |\n| refresh | Rebuild and incremental refresh engine with run tracking, watermarks, locks, and tenant scoping |\n| state | Internal `_smrt_report_*` system models for runs, watermarks, locks, schedules, and refresh tasks |\n| scheduler | Cron schedule runner, durable refresh job enqueueing, and `onChange` interceptor registration |\n| adapter | Transport-neutral report descriptor, canonical materialized-row reads, and stable `id` row identity |\n| lifecycle | Tenant-safe freshness, run, lock, failure, and manual refresh preview/apply surfaces |\n| views | Policy-revalidated saved views, snapshot-bound exports, bounded job handoff, and artifact metadata validation |\n| runtime reports | Declarative `RuntimeReportSpec` stored as data (`RuntimeReport`), compiled per principal with field-policy, RBAC and tenant checks; see [agents/runtime-reports.md](agents/runtime-reports.md) |\n\n## Adapter contract\n\n- `buildReportAdapterDescriptor()` returns deterministic, serializable metadata\n for a report surface: a stable resource id, typed persisted report columns,\n the canonical `DataQuerySchema`, and UI-neutral DataTable hints. It must not\n import `smrt-ui` or expose a report-domain class to the consumer.\n- `queryReportMaterializedRows()` owns only the bounded read slice for rows that\n are already materialized. It supports projection, offset/limit paging,\n validated filters, deterministic multi-sort with an `id` tie-breaker, exact\n totals, and dimension facets. At source-query compilation,\n dimension and bucket filters compile to `WHERE`, aggregate-measure filters\n compile to `HAVING`, and mixed `OR`/`NOT` filter scopes fail closed.\n- `id` is the only row identity. It must be a non-empty persisted string and is\n never replaced by a display index or page position.\n- The descriptor is an exposure boundary. Sensitive/secret fields, fields with\n `readPermission`, and transient, system, or non-column fields fail closed and\n do not become public columns when no principal is available.\n- `tenantScoped`/`tenantField` reflect actual registered tenant metadata. A\n `tenantScope` option only contributes to the stable resource id; it is not\n authorization. Pass the same `adapter` options to\n `queryReportMaterializedRows()` that were used to build the selected\n descriptor, so results and background tasks retain that stable resource id.\n The default query path resolves the registered collection via `ObjectRegistry`,\n so normal collection tenancy interceptors apply. An injected collection is\n application-owned and must preserve the same boundary.\n- `refresh` is a declaration, not execution. It describes configured mode,\n triggers, positive-TTL stale-read behavior, and a permissioned/audited action\n with preview/apply phases. The adapter remains read-only, while\n `getReportLifecycle()` provides an explicit, tenant-safe lifecycle snapshot\n and `previewReportRefresh()` / `applyReportRefresh()` delegate authorization,\n audit, and queueing through an application action host. Only a registered\n `SmrtReportCollection` may synchronously refresh stale reads, when its TTL is\n positive and the report is not manual.\n- `views` accepts no authority. Persisted views must be normalized again through\n the current descriptor before restoring them, so changed column policy or a\n changed report definition fails closed. Treat unversioned persisted layouts as\n legacy v0 and migrate them to v1 before normalization; unknown versions must\n fail rather than being inferred. Export snapshots retain the canonical\n query fingerprint, projection, sort, as-of/freshness state, exact row count,\n and the fixed principal/tenant/report-definition/field-policy inheritance\n contract. A snapshot needs an application-host-issued opaque binding and an\n explicit offset-page read plan: workers advance it from offset zero instead\n of reusing a visible page. Preview, apply, worker, and artifact-serving\n boundaries must call the validators, including the host's immutable-snapshot\n assertion; application hosts own authorization, audit records, durable\n storage, download tokens, and queue execution.\n\n## Runtime reports (#3711)\n\nA runtime report is data, not code: `parseRuntimeReportSpec()` accepts a closed\ngrammar (no SQL, expressions, joins, tables, or tenants), and\n`compileRuntimeReportSpec()` resolves it against a server-owned source\nallow-list, the ObjectRegistry field policy, the live principal's permissions,\nand an explicit fail-closed tenant predicate. Stored `RuntimeReport` specs are\nre-compiled for the running principal on every run, and result rows are never\npersisted. Assistant tools are in `@happyvertical/smrt-chat`. The full\ncontract, authority model, and deferred work are in\n[agents/runtime-reports.md](agents/runtime-reports.md).\n\n## Recipes (#3719)\n\n`src/recipes.ts` declares `reports.materialized` (`MaterializedReportsRecipe`),\nemitted into the built `manifest.json` and `smrt-knowledge.json` `recipes`. It\nbrings only the internal refresh bookkeeping tables (runs, watermarks, locks,\nschedules, refresh tasks); an app's own `@report` classes are not listed because\nthey belong to the app. Those tables have no REST or MCP surface, so the recipe\nhas no `nav`. It declares `runtime: 'server'` (SQL aggregation, jobs) and no\n`surfaces`: this package ships no Svelte components, and declaring one that does\nnot exist is a scan-time lie. Do not add a surface until a real export exists.\n\nUser-facing help is `src/materialized.recipe.md` (#3591). It explains how the\nassistant reads declared reports (`data.discover`/`data.inspect` and\n`reports.query`/`reports.refresh` from `@happyvertical/smrt-agents`) and how\nruntime reports (`reports.runtime.*` in `@happyvertical/smrt-chat`) differ: live,\nnever stored, saved only after a person confirms. Keep that split accurate when\neither tool set changes. `{field:...}` references need a `@field({ description })`\non `state.ts` so the glossary can explain them. `src/__tests__/recipes.test.ts`\nruns the real scan, manifest and generation passes in memory and renders the help.\nSee [core recipes](../core/agents/recipes.md).\n\n## Conventions\n\n- Report cache tables are normal `@smrt()` tables. Runtime refresh must not create schema.\n- Store report metadata under field `_meta.__report`; scanner and runtime decorators must stay aligned.\n- Keep SQL generation portable. Use the SDK `buildAggregate()`/`bucketExpr()` helpers for time buckets and `$N` placeholders.\n- Do not add a local aggregate SQL builder here; the implementation lives in `@happyvertical/sql`.\n- Refresh runtime tables are schema-managed. Runtime refresh must fail clearly when `_smrt_report_runs`, `_smrt_report_watermarks`, or `_smrt_report_locks` have not been migrated.\n- Incremental refresh requires a source watermark column (default `updatedAt`) and soft-delete column (default `deletedAt`); it recomputes affected groups and deletes empty report groups instead of applying aggregate deltas.\n- Raw aggregate refreshes must explicitly filter `tenant_id`; the tenancy interceptor only protects normal collection reads.\n- Scheduled/on-change refreshes enqueue `SmrtReportRefreshTask.run()` through `@happyvertical/smrt-jobs`; do not add a separate report queue.\n- `SmrtReportCollection` is a framework collection base (core's `SMRT_COLLECTION_BASE_NAMES`): a report collection never owns its model's table, even when an app manifest attributes the two to different packages. `pnpm test:postgres` runs the `*.optional.test.ts` lane (#3110).\n",
1158
+ "agentDoc": "# @happyvertical/smrt-reports\n\nMaterialized aggregate report models for SMRT.\n\n## Key Pieces\n\n| Module | Purpose |\n| --- | --- |\n| `SmrtReport` | Abstract report row base with `refreshedAt`, `isStale()`, and manual `refresh()` |\n| decorators | `@report`, grouping decorators, time buckets, and aggregate measure decorators |\n| compiler | Pure `ReportDefinition -> AggregateSpec` compiler and ObjectRegistry adapter |\n| aggregate | Compatibility re-export of the SDK aggregate query builder |\n| refresh | Rebuild and incremental refresh engine with run tracking, watermarks, locks, and tenant scoping |\n| state | Internal `_smrt_report_*` system models for runs, watermarks, locks, schedules, and refresh tasks |\n| scheduler | Cron schedule runner, durable refresh job enqueueing, and `onChange` interceptor registration |\n| adapter | Transport-neutral report descriptor, canonical materialized-row reads, and stable `id` row identity |\n| lifecycle | Tenant-safe freshness, run, lock, failure, and manual refresh preview/apply surfaces |\n| views | Policy-revalidated saved views, snapshot-bound exports, bounded job handoff, and artifact metadata validation |\n| runtime reports | Declarative `RuntimeReportSpec` stored as data (`RuntimeReport`), compiled per principal with field-policy, RBAC and tenant checks; see [agents/runtime-reports.md](agents/runtime-reports.md) |\n\n## Adapter contract\n\n- `buildReportAdapterDescriptor()` returns deterministic, serializable metadata\n for a report surface: a stable resource id, typed persisted report columns,\n the canonical `DataQuerySchema`, and UI-neutral DataTable hints. It must not\n import `smrt-ui` or expose a report-domain class to the consumer.\n- `queryReportMaterializedRows()` owns only the bounded read slice for rows that\n are already materialized. It supports projection, offset/limit paging,\n validated filters, deterministic multi-sort with an `id` tie-breaker, exact\n totals, and dimension facets. At source-query compilation,\n dimension and bucket filters compile to `WHERE`, aggregate-measure filters\n compile to `HAVING`, and mixed `OR`/`NOT` filter scopes fail closed.\n- `id` is the only row identity. It must be a non-empty persisted string and is\n never replaced by a display index or page position.\n- The descriptor is an exposure boundary. Sensitive/secret fields, fields with\n `readPermission`, and transient, system, or non-column fields fail closed and\n do not become public columns when no principal is available.\n- `tenantScoped`/`tenantField` reflect actual registered tenant metadata. A\n `tenantScope` option only contributes to the stable resource id; it is not\n authorization. Pass the same `adapter` options to\n `queryReportMaterializedRows()` that were used to build the selected\n descriptor, so results and background tasks retain that stable resource id.\n The default query path resolves the registered collection via `ObjectRegistry`,\n so normal collection tenancy interceptors apply. An injected collection is\n application-owned and must preserve the same boundary.\n- `refresh` is a declaration, not execution. It describes configured mode,\n triggers, positive-TTL stale-read behavior, and a permissioned/audited action\n with preview/apply phases. The adapter remains read-only, while\n `getReportLifecycle()` provides an explicit, tenant-safe lifecycle snapshot\n and `previewReportRefresh()` / `applyReportRefresh()` delegate authorization,\n audit, and queueing through an application action host. Only a registered\n `SmrtReportCollection` may synchronously refresh stale reads, when its TTL is\n positive and the report is not manual.\n- `views` accepts no authority. Persisted views must be normalized again through\n the current descriptor before restoring them, so changed column policy or a\n changed report definition fails closed. Treat unversioned persisted layouts as\n legacy v0 and migrate them to v1 before normalization; unknown versions must\n fail rather than being inferred. Export snapshots retain the canonical\n query fingerprint, projection, sort, as-of/freshness state, exact row count,\n and the fixed principal/tenant/report-definition/field-policy inheritance\n contract. A snapshot needs an application-host-issued opaque binding and an\n explicit offset-page read plan: workers advance it from offset zero instead\n of reusing a visible page. Preview, apply, worker, and artifact-serving\n boundaries must call the validators, including the host's immutable-snapshot\n assertion; application hosts own authorization, audit records, durable\n storage, download tokens, and queue execution.\n\n## Runtime reports (#3711)\n\nA runtime report is data, not code: `parseRuntimeReportSpec()` accepts a closed\ngrammar (no SQL, expressions, joins, tables, or tenants), and\n`compileRuntimeReportSpec()` resolves it against a server-owned source\nallow-list (including optional `fields`/`excludeFields`), the ObjectRegistry field policy, the live principal's permissions,\nand an explicit fail-closed tenant predicate. Stored `RuntimeReport` specs are\nre-compiled for the running principal on every run, and result rows are never\npersisted. Assistant tools are in `@happyvertical/smrt-chat`. The full\ncontract, authority model, and deferred work are in\n[agents/runtime-reports.md](agents/runtime-reports.md).\n\n## Recipes (#3719)\n\n`src/recipes.ts` declares `reports.materialized` (`MaterializedReportsRecipe`),\nemitted into the built `manifest.json` and `smrt-knowledge.json` `recipes`. It\nbrings only the internal refresh bookkeeping tables (runs, watermarks, locks,\nschedules, refresh tasks); an app's own `@report` classes are not listed because\nthey belong to the app. Those tables have no REST or MCP surface, so the recipe\nhas no `nav`. It declares `runtime: 'server'` (SQL aggregation, jobs) and no\n`surfaces`: this package ships no Svelte components, and declaring one that does\nnot exist is a scan-time lie. Do not add a surface until a real export exists.\n\nUser-facing help is `src/materialized.recipe.md` (#3591). It explains how the\nassistant reads declared reports (`data.discover`/`data.inspect` and\n`reports.query`/`reports.refresh` from `@happyvertical/smrt-agents`) and how\nruntime reports (`reports.runtime.*` in `@happyvertical/smrt-chat`) differ: live,\nnever stored, saved only after a person confirms. Keep that split accurate when\neither tool set changes. `{field:...}` references need a `@field({ description })`\non `state.ts` so the glossary can explain them. `src/__tests__/recipes.test.ts`\nruns the real scan, manifest and generation passes in memory and renders the help.\nSee [core recipes](../core/agents/recipes.md).\n\n## Conventions\n\n- Report cache tables are normal `@smrt()` tables. Runtime refresh must not create schema.\n- Store report metadata under field `_meta.__report`; scanner and runtime decorators must stay aligned.\n- Keep SQL generation portable. Use the SDK `buildAggregate()`/`bucketExpr()` helpers for time buckets and `$N` placeholders.\n- Do not add a local aggregate SQL builder here; the implementation lives in `@happyvertical/sql`.\n- Refresh runtime tables are schema-managed. Runtime refresh must fail clearly when `_smrt_report_runs`, `_smrt_report_watermarks`, or `_smrt_report_locks` have not been migrated.\n- Incremental refresh requires a source watermark column (default `updatedAt`) and soft-delete column (default `deletedAt`); it recomputes affected groups and deletes empty report groups instead of applying aggregate deltas.\n- Raw aggregate refreshes must explicitly filter `tenant_id`; the tenancy interceptor only protects normal collection reads.\n- Scheduled/on-change refreshes enqueue `SmrtReportRefreshTask.run()` through `@happyvertical/smrt-jobs`; do not add a separate report queue.\n- `SmrtReportCollection` is a framework collection base (core's `SMRT_COLLECTION_BASE_NAMES`): a report collection never owns its model's table, even when an app manifest attributes the two to different packages. `pnpm test:postgres` runs the `*.optional.test.ts` lane (#3110).\n\n## Package registration\n\n`src/__smrt-register__.ts` embeds the provider manifest during library builds.\nImport it before model decorators, including model-bearing subpaths (`state`\nand `scheduler`), so consumer SSR bundles retain reports-qualified identities.\nThe bundle gate executes these public entries in a fresh consumer process.\nAudit #3525 found no other published domain model package missing its shim;\npackages without one contain framework utilities or example/test decorators.\n\n## Schedule lifecycle\n\n`tenantFanout: 'discover'` resolves non-null tenants in the source and existing\nmaterialization at execution time only for signed global scheduled maintenance.\nInclude materialized scopes so rebuilds clear groups after their last source\nrow is deleted; discovery does not change incremental watermark semantics. Empty discovery must not call\nrefresh with an empty tenant array, which would select the global slice.\nSchedule provisioning preserves existing state. Hosts bind terminal job events\nwith `ReportScheduleRunner.bindTaskRunner()` and dispose the returned binding\non shutdown; see [scheduled refresh lifecycle](README.md#scheduled-refresh-lifecycle).\n",
1158
1159
  "moduleDocs": [
1159
1160
  {
1160
1161
  "path": "agents/runtime-reports.md",
1161
1162
  "module": "runtime-reports",
1162
- "content": "# Runtime reports (#3711)\n\nReports the assistant (or any caller) defines at runtime from a user request.\nA report is a **declarative spec stored as data**; there is no codegen, no new\ntable per report, and no SQL, expression, join or raw-identifier syntax in the\ngrammar. Declared (`@report`) reports are unchanged and remain the way to get a\n**materialized** report.\n\n## Files\n\n| File | Role |\n| --- | --- |\n| `src/runtime-spec.ts` | `RuntimeReportSpec` grammar, strict parser, limits, stable hash |\n| `src/runtime-compiler.ts` | `compileRuntimeReportSpec()`, `describeRuntimeReportSource()`, `runRuntimeReport()` |\n| `src/runtime-report.ts` | `RuntimeReport` model (`runtime_reports`), `saveRuntimeReport()`, list/get/archive, `runStoredRuntimeReport()` |\n\nAssistant tools live in `@happyvertical/smrt-chat` (`createRuntimeReportTools()`,\n`src/runtime-report-tools.ts`).\n\n## Spec\n\n`version: 1`, `title`, optional `description`, `source` (a server-owned source\nid), `dimensions[]` (`field`, `as?`, `bucket?`), `measures[]` (`fn`: `count`,\n`countDistinct`, `sum`, `avg`, `min`, `max`; `field?`; `as?`), `filters[]`\n(AND-ed; ops `eq ne gt gte lt lte in notIn contains isNull isNotNull`),\n`having[]` (measure alias, numeric compare), `sort[]`, `limit` (1-1000, default\n100) and a `chart` hint (`table|bar|line|pie|stat`, `x`, `y[]`). Unknown keys\nat any level are rejected. Bounds (entries, string length, `in` list size,\nalias charset, reserved words) are in `RUNTIME_REPORT_LIMITS`.\n\nThe parser normalizes defaults and derives aliases, so equal specs hash equally\n(`runtimeReportSpecHash`). The hash is what a human confirmation binds to.\n\n## Authority model\n\nA spec carries **no authority**. Everything is decided at compile time from\ninputs the caller cannot supply through the spec:\n\n- **Sources.** `RuntimeReportCompileContext.sources` is a server allow-list of\n `{ id, className, collection }`. The spec names an id; unknown and\n not-offered answer identically (`unknown_source`). `authorizeSource` is the\n host's RBAC hook (the chat tools call `run.assertOperation(collection,\n 'read')`) and runs before any field is enumerated. A host should also narrow\n `sources` to those the principal may read before compiling (the chat tools do),\n because `authorizeSource` only runs for an id that is in the list: otherwise\n an RBAC refusal for a configured id and `unknown_source` for an absent one are\n different answers, and a caller can enumerate the configured source ids. `collection` is the\n permission-catalog collection **slug** (the `<collection>` of\n `<collection>.read`, for example `runtimereports`), or the registered model\n class, which resolves to that slug through the catalog's own helper. It is\n never a class name string: `Invoice` derives `Invoice.read`, which no\n catalog contains, so every read is denied and the source is silently not\n offered.\n- **Field policy.** Names are resolved from the ObjectRegistry, never from the\n spec. Not nameable: `sensitive` / `sensitivity: sensitive|secret` fields,\n `readPermission` fields unless `context.permissions` holds that permission,\n `transient`, `json`/relationship fields, `_`-prefixed internals and the tenant\n column. A hidden field is reported exactly like an absent one\n (`unknown_field`), so the compiler is not an existence oracle.\n- **Types.** `sum`/`avg` need numeric fields, `min`/`max` numeric or datetime,\n `bucket` needs datetime, `contains` needs text, id fields accept only UUIDs\n and equality-style ops. Values are coerced to the field type, then bound.\n- **Tenancy.** Raw aggregates bypass collection interceptors, so the compiler\n adds the tenant predicate itself (same column resolution as refresh).\n Tenant-scoped source with no tenant in scope reads only NULL-tenant rows;\n there is no cross-tenant option and `withSystemContext()`/super-admin bypass\n are deliberately not honored. Sources without a tenant column are global by\n definition. `deletedAt` and STI-child discriminators are applied when the\n source has them.\n- **SQL safety.** The plan is an `AggregateSpec` executed by the SDK\n `buildAggregate()`: identifiers are registry-derived and re-validated by the\n builder, values are `$N` parameters, and output aliases are prefixed `r_`\n inside the SQL so an alias can never shadow a source column.\n- **Re-validation.** A stored report is re-parsed (hash-checked) and\n re-compiled against the **running** principal on every run, so policy changes\n apply immediately and a viewer without a permission cannot run a report saved\n by someone who had it.\n- **Stored-report storage.** The tenancy interceptor filters by the AMBIENT\n tenant, while compilation uses the principal's tenant. The chat tools bind the\n two: they enter the principal's tenant context for `list`/`get`/`save` (as the\n data-surface guard does), refuse when an ambient tenant disagrees with the\n principal (on `apply`, before the human is asked to confirm, #3768), require\n a tenant for any stored-report use, and pass the principal\n tenant as an explicit owner filter (`getRuntimeReport`/`listRuntimeReports`\n accept `tenantId`) that still holds under a system context.\n\n`listRuntimeReports` also accepts exact `createdByUserId` and `specHash`\npredicates. They are ANDed with status and explicit/ambient tenant filters before\nlimiting results, so hosts can list a principal's saved reports or find a previous\nsave of the compiled normalized spec without retrieving other authors' reports.\nThese filters never replace authorization. See the\n[list-filter contract and test matrix](runtime-report-list-filters.md).\n\n## Storage and surface\n\n`RuntimeReport` is `@TenantScoped({ mode: 'required' })` with `spec` (normalized\nJSON text), `specHash`, denormalized `title`/`description`/`sourceId`,\n`status` (`active|archived`) and `createdByUserId`. `validateBeforeSave()` parses\nwhatever was written and re-derives every denormalized column, so columns\ncannot disagree with the spec. `getSpec()` throws if the JSON no longer matches\n`specHash` (out-of-band edit). Adopting apps need a `db:migrate` for the new\n`runtime_reports` table; runtime never creates schema.\n\n**Invariant: no row is inserted, and no spec/title/source/description/author is\nchanged, except through the confirmed save.** It is enforced in the model layer,\nnot by which surfaces exist. `validateBeforeSave()` refuses (`confirmation_required`,\n403) every insert, and every update that changes those columns, unless the\ninstance carries a confirmation proof. The proof is a symbol-keyed constructor\noption plus a module-private `WeakMap`; only `saveRuntimeReport()` can mint it,\nand only when `confirmedSpecHash` equals the compiled plan's hash (the value\nhanded to the application's `RuntimeReportConfirmationHost`). It is single use\nand tied to that exact spec hash. A REST body, a manifest tool call or\n`new RuntimeReport({...}).save()` cannot carry it, so it is refused whatever\nsurface delivered it. A reused id on an insert is refused the same way, so it\ncannot overwrite a stored spec. Archiving or unarchiving (a `status` change with\nthe stored columns untouched) is allowed for an authorised writer.\n\n**Exposed operations** (these decide the permission catalog and RLS grants):\n\n| Surface | Operations |\n| --- | --- |\n| REST (`api`) | `list`, `get`, `create` |\n| CLI | `list`, `get` |\n| MCP | `list`, `get` |\n\nThe permission catalog therefore holds exactly `<collection>.read` and\n`<collection>.create` for `RuntimeReport` (the collection slug is\n`runtimereports`). `update` and `delete` exist on no surface, so they are not in\nthe catalog, not grantable to a role and not nameable in a persona's\n`allowedTools`. REST `create` exists only so the catalog knows the operation the\nconfirmed save performs (`assertOperation(RuntimeReport, 'create')`); its body is\nalways refused by the invariant above. The generic manifest tool dispatcher in\n`@happyvertical/smrt-chat` additionally never offers or runs a write operation on\n`RuntimeReport` (`isRuntimeReportWriteOperation`), so the assistant persists a\nreport only through `reports.runtime.define` (apply).\n\n**Result rows are never persisted.** A result computed for one principal must\nnot be served to another, so the table stores definitions only.\n\n## Why not materialize\n\nDeclared reports materialize into a build-time `@smrt()` table and refresh\nthrough the lock/watermark engine. Runtime schema creation is forbidden here, so\na runtime spec runs as a bounded live aggregate on the same `AggregateSpec`\ncompile target instead. Promoting a proven runtime spec to a declared,\nmaterialized `@report` (codegen plus a migration) is a maintainer decision and\nis tracked as a follow-up.\n\n## Money\n\nMoney fields are integer minor units; `sum`/`min`/`max` keep the integer type and\nthe source field's `format` hint is carried onto the result column. Integer\nresults beyond the JavaScript safe range throw `invalid_result` rather than\nlosing precision. `avg` is decimal.\n\n## Assistant flow (`@happyvertical/smrt-chat`)\n\n`reports.runtime.sources` (field catalogue for the principal) ->\n`reports.runtime.define` `phase: 'preview'` (validate, compile, bounded sample;\nnothing stored) -> a human confirms -> `phase: 'apply'` with the previewed\n`specHash` -> `reports.runtime.list` / `reports.runtime.run`. `apply` needs an\napplication-owned `RuntimeReportConfirmationHost.confirmSave()` that resolves\nonly after a person approved that exact spec; without a host the tools are\npropose-only, and a model-supplied `confirmed` flag is ignored. Offer the tools\nvia the persona conversation `extraTools` seam; they use the same fail-closed\n`allowedTools` gates, and `RuntimeReportError.status` (422/403/500) lets\n`classifyToolError` return repairable spec problems to the model.\n\n## Not done (needs #3708 or a design call)\n\n- A `reports.runtime` recipe for `RuntimeReport` itself. `reports.materialized`\n (see `AGENTS.md`) covers declared reports only and its help points at the\n `reports.runtime.*` tools; `RuntimeReport` has no recipe of its own yet.\n- A browser UI for the confirmation host and for rendering the chart hint.\n- Archive/rename tools, scheduling a runtime report, exporting its rows.\n"
1163
+ "content": "# Runtime reports (#3711)\n\nReports the assistant (or any caller) defines at runtime from a user request.\nA report is a **declarative spec stored as data**; there is no codegen, no new\ntable per report, and no SQL, expression, join or raw-identifier syntax in the\ngrammar. Declared (`@report`) reports are unchanged and remain the way to get a\n**materialized** report.\n\n## Files\n\n| File | Role |\n| --- | --- |\n| `src/runtime-spec.ts` | `RuntimeReportSpec` grammar, strict parser, limits, stable hash |\n| `src/runtime-compiler.ts` | `compileRuntimeReportSpec()`, `describeRuntimeReportSource()`, `runRuntimeReport()` |\n| `src/runtime-report.ts` | `RuntimeReport` model (`runtime_reports`), `saveRuntimeReport()`, list/get/archive, `runStoredRuntimeReport()` |\n\nAssistant tools live in `@happyvertical/smrt-chat` (`createRuntimeReportTools()`,\n`src/runtime-report-tools.ts`).\n\n\nSource definitions may set `fields` (an allow-list) and `excludeFields` (a deny-list).\nBoth use exact public model field names. Omitted `fields` retains all eligible\nfields; `fields: []` exposes none. Exclusions win, and neither option bypasses\nmodel sensitivity, field permissions, or tenant restrictions. Invalid lists and\nunknown names reject the source with `unsupported_source`, including misspelled\nexclusions. Descriptions, assistant tools, confirmation and saved-report runs\nresolve this same policy on every invocation.\n\nStandard timestamps have one public spelling: `createdAt` and `updatedAt`.\nSnake-case `created_at`/`updated_at` remain physical SQL columns, not accepted\nspec or configuration field names. Existing saved specs using those aliases must\nbe proposed again with canonical names and confirmed. Model restrictions on any\nalias protect the whole physical column; host exclusions also cover aliases.\n\n```typescript\n{ id: 'incidents', className: 'Incident', collection: 'incidents',\n fields: ['id', 'title', 'createdAt', 'status'],\n excludeFields: ['signatureAssetId'] }\n```\nThe example assumes `signatureAssetId` is a declared model field; exclusions\nare validated even if an allow-list already omits that field.\n\n## Spec\n\n`version: 1`, `title`, optional `description`, `source` (a server-owned source\nid), `dimensions[]` (`field`, `as?`, `bucket?`), `measures[]` (`fn`: `count`,\n`countDistinct`, `sum`, `avg`, `min`, `max`; `field?`; `as?`), `filters[]`\n(AND-ed; ops `eq ne gt gte lt lte in notIn contains isNull isNotNull`),\n`having[]` (measure alias, numeric compare), `sort[]`, `limit` (1-1000, default\n100) and a `chart` hint (`table|bar|line|pie|stat`, `x`, `y[]`). Unknown keys\nat any level are rejected. Bounds (entries, string length, `in` list size,\nalias charset, reserved words) are in `RUNTIME_REPORT_LIMITS`.\n\nThe parser normalizes defaults and derives aliases, so equal specs hash equally\n(`runtimeReportSpecHash`). The hash is what a human confirmation binds to.\n\n## Authority model\n\nA spec carries **no authority**. Everything is decided at compile time from\ninputs the caller cannot supply through the spec:\n\n- **Sources.** `RuntimeReportCompileContext.sources` is a server allow-list of\n `{ id, className, collection }`. The spec names an id; unknown and\n not-offered answer identically (`unknown_source`). `authorizeSource` is the\n host's RBAC hook (the chat tools call `run.assertOperation(collection,\n 'read')`) and runs before any field is enumerated. A host should also narrow\n `sources` to those the principal may read before compiling (the chat tools do),\n because `authorizeSource` only runs for an id that is in the list: otherwise\n an RBAC refusal for a configured id and `unknown_source` for an absent one are\n different answers, and a caller can enumerate the configured source ids. `collection` is the\n permission-catalog collection **slug** (the `<collection>` of\n `<collection>.read`, for example `runtimereports`), or the registered model\n class, which resolves to that slug through the catalog's own helper. It is\n never a class name string: `Invoice` derives `Invoice.read`, which no\n catalog contains, so every read is denied and the source is silently not\n offered.\n- **Field policy.** Names are resolved from the ObjectRegistry, never from the\n spec. Not nameable: `sensitive` / `sensitivity: sensitive|secret` fields,\n `readPermission` fields unless `context.permissions` holds that permission,\n `transient`, `json`/relationship fields, `_`-prefixed internals and the tenant\n column. A hidden field is reported exactly like an absent one\n (`unknown_field`), so the compiler is not an existence oracle.\n- **Types.** `sum`/`avg` need numeric fields, `min`/`max` numeric or datetime,\n `bucket` needs datetime, `contains` needs text, id fields accept only UUIDs\n and equality-style ops. Values are coerced to the field type, then bound.\n- **Tenancy.** Raw aggregates bypass collection interceptors, so the compiler\n adds the tenant predicate itself (same column resolution as refresh).\n Tenant-scoped source with no tenant in scope reads only NULL-tenant rows;\n there is no cross-tenant option and `withSystemContext()`/super-admin bypass\n are deliberately not honored. Sources without a tenant column are global by\n definition. `deletedAt` and STI-child discriminators are applied when the\n source has them.\n- **SQL safety.** The plan is an `AggregateSpec` executed by the SDK\n `buildAggregate()`: identifiers are registry-derived and re-validated by the\n builder, values are `$N` parameters, and output aliases are prefixed `r_`\n inside the SQL so an alias can never shadow a source column.\n- **Re-validation.** A stored report is re-parsed (hash-checked) and\n re-compiled against the **running** principal on every run, so policy changes\n apply immediately and a viewer without a permission cannot run a report saved\n by someone who had it.\n- **Stored-report storage.** The tenancy interceptor filters by the AMBIENT\n tenant, while compilation uses the principal's tenant. The chat tools bind the\n two: they enter the principal's tenant context for `list`/`get`/`save` (as the\n data-surface guard does), refuse when an ambient tenant disagrees with the\n principal (on `apply`, before the human is asked to confirm, #3768), require\n a tenant for any stored-report use, and pass the principal\n tenant as an explicit owner filter (`getRuntimeReport`/`listRuntimeReports`\n accept `tenantId`) that still holds under a system context.\n\n`listRuntimeReports` also accepts exact `createdByUserId` and `specHash`\npredicates. They are ANDed with status and explicit/ambient tenant filters before\nlimiting results, so hosts can list a principal's saved reports or find a previous\nsave of the compiled normalized spec without retrieving other authors' reports.\nThese filters never replace authorization. See the\n[list-filter contract and test matrix](runtime-report-list-filters.md).\n\n## Storage and surface\n\n`RuntimeReport` is `@TenantScoped({ mode: 'required' })` with `spec` (normalized\nJSON text), `specHash`, denormalized `title`/`description`/`sourceId`,\n`status` (`active|archived`) and `createdByUserId`. `validateBeforeSave()` parses\nwhatever was written and re-derives every denormalized column, so columns\ncannot disagree with the spec. `getSpec()` throws if the JSON no longer matches\n`specHash` (out-of-band edit). Adopting apps need a `db:migrate` for the new\n`runtime_reports` table; runtime never creates schema.\n\n**Invariant: no row is inserted, and no spec/title/source/description/author is\nchanged, except through the confirmed save.** It is enforced in the model layer,\nnot by which surfaces exist. `validateBeforeSave()` refuses (`confirmation_required`,\n403) every insert, and every update that changes those columns, unless the\ninstance carries a confirmation proof. The proof is a symbol-keyed constructor\noption plus a module-private `WeakMap`; only `saveRuntimeReport()` can mint it,\nand only when `confirmedSpecHash` equals the compiled plan's hash (the value\nhanded to the application's `RuntimeReportConfirmationHost`). It is single use\nand tied to that exact spec hash. A REST body, a manifest tool call or\n`new RuntimeReport({...}).save()` cannot carry it, so it is refused whatever\nsurface delivered it. A reused id on an insert is refused the same way, so it\ncannot overwrite a stored spec. Archiving or unarchiving (a `status` change with\nthe stored columns untouched) is allowed for an authorised writer.\n\n**Exposed operations** (these decide the permission catalog and RLS grants):\n\n| Surface | Operations |\n| --- | --- |\n| REST (`api`) | `list`, `get`, `create` |\n| CLI | `list`, `get` |\n| MCP | `list`, `get` |\n\nThe permission catalog therefore holds exactly `<collection>.read` and\n`<collection>.create` for `RuntimeReport` (the collection slug is\n`runtimereports`). `update` and `delete` exist on no surface, so they are not in\nthe catalog, not grantable to a role and not nameable in a persona's\n`allowedTools`. REST `create` exists only so the catalog knows the operation the\nconfirmed save performs (`assertOperation(RuntimeReport, 'create')`); its body is\nalways refused by the invariant above. The generic manifest tool dispatcher in\n`@happyvertical/smrt-chat` additionally never offers or runs a write operation on\n`RuntimeReport` (`isRuntimeReportWriteOperation`), so the assistant persists a\nreport only through `reports.runtime.define` (apply).\n\n**Result rows are never persisted.** A result computed for one principal must\nnot be served to another, so the table stores definitions only.\n\n## Why not materialize\n\nDeclared reports materialize into a build-time `@smrt()` table and refresh\nthrough the lock/watermark engine. Runtime schema creation is forbidden here, so\na runtime spec runs as a bounded live aggregate on the same `AggregateSpec`\ncompile target instead. Promoting a proven runtime spec to a declared,\nmaterialized `@report` (codegen plus a migration) is a maintainer decision and\nis tracked as a follow-up.\n\n## Money\n\nMoney fields are integer minor units; `sum`/`min`/`max` keep the integer type and\nthe source field's `format` hint is carried onto the result column. Integer\nresults beyond the JavaScript safe range throw `invalid_result` rather than\nlosing precision. `avg` is decimal.\n\n## Assistant flow (`@happyvertical/smrt-chat`)\n\n`reports.runtime.sources` (field catalogue for the principal) ->\n`reports.runtime.define` `phase: 'preview'` (validate, compile, bounded sample;\nnothing stored) -> a human confirms -> `phase: 'apply'` with the previewed\n`specHash` -> `reports.runtime.list` / `reports.runtime.run`. `apply` needs an\napplication-owned `RuntimeReportConfirmationHost.confirmSave()` that resolves\nonly after a person approved that exact spec; without a host the tools are\npropose-only, and a model-supplied `confirmed` flag is ignored. Offer the tools\nvia the persona conversation `extraTools` seam; they use the same fail-closed\n`allowedTools` gates, and `RuntimeReportError.status` (422/403/500) lets\n`classifyToolError` return repairable spec problems to the model.\n\n## Not done (needs #3708 or a design call)\n\n- A `reports.runtime` recipe for `RuntimeReport` itself. `reports.materialized`\n (see `AGENTS.md`) covers declared reports only and its help points at the\n `reports.runtime.*` tools; `RuntimeReport` has no recipe of its own yet.\n- A browser UI for the confirmation host and for rendering the chart hint.\n- Archive/rename tools, scheduling a runtime report, exporting its rows.\n"
1164
+ },
1165
+ {
1166
+ "path": "README.md",
1167
+ "module": "README",
1168
+ "content": "# @happyvertical/smrt-reports\n\nMaterialized aggregate reports for s-m-r-t. Define a report as a decorated class,\ncompile it to a portable aggregate query, and refresh its normal s-m-r-t table\nmanually, on a schedule, after source changes, or when a TTL expires.\n\nRuntime refresh checks that its tables exist (existence only, not columns or\nindexes) and fails clearly if they do not; it does not create report or system\ntables. Include report models in normal manifest-driven migrations before\nrefreshing.\n\n## Installation\n\n```bash\npnpm add @happyvertical/smrt-reports\n```\n\n## Define a report\n\n```ts\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport {\n groupBy,\n month,\n report,\n SmrtReport,\n SmrtReportCollection,\n sum,\n} from '@happyvertical/smrt-reports';\n\n@smrt()\nclass Invoice extends SmrtObject {\n customerId = '';\n issuedAt = new Date();\n totalAmount = 0.0;\n status = '';\n}\n\n@report({\n source: Invoice,\n where: { status: 'paid' },\n refresh: { manual: true },\n})\n@smrt()\nclass MonthlyRevenue extends SmrtReport {\n @groupBy('customerId') customerId = '';\n @month('issuedAt') issuedMonth = new Date();\n @sum('totalAmount') revenue = 0.0;\n}\n\nclass MonthlyRevenueCollection extends SmrtReportCollection<MonthlyRevenue> {\n static readonly _itemClass = MonthlyRevenue;\n}\n\nconst reports = await MonthlyRevenueCollection.create({ db: 'app.db' });\nawait reports.refresh();\nconst rows = await reports.list({ orderBy: 'issued_month DESC' });\n```\n\n## Decorators\n\n| Decorator | Meaning |\n| --- | --- |\n| `@report({ source, ... })` | Declare the source and refresh policy |\n| `@groupBy(column?)` | Group by a source column |\n| `@minute()` through `@year()` | Bucket a source timestamp |\n| `@sum()`, `@avg()`, `@min()`, `@max()` | Aggregate a source column |\n| `@count()` | Count rows or distinct values |\n| `@aggregate()` | Define a custom supported aggregate shape |\n\nEvery non-system report field must be a grouping key, time bucket, or aggregate.\nMetadata is stored under the field's `_meta.__report` contract so scanner and\nruntime compilation stay aligned.\n\n## Refresh modes\n\n- `rebuild` replaces the materialized result.\n- `incremental` recomputes groups affected after a source watermark and removes\n groups that become empty.\n- TTL-backed collections refresh before reads when the report is stale.\n- `ReportScheduleRunner` and `registerReportRefreshInterceptor()` enqueue\n durable refresh jobs through [`smrt-jobs`](../jobs/README.md).\n\nIncremental sources need watermark and soft-delete columns, normally\n`updatedAt` and `deletedAt`. Tenant-scoped aggregate SQL explicitly filters\n`tenant_id`; normal collection interceptors cannot secure a raw aggregate.\n\n## Public entry points\n\nThe package exposes focused subpaths for consumers that want only the compiler,\ndecorators, refresh engine, scheduler, state models, or aggregate compatibility\nhelpers:\n\n```text\n@happyvertical/smrt-reports\n@happyvertical/smrt-reports/compiler\n@happyvertical/smrt-reports/decorators\n@happyvertical/smrt-reports/refresh\n@happyvertical/smrt-reports/scheduler\n@happyvertical/smrt-reports/state\n@happyvertical/smrt-reports/aggregate\n```\n\n## Report adapter\n\nThe package also exports a transport-neutral adapter for report surfaces:\n\n```ts\nimport {\n buildReportAdapterDescriptor,\n buildReportDrilldownQuery,\n queryReportMaterializedRows,\n reportMaterializedRowKey,\n splitReportFilterScopes,\n} from '@happyvertical/smrt-reports';\n\nconst descriptor = await buildReportAdapterDescriptor(MonthlyRevenue, {\n tenantScope: 'current',\n});\nconst reports = await MonthlyRevenueCollection.create({ db: 'app.db' });\nconst result = await queryReportMaterializedRows(\n MonthlyRevenue,\n {\n version: 1,\n requestId: 'monthly-revenue-first-page',\n mode: 'rows',\n projection: ['id', 'customer_id', 'revenue'],\n filter: {\n kind: 'all',\n filters: [\n {\n kind: 'condition',\n field: 'customer_id',\n operator: 'eq',\n value: 'customer-42',\n },\n {\n kind: 'condition',\n field: 'revenue',\n operator: 'gte',\n value: 1000,\n },\n ],\n },\n page: { kind: 'offset', offset: 0, limit: 25 },\n sort: [{ field: 'revenue', direction: 'desc' }],\n },\n {\n collection: reports,\n // Preserve the resource identity of the descriptor selected above.\n adapter: { tenantScope: 'current' },\n },\n);\nconst rowKey = reportMaterializedRowKey(result.rows[0]);\nconst drilldown = await buildReportDrilldownQuery(MonthlyRevenue, result.rows[0]);\n```\n\n`ReportAdapterDescriptor` is deterministic JSON with a stable resource id,\n`id` as the identity field, typed report columns, a canonical `DataQuerySchema`,\nand structural DataTable hints. The hints are deliberately not a `smrt-ui`\ndependency. Consumers may map the descriptor to their own presentation layer.\nThe adapter exposes only persisted report columns and the primary key: transient,\nsystem, and non-column fields are not surfaced. Sensitive/secret fields and\nfields with a `readPermission` are excluded without a principal, so the\ndescriptor fails closed.\n\nThe `dataTable.columns` entries carry neutral `headerPath`, `valueFormat`,\nalignment, role, and responsive hints. Grouping fields, time buckets, and\naggregate measures receive deterministic multi-level header ancestry; a\nconsumer can override any column by stable id with\n`buildReportAdapterDescriptor(..., { dataTable: { columns: { ... } } })`.\n`valueFormat` is an instruction for the rendering boundary only: rows returned\nby `queryReportMaterializedRows()` retain their raw JSON-safe values for\nsorting, exports, and agents. Use `dataTable.structuralRows` for a computed\nsummary, subtotal, aggregate, or footer. Each is marked `selection: 'excluded'`\nand `actions: 'excluded'`, and must be passed to the consumer table's structural\nrow surface rather than its selectable data rows.\n\n`queryReportMaterializedRows()` is the bounded read slice for already-materialized\nrows. It supports projection, offset/limit paging (default limit 50),\ndeterministic multi-sort (with `id` as the final tie-break), typed filters, and\ndatabase-backed dimension facets. A descriptor marks group/time fields as\n`filterScope: 'where'` and aggregate measures as `filterScope: 'having'`; use\n`splitReportFilterScopes()` when constructing a live source query. An AND may\ncombine the two scopes, but an OR or NOT cannot mix them, because moving either\nside across a source `WHERE`/`HAVING` boundary would change its meaning.\n\nMaterialized query results normalize hydrated values against their declared\ncolumn types. Objects in scalar columns become JSON text; JSON columns retain\nstructured values. Date-only and timezone-less timestamps use UTC, matching\nSQLite storage, and temporal facet buckets follow the same rule. Strings over\n4096 characters are shortened with `truncated` and a warning, except row IDs:\nidentities are never rewritten, and unsupported IDs fail validation. Unsafe bigint\nvalues fail instead of silently losing precision.\n\nThe materialized collection executes the normalized, allowlisted predicate as\nparameterized SQL and applies it identically to rows, totals, and facets. It\nnever accepts raw SQL, source field paths, tenant ids, or principal ids.\nThe descriptor's `queryExecution` contract declares three delivery choices:\n`visible` (the default) returns rows for an already-authorized surface,\n`silent` returns the same bounded result while making no visible-surface change,\nand `background` delegates a normalized, authority-free task to the application's\n`enqueueBackgroundQuery` host. A background result is only a queue handle, never\nmaterialized rows. The host retains the authenticated principal, tenant, report\ndefinition, field policy, database, and eventual job execution; none can be\nsupplied in a query request or background task.\nWhen a consumer selects a descriptor with `ReportAdapterOptions`, pass those\nsame options through the query's `adapter` option so returned and background\ntask resource ids stay aligned with the selected descriptor.\n`buildReportDrilldownQuery()` carries only the row's declared groups/buckets\nand a fixed inheritance contract for the current principal, tenant, report\ndefinition, and field policy; an authenticated source adapter must enforce that\ncontract before it reads source records. Time buckets remain declarative so the\nsource adapter keeps the report's database/timezone semantics. Every returned\nrow must have a non-empty string `id`; use that value, never a display or page\nindex, for row identity.\n\nWhen no collection is injected, reads resolve the registered report collection\nthrough `ObjectRegistry`, allowing normal s-m-r-t collection interceptors to enforce\ntenant filtering. `tenantScoped` and `tenantField` in the descriptor are true\nonly when the report is actually registered with tenant metadata; a scope string\nalone is not an authority boundary. An injected collection is application-owned\nand must provide the equivalent tenant boundary. Raw aggregate refreshes still\nneed explicit tenant predicates as described above.\n\nThe descriptor's `refresh` section declares mode, triggers, stale-read behavior,\nand a permissioned, audited `refresh` action with `preview` and `apply` phases.\nThe adapter itself stays read-only. Call `getReportLifecycle()` for a tenant-safe\nsnapshot of current, stale, refreshing, lock-skipped, or failed materialization\nstate; it redacts lock owners, raw errors, and tenant-fanout identifiers. Pass a\n`lifecycle` option to `queryReportMaterializedRows()` only when a consumer needs\nthat context; the result then distinguishes a current, stale, or read-triggered\nrefresh. `previewReportRefresh()` and `applyReportRefresh()` require an\napplication action host to authorize and audit the caller before a durable\nreport-refresh job is queued. Manual refresh hosts must also return a versioned,\nnon-secret `executionAuthority` binding. Each worker process registers the named\nhost with `registerReportRefreshExecutionAuthorityHost()`; the task checks the\npersisted tenant binding, resolves current membership and permission, and records\nan allowed or denied execution audit immediately before refresh. Every enqueue\npath also receives a `DurableJobPayloadSigner`, and every worker registers it\nwith `registerReportRefreshJobIntegritySigner()`. This authenticates the task\ntarget, trigger, tenant scope, authority reference, and payload across storage;\nthe signing key is never persisted. Scheduled and on-change maintenance jobs\nremain system-authority jobs and do not carry an initiating user, but their job\npayloads use the same integrity boundary. Only a registered `SmrtReportCollection` can\nsynchronously refresh a stale read, and only when its TTL policy is positive and\nnot manual.\n\n### Scheduled refresh lifecycle\n\nDeclare `refresh: { schedule: '*/5 * * * *', tenantFanout: 'discover' }`\non a tenant-scoped report to discover distinct non-null source tenants each\nscheduled run. Include scopes still present in the materialized table so a\nthe next rebuild removes stale groups for tenants losing their final source row.\nDiscovery preserves the configured refresh mode. Incremental reports still need\na `fullRebuildSchedule` to catch hard deletions that do not advance a source\nwatermark. Provision schedules with `ensureReportRefreshSchedules({ db,\nreports })`; no discovered scopes means no work, and discovery never refreshes\nthe global slice.\nThe worker uses the registered source/report tenant columns and its supplied\nmaintenance executor. Discovery does not grant database privileges or widen a\ntenant-bound or manual job. Keep `tenantFanout: true` with explicit `tenantIds`\nwhen the host owns a fixed tenant list.\n\nInitialize a `ReportScheduleRunner` and a jobs `TaskRunner` against the migrated\ndatabase, then call `const unbind = schedules.bindTaskRunner(tasks)` before\nstarting them. This connects terminal job events to schedule counters, freeing\nthe concurrency slot after success or failure. Repeated binding is idempotent;\ncall `unbind()` on shutdown. Listen for `runner:error` to detect failed counter\nupdates. Persisted failure messages use the jobs package redaction policy.\nDurable payload signing and execution-time authority checks remain\nrequired; completion accounting uses the signed schedule identity.\n\nRepeated schedule provisioning inserts missing identities and preserves existing\nhistory, next-run time, running count, and operator settings. A different cron,\nmode, or tenant scope identifies a different schedule; explicitly disable obsolete\nschedules when changing those settings.\n\n### Saved views and snapshot exports\n\n`normalizeReportSavedView()` and `restoreReportSavedView()` provide the\nserializable view boundary. A storage host owns the saved view's tenant and\nowner; on every restore it must pass the stored payload through the current\ndescriptor. That reapplies field, projection, sorting, grouping, and definition\npolicy, so a stale view cannot reveal a field that is no longer allowed.\n`migrateReportSavedView()` upgrades the original unversioned (or explicit v0)\nlayout to v1 before that current-policy validation; unsupported future versions\nfail clearly rather than being guessed.\n\nBuild an export from a completed materialized-row read with\n`createReportExportSnapshot()`, supplying an opaque binding from the\napplication's immutable materialization-snapshot host, then call\n`createReportExportRequest()`. The snapshot fixes the canonical query\nfingerprint, normalized projection and sort, exact row count, `asOf`,\n`refreshedAt`, stale state, and definition fingerprint. Every request also\ncontains a deterministic offset-page read plan; a renderer begins at offset zero\nand advances by its validated page size until the exact bounded row count is\nreached. Use `createReportExportPageRequest()` for each page; it preserves the\nfrozen query semantics while replacing only visible pagination.\n\nEvery request is bounded by rows, bytes, and deadline; exports over the\nforeground row limit become an authority-free background handoff. Use\n`previewReportExport()` and `applyReportExport()` with the same application\naction host for human and agent callers. The host authorizes and audits the\nfixed `reports.export` action; exports containing personal, sensitive, or secret\ncolumns require explicit confirmation.\n\nPreview, apply, and every worker call `validateReportExportExecution()`, which\nrequires the host to prove that the opaque binding still resolves to the exact\nimmutable materialization snapshot under its current principal, tenant, report\ndefinition, and field policy. If it cannot, the operation fails rather than\nrelabelling newer rows with an old `asOf`. Artifact metadata has no URL or\ndownload token; before serving it, call `validateReportExportArtifact()` to\nreject expiry, definition drift, and out-of-bounds progress, then apply the\nhost's current authorization and snapshot validation again.\n\n## Runtime reports\n\nA report can also be defined at runtime as data. `RuntimeReportSpec` is a closed,\nversioned structure (source id, dimensions, measures, filters, having, sort,\nchart hint) with no free-form query text. `compileRuntimeReportSpec()` checks it\nagainst a server-owned source allow-list, field policy (sensitive and\npermission-gated fields are not nameable), the caller's permissions, and tenant\nisolation, then targets the same aggregate plan as declared reports.\n`RuntimeReport` stores validated specs per tenant, and a stored spec is\nre-compiled for the running user on every run. Result rows are not stored. A row\ncan only be inserted, or have its spec changed, through `saveRuntimeReport()`\nwith the spec hash a human confirmed; the model layer refuses every other write. The\nchat package offers matching assistant tools that propose a spec, preview it,\nand save only after a human confirms. See\n[`agents/runtime-reports.md`](./agents/runtime-reports.md).\n\n## Development\n\n```bash\npnpm --filter @happyvertical/smrt-reports test\npnpm --filter @happyvertical/smrt-reports typecheck\npnpm --filter @happyvertical/smrt-reports build\n```\n\nSee [`AGENTS.md`](./AGENTS.md) for refresh, schema, and tenancy invariants.\n"
1163
1169
  }
1164
1170
  ],
1165
1171
  "recipes": [
package/dist/state.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import './__smrt-register__.js';
1
2
  import { SmrtCollection, SmrtObject } from '@happyvertical/smrt-core';
2
3
  import type { DatabaseInterface } from '@happyvertical/sql';
3
4
  import type { ReportRefreshMode, ReportRefreshTrigger } from './types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
package/dist/state.js CHANGED
@@ -1,5 +1,8 @@
1
- import { SmrtCollection, SmrtObject, field, smrt } from "@happyvertical/smrt-core";
1
+ import { ObjectRegistry, SmrtCollection, SmrtObject, field, smrt } from "@happyvertical/smrt-core";
2
2
  import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
3
+ //#region src/__smrt-register__.ts
4
+ ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-reports","packageVersion":"0.55.22","objects":{"@happyvertical/smrt-reports:SmrtReport":{"name":"smrtreport","className":"SmrtReport","qualifiedName":"@happyvertical/smrt-reports:SmrtReport","collection":"smrtreports","filePath":"/home/runner/work/smrt/smrt/packages/reports/src/report.ts","packageName":"@happyvertical/smrt-reports","fields":{"refreshedAt":{"type":"datetime","required":false}},"methods":{"isStale":{"name":"isStale","async":false,"parameters":[{"name":"ttlMs","type":"number","optional":true}],"returnType":"boolean","isStatic":false,"isPublic":true},"refresh":{"name":"refresh","async":true,"parameters":[{"name":"options","type":"Omit<ReportRefreshOptions, 'db'>","optional":true}],"returnType":"Promise<ReportRefreshResult>","isStatic":false,"isPublic":true}},"decoratorConfig":{},"extends":"SmrtObject","exportName":"SmrtReport","collectionExportName":"SmrtReportCollection","importPath":"@happyvertical/smrt-reports"},"@happyvertical/smrt-reports:SmrtReportCollection":{"name":"smrtreportcollection","className":"SmrtReportCollection","qualifiedName":"@happyvertical/smrt-reports:SmrtReportCollection","collection":"smrtreports","filePath":"/home/runner/work/smrt/smrt/packages/reports/src/report.ts","packageName":"@happyvertical/smrt-reports","fields":{},"methods":{"refresh":{"name":"refresh","async":true,"parameters":[{"name":"options","type":"Omit<ReportRefreshOptions, 'db'>","optional":true}],"returnType":"Promise<ReportRefreshResult>","isStatic":false,"isPublic":true},"list":{"name":"list","async":true,"parameters":[{"name":"options","type":"SmrtListOptions<ModelType>","optional":true}],"returnType":"Promise<SmrtObject[] | Record<string, unknown>[]>","isStatic":false,"isPublic":true},"get":{"name":"get","async":true,"parameters":[{"name":"filter","type":"any","optional":false,"typeUnresolved":true},{"name":"options","type":"any","optional":true,"typeUnresolved":true}],"returnType":"Promise<ModelType | null>","isStatic":false,"isPublic":true}},"decoratorConfig":{},"extends":"SmrtCollection","extendsTypeArg":"ModelType","exportName":"SmrtReportCollection","collectionExportName":"SmrtReportCollectionCollection","importPath":"@happyvertical/smrt-reports"},"@happyvertical/smrt-reports:RuntimeReport":{"name":"runtimereport","className":"RuntimeReport","qualifiedName":"@happyvertical/smrt-reports:RuntimeReport","collection":"runtimereports","filePath":"/home/runner/work/smrt/smrt/packages/reports/src/runtime-report.ts","packageName":"@happyvertical/smrt-reports","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"title":{"type":"text","required":false,"_meta":{}},"description":{"type":"text","required":false,"_meta":{}},"sourceId":{"type":"text","required":false,"_meta":{}},"spec":{"type":"text","required":true,"_meta":{"required":true}},"specHash":{"type":"text","required":false,"_meta":{}},"status":{"type":"text","required":true,"_meta":{"required":true},"enum":["active","archived"]},"createdByUserId":{"type":"text","required":false,"_meta":{}}},"methods":{"getSpec":{"name":"getSpec","async":false,"parameters":[],"returnType":"RuntimeReportSpec","isStatic":false,"isPublic":true},"setSpec":{"name":"setSpec","async":false,"parameters":[{"name":"spec","type":"RuntimeReportSpec","optional":false}],"returnType":"void","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"runtime_reports","api":{"include":["list","get","create"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]},"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"RuntimeReport","collectionExportName":"RuntimeReportCollection","displayLabelField":"title","validationRules":[{"field":"spec","rule":"required","fieldType":"text"},{"field":"status","rule":"required","fieldType":"text"}],"schema":{"tableName":"runtime_reports","ddl":"CREATE TABLE IF NOT EXISTS \\"runtime_reports\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"title\\" TEXT,\\n \\"description\\" TEXT,\\n \\"source_id\\" TEXT,\\n \\"spec\\" TEXT NOT NULL,\\n \\"spec_hash\\" TEXT,\\n \\"status\\" TEXT NOT NULL,\\n \\"created_by_user_id\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"title":{"type":"TEXT","notNull":false,"unique":false},"description":{"type":"TEXT","notNull":false,"unique":false},"source_id":{"type":"TEXT","notNull":false,"unique":false},"spec":{"type":"TEXT","notNull":true,"unique":false},"spec_hash":{"type":"TEXT","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":true,"unique":false},"created_by_user_id":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"runtime_reports_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"runtime_reports_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"04a92513"},"importPath":"@happyvertical/smrt-reports"},"@happyvertical/smrt-reports:RuntimeReportCollection":{"name":"runtimereportcollection","className":"RuntimeReportCollection","qualifiedName":"@happyvertical/smrt-reports:RuntimeReportCollection","collection":"runtimereports","filePath":"/home/runner/work/smrt/smrt/packages/reports/src/runtime-report.ts","packageName":"@happyvertical/smrt-reports","fields":{},"methods":{},"decoratorConfig":{"tableName":"runtime_reports"},"extends":"SmrtCollection","extendsTypeArg":"RuntimeReport","exportName":"RuntimeReportCollection","collectionExportName":"RuntimeReportCollectionCollection","schema":{"tableName":"runtime_reports","ddl":"CREATE TABLE IF NOT EXISTS \\"runtime_reports\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"title\\" TEXT,\\n \\"description\\" TEXT,\\n \\"source_id\\" TEXT,\\n \\"spec\\" TEXT NOT NULL,\\n \\"spec_hash\\" TEXT,\\n \\"status\\" TEXT NOT NULL,\\n \\"created_by_user_id\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"title":{"type":"TEXT","notNull":false,"unique":false},"description":{"type":"TEXT","notNull":false,"unique":false},"source_id":{"type":"TEXT","notNull":false,"unique":false},"spec":{"type":"TEXT","notNull":true,"unique":false},"spec_hash":{"type":"TEXT","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":true,"unique":false},"created_by_user_id":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"runtime_reports_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"runtime_reports_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"04a92513"},"importPath":"@happyvertical/smrt-reports"},"@happyvertical/smrt-reports:SmrtReportRefreshTask":{"name":"smrtreportrefreshtask","className":"SmrtReportRefreshTask","qualifiedName":"@happyvertical/smrt-reports:SmrtReportRefreshTask","collection":"smrtreportrefreshtasks","filePath":"/home/runner/work/smrt/smrt/packages/reports/src/scheduler.ts","packageName":"@happyvertical/smrt-reports","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"reportClass":{"type":"text","required":true,"_meta":{"required":true}},"mode":{"type":"text","required":true,"_meta":{"required":true},"enum":["rebuild","incremental"]},"trigger":{"type":"text","required":true,"_meta":{"required":true},"enum":["manual","schedule","change","ttl","job"]},"args":{"type":"json","required":false,"_meta":{}}},"methods":{"run":{"name":"run","async":true,"parameters":[{"name":"args","type":"ReportRefreshJobArgs","optional":true},{"name":"context","type":"JobExecutionContext","optional":true}],"returnType":"Promise<unknown>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_report_refresh_tasks","api":false,"cli":{"include":["list","get"],"skipApiCheck":true,"http":false},"mcp":false,"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"SmrtReportRefreshTask","collectionExportName":"SmrtReportRefreshTaskCollection","validationRules":[{"field":"reportClass","rule":"required","fieldType":"text"},{"field":"mode","rule":"required","fieldType":"text"},{"field":"trigger","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_report_refresh_tasks","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_report_refresh_tasks\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"report_class\\" TEXT NOT NULL,\\n \\"mode\\" TEXT NOT NULL,\\n \\"trigger\\" TEXT NOT NULL,\\n \\"args\\" JSON\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"report_class":{"type":"TEXT","notNull":true,"unique":false},"mode":{"type":"TEXT","notNull":true,"unique":false},"trigger":{"type":"TEXT","notNull":true,"unique":false},"args":{"type":"JSON","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_report_refresh_tasks_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_report_refresh_tasks_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"c9142042"},"importPath":"@happyvertical/smrt-reports/scheduler"},"@happyvertical/smrt-reports:SmrtPrincipalReportRefreshTask":{"name":"smrtprincipalreportrefreshtask","className":"SmrtPrincipalReportRefreshTask","qualifiedName":"@happyvertical/smrt-reports:SmrtPrincipalReportRefreshTask","collection":"smrtprincipalreportrefreshtasks","description":"Worker target whose authority requirement cannot be downgraded by job args.","filePath":"/home/runner/work/smrt/smrt/packages/reports/src/scheduler.ts","packageName":"@happyvertical/smrt-reports","fields":{"tenantId":{"type":"text","required":false,"_meta":{"generated":true,"source":"tenantScoped_decorator","sqlType":"UUID","__tenancy":{"isTenantIdField":true,"mode":"optional","field":"tenantId","autoFilter":true,"autoPopulate":true,"allowSuperAdminBypass":false}}}},"methods":{"run":{"name":"run","async":true,"parameters":[{"name":"args","type":"ReportRefreshJobArgs","optional":true},{"name":"context","type":"JobExecutionContext","optional":true}],"returnType":"Promise<unknown>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_principal_report_refresh_tasks","api":false,"cli":{"include":["list","get"],"skipApiCheck":true,"http":false},"mcp":false,"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtReportRefreshTask","exportName":"SmrtPrincipalReportRefreshTask","collectionExportName":"SmrtPrincipalReportRefreshTaskCollection","schema":{"tableName":"_smrt_principal_report_refresh_tasks","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_principal_report_refresh_tasks\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_principal_report_refresh_tasks_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_principal_report_refresh_tasks_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"226a3b97"},"importPath":"@happyvertical/smrt-reports/scheduler"},"@happyvertical/smrt-reports:SmrtReportRun":{"name":"smrtreportrun","className":"SmrtReportRun","qualifiedName":"@happyvertical/smrt-reports:SmrtReportRun","collection":"smrtreportruns","filePath":"/home/runner/work/smrt/smrt/packages/reports/src/state.ts","packageName":"@happyvertical/smrt-reports","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"scopeKey":{"type":"text","required":true,"_meta":{"required":true}},"reportClass":{"type":"text","required":true,"_meta":{"required":true}},"sourceClass":{"type":"text","required":true,"_meta":{"required":true}},"mode":{"type":"text","required":true,"_meta":{"required":true},"enum":["rebuild","incremental"]},"trigger":{"type":"text","required":true,"_meta":{"required":true},"enum":["manual","schedule","change","ttl","job"]},"status":{"type":"text","required":true,"description":"Whether the refresh is running, finished, failed or was skipped.","_meta":{"required":true,"description":"Whether the refresh is running, finished, failed or was skipped."},"enum":["running","success","failed","skipped"]},"startedAt":{"type":"datetime","required":true,"description":"When the refresh began.","_meta":{"required":true,"description":"When the refresh began."}},"completedAt":{"type":"datetime","required":false,"description":"When the refresh finished. Empty while it is still running.","_meta":{"nullable":true,"description":"When the refresh finished. Empty while it is still running."}},"rowCount":{"type":"integer","required":true,"description":"How many rows the report held after the refresh.","_meta":{"required":true,"description":"How many rows the report held after the refresh."}},"changedGroupCount":{"type":"integer","required":true,"_meta":{"required":true}},"watermarkBefore":{"type":"text","required":false,"_meta":{"nullable":true}},"watermarkAfter":{"type":"text","required":false,"_meta":{"nullable":true}},"error":{"type":"text","required":false,"description":"Why the refresh failed. Empty when it worked.","_meta":{"nullable":true,"description":"Why the refresh failed. Empty when it worked."}},"metadata":{"type":"json","required":false,"_meta":{}}},"methods":{},"decoratorConfig":{"tableName":"_smrt_report_runs","api":false,"cli":{"include":["list","get"],"skipApiCheck":true,"http":false},"mcp":false,"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"SmrtReportRun","collectionExportName":"SmrtReportRunCollection","validationRules":[{"field":"scopeKey","rule":"required","fieldType":"text"},{"field":"reportClass","rule":"required","fieldType":"text"},{"field":"sourceClass","rule":"required","fieldType":"text"},{"field":"mode","rule":"required","fieldType":"text"},{"field":"trigger","rule":"required","fieldType":"text"},{"field":"status","rule":"required","fieldType":"text"},{"field":"startedAt","rule":"required","fieldType":"datetime"},{"field":"rowCount","rule":"required","fieldType":"integer"},{"field":"changedGroupCount","rule":"required","fieldType":"integer"}],"schema":{"tableName":"_smrt_report_runs","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_report_runs\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"scope_key\\" TEXT NOT NULL,\\n \\"report_class\\" TEXT NOT NULL,\\n \\"source_class\\" TEXT NOT NULL,\\n \\"mode\\" TEXT NOT NULL,\\n \\"trigger\\" TEXT NOT NULL,\\n \\"status\\" TEXT NOT NULL,\\n \\"started_at\\" TIMESTAMP NOT NULL,\\n \\"completed_at\\" TIMESTAMP,\\n \\"row_count\\" INTEGER NOT NULL,\\n \\"changed_group_count\\" INTEGER NOT NULL,\\n \\"watermark_before\\" TEXT,\\n \\"watermark_after\\" TEXT,\\n \\"error\\" TEXT,\\n \\"metadata\\" JSON\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"scope_key":{"type":"TEXT","notNull":true,"unique":false},"report_class":{"type":"TEXT","notNull":true,"unique":false},"source_class":{"type":"TEXT","notNull":true,"unique":false},"mode":{"type":"TEXT","notNull":true,"unique":false},"trigger":{"type":"TEXT","notNull":true,"unique":false},"status":{"type":"TEXT","notNull":true,"unique":false},"started_at":{"type":"TIMESTAMP","notNull":true,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"row_count":{"type":"INTEGER","notNull":true,"unique":false},"changed_group_count":{"type":"INTEGER","notNull":true,"unique":false},"watermark_before":{"type":"TEXT","notNull":false,"unique":false},"watermark_after":{"type":"TEXT","notNull":false,"unique":false},"error":{"type":"TEXT","notNull":false,"unique":false},"metadata":{"type":"JSON","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_report_runs_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_report_runs_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"54ff5048"},"importPath":"@happyvertical/smrt-reports/state"},"@happyvertical/smrt-reports:SmrtReportRunCollection":{"name":"smrtreportruncollection","className":"SmrtReportRunCollection","qualifiedName":"@happyvertical/smrt-reports:SmrtReportRunCollection","collection":"smrtreportruns","filePath":"/home/runner/work/smrt/smrt/packages/reports/src/state.ts","packageName":"@happyvertical/smrt-reports","fields":{},"methods":{},"decoratorConfig":{"tableName":"_smrt_report_runs","api":false,"mcp":false},"extends":"SmrtCollection","extendsTypeArg":"SmrtReportRun","exportName":"SmrtReportRunCollection","collectionExportName":"SmrtReportRunCollectionCollection","schema":{"tableName":"_smrt_report_runs","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_report_runs\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"scope_key\\" TEXT NOT NULL,\\n \\"report_class\\" TEXT NOT NULL,\\n \\"source_class\\" TEXT NOT NULL,\\n \\"mode\\" TEXT NOT NULL,\\n \\"trigger\\" TEXT NOT NULL,\\n \\"status\\" TEXT NOT NULL,\\n \\"started_at\\" TIMESTAMP NOT NULL,\\n \\"completed_at\\" TIMESTAMP,\\n \\"row_count\\" INTEGER NOT NULL,\\n \\"changed_group_count\\" INTEGER NOT NULL,\\n \\"watermark_before\\" TEXT,\\n \\"watermark_after\\" TEXT,\\n \\"error\\" TEXT,\\n \\"metadata\\" JSON\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"scope_key":{"type":"TEXT","notNull":true,"unique":false},"report_class":{"type":"TEXT","notNull":true,"unique":false},"source_class":{"type":"TEXT","notNull":true,"unique":false},"mode":{"type":"TEXT","notNull":true,"unique":false},"trigger":{"type":"TEXT","notNull":true,"unique":false},"status":{"type":"TEXT","notNull":true,"unique":false},"started_at":{"type":"TIMESTAMP","notNull":true,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"row_count":{"type":"INTEGER","notNull":true,"unique":false},"changed_group_count":{"type":"INTEGER","notNull":true,"unique":false},"watermark_before":{"type":"TEXT","notNull":false,"unique":false},"watermark_after":{"type":"TEXT","notNull":false,"unique":false},"error":{"type":"TEXT","notNull":false,"unique":false},"metadata":{"type":"JSON","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_report_runs_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_report_runs_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"54ff5048"},"importPath":"@happyvertical/smrt-reports/state"},"@happyvertical/smrt-reports:SmrtReportWatermark":{"name":"smrtreportwatermark","className":"SmrtReportWatermark","qualifiedName":"@happyvertical/smrt-reports:SmrtReportWatermark","collection":"smrtreportwatermarks","filePath":"/home/runner/work/smrt/smrt/packages/reports/src/state.ts","packageName":"@happyvertical/smrt-reports","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"scopeKey":{"type":"text","required":true,"_meta":{"required":true}},"reportClass":{"type":"text","required":true,"_meta":{"required":true}},"sourceClass":{"type":"text","required":true,"_meta":{"required":true}},"watermarkColumn":{"type":"text","required":true,"_meta":{"required":true}},"watermarkValue":{"type":"text","required":false,"_meta":{"nullable":true}},"lastRunId":{"type":"text","required":false,"_meta":{"nullable":true}}},"methods":{},"decoratorConfig":{"tableName":"_smrt_report_watermarks","conflictColumns":["report_class","scope_key","source_class","watermark_column"],"api":false,"cli":{"include":["list","get"],"skipApiCheck":true,"http":false},"mcp":false,"tenantScoped":{"mode":"optional"}},"extends":"SmrtObject","exportName":"SmrtReportWatermark","collectionExportName":"SmrtReportWatermarkCollection","validationRules":[{"field":"scopeKey","rule":"required","fieldType":"text"},{"field":"reportClass","rule":"required","fieldType":"text"},{"field":"sourceClass","rule":"required","fieldType":"text"},{"field":"watermarkColumn","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_report_watermarks","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_report_watermarks\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"scope_key\\" TEXT NOT NULL,\\n \\"report_class\\" TEXT NOT NULL,\\n \\"source_class\\" TEXT NOT NULL,\\n \\"watermark_column\\" TEXT NOT NULL,\\n \\"watermark_value\\" TEXT,\\n \\"last_run_id\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"scope_key":{"type":"TEXT","notNull":true,"unique":false},"report_class":{"type":"TEXT","notNull":true,"unique":false},"source_class":{"type":"TEXT","notNull":true,"unique":false},"watermark_column":{"type":"TEXT","notNull":true,"unique":false},"watermark_value":{"type":"TEXT","notNull":false,"unique":false},"last_run_id":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_report_watermarks_report_class_scope_key_idx","columns":["report_class","scope_key","source_class","watermark_column"],"unique":true},{"name":"_smrt_report_watermarks_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_report_watermarks_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"dac9d57a"},"importPath":"@happyvertical/smrt-reports/state"},"@happyvertical/smrt-reports:SmrtReportWatermarkCollection":{"name":"smrtreportwatermarkcollection","className":"SmrtReportWatermarkCollection","qualifiedName":"@happyvertical/smrt-reports:SmrtReportWatermarkCollection","collection":"smrtreportwatermarks","filePath":"/home/runner/work/smrt/smrt/packages/reports/src/state.ts","packageName":"@happyvertical/smrt-reports","fields":{},"methods":{},"decoratorConfig":{"tableName":"_smrt_report_watermarks","api":false,"mcp":false},"extends":"SmrtCollection","extendsTypeArg":"SmrtReportWatermark","exportName":"SmrtReportWatermarkCollection","collectionExportName":"SmrtReportWatermarkCollectionCollection","schema":{"tableName":"_smrt_report_watermarks","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_report_watermarks\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"scope_key\\" TEXT NOT NULL,\\n \\"report_class\\" TEXT NOT NULL,\\n \\"source_class\\" TEXT NOT NULL,\\n \\"watermark_column\\" TEXT NOT NULL,\\n \\"watermark_value\\" TEXT,\\n \\"last_run_id\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"scope_key":{"type":"TEXT","notNull":true,"unique":false},"report_class":{"type":"TEXT","notNull":true,"unique":false},"source_class":{"type":"TEXT","notNull":true,"unique":false},"watermark_column":{"type":"TEXT","notNull":true,"unique":false},"watermark_value":{"type":"TEXT","notNull":false,"unique":false},"last_run_id":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_report_watermarks_report_class_scope_key_idx","columns":["report_class","scope_key","source_class","watermark_column"],"unique":true},{"name":"_smrt_report_watermarks_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_report_watermarks_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"dac9d57a"},"importPath":"@happyvertical/smrt-reports/state"},"@happyvertical/smrt-reports:SmrtReportLock":{"name":"smrtreportlock","className":"SmrtReportLock","qualifiedName":"@happyvertical/smrt-reports:SmrtReportLock","collection":"smrtreportlocks","filePath":"/home/runner/work/smrt/smrt/packages/reports/src/state.ts","packageName":"@happyvertical/smrt-reports","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"scopeKey":{"type":"text","required":true,"_meta":{"required":true}},"reportClass":{"type":"text","required":true,"_meta":{"required":true}},"ownerId":{"type":"text","required":false,"_meta":{"nullable":true}},"acquiredAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"heartbeatAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"expiresAt":{"type":"datetime","required":false,"_meta":{"nullable":true}}},"methods":{},"decoratorConfig":{"tableName":"_smrt_report_locks","conflictColumns":["report_class","scope_key"],"api":false,"cli":{"include":["list","get"],"skipApiCheck":true,"http":false},"mcp":false,"tenantScoped":{"mode":"optional"}},"extends":"SmrtObject","exportName":"SmrtReportLock","collectionExportName":"SmrtReportLockCollection","validationRules":[{"field":"scopeKey","rule":"required","fieldType":"text"},{"field":"reportClass","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_report_locks","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_report_locks\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"scope_key\\" TEXT NOT NULL,\\n \\"report_class\\" TEXT NOT NULL,\\n \\"owner_id\\" TEXT,\\n \\"acquired_at\\" TIMESTAMP,\\n \\"heartbeat_at\\" TIMESTAMP,\\n \\"expires_at\\" TIMESTAMP\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"scope_key":{"type":"TEXT","notNull":true,"unique":false},"report_class":{"type":"TEXT","notNull":true,"unique":false},"owner_id":{"type":"TEXT","notNull":false,"unique":false},"acquired_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"heartbeat_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"expires_at":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_report_locks_report_class_scope_key_idx","columns":["report_class","scope_key"],"unique":true},{"name":"_smrt_report_locks_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_report_locks_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"4a2043f9"},"importPath":"@happyvertical/smrt-reports/state"},"@happyvertical/smrt-reports:SmrtReportLockCollection":{"name":"smrtreportlockcollection","className":"SmrtReportLockCollection","qualifiedName":"@happyvertical/smrt-reports:SmrtReportLockCollection","collection":"smrtreportlocks","filePath":"/home/runner/work/smrt/smrt/packages/reports/src/state.ts","packageName":"@happyvertical/smrt-reports","fields":{},"methods":{},"decoratorConfig":{"tableName":"_smrt_report_locks","api":false,"mcp":false},"extends":"SmrtCollection","extendsTypeArg":"SmrtReportLock","exportName":"SmrtReportLockCollection","collectionExportName":"SmrtReportLockCollectionCollection","schema":{"tableName":"_smrt_report_locks","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_report_locks\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"scope_key\\" TEXT NOT NULL,\\n \\"report_class\\" TEXT NOT NULL,\\n \\"owner_id\\" TEXT,\\n \\"acquired_at\\" TIMESTAMP,\\n \\"heartbeat_at\\" TIMESTAMP,\\n \\"expires_at\\" TIMESTAMP\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"scope_key":{"type":"TEXT","notNull":true,"unique":false},"report_class":{"type":"TEXT","notNull":true,"unique":false},"owner_id":{"type":"TEXT","notNull":false,"unique":false},"acquired_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"heartbeat_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"expires_at":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_report_locks_report_class_scope_key_idx","columns":["report_class","scope_key"],"unique":true},{"name":"_smrt_report_locks_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_report_locks_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"4a2043f9"},"importPath":"@happyvertical/smrt-reports/state"},"@happyvertical/smrt-reports:SmrtReportSchedule":{"name":"smrtreportschedule","className":"SmrtReportSchedule","qualifiedName":"@happyvertical/smrt-reports:SmrtReportSchedule","collection":"smrtreportschedules","filePath":"/home/runner/work/smrt/smrt/packages/reports/src/state.ts","packageName":"@happyvertical/smrt-reports","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"scopeKey":{"type":"text","required":true,"_meta":{"required":true}},"reportClass":{"type":"text","required":true,"_meta":{"required":true}},"cron":{"type":"text","required":true,"description":"When the report refreshes, as a cron schedule such as 0 6 * * * for 6 am daily.","_meta":{"required":true,"description":"When the report refreshes, as a cron schedule such as 0 6 * * * for 6 am daily."}},"trigger":{"type":"text","required":true,"_meta":{"required":true},"enum":["manual","schedule","change","ttl","job"]},"mode":{"type":"text","required":true,"description":"Rebuild recomputes the whole report. Incremental updates only what changed.","_meta":{"required":true,"description":"Rebuild recomputes the whole report. Incremental updates only what changed."},"enum":["rebuild","incremental"]},"enabled":{"type":"boolean","required":true,"description":"Turn off to stop refreshing without deleting the schedule.","_meta":{"required":true,"description":"Turn off to stop refreshing without deleting the schedule."}},"status":{"type":"text","required":true,"description":"Whether the schedule is active, paused, disabled or in error.","_meta":{"required":true,"description":"Whether the schedule is active, paused, disabled or in error."},"enum":["active","paused","disabled","error"]},"nextRun":{"type":"datetime","required":false,"description":"When the report is next due to refresh.","_meta":{"nullable":true,"description":"When the report is next due to refresh."}},"lastRun":{"type":"datetime","required":false,"description":"When the report last started a scheduled refresh.","_meta":{"nullable":true,"description":"When the report last started a scheduled refresh."}},"lastStatus":{"type":"text","required":false,"description":"Whether the last scheduled refresh worked or failed.","_meta":{"nullable":true,"description":"Whether the last scheduled refresh worked or failed."},"enum":["success","failed"]},"lastError":{"type":"text","required":false,"description":"Why the last scheduled refresh failed. Empty when it worked.","_meta":{"nullable":true,"description":"Why the last scheduled refresh failed. Empty when it worked."}},"runCount":{"type":"integer","required":true,"_meta":{"required":true}},"successCount":{"type":"integer","required":true,"_meta":{"required":true}},"failureCount":{"type":"integer","required":true,"_meta":{"required":true}},"runningCount":{"type":"integer","required":true,"_meta":{"required":true}},"maxConcurrent":{"type":"integer","required":true,"_meta":{"required":true}},"queue":{"type":"text","required":true,"_meta":{"required":true}},"priority":{"type":"integer","required":true,"_meta":{"required":true}},"timeout":{"type":"integer","required":true,"_meta":{"required":true}}},"methods":{},"decoratorConfig":{"tableName":"_smrt_report_schedules","conflictColumns":["report_class","scope_key","cron","mode"],"api":false,"cli":{"include":["list","get"],"skipApiCheck":true,"http":false},"mcp":false,"tenantScoped":{"mode":"optional"}},"extends":"SmrtObject","exportName":"SmrtReportSchedule","collectionExportName":"SmrtReportScheduleCollection","validationRules":[{"field":"scopeKey","rule":"required","fieldType":"text"},{"field":"reportClass","rule":"required","fieldType":"text"},{"field":"cron","rule":"required","fieldType":"text"},{"field":"trigger","rule":"required","fieldType":"text"},{"field":"mode","rule":"required","fieldType":"text"},{"field":"enabled","rule":"required","fieldType":"boolean"},{"field":"status","rule":"required","fieldType":"text"},{"field":"runCount","rule":"required","fieldType":"integer"},{"field":"successCount","rule":"required","fieldType":"integer"},{"field":"failureCount","rule":"required","fieldType":"integer"},{"field":"runningCount","rule":"required","fieldType":"integer"},{"field":"maxConcurrent","rule":"required","fieldType":"integer"},{"field":"queue","rule":"required","fieldType":"text"},{"field":"priority","rule":"required","fieldType":"integer"},{"field":"timeout","rule":"required","fieldType":"integer"}],"schema":{"tableName":"_smrt_report_schedules","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_report_schedules\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"scope_key\\" TEXT NOT NULL,\\n \\"report_class\\" TEXT NOT NULL,\\n \\"cron\\" TEXT NOT NULL,\\n \\"trigger\\" TEXT NOT NULL,\\n \\"mode\\" TEXT NOT NULL,\\n \\"enabled\\" BOOLEAN NOT NULL,\\n \\"status\\" TEXT NOT NULL,\\n \\"next_run\\" TIMESTAMP,\\n \\"last_run\\" TIMESTAMP,\\n \\"last_status\\" TEXT,\\n \\"last_error\\" TEXT,\\n \\"run_count\\" INTEGER NOT NULL,\\n \\"success_count\\" INTEGER NOT NULL,\\n \\"failure_count\\" INTEGER NOT NULL,\\n \\"running_count\\" INTEGER NOT NULL,\\n \\"max_concurrent\\" INTEGER NOT NULL,\\n \\"queue\\" TEXT NOT NULL,\\n \\"priority\\" INTEGER NOT NULL,\\n \\"timeout\\" INTEGER NOT NULL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"scope_key":{"type":"TEXT","notNull":true,"unique":false},"report_class":{"type":"TEXT","notNull":true,"unique":false},"cron":{"type":"TEXT","notNull":true,"unique":false},"trigger":{"type":"TEXT","notNull":true,"unique":false},"mode":{"type":"TEXT","notNull":true,"unique":false},"enabled":{"type":"BOOLEAN","notNull":true,"unique":false},"status":{"type":"TEXT","notNull":true,"unique":false},"next_run":{"type":"TIMESTAMP","notNull":false,"unique":false},"last_run":{"type":"TIMESTAMP","notNull":false,"unique":false},"last_status":{"type":"TEXT","notNull":false,"unique":false},"last_error":{"type":"TEXT","notNull":false,"unique":false},"run_count":{"type":"INTEGER","notNull":true,"unique":false},"success_count":{"type":"INTEGER","notNull":true,"unique":false},"failure_count":{"type":"INTEGER","notNull":true,"unique":false},"running_count":{"type":"INTEGER","notNull":true,"unique":false},"max_concurrent":{"type":"INTEGER","notNull":true,"unique":false},"queue":{"type":"TEXT","notNull":true,"unique":false},"priority":{"type":"INTEGER","notNull":true,"unique":false},"timeout":{"type":"INTEGER","notNull":true,"unique":false}},"indexes":[{"name":"_smrt_report_schedules_report_class_scope_key_idx","columns":["report_class","scope_key","cron","mode"],"unique":true},{"name":"_smrt_report_schedules_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_report_schedules_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"4b9f0e69"},"importPath":"@happyvertical/smrt-reports/state"},"@happyvertical/smrt-reports:SmrtReportScheduleCollection":{"name":"smrtreportschedulecollection","className":"SmrtReportScheduleCollection","qualifiedName":"@happyvertical/smrt-reports:SmrtReportScheduleCollection","collection":"smrtreportschedules","filePath":"/home/runner/work/smrt/smrt/packages/reports/src/state.ts","packageName":"@happyvertical/smrt-reports","fields":{},"methods":{},"decoratorConfig":{"tableName":"_smrt_report_schedules","api":false,"mcp":false},"extends":"SmrtCollection","extendsTypeArg":"SmrtReportSchedule","exportName":"SmrtReportScheduleCollection","collectionExportName":"SmrtReportScheduleCollectionCollection","schema":{"tableName":"_smrt_report_schedules","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_report_schedules\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"scope_key\\" TEXT NOT NULL,\\n \\"report_class\\" TEXT NOT NULL,\\n \\"cron\\" TEXT NOT NULL,\\n \\"trigger\\" TEXT NOT NULL,\\n \\"mode\\" TEXT NOT NULL,\\n \\"enabled\\" BOOLEAN NOT NULL,\\n \\"status\\" TEXT NOT NULL,\\n \\"next_run\\" TIMESTAMP,\\n \\"last_run\\" TIMESTAMP,\\n \\"last_status\\" TEXT,\\n \\"last_error\\" TEXT,\\n \\"run_count\\" INTEGER NOT NULL,\\n \\"success_count\\" INTEGER NOT NULL,\\n \\"failure_count\\" INTEGER NOT NULL,\\n \\"running_count\\" INTEGER NOT NULL,\\n \\"max_concurrent\\" INTEGER NOT NULL,\\n \\"queue\\" TEXT NOT NULL,\\n \\"priority\\" INTEGER NOT NULL,\\n \\"timeout\\" INTEGER NOT NULL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"scope_key":{"type":"TEXT","notNull":true,"unique":false},"report_class":{"type":"TEXT","notNull":true,"unique":false},"cron":{"type":"TEXT","notNull":true,"unique":false},"trigger":{"type":"TEXT","notNull":true,"unique":false},"mode":{"type":"TEXT","notNull":true,"unique":false},"enabled":{"type":"BOOLEAN","notNull":true,"unique":false},"status":{"type":"TEXT","notNull":true,"unique":false},"next_run":{"type":"TIMESTAMP","notNull":false,"unique":false},"last_run":{"type":"TIMESTAMP","notNull":false,"unique":false},"last_status":{"type":"TEXT","notNull":false,"unique":false},"last_error":{"type":"TEXT","notNull":false,"unique":false},"run_count":{"type":"INTEGER","notNull":true,"unique":false},"success_count":{"type":"INTEGER","notNull":true,"unique":false},"failure_count":{"type":"INTEGER","notNull":true,"unique":false},"running_count":{"type":"INTEGER","notNull":true,"unique":false},"max_concurrent":{"type":"INTEGER","notNull":true,"unique":false},"queue":{"type":"TEXT","notNull":true,"unique":false},"priority":{"type":"INTEGER","notNull":true,"unique":false},"timeout":{"type":"INTEGER","notNull":true,"unique":false}},"indexes":[{"name":"_smrt_report_schedules_report_class_scope_key_idx","columns":["report_class","scope_key","cron","mode"],"unique":true},{"name":"_smrt_report_schedules_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_report_schedules_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"4b9f0e69"},"importPath":"@happyvertical/smrt-reports/state"}},"moduleType":"smrt","recipes":[{"id":"reports.materialized","className":"MaterializedReportsRecipe","label":"Reports","summary":"Keep totals and trends ready to read, refreshed on a schedule and open to the assistant.","synonyms":["reports","totals","summaries","rollups","kpis","trends","dashboards"],"models":["@happyvertical/smrt-reports:SmrtReportRun","@happyvertical/smrt-reports:SmrtReportWatermark","@happyvertical/smrt-reports:SmrtReportLock","@happyvertical/smrt-reports:SmrtReportSchedule","@happyvertical/smrt-reports:SmrtReportRefreshTask","@happyvertical/smrt-reports:SmrtPrincipalReportRefreshTask"],"nav":[],"requires":[],"help":{"markdown":"## Overview\\n\\nReports keep totals and trends, such as revenue by month or signups by week,\\nworked out ahead of time so they open instantly. Someone on your team sets up\\neach report once. This feature then records when each report was last\\nrefreshed, how that went, and when it runs next.\\n\\nReports are calculated on your server, never in the browser, so this feature\\nneeds your app's database.\\n\\n## Tasks\\n\\n### Ask the assistant about a report\\n\\n1. Ask in plain words, for example \\"what was revenue last month?\\".\\n2. The assistant looks up the reports your account may see\\n (\`data.discover\`, \`data.inspect\`), then reads rows with \`reports.query\`.\\n It only ever sees the columns and the customer data you are allowed to see.\\n3. If the numbers look old, ask how fresh the report is. The answer says when\\n it was last refreshed.\\n4. Ask the assistant to refresh a stale report. It previews the refresh with\\n \`reports.refresh\` and your app decides who may apply it.\\n\\nThe assistant offers these tools only when your app has switched them on.\\n\\n### Ask a question no report covers\\n\\n1. Describe what you want to see, for example \\"orders by status this quarter\\".\\n2. The assistant checks what it may report on (\`reports.runtime.sources\`) and\\n shows a preview of the report it would save (\`reports.runtime.define\`).\\n3. Nothing is saved until you confirm that exact report.\\n4. Open it again later with \`reports.runtime.list\` and \`reports.runtime.run\`.\\n\\nThese reports are worked out live each time and their rows are never stored.\\nThey are not refreshed in the background. A report that people open every day\\nis worth asking your team to set up as a refreshed report instead.\\n\\n### Keep a report fresh\\n\\n1. Ask your team how the report refreshes: by hand, on a schedule, after the\\n data changes, or when it goes out of date.\\n2. A scheduled report repeats on its **{field:SmrtReportSchedule.cron}**, either\\n rebuilding everything or updating only what changed, per its\\n **{field:SmrtReportSchedule.mode}**.\\n3. Turn **{field:SmrtReportSchedule.enabled}** off to pause a schedule without\\n losing it.\\n4. **{field:SmrtReportSchedule.nextRun}** shows when it is next due.\\n\\n### Check that a refresh worked\\n\\n1. Look at the latest run for the report. **{field:SmrtReportRun.status}**\\n says whether it is running, finished, failed or skipped.\\n2. **{field:SmrtReportRun.completedAt}** and **{field:SmrtReportRun.rowCount}**\\n show when it finished and how many rows it holds.\\n3. If it failed, **{field:SmrtReportRun.error}** says why.\\n","fieldRefs":["SmrtReportRun.completedAt","SmrtReportRun.error","SmrtReportRun.rowCount","SmrtReportRun.status","SmrtReportSchedule.cron","SmrtReportSchedule.enabled","SmrtReportSchedule.mode","SmrtReportSchedule.nextRun"]},"runtime":"server","demo":{"mode":"server","reasons":["The recipe declares runtime \\"server\\".","Needs a server because its package does not build for a browser: node:crypto/events; @happyvertical/sql root: the sync query builders it needs (validateColumnName, buildWhere, buildAggregate, bucketExpr, tableExists) have no browser-safe SDK entry; and its package depends on @happyvertical/smrt-jobs, @happyvertical/smrt-tenancy, which do not build for a browser (#3615, #3624, #3625, #3627)."]}}],"smrtDependencies":["@happyvertical/smrt-core","@happyvertical/smrt-jobs","@happyvertical/smrt-tenancy"],"browser":{"status":"server-only","issues":["#3615","#3624","#3625","#3627"],"reason":"node:crypto/events; @happyvertical/sql root: the sync query builders it needs (validateColumnName, buildWhere, buildAggregate, bucketExpr, tableExists) have no browser-safe SDK entry","via":["@happyvertical/smrt-jobs","@happyvertical/smrt-tenancy"]},"stampsConstructors":true}`));
5
+ //#endregion
3
6
  //#region src/state.ts
4
7
  var __defProp = Object.defineProperty;
5
8
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;