@happyvertical/smrt-cli 0.60.9 → 0.60.11

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 (55) hide show
  1. package/AGENTS.md +24 -53
  2. package/README.md +5 -0
  3. package/agents/app-commands.md +2 -0
  4. package/agents/db-drop-framework-base-tables.md +49 -0
  5. package/agents/db-migrate-concurrency.md +11 -0
  6. package/dist/{AuditLogCollection-BBbvr8Qc-CoDs0BaX.js → AuditLogCollection-BBbvr8Qc-mcBSLXW9.js} +1 -1
  7. package/dist/{OidcIdentity-COeMidoC-B-cVovG3.js → OidcIdentity-COeMidoC-ETkpZbIS.js} +2 -2
  8. package/dist/{OidcIdentityCollection-D4DIZv0K-CexIL7No.js → OidcIdentityCollection-D4DIZv0K-Do3bwRuw.js} +1 -1
  9. package/dist/{PasswordCredentialService-DHJNKEeJ-DQlR62e-.js → PasswordCredentialService-BSSP_kyT-t7bjBu4L.js} +40 -22
  10. package/dist/{ProfileAssetCollection-D5-tG9Mm-Bmn4-Urc.js → ProfileAssetCollection-D5-tG9Mm-FbzWS3Fo.js} +3 -3
  11. package/dist/{ProfileCollection-CmRaiE9U-52qjwbhJ.js → ProfileCollection-CmRaiE9U-BYvpJg0y.js} +17 -17
  12. package/dist/{ProfileLinkCollection-pvPTsysg-ocN3hj_M.js → ProfileLinkCollection-pvPTsysg-nDbgawOW.js} +2 -2
  13. package/dist/{ProfileMetadataCollection-CAgJyjze-FX20yKjY.js → ProfileMetadataCollection-CAgJyjze-D-5QuGnR.js} +1 -1
  14. package/dist/{ProfileMetafieldCollection-19ae6Lpf-qg7eMcJk.js → ProfileMetafieldCollection-19ae6Lpf-CgEt1gPi.js} +1 -1
  15. package/dist/{ProfileRelationshipCollection-BFSlTQ7J-DlDILj3m.js → ProfileRelationshipCollection-BFSlTQ7J-Dv0ll9S1.js} +1 -1
  16. package/dist/{ProfileTypeCollection-BRLK8cHS-QVwjfqe8.js → ProfileTypeCollection-BRLK8cHS-BKuh_ajh.js} +2 -2
  17. package/dist/app/portability-assets.d.ts +2 -0
  18. package/dist/app/portability-assets.d.ts.map +1 -1
  19. package/dist/app.js +2 -2
  20. package/dist/{cli-CtRx1_yP.js → cli-DXIN1FbA.js} +1 -1
  21. package/dist/commands/docs-claude.d.ts +1 -0
  22. package/dist/commands/docs-claude.d.ts.map +1 -1
  23. package/dist/commands/docs-knowledge-bundle.d.ts +87 -0
  24. package/dist/commands/docs-knowledge-bundle.d.ts.map +1 -0
  25. package/dist/commands/docs-skills-install.d.ts +31 -0
  26. package/dist/commands/docs-skills-install.d.ts.map +1 -0
  27. package/dist/{commands-DNssH5Dv.js → commands-DD5gexRY.js} +400 -7
  28. package/dist/{dist-BJABhykg.js → dist-CivxAJ6C.js} +2 -2
  29. package/dist/dist-DcYLLDwF.js +1854 -0
  30. package/dist/{dist-D25iWIXX.js → dist-DjG0qPkx.js} +5 -5
  31. package/dist/index.js +12 -12
  32. package/dist/knowledge/intents/audit-trail.md +63 -0
  33. package/dist/knowledge/intents/auth-and-users.md +67 -0
  34. package/dist/knowledge/intents/background-work.md +72 -0
  35. package/dist/knowledge/intents/feature-flags-and-entitlements.md +60 -0
  36. package/dist/knowledge/intents/file-and-asset-storage.md +71 -0
  37. package/dist/knowledge/intents/human-approvals.md +56 -0
  38. package/dist/knowledge/intents/index.md +41 -0
  39. package/dist/knowledge/intents/money-and-rates.md +65 -0
  40. package/dist/knowledge/intents/outbound-messages.md +67 -0
  41. package/dist/knowledge/intents/prompts-and-ai-operations.md +72 -0
  42. package/dist/knowledge/intents/relationships-and-trees.md +56 -0
  43. package/dist/knowledge/intents/scheduled-runs.md +61 -0
  44. package/dist/knowledge/intents/search-facts-knowledge.md +68 -0
  45. package/dist/knowledge/intents/secrets-and-config.md +66 -0
  46. package/dist/knowledge/intents/tenant-scoping.md +63 -0
  47. package/dist/knowledge/intents/webhooks-and-events.md +71 -0
  48. package/dist/{oidcProvisioningPrimitives-KpIiLmG4-CnRTofeV.js → oidcProvisioningPrimitives-KpIiLmG4-D5-0dAY3.js} +3 -3
  49. package/dist/{operator-fwao3Y1j.js → operator-B0ZraSEE.js} +2 -2
  50. package/dist/{portability-CbMgEnD1.js → portability-sL8U5Hld.js} +21 -13
  51. package/dist/{resolveIdentity-j2WFuKDT-C9XtigVZ.js → resolveIdentity-j2WFuKDT-BieMIOLz.js} +14 -14
  52. package/dist/smrt-knowledge.okf.md +58 -0
  53. package/package.json +16 -15
  54. package/dist/dist-B3gpMpBj.js +0 -1908
  55. package/dist/dist-BQI8s_yA.js +0 -1854
@@ -0,0 +1,56 @@
1
+ ---
2
+ type: Capability
3
+ title: "Polymorphic links, junctions and trees"
4
+ description: "Extend SmrtJunction, SmrtHierarchical and SmrtPolymorphicAssociation instead of writing link tables, parent_id traversal or type/id pairs by hand."
5
+ tags:
6
+ - smrt
7
+ - intent
8
+ status: draft
9
+ generated:
10
+ by: claude/smrt-4687
11
+ at: 2026-11-05T00:00:00Z
12
+ stale_after: 2027-02-03T00:00:00Z
13
+ audience: internal
14
+ evidence_level: source-inspected
15
+ package_version: 0.60.8
16
+ sources:
17
+ - id: s1
18
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/core/src/hierarchical.ts
19
+ path: packages/core/src/hierarchical.ts
20
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
21
+ title: "packages/core/src/hierarchical.ts (SmrtHierarchical.moveTo, getDescendants)"
22
+ - id: s2
23
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/core/src/junction.ts
24
+ path: packages/core/src/junction.ts
25
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
26
+ title: "packages/core/src/junction.ts (SmrtJunction.attach, setLinks)"
27
+ - id: s3
28
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/core/src/polymorphic-association.ts
29
+ path: packages/core/src/polymorphic-association.ts
30
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
31
+ title: "packages/core/src/polymorphic-association.ts (SmrtPolymorphicAssociation)"
32
+ ---
33
+
34
+ # Polymorphic links, junctions and trees
35
+
36
+ # Entry point
37
+
38
+ All three are exported from `@happyvertical/smrt-core`. Trees: `class Place extends SmrtHierarchical` (a `parentId` self-reference) gives `getAncestors()`, `getDescendants()`, `getHierarchy()` and a cycle-checked `moveTo()`. Many-to-many: a collection extending `SmrtJunction` declares `leftField`/`rightField` and gets `attach()`, `detach()`, `byLeft()`, `byRight()` and `setLinks()`; composite-key owners extend `SmrtJunctionBase`. Links to any model: `class CommentAssociation extends SmrtPolymorphicAssociation` stores `metaType` (qualified class name), `metaId` and `role`, and `hydrate()` resolves the target through the registry. Classes whose self-reference means something else (an evolution chain, a derivation DAG) must not extend `SmrtHierarchical`.
39
+
40
+ # Why not hand-roll it
41
+
42
+ `SmrtHierarchical.moveTo()` in `hierarchical.ts` refuses to move a node to itself or beneath one of its own descendants before it saves, which a bare `parentId` update does not, and traversal is breadth-first with one batched query per level.[^s1] `SmrtJunction.attach()` in `junction.ts` spreads caller options first so the fixed left and right keys always win and untrusted options cannot retarget the write, and it upserts on the model's `conflictColumns` (with the documented caveat that a duplicate rewrites the row id); `setLinks()` replaces a left id's full set and is documented as not transactional.[^s2] `SmrtPolymorphicAssociation` keeps `metaId` a bare string with `metaType` resolved through `ObjectRegistry`, rather than an untyped foreign key per target.[^s3]
43
+
44
+ # Component pages
45
+
46
+ - [smrt-core](/components/smrt-core.md)
47
+
48
+ # Detection signals
49
+
50
+ - `regex:parent_?[iI]d\s*[:=]`
51
+ - `regex:WITH RECURSIVE`
52
+ - `regex:entity_?type`
53
+
54
+ [^s1]: `packages/core/src/hierarchical.ts` at `478ae36900`: SmrtHierarchical.moveTo, getDescendants.
55
+ [^s2]: `packages/core/src/junction.ts` at `478ae36900`: SmrtJunction.attach, setLinks.
56
+ [^s3]: `packages/core/src/polymorphic-association.ts` at `478ae36900`: SmrtPolymorphicAssociation.
@@ -0,0 +1,61 @@
1
+ ---
2
+ type: Capability
3
+ title: "Scheduled and recurring runs"
4
+ description: "Reach for the smrt-jobs ScheduleRunner and smrt-agents AgentSchedule before writing a cron loop or setInterval poller."
5
+ tags:
6
+ - smrt
7
+ - intent
8
+ status: draft
9
+ generated:
10
+ by: claude/smrt-4687
11
+ at: 2026-11-05T00:00:00Z
12
+ stale_after: 2027-02-03T00:00:00Z
13
+ audience: internal
14
+ evidence_level: source-inspected
15
+ package_version: 0.60.8
16
+ sources:
17
+ - id: s1
18
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/jobs/src/schedule-runner.ts
19
+ path: packages/jobs/src/schedule-runner.ts
20
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
21
+ title: "packages/jobs/src/schedule-runner.ts (ScheduleRunner.triggerSchedule, validateCronExpression, getNextCronDate)"
22
+ - id: s2
23
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/agents/src/schedule.ts
24
+ path: packages/agents/src/schedule.ts
25
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
26
+ title: "packages/agents/src/schedule.ts (AgentSchedule, getNextCronDate import)"
27
+ - id: s3
28
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/jobs/src/index.ts
29
+ path: packages/jobs/src/index.ts
30
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
31
+ title: "packages/jobs/src/index.ts (ScheduleRunner, SCHEDULED_JOB_QUEUE exports)"
32
+ ---
33
+
34
+ # Scheduled and recurring runs
35
+
36
+ # Entry point
37
+
38
+ Schedules are rows, not code. Define an `AgentSchedule` (`@happyvertical/smrt-agents`: agent type, method, args, 5-field `cron`, IANA `timezone`, optional `tenantId`) and run one `ScheduleRunner` (`createScheduleRunner()` from `@happyvertical/smrt-jobs`). Each due schedule is turned into a `SmrtJob` on the `SCHEDULED_JOB_QUEUE`, so a scheduled run gets the same retries, tenant cap and recovery as any other background job (see [background-work](/intents/background-work.md)). `validateCronExpression()` and `getNextCronDate()` are exported for write-time validation.[^s3]
39
+
40
+ # Why not hand-roll it
41
+
42
+ `ScheduleRunner.triggerSchedule()` in `schedule-runner.ts` validates the cron and time zone first and moves a schedule with an invalid expression to `status = 'error'` instead of retrying it on every poll; it then enqueues the job before advancing `next_run`, so a transient failure (a tenant-cap hit, a database blip) leaves the due slot to be retried rather than silently losing the run (#1401), and it enqueues through `enqueueJob` so the per-tenant cap and retry ceiling apply (#1402).[^s1] Cron is evaluated in the row's IANA zone with UTC as the default, never the host process zone (#3859).[^s1] `AgentSchedule.calculateNextRun()` imports `getNextCronDate` from smrt-jobs so the model and the runner cannot compute different next-run times.[^s2] A hand-written `setInterval` loop has none of this.
43
+
44
+ # Component pages
45
+
46
+ - [smrt-jobs](/components/smrt-jobs.md)
47
+ - [smrt-agents](/components/smrt-agents.md)
48
+
49
+ # Detection signals
50
+
51
+ - `import:node-cron`
52
+ - `import:cron`
53
+ - `import:node-schedule`
54
+ - `import:agenda`
55
+ - `import:croner`
56
+ - `call:setInterval`
57
+ - `regex:cron\.schedule\(`
58
+
59
+ [^s1]: `packages/jobs/src/schedule-runner.ts` at `478ae36900`: ScheduleRunner.triggerSchedule, validateCronExpression, getNextCronDate.
60
+ [^s2]: `packages/agents/src/schedule.ts` at `478ae36900`: AgentSchedule, getNextCronDate import.
61
+ [^s3]: `packages/jobs/src/index.ts` at `478ae36900`: ScheduleRunner, SCHEDULED_JOB_QUEUE exports.
@@ -0,0 +1,68 @@
1
+ ---
2
+ type: Capability
3
+ title: "Search, facts and extracted knowledge"
4
+ description: "Reach for core semanticSearch, smrt-facts reconciliation and smrt-ingestion before writing embedding search or a facts table."
5
+ tags:
6
+ - smrt
7
+ - intent
8
+ status: draft
9
+ generated:
10
+ by: claude/smrt-4687
11
+ at: 2026-11-05T00:00:00Z
12
+ stale_after: 2027-02-03T00:00:00Z
13
+ audience: internal
14
+ evidence_level: source-inspected
15
+ package_version: 0.60.8
16
+ sources:
17
+ - id: s1
18
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/facts/src/facts.ts
19
+ path: packages/facts/src/facts.ts
20
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
21
+ title: "packages/facts/src/facts.ts (FactCollection.reconcile)"
22
+ - id: s2
23
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/core/src/collection.ts
24
+ path: packages/core/src/collection.ts
25
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
26
+ title: "packages/core/src/collection.ts (SmrtCollection.findSimilarToEmbedding, semanticSearch)"
27
+ - id: s3
28
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/core/src/embeddings/storage.ts
29
+ path: packages/core/src/embeddings/storage.ts
30
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
31
+ title: "packages/core/src/embeddings/storage.ts (EmbeddingStorage.searchSimilar)"
32
+ - id: s4
33
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/ingestion/AGENTS.md
34
+ path: packages/ingestion/AGENTS.md
35
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
36
+ title: "packages/ingestion/AGENTS.md (ingestion invariants)"
37
+ ---
38
+
39
+ # Search, facts and extracted knowledge
40
+
41
+ # Entry point
42
+
43
+ Search: any SmrtCollection with embeddings configured exposes `semanticSearch(query, options)` (core). Knowledge: `FactCollection.reconcile(options)` from `@happyvertical/smrt-facts` decides whether new text creates, merges into or branches an existing `Fact`, with provenance in `FactSource`, entity links in `FactSubject` and document links in `FactContent` (to smrt-content `Content`). Extraction pipelines: smrt-ingestion owns sources, extraction attempts, proposals, review and execution; server-only services are under its `/server` subpath and generated mutation routes are disabled.
44
+
45
+ # Why not hand-roll it
46
+
47
+ `FactCollection.reconcile()` in `facts.ts` serializes under a PostgreSQL advisory lock keyed on the reconciliation identity and applies three zones over embedding similarity: at or above 0.85 merge (add a source, bump `sourceCount`), between 0.60 and 0.85 a typed decision or AI disambiguation, below 0.60 create.[^s1] In core, `findSimilarToEmbedding()` runs the tenant interceptor's list prefilter and ranks only that tenant's ids through `EmbeddingStorage.searchSimilar(..., { objectIds })`, where an empty id set returns no results rather than falling through to an unrestricted search, because `_smrt_embeddings` has no tenant column.[^s2][^s3] The ingestion package requires a tenant and confidentiality scope on every record and keeps analysis input, attempts, proposals and decisions append-only.[^s4] A hand-rolled vector search ranks across tenants first and filters afterwards, which both leaks and truncates.
48
+
49
+ # Component pages
50
+
51
+ - [smrt-facts](/components/smrt-facts.md)
52
+ - [smrt-content](/components/smrt-content.md)
53
+ - [smrt-ingestion](/components/smrt-ingestion.md)
54
+ - [smrt-core](/components/smrt-core.md)
55
+
56
+ # Detection signals
57
+
58
+ - `import:@pinecone-database/pinecone`
59
+ - `import:pgvector`
60
+ - `import:minisearch`
61
+ - `import:flexsearch`
62
+ - `import:lunr`
63
+ - `regex:cosine_?[sS]imilarity`
64
+
65
+ [^s1]: `packages/facts/src/facts.ts` at `478ae36900`: FactCollection.reconcile.
66
+ [^s2]: `packages/core/src/collection.ts` at `478ae36900`: SmrtCollection.findSimilarToEmbedding, semanticSearch.
67
+ [^s3]: `packages/core/src/embeddings/storage.ts` at `478ae36900`: EmbeddingStorage.searchSimilar.
68
+ [^s4]: `packages/ingestion/AGENTS.md` at `478ae36900`: ingestion invariants.
@@ -0,0 +1,66 @@
1
+ ---
2
+ type: Capability
3
+ title: "Secrets and configuration"
4
+ description: "Reach for smrt-secrets for per-tenant encrypted secrets and smrt-config for application configuration instead of raw env reads and ad-hoc encryption."
5
+ tags:
6
+ - smrt
7
+ - intent
8
+ status: draft
9
+ generated:
10
+ by: claude/smrt-4687
11
+ at: 2026-11-05T00:00:00Z
12
+ stale_after: 2027-02-03T00:00:00Z
13
+ audience: internal
14
+ evidence_level: source-inspected
15
+ package_version: 0.60.8
16
+ sources:
17
+ - id: s1
18
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/secrets/src/services/SecretService.ts
19
+ path: packages/secrets/src/services/SecretService.ts
20
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
21
+ title: "packages/secrets/src/services/SecretService.ts (SecretService.store)"
22
+ - id: s2
23
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/secrets/src/index.ts
24
+ path: packages/secrets/src/index.ts
25
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
26
+ title: "packages/secrets/src/index.ts (public exports)"
27
+ - id: s3
28
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/config/src/loaded-config.ts
29
+ path: packages/config/src/loaded-config.ts
30
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
31
+ title: "packages/config/src/loaded-config.ts (globalThis.__smrtConfigCache)"
32
+ - id: s4
33
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/config/src/index.ts
34
+ path: packages/config/src/index.ts
35
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
36
+ title: "packages/config/src/index.ts (loadConfig)"
37
+ ---
38
+
39
+ # Secrets and configuration
40
+
41
+ # Entry point
42
+
43
+ Secrets: `SecretService.create({ db, ... })` from `@happyvertical/smrt-secrets`, then `store(name, value, { description, category, expiresAt })` inside a tenant context (`storeForTenant()` outside one). Values are envelope-encrypted by the @happyvertical/secrets store under a per-tenant data key, and key drift is diagnosed with `diagnoseTenantSecretKeyDrift()`. Config: `await loadConfig()` once at startup (finds `smrt.config.*`), then `getConfig()`, `getModuleConfig()` or `getPackageConfig()` from `@happyvertical/smrt-config`; `defineConfig()` types the file.[^s2][^s4]
44
+
45
+ # Why not hand-roll it
46
+
47
+ `SecretService.store()` in `SecretService.ts` looks the secret up for the current tenant only (an unscoped lookup once found another tenant's same-named row and overwrote it with an envelope encrypted under the wrong key, #1501), refuses to save over a foreign row even if a lookup regresses, keys the upsert on slug plus `context = tenantId` so tenants can reuse names, and writes a `SecretAuditLog` row for create, update and failure alike, classifying tenant-key failures.[^s1] For config, `loaded-config.ts` keeps the parsed config on `globalThis.__smrtConfigCache` so every module instance, including pnpm workspace packages loaded from different paths, sees the same object.[^s3] Raw `process.env` reads and home-made AES have no tenant boundary, rotation or audit trail.
48
+
49
+ # Component pages
50
+
51
+ - [smrt-secrets](/components/smrt-secrets.md)
52
+ - [smrt-config](/components/smrt-config.md)
53
+
54
+ # Detection signals
55
+
56
+ - `import:dotenv`
57
+ - `import:node-vault`
58
+ - `import:cosmiconfig`
59
+ - `import:convict`
60
+ - `regex:process\.env\.\w*SECRET`
61
+ - `regex:createCipheriv\(`
62
+
63
+ [^s1]: `packages/secrets/src/services/SecretService.ts` at `478ae36900`: SecretService.store.
64
+ [^s2]: `packages/secrets/src/index.ts` at `478ae36900`: public exports.
65
+ [^s3]: `packages/config/src/loaded-config.ts` at `478ae36900`: globalThis.__smrtConfigCache.
66
+ [^s4]: `packages/config/src/index.ts` at `478ae36900`: loadConfig.
@@ -0,0 +1,63 @@
1
+ ---
2
+ type: Capability
3
+ title: "Tenant scoping and cross-tenant reads"
4
+ description: "Reach for smrt-tenancy and the core allowCrossTenant opt-in instead of filtering by tenant_id by hand."
5
+ tags:
6
+ - smrt
7
+ - intent
8
+ status: draft
9
+ generated:
10
+ by: claude/smrt-4687
11
+ at: 2026-11-05T00:00:00Z
12
+ stale_after: 2027-02-03T00:00:00Z
13
+ audience: internal
14
+ evidence_level: source-inspected
15
+ package_version: 0.60.8
16
+ sources:
17
+ - id: s1
18
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/tenancy/src/interceptor.ts
19
+ path: packages/tenancy/src/interceptor.ts
20
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
21
+ title: "packages/tenancy/src/interceptor.ts (createTenantInterceptor, rawQueryPolicy)"
22
+ - id: s2
23
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/tenancy/src/index.ts
24
+ path: packages/tenancy/src/index.ts
25
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
26
+ title: "packages/tenancy/src/index.ts (withTenant, withSystemContext, queryGlobal, queryWithGlobals)"
27
+ - id: s3
28
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/core/src/object.ts
29
+ path: packages/core/src/object.ts
30
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
31
+ title: "packages/core/src/object.ts (SmrtObject.assertRelatedTenant, allowCrossTenant)"
32
+ - id: s4
33
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/core/src/collection.ts
34
+ path: packages/core/src/collection.ts
35
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
36
+ title: "packages/core/src/collection.ts (SmrtCollection.findSimilarToEmbedding tenant prefilter)"
37
+ ---
38
+
39
+ # Tenant scoping and cross-tenant reads
40
+
41
+ # Entry point
42
+
43
+ Import from `@happyvertical/smrt-tenancy`. Declare a model with `@TenantScoped({ mode })` and a `@tenantId()` field, call `enableTenancy()` once at startup, and run request work inside `withTenant({ tenantId }, fn)` (adapters: `createSvelteKitHandle`, `createExpressMiddleware`, `createCliContext`). Collections then filter by tenant automatically. Read shared rows with `queryWithGlobals()`/`queryGlobal()`, use `withSystemContext()` only for reviewed system work, and opt into a cross-tenant relationship load with `{ allowCrossTenant: true }` on `loadRelated`/`getRelated` (core).[^s2]
44
+
45
+ # Why not hand-roll it
46
+
47
+ A hand-written `WHERE tenant_id = ?` covers only the query you wrote. The interceptor in `interceptor.ts` throws `TenantIsolationError` for any raw SQL against a tenant-scoped class unless the call passes `allowRawOnTenantScoped` (default `rawQueryPolicy: 'throw'`).[^s1] In core, `SmrtObject.assertRelatedTenant()` makes relationship loads, including cache hits from an earlier eager load, throw on a cross-tenant reference, and only the exact value `allowCrossTenant === true` bypasses it, not a truthy value from untyped callers (#1321).[^s3] Semantic search runs the tenant interceptor's list prefilter first and ranks only that tenant's ids, because the embeddings table has no tenant column.[^s4]
48
+
49
+ # Component pages
50
+
51
+ - [smrt-tenancy](/components/smrt-tenancy.md)
52
+ - [smrt-core](/components/smrt-core.md)
53
+
54
+ # Detection signals
55
+
56
+ - `regex:WHERE\s+tenant_id`
57
+ - `regex:tenant_?[iI]d\s*=\s*\?`
58
+ - `regex:tenantId\s*[!=]==`
59
+
60
+ [^s1]: `packages/tenancy/src/interceptor.ts` at `478ae36900`: createTenantInterceptor, rawQueryPolicy.
61
+ [^s2]: `packages/tenancy/src/index.ts` at `478ae36900`: withTenant, withSystemContext, queryGlobal, queryWithGlobals.
62
+ [^s3]: `packages/core/src/object.ts` at `478ae36900`: SmrtObject.assertRelatedTenant, allowCrossTenant.
63
+ [^s4]: `packages/core/src/collection.ts` at `478ae36900`: SmrtCollection.findSimilarToEmbedding tenant prefilter.
@@ -0,0 +1,71 @@
1
+ ---
2
+ type: Capability
3
+ title: "Webhooks and events"
4
+ description: "Reach for smrt-webhooks for outbound signed delivery and the core DispatchBus for in-process events; there is no generic inbound webhook receiver at this revision."
5
+ tags:
6
+ - smrt
7
+ - intent
8
+ status: draft
9
+ generated:
10
+ by: claude/smrt-4687
11
+ at: 2026-11-05T00:00:00Z
12
+ stale_after: 2027-02-03T00:00:00Z
13
+ audience: internal
14
+ evidence_level: source-inspected
15
+ package_version: 0.60.8
16
+ sources:
17
+ - id: s1
18
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/webhooks/src/security.ts
19
+ path: packages/webhooks/src/security.ts
20
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
21
+ title: "packages/webhooks/src/security.ts (assertWebhookUrl, assertHmacSecret, sendPublicWebhook)"
22
+ - id: s2
23
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/webhooks/src/dispatcher.ts
24
+ path: packages/webhooks/src/dispatcher.ts
25
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
26
+ title: "packages/webhooks/src/dispatcher.ts (WebhookDispatcher.deliver)"
27
+ - id: s3
28
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/core/src/dispatch/bus.ts
29
+ path: packages/core/src/dispatch/bus.ts
30
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
31
+ title: "packages/core/src/dispatch/bus.ts (DispatchBus.emit, process)"
32
+ - id: s4
33
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/jobs/src/forge-projection.ts
34
+ path: packages/jobs/src/forge-projection.ts
35
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
36
+ title: "packages/jobs/src/forge-projection.ts (ForgeDelivery inbox)"
37
+ - id: s5
38
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/events/package.json
39
+ path: packages/events/package.json
40
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
41
+ title: "packages/events/package.json (description)"
42
+ ---
43
+
44
+ # Webhooks and events
45
+
46
+ # Entry point
47
+
48
+ Outbound: `@happyvertical/smrt-webhooks` is server-only. Subscribers are `WebhookSubscription` rows managed through `WebhookAdminService`; publish from the change feed with `WebhookChangeFeed` (preferred) or `registerWebhookModelEvents()` (never both for one model), and run deliveries with `runWebhookDeliveryJob` on the smrt-jobs queue. In-process events: `createDispatchBus()` / `DispatchBus` (`emit()`, `on()`, `subscribe()`, `process()`) in `@happyvertical/smrt-core`. Corrections to the expected home: [smrt-events](/components/smrt-events.md) is the calendar and event-hierarchy model, not an event bus; and no package provides a generic inbound webhook receiver at this revision. The closest inbound pattern is the provider-neutral forge delivery inbox in smrt-jobs (`ForgeDelivery`, `ForgeProjectionRuntime`).[^s4][^s5]
49
+
50
+ # Why not hand-roll it
51
+
52
+ `sendPublicWebhook()` in `security.ts` validates every DNS answer as a public address, pins the socket lookup to the validated answer so a second resolution cannot rebind, follows no redirects, uses no proxy or pooled sockets, and bounds DNS (5 s) and request (10 s) time; `assertWebhookUrl()` rejects non-HTTPS, ports other than 443, embedded credentials and localhost, and `assertHmacSecret()` requires a 32 to 4096 byte secret.[^s1] `WebhookDispatcher.deliver()` signs `timestamp.payload` with HMAC-SHA256 over the exact stored bytes and sends a stable `x-smrt-delivery` id because HTTP delivery is at-least-once and receivers must deduplicate.[^s2] `DispatchBus` derives the tenant server-side from the active context and claims a dispatch with a conditional UPDATE on `status = 'pending'`, so a subscriber in one tenant cannot see another tenant's dispatches.[^s3]
53
+
54
+ # Component pages
55
+
56
+ - [smrt-webhooks](/components/smrt-webhooks.md)
57
+ - [smrt-core](/components/smrt-core.md)
58
+ - [smrt-jobs](/components/smrt-jobs.md)
59
+ - [smrt-events](/components/smrt-events.md)
60
+
61
+ # Detection signals
62
+
63
+ - `import:svix`
64
+ - `regex:createHmac\(`
65
+ - `regex:x-[a-z-]*signature`
66
+
67
+ [^s1]: `packages/webhooks/src/security.ts` at `478ae36900`: assertWebhookUrl, assertHmacSecret, sendPublicWebhook.
68
+ [^s2]: `packages/webhooks/src/dispatcher.ts` at `478ae36900`: WebhookDispatcher.deliver.
69
+ [^s3]: `packages/core/src/dispatch/bus.ts` at `478ae36900`: DispatchBus.emit, process.
70
+ [^s4]: `packages/jobs/src/forge-projection.ts` at `478ae36900`: ForgeDelivery inbox.
71
+ [^s5]: `packages/events/package.json` at `478ae36900`: description.
@@ -1,8 +1,8 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
2
  import { n as __exportAll$1 } from "./NostrIdentity-RJaATTCt-BbRHWyee.js";
3
- import { t as OidcIdentity } from "./OidcIdentity-COeMidoC-B-cVovG3.js";
4
- import { l as saveOidcRaceArbiter, n as Profile, r as ProfileCollection } from "./ProfileCollection-CmRaiE9U-52qjwbhJ.js";
5
- import { t as ProfileTypeCollection } from "./ProfileTypeCollection-BRLK8cHS-QVwjfqe8.js";
3
+ import { t as OidcIdentity } from "./OidcIdentity-COeMidoC-ETkpZbIS.js";
4
+ import { l as saveOidcRaceArbiter, n as Profile, r as ProfileCollection } from "./ProfileCollection-CmRaiE9U-BYvpJg0y.js";
5
+ import { t as ProfileTypeCollection } from "./ProfileTypeCollection-BRLK8cHS-BKuh_ajh.js";
6
6
  import { smrt } from "@happyvertical/smrt-core";
7
7
  //#region ../profiles/dist/chunks/oidcProvisioningPrimitives-KpIiLmG4.js
8
8
  var oidcProvisioningPrimitives_KpIiLmG4_exports = /* @__PURE__ */ __exportAll({
@@ -1,5 +1,5 @@
1
- import { m as withSystemContext } from "./dist-BJABhykg.js";
2
- import { Ct as TenantStatus, E as PasswordCredentialService, K as UserCollection, N as RoleCollection, Q as UsersPasswordCredentialCollection, V as TenantCollection, dt as normalizeUsername, g as MembershipCollection, ht as verifySecretHash, j as PermissionResolver, lt as normalizeEmail, st as isValidEmail, wt as UserStatus, yt as MembershipStatus } from "./PasswordCredentialService-DHJNKEeJ-DQlR62e-.js";
1
+ import { m as withSystemContext } from "./dist-CivxAJ6C.js";
2
+ import { Ct as TenantStatus, E as PasswordCredentialService, K as UserCollection, N as RoleCollection, Q as UsersPasswordCredentialCollection, V as TenantCollection, dt as normalizeUsername, g as MembershipCollection, ht as verifySecretHash, j as PermissionResolver, lt as normalizeEmail, st as isValidEmail, wt as UserStatus, yt as MembershipStatus } from "./PasswordCredentialService-BSSP_kyT-t7bjBu4L.js";
3
3
  import { isPostgresDatabase, withEmbeddedWriteTransaction } from "@happyvertical/smrt-core";
4
4
  //#region ../users/dist/operator.js
5
5
  var OperatorProvisioningError = class extends Error {
@@ -1,9 +1,10 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { A as assertExternalArtifactPath, L as errorCode } from "./cli-CtRx1_yP.js";
2
+ import { A as assertExternalArtifactPath, L as errorCode } from "./cli-DXIN1FbA.js";
3
3
  import { closeSync, constants, existsSync, fstatSync, linkSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, writeFileSync } from "node:fs";
4
4
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { createHash, randomBytes } from "node:crypto";
7
+ import { isLocalAssetLocator, localAssetKey, makeLocalAssetLocator } from "@happyvertical/smrt-assets/locator";
7
8
  //#region src/app/portability-assets.ts
8
9
  /**
9
10
  * Filesystem asset portability for logical export/import bundles.
@@ -102,45 +103,47 @@ function readBoundedDescriptor(descriptor, expectedSize, changedCode) {
102
103
  if (readSync(descriptor, Buffer.allocUnsafe(1), 0, 1, null) !== 0) throw fail(changedCode);
103
104
  return bytes;
104
105
  }
105
- function readRegularFileUnchecked(path, approvedRoot, onOpened) {
106
+ function readRegularFileUnchecked(path, approvedRoot, onOpened, requireSingleLink = false) {
106
107
  const lexicalPath = resolve(path);
107
108
  if (!isInside(approvedRoot, lexicalPath)) throw fail("asset-outside-root");
108
109
  const before = lstatSync(lexicalPath);
109
- if (before.isSymbolicLink() || !before.isFile()) throw fail("asset-not-regular");
110
+ if (!before.isFile() || requireSingleLink && before.nlink !== 1) throw fail("asset-not-regular");
110
111
  if (before.size > 67108864) throw fail("asset-too-large");
111
112
  if (!isInside(approvedRoot, realpathSync(lexicalPath))) throw fail("asset-outside-root");
112
113
  const noFollow = constants.O_NOFOLLOW ?? 0;
113
114
  const descriptor = openSync(lexicalPath, constants.O_RDONLY | noFollow);
114
115
  try {
115
116
  const opened = fstatSync(descriptor);
116
- if (!opened.isFile() || opened.size !== before.size) throw fail("asset-changed-during-read");
117
+ if (!opened.isFile() || opened.size !== before.size || requireSingleLink && opened.nlink !== 1) throw fail("asset-changed-during-read");
117
118
  onOpened?.();
118
119
  const openedPath = statSync(lexicalPath);
119
120
  if (!isInside(approvedRoot, realpathSync(lexicalPath)) || openedPath.dev !== opened.dev || openedPath.ino !== opened.ino) throw fail("asset-changed-during-read");
120
121
  const bytes = readBoundedDescriptor(descriptor, opened.size, "asset-changed-during-read");
121
122
  const after = fstatSync(descriptor);
122
123
  const afterPath = statSync(lexicalPath);
123
- if (after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size || bytes.byteLength !== opened.size || !isInside(approvedRoot, realpathSync(lexicalPath)) || afterPath.dev !== opened.dev || afterPath.ino !== opened.ino) throw fail("asset-changed-during-read");
124
+ if (after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size || requireSingleLink && after.nlink !== 1 || bytes.byteLength !== opened.size || !isInside(approvedRoot, realpathSync(lexicalPath)) || afterPath.dev !== opened.dev || afterPath.ino !== opened.ino) throw fail("asset-changed-during-read");
124
125
  return bytes;
125
126
  } finally {
126
127
  closeSync(descriptor);
127
128
  }
128
129
  }
129
- function readRegularFile(path, approvedRoot, onOpened) {
130
+ function readRegularFile(path, approvedRoot, onOpened, requireSingleLink = false) {
130
131
  try {
131
- return readRegularFileUnchecked(path, approvedRoot, onOpened);
132
+ return readRegularFileUnchecked(path, approvedRoot, onOpened, requireSingleLink);
132
133
  } catch (error) {
133
134
  if (isSanitizedFailure(error)) throw error;
134
135
  throw fail("asset-changed-during-read");
135
136
  }
136
137
  }
137
- function pathFromSourceUri(sourceUri) {
138
- if (typeof sourceUri !== "string" || !sourceUri.startsWith("file://")) throw fail("unsupported-asset-source");
138
+ function pathFromSourceUri(sourceUri, root) {
139
+ if (typeof sourceUri !== "string") throw fail("unsupported-asset-source");
139
140
  try {
140
- return fileURLToPath(sourceUri);
141
+ if (isLocalAssetLocator(sourceUri)) return join(root, localAssetKey(sourceUri));
142
+ if (sourceUri.startsWith("file://")) return fileURLToPath(sourceUri);
141
143
  } catch {
142
144
  throw fail("unsupported-asset-source");
143
145
  }
146
+ throw fail("unsupported-asset-source");
144
147
  }
145
148
  function assetRows(tables) {
146
149
  const table = tables.find((candidate) => candidate?.name === "assets");
@@ -175,7 +178,8 @@ function collectFilesystemAssets({ tables, sourceRoot, assetRoot, onSourceOpened
175
178
  const payloadPath = payloadPathForKey(key);
176
179
  if (payloadPaths.has(payloadPath)) throw fail("duplicate-payload-path");
177
180
  payloadPaths.add(payloadPath);
178
- const bytes = readRegularFile(pathFromSourceUri(row.source_uri), root, onSourceOpened);
181
+ const relativeLocator = typeof row.source_uri === "string" && isLocalAssetLocator(row.source_uri);
182
+ const bytes = readRegularFile(pathFromSourceUri(row.source_uri, root), root, onSourceOpened, relativeLocator);
179
183
  totalBytes += bytes.byteLength;
180
184
  if (totalBytes > 268435456) throw fail("asset-total-too-large");
181
185
  const contentDigest = digestBytes(bytes);
@@ -183,7 +187,8 @@ function collectFilesystemAssets({ tables, sourceRoot, assetRoot, onSourceOpened
183
187
  key,
184
188
  byteLength: bytes.byteLength,
185
189
  contentDigest,
186
- payloadPath
190
+ payloadPath,
191
+ ...relativeLocator ? { locator: "relative" } : {}
187
192
  });
188
193
  payloads.push({
189
194
  path: payloadPath,
@@ -231,6 +236,7 @@ function verifyFilesystemAssets({ assetBundle, tables, sourceRoot, assetRoot })
231
236
  }
232
237
  const keys = /* @__PURE__ */ new Set();
233
238
  const paths = /* @__PURE__ */ new Set();
239
+ const relativeLocators = /* @__PURE__ */ new Set();
234
240
  const verified = [];
235
241
  let totalBytes = 0;
236
242
  for (const rawEntry of bundle.entries) {
@@ -239,6 +245,8 @@ function verifyFilesystemAssets({ assetBundle, tables, sourceRoot, assetRoot })
239
245
  const payloadPath = normalizePayloadPath(entry?.payloadPath);
240
246
  if (!key || key.length > 512 || keys.has(key)) throw fail("duplicate-asset-key");
241
247
  if (paths.has(payloadPath)) throw fail("duplicate-payload-path");
248
+ if (entry?.locator === "relative") relativeLocators.add(key);
249
+ else if (entry?.locator !== void 0) throw fail("invalid-asset-locator");
242
250
  keys.add(key);
243
251
  paths.add(payloadPath);
244
252
  const byteLength = entry?.byteLength;
@@ -270,7 +278,7 @@ function verifyFilesystemAssets({ assetBundle, tables, sourceRoot, assetRoot })
270
278
  assetRoot,
271
279
  allowMissing: true
272
280
  });
273
- for (const entry of verified) rowByKey.get(entry.key).source_uri = pathToFileURL(join(root, entry.relativePath)).href;
281
+ for (const entry of verified) rowByKey.get(entry.key).source_uri = relativeLocators.has(entry.key) ? makeLocalAssetLocator(entry.relativePath) : pathToFileURL(join(root, entry.relativePath)).href;
274
282
  return {
275
283
  root,
276
284
  entries: verified
@@ -1,10 +1,10 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { m as withSystemContext } from "./dist-BJABhykg.js";
2
+ import { m as withSystemContext } from "./dist-CivxAJ6C.js";
3
3
  import { a as verifyAuthEvent, n as __exportAll$1, t as NostrIdentity } from "./NostrIdentity-RJaATTCt-BbRHWyee.js";
4
4
  import { t as ApiKey } from "./ApiKey-9YuYcpWy-BdrXOe-N.js";
5
- import { t as OidcIdentity } from "./OidcIdentity-COeMidoC-B-cVovG3.js";
6
- import { c as normalizeIdentityEmail, o as coordinateOidcProvisioning, s as isOidcProvisioningRaceConflict } from "./ProfileCollection-CmRaiE9U-52qjwbhJ.js";
7
- import { n as insertOidcProvisioningIdentity, t as createOidcProvisioningPerson } from "./oidcProvisioningPrimitives-KpIiLmG4-CnRTofeV.js";
5
+ import { t as OidcIdentity } from "./OidcIdentity-COeMidoC-ETkpZbIS.js";
6
+ import { c as normalizeIdentityEmail, o as coordinateOidcProvisioning, s as isOidcProvisioningRaceConflict } from "./ProfileCollection-CmRaiE9U-BYvpJg0y.js";
7
+ import { n as insertOidcProvisioningIdentity, t as createOidcProvisioningPerson } from "./oidcProvisioningPrimitives-KpIiLmG4-D5-0dAY3.js";
8
8
  import { resolveDatabase } from "@happyvertical/smrt-core";
9
9
  //#region ../profiles/dist/chunks/resolveIdentity-j2WFuKDT.js
10
10
  var resolveIdentity_j2WFuKDT_exports = /* @__PURE__ */ __exportAll({
@@ -72,13 +72,13 @@ async function resolveIdentity(context) {
72
72
  };
73
73
  }
74
74
  async function findProfileByExternalId(provider, externalId, options) {
75
- const { OidcIdentityCollection } = await import("./OidcIdentityCollection-D4DIZv0K-CexIL7No.js").then((n) => n.r).then((n) => n.r);
75
+ const { OidcIdentityCollection } = await import("./OidcIdentityCollection-D4DIZv0K-Do3bwRuw.js").then((n) => n.r).then((n) => n.r);
76
76
  const identities = await (await OidcIdentityCollection.create(options)).findByProvider(provider);
77
77
  for (const identity of identities) if (identity.subject === externalId || identity.email?.includes(externalId)) {
78
78
  const profile = await identity.getProfile();
79
79
  if (profile) return profile;
80
80
  }
81
- const { ProfileCollection } = await import("./ProfileCollection-CmRaiE9U-52qjwbhJ.js").then((n) => n.i).then((n) => n.r);
81
+ const { ProfileCollection } = await import("./ProfileCollection-CmRaiE9U-BYvpJg0y.js").then((n) => n.i).then((n) => n.r);
82
82
  const profiles = await (await ProfileCollection.create(options)).list({
83
83
  where: { email: `${externalId}@users.noreply.github.com` },
84
84
  limit: 1
@@ -99,7 +99,7 @@ async function reuseExistingOidcIdentityForProfile(profileId, claims, provider,
99
99
  createConcurrencyError: (cause) => new Error("Concurrent exact OIDC identity reuse did not converge.", cause === void 0 ? void 0 : { cause }),
100
100
  rebindRootResult: rebindOidcProfileResult,
101
101
  provision: (tx) => withSystemContext(async () => {
102
- const { OidcIdentityCollection } = await import("./OidcIdentityCollection-D4DIZv0K-CexIL7No.js").then((n) => n.r).then((n) => n.r);
102
+ const { OidcIdentityCollection } = await import("./OidcIdentityCollection-D4DIZv0K-Do3bwRuw.js").then((n) => n.r).then((n) => n.r);
103
103
  const identity = await (await OidcIdentityCollection.create({ db: tx })).findBySubject(normalizedClaims.iss, normalizedClaims.sub);
104
104
  if (!identity) throw new Error("OidcIdentity.findOrCreate() no longer creates authentication links; use UserCollection.getOrCreateFromOidc() or createProfileFromOidc().");
105
105
  if (identity.profileId !== profileId) throw new Error(`OIDC identity ${normalizedClaims.iss} subject ${normalizedClaims.sub} belongs to a different Profile.`);
@@ -133,7 +133,7 @@ async function coordinateProfileOidcProvisioning(claims, provider, options) {
133
133
  });
134
134
  }
135
135
  async function provisionOidcProfile(claims, provider, options) {
136
- const { OidcIdentityCollection } = await import("./OidcIdentityCollection-D4DIZv0K-CexIL7No.js").then((n) => n.r).then((n) => n.r);
136
+ const { OidcIdentityCollection } = await import("./OidcIdentityCollection-D4DIZv0K-Do3bwRuw.js").then((n) => n.r).then((n) => n.r);
137
137
  const existingIdentity = await (await OidcIdentityCollection.create(options)).findBySubject(claims.iss, claims.sub);
138
138
  if (existingIdentity) {
139
139
  const profile2 = await existingIdentity.getProfile();
@@ -148,7 +148,7 @@ async function provisionOidcProfile(claims, provider, options) {
148
148
  created: false
149
149
  };
150
150
  }
151
- const { ProfileCollection } = await import("./ProfileCollection-CmRaiE9U-52qjwbhJ.js").then((n) => n.i).then((n) => n.r);
151
+ const { ProfileCollection } = await import("./ProfileCollection-CmRaiE9U-BYvpJg0y.js").then((n) => n.i).then((n) => n.r);
152
152
  const profileCollection = await ProfileCollection.create(options);
153
153
  if (claims.email) {
154
154
  if (await profileCollection.findUniqueGlobalPersonByEmail(claims.email)) {
@@ -175,8 +175,8 @@ async function provisionOidcProfile(claims, provider, options) {
175
175
  };
176
176
  }
177
177
  async function rebindOidcProfileResult(result, rootDb) {
178
- const { OidcIdentityCollection } = await import("./OidcIdentityCollection-D4DIZv0K-CexIL7No.js").then((n) => n.r).then((n) => n.r);
179
- const { ProfileCollection } = await import("./ProfileCollection-CmRaiE9U-52qjwbhJ.js").then((n) => n.i).then((n) => n.r);
178
+ const { OidcIdentityCollection } = await import("./OidcIdentityCollection-D4DIZv0K-Do3bwRuw.js").then((n) => n.r).then((n) => n.r);
179
+ const { ProfileCollection } = await import("./ProfileCollection-CmRaiE9U-BYvpJg0y.js").then((n) => n.i).then((n) => n.r);
180
180
  const profileId = requireOidcProfileId(result.profile.id);
181
181
  const identityId = requireOidcProfileId(result.oidcIdentity.id);
182
182
  const [profile, oidcIdentity] = await withSystemContext(async () => {
@@ -225,11 +225,11 @@ async function createProfileFromNostr(email, nostrData, options) {
225
225
  created: false
226
226
  };
227
227
  }
228
- const { Person } = await import("./oidcProvisioningPrimitives-KpIiLmG4-CnRTofeV.js").then((n) => n.r).then((n) => n.o);
229
- const { ProfileTypeCollection } = await import("./ProfileTypeCollection-BRLK8cHS-QVwjfqe8.js").then((n) => n.n).then((n) => n.n);
228
+ const { Person } = await import("./oidcProvisioningPrimitives-KpIiLmG4-D5-0dAY3.js").then((n) => n.r).then((n) => n.o);
229
+ const { ProfileTypeCollection } = await import("./ProfileTypeCollection-BRLK8cHS-BKuh_ajh.js").then((n) => n.n).then((n) => n.n);
230
230
  let personType = await (await ProfileTypeCollection.create(options)).getBySlug("person");
231
231
  if (!personType) {
232
- const { ProfileType } = await import("./ProfileCollection-CmRaiE9U-52qjwbhJ.js").then((n) => n.i).then((n) => n.h);
232
+ const { ProfileType } = await import("./ProfileCollection-CmRaiE9U-BYvpJg0y.js").then((n) => n.i).then((n) => n.h);
233
233
  personType = new ProfileType({
234
234
  ...options,
235
235
  slug: "person",