@happyvertical/smrt-playbooks 0.46.0 → 0.47.1

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 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` still carry the unguarded version of this
257
- race; fixing them is separate work.
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.46.0\",\"objects\":{\"@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}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"getPlanes\":{\"name\":\"getPlanes\",\"async\":false,\"parameters\":[],\"returnType\":\"null\",\"isStatic\":false,\"isPublic\":true},\"setPlanes\":{\"name\":\"setPlanes\",\"async\":false,\"parameters\":[{\"name\":\"planes\",\"type\":\"null\",\"optional\":false}],\"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\",\"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\"}},\"@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}],\"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}],\"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},{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"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\"}}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\",\"@happyvertical/smrt-tenancy\"]}"));
18
+ ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-playbooks\",\"packageVersion\":\"0.47.1\",\"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;
@@ -2,8 +2,158 @@
2
2
  "version": "1.0.0",
3
3
  "timestamp": 0,
4
4
  "packageName": "@happyvertical/smrt-playbooks",
5
- "packageVersion": "0.46.0",
5
+ "packageVersion": "0.47.1",
6
6
  "objects": {
7
+ "@happyvertical/smrt-playbooks:PlaybookOverrideCollection": {
8
+ "name": "playbookoverridecollection",
9
+ "className": "PlaybookOverrideCollection",
10
+ "qualifiedName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
11
+ "collection": "playbookoverrides",
12
+ "filePath": "/home/runner/work/smrt/smrt/packages/playbooks/src/collections/PlaybookOverrideCollection.ts",
13
+ "packageName": "@happyvertical/smrt-playbooks",
14
+ "fields": {},
15
+ "methods": {
16
+ "getAppOverride": {
17
+ "name": "getAppOverride",
18
+ "async": true,
19
+ "parameters": [
20
+ {
21
+ "name": "key",
22
+ "type": "string",
23
+ "optional": false
24
+ },
25
+ {
26
+ "name": "options",
27
+ "type": "object",
28
+ "optional": true,
29
+ "memberTypes": [
30
+ "string"
31
+ ]
32
+ }
33
+ ],
34
+ "returnType": "Promise<PlaybookOverride | null>",
35
+ "isStatic": false,
36
+ "isPublic": true
37
+ },
38
+ "getTenantOverride": {
39
+ "name": "getTenantOverride",
40
+ "async": true,
41
+ "parameters": [
42
+ {
43
+ "name": "key",
44
+ "type": "string",
45
+ "optional": false
46
+ },
47
+ {
48
+ "name": "tenantId",
49
+ "type": "string",
50
+ "optional": false
51
+ },
52
+ {
53
+ "name": "options",
54
+ "type": "object",
55
+ "optional": true,
56
+ "memberTypes": [
57
+ "string"
58
+ ]
59
+ }
60
+ ],
61
+ "returnType": "Promise<PlaybookOverride | null>",
62
+ "isStatic": false,
63
+ "isPublic": true
64
+ },
65
+ "getResolutionLayers": {
66
+ "name": "getResolutionLayers",
67
+ "async": true,
68
+ "parameters": [
69
+ {
70
+ "name": "key",
71
+ "type": "string",
72
+ "optional": false
73
+ },
74
+ {
75
+ "name": "tenantId",
76
+ "type": "string | null",
77
+ "optional": true,
78
+ "unionBranches": [
79
+ {
80
+ "type": "string"
81
+ },
82
+ {
83
+ "type": "null"
84
+ }
85
+ ]
86
+ },
87
+ {
88
+ "name": "options",
89
+ "type": "object",
90
+ "optional": true,
91
+ "memberTypes": [
92
+ "string"
93
+ ]
94
+ }
95
+ ],
96
+ "returnType": "Promise<object>",
97
+ "isStatic": false,
98
+ "isPublic": true
99
+ }
100
+ },
101
+ "decoratorConfig": {
102
+ "tableName": "_smrt_playbook_overrides"
103
+ },
104
+ "extends": "SmrtCollection",
105
+ "extendsTypeArg": "PlaybookOverride",
106
+ "exportName": "PlaybookOverrideCollection",
107
+ "collectionExportName": "PlaybookOverrideCollectionCollection",
108
+ "schema": {
109
+ "tableName": "_smrt_playbook_overrides",
110
+ "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);",
111
+ "columns": {
112
+ "id": {
113
+ "type": "UUID",
114
+ "primaryKey": true,
115
+ "referenceKind": "id",
116
+ "notNull": true
117
+ },
118
+ "slug": {
119
+ "type": "TEXT",
120
+ "notNull": true
121
+ },
122
+ "context": {
123
+ "type": "TEXT",
124
+ "notNull": true,
125
+ "default": ""
126
+ },
127
+ "created_at": {
128
+ "type": "TIMESTAMP",
129
+ "notNull": true,
130
+ "default": "current_timestamp"
131
+ },
132
+ "updated_at": {
133
+ "type": "TIMESTAMP",
134
+ "notNull": true,
135
+ "default": "current_timestamp"
136
+ }
137
+ },
138
+ "indexes": [
139
+ {
140
+ "name": "_smrt_playbook_overrides_slug_context_idx",
141
+ "columns": [
142
+ "slug",
143
+ "context"
144
+ ],
145
+ "unique": true
146
+ },
147
+ {
148
+ "name": "_smrt_playbook_overrides_created_at_idx",
149
+ "columns": [
150
+ "created_at"
151
+ ]
152
+ }
153
+ ],
154
+ "version": "410e08c4"
155
+ }
156
+ },
7
157
  "@happyvertical/smrt-playbooks:PlaybookOverride": {
8
158
  "name": "playbookoverride",
9
159
  "className": "PlaybookOverride",
@@ -86,7 +236,15 @@
86
236
  {
87
237
  "name": "metadata",
88
238
  "type": "PlaybookMetadata | null",
89
- "optional": false
239
+ "optional": false,
240
+ "unionBranches": [
241
+ {
242
+ "type": "PlaybookMetadata"
243
+ },
244
+ {
245
+ "type": "null"
246
+ }
247
+ ]
90
248
  }
91
249
  ],
92
250
  "returnType": "void",
@@ -97,7 +255,7 @@
97
255
  "name": "getPlanes",
98
256
  "async": false,
99
257
  "parameters": [],
100
- "returnType": "null",
258
+ "returnType": "any",
101
259
  "isStatic": false,
102
260
  "isPublic": true
103
261
  },
@@ -107,8 +265,9 @@
107
265
  "parameters": [
108
266
  {
109
267
  "name": "planes",
110
- "type": "null",
111
- "optional": false
268
+ "type": "any",
269
+ "optional": false,
270
+ "typeUnresolved": true
112
271
  }
113
272
  ],
114
273
  "returnType": "void",
@@ -127,7 +286,7 @@
127
286
  "name": "save",
128
287
  "async": true,
129
288
  "parameters": [],
130
- "returnType": "Promise",
289
+ "returnType": "Promise<this>",
131
290
  "isStatic": false,
132
291
  "isPublic": true
133
292
  },
@@ -281,139 +440,6 @@
281
440
  ],
282
441
  "version": "8aec8e13"
283
442
  }
284
- },
285
- "@happyvertical/smrt-playbooks:PlaybookOverrideCollection": {
286
- "name": "playbookoverridecollection",
287
- "className": "PlaybookOverrideCollection",
288
- "qualifiedName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
289
- "collection": "playbookoverrides",
290
- "filePath": "/home/runner/work/smrt/smrt/packages/playbooks/src/collections/PlaybookOverrideCollection.ts",
291
- "packageName": "@happyvertical/smrt-playbooks",
292
- "fields": {},
293
- "methods": {
294
- "getAppOverride": {
295
- "name": "getAppOverride",
296
- "async": true,
297
- "parameters": [
298
- {
299
- "name": "key",
300
- "type": "string",
301
- "optional": false
302
- },
303
- {
304
- "name": "options",
305
- "type": "object",
306
- "optional": true
307
- }
308
- ],
309
- "returnType": "Promise<PlaybookOverride | null>",
310
- "isStatic": false,
311
- "isPublic": true
312
- },
313
- "getTenantOverride": {
314
- "name": "getTenantOverride",
315
- "async": true,
316
- "parameters": [
317
- {
318
- "name": "key",
319
- "type": "string",
320
- "optional": false
321
- },
322
- {
323
- "name": "tenantId",
324
- "type": "string",
325
- "optional": false
326
- },
327
- {
328
- "name": "options",
329
- "type": "object",
330
- "optional": true
331
- }
332
- ],
333
- "returnType": "Promise<PlaybookOverride | null>",
334
- "isStatic": false,
335
- "isPublic": true
336
- },
337
- "getResolutionLayers": {
338
- "name": "getResolutionLayers",
339
- "async": true,
340
- "parameters": [
341
- {
342
- "name": "key",
343
- "type": "string",
344
- "optional": false
345
- },
346
- {
347
- "name": "tenantId",
348
- "type": "string | null",
349
- "optional": true
350
- },
351
- {
352
- "name": "options",
353
- "type": "object",
354
- "optional": true
355
- }
356
- ],
357
- "returnType": "Promise<object>",
358
- "isStatic": false,
359
- "isPublic": true
360
- }
361
- },
362
- "decoratorConfig": {
363
- "tableName": "_smrt_playbook_overrides"
364
- },
365
- "extends": "SmrtCollection",
366
- "extendsTypeArg": "PlaybookOverride",
367
- "exportName": "PlaybookOverrideCollection",
368
- "collectionExportName": "PlaybookOverrideCollectionCollection",
369
- "schema": {
370
- "tableName": "_smrt_playbook_overrides",
371
- "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);",
372
- "columns": {
373
- "id": {
374
- "type": "UUID",
375
- "primaryKey": true,
376
- "referenceKind": "id",
377
- "notNull": true
378
- },
379
- "slug": {
380
- "type": "TEXT",
381
- "notNull": true
382
- },
383
- "context": {
384
- "type": "TEXT",
385
- "notNull": true,
386
- "default": ""
387
- },
388
- "created_at": {
389
- "type": "TIMESTAMP",
390
- "notNull": true,
391
- "default": "current_timestamp"
392
- },
393
- "updated_at": {
394
- "type": "TIMESTAMP",
395
- "notNull": true,
396
- "default": "current_timestamp"
397
- }
398
- },
399
- "indexes": [
400
- {
401
- "name": "_smrt_playbook_overrides_slug_context_idx",
402
- "columns": [
403
- "slug",
404
- "context"
405
- ],
406
- "unique": true
407
- },
408
- {
409
- "name": "_smrt_playbook_overrides_created_at_idx",
410
- "columns": [
411
- "created_at"
412
- ]
413
- }
414
- ],
415
- "version": "410e08c4"
416
- }
417
443
  }
418
444
  },
419
445
  "moduleType": "smrt",
@@ -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.46.0",
6
+ "packageVersion": "0.47.1",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "6feea8f52be712a24448665b6550874154e8a3d7463082064a5effab95ce6a97",
11
- "packageJson": "c46c5988370edd41e67c99583c368322e2a93a28b147d12a5709ebe45421dd30",
12
- "agents": "db7f96be7f32be81a0df3e55532da96bb0bace194fb4314feafacbdeafebf6ae"
10
+ "manifest": "e1590cea0e2f3b73c8c0989fadf9a71b201a275c9d6cf1e5e8c9e3e8b54b6dbd",
11
+ "packageJson": "9e3bf71baebb601f77f01225c04ef629f0b05ba111e45467111db9cd871356cf",
12
+ "agents": "24cc1ff7167bdb3f2c2b9350d6ba24fb0286013023c5cc49876f16811387e312"
13
13
  },
14
14
  "exports": [
15
15
  ".",
@@ -41,6 +41,119 @@
41
41
  "tags": [],
42
42
  "risks": [],
43
43
  "objects": [
44
+ {
45
+ "name": "PlaybookOverrideCollection",
46
+ "qualifiedName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
47
+ "collection": "playbookoverrides",
48
+ "tableName": "_smrt_playbook_overrides",
49
+ "packageName": "@happyvertical/smrt-playbooks",
50
+ "extends": "SmrtCollection",
51
+ "fields": [],
52
+ "relationships": [],
53
+ "methods": [
54
+ "getAppOverride",
55
+ "getResolutionLayers",
56
+ "getTenantOverride"
57
+ ],
58
+ "methodSignatures": [
59
+ {
60
+ "name": "getAppOverride",
61
+ "async": true,
62
+ "params": [
63
+ "key: string",
64
+ "options?: object"
65
+ ],
66
+ "returns": "Promise<PlaybookOverride | null>"
67
+ },
68
+ {
69
+ "name": "getResolutionLayers",
70
+ "async": true,
71
+ "params": [
72
+ "key: string",
73
+ "tenantId?: string | null",
74
+ "options?: object"
75
+ ],
76
+ "returns": "Promise<object>"
77
+ },
78
+ {
79
+ "name": "getTenantOverride",
80
+ "async": true,
81
+ "params": [
82
+ "key: string",
83
+ "tenantId: string",
84
+ "options?: object"
85
+ ],
86
+ "returns": "Promise<PlaybookOverride | null>"
87
+ }
88
+ ],
89
+ "surfaces": [
90
+ {
91
+ "kind": "api",
92
+ "name": "playbookoverrides.getAppOverride",
93
+ "operation": "getAppOverride",
94
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
95
+ "path": "/playbookoverrides/getAppOverride",
96
+ "method": "POST"
97
+ },
98
+ {
99
+ "kind": "api",
100
+ "name": "playbookoverrides.getTenantOverride",
101
+ "operation": "getTenantOverride",
102
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
103
+ "path": "/playbookoverrides/getTenantOverride",
104
+ "method": "POST"
105
+ },
106
+ {
107
+ "kind": "api",
108
+ "name": "playbookoverrides.getResolutionLayers",
109
+ "operation": "getResolutionLayers",
110
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
111
+ "path": "/playbookoverrides/getResolutionLayers",
112
+ "method": "POST"
113
+ },
114
+ {
115
+ "kind": "cli",
116
+ "name": "playbookoverridecollection_getAppOverride",
117
+ "operation": "getAppOverride",
118
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
119
+ },
120
+ {
121
+ "kind": "cli",
122
+ "name": "playbookoverridecollection_getTenantOverride",
123
+ "operation": "getTenantOverride",
124
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
125
+ },
126
+ {
127
+ "kind": "cli",
128
+ "name": "playbookoverridecollection_getResolutionLayers",
129
+ "operation": "getResolutionLayers",
130
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
131
+ },
132
+ {
133
+ "kind": "mcp",
134
+ "name": "playbookoverridecollection_getappoverride",
135
+ "operation": "getAppOverride",
136
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
137
+ },
138
+ {
139
+ "kind": "mcp",
140
+ "name": "playbookoverridecollection_gettenantoverride",
141
+ "operation": "getTenantOverride",
142
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
143
+ },
144
+ {
145
+ "kind": "mcp",
146
+ "name": "playbookoverridecollection_getresolutionlayers",
147
+ "operation": "getResolutionLayers",
148
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
149
+ }
150
+ ],
151
+ "relationshipFeatures": [
152
+ "uuidColumns"
153
+ ],
154
+ "tags": [],
155
+ "risks": []
156
+ },
44
157
  {
45
158
  "name": "PlaybookOverride",
46
159
  "qualifiedName": "@happyvertical/smrt-playbooks:PlaybookOverride",
@@ -121,12 +234,12 @@
121
234
  },
122
235
  {
123
236
  "name": "getPlanes",
124
- "returns": "null"
237
+ "returns": "any"
125
238
  },
126
239
  {
127
240
  "name": "save",
128
241
  "async": true,
129
- "returns": "Promise"
242
+ "returns": "Promise<this>"
130
243
  },
131
244
  {
132
245
  "name": "setMetadata",
@@ -138,7 +251,7 @@
138
251
  {
139
252
  "name": "setPlanes",
140
253
  "params": [
141
- "planes: null"
254
+ "planes: any"
142
255
  ],
143
256
  "returns": "void"
144
257
  },
@@ -223,117 +336,48 @@
223
336
  "objectName": "@happyvertical/smrt-playbooks:PlaybookOverride"
224
337
  }
225
338
  ],
226
- "relationshipFeatures": [
227
- "uuidColumns"
228
- ],
229
- "tags": [],
230
- "risks": []
231
- },
232
- {
233
- "name": "PlaybookOverrideCollection",
234
- "qualifiedName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
235
- "collection": "playbookoverrides",
236
- "tableName": "_smrt_playbook_overrides",
237
- "packageName": "@happyvertical/smrt-playbooks",
238
- "extends": "SmrtCollection",
239
- "fields": [],
240
- "relationships": [],
241
- "methods": [
242
- "getAppOverride",
243
- "getResolutionLayers",
244
- "getTenantOverride"
245
- ],
246
- "methodSignatures": [
247
- {
248
- "name": "getAppOverride",
249
- "async": true,
250
- "params": [
251
- "key: string",
252
- "options?: object"
253
- ],
254
- "returns": "Promise<PlaybookOverride | null>"
255
- },
256
- {
257
- "name": "getResolutionLayers",
258
- "async": true,
259
- "params": [
260
- "key: string",
261
- "tenantId?: string | null",
262
- "options?: object"
263
- ],
264
- "returns": "Promise<object>"
265
- },
266
- {
267
- "name": "getTenantOverride",
268
- "async": true,
269
- "params": [
270
- "key: string",
271
- "tenantId: string",
272
- "options?: object"
273
- ],
274
- "returns": "Promise<PlaybookOverride | null>"
275
- }
276
- ],
277
- "surfaces": [
339
+ "withheldSurfaces": [
278
340
  {
279
341
  "kind": "api",
280
- "name": "playbookoverrides.getAppOverride",
281
- "operation": "getAppOverride",
282
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
283
- "path": "/playbookoverrides/getAppOverride",
284
- "method": "POST"
342
+ "operation": "getMetadata",
343
+ "code": "not-included",
344
+ "reason": "not listed in api.include",
345
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverride"
285
346
  },
286
347
  {
287
348
  "kind": "api",
288
- "name": "playbookoverrides.getTenantOverride",
289
- "operation": "getTenantOverride",
290
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
291
- "path": "/playbookoverrides/getTenantOverride",
292
- "method": "POST"
349
+ "operation": "getPlanes",
350
+ "code": "not-included",
351
+ "reason": "not listed in api.include",
352
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverride"
293
353
  },
294
354
  {
295
355
  "kind": "api",
296
- "name": "playbookoverrides.getResolutionLayers",
297
- "operation": "getResolutionLayers",
298
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
299
- "path": "/playbookoverrides/getResolutionLayers",
300
- "method": "POST"
301
- },
302
- {
303
- "kind": "cli",
304
- "name": "playbookoverridecollection_getAppOverride",
305
- "operation": "getAppOverride",
306
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
307
- },
308
- {
309
- "kind": "cli",
310
- "name": "playbookoverridecollection_getTenantOverride",
311
- "operation": "getTenantOverride",
312
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
313
- },
314
- {
315
- "kind": "cli",
316
- "name": "playbookoverridecollection_getResolutionLayers",
317
- "operation": "getResolutionLayers",
318
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
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"
319
360
  },
320
361
  {
321
- "kind": "mcp",
322
- "name": "playbookoverridecollection_getappoverride",
323
- "operation": "getAppOverride",
324
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
362
+ "kind": "api",
363
+ "operation": "setMetadata",
364
+ "code": "not-included",
365
+ "reason": "not listed in api.include",
366
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverride"
325
367
  },
326
368
  {
327
- "kind": "mcp",
328
- "name": "playbookoverridecollection_gettenantoverride",
329
- "operation": "getTenantOverride",
330
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
369
+ "kind": "api",
370
+ "operation": "setPlanes",
371
+ "code": "not-included",
372
+ "reason": "not listed in api.include",
373
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverride"
331
374
  },
332
375
  {
333
- "kind": "mcp",
334
- "name": "playbookoverridecollection_getresolutionlayers",
335
- "operation": "getResolutionLayers",
336
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
376
+ "kind": "api",
377
+ "operation": "toPlaybookLayer",
378
+ "code": "not-included",
379
+ "reason": "not listed in api.include",
380
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverride"
337
381
  }
338
382
  ],
339
383
  "relationshipFeatures": [
@@ -344,6 +388,66 @@
344
388
  }
345
389
  ],
346
390
  "surfaces": [
391
+ {
392
+ "kind": "api",
393
+ "name": "playbookoverrides.getAppOverride",
394
+ "operation": "getAppOverride",
395
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
396
+ "path": "/playbookoverrides/getAppOverride",
397
+ "method": "POST"
398
+ },
399
+ {
400
+ "kind": "api",
401
+ "name": "playbookoverrides.getTenantOverride",
402
+ "operation": "getTenantOverride",
403
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
404
+ "path": "/playbookoverrides/getTenantOverride",
405
+ "method": "POST"
406
+ },
407
+ {
408
+ "kind": "api",
409
+ "name": "playbookoverrides.getResolutionLayers",
410
+ "operation": "getResolutionLayers",
411
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
412
+ "path": "/playbookoverrides/getResolutionLayers",
413
+ "method": "POST"
414
+ },
415
+ {
416
+ "kind": "cli",
417
+ "name": "playbookoverridecollection_getAppOverride",
418
+ "operation": "getAppOverride",
419
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
420
+ },
421
+ {
422
+ "kind": "cli",
423
+ "name": "playbookoverridecollection_getTenantOverride",
424
+ "operation": "getTenantOverride",
425
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
426
+ },
427
+ {
428
+ "kind": "cli",
429
+ "name": "playbookoverridecollection_getResolutionLayers",
430
+ "operation": "getResolutionLayers",
431
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
432
+ },
433
+ {
434
+ "kind": "mcp",
435
+ "name": "playbookoverridecollection_getappoverride",
436
+ "operation": "getAppOverride",
437
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
438
+ },
439
+ {
440
+ "kind": "mcp",
441
+ "name": "playbookoverridecollection_gettenantoverride",
442
+ "operation": "getTenantOverride",
443
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
444
+ },
445
+ {
446
+ "kind": "mcp",
447
+ "name": "playbookoverridecollection_getresolutionlayers",
448
+ "operation": "getResolutionLayers",
449
+ "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
450
+ },
347
451
  {
348
452
  "kind": "api",
349
453
  "name": "playbookoverrides.list",
@@ -413,66 +517,6 @@
413
517
  "name": "playbookoverride_delete",
414
518
  "operation": "delete",
415
519
  "objectName": "@happyvertical/smrt-playbooks:PlaybookOverride"
416
- },
417
- {
418
- "kind": "api",
419
- "name": "playbookoverrides.getAppOverride",
420
- "operation": "getAppOverride",
421
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
422
- "path": "/playbookoverrides/getAppOverride",
423
- "method": "POST"
424
- },
425
- {
426
- "kind": "api",
427
- "name": "playbookoverrides.getTenantOverride",
428
- "operation": "getTenantOverride",
429
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
430
- "path": "/playbookoverrides/getTenantOverride",
431
- "method": "POST"
432
- },
433
- {
434
- "kind": "api",
435
- "name": "playbookoverrides.getResolutionLayers",
436
- "operation": "getResolutionLayers",
437
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection",
438
- "path": "/playbookoverrides/getResolutionLayers",
439
- "method": "POST"
440
- },
441
- {
442
- "kind": "cli",
443
- "name": "playbookoverridecollection_getAppOverride",
444
- "operation": "getAppOverride",
445
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
446
- },
447
- {
448
- "kind": "cli",
449
- "name": "playbookoverridecollection_getTenantOverride",
450
- "operation": "getTenantOverride",
451
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
452
- },
453
- {
454
- "kind": "cli",
455
- "name": "playbookoverridecollection_getResolutionLayers",
456
- "operation": "getResolutionLayers",
457
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
458
- },
459
- {
460
- "kind": "mcp",
461
- "name": "playbookoverridecollection_getappoverride",
462
- "operation": "getAppOverride",
463
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
464
- },
465
- {
466
- "kind": "mcp",
467
- "name": "playbookoverridecollection_gettenantoverride",
468
- "operation": "getTenantOverride",
469
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
470
- },
471
- {
472
- "kind": "mcp",
473
- "name": "playbookoverridecollection_getresolutionlayers",
474
- "operation": "getResolutionLayers",
475
- "objectName": "@happyvertical/smrt-playbooks:PlaybookOverrideCollection"
476
520
  }
477
521
  ],
478
522
  "prompts": [],
@@ -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.46.0",
3
+ "version": "0.47.1",
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/sql": "^0.89.4",
23
- "@happyvertical/smrt-config": "0.46.0",
24
- "@happyvertical/smrt-types": "0.46.0",
25
- "@happyvertical/smrt-core": "0.46.0",
26
- "@happyvertical/smrt-tenancy": "0.46.0"
22
+ "@happyvertical/smrt-config": "0.47.1",
23
+ "@happyvertical/smrt-core": "0.47.1",
24
+ "@happyvertical/smrt-tenancy": "0.47.1",
25
+ "@happyvertical/smrt-types": "0.47.1",
26
+ "@happyvertical/sql": "^0.89.6"
27
27
  },
28
28
  "devDependencies": {
29
+ "@happyvertical/smrt-vitest": "0.47.1",
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.46.0"
33
+ "vitest": "4.1.10"
34
34
  },
35
35
  "keywords": [
36
36
  "smrt",