@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,67 @@
1
+ ---
2
+ type: Capability
3
+ title: "Auth, users and sessions"
4
+ description: "Reach for smrt-users before writing password hashing, sessions, login throttling or role checks."
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/users/src/services/credential-hash.ts
19
+ path: packages/users/src/services/credential-hash.ts
20
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
21
+ title: "packages/users/src/services/credential-hash.ts (scrypt hash, timingSafeEqual, scryptHashNeedsUpgrade)"
22
+ - id: s2
23
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/users/src/services/LoginAttemptLimiter.ts
24
+ path: packages/users/src/services/LoginAttemptLimiter.ts
25
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
26
+ title: "packages/users/src/services/LoginAttemptLimiter.ts (LoginAttemptLimiter.reserve, DEFAULT_LOGIN_*)"
27
+ - id: s3
28
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/users/src/services/SessionService.ts
29
+ path: packages/users/src/services/SessionService.ts
30
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
31
+ title: "packages/users/src/services/SessionService.ts (SessionService.createSession)"
32
+ - id: s4
33
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/users/AGENTS.md
34
+ path: packages/users/AGENTS.md
35
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
36
+ title: "packages/users/AGENTS.md (models and authority)"
37
+ ---
38
+
39
+ # Auth, users and sessions
40
+
41
+ # Entry point
42
+
43
+ Import from `@happyvertical/smrt-users` (SvelteKit handlers from `@happyvertical/smrt-users/sveltekit`). Identity is `User` (global) plus `Membership` per `Tenant` with `Role` permissions resolved by `PermissionResolver`; sessions by `SessionService.createSession(userId, tenantId)`; email-and-password sign-in by `PasswordCredentialService`, with passkey, device PIN, OIDC and mobile services alongside. Request-scoped tenant context comes from smrt-tenancy adapters (`createSvelteKitHandle`, `createExpressMiddleware`).[^s3]
44
+
45
+ # Why not hand-roll it
46
+
47
+ `credential-hash.ts` stores a self-describing `scrypt$N$r$p$salt$hash` string, compares with `timingSafeEqual`, and exposes `scryptHashNeedsUpgrade()` so stronger parameters roll out at the next sign-in.[^s1] `LoginAttemptLimiter` draws attempts from two independent budgets, the submitted subject and the source, stores both as hashes, locks out with exponential backoff (defaults: 5 attempts per 5 minutes, 60 s base, doubling, capped at one hour), and its contract says the credential check always runs so a response never reveals whether an identifier maps to an account.[^s2] The package invariants also make users global with tenant access only through `Membership`, and derive tenant hierarchy paths on save rather than trusting callers.[^s4] A hand-rolled login with bcrypt or a JWT library has none of the throttling, non-enumeration or tenant model.
48
+
49
+ # Component pages
50
+
51
+ - [smrt-users](/components/smrt-users.md)
52
+ - [smrt-tenancy](/components/smrt-tenancy.md)
53
+
54
+ # Detection signals
55
+
56
+ - `import:bcrypt`
57
+ - `import:bcryptjs`
58
+ - `import:argon2`
59
+ - `import:jsonwebtoken`
60
+ - `import:passport`
61
+ - `import:express-session`
62
+ - `import:lucia`
63
+
64
+ [^s1]: `packages/users/src/services/credential-hash.ts` at `478ae36900`: scrypt hash, timingSafeEqual, scryptHashNeedsUpgrade.
65
+ [^s2]: `packages/users/src/services/LoginAttemptLimiter.ts` at `478ae36900`: LoginAttemptLimiter.reserve, DEFAULT_LOGIN_*.
66
+ [^s3]: `packages/users/src/services/SessionService.ts` at `478ae36900`: SessionService.createSession.
67
+ [^s4]: `packages/users/AGENTS.md` at `478ae36900`: models and authority.
@@ -0,0 +1,72 @@
1
+ ---
2
+ type: Capability
3
+ title: "Background work and retries"
4
+ description: "Reach for smrt-jobs before writing a queue, worker loop, retry counter or job 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/jobs/src/smrt-job.ts
19
+ path: packages/jobs/src/smrt-job.ts
20
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
21
+ title: "packages/jobs/src/smrt-job.ts (SmrtJobCollection.claimReady)"
22
+ - id: s2
23
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/jobs/src/background-policy.ts
24
+ path: packages/jobs/src/background-policy.ts
25
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
26
+ title: "packages/jobs/src/background-policy.ts (MAX_JOB_RETRIES, DEFAULT_TENANT_JOB_CAP, clampRetries)"
27
+ - id: s3
28
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/jobs/src/stale-recovery.ts
29
+ path: packages/jobs/src/stale-recovery.ts
30
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
31
+ title: "packages/jobs/src/stale-recovery.ts (DEFAULT_LEASE_TTL_MS, getEffectiveLeaseTtlMs)"
32
+ - id: s4
33
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/jobs/src/object-extension.ts
34
+ path: packages/jobs/src/object-extension.ts
35
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
36
+ title: "packages/jobs/src/object-extension.ts (withBackgroundJobs, BackgroundCapable, BgOptions)"
37
+ - id: s5
38
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/jobs/src/index.ts
39
+ path: packages/jobs/src/index.ts
40
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
41
+ title: "packages/jobs/src/index.ts (public exports)"
42
+ ---
43
+
44
+ # Background work and retries
45
+
46
+ # Entry point
47
+
48
+ Import from `@happyvertical/smrt-jobs`. `withBackgroundJobs(Class)` adds `bg(method, args, { delay, retries, priority, queue, timeout })` and a fluent `background(method, args).delay('5m').retries(5).priority('high').enqueue()` to a SmrtObject class; both return a `JobHandle` and persist a `SmrtJob` row. Workers are a `TaskRunner` (`createTaskRunner()`); job progress and events go through `JobContext` and `SmrtJobEvent`. Mark which methods a persisted job may invoke with `backgroundEligible`/`markBackgroundEligible`.[^s4][^s5]
49
+
50
+ # Why not hand-roll it
51
+
52
+ A quick queue over a table usually claims work with a SELECT followed by an UPDATE, which lets two workers take the same row. `SmrtJobCollection.claimReady()` in `smrt-job.ts` claims in one conditional UPDATE so concurrent workers cannot receive the same pending row and never claim more than `limit` rows; on PostgreSQL it selects candidates in a `MATERIALIZED` CTE because the earlier `IN (SELECT ... FOR UPDATE SKIP LOCKED)` form let a single claim take the whole backlog (#3145).[^s1] `background-policy.ts` clamps a requested retry count to `MAX_JOB_RETRIES` (25) so a poison job cannot pin a worker, and `assertWithinTenantCreationCap` bounds a tenant to `DEFAULT_TENANT_JOB_CAP` (10,000) non-terminal jobs so one tenant cannot starve the shared pool.[^s2] A crashed worker's `running` jobs are recovered by worker liveness leases (`DEFAULT_LEASE_TTL_MS` 30 s, never fewer than three renewal ticks), not by per-job heartbeats.[^s3]
53
+
54
+ # Component pages
55
+
56
+ - [smrt-jobs](/components/smrt-jobs.md)
57
+ - [smrt-core](/components/smrt-core.md)
58
+
59
+ # Detection signals
60
+
61
+ - `import:bullmq`
62
+ - `import:bull`
63
+ - `import:bee-queue`
64
+ - `import:pg-boss`
65
+ - `import:kue`
66
+ - `regex:FOR UPDATE SKIP LOCKED`
67
+
68
+ [^s1]: `packages/jobs/src/smrt-job.ts` at `478ae36900`: SmrtJobCollection.claimReady.
69
+ [^s2]: `packages/jobs/src/background-policy.ts` at `478ae36900`: MAX_JOB_RETRIES, DEFAULT_TENANT_JOB_CAP, clampRetries.
70
+ [^s3]: `packages/jobs/src/stale-recovery.ts` at `478ae36900`: DEFAULT_LEASE_TTL_MS, getEffectiveLeaseTtlMs.
71
+ [^s4]: `packages/jobs/src/object-extension.ts` at `478ae36900`: withBackgroundJobs, BackgroundCapable, BgOptions.
72
+ [^s5]: `packages/jobs/src/index.ts` at `478ae36900`: public exports.
@@ -0,0 +1,60 @@
1
+ ---
2
+ type: Capability
3
+ title: "Feature flags and entitlements"
4
+ description: "Reach for smrt-features for tenant-aware flags and smrt-subscriptions for plan entitlements and usage thresholds."
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/features/src/feature-resolver.ts
19
+ path: packages/features/src/feature-resolver.ts
20
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
21
+ title: "packages/features/src/feature-resolver.ts (FeatureResolver.isEnabled, resolveBaseState)"
22
+ - id: s2
23
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/subscriptions/src/services/subscription-resolver.ts
24
+ path: packages/subscriptions/src/services/subscription-resolver.ts
25
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
26
+ title: "packages/subscriptions/src/services/subscription-resolver.ts (SubscriptionResolver)"
27
+ - id: s3
28
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/subscriptions/src/index.ts
29
+ path: packages/subscriptions/src/index.ts
30
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
31
+ title: "packages/subscriptions/src/index.ts (public exports)"
32
+ ---
33
+
34
+ # Feature flags and entitlements
35
+
36
+ # Entry point
37
+
38
+ Flags: import `FeatureResolver` from `@happyvertical/smrt-features` and call `isEnabled(featureKey, { tenantId })` or `isEnabledFor(Class, localId, ctx)`. Defaults are declared in code and synced with `FeatureSyncService`; changes are written through `FeatureOverrideService` as global or tenant `FeatureOverride` rows. Entitlements: `SubscriptionResolver` (resolves a subscriber's plan terms and thresholds into an `EntitlementResolution`) and `TenantUsageMeter` (`record()`/`summarize()`) from `@happyvertical/smrt-subscriptions`.[^s3]
39
+
40
+ # Why not hand-roll it
41
+
42
+ `FeatureResolver.isEnabled()` in `feature-resolver.ts` resolves the registry default, then the global override, then walks the tenant's ancestor chain applying each tenant override, inheriting down the chain only where the parent has `cascadePermissions` and the child `inheritPermissions`, and it throws for a key that is neither registered nor synced instead of returning false.[^s1] It also strips host list bounds from its reads, because a configured default list limit would drop an ancestor override and silently change the resolved value (#3056).[^s1] `SubscriptionResolver` resolves terms from the plan version frozen on the subscription, fails closed when a pinned version cannot be found, and rejects a plan whose tenant does not match the subscription.[^s2] An environment variable or a column on the tenant row has no inheritance, audit or pinned-version semantics.
43
+
44
+ # Component pages
45
+
46
+ - [smrt-features](/components/smrt-features.md)
47
+ - [smrt-subscriptions](/components/smrt-subscriptions.md)
48
+
49
+ # Detection signals
50
+
51
+ - `import:launchdarkly-node-server-sdk`
52
+ - `import:unleash-client`
53
+ - `import:flagsmith-nodejs`
54
+ - `import:@growthbook/growthbook`
55
+ - `regex:FEATURE_[A-Z_]+`
56
+ - `regex:plan\s*===`
57
+
58
+ [^s1]: `packages/features/src/feature-resolver.ts` at `478ae36900`: FeatureResolver.isEnabled, resolveBaseState.
59
+ [^s2]: `packages/subscriptions/src/services/subscription-resolver.ts` at `478ae36900`: SubscriptionResolver.
60
+ [^s3]: `packages/subscriptions/src/index.ts` at `478ae36900`: public exports.
@@ -0,0 +1,71 @@
1
+ ---
2
+ type: Capability
3
+ title: "File and asset upload and storage"
4
+ description: "Reach for smrt-assets AssetStore and the serving contract before writing an upload handler or file-serving route."
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/assets/src/asset-serving.ts
19
+ path: packages/assets/src/asset-serving.ts
20
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
21
+ title: "packages/assets/src/asset-serving.ts (resolveAssetForServing, AssetServeError, remoteMode)"
22
+ - id: s2
23
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/assets/src/owned-asset-helpers.ts
24
+ path: packages/assets/src/owned-asset-helpers.ts
25
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
26
+ title: "packages/assets/src/owned-asset-helpers.ts (assertAssetLinkable)"
27
+ - id: s3
28
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/assets/src/asset-store.ts
29
+ path: packages/assets/src/asset-store.ts
30
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
31
+ title: "packages/assets/src/asset-store.ts (AssetStore.store)"
32
+ - id: s4
33
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/assets-local/src/index.ts
34
+ path: packages/assets-local/src/index.ts
35
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
36
+ title: "packages/assets-local/src/index.ts (createLocalAssetProcessor)"
37
+ - id: s5
38
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/assets-ergot/src/index.ts
39
+ path: packages/assets-ergot/src/index.ts
40
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
41
+ title: "packages/assets-ergot/src/index.ts (createErgotAssetProcessor)"
42
+ ---
43
+
44
+ # File and asset upload and storage
45
+
46
+ # Entry point
47
+
48
+ Import from `@happyvertical/smrt-assets`. `AssetStore` (`store()`, `read()`, `remove()`, `storeVersion()`) writes bytes through the @happyvertical/files provider layer (loaded lazily, so the storage backend is a configuration choice) and creates the matching `Asset` row. `createAssetRuntime()` bundles the store and collection; `serveAsset()` and `resolveAssetForServing()` are the serving contract; `addOwnedAssetFromCollection()` attaches assets to an owner through noun-specific joins. Correction to the expected home: `smrt-assets-local` and `smrt-assets-ergot` are not storage backends. They are `AssetCapabilityProvider` processors: `createLocalAssetProcessor()` (sharp image variants and metadata) and `createErgotAssetProcessor()` (Ergot workflow and external-source sync).[^s3][^s4][^s5]
49
+
50
+ # Why not hand-roll it
51
+
52
+ `resolveAssetForServing()` in `asset-serving.ts` returns 403 when an asset's `tenantId` is set and differs from the caller's, runs the host's `canAccess` hook, and defaults `remoteMode` to `'error'` because `sourceUri` is attacker-settable and proxying it is an SSRF vector (the proxy mode requires a public-IP host, re-validates redirects and caps bytes) (S5 #1396).[^s1] `assertAssetLinkable()` in `owned-asset-helpers.ts` refuses to associate an asset with an owner of another tenant, and refuses a tenant's asset on a shared owner.[^s2] A hand-rolled upload route typically serves `sourceUri` or a path directly and checks neither.
53
+
54
+ # Component pages
55
+
56
+ - [smrt-assets](/components/smrt-assets.md)
57
+ - [smrt-assets-local](/components/smrt-assets-local.md)
58
+ - [smrt-assets-ergot](/components/smrt-assets-ergot.md)
59
+
60
+ # Detection signals
61
+
62
+ - `import:multer`
63
+ - `import:busboy`
64
+ - `import:formidable`
65
+ - `import:@aws-sdk/client-s3`
66
+
67
+ [^s1]: `packages/assets/src/asset-serving.ts` at `478ae36900`: resolveAssetForServing, AssetServeError, remoteMode.
68
+ [^s2]: `packages/assets/src/owned-asset-helpers.ts` at `478ae36900`: assertAssetLinkable.
69
+ [^s3]: `packages/assets/src/asset-store.ts` at `478ae36900`: AssetStore.store.
70
+ [^s4]: `packages/assets-local/src/index.ts` at `478ae36900`: createLocalAssetProcessor.
71
+ [^s5]: `packages/assets-ergot/src/index.ts` at `478ae36900`: createErgotAssetProcessor.
@@ -0,0 +1,56 @@
1
+ ---
2
+ type: Capability
3
+ title: "Human approval steps"
4
+ description: "Reach for smrt-approvals ApprovalService before writing an approved flag, a status column or an approval 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/approvals/src/service.ts
19
+ path: packages/approvals/src/service.ts
20
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
21
+ title: "packages/approvals/src/service.ts (ApprovalService.decide, requestApproval, consume)"
22
+ - id: s2
23
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/approvals/src/kinds.ts
24
+ path: packages/approvals/src/kinds.ts
25
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
26
+ title: "packages/approvals/src/kinds.ts (defineApprovalKind, assertPolicyTightens)"
27
+ - id: s3
28
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/approvals/AGENTS.md
29
+ path: packages/approvals/AGENTS.md
30
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
31
+ title: "packages/approvals/AGENTS.md (invariants)"
32
+ ---
33
+
34
+ # Human approval steps
35
+
36
+ # Entry point
37
+
38
+ Import from `@happyvertical/smrt-approvals`. Register a kind with `defineApprovalKind({ ... })` (default quorum, TTL, required permissions, optional self-approval permission), construct an `ApprovalService`, and call `requestApproval(principal, input)` when an agent or service needs a human gate, `decide(principal, requestId, { decision, reason })` for the human outcome, and `consume()` to spend an approved request exactly where the gated action happens. Tenants may tighten a kind through `ApprovalPolicy`. This is a decision gate and ledger, not a workflow engine.
39
+
40
+ # Why not hand-roll it
41
+
42
+ `ApprovalService.decide()` in `service.ts` refuses any principal whose type is not `human` (`not_human`), refuses the requester unless the kind grants a self-approval permission (`self_approval`), checks every permission the request snapshot requires, refuses a non-pending or expired request, and allows one decision per actor.[^s1] Per the package invariants every transition is one guarded UPDATE on tenant, status, version and expiry plus an append-only `ApprovalEvent` in one transaction, so two concurrent approvers cannot both win, and `assertPolicyTightens()` rejects a policy that lowers quorum or lengthens expiry.[^s2][^s3] An `approved` boolean cannot tell who approved, whether they were the requester, or whether the approval was already used.
43
+
44
+ # Component pages
45
+
46
+ - [smrt-approvals](/components/smrt-approvals.md)
47
+
48
+ # Detection signals
49
+
50
+ - `regex:approved_by`
51
+ - `regex:status\s*=\s*['"]approved['"]`
52
+ - `regex:require_?[aA]pproval`
53
+
54
+ [^s1]: `packages/approvals/src/service.ts` at `478ae36900`: ApprovalService.decide, requestApproval, consume.
55
+ [^s2]: `packages/approvals/src/kinds.ts` at `478ae36900`: defineApprovalKind, assertPolicyTightens.
56
+ [^s3]: `packages/approvals/AGENTS.md` at `478ae36900`: invariants.
@@ -0,0 +1,41 @@
1
+ ---
2
+ type: Index
3
+ title: "Developer-intent index"
4
+ description: "What to reach for in smrt before hand-rolling infrastructure, keyed by what you are about to write."
5
+ status: draft
6
+ generated:
7
+ by: claude/smrt-4687
8
+ at: 2026-11-05T00:00:00Z
9
+ stale_after: 2027-02-03T00:00:00Z
10
+ audience: internal
11
+ evidence_level: source-inspected
12
+ package_version: 0.60.8
13
+ sources:
14
+ - id: s1
15
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/AGENTS.md
16
+ path: AGENTS.md
17
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
18
+ title: "AGENTS.md"
19
+ ---
20
+
21
+ # Developer-intent index
22
+
23
+ Read this before writing queues, schedulers, mailers, uploaders, tenant filters, money fields, flags, audit tables, secrets handling, approvals, webhooks, search, auth or prompts. Each page names the smrt package and entry point, one verified reason not to hand-roll it, and the generated component page. The last column lists import, call and regex signals that suggest a hand-rolled equivalent.
24
+
25
+ | Intent | Page | Home package(s) | Entry point | Hand-rolled signals |
26
+ | --- | --- | --- | --- | --- |
27
+ | Background work and retries | [background-work](/intents/background-work.md) | [smrt-jobs](/components/smrt-jobs.md), [smrt-core](/components/smrt-core.md) | `withBackgroundJobs` (`bg()`/`background()`), `SmrtJob`, `TaskRunner` | `import:bullmq`; `import:bull`; `import:bee-queue`; `import:pg-boss`; `import:kue`; `regex:FOR UPDATE SKIP LOCKED` |
28
+ | Scheduled and recurring runs | [scheduled-runs](/intents/scheduled-runs.md) | [smrt-jobs](/components/smrt-jobs.md), [smrt-agents](/components/smrt-agents.md) | `AgentSchedule` + `createScheduleRunner()`/`ScheduleRunner` | `import:node-cron`; `import:cron`; `import:node-schedule`; `import:agenda`; `import:croner`; `call:setInterval`; `regex:cron\.schedule\(` |
29
+ | Outbound email, SMS, chat messages | [outbound-messages](/intents/outbound-messages.md) | [smrt-messages](/components/smrt-messages.md), [smrt-tenancy](/components/smrt-tenancy.md) | `PersonaMessagingService.send()`, `EmailSender`/`SlackSender`/`TelegramSender`/`ZulipSender`, `UserNotificationService` | `import:nodemailer`; `import:@sendgrid/mail`; `import:twilio`; `import:@slack/web-api`; `import:resend`; `import:postmark`; `import:mailgun.js` |
30
+ | File and asset upload, storage backends | [file-and-asset-storage](/intents/file-and-asset-storage.md) | [smrt-assets](/components/smrt-assets.md), [smrt-assets-local](/components/smrt-assets-local.md), [smrt-assets-ergot](/components/smrt-assets-ergot.md) | `AssetStore`, `createAssetRuntime()`, `serveAsset()`/`resolveAssetForServing()` | `import:multer`; `import:busboy`; `import:formidable`; `import:@aws-sdk/client-s3` |
31
+ | Tenant scoping and cross-tenant reads | [tenant-scoping](/intents/tenant-scoping.md) | [smrt-tenancy](/components/smrt-tenancy.md), [smrt-core](/components/smrt-core.md) | `@TenantScoped` + `@tenantId`, `withTenant()`, `enableTenancy()`, `allowCrossTenant` | `regex:WHERE\s+tenant_id`; `regex:tenant_?[iI]d\s*=\s*\?`; `regex:tenantId\s*[!=]==` |
32
+ | Money, rates, confidence fields | [money-and-rates](/intents/money-and-rates.md) | [smrt-core](/components/smrt-core.md), [smrt-subscriptions](/components/smrt-subscriptions.md) | `amount = 0` (integer minor units), `rate = 0.0` (decimal), `@field({ ui: { format } })` | `regex:\.toFixed\(2\)`; `regex:price\w*\s*=\s*0\.0`; `regex:DECIMAL\(\d+,\s*2\)`; `decimal-field:*_cents`; `decimal-field:*_amount` |
33
+ | Feature flags and entitlements | [feature-flags-and-entitlements](/intents/feature-flags-and-entitlements.md) | [smrt-features](/components/smrt-features.md), [smrt-subscriptions](/components/smrt-subscriptions.md) | `FeatureResolver.isEnabled()`, `SubscriptionResolver`, `TenantUsageMeter` | `import:launchdarkly-node-server-sdk`; `import:unleash-client`; `import:flagsmith-nodejs`; `import:@growthbook/growthbook`; `regex:FEATURE_[A-Z_]+`; `regex:plan\s*===` |
34
+ | Audit trail and change feed | [audit-trail](/intents/audit-trail.md) | [smrt-core](/components/smrt-core.md), [smrt-dev-mcp](/components/smrt-dev-mcp.md) | `@smrt({ audit: true })` + `withAuditContext()`, `getChangesSince()`, `recent-changes` tool | `regex:audit_?log`; `regex:audit_?trail`; `regex:CREATE TABLE\s+\w*audit` |
35
+ | Secrets and config | [secrets-and-config](/intents/secrets-and-config.md) | [smrt-secrets](/components/smrt-secrets.md), [smrt-config](/components/smrt-config.md) | `SecretService.create()` + `store()`, `loadConfig()`, `getModuleConfig()`, `defineConfig()` | `import:dotenv`; `import:node-vault`; `import:cosmiconfig`; `import:convict`; `regex:process\.env\.\w*SECRET`; `regex:createCipheriv\(` |
36
+ | Human approval steps | [human-approvals](/intents/human-approvals.md) | [smrt-approvals](/components/smrt-approvals.md) | `ApprovalService` (`requestApproval()`, `decide()`, `consume()`), `defineApprovalKind()` | `regex:approved_by`; `regex:status\s*=\s*['"]approved['"]`; `regex:require_?[aA]pproval` |
37
+ | Inbound and outbound webhooks | [webhooks-and-events](/intents/webhooks-and-events.md) | [smrt-webhooks](/components/smrt-webhooks.md), [smrt-core](/components/smrt-core.md), [smrt-jobs](/components/smrt-jobs.md), [smrt-events](/components/smrt-events.md) | `WebhookDispatcher`, `WebhookAdminService`, `registerWebhookRuntime()`; core `createDispatchBus()` | `import:svix`; `regex:createHmac\(`; `regex:x-[a-z-]*signature` |
38
+ | Search, facts, extracted knowledge | [search-facts-knowledge](/intents/search-facts-knowledge.md) | [smrt-facts](/components/smrt-facts.md), [smrt-content](/components/smrt-content.md), [smrt-ingestion](/components/smrt-ingestion.md), [smrt-core](/components/smrt-core.md) | `FactCollection.reconcile()`, `collection.semanticSearch()`, smrt-ingestion models and `/server` services | `import:@pinecone-database/pinecone`; `import:pgvector`; `import:minisearch`; `import:flexsearch`; `import:lunr`; `regex:cosine_?[sS]imilarity` |
39
+ | Auth, users, sessions | [auth-and-users](/intents/auth-and-users.md) | [smrt-users](/components/smrt-users.md), [smrt-tenancy](/components/smrt-tenancy.md) | `SessionService`, `PasswordCredentialService`, `PermissionResolver`, `LoginAttemptLimiter`, `/sveltekit` handlers | `import:bcrypt`; `import:bcryptjs`; `import:argon2`; `import:jsonwebtoken`; `import:passport`; `import:express-session`; `import:lucia` |
40
+ | Prompts and AI operations | [prompts-and-ai-operations](/intents/prompts-and-ai-operations.md) | [smrt-prompts](/components/smrt-prompts.md), [smrt-core](/components/smrt-core.md), [smrt-agents](/components/smrt-agents.md) | `definePrompt()` + `resolvePrompt()`, `PromptOverrideService`, `@smrt({ ai: { callable } })`, `SmrtObject.is()/do()/describe()` | `import:openai`; `import:@anthropic-ai/sdk`; `import:langchain`; `regex:You are an? [a-z ]+`; `regex:role:\s*['"]system['"]` |
41
+ | Polymorphic links, junctions, trees | [relationships-and-trees](/intents/relationships-and-trees.md) | [smrt-core](/components/smrt-core.md) | `SmrtJunction`/`SmrtJunctionBase`, `SmrtHierarchical`, `SmrtPolymorphicAssociation` | `regex:parent_?[iI]d\s*[:=]`; `regex:WITH RECURSIVE`; `regex:entity_?type` |
@@ -0,0 +1,65 @@
1
+ ---
2
+ type: Capability
3
+ title: "Money, rates and confidence fields"
4
+ description: "Declare money as integer minor units and rates as decimals through the core numeric-default rule instead of floats or custom decimal columns."
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/ui-metadata.ts
19
+ path: packages/core/src/ui-metadata.ts
20
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
21
+ title: "packages/core/src/ui-metadata.ts (FIELD_UI_FORMAT_TYPES)"
22
+ - id: s2
23
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/core/src/scanner/manifest-generator.ts
24
+ path: packages/core/src/scanner/manifest-generator.ts
25
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
26
+ title: "packages/core/src/scanner/manifest-generator.ts (ui.format validation against field type)"
27
+ - id: s3
28
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/core/src/migrations/minor-units.ts
29
+ path: packages/core/src/migrations/minor-units.ts
30
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
31
+ title: "packages/core/src/migrations/minor-units.ts (preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits)"
32
+ - id: s4
33
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/subscriptions/src/index.ts
34
+ path: packages/subscriptions/src/index.ts
35
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
36
+ title: "packages/subscriptions/src/index.ts (migrateSubscriptionsMoneyToMinorUnits)"
37
+ ---
38
+
39
+ # Money, rates and confidence fields
40
+
41
+ # Entry point
42
+
43
+ The rule is in the field default: `price = 0` is an integer column holding minor units (`1999` for $19.99), `rate = 0.0` is a decimal for rates and confidence. Mark presentation with `@field({ ui: { format: 'money' } })` (integer only) or `'percent'` (decimal or integer); a currency code is a separate text field. To convert a legacy floating-point money column, use `preflightMinorUnitsRescale()` then `rescaleMoneyColumnsToMinorUnits()` from `@happyvertical/smrt-core/migrations` (per-package wrappers such as `migrateSubscriptionsMoneyToMinorUnits` exist).[^s4]
44
+
45
+ # Why not hand-roll it
46
+
47
+ `FIELD_UI_FORMAT_TYPES` in `ui-metadata.ts` lists the only field type each presentation format may sit on (`money: ['integer']`), and the manifest generator fails the build when a `money` format is put on a non-integer field.[^s1][^s2] Converting an existing float column is deliberately not an automatic type change, because `ALTER ... TYPE BIGINT` would truncate `19.99` to `19`; `minor-units.ts` instead provides a read-only preflight that lists rows that would lose information (half-cents, values beyond the JavaScript safe-integer range once scaled) and a rescale guarded by a `BackfillTracker` marker so a re-run can never multiply a table by 100 twice.[^s3] Float money fields and `toFixed(2)` rounding lose exactly these cases silently.
48
+
49
+ # Component pages
50
+
51
+ - [smrt-core](/components/smrt-core.md)
52
+ - [smrt-subscriptions](/components/smrt-subscriptions.md)
53
+
54
+ # Detection signals
55
+
56
+ - `regex:\.toFixed\(2\)`
57
+ - `regex:price\w*\s*=\s*0\.0`
58
+ - `regex:DECIMAL\(\d+,\s*2\)`
59
+ - `decimal-field:*_cents`
60
+ - `decimal-field:*_amount`
61
+
62
+ [^s1]: `packages/core/src/ui-metadata.ts` at `478ae36900`: FIELD_UI_FORMAT_TYPES.
63
+ [^s2]: `packages/core/src/scanner/manifest-generator.ts` at `478ae36900`: ui.format validation against field type.
64
+ [^s3]: `packages/core/src/migrations/minor-units.ts` at `478ae36900`: preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits.
65
+ [^s4]: `packages/subscriptions/src/index.ts` at `478ae36900`: migrateSubscriptionsMoneyToMinorUnits.
@@ -0,0 +1,67 @@
1
+ ---
2
+ type: Capability
3
+ title: "Outbound email, SMS and chat messages"
4
+ description: "Reach for smrt-messages before calling a mailer or chat SDK directly from application code."
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/messages/src/services/PersonaMessagingService.ts
19
+ path: packages/messages/src/services/PersonaMessagingService.ts
20
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
21
+ title: "packages/messages/src/services/PersonaMessagingService.ts (PersonaMessagingService.send)"
22
+ - id: s2
23
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/messages/src/permissions.ts
24
+ path: packages/messages/src/permissions.ts
25
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
26
+ title: "packages/messages/src/permissions.ts (assertMessagingPermission, SEND_MESSAGES_PERMISSION)"
27
+ - id: s3
28
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/messages/src/senders/EmailSender.ts
29
+ path: packages/messages/src/senders/EmailSender.ts
30
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
31
+ title: "packages/messages/src/senders/EmailSender.ts (EmailSender.send)"
32
+ - id: s4
33
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/messages/src/index.ts
34
+ path: packages/messages/src/index.ts
35
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
36
+ title: "packages/messages/src/index.ts (public exports)"
37
+ ---
38
+
39
+ # Outbound email, SMS and chat messages
40
+
41
+ # Entry point
42
+
43
+ Import from `@happyvertical/smrt-messages`. Send through `new PersonaMessagingService({ db, tenantId, principal }).send({ personaId, body, subject, attachments, routeId })`, which resolves a persona-owned route (account plus endpoint), persists a `Message` (or `Email`) row and delivers it. Channel transports are `EmailSender`, `SlackSender`, `TelegramSender`, `ZulipSender` and `TweetSender`; accounts and routes are managed through `MessagingSettingsService`, and in-app notices through `UserNotificationService`. Providers are registered with `registerMessagingProvider()`.[^s4]
44
+
45
+ # Why not hand-roll it
46
+
47
+ `PersonaMessagingService.send()` in `PersonaMessagingService.ts` checks `SEND_MESSAGES_PERMISSION` with `assertMessagingPermission` (a principal from another tenant is rejected), runs inside `withTenant`, refuses an account or endpoint that belongs to another tenant, is inactive, or whose channel does not match the route, rejects attachments on non-email routes, and saves the message with `sendStatus: 'pending'` before it calls the transport, so every send has a ledger row.[^s1][^s2] `EmailSender.send()` resolves every attachment before anything reaches the transport, so an unreadable file fails the send instead of leaving a message silently without it (#3808).[^s3] A direct SDK call skips the permission check, the tenant ownership checks and the persisted record.
48
+
49
+ # Component pages
50
+
51
+ - [smrt-messages](/components/smrt-messages.md)
52
+ - [smrt-tenancy](/components/smrt-tenancy.md)
53
+
54
+ # Detection signals
55
+
56
+ - `import:nodemailer`
57
+ - `import:@sendgrid/mail`
58
+ - `import:twilio`
59
+ - `import:@slack/web-api`
60
+ - `import:resend`
61
+ - `import:postmark`
62
+ - `import:mailgun.js`
63
+
64
+ [^s1]: `packages/messages/src/services/PersonaMessagingService.ts` at `478ae36900`: PersonaMessagingService.send.
65
+ [^s2]: `packages/messages/src/permissions.ts` at `478ae36900`: assertMessagingPermission, SEND_MESSAGES_PERMISSION.
66
+ [^s3]: `packages/messages/src/senders/EmailSender.ts` at `478ae36900`: EmailSender.send.
67
+ [^s4]: `packages/messages/src/index.ts` at `478ae36900`: public exports.
@@ -0,0 +1,72 @@
1
+ ---
2
+ type: Capability
3
+ title: "Prompts and AI operations"
4
+ description: "Register prompts with smrt-prompts and expose AI-callable operations through @smrt({ ai }) instead of inline prompt strings and ad-hoc LLM clients."
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/prompts/src/models/PromptOverride.ts
19
+ path: packages/prompts/src/models/PromptOverride.ts
20
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
21
+ title: "packages/prompts/src/models/PromptOverride.ts (PromptOverride.validatePromptOverride)"
22
+ - id: s2
23
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/prompts/src/prompt-override-service.ts
24
+ path: packages/prompts/src/prompt-override-service.ts
25
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
26
+ title: "packages/prompts/src/prompt-override-service.ts (assertValidPromptScope, InvalidPromptScopeError)"
27
+ - id: s3
28
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/prompts/src/prompt-resolver.ts
29
+ path: packages/prompts/src/prompt-resolver.ts
30
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
31
+ title: "packages/prompts/src/prompt-resolver.ts (resolvePrompt)"
32
+ - id: s4
33
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/agents/src/ai-config.ts
34
+ path: packages/agents/src/ai-config.ts
35
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
36
+ title: "packages/agents/src/ai-config.ts (resolveAgentAIOptions)"
37
+ - id: s5
38
+ resource: https://github.com/happyvertical/smrt/blob/478ae3690052b825b79b28235369ee7a5f6d2f20/packages/core/src/registry/types.ts
39
+ path: packages/core/src/registry/types.ts
40
+ source_revision: 478ae3690052b825b79b28235369ee7a5f6d2f20
41
+ title: "packages/core/src/registry/types.ts (SmrtClassOptions.ai)"
42
+ ---
43
+
44
+ # Prompts and AI operations
45
+
46
+ # Entry point
47
+
48
+ Prompts: `definePrompt({ key, template, ai })` registers a namespaced code default; `await resolvePrompt(key, { override })` returns the effective template, profile, model and params after layering code default, config file, app override, tenant override and runtime override field by field. Writes go through `PromptOverrideService` with a host-supplied authorizer; a management screen uses `PromptSettingsService`. AI operations on models: `@smrt({ ai: { callable: ['analyze'] } })` chooses which methods an AI may call, and `SmrtObject` provides `is()`, `do()` and `describe()`. Agents resolve per-tenant provider keys with `resolveAgentAIOptions()` (smrt-agents).[^s3][^s5]
49
+
50
+ # Why not hand-roll it
51
+
52
+ `PromptOverride` validates on save in `PromptOverride.ts` that the prompt key is registered and that no field the definition marks not editable is overridden, so a tenant cannot override a locked template or model.[^s1] `assertValidPromptScope()` in `prompt-override-service.ts` rejects an unknown scope type, an app id other than the app scope constant and blank or untrimmed tenant ids before the authorizer runs, so a malformed tenant write cannot land on the app-wide row, and every write fails closed without a `true` from the authorizer.[^s2] `resolveAgentAIOptions()` takes the provider key from `SecretService` for the current tenant with ancestor-tenant fallback by default, instead of reading one global environment key.[^s4] An inline prompt string has no override layering, no authorization of edits and no key management.
53
+
54
+ # Component pages
55
+
56
+ - [smrt-prompts](/components/smrt-prompts.md)
57
+ - [smrt-core](/components/smrt-core.md)
58
+ - [smrt-agents](/components/smrt-agents.md)
59
+
60
+ # Detection signals
61
+
62
+ - `import:openai`
63
+ - `import:@anthropic-ai/sdk`
64
+ - `import:langchain`
65
+ - `regex:You are an? [a-z ]+`
66
+ - `regex:role:\s*['"]system['"]`
67
+
68
+ [^s1]: `packages/prompts/src/models/PromptOverride.ts` at `478ae36900`: PromptOverride.validatePromptOverride.
69
+ [^s2]: `packages/prompts/src/prompt-override-service.ts` at `478ae36900`: assertValidPromptScope, InvalidPromptScopeError.
70
+ [^s3]: `packages/prompts/src/prompt-resolver.ts` at `478ae36900`: resolvePrompt.
71
+ [^s4]: `packages/agents/src/ai-config.ts` at `478ae36900`: resolveAgentAIOptions.
72
+ [^s5]: `packages/core/src/registry/types.ts` at `478ae36900`: SmrtClassOptions.ai.