@happyvertical/smrt-playbooks 0.45.3 → 0.47.0
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.
- package/AGENTS.md +7 -2
- package/dist/index.js +1 -1
- package/dist/manifest.json +36 -10
- package/dist/smrt-knowledge.json +52 -8
- package/package.json +8 -8
package/AGENTS.md
CHANGED
|
@@ -253,8 +253,13 @@ stale one:
|
|
|
253
253
|
are tracked per `(db, key)`, not per tenant, because an app-level row is
|
|
254
254
|
inherited by every tenant. `clearPlaybookCache()` bumps rather than resets
|
|
255
255
|
them, so a resolution that started before the clear cannot write back either.
|
|
256
|
-
`smrt-prompts` and `smrt-languages`
|
|
257
|
-
|
|
256
|
+
`smrt-prompts` and `smrt-languages` carry the same mechanism (#2609);
|
|
257
|
+
`smrt-languages` additionally keys its generation by locale, and both raise a
|
|
258
|
+
single `clearedThrough` floor in `clear*Cache()` instead of the per-key bump
|
|
259
|
+
here. The per-key bump cannot reach a key that has never been invalidated —
|
|
260
|
+
it has no map entry, so it reads as generation 0 before and after the clear
|
|
261
|
+
and an in-flight resolution writes its pre-clear value back. `clearPlaybookCache()`
|
|
262
|
+
still carries that gap; #2716 ports the floor.
|
|
258
263
|
- **Restart vitest after adding a decorated class**; the manifest is generated
|
|
259
264
|
at startup.
|
|
260
265
|
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
15
15
|
};
|
|
16
16
|
//#endregion
|
|
17
17
|
//#region src/__smrt-register__.ts
|
|
18
|
-
ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-playbooks\",\"packageVersion\":\"0.
|
|
18
|
+
ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-playbooks\",\"packageVersion\":\"0.47.0\",\"objects\":{\"@happyvertical/smrt-playbooks:PlaybookOverrideCollection\":{\"name\":\"playbookoverridecollection\",\"className\":\"PlaybookOverrideCollection\",\"qualifiedName\":\"@happyvertical/smrt-playbooks:PlaybookOverrideCollection\",\"collection\":\"playbookoverrides\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/playbooks/src/collections/PlaybookOverrideCollection.ts\",\"packageName\":\"@happyvertical/smrt-playbooks\",\"fields\":{},\"methods\":{\"getAppOverride\":{\"name\":\"getAppOverride\",\"async\":true,\"parameters\":[{\"name\":\"key\",\"type\":\"string\",\"optional\":false},{\"name\":\"options\",\"type\":\"object\",\"optional\":true,\"memberTypes\":[\"string\"]}],\"returnType\":\"Promise<PlaybookOverride | null>\",\"isStatic\":false,\"isPublic\":true},\"getTenantOverride\":{\"name\":\"getTenantOverride\",\"async\":true,\"parameters\":[{\"name\":\"key\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"options\",\"type\":\"object\",\"optional\":true,\"memberTypes\":[\"string\"]}],\"returnType\":\"Promise<PlaybookOverride | null>\",\"isStatic\":false,\"isPublic\":true},\"getResolutionLayers\":{\"name\":\"getResolutionLayers\",\"async\":true,\"parameters\":[{\"name\":\"key\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":true,\"unionBranches\":[{\"type\":\"string\"},{\"type\":\"null\"}]},{\"name\":\"options\",\"type\":\"object\",\"optional\":true,\"memberTypes\":[\"string\"]}],\"returnType\":\"Promise<object>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"_smrt_playbook_overrides\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"PlaybookOverride\",\"exportName\":\"PlaybookOverrideCollection\",\"collectionExportName\":\"PlaybookOverrideCollectionCollection\",\"schema\":{\"tableName\":\"_smrt_playbook_overrides\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_playbook_overrides\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"_smrt_playbook_overrides_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true},{\"name\":\"_smrt_playbook_overrides_created_at_idx\",\"columns\":[\"created_at\"]}],\"version\":\"410e08c4\"}},\"@happyvertical/smrt-playbooks:PlaybookOverride\":{\"name\":\"playbookoverride\",\"className\":\"PlaybookOverride\",\"qualifiedName\":\"@happyvertical/smrt-playbooks:PlaybookOverride\",\"collection\":\"playbookoverrides\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/playbooks/src/models/PlaybookOverride.ts\",\"packageName\":\"@happyvertical/smrt-playbooks\",\"fields\":{\"key\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"title\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"description\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"planes\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"onStepFailure\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"enabled\":{\"type\":\"boolean\",\"required\":false,\"_meta\":{\"nullable\":true}},\"metadata\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}}},\"methods\":{\"getMetadata\":{\"name\":\"getMetadata\",\"async\":false,\"parameters\":[],\"returnType\":\"PlaybookMetadata\",\"isStatic\":false,\"isPublic\":true},\"setMetadata\":{\"name\":\"setMetadata\",\"async\":false,\"parameters\":[{\"name\":\"metadata\",\"type\":\"PlaybookMetadata | null\",\"optional\":false,\"unionBranches\":[{\"type\":\"PlaybookMetadata\"},{\"type\":\"null\"}]}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"getPlanes\":{\"name\":\"getPlanes\",\"async\":false,\"parameters\":[],\"returnType\":\"any\",\"isStatic\":false,\"isPublic\":true},\"setPlanes\":{\"name\":\"setPlanes\",\"async\":false,\"parameters\":[{\"name\":\"planes\",\"type\":\"any\",\"optional\":false,\"typeUnresolved\":true}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"toPlaybookLayer\":{\"name\":\"toPlaybookLayer\",\"async\":false,\"parameters\":[],\"returnType\":\"PlaybookLayer\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<this>\",\"isStatic\":false,\"isPublic\":true},\"delete\":{\"name\":\"delete\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"_smrt_playbook_overrides\",\"conflictColumns\":[\"key\",\"context\"],\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"]},\"cli\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"],\"exclude\":[\"getMetadata\",\"setMetadata\",\"getPlanes\",\"setPlanes\",\"toPlaybookLayer\"]},\"mcp\":{\"include\":[]}},\"extends\":\"SmrtObject\",\"exportName\":\"PlaybookOverride\",\"collectionExportName\":\"PlaybookOverrideCollection\",\"validationRules\":[{\"field\":\"key\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"_smrt_playbook_overrides\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_playbook_overrides\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"key\\\" TEXT NOT NULL DEFAULT '',\\n \\\"tenant_id\\\" TEXT,\\n \\\"title\\\" TEXT,\\n \\\"description\\\" TEXT,\\n \\\"planes\\\" TEXT,\\n \\\"on_step_failure\\\" TEXT,\\n \\\"enabled\\\" BOOLEAN,\\n \\\"metadata\\\" TEXT\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"key\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"tenant_id\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"title\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"description\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"planes\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"on_step_failure\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"enabled\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false},\"metadata\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"_smrt_playbook_overrides_key_context_idx\",\"columns\":[\"key\",\"context\"],\"unique\":true},{\"name\":\"_smrt_playbook_overrides_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"_smrt_playbook_overrides_created_at_idx\",\"columns\":[\"created_at\"]}],\"version\":\"8aec8e13\"}}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\",\"@happyvertical/smrt-tenancy\"]}"));
|
|
19
19
|
//#endregion
|
|
20
20
|
//#region src/cache.ts
|
|
21
21
|
var PLAYBOOK_CACHE_TTL_MS = 3e4;
|
package/dist/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"version": "1.0.0",
|
|
3
3
|
"timestamp": 0,
|
|
4
4
|
"packageName": "@happyvertical/smrt-playbooks",
|
|
5
|
-
"packageVersion": "0.
|
|
5
|
+
"packageVersion": "0.47.0",
|
|
6
6
|
"objects": {
|
|
7
7
|
"@happyvertical/smrt-playbooks:PlaybookOverrideCollection": {
|
|
8
8
|
"name": "playbookoverridecollection",
|
|
@@ -25,7 +25,10 @@
|
|
|
25
25
|
{
|
|
26
26
|
"name": "options",
|
|
27
27
|
"type": "object",
|
|
28
|
-
"optional": true
|
|
28
|
+
"optional": true,
|
|
29
|
+
"memberTypes": [
|
|
30
|
+
"string"
|
|
31
|
+
]
|
|
29
32
|
}
|
|
30
33
|
],
|
|
31
34
|
"returnType": "Promise<PlaybookOverride | null>",
|
|
@@ -49,7 +52,10 @@
|
|
|
49
52
|
{
|
|
50
53
|
"name": "options",
|
|
51
54
|
"type": "object",
|
|
52
|
-
"optional": true
|
|
55
|
+
"optional": true,
|
|
56
|
+
"memberTypes": [
|
|
57
|
+
"string"
|
|
58
|
+
]
|
|
53
59
|
}
|
|
54
60
|
],
|
|
55
61
|
"returnType": "Promise<PlaybookOverride | null>",
|
|
@@ -68,12 +74,23 @@
|
|
|
68
74
|
{
|
|
69
75
|
"name": "tenantId",
|
|
70
76
|
"type": "string | null",
|
|
71
|
-
"optional": true
|
|
77
|
+
"optional": true,
|
|
78
|
+
"unionBranches": [
|
|
79
|
+
{
|
|
80
|
+
"type": "string"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"type": "null"
|
|
84
|
+
}
|
|
85
|
+
]
|
|
72
86
|
},
|
|
73
87
|
{
|
|
74
88
|
"name": "options",
|
|
75
89
|
"type": "object",
|
|
76
|
-
"optional": true
|
|
90
|
+
"optional": true,
|
|
91
|
+
"memberTypes": [
|
|
92
|
+
"string"
|
|
93
|
+
]
|
|
77
94
|
}
|
|
78
95
|
],
|
|
79
96
|
"returnType": "Promise<object>",
|
|
@@ -219,7 +236,15 @@
|
|
|
219
236
|
{
|
|
220
237
|
"name": "metadata",
|
|
221
238
|
"type": "PlaybookMetadata | null",
|
|
222
|
-
"optional": false
|
|
239
|
+
"optional": false,
|
|
240
|
+
"unionBranches": [
|
|
241
|
+
{
|
|
242
|
+
"type": "PlaybookMetadata"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"type": "null"
|
|
246
|
+
}
|
|
247
|
+
]
|
|
223
248
|
}
|
|
224
249
|
],
|
|
225
250
|
"returnType": "void",
|
|
@@ -230,7 +255,7 @@
|
|
|
230
255
|
"name": "getPlanes",
|
|
231
256
|
"async": false,
|
|
232
257
|
"parameters": [],
|
|
233
|
-
"returnType": "
|
|
258
|
+
"returnType": "any",
|
|
234
259
|
"isStatic": false,
|
|
235
260
|
"isPublic": true
|
|
236
261
|
},
|
|
@@ -240,8 +265,9 @@
|
|
|
240
265
|
"parameters": [
|
|
241
266
|
{
|
|
242
267
|
"name": "planes",
|
|
243
|
-
"type": "
|
|
244
|
-
"optional": false
|
|
268
|
+
"type": "any",
|
|
269
|
+
"optional": false,
|
|
270
|
+
"typeUnresolved": true
|
|
245
271
|
}
|
|
246
272
|
],
|
|
247
273
|
"returnType": "void",
|
|
@@ -260,7 +286,7 @@
|
|
|
260
286
|
"name": "save",
|
|
261
287
|
"async": true,
|
|
262
288
|
"parameters": [],
|
|
263
|
-
"returnType": "Promise",
|
|
289
|
+
"returnType": "Promise<this>",
|
|
264
290
|
"isStatic": false,
|
|
265
291
|
"isPublic": true
|
|
266
292
|
},
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-playbooks",
|
|
6
|
-
"packageVersion": "0.
|
|
6
|
+
"packageVersion": "0.47.0",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
12
|
-
"agents": "
|
|
10
|
+
"manifest": "c5048d054771616552ff0359b29fc5376cdb209ca776cf0e4dc37fd8e8262c98",
|
|
11
|
+
"packageJson": "7f54adaab1979e6c019d79261528e0cd4d30b1751ed3f038dec298a79606ab9b",
|
|
12
|
+
"agents": "24cc1ff7167bdb3f2c2b9350d6ba24fb0286013023c5cc49876f16811387e312"
|
|
13
13
|
},
|
|
14
14
|
"exports": [
|
|
15
15
|
".",
|
|
@@ -234,12 +234,12 @@
|
|
|
234
234
|
},
|
|
235
235
|
{
|
|
236
236
|
"name": "getPlanes",
|
|
237
|
-
"returns": "
|
|
237
|
+
"returns": "any"
|
|
238
238
|
},
|
|
239
239
|
{
|
|
240
240
|
"name": "save",
|
|
241
241
|
"async": true,
|
|
242
|
-
"returns": "Promise"
|
|
242
|
+
"returns": "Promise<this>"
|
|
243
243
|
},
|
|
244
244
|
{
|
|
245
245
|
"name": "setMetadata",
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
{
|
|
252
252
|
"name": "setPlanes",
|
|
253
253
|
"params": [
|
|
254
|
-
"planes:
|
|
254
|
+
"planes: any"
|
|
255
255
|
],
|
|
256
256
|
"returns": "void"
|
|
257
257
|
},
|
|
@@ -336,6 +336,50 @@
|
|
|
336
336
|
"objectName": "@happyvertical/smrt-playbooks:PlaybookOverride"
|
|
337
337
|
}
|
|
338
338
|
],
|
|
339
|
+
"withheldSurfaces": [
|
|
340
|
+
{
|
|
341
|
+
"kind": "api",
|
|
342
|
+
"operation": "getMetadata",
|
|
343
|
+
"code": "not-included",
|
|
344
|
+
"reason": "not listed in api.include",
|
|
345
|
+
"objectName": "@happyvertical/smrt-playbooks:PlaybookOverride"
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
"kind": "api",
|
|
349
|
+
"operation": "getPlanes",
|
|
350
|
+
"code": "not-included",
|
|
351
|
+
"reason": "not listed in api.include",
|
|
352
|
+
"objectName": "@happyvertical/smrt-playbooks:PlaybookOverride"
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
"kind": "api",
|
|
356
|
+
"operation": "save",
|
|
357
|
+
"code": "lifecycle-method",
|
|
358
|
+
"reason": "`save` is a framework lifecycle method, not a distinct operation",
|
|
359
|
+
"objectName": "@happyvertical/smrt-playbooks:PlaybookOverride"
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
"kind": "api",
|
|
363
|
+
"operation": "setMetadata",
|
|
364
|
+
"code": "not-included",
|
|
365
|
+
"reason": "not listed in api.include",
|
|
366
|
+
"objectName": "@happyvertical/smrt-playbooks:PlaybookOverride"
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
"kind": "api",
|
|
370
|
+
"operation": "setPlanes",
|
|
371
|
+
"code": "not-included",
|
|
372
|
+
"reason": "not listed in api.include",
|
|
373
|
+
"objectName": "@happyvertical/smrt-playbooks:PlaybookOverride"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"kind": "api",
|
|
377
|
+
"operation": "toPlaybookLayer",
|
|
378
|
+
"code": "not-included",
|
|
379
|
+
"reason": "not listed in api.include",
|
|
380
|
+
"objectName": "@happyvertical/smrt-playbooks:PlaybookOverride"
|
|
381
|
+
}
|
|
382
|
+
],
|
|
339
383
|
"relationshipFeatures": [
|
|
340
384
|
"uuidColumns"
|
|
341
385
|
],
|
|
@@ -484,5 +528,5 @@
|
|
|
484
528
|
"polymorphicAssociations": 0,
|
|
485
529
|
"uuidColumns": 2
|
|
486
530
|
},
|
|
487
|
-
"agentDoc": "# smrt-playbooks\n\nLayered playbook registry, app/tenant overrides, and plan resolution. A\nplaybook is a named, described, layered sequence of steps an agent follows;\nbrowser agents, in-app agents, the Node MCP server, and the CLI all follow the\nsame resolved plan. Fourth instance of the `smrt-prompts` layered-override\npattern — keep it consistent with `prompts`, `languages`, and `features`.\n\n## Core pieces\n\n- `definePlaybook()` registers code defaults in a global process registry\n (`globalThis.__smrtPlaybookRegistry`), so a package-bundled playbook resolves\n with no application registration\n- `resolvePlaybook()` merges the layers and returns a `PlaybookResolution`\n- `PlaybookOverride` (`_smrt_playbook_overrides`) stores partial app-level and\n tenant-level overrides with write-time validation\n- `PlaybookOverrideCollection` exposes the standard SmrtCollection CRUD surface\n\n## Resolution layers (priority low → high)\n\n1. Code default — `definePlaybook({ key, title, description, steps })`\n2. File/config override — `getPackageConfig<PlaybookPackageConfig>('playbooks', defaults)`\n3. App-level stored override — `PlaybookOverride` row with `tenantId = null`\n4. Tenant-level stored override — `PlaybookOverride` row with the current tenant\n5. Runtime override — passed to `resolvePlaybook(key, { override })`\n\nInheritance is field-by-field: a stored column is nullable, and `null` means\n\"use the lower layer\".\n\n## Script semantics\n\nA playbook resolves to a plan the agent executes step by step. **It never\nexecutes as a unit** (epic #2585 invariant 4), so it is never an authority\nboundary and adds no new security object to review. Each step is authorized\nindependently — at the REST boundary in the browser, or by\n`PrincipalRun.assertToolAllowed()` server-side. Nothing in this package\nexecutes a step, and there is deliberately no executor export.\n\nPlaybooks are consequently never atomic and have no compensation.\n`onStepFailure` (`'abort'` | `'continue'`) is the whole of the contract for\nwhat an agent does when step 3 of 5 fails.\n\n## Steps\n\nExactly two kinds in v1, and playbooks cannot nest:\n\n- `{ kind: 'operation', model: '@happyvertical/smrt-commerce:Order', action: 'submit' }`\n — the qualified pair already used by STI discriminators and\n `@crossPackageRef`. **Never a generated tool name**: that is derived from\n model, action, and namespace, and would silently orphan stored tenant\n overrides on a namespace change.\n- `{ kind: 'intent', id }` — a view intent named by its declared #2588\n identity. Valid only where a surface is mounted.\n\nA step referencing another playbook is rejected in `normalizeSteps()` at\ndefinition time.\n\n### Classification is inherited, never self-declared\n\nA step never classifies itself. `resolvePlaybook()` takes a\n`classifier` (and, for intents, an `intents` registry) supplied by the host,\nwhich returns the `CapabilityDeclaration` emitted for the referenced operation\nby `@happyvertical/smrt-types`. Anything undeclared resolves fail-closed to\n`{ effect: 'destructive', idempotent: false, openWorld: true }`. The\nvocabulary itself is older and lower and shared with model tools — it lives in\n`smrt-types`, not here (owning it here would close a\ncore → playbooks → core cycle).\n\n## Plane validity\n\nA playbook declares `planes: readonly ('browser' | 'server')[]`. Resolution for\na caller on an undeclared plane fails closed with reason\n`'plane-not-declared'`.\n\n- Operation-only playbooks default to both planes.\n- A playbook containing a view-intent step defaults to `['browser']`. Server\n validity rides the shipped #2446 browser command/ack bridge, which lets a\n server-side agent drive mounted surfaces with acknowledgement — it must be\n **declared explicitly**, never assumed.\n- The same default applies one level down: a `PlaybookIntentRecord` that\n declares no `planes` is browser-only, so server validity must be declared at\n **both** the playbook and the intent. Silence from the intent registry never\n widens a plane (`'intent-plane-not-declared'`).\n\nThe `#2588` intent registry does not exist yet, so an intent step with no\n`intents` resolver supplied fails closed with\n`'intent-registry-unavailable'`. That resolver is the seam #2588 wires into.\n\n## Editability\n\n`editable` defaults **all-false**, matching `normalizeEditableConfig` in\n`smrt-prompts`. Every stored column has a flag — `title`, `description`,\n`planes`, `onStepFailure`, `enabled`, `metadata` — and `save()` rejects a\nnon-null value for any field the definition has not opted in. `onStepFailure`\nis gated like the rest: flipping a locked playbook from `'abort'` to\n`'continue'` would change what an agent does after a failed prerequisite.\n\n`steps` is **structurally** non-editable, not merely defaulted false:\n\n- `PlaybookEditableConfig` has no `steps` key, and marking one throws at\n definition time\n- `_smrt_playbook_overrides` has **no `steps` column**, so no write of any kind\n has anywhere to put a step list\n- `PlaybookOverride.save()` rejects a `steps` property assigned through the\n untyped option bag rather than dropping it silently\n- `normalizePlaybookLayer()` throws on a `steps` key from the config or runtime\n layer\n- the resolver reads steps only from `PlaybookRegistry`\n\nThe reason is not escalation — under the script model a tenant cannot escalate,\nsince every step is authorized independently regardless of who wrote the list.\nIt is that an agent announcing \"checking out your cart\" while an overridden\nstep list does something else is a description-behavior mismatch.\n\nEnablement overrides are one-directional: a layer may disable, never re-enable\nwhat a lower layer disabled. Enforced in `mergePlaybookLayers()` (`enabled &&\nlayer.enabled`) and rejected at `save()` with a specific message. Plane lists\nnarrow the same way.\n\n## Preflight (#2590)\n\n`preflightPlaybook({ key, plane, principal, resolve, evaluate })` resolves a\nplaybook through the **caller's own layer chain**, decomposes it, and returns a\nper-step verdict — `allow` | `deny` | `unknown` — plus an aggregate. It executes\nnothing.\n\n**Advisory only. Preflight predicts; it never grants.** Every step re-enforces at\nexecution, unconditionally. Without that, a permission revoked mid-playbook would\nleave a cached \"allowed\" standing — a time-of-check/time-of-use bypass. That\nconstraint is also what makes the cache free: a stale `allow` costs a\ncorrectly-denied step, a stale `deny` costs a briefly hidden capability that\nexpires on the TTL, and neither is a security event.\n\nThe two planes are **not symmetric**, deliberately:\n\n| | Server (`createServerStepEvaluator`) | Browser (`createBrowserStepEvaluator`) |\n|---|---|---|\n| Layers | `tool-allowlist`, `operation-permission` | `action-exposure`, `public-access`, `field-permissions`, `app-auth` |\n| Source | `PrincipalRun.isToolAllowed` + the operation-permission predicate | `isApiActionEnabled`, `isRoutePublic`, field read-permission slugs |\n| Intent step | `plane` — denied unless the intent declares `server` | `intent-mount` — always `unknown` |\n| App auth | n/a (the predicate *is* the gate) | **`unknown`** — never evaluated |\n\nBrowser preflight covers the **static layers only**. Generated REST auth is\n`authMiddleware?: (objectName, action) => (req) => Promise<Request | Response>`:\nrequest-bound, `Response`-returning rather than boolean, and free to consult\nsession stores, rate-limit, or audit. It is not a dry-run predicate, so preflight\n**never invokes it**, synthetically or otherwise — the `_preflight` route's\noptions in `smrt-core` carry no auth handle at all, only the boolean\n`appAuthConfigured`. An optional `authPredicate` seam can later be added to\n`BrowserPreflightLayerSource` and turn the `app-auth` `unknown` into a real\nverdict **without changing the report contract**.\n\n`createBrowserPlaybookPreflight()` (in `rest-preflight.ts`) is the provider wired\ninto `APIConfig.playbookPreflight`. Core owns the route and the static-layer\nfacts because `ObjectRegistry` is core's; this package owns resolution and the\nverdict vocabulary — so the dependency stays one-way.\n\n### Not an oracle\n\nEvery playbook the caller's chain cannot resolve — unknown key, disabled,\nwrong-plane, unresolvable intent, or an error thrown anywhere in resolution or\nevaluation — returns the single frozen `PLAYBOOK_PREFLIGHT_UNAVAILABLE` value: no\nkey echo, no reason, no message, and served by the route with an unconditional\n200. An unknown key and an unauthorized key are byte-identical.\n\nTiming is held in the same class from both ends. The unknown-key path pays the\nsame override-layer read a resolvable key pays (`equalizeUnknownKeyCost`),\nbecause `resolvePlaybook()` short-circuits a registry miss before touching the\ndatabase; and unavailable results are cached for unknown and\nregistered-but-unavailable keys **alike**, because caching only one of them would\nput every *repeat* probe of the other in a different timing class. Growth from\nprobing random keys is bounded where it belongs — the preflight cache is capped\nwith expiry-first eviction — not by declining to cache.\n\n### Verdict rules worth knowing\n\n- `deny` beats `unknown` beats `allow` (`worstVerdict`), and a step's `reason` is\n always the first layer that produced its verdict — reason and verdict can never\n describe different layers.\n- Evaluation never short-circuits: preflight exists to say *which* step of five\n would die.\n- A missing **field** read-permission slug redacts a field, it does not fail the\n step, so `field-permissions` stays `allow` with `reason: 'fields-redacted'` and\n the missing slugs attached. Reporting `deny` would predict a failure that will\n not happen.\n- A non-public route with **no** middleware wired is a real, statically knowable\n `deny` (the generator's fail-closed 401). With one wired it is `unknown`.\n- A tool listing may filter on preflight (`filterPlaybooksByPreflight` in\n `smrt-agents`), but that filter is a listing convenience and **never**\n load-bearing for authorization.\n\n## Caching\n\nResolutions are cached per `(key, tenantId, db)` with a TTL. The cache is\ninvalidated on `PlaybookOverride.save()` and `.delete()`; an app-level write\n(`tenantId = null`) clears every tenant's entry for that key, because each\ntenant inherits from it. Use `clearPlaybookCache()` in tests.\n\nA monotonic per-`(db, key)` invalidation generation closes the read-racing-a-\nwrite window; see the Gotchas entry below before touching `cache.ts`.\n\nPreflight results cache separately, per `(principal, key, plane, tenant)`, with a\nshorter TTL and **no invalidation ceremony of their own** — an entry captured\nunder an older generation of the playbook cache is dropped on read. `principal`\nis an opaque, caller-scoped partition key: it is never echoed in a report and\nnever consulted for authority. Use `clearPlaybookPreflightCache()` in tests.\n\nTwo partitioning traps, both of which produce a cross-context read rather than a\nstale one:\n\n- **The tenant is resolved, not defaulted.** `preflightPlaybook()` applies the\n same `tenantId !== undefined ? tenantId : (getTenantId() ?? null)` fallback\n `loadPlaybookBase()` does. Scoping an omitted tenant to `null` would let two\n ambient `withTenant()` callers with one principal share an entry — and the\n report carries the resolved title and description.\n- **The principal must cover every input the evaluation reads.** The REST\n provider's default folds in `appAuthConfigured` (it decides whether\n `public-access` / `app-auth` are verdicts or `unknown`) and distinguishes an\n *absent* permission set (`perm:unpublished`, field layer `unknown`) from an\n explicitly empty one (known, `allow` with redactions). A custom `principal`\n must do the same.\n\n## Gotchas\n\n- **`context` carries the tenant scope.** `save()` sets\n `this.context = this.tenantId ?? '__app__'` and `conflictColumns` is\n `['key', 'context']`. `tenantId` is nullable, and a unique index over it\n would let multiple NULL rows coexist on PostgreSQL and DuckDB. The `context`\n trick (from `smrt-languages`) is what makes the same upsert correct on all\n three dialects — do not \"simplify\" it to `['key', 'tenantId']`.\n- **Identity changes need the delete-then-insert dance.** Changing `key` or\n `tenantId` on an existing row changes the conflict identity, so a plain\n `super.save()` writes the old primary key under a new one. Same handling as\n `PromptOverride` / `LanguageOverride`: a transaction where the driver has\n one, otherwise a staged replacement row deleted only after the new row is\n durable.\n- **JSON fields are stored as strings.** `planes` and `metadata` are text\n columns with guarded `getPlanes()` / `setPlanes()` / `getMetadata()` /\n `setMetadata()` helpers that swallow parse errors. Never override\n `toJSON()`; extend serialization through `transformJSON()`.\n- **`resolvePlaybook()` returns a result, it does not throw for policy.**\n Unknown key, disabled, wrong plane, and unresolvable intents all come back as\n `{ ok: false, reason, message }`. It *does* throw for programming errors —\n notably a `steps` key on an override layer.\n- **The cache carries an invalidation generation, and this is where it\n diverges from `smrt-prompts`.** A resolution captures\n `getPlaybookCacheGeneration(key, db)` before its asynchronous layer loads and\n hands it back to `setCachedPlaybookBase()`; a concurrent `save()` / `delete()`\n bumps the generation, and the in-flight resolution is then refused the cache\n write instead of repopulating the key it just invalidated with the pre-write\n value. Without it, \"a stale entry is never served after a write\" held only\n until a read raced a write, and then failed for the full 30s TTL. Generations\n are tracked per `(db, key)`, not per tenant, because an app-level row is\n inherited by every tenant. `clearPlaybookCache()` bumps rather than resets\n them, so a resolution that started before the clear cannot write back either.\n `smrt-prompts` and `smrt-languages` still carry the unguarded version of this\n race; fixing them is separate work.\n- **Restart vitest after adding a decorated class**; the manifest is generated\n at startup.\n\n## Related\n\n- `@happyvertical/smrt-prompts` — the pattern this package clones\n- `@happyvertical/smrt-languages` — source of the `context` column convention\n- `@happyvertical/smrt-features` — parallel package for feature flags\n- Epic #2585 — declared agent surface; #2587 capability vocabulary,\n #2588 view intents, #2590 preflight, #2591 manifest emission\n"
|
|
531
|
+
"agentDoc": "# smrt-playbooks\n\nLayered playbook registry, app/tenant overrides, and plan resolution. A\nplaybook is a named, described, layered sequence of steps an agent follows;\nbrowser agents, in-app agents, the Node MCP server, and the CLI all follow the\nsame resolved plan. Fourth instance of the `smrt-prompts` layered-override\npattern — keep it consistent with `prompts`, `languages`, and `features`.\n\n## Core pieces\n\n- `definePlaybook()` registers code defaults in a global process registry\n (`globalThis.__smrtPlaybookRegistry`), so a package-bundled playbook resolves\n with no application registration\n- `resolvePlaybook()` merges the layers and returns a `PlaybookResolution`\n- `PlaybookOverride` (`_smrt_playbook_overrides`) stores partial app-level and\n tenant-level overrides with write-time validation\n- `PlaybookOverrideCollection` exposes the standard SmrtCollection CRUD surface\n\n## Resolution layers (priority low → high)\n\n1. Code default — `definePlaybook({ key, title, description, steps })`\n2. File/config override — `getPackageConfig<PlaybookPackageConfig>('playbooks', defaults)`\n3. App-level stored override — `PlaybookOverride` row with `tenantId = null`\n4. Tenant-level stored override — `PlaybookOverride` row with the current tenant\n5. Runtime override — passed to `resolvePlaybook(key, { override })`\n\nInheritance is field-by-field: a stored column is nullable, and `null` means\n\"use the lower layer\".\n\n## Script semantics\n\nA playbook resolves to a plan the agent executes step by step. **It never\nexecutes as a unit** (epic #2585 invariant 4), so it is never an authority\nboundary and adds no new security object to review. Each step is authorized\nindependently — at the REST boundary in the browser, or by\n`PrincipalRun.assertToolAllowed()` server-side. Nothing in this package\nexecutes a step, and there is deliberately no executor export.\n\nPlaybooks are consequently never atomic and have no compensation.\n`onStepFailure` (`'abort'` | `'continue'`) is the whole of the contract for\nwhat an agent does when step 3 of 5 fails.\n\n## Steps\n\nExactly two kinds in v1, and playbooks cannot nest:\n\n- `{ kind: 'operation', model: '@happyvertical/smrt-commerce:Order', action: 'submit' }`\n — the qualified pair already used by STI discriminators and\n `@crossPackageRef`. **Never a generated tool name**: that is derived from\n model, action, and namespace, and would silently orphan stored tenant\n overrides on a namespace change.\n- `{ kind: 'intent', id }` — a view intent named by its declared #2588\n identity. Valid only where a surface is mounted.\n\nA step referencing another playbook is rejected in `normalizeSteps()` at\ndefinition time.\n\n### Classification is inherited, never self-declared\n\nA step never classifies itself. `resolvePlaybook()` takes a\n`classifier` (and, for intents, an `intents` registry) supplied by the host,\nwhich returns the `CapabilityDeclaration` emitted for the referenced operation\nby `@happyvertical/smrt-types`. Anything undeclared resolves fail-closed to\n`{ effect: 'destructive', idempotent: false, openWorld: true }`. The\nvocabulary itself is older and lower and shared with model tools — it lives in\n`smrt-types`, not here (owning it here would close a\ncore → playbooks → core cycle).\n\n## Plane validity\n\nA playbook declares `planes: readonly ('browser' | 'server')[]`. Resolution for\na caller on an undeclared plane fails closed with reason\n`'plane-not-declared'`.\n\n- Operation-only playbooks default to both planes.\n- A playbook containing a view-intent step defaults to `['browser']`. Server\n validity rides the shipped #2446 browser command/ack bridge, which lets a\n server-side agent drive mounted surfaces with acknowledgement — it must be\n **declared explicitly**, never assumed.\n- The same default applies one level down: a `PlaybookIntentRecord` that\n declares no `planes` is browser-only, so server validity must be declared at\n **both** the playbook and the intent. Silence from the intent registry never\n widens a plane (`'intent-plane-not-declared'`).\n\nThe `#2588` intent registry does not exist yet, so an intent step with no\n`intents` resolver supplied fails closed with\n`'intent-registry-unavailable'`. That resolver is the seam #2588 wires into.\n\n## Editability\n\n`editable` defaults **all-false**, matching `normalizeEditableConfig` in\n`smrt-prompts`. Every stored column has a flag — `title`, `description`,\n`planes`, `onStepFailure`, `enabled`, `metadata` — and `save()` rejects a\nnon-null value for any field the definition has not opted in. `onStepFailure`\nis gated like the rest: flipping a locked playbook from `'abort'` to\n`'continue'` would change what an agent does after a failed prerequisite.\n\n`steps` is **structurally** non-editable, not merely defaulted false:\n\n- `PlaybookEditableConfig` has no `steps` key, and marking one throws at\n definition time\n- `_smrt_playbook_overrides` has **no `steps` column**, so no write of any kind\n has anywhere to put a step list\n- `PlaybookOverride.save()` rejects a `steps` property assigned through the\n untyped option bag rather than dropping it silently\n- `normalizePlaybookLayer()` throws on a `steps` key from the config or runtime\n layer\n- the resolver reads steps only from `PlaybookRegistry`\n\nThe reason is not escalation — under the script model a tenant cannot escalate,\nsince every step is authorized independently regardless of who wrote the list.\nIt is that an agent announcing \"checking out your cart\" while an overridden\nstep list does something else is a description-behavior mismatch.\n\nEnablement overrides are one-directional: a layer may disable, never re-enable\nwhat a lower layer disabled. Enforced in `mergePlaybookLayers()` (`enabled &&\nlayer.enabled`) and rejected at `save()` with a specific message. Plane lists\nnarrow the same way.\n\n## Preflight (#2590)\n\n`preflightPlaybook({ key, plane, principal, resolve, evaluate })` resolves a\nplaybook through the **caller's own layer chain**, decomposes it, and returns a\nper-step verdict — `allow` | `deny` | `unknown` — plus an aggregate. It executes\nnothing.\n\n**Advisory only. Preflight predicts; it never grants.** Every step re-enforces at\nexecution, unconditionally. Without that, a permission revoked mid-playbook would\nleave a cached \"allowed\" standing — a time-of-check/time-of-use bypass. That\nconstraint is also what makes the cache free: a stale `allow` costs a\ncorrectly-denied step, a stale `deny` costs a briefly hidden capability that\nexpires on the TTL, and neither is a security event.\n\nThe two planes are **not symmetric**, deliberately:\n\n| | Server (`createServerStepEvaluator`) | Browser (`createBrowserStepEvaluator`) |\n|---|---|---|\n| Layers | `tool-allowlist`, `operation-permission` | `action-exposure`, `public-access`, `field-permissions`, `app-auth` |\n| Source | `PrincipalRun.isToolAllowed` + the operation-permission predicate | `isApiActionEnabled`, `isRoutePublic`, field read-permission slugs |\n| Intent step | `plane` — denied unless the intent declares `server` | `intent-mount` — always `unknown` |\n| App auth | n/a (the predicate *is* the gate) | **`unknown`** — never evaluated |\n\nBrowser preflight covers the **static layers only**. Generated REST auth is\n`authMiddleware?: (objectName, action) => (req) => Promise<Request | Response>`:\nrequest-bound, `Response`-returning rather than boolean, and free to consult\nsession stores, rate-limit, or audit. It is not a dry-run predicate, so preflight\n**never invokes it**, synthetically or otherwise — the `_preflight` route's\noptions in `smrt-core` carry no auth handle at all, only the boolean\n`appAuthConfigured`. An optional `authPredicate` seam can later be added to\n`BrowserPreflightLayerSource` and turn the `app-auth` `unknown` into a real\nverdict **without changing the report contract**.\n\n`createBrowserPlaybookPreflight()` (in `rest-preflight.ts`) is the provider wired\ninto `APIConfig.playbookPreflight`. Core owns the route and the static-layer\nfacts because `ObjectRegistry` is core's; this package owns resolution and the\nverdict vocabulary — so the dependency stays one-way.\n\n### Not an oracle\n\nEvery playbook the caller's chain cannot resolve — unknown key, disabled,\nwrong-plane, unresolvable intent, or an error thrown anywhere in resolution or\nevaluation — returns the single frozen `PLAYBOOK_PREFLIGHT_UNAVAILABLE` value: no\nkey echo, no reason, no message, and served by the route with an unconditional\n200. An unknown key and an unauthorized key are byte-identical.\n\nTiming is held in the same class from both ends. The unknown-key path pays the\nsame override-layer read a resolvable key pays (`equalizeUnknownKeyCost`),\nbecause `resolvePlaybook()` short-circuits a registry miss before touching the\ndatabase; and unavailable results are cached for unknown and\nregistered-but-unavailable keys **alike**, because caching only one of them would\nput every *repeat* probe of the other in a different timing class. Growth from\nprobing random keys is bounded where it belongs — the preflight cache is capped\nwith expiry-first eviction — not by declining to cache.\n\n### Verdict rules worth knowing\n\n- `deny` beats `unknown` beats `allow` (`worstVerdict`), and a step's `reason` is\n always the first layer that produced its verdict — reason and verdict can never\n describe different layers.\n- Evaluation never short-circuits: preflight exists to say *which* step of five\n would die.\n- A missing **field** read-permission slug redacts a field, it does not fail the\n step, so `field-permissions` stays `allow` with `reason: 'fields-redacted'` and\n the missing slugs attached. Reporting `deny` would predict a failure that will\n not happen.\n- A non-public route with **no** middleware wired is a real, statically knowable\n `deny` (the generator's fail-closed 401). With one wired it is `unknown`.\n- A tool listing may filter on preflight (`filterPlaybooksByPreflight` in\n `smrt-agents`), but that filter is a listing convenience and **never**\n load-bearing for authorization.\n\n## Caching\n\nResolutions are cached per `(key, tenantId, db)` with a TTL. The cache is\ninvalidated on `PlaybookOverride.save()` and `.delete()`; an app-level write\n(`tenantId = null`) clears every tenant's entry for that key, because each\ntenant inherits from it. Use `clearPlaybookCache()` in tests.\n\nA monotonic per-`(db, key)` invalidation generation closes the read-racing-a-\nwrite window; see the Gotchas entry below before touching `cache.ts`.\n\nPreflight results cache separately, per `(principal, key, plane, tenant)`, with a\nshorter TTL and **no invalidation ceremony of their own** — an entry captured\nunder an older generation of the playbook cache is dropped on read. `principal`\nis an opaque, caller-scoped partition key: it is never echoed in a report and\nnever consulted for authority. Use `clearPlaybookPreflightCache()` in tests.\n\nTwo partitioning traps, both of which produce a cross-context read rather than a\nstale one:\n\n- **The tenant is resolved, not defaulted.** `preflightPlaybook()` applies the\n same `tenantId !== undefined ? tenantId : (getTenantId() ?? null)` fallback\n `loadPlaybookBase()` does. Scoping an omitted tenant to `null` would let two\n ambient `withTenant()` callers with one principal share an entry — and the\n report carries the resolved title and description.\n- **The principal must cover every input the evaluation reads.** The REST\n provider's default folds in `appAuthConfigured` (it decides whether\n `public-access` / `app-auth` are verdicts or `unknown`) and distinguishes an\n *absent* permission set (`perm:unpublished`, field layer `unknown`) from an\n explicitly empty one (known, `allow` with redactions). A custom `principal`\n must do the same.\n\n## Gotchas\n\n- **`context` carries the tenant scope.** `save()` sets\n `this.context = this.tenantId ?? '__app__'` and `conflictColumns` is\n `['key', 'context']`. `tenantId` is nullable, and a unique index over it\n would let multiple NULL rows coexist on PostgreSQL and DuckDB. The `context`\n trick (from `smrt-languages`) is what makes the same upsert correct on all\n three dialects — do not \"simplify\" it to `['key', 'tenantId']`.\n- **Identity changes need the delete-then-insert dance.** Changing `key` or\n `tenantId` on an existing row changes the conflict identity, so a plain\n `super.save()` writes the old primary key under a new one. Same handling as\n `PromptOverride` / `LanguageOverride`: a transaction where the driver has\n one, otherwise a staged replacement row deleted only after the new row is\n durable.\n- **JSON fields are stored as strings.** `planes` and `metadata` are text\n columns with guarded `getPlanes()` / `setPlanes()` / `getMetadata()` /\n `setMetadata()` helpers that swallow parse errors. Never override\n `toJSON()`; extend serialization through `transformJSON()`.\n- **`resolvePlaybook()` returns a result, it does not throw for policy.**\n Unknown key, disabled, wrong plane, and unresolvable intents all come back as\n `{ ok: false, reason, message }`. It *does* throw for programming errors —\n notably a `steps` key on an override layer.\n- **The cache carries an invalidation generation, and this is where it\n diverges from `smrt-prompts`.** A resolution captures\n `getPlaybookCacheGeneration(key, db)` before its asynchronous layer loads and\n hands it back to `setCachedPlaybookBase()`; a concurrent `save()` / `delete()`\n bumps the generation, and the in-flight resolution is then refused the cache\n write instead of repopulating the key it just invalidated with the pre-write\n value. Without it, \"a stale entry is never served after a write\" held only\n until a read raced a write, and then failed for the full 30s TTL. Generations\n are tracked per `(db, key)`, not per tenant, because an app-level row is\n inherited by every tenant. `clearPlaybookCache()` bumps rather than resets\n them, so a resolution that started before the clear cannot write back either.\n `smrt-prompts` and `smrt-languages` carry the same mechanism (#2609);\n `smrt-languages` additionally keys its generation by locale, and both raise a\n single `clearedThrough` floor in `clear*Cache()` instead of the per-key bump\n here. The per-key bump cannot reach a key that has never been invalidated —\n it has no map entry, so it reads as generation 0 before and after the clear\n and an in-flight resolution writes its pre-clear value back. `clearPlaybookCache()`\n still carries that gap; #2716 ports the floor.\n- **Restart vitest after adding a decorated class**; the manifest is generated\n at startup.\n\n## Related\n\n- `@happyvertical/smrt-prompts` — the pattern this package clones\n- `@happyvertical/smrt-languages` — source of the `context` column convention\n- `@happyvertical/smrt-features` — parallel package for feature flags\n- Epic #2585 — declared agent surface; #2587 capability vocabulary,\n #2588 view intents, #2590 preflight, #2591 manifest emission\n"
|
|
488
532
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-playbooks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"description": "Layered playbook registry, app/tenant overrides, and plan resolution for SMRT agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,18 +19,18 @@
|
|
|
19
19
|
"./manifest.json": "./dist/manifest.json"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@happyvertical/
|
|
23
|
-
"@happyvertical/smrt-
|
|
24
|
-
"@happyvertical/smrt-
|
|
25
|
-
"@happyvertical/smrt-types": "0.
|
|
26
|
-
"@happyvertical/
|
|
22
|
+
"@happyvertical/smrt-config": "0.47.0",
|
|
23
|
+
"@happyvertical/smrt-core": "0.47.0",
|
|
24
|
+
"@happyvertical/smrt-tenancy": "0.47.0",
|
|
25
|
+
"@happyvertical/smrt-types": "0.47.0",
|
|
26
|
+
"@happyvertical/sql": "^0.89.4"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
+
"@happyvertical/smrt-vitest": "0.47.0",
|
|
29
30
|
"@types/node": "24.13.2",
|
|
30
31
|
"typescript": "5.9.3",
|
|
31
32
|
"vite": "8.1.4",
|
|
32
|
-
"vitest": "4.1.10"
|
|
33
|
-
"@happyvertical/smrt-vitest": "0.45.3"
|
|
33
|
+
"vitest": "4.1.10"
|
|
34
34
|
},
|
|
35
35
|
"keywords": [
|
|
36
36
|
"smrt",
|