@happyvertical/smrt-core 0.53.1 → 0.53.3

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.
Files changed (70) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +49 -0
  3. package/agents/test-types.md +29 -0
  4. package/dist/decorators/index.d.ts +8 -0
  5. package/dist/decorators/index.d.ts.map +1 -1
  6. package/dist/decorators/index.js.map +1 -1
  7. package/dist/generators/mcp.js.map +1 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/manifest/static-manifest.d.ts.map +1 -1
  11. package/dist/manifest/static-manifest.js +4 -3
  12. package/dist/manifest/static-manifest.js.map +1 -1
  13. package/dist/manifest/store.js +1 -1
  14. package/dist/manifest/store.js.map +1 -1
  15. package/dist/manifest.json +4 -3
  16. package/dist/object.d.ts +3 -1
  17. package/dist/object.d.ts.map +1 -1
  18. package/dist/object.js +2 -0
  19. package/dist/object.js.map +1 -1
  20. package/dist/registry/class-registration.d.ts.map +1 -1
  21. package/dist/registry/class-registration.js +72 -9
  22. package/dist/registry/class-registration.js.map +1 -1
  23. package/dist/registry/runtime-overrides.d.ts +34 -0
  24. package/dist/registry/runtime-overrides.d.ts.map +1 -0
  25. package/dist/registry/runtime-overrides.js +78 -0
  26. package/dist/registry/runtime-overrides.js.map +1 -0
  27. package/dist/registry/schema-builder.d.ts.map +1 -1
  28. package/dist/registry/schema-builder.js +3 -3
  29. package/dist/registry/schema-builder.js.map +1 -1
  30. package/dist/registry/subtype-lineage.d.ts +10 -0
  31. package/dist/registry/subtype-lineage.d.ts.map +1 -0
  32. package/dist/registry/subtype-lineage.js +57 -0
  33. package/dist/registry/subtype-lineage.js.map +1 -0
  34. package/dist/registry/types.d.ts +2 -0
  35. package/dist/registry/types.d.ts.map +1 -1
  36. package/dist/registry.d.ts +13 -1
  37. package/dist/registry.d.ts.map +1 -1
  38. package/dist/registry.js +17 -0
  39. package/dist/registry.js.map +1 -1
  40. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  41. package/dist/scanner/manifest-generator.js +2 -2
  42. package/dist/scanner/manifest-generator.js.map +1 -1
  43. package/dist/scanner/types.d.ts +2 -0
  44. package/dist/scanner/types.d.ts.map +1 -1
  45. package/dist/scanner/types.js.map +1 -1
  46. package/dist/schema/foreign-key-policy.d.ts +2 -0
  47. package/dist/schema/foreign-key-policy.d.ts.map +1 -1
  48. package/dist/schema/foreign-key-policy.js +5 -1
  49. package/dist/schema/foreign-key-policy.js.map +1 -1
  50. package/dist/schema/generator.d.ts.map +1 -1
  51. package/dist/schema/generator.js +4 -4
  52. package/dist/schema/generator.js.map +1 -1
  53. package/dist/smrt-knowledge.json +13 -7
  54. package/package.json +5 -5
  55. package/dist/__tests__/fixtures/collection-coverage-fixtures.d.ts +0 -20
  56. package/dist/__tests__/fixtures/collection-coverage-fixtures.d.ts.map +0 -1
  57. package/dist/__tests__/fixtures/inheritance-resolver-fixtures.d.ts +0 -37
  58. package/dist/__tests__/fixtures/inheritance-resolver-fixtures.d.ts.map +0 -1
  59. package/dist/__tests__/fixtures/inheritance-test-classes.d.ts +0 -48
  60. package/dist/__tests__/fixtures/inheritance-test-classes.d.ts.map +0 -1
  61. package/dist/__tests__/fixtures/mcp-integration-test-classes.d.ts +0 -18
  62. package/dist/__tests__/fixtures/mcp-integration-test-classes.d.ts.map +0 -1
  63. package/dist/__tests__/fixtures/object-ai-memory-fixtures.d.ts +0 -25
  64. package/dist/__tests__/fixtures/object-ai-memory-fixtures.d.ts.map +0 -1
  65. package/dist/__tests__/fixtures/object-spec-test-classes.d.ts +0 -13
  66. package/dist/__tests__/fixtures/object-spec-test-classes.d.ts.map +0 -1
  67. package/dist/__tests__/fixtures/registry-test-classes.d.ts +0 -23
  68. package/dist/__tests__/fixtures/registry-test-classes.d.ts.map +0 -1
  69. package/dist/registry/__tests__/helpers/consumer-workspace.d.ts +0 -42
  70. package/dist/registry/__tests__/helpers/consumer-workspace.d.ts.map +0 -1
@@ -3,13 +3,13 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-core",
6
- "packageVersion": "0.53.1",
6
+ "packageVersion": "0.53.3",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "8e587bc33751c3117698192b6030a56fbf8117b67c6786d7dd5648a7a9dc90ac",
11
- "packageJson": "728363819086ece0e39d4ce2613f8388517a4e0c24d0e8be8aee18ae23836420",
12
- "agents": "dcf3e194fb7fb4c358063938a9e155e8d97c178d4ad418f011621dda3d8ac3a4",
10
+ "manifest": "be715c324961b627e280e5529e4f4499b1a98b44b10b4a608b61c0da9dce8ecb",
11
+ "packageJson": "0eea72e563da9116475c6cdf68d9738e5cdab7001a8a3e9118ef5f1637ba85f4",
12
+ "agents": "36f26d684abee7ee2f552f0e0ee929eaa6d63cbba1f88bfd8e0f60d1b4cca81d",
13
13
  "moduleDoc:agents/object-runtime.md": "39a68186cdb57bc9d9dfacb805b19a00133f189f6e287e6ebe2fb347418b878b",
14
14
  "moduleDoc:agents/revision-guard.md": "aa6b1ddb5b6b49fa27ebbe575ec7fcd6d5ceb35ee25acc702f877a226eb9a99a",
15
15
  "moduleDoc:agents/collection-reads.md": "e5bb79b59c17e79a85a71580a019e6b0b641e498ac4d850073fd5ad634b57cb7",
@@ -24,7 +24,8 @@
24
24
  "moduleDoc:agents/system-diagnostics.md": "b7ccae18ee312bf788d5470f95fa78d3535f1789247d5372e40ca86538290772",
25
25
  "moduleDoc:agents/registry-snapshot.md": "6234aa540427396d1dda1825a4641b1072e247187d5ddf413ae46c691344ee30",
26
26
  "moduleDoc:agents/model-moves.md": "c64147fee1b7ec242fd1c178e7aea7cc90cafc704dee2738141c7a6d61403cd8",
27
- "moduleDoc:agents/run-once.md": "0b858c18c78eca5050257a2af06f16de99327e05e0bf9fee9e6277242e7707ef"
27
+ "moduleDoc:agents/run-once.md": "0b858c18c78eca5050257a2af06f16de99327e05e0bf9fee9e6277242e7707ef",
28
+ "moduleDoc:agents/test-types.md": "37c009ae2bd21001d83bba6cfab2047465fe32d5443de12661e24ba7d4e2d613"
28
29
  },
29
30
  "exports": [
30
31
  ".",
@@ -856,7 +857,7 @@
856
857
  "async": true,
857
858
  "params": [
858
859
  "criteria: string",
859
- "options?: AiOperationOptions"
860
+ "options?: any"
860
861
  ],
861
862
  "returns": "any"
862
863
  },
@@ -1063,7 +1064,7 @@
1063
1064
  "polymorphicAssociations": 1,
1064
1065
  "uuidColumns": 3
1065
1066
  },
1066
- "agentDoc": "# @happyvertical/smrt-core\n\nFoundation ORM, registry, schema/code generation, AI integration, and DispatchBus.\nRead the module for the subsystem being edited; root AGENTS covers shared model\nand repository rules.\n\n## Modules\n\n| Source | Scope | Module doc |\n|---|---|---|\n| `src/object.ts`, `src/collection.ts`, `src/child-accessors.ts` | Lifecycle, hydration, operators, STI, child accessors, dispatch | [agents/object-runtime.md](agents/object-runtime.md) |\n| `src/revision-guard.ts` | Guarded writes and PostgreSQL revision precision | [agents/revision-guard.md](agents/revision-guard.md) |\n| `src/collection.ts` | Projections, latest-related, facets, counts, read plans | [agents/collection-reads.md](agents/collection-reads.md) |\n| `src/collection.ts` | Limits, sort whitelist, generated list order | [agents/query-bounds.md](agents/query-bounds.md) |\n| `src/data-query.ts` | Transport-neutral bounded query normalization | [agents/data-query.md](agents/data-query.md) |\n| `src/schema/`, `src/migrations/`, `src/cascade.ts`, `src/system/` | DDL parity, indexes, migrations, delete integrity, retention | [agents/schema-paths.md](agents/schema-paths.md) |\n| `src/postgres-permissions.ts` | Explicit PostgreSQL ACL plans and atomic reconciliation | [deployment permission contract](../../docs/content/postgres-permissions.md) |\n| `src/change-feed.ts` | Durable changes, cursors, table versions, retention | [agents/change-feed.md](agents/change-feed.md) |\n| `src/change-feed-authz.ts` | Consumer table/row change-feed authorization hooks (#3020) | [agents/change-feed.md](agents/change-feed.md) |\n| `src/change-signals.ts` | Signal bus, replica fan-out, SSE | [agents/change-signals.md](agents/change-signals.md) |\n| `src/generators/`, `src/vite-plugin/web-collections.ts` | REST/CLI/MCP generation, manifest hashes, ETags | [agents/generators.md](agents/generators.md) |\n| `src/vite-plugin/`, `src/consumer-plugin/`, `src/knowledge.ts` | Decorator UI hints, knowledge projection, generation snapshots | [agents/build-knowledge.md](agents/build-knowledge.md) |\n| `src/knowledge-graph.ts` | Cross-package knowledge graph merge and freshness check (#2863) — see [standards](../../docs/content/standards.md) | — |\n| `src/object.ts`, `src/collection.ts`, `src/learning/memory.ts` | Context memory and semantic search | [agents/memory.md](agents/memory.md) |\n| `src/system/diagnostics.ts` | SELECT-only `_smrt_*` diagnostics reader behind smrt-dev-mcp runtime tools (#1824) | [agents/system-diagnostics.md](agents/system-diagnostics.md) |\n| `src/system/registry-snapshot.ts` | Sanitized plain-JSON projection of the booted `ObjectRegistry` for the smrt-dev-mcp runtime dev-plane (#1831); never constructors, validators, values, or absolute paths | [agents/registry-snapshot.md](agents/registry-snapshot.md) |\n| `src/registry/qualified-name-aliases.ts`, `src/migrations/qualified-name-aliases.ts` | Model moves: `previousQualifiedNames` aliases, deprecation, doctor counts, opt-in backfill (#3338) | [agents/model-moves.md](agents/model-moves.md) |\n| `src/run-once.ts` | Shared idempotency seam: insert-only claim + caller work in one transaction, replay on retry, typed in-flight/unknown-outcome answers (#3080) | [agents/run-once.md](agents/run-once.md) |\n\n## Cross-module invariants\n\n- `ObjectRegistry` is a `globalThis` singleton so registration survives HMR.\n- Per-class registry work on the hydration path (manifest reconciliation in\n `ensureManifestLoaded()`, the simple-name index behind `findClass()`) is\n memoized per registry generation (`src/registry/generation.ts`, #3047). Any\n new writer of registered classes, their field/method metadata, or loaded\n manifests must go through the tracked maps or call\n `bumpRegistryGeneration()`; `issue-3047-hydration-registry-work.test.ts`\n guards that hydration does not repeat this work per row.\n\n- Production DDL uses manifest generators; `getTestDatabase()` uses registry\n generators. Keep columns, indexes, FK actions, and runtime conflict targets in\n parity (`src/schema/schema-path-parity.test.ts`). Every new query predicate\n needs its index or an explicit reason none is needed.\n- Tenant scoping covers every read and every unique/conflict key. Explicit\n conflict columns are not rewritten; their author must include tenant scope.\n A `tenantId` field makes rows tenant-OWNED without any tenancy declaration:\n the default key leads with `tenant_id`. A new object's natural-key save reads\n the row its upsert would hit and refuses a different owner\n (`TENANT_ISOLATION_VIOLATION`) or adopts a same-owner row's id, so\n `DO UPDATE` never rewrites a primary key or owner. Only an explicit natural\n key adopts: a slug `getSlug()` derived from `name`/`title`/`label` moves to\n a free `-2`, `-3`, … slug and INSERTs. See `agents/schema-paths.md`\n (\"Tenant OWNERSHIP\").\n- Persisted saves use `id` and loaded `updated_at`; new saves use natural keys.\n Preserve revision compare-and-swap ordering through public `save()`,\n `claimRevision()`, and transaction APIs. Embedded saves, deletes, and complete\n `withTransaction()` callbacks share a process-local write queue.\n- Collection model hydration is serial in result order: initialization may query\n the same transaction-bound PostgreSQL client. Use projections for plain rows.\n- Native DuckDB UUIDs must be cast coherently on read before identity reuse;\n custom embedded revision paths use `getCanonicalPersistedRow()`. Never replay\n a mutation to discover result types.\n- `withDatabase(db, callback)` restores only database bindings (including public\n `options.db`); `withTransaction(callback)` also restores identity/revision\n metadata after rollback. Do not use a bound instance concurrently.\n- `runOnce({ db, tenantId, actor, token, content }, work)` (`src/run-once.ts`,\n #3080) claims a key derived from all four inputs — never the token alone,\n which collapses distinct submissions — via `INSERT ... ON CONFLICT\n (claim_key) DO NOTHING RETURNING claim_key` into the hand-DDL\n `_smrt_run_once_claims` system table, and runs `work` in the SAME\n transaction; a completed claim replays its stored result instead of\n re-running, a failed `work` rolls the claim back with the transaction, and\n an unresolved claim raises typed `RunOnceClaimError`\n (`RUN_ONCE_IN_FLIGHT` / `RUN_ONCE_OUTCOME_UNKNOWN`) rather than guessing.\n The claim insert must never raise on conflict: PostgreSQL aborts the whole\n transaction on any raised error, which would take the recovery `SELECT`\n down with it too — this is why it is `DO NOTHING`, not a plain `INSERT`\n caught for `isUniqueViolationError`. Completed claims are bounded by\n `runRetentionSweep()`'s `run-once-claims` task (`system/retention.ts`,\n `DEFAULT_RETENTION_POLICY.runOnceClaims`, default 30 days on\n `completed_at`; `in_progress` rows are never matched) — the window is a\n replay deadline, not just disk hygiene: a token replayed after its claim\n is swept runs `work()` again. See `agents/run-once.md`.\n- `ensureSystemTables(db, typeHint?)` provisions framework tables idempotently;\n call it on a base PostgreSQL connection before caller-owned transactions.\n Bootstrap uses a transaction-scoped advisory lock, taken only when the\n PostgreSQL schema version is not yet stamped: inside a caller's transaction it\n is held until that transaction ends (#2868). Application tables still require migrations;\n runtime table verification checks existence only.\n- Manifest generation fails closed on scanner errors, including unresolved\n decorator spreads. Never emit partial, default-open registration.\n- Generated registration repairs bundled class identity using the exact imported\n constructor, explicit package, and isolated one-object manifest. Never infer\n ownership from paths, simple names, or table names; packages can share names.\n Consumer regression gate: `packages/bundle-gate/src/__tests__/registry-identity.spec.ts`.\n- Decorator registration trusts a class's stack-derived package only when that\n package declares the class (stub, own manifest entry, or explicit\n `packageName`); it then matches the qualified key and never adopts another\n package's same-named registration. Otherwise a loaded (or the cwd project's)\n manifest entry describing the declaring file supplies the identity — apps\n scan workspace-package sources under the app's name (#3110). A class of\n another known package coexists under its qualified key\n (`decorator-different-packages-qualified-coexist`); bundled output needs\n a resolved table that differs first (#3106). Stack\n attribution skips smrt-core (including source-mapped installed\n `smrt-core/src` frames, #3109), decorator-helper, and module-runner frames.\n Instance and collection registry lookups pass the qualified name. The schema\n planner rejects a table claimed by two unrelated classes\n (`CONFIG_TABLE_NAME_COLLISION`): only one STI family shares a table (#3098);\n collections (including `SmrtReportCollection`) never claim one. Consumer\n gates: `registry/__tests__/issue-3110-*.test.ts`, `issue-3109-*.test.ts`,\n `packages/bundle-gate/src/__tests__/consumer-same-name.spec.ts`.\n- API custom-action eligibility has ONE resolver, `resolveApiMethodExposure()`\n in `generators/custom-action.ts`: both SvelteKit route emitters,\n `resolveApiActionSet`, and `knowledge.ts` read it, and a new consumer must too\n — a local mirror is how a method gets reported unavailable while its route\n file is still written. A public method routes by default only when every\n parameter is JSON-shaped; `@method({ expose })` overrides in both directions,\n `expose: true` bypasses the heuristic ALONE, and an explicit `api.include` or\n `api.routes` entry keeps its pre-#2686 route. Fail closed on scanner\n uncertainty: read `parameters[].typeUnresolved`, never the `'any'` it\n substitutes. Accepting `Date` as wire-able and hydrating it\n (`toCustomActionDate`) are one decision — changing either breaks the other.\n The runtime `APIGenerator` transport stays declaration-gated: `rest.ts`\n dispatch and `preflight-route.ts` prediction both read\n `declaresRuntimeRestRoute()`, which must accept every `ApiCustomRouteConfig`\n option so a sweep moving one onto its method cannot delete the endpoint. Its\n twin `declaresRuntimeRestRouteShape()` deliberately ignores `expose: false`:\n the dispatcher must still SEE a withheld declaration to answer 404, because\n `POST /<collection>/<segment>` resolves to `create` when nothing claims the\n segment. Split unions and type arguments with `splitTopLevel()` — a naive\n `split('|')` truncates `Record<string, Asset | null>` into fragments that\n match no rule and are then accepted, widening the gate. `extractTypeName`\n returns `null` for a generic with an unresolvable ARGUMENT or a union with an\n unresolvable BRANCH so those reach that fail-closed path instead of arriving\n as a bare `'Array'`. Runtime transports read\n `ObjectRegistry.resolveRuntimeMethod()`, which tries the item class's manifest\n entry, then the COLLECTION class's (where a collection-hosted action's\n parameters live), then the live `@method()` store — keyed by CONSTRUCTOR,\n never by simple name (`Account` exists in two packages), and recording\n `isStatic` because an unscanned runtime has no manifest to recover the\n receiver from. `isRestActionRoutable` ANDs \"declared\" with that receiver, so\n it never predicts `allow` for an action dispatch refuses. A declared action\n with no receiver is refused (501 collection-scoped, 404 item-scoped), never\n allowed to fall through into `create`. All eight consumers read the resolver,\n including `packages/smrt-workbench/src/discovery.ts`; it imports the\n `./generators/custom-action` LEAF subpath, not `./generators`, because that\n barrel value-re-exports `MCPGenerator` and so drags `@happyvertical/ai` and\n `@happyvertical/sql` into a build-time helper (2 modules vs 109). Keep\n `custom-action.ts` free of value imports outside `tools/tool-generator`.\n\n## Gotchas\n\n- Optional filesystem support stays lazy: use `createFilesystemAdapter()` in\n `src/filesystem-loader.ts`, not a static files-SDK import. Fully bundled apps\n import `@happyvertical/smrt-core/filesystem` at startup. Use\n `importOptionalDependency()` for similarly heavy optional dependencies.\n- Database retries are transient-only, four attempts total for `get`/`upsert`.\n Use `src/db-errors.ts` classifiers through the cause chain, never message\n matching (SDK driver text can live in `context.originalError`). Constraints,\n bad input, missing tables, and aborted PostgreSQL transactions fail immediately.\n Unique/PK violations become `VALIDATION_UNIQUE_CONSTRAINT`, NOT NULL becomes\n `VALIDATION_REQUIRED_FIELD`; other failures keep the driver error as `cause`.\n- Property initializers precede option values; options win. Arrays/objects are\n shallow-cloned. Collection creation caches fields; table verification caches\n by DB URL and table. Preserve these scopes when changing initialization.\n- The Vite plugin loads scanner/schema code from `dist/` when present. Rebuild\n core after editing those sources before testing consumer manifest generation.\n Vite 8 requires `oxc.decorator: { legacy: true, emitDecoratorMetadata: true }`.\n\n## Validation\n\nRun focused tests first, then applicable package checks:\n\n```bash\npnpm --filter @happyvertical/smrt-core test\npnpm --filter @happyvertical/smrt-core typecheck\npnpm --filter @happyvertical/smrt-core build\npnpm --filter @happyvertical/smrt-core test:postgres\npnpm check:agents-chain\npnpm smrt dev:knowledge-check\n```\n\nThe PostgreSQL lane is required for numeric types, UUID casts, conflict targets,\ntimestamps, and migrations. Tests generate their manifest before Vitest; restart\nwatch mode after adding decorated classes. Documentation-only changes need\ninstruction-chain and knowledge freshness checks, not the runtime test suite.\n",
1067
+ "agentDoc": "# @happyvertical/smrt-core\n\nFoundation ORM, registry, schema/code generation, AI integration, and DispatchBus.\nRead the module for the subsystem being edited; root AGENTS covers shared model\nand repository rules.\n\n## Modules\n\n| Source | Scope | Module doc |\n|---|---|---|\n| `src/object.ts`, `src/collection.ts`, `src/child-accessors.ts` | Lifecycle, hydration, operators, STI, child accessors, dispatch | [agents/object-runtime.md](agents/object-runtime.md) |\n| `src/revision-guard.ts` | Guarded writes and PostgreSQL revision precision | [agents/revision-guard.md](agents/revision-guard.md) |\n| `src/collection.ts` | Projections, latest-related, facets, counts, read plans | [agents/collection-reads.md](agents/collection-reads.md) |\n| `src/collection.ts` | Limits, sort whitelist, generated list order | [agents/query-bounds.md](agents/query-bounds.md) |\n| `src/data-query.ts` | Transport-neutral bounded query normalization | [agents/data-query.md](agents/data-query.md) |\n| `src/schema/`, `src/migrations/`, `src/cascade.ts`, `src/system/` | DDL parity, indexes, migrations, delete integrity, retention | [agents/schema-paths.md](agents/schema-paths.md) |\n| `src/postgres-permissions.ts` | Explicit PostgreSQL ACL plans and atomic reconciliation | [deployment permission contract](../../docs/content/postgres-permissions.md) |\n| `src/change-feed.ts` | Durable changes, cursors, table versions, retention | [agents/change-feed.md](agents/change-feed.md) |\n| `src/change-feed-authz.ts` | Consumer table/row change-feed authorization hooks (#3020) | [agents/change-feed.md](agents/change-feed.md) |\n| `src/change-signals.ts` | Signal bus, replica fan-out, SSE | [agents/change-signals.md](agents/change-signals.md) |\n| `src/generators/`, `src/vite-plugin/web-collections.ts` | REST/CLI/MCP generation, manifest hashes, ETags | [agents/generators.md](agents/generators.md) |\n| `src/vite-plugin/`, `src/consumer-plugin/`, `src/knowledge.ts` | Decorator UI hints, knowledge projection, generation snapshots | [agents/build-knowledge.md](agents/build-knowledge.md) |\n| `src/knowledge-graph.ts` | Cross-package knowledge graph merge and freshness check (#2863) — see [standards](../../docs/content/standards.md) | — |\n| `src/object.ts`, `src/collection.ts`, `src/learning/memory.ts` | Context memory and semantic search | [agents/memory.md](agents/memory.md) |\n| `src/system/diagnostics.ts` | SELECT-only `_smrt_*` diagnostics reader behind smrt-dev-mcp runtime tools (#1824) | [agents/system-diagnostics.md](agents/system-diagnostics.md) |\n| `src/system/registry-snapshot.ts` | Sanitized plain-JSON projection of the booted `ObjectRegistry` for the smrt-dev-mcp runtime dev-plane (#1831); never constructors, validators, values, or absolute paths | [agents/registry-snapshot.md](agents/registry-snapshot.md) |\n| `src/registry/qualified-name-aliases.ts`, `src/migrations/qualified-name-aliases.ts` | Model moves: `previousQualifiedNames` aliases, deprecation, doctor counts, opt-in backfill (#3338) | [agents/model-moves.md](agents/model-moves.md) |\n| `src/run-once.ts` | Shared idempotency seam: insert-only claim + caller work in one transaction, replay on retry, typed in-flight/unknown-outcome answers (#3080) | [agents/run-once.md](agents/run-once.md) |\n\n## Cross-module invariants\n\n- `ObjectRegistry` is a `globalThis` singleton so registration survives HMR.\n- Per-class registry work on the hydration path (manifest reconciliation in\n `ensureManifestLoaded()`, the simple-name index behind `findClass()`) is\n memoized per registry generation (`src/registry/generation.ts`, #3047). Any\n new writer of registered classes, their field/method metadata, or loaded\n manifests must go through the tracked maps or call\n `bumpRegistryGeneration()`; `issue-3047-hydration-registry-work.test.ts`\n guards that hydration does not repeat this work per row.\n\n- Production DDL uses manifest generators; `getTestDatabase()` uses registry\n generators. Keep columns, indexes, FK actions, and runtime conflict targets in\n parity (`src/schema/schema-path-parity.test.ts`). Every new query predicate\n needs its index or an explicit reason none is needed.\n- Tenant scoping covers every read and every unique/conflict key. Explicit\n conflict columns are not rewritten; their author must include tenant scope.\n A `tenantId` field makes rows tenant-OWNED without any tenancy declaration:\n the default key leads with `tenant_id`. A new object's natural-key save reads\n the row its upsert would hit and refuses a different owner\n (`TENANT_ISOLATION_VIOLATION`) or adopts a same-owner row's id, so\n `DO UPDATE` never rewrites a primary key or owner. Only an explicit natural\n key adopts: a slug `getSlug()` derived from `name`/`title`/`label` moves to\n a free `-2`, `-3`, … slug and INSERTs. See `agents/schema-paths.md`\n (\"Tenant OWNERSHIP\").\n- Persisted saves use `id` and loaded `updated_at`; new saves use natural keys.\n Preserve revision compare-and-swap ordering through public `save()`,\n `claimRevision()`, and transaction APIs. Embedded saves, deletes, and complete\n `withTransaction()` callbacks share a process-local write queue.\n- Collection model hydration is serial in result order: initialization may query\n the same transaction-bound PostgreSQL client. Use projections for plain rows.\n- Native DuckDB UUIDs must be cast coherently on read before identity reuse;\n custom embedded revision paths use `getCanonicalPersistedRow()`. Never replay\n a mutation to discover result types.\n- `withDatabase(db, callback)` restores only database bindings (including public\n `options.db`); `withTransaction(callback)` also restores identity/revision\n metadata after rollback. Do not use a bound instance concurrently.\n- `runOnce({ db, tenantId, actor, token, content }, work)` (`src/run-once.ts`,\n #3080) claims a key derived from all four inputs — never the token alone,\n which collapses distinct submissions — via `INSERT ... ON CONFLICT\n (claim_key) DO NOTHING RETURNING claim_key` into the hand-DDL\n `_smrt_run_once_claims` system table, and runs `work` in the SAME\n transaction; a completed claim replays its stored result instead of\n re-running, a failed `work` rolls the claim back with the transaction, and\n an unresolved claim raises typed `RunOnceClaimError`\n (`RUN_ONCE_IN_FLIGHT` / `RUN_ONCE_OUTCOME_UNKNOWN`) rather than guessing.\n The claim insert must never raise on conflict: PostgreSQL aborts the whole\n transaction on any raised error, which would take the recovery `SELECT`\n down with it too — this is why it is `DO NOTHING`, not a plain `INSERT`\n caught for `isUniqueViolationError`. Completed claims are bounded by\n `runRetentionSweep()`'s `run-once-claims` task (`system/retention.ts`,\n `DEFAULT_RETENTION_POLICY.runOnceClaims`, default 30 days on\n `completed_at`; `in_progress` rows are never matched) — the window is a\n replay deadline, not just disk hygiene: a token replayed after its claim\n is swept runs `work()` again. See `agents/run-once.md`.\n- `ensureSystemTables(db, typeHint?)` provisions framework tables idempotently;\n call it on a base PostgreSQL connection before caller-owned transactions.\n Bootstrap uses a transaction-scoped advisory lock, taken only when the\n PostgreSQL schema version is not yet stamped: inside a caller's transaction it\n is held until that transaction ends (#2868). Application tables still require migrations;\n runtime table verification checks existence only.\n- Manifest generation fails closed on scanner errors, including unresolved\n decorator spreads. Never emit partial, default-open registration.\n- Generated registration repairs bundled class identity using the exact imported\n constructor, explicit package, and isolated one-object manifest. Never infer\n ownership from paths, simple names, or table names; packages can share names.\n Consumer regression gate: `packages/bundle-gate/src/__tests__/registry-identity.spec.ts`.\n- Decorator registration trusts a class's stack-derived package only when that\n package declares the class (stub, own manifest entry, or explicit\n `packageName`); it then matches the qualified key and never adopts another\n package's same-named registration. Otherwise a loaded (or the cwd project's)\n manifest entry describing the declaring file supplies the identity — apps\n scan workspace-package sources under the app's name (#3110). A class of\n another known package coexists under its qualified key\n (`decorator-different-packages-qualified-coexist`); bundled output needs\n a resolved table that differs first (#3106). Stack\n attribution skips smrt-core (including source-mapped installed\n `smrt-core/src` frames, #3109), decorator-helper, and module-runner frames.\n Instance and collection registry lookups pass the qualified name. The schema\n planner rejects a table claimed by two unrelated classes\n (`CONFIG_TABLE_NAME_COLLISION`): only one STI family shares a table (#3098);\n collections (including `SmrtReportCollection`) never claim one. Consumer\n gates: `registry/__tests__/issue-3110-*.test.ts`, `issue-3109-*.test.ts`,\n `packages/bundle-gate/src/__tests__/consumer-same-name.spec.ts`.\n- API custom-action eligibility has ONE resolver, `resolveApiMethodExposure()`\n in `generators/custom-action.ts`: both SvelteKit route emitters,\n `resolveApiActionSet`, and `knowledge.ts` read it, and a new consumer must too\n — a local mirror is how a method gets reported unavailable while its route\n file is still written. A public method routes by default only when every\n parameter is JSON-shaped; `@method({ expose })` overrides in both directions,\n `expose: true` bypasses the heuristic ALONE, and an explicit `api.include` or\n `api.routes` entry keeps its pre-#2686 route. Fail closed on scanner\n uncertainty: read `parameters[].typeUnresolved`, never the `'any'` it\n substitutes. Accepting `Date` as wire-able and hydrating it\n (`toCustomActionDate`) are one decision — changing either breaks the other.\n The runtime `APIGenerator` transport stays declaration-gated: `rest.ts`\n dispatch and `preflight-route.ts` prediction both read\n `declaresRuntimeRestRoute()`, which must accept every `ApiCustomRouteConfig`\n option so a sweep moving one onto its method cannot delete the endpoint. Its\n twin `declaresRuntimeRestRouteShape()` deliberately ignores `expose: false`:\n the dispatcher must still SEE a withheld declaration to answer 404, because\n `POST /<collection>/<segment>` resolves to `create` when nothing claims the\n segment. Split unions and type arguments with `splitTopLevel()` — a naive\n `split('|')` truncates `Record<string, Asset | null>` into fragments that\n match no rule and are then accepted, widening the gate. `extractTypeName`\n returns `null` for a generic with an unresolvable ARGUMENT or a union with an\n unresolvable BRANCH so those reach that fail-closed path instead of arriving\n as a bare `'Array'`. Runtime transports read\n `ObjectRegistry.resolveRuntimeMethod()`, which tries the item class's manifest\n entry, then the COLLECTION class's (where a collection-hosted action's\n parameters live), then the live `@method()` store — keyed by CONSTRUCTOR,\n never by simple name (`Account` exists in two packages), and recording\n `isStatic` because an unscanned runtime has no manifest to recover the\n receiver from. `isRestActionRoutable` ANDs \"declared\" with that receiver, so\n it never predicts `allow` for an action dispatch refuses. A declared action\n with no receiver is refused (501 collection-scoped, 404 item-scoped), never\n allowed to fall through into `create`. All eight consumers read the resolver,\n including `packages/smrt-workbench/src/discovery.ts`; it imports the\n `./generators/custom-action` LEAF subpath, not `./generators`, because that\n barrel value-re-exports `MCPGenerator` and so drags `@happyvertical/ai` and\n `@happyvertical/sql` into a build-time helper (2 modules vs 109). Keep\n `custom-action.ts` free of value imports outside `tools/tool-generator`.\n\n## Gotchas\n\n- Optional filesystem support stays lazy: use `createFilesystemAdapter()` in\n `src/filesystem-loader.ts`, not a static files-SDK import. Fully bundled apps\n import `@happyvertical/smrt-core/filesystem` at startup. Use\n `importOptionalDependency()` for similarly heavy optional dependencies.\n- Database retries are transient-only, four attempts total for `get`/`upsert`.\n Use `src/db-errors.ts` classifiers through the cause chain, never message\n matching (SDK driver text can live in `context.originalError`). Constraints,\n bad input, missing tables, and aborted PostgreSQL transactions fail immediately.\n Unique/PK violations become `VALIDATION_UNIQUE_CONSTRAINT`, NOT NULL becomes\n `VALIDATION_REQUIRED_FIELD`; other failures keep the driver error as `cause`.\n- Property initializers precede option values; options win. Arrays/objects are\n shallow-cloned. Collection creation caches fields; table verification caches\n by DB URL and table. Preserve these scopes when changing initialization.\n- The Vite plugin loads scanner/schema code from `dist/` when present. Rebuild\n core after editing those sources before testing consumer manifest generation.\n Vite 8 requires `oxc.decorator: { legacy: true, emitDecoratorMetadata: true }`.\n\n## Validation\n\n`typecheck` checks production source first, then the test project in\n`tsconfig.test.json`. Test coverage and the shared relaxed config\ncontract are documented in [agents/test-types.md](agents/test-types.md).\n\nRun focused tests first, then applicable package checks:\n\n```bash\npnpm --filter @happyvertical/smrt-core test\npnpm --filter @happyvertical/smrt-core typecheck\npnpm --filter @happyvertical/smrt-core build\npnpm --filter @happyvertical/smrt-core test:postgres\npnpm check:agents-chain\npnpm smrt dev:knowledge-check\n```\n\nThe PostgreSQL lane is required for numeric types, UUID casts, conflict targets,\ntimestamps, and migrations. Tests generate their manifest before Vitest; restart\nwatch mode after adding decorated classes. Documentation-only changes need\ninstruction-chain and knowledge freshness checks, not the runtime test suite.\n",
1067
1068
  "moduleDocs": [
1068
1069
  {
1069
1070
  "path": "agents/object-runtime.md",
@@ -1139,6 +1140,11 @@
1139
1140
  "path": "agents/run-once.md",
1140
1141
  "module": "run-once",
1141
1142
  "content": "# runOnce: shared idempotency seam (#3080)\n\n`src/run-once.ts`. A DB/transaction-level primitive — it takes a\n`DatabaseInterface`, not a `SmrtObject`/`SmrtCollection` — so it works outside\nSvelteKit and outside the ORM. Any caller with a database handle and a\nJSON-serializable work result can use it.\n\n## Why this exists, and why it is not the token alone\n\nEvery consumer with a form, a retried request, or a double-tapped button\nneeds the same guard, and a first hand-rolled attempt at it (reported against\nthis issue) keyed the claim on the per-form token alone. That silently\ncollapsed two genuinely different submissions that happened to reuse one\nform's token — the exact bug this primitive exists to prevent everyone from\nre-introducing.\n\n## Key derivation\n\n```\nnormalized = normalizeRunOnceContent(content) // one pass; FormData captured as a node\ncontentDigest = containsFormData(normalized)\n ? sha256(\"\\u0001run-once-formdata-v1\\n\" + stableStringify(toFormDataDomain(normalized)))\n : sha256(stableStringify(normalized)) // plain JSON: unchanged since #3080\nclaimKey = sha256(stableStringify([tenantId, actor, token, contentDigest]))\n```\n\n`stableStringify` (`../src/knowledge-graph.ts`) is sorted-key JSON: property\ninsertion order never changes the digest. Content is normalized first, following\nJSON serialization exactly: `toJSON(key)` is called with the property name or\narray index (a `Date` digests as its ISO string, not `{}`), and `undefined`,\nfunction and symbol values are omitted from objects and written as `null` in\narrays. A `FormData` has no enumerable properties, so\nJSON would reduce every form to `{}`; it digests as its ordered entry list\ninstead (repeated fields are significant; a `File` digests by name, type and\nsize), hashed in a separate domain (the input is prefixed with a byte sorted-key\nJSON never starts with, and user keys starting with NUL are escaped) so no plain\nobject can forge it and plain-JSON digests are unchanged. `Map`, `Set`,\n`RegExp`, any other non-plain object without `toJSON()`, and `bigint` throw a\n`TypeError` rather than silently hashing as `{}` (#3136). Folding in `tenantId`\nand `actor`\nmeans two tenants or two actors can never collide on the same claim even if a\ntoken were somehow shared between them; folding in `contentDigest` means a\nretry of the SAME submission (same token, same content) replays, while a\nDIFFERENT submission through the same token (edited content before a\nresubmit) runs as its own claim.\n\nNeither the raw token nor the raw content is ever persisted — only their\ndigests — so `_smrt_run_once_claims` cannot leak submitted business data.\n\n## Placement: `smrt-core`, not `smrt-svelte`\n\nThe issue posed this as a judgement call. `runOnce()` lives in `smrt-core`\nbeside the other write primitives (`object.ts`, `collection.ts`) because:\n\n- It is DB/transaction-level, not SvelteKit-specific — any caller with a\n `DatabaseInterface` can use it, including CLI jobs, MCP tool handlers, and\n non-web callers.\n- The claim table must exist for every `smrt-core` consumer, not only ones\n that also depend on `smrt-svelte`.\n\nThe browser half did not ship with it; it now lives in\n`@happyvertical/smrt-ui/form-retry` (`createFormRetry()`, #3291): a per-tab,\nper-form submission key in `sessionStorage` with an in-memory fallback,\nrotated only on a confirmed write that cleared the form, refused while a\nsubmit is in flight, kept with the typed values across a transport error, and\noptional restore-after-reload so the retry carries byte-identical content. It\nis in `smrt-ui` rather than `smrt-svelte` because smrt-ui is the dependency\nleaf that both `smrt-svelte` and plain SvelteKit apps can use, and its `Form`\ntakes `enhance` and the retry attachment directly (smrt#3265: attach\n`fromAction(enhance, () => retry.enhance())` and `retry.attach`). It is typed\nstructurally against SvelteKit's `SubmitFunction`, so smrt-ui takes no\n`@sveltejs/kit` dependency. Its `digestSubmissionContent()` is a Web Crypto port of\n`digestRunOnceContent()`; `src/__tests__/issue-3291-digest-vectors.test.ts`\npins vectors shared with the port, so change both together (and remember a\ndigest change re-keys every stored claim). Guide:\n[form retry](../../../docs/content/form-retry.md).\n\n## Schema: a hand-DDL system table, not an `@smrt()` model\n\n`_smrt_run_once_claims` is declared in `src/system/schema.ts`\n(`CREATE_SMRT_RUN_ONCE_CLAIMS_TABLE`), added to `ALL_SYSTEM_TABLES`, and\ncreated by `ensureSystemTables()` / `bootstrapSystemTables()`\n(`src/system/bootstrap.ts`) — the same mechanism that creates\n`_smrt_migrations` and `_smrt_contexts`. This is \"Mechanism A\" in the\n`_smrt_` prefix classification ([schema-paths.md](schema-paths.md)), not\n\"Mechanism B\" (an `@smrt()`-decorated model migrated via `db:migrate`, like\n`_smrt_jobs`).\n\nThis was a deliberate choice, not the default: `runOnce()` must work for any\n`smrt-core` consumer without that consumer registering a class and running a\nmigration for a table they've never heard of. A Mechanism-B table only exists\nonce an app imports and registers its owning class; a Mechanism-A table\nexists for every `smrt-core` database unconditionally, which is what a\nframework-wide primitive needs.\n\nAdding the table required:\n\n- A new DDL constant in `ALL_SYSTEM_TABLES`.\n- Bumping `SMRT_SCHEMA_VERSION` (1.10.1 → 1.11.0) — `CREATE TABLE IF NOT\n EXISTS` is a no-op on an existing database, so nothing upgrades without the\n bump.\n- A new `SMRT_SCHEMA_DDL_CHECKSUMS` entry — `system-schema-evolution.test.ts`\n recomputes the DDL hash and fails the build if it does not match the\n recorded value for the current version.\n\nNo new column was added to an existing system table, so no\n`addColumnIfMissing()` entry in `system/compatibility.ts` was needed. Live\nschema parity (`checkLiveSchemaParity`, `smrt doctor --db` /\n`db:status --parity`) picks the table up automatically: system-table shape\nparsing (`schema/system-table-shapes.ts`) derives its expectation from the\nsame DDL rather than a hand-maintained list.\n\n## Table shape\n\n```sql\nCREATE TABLE IF NOT EXISTS _smrt_run_once_claims (\n claim_key TEXT PRIMARY KEY, -- the derived key above\n tenant_id TEXT NOT NULL, -- redundant with claim_key; kept for admin queries and a defense-in-depth tenant filter on lookup\n actor TEXT NOT NULL, -- same\n content_digest TEXT NOT NULL, -- same, plus lets an operator distinguish \"same token, different content\" incidents\n status TEXT NOT NULL DEFAULT 'in_progress', -- 'in_progress' | 'completed'\n result TEXT, -- JSON-serialized work() return value, set only when status = 'completed'\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n completed_at TIMESTAMP\n);\n```\n\n`claim_key` is the PRIMARY KEY — the \"insert-only against a UNIQUE column\"\nthe issue asked for. A second `INSERT` for the same key never adopts the\nexisting row.\n\n**Why the claim insert is raw SQL (`INSERT ... ON CONFLICT (claim_key) DO\nNOTHING RETURNING claim_key`), not a plain `INSERT` caught for a classified\n`unique_violation`:** on PostgreSQL, a statement that raises an error aborts\nthe WHOLE transaction (\"current transaction is aborted, commands ignored\nuntil end of transaction block\") — every later statement in that same\ntransaction fails too, including the `SELECT` `runOnce()` needs to read the\nwinner's stored result back. A first version of this primitive did exactly\nthat (plain `INSERT` + catch classified via `isUniqueViolationError`,\n`../src/db-errors.ts`) and every replay/double-submit on PostgreSQL broke:\nreview caught it before merge. `DO NOTHING` never raises — it reports zero\nrows via `RETURNING` instead — so the transaction's error state is never\ntouched. SQLite and DuckDB support the identical `ON CONFLICT ... DO\nNOTHING` / `RETURNING` syntax (already used elsewhere in this codebase for\n`_smrt_migrations`, `system/bootstrap.ts`), so one query is portable across\nevery adapter without engine branching.\n\n## Atomicity and rollback\n\n`runOnce()` opens `db.transaction()` (or nests under a caller-supplied\ntransaction, per `@happyvertical/sql`'s own SAVEPOINT /\n`NestedTransactionError` rules) and, inside it: inserts the claim\n(`in_progress`), runs `work(tx)` with that SAME transaction-bound handle, then\nupdates the claim to `completed` with the JSON-serialized result — all one\ntransaction.\n\nIf `work()` throws, the callback throws, and `db.transaction()`'s own\ncommit-on-success/rollback-on-error contract undoes the claim insert along\nwith whatever `work()` partially wrote. A claim is never stranded\n`in_progress` by a failed attempt; a retry with the same key can always run\nagain. No compensating delete is needed — this falls out of using one\ntransaction for the whole claim+work+completion sequence, not a special case\nin `runOnce()`'s own code.\n\n## Concurrency\n\n`@happyvertical/sql`'s single-connection adapters (SQLite, DuckDB, JSON)\nserialize `transaction()` calls on one connection: a second, concurrent\n`runOnce()` call does not begin its own transaction until the first ends.\nPostgreSQL pools, but the claim insert's arbiter still has to check the\nunique index against another transaction's uncommitted row of the same\nPRIMARY KEY, and blocks until that transaction resolves, then either no-ops\n(committed — `DO NOTHING` fires, zero rows) or inserts (rolled back). On\nevery adapter, two concurrent callers with the same key never both run\n`work()`, and the loser never sees a PostgreSQL error for it — only zero\nrows back, at which point it reads the now-committed, now-`completed` claim\nrow and replays its stored result.\n\nThis is also why the \"in flight\" and \"unknown outcome\" branches below are\nnormally unreachable through `runOnce()`-to-`runOnce()` races alone on a\nsingle connection — the losing transaction can only ever observe a\nCOMMITTED row, and this module only ever commits a row once it is\n`completed`. They are kept as defined answers (not assumptions) for a claim\nrow that reached that state some other way, and are exercised in\n`src/__tests__/issue-3080-run-once.test.ts` by seeding the table directly and\nby unit-testing the pure resolver (`resolveExistingRunOnceClaim`).\n\n## Typed answers: refused vs. unknown outcome (mirrors #2990)\n\nA conflict on insert is a *definite* decision — the row exists — but whether\nit is safe to hand back a result depends on `status`:\n\n| Row state after conflict | Result |\n|---|---|\n| `status: 'completed'` | Replay the stored `result`. No error. |\n| exists, not completed | `RunOnceClaimError.inFlight()` — `code: 'RUN_ONCE_IN_FLIGHT'`. Some execution holds this key right now; retry later with the SAME token and content. |\n| conflict reported, no row readable | `RunOnceClaimError.outcomeUnknown()` — `code: 'RUN_ONCE_OUTCOME_UNKNOWN'`. Genuinely indeterminate; retrying with the SAME token and content is still safe. |\n\nBoth error codes live on one `RunOnceClaimError` class (`src/errors.ts`),\nfollowing this codebase's existing error-hierarchy convention (one class,\nmultiple codes via static factories — see `TenantIsolationError`,\n`DatabaseError`) rather than a bespoke class hierarchy. Neither is retried\nautomatically by `runOnce()` itself: only the caller knows whether retrying\ninline is appropriate for its own request.\n\n## Retention: completed claims are pruned; the window is a replay deadline\n\n`_smrt_run_once_claims` is append-only in normal operation — every submission\nadds a row and nothing else ever deletes one — so, like `_smrt_changes`,\n`_smrt_ai_usage`, and `_smrt_dispatch`, it is wired into\n`system/retention.ts`'s `runRetentionSweep()` as a built-in task\n(`'run-once-claims'`), covered by `DEFAULT_RETENTION_POLICY.runOnceClaims`\n(default `{ maxAgeDays: 30 }`, overridable per policy or disabled with\n`runOnceClaims: false`). `pruneRunOnceClaims()` deletes only rows with\n`status = 'completed'` AND `completed_at` older than the window; an\n`'in_progress'` row has no `completed_at` and can never match this predicate,\nso a claim held by a live (or abandoned) transaction is never pruned by age\nalone. The predicate is covered by\n`idx_smrt_run_once_claims_status_completed` (`status, completed_at`), added\nin `SMRT_SCHEMA_VERSION` 1.11.1.\n\n**The window is a replay deadline, not just disk hygiene.** `runOnce()`'s\nentire contract — a retry or double-submit with the SAME token and content\nreplays the first attempt's stored result instead of running `work()` again\n— depends on the claim row still existing. Once retention sweeps a completed\nclaim, a client that replays that exact request (same `tenantId`, `actor`,\n`token`, and content) no longer collides with anything: `runOnce()` sees no\nexisting row, wins the insert, and runs `work()` a second time. This is\nsilent — there is no error, just a second execution of whatever `work()`\ndoes. The default 30-day window is chosen to be comfortably longer than any\nrealistic client retry window (a browser tab left open, a queued mobile\nrequest, a webhook redelivery schedule) for the same reason the other\ndefault windows in `DEFAULT_RETENTION_POLICY` are generous relative to their\nconsumers (see `system/retention.ts`'s module doc). A consumer with a\nlonger-lived retry/replay window (e.g. an at-least-once delivery queue with a\nmulti-week redelivery tail) must raise `runOnceClaims.maxAgeDays` to exceed\nit, not rely on the default.\n\nTests: `src/__tests__/issue-2375-retention.test.ts` covers\n`pruneRunOnceClaims()` end-to-end through `runRetentionSweep()` — an old\ncompleted claim is removed, a recent completed claim and an old\n`in_progress` claim both survive.\n\n## Tests\n\n`src/__tests__/issue-3080-run-once.test.ts` (real in-memory SQLite via\n`getTestDatabase({ classes: [] })` — no `@smrt()` model is needed, only the\nsystem table):\n\n- Runs work once; returns its result.\n- Retry after completion replays the stored result without re-running.\n- Concurrent double submit (`Promise.all`) produces one claim row and the\n same result for both callers.\n- Same token, different content → two separate claims (guards the\n token-alone regression this issue was filed against).\n- Same token/content, different actor → separate claim.\n- Same token/content, different tenant → separate claim.\n- Work throws → claim rolls back; a subsequent retry with the same key runs.\n- A claim seeded directly as `in_progress` → `RunOnceClaimError.inFlight()`,\n `work()` never called.\n- Content-digest stability (key order) and sensitivity (value change) are\n unit-tested directly.\n- `resolveExistingRunOnceClaim` (pure resolver) is unit-tested for all three\n branches, including the otherwise-unreachable `outcomeUnknown` case.\n\n`src/__tests__/issue-3080-run-once-postgres.optional.test.ts` (real\nPostgreSQL, `SMRT_TEST_POSTGRES_URL`, `describe.skipIf`, following\n`change-feed-concurrency.optional.test.ts`'s established pattern: distinct\n`dbid`s for two genuinely concurrent connections, `getTestDatabase({ db,\nclasses: [] })` to bootstrap system tables): replay after completion, and\nconcurrent double submit across the two real connections — both would\nreject with a PostgreSQL `DatabaseError` (\"Failed to retrieve record from\ntable\", the driver's wrapping of 25P02 \"current transaction is aborted\")\nagainst the pre-fix plain-`INSERT`-and-catch version; verified locally\nagainst a disposable `postgres:18-alpine` container (revert via patch file,\nconfirmed both tests red, restored, confirmed both green) since the SQLite\nsuite cannot reach this PostgreSQL-specific failure mode at all.\n"
1143
+ },
1144
+ {
1145
+ "path": "agents/test-types.md",
1146
+ "module": "test-types",
1147
+ "content": "# Test typechecking\n\nCore's normal `typecheck` command runs its strict production project followed\nby `tsconfig.test.json`; the existing required CI typecheck task runs that same\ncommand. The test project includes every `*.test.ts` and `*.spec.ts` under\n`src/`, including optional and integration suites. These globs include future\ntests automatically. Do not exclude individual failing tests.\n\nAll sibling packages in epic #2726 should extend the root\n`tsconfig.package-test.json` from a package-local `tsconfig.test.json`, override\n`include` and `exclude` explicitly, and add the test project to their normal\n`typecheck` command after source validation. The shared project emits nothing,\nloads Node and Vitest globals, and relaxes implicit-any, property-initialization,\nexact-optional-property, and unchecked-index checks for fixtures. Production\nprojects keep their strict settings. Module resolution, missing-member, and\nargument compatibility diagnostics remain enabled.\n\nNull checks remain enabled: turning them off also changes narrowing in imported\nproduction source, breaking discriminated unions and guards for unknown values.\nUse existing fixture assertions to justify non-null assertions or narrow values\nat the test boundary. Deliberately invalid inputs and intentional protected\nlifecycle access require a narrowly scoped escape with its reason documented.\nUse public registry APIs when they provide the same behavior.\n\nVerify membership with `tsc -p tsconfig.test.json --listFiles`, and measure the\ntest project separately from the source project when reporting compiler cost.\nBefore claiming that drift is caught, add a temporary included test referencing\na deleted type-only import, a missing member, and an invalid argument; confirm\nthe normal package `typecheck` command fails, then remove the probe and rerun it.\n"
1142
1148
  }
1143
1149
  ]
1144
1150
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-core",
3
- "version": "0.53.1",
3
+ "version": "0.53.3",
4
4
  "description": "Core AI agent framework with standardized collections, object-relational mapping, and code generators",
5
5
  "author": "HappyVertical",
6
6
  "type": "module",
@@ -154,9 +154,9 @@
154
154
  "@happyvertical/files": "^0.98.0",
155
155
  "@happyvertical/json": "^0.98.0",
156
156
  "@happyvertical/logger": "^0.98.0",
157
- "@happyvertical/smrt-config": "0.53.1",
158
- "@happyvertical/smrt-scanner": "0.53.1",
159
- "@happyvertical/smrt-types": "0.53.1",
157
+ "@happyvertical/smrt-config": "0.53.3",
158
+ "@happyvertical/smrt-scanner": "0.53.3",
159
+ "@happyvertical/smrt-types": "0.53.3",
160
160
  "@happyvertical/sql": "^0.98.0",
161
161
  "@happyvertical/utils": "^0.98.0",
162
162
  "@oxc-project/runtime": "^0.150.0",
@@ -204,7 +204,7 @@
204
204
  "directory": "packages/core"
205
205
  },
206
206
  "scripts": {
207
- "typecheck": "tsc -p tsconfig.typecheck.json",
207
+ "typecheck": "tsc -p tsconfig.typecheck.json && tsc -p tsconfig.test.json",
208
208
  "generate": "node scripts/generate-manifest.js",
209
209
  "generate:test": "node scripts/generate-test-manifest.js",
210
210
  "test": "node scripts/generate-test-manifest.js && vitest run",
@@ -1,20 +0,0 @@
1
- /**
2
- * Fixtures for collection.ts coverage tests (convenience finders, count,
3
- * getDiff, getOrUpsert, and collection-scoped memory).
4
- *
5
- * Separate file so the @smrt() classes land in the test manifest.
6
- *
7
- * @see src/collection.ts (issue #1500 coverage, ORM group)
8
- */
9
- import { SmrtCollection } from '../../collection.js';
10
- import { SmrtObject } from '../../object.js';
11
- export declare class CovWidget extends SmrtObject {
12
- name: string;
13
- price: number;
14
- category: string;
15
- inStock: boolean;
16
- }
17
- export declare class CovWidgetCollection extends SmrtCollection<CovWidget> {
18
- static readonly _itemClass: typeof CovWidget;
19
- }
20
- //# sourceMappingURL=collection-coverage-fixtures.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"collection-coverage-fixtures.d.ts","sourceRoot":"","sources":["../../../src/__tests__/fixtures/collection-coverage-fixtures.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7C,qBACa,SAAU,SAAQ,UAAU;IACvC,IAAI,EAAE,MAAM,CAAM;IAClB,KAAK,EAAE,MAAM,CAAO;IACpB,QAAQ,EAAE,MAAM,CAAM;IACtB,OAAO,EAAE,OAAO,CAAQ;CACzB;AAED,qBAAa,mBAAoB,SAAQ,cAAc,CAAC,SAAS,CAAC;IAChE,MAAM,CAAC,QAAQ,CAAC,UAAU,mBAAa;CACxC"}
@@ -1,37 +0,0 @@
1
- /**
2
- * Fixture classes for inheritance-resolver coverage tests.
3
- *
4
- * Kept in a separate file (not the test file) so they are picked up by
5
- * test-manifest generation. Mirrors the convention used by
6
- * inheritance-test-classes.ts.
7
- *
8
- * @see src/registry/inheritance-resolver.ts (issue #1500 coverage, ORM group)
9
- */
10
- import { SmrtHierarchical } from '../../hierarchical.js';
11
- import { SmrtObject } from '../../object.js';
12
- export declare class IRBase extends SmrtObject {
13
- baseTitle: string;
14
- shared: string;
15
- baseMethod(): Promise<string>;
16
- }
17
- export declare class IRMiddle extends IRBase {
18
- middleField: string;
19
- middleMethod(): Promise<string>;
20
- }
21
- export declare class IRLeaf extends IRMiddle {
22
- leafField: string;
23
- baseMethod(): Promise<string>;
24
- }
25
- export declare class IRConstraintParent extends SmrtObject {
26
- amount: number;
27
- }
28
- export declare class IRConstraintChild extends IRConstraintParent {
29
- amount: number;
30
- }
31
- export declare class IRTreeNode extends SmrtHierarchical {
32
- label: string;
33
- }
34
- export declare class IRSolo extends SmrtObject {
35
- soloField: string;
36
- }
37
- //# sourceMappingURL=inheritance-resolver-fixtures.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"inheritance-resolver-fixtures.d.ts","sourceRoot":"","sources":["../../../src/__tests__/fixtures/inheritance-resolver-fixtures.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,qBACa,MAAO,SAAQ,UAAU;IACpC,SAAS,EAAE,MAAM,CAAM;IACvB,MAAM,EAAE,MAAM,CAAM;IAEd,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;CAGpC;AAED,qBACa,QAAS,SAAQ,MAAM;IAClC,WAAW,EAAE,MAAM,CAAM;IAEnB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;CAGtC;AAED,qBACa,MAAO,SAAQ,QAAQ;IAClC,SAAS,EAAE,MAAM,CAAM;IAGjB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;CAGpC;AAGD,qBACa,kBAAmB,SAAQ,UAAU;IAEhD,MAAM,EAAE,MAAM,CAAK;CACpB;AAED,qBACa,iBAAkB,SAAQ,kBAAkB;IAEvD,MAAM,EAAE,MAAM,CAAK;CACpB;AAKD,qBACa,UAAW,SAAQ,gBAAgB;IAC9C,KAAK,EAAE,MAAM,CAAM;CACpB;AAGD,qBACa,MAAO,SAAQ,UAAU;IACpC,SAAS,EAAE,MAAM,CAAM;CACxB"}
@@ -1,48 +0,0 @@
1
- /**
2
- * Test fixture classes for inheritance tests
3
- * These classes must be in a separate file (not in the test file itself)
4
- * so they can be included in the test manifest generation
5
- */
6
- import { SmrtObject } from '../../object.js';
7
- export declare class InheritanceTestContent extends SmrtObject {
8
- title: string;
9
- body: string;
10
- publishedAt: Date | null;
11
- wordCount: number;
12
- generateSummary(): Promise<string>;
13
- }
14
- export declare class InheritanceTestPraecoContent extends InheritanceTestContent {
15
- praecoCustom1: string;
16
- sourceUrl: string;
17
- rating: number;
18
- analyzeSentiment(): Promise<string>;
19
- }
20
- export declare class InheritanceTestBentleyContent extends InheritanceTestPraecoContent {
21
- bentleyCustom1: string;
22
- localTags: string[];
23
- analyzeLocal(): Promise<string>;
24
- }
25
- export declare class ParentWithConstraints extends SmrtObject {
26
- age: number;
27
- score: number;
28
- }
29
- export declare class ChildWithConstraints extends ParentWithConstraints {
30
- age: number;
31
- score: number;
32
- }
33
- export declare class ParentTypeMismatch extends SmrtObject {
34
- myField: string;
35
- }
36
- export declare class ChildTypeMismatch extends ParentTypeMismatch {
37
- myField: number;
38
- }
39
- export declare class ParentWithMethod extends SmrtObject {
40
- doSomething(): Promise<string>;
41
- }
42
- export declare class ChildWithMethod extends ParentWithMethod {
43
- doSomething(): Promise<string>;
44
- }
45
- export declare class OrphanClass extends SmrtObject {
46
- orphanField: string;
47
- }
48
- //# sourceMappingURL=inheritance-test-classes.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"inheritance-test-classes.d.ts","sourceRoot":"","sources":["../../../src/__tests__/fixtures/inheritance-test-classes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,qBACa,sBAAuB,SAAQ,UAAU;IACpD,KAAK,EAAE,MAAM,CAAM;IACnB,IAAI,EAAE,MAAM,CAAM;IAClB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAQ;IAChC,SAAS,EAAE,MAAM,CAAK;IAEhB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;CAGzC;AAGD,qBACa,4BAA6B,SAAQ,sBAAsB;IACtE,aAAa,EAAE,MAAM,CAAM;IAC3B,SAAS,EAAE,MAAM,CAAM;IACvB,MAAM,EAAE,MAAM,CAAO;IAEf,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;CAG1C;AAGD,qBACa,6BAA8B,SAAQ,4BAA4B;IAC7E,cAAc,EAAE,MAAM,CAAM;IAC5B,SAAS,EAAE,MAAM,EAAE,CAAM;IAEnB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;CAGtC;AAGD,qBACa,qBAAsB,SAAQ,UAAU;IAEnD,GAAG,EAAE,MAAM,CAAK;IAGhB,KAAK,EAAE,MAAM,CAAO;CACrB;AAED,qBACa,oBAAqB,SAAQ,qBAAqB;IAG7D,GAAG,EAAE,MAAM,CAAK;IAGhB,KAAK,EAAE,MAAM,CAAO;CACrB;AAGD,qBACa,kBAAmB,SAAQ,UAAU;IAChD,OAAO,EAAE,MAAM,CAAM;CACtB;AAED,qBACa,iBAAkB,SAAQ,kBAAkB;IAGvD,OAAO,EAAE,MAAM,CAAK;CACrB;AAGD,qBACa,gBAAiB,SAAQ,UAAU;IACxC,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;CAGrC;AAED,qBACa,eAAgB,SAAQ,gBAAgB;IAC7C,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;CAGrC;AAGD,qBACa,WAAY,SAAQ,UAAU;IACzC,WAAW,EAAE,MAAM,CAAM;CAC1B"}
@@ -1,18 +0,0 @@
1
- import { SmrtCollection } from '../../collection.js';
2
- import { SmrtObject } from '../../object.js';
3
- export declare class McpIntegrationTestProduct extends SmrtObject {
4
- name: string;
5
- price: number;
6
- stock: number;
7
- analyze(options?: any): Promise<{
8
- action: string;
9
- product: string;
10
- price: number;
11
- inStock: boolean;
12
- timestamp: Date;
13
- }>;
14
- }
15
- export declare class McpIntegrationTestProductCollection extends SmrtCollection<McpIntegrationTestProduct> {
16
- static readonly _itemClass: typeof McpIntegrationTestProduct;
17
- }
18
- //# sourceMappingURL=mcp-integration-test-classes.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp-integration-test-classes.d.ts","sourceRoot":"","sources":["../../../src/__tests__/fixtures/mcp-integration-test-classes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,qBAKa,yBAA0B,SAAQ,UAAU;IAEvD,IAAI,EAAE,MAAM,CAAM;IAGlB,KAAK,EAAE,MAAM,CAAO;IAGpB,KAAK,EAAE,MAAM,CAAK;IAEZ,OAAO,CAAC,OAAO,GAAE,GAAQ;;;;;;;CAShC;AAED,qBAAa,mCAAoC,SAAQ,cAAc,CAAC,yBAAyB,CAAC;IAChG,MAAM,CAAC,QAAQ,CAAC,UAAU,mCAA6B;CACxD"}
@@ -1,25 +0,0 @@
1
- /**
2
- * Fixtures for object.ts AI + memory coverage tests.
3
- *
4
- * Separate file so the @smrt() classes are picked up by test-manifest
5
- * generation. The AI client is injectable so is()/do()/describe() can run
6
- * against a stub without a real provider (mocking AI is the only mocking
7
- * allowed by the testing rules).
8
- *
9
- * @see src/object.ts (issue #1500 coverage, ORM group)
10
- */
11
- import { SmrtObject } from '../../object.js';
12
- /** Minimal stub matching the slice of AIClient that is()/do()/describe() use. */
13
- export interface StubAi {
14
- message: (prompt: string, options?: any) => Promise<string>;
15
- }
16
- export declare class ObjectAiMemoryNode extends SmrtObject {
17
- name: string;
18
- /** Test seam: set this to control what the AI returns. */
19
- stubAi?: StubAi;
20
- /** Capture the last prompt/options passed to the stub for assertions. */
21
- lastPrompt?: string;
22
- lastOptions?: any;
23
- protected getAiClient(): Promise<any>;
24
- }
25
- //# sourceMappingURL=object-ai-memory-fixtures.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"object-ai-memory-fixtures.d.ts","sourceRoot":"","sources":["../../../src/__tests__/fixtures/object-ai-memory-fixtures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7C,iFAAiF;AACjF,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC7D;AAED,qBACa,kBAAmB,SAAQ,UAAU;IAChD,IAAI,EAAE,MAAM,CAAM;IAElB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,GAAG,CAAC;cAIF,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC;CAa5C"}
@@ -1,13 +0,0 @@
1
- import { SmrtObject } from '../../object.js';
2
- export declare class TestObject extends SmrtObject {
3
- static tableName: string;
4
- name: string;
5
- description?: string;
6
- active: boolean;
7
- count: number;
8
- }
9
- export declare class ObjectSpecTestCouncil extends SmrtObject {
10
- name: string;
11
- description?: string;
12
- }
13
- //# sourceMappingURL=object-spec-test-classes.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"object-spec-test-classes.d.ts","sourceRoot":"","sources":["../../../src/__tests__/fixtures/object-spec-test-classes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,qBACa,UAAW,SAAQ,UAAU;IACxC,MAAM,CAAC,SAAS,SAAkB;IAGlC,IAAI,EAAE,MAAM,CAAM;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,CAAS;IACxB,KAAK,EAAE,MAAM,CAAK;CACnB;AAGD,qBACa,qBAAsB,SAAQ,UAAU;IACnD,IAAI,EAAE,MAAM,CAAM;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
@@ -1,23 +0,0 @@
1
- import { SmrtObject } from '../../object.js';
2
- export declare class TestCategory extends SmrtObject {
3
- name: string;
4
- description: string;
5
- }
6
- export declare class TestCustomer extends SmrtObject {
7
- name: string;
8
- email: string;
9
- }
10
- export declare class RegistryTestProduct extends SmrtObject {
11
- name: string;
12
- price: number;
13
- categoryId: string;
14
- }
15
- export declare class TestOrder extends SmrtObject {
16
- customerId: string;
17
- productId: string;
18
- total: number;
19
- }
20
- export declare class OverwriteTestObject extends SmrtObject {
21
- name: string;
22
- }
23
- //# sourceMappingURL=registry-test-classes.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry-test-classes.d.ts","sourceRoot":"","sources":["../../../src/__tests__/fixtures/registry-test-classes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,qBACa,YAAa,SAAQ,UAAU;IAC1C,IAAI,EAAE,MAAM,CAAM;IAClB,WAAW,EAAE,MAAM,CAAM;CAC1B;AAED,qBACa,YAAa,SAAQ,UAAU;IAC1C,IAAI,EAAE,MAAM,CAAM;IAClB,KAAK,EAAE,MAAM,CAAM;CACpB;AAED,qBACa,mBAAoB,SAAQ,UAAU;IACjD,IAAI,EAAE,MAAM,CAAM;IAClB,KAAK,EAAE,MAAM,CAAK;IAClB,UAAU,EAAE,MAAM,CAAM;CACzB;AAED,qBACa,SAAU,SAAQ,UAAU;IACvC,UAAU,EAAE,MAAM,CAAM;IACxB,SAAS,EAAE,MAAM,CAAM;IACvB,KAAK,EAAE,MAAM,CAAK;CACnB;AAGD,qBACa,mBAAoB,SAAQ,UAAU;IAGjD,IAAI,EAAE,MAAM,CAAM;CACnB"}
@@ -1,42 +0,0 @@
1
- export interface ConsumerWorkspace {
2
- root: string;
3
- path: (...segments: string[]) => string;
4
- write: (relativePath: string, content: string) => string;
5
- writePackage: (relativeDir: string, name: string) => void;
6
- /** A fixture module declaring one class via `define({ smrt, SmrtObject })`. */
7
- writeModel: (relativePath: string, className: string, config?: Record<string, unknown>) => string;
8
- dispose: () => void;
9
- }
10
- export declare function createConsumerWorkspace(prefix: string): ConsumerWorkspace;
11
- /** A manifest entry as an app's scanner writes it. */
12
- export declare function manifestEntry(options: {
13
- className: string;
14
- packageName: string;
15
- filePath: string;
16
- tableName: string;
17
- fields: Record<string, {
18
- type: string;
19
- }>;
20
- }): {
21
- name: string;
22
- className: string;
23
- qualifiedName: string;
24
- packageName: string;
25
- filePath: string;
26
- extends: string;
27
- fields: Record<string, {
28
- type: string;
29
- }>;
30
- methods: {};
31
- decoratorConfig: {
32
- tableName: string;
33
- };
34
- schema: {
35
- tableName: string;
36
- ddl: string;
37
- columns: {};
38
- indexes: never[];
39
- version: string;
40
- };
41
- };
42
- //# sourceMappingURL=consumer-workspace.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"consumer-workspace.d.ts","sourceRoot":"","sources":["../../../../src/registry/__tests__/helpers/consumer-workspace.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;IACxC,KAAK,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACzD,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,+EAA+E;IAC/E,UAAU,EAAE,CACV,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAiCzE;AAED,sDAAsD;AACtD,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1C;;;;;;;;cADgC,MAAM;;;;;;;;;;;;;EAqBtC"}