@mccaskie-labs/workbench-config 0.0.0-stage → 0.1.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.
@@ -0,0 +1,554 @@
1
+ // Generated by `npm run capabilities:build` from capabilities/*.json. Do not edit.
2
+ // check:capabilities fails in Checks when this file is stale.
3
+ export const MANIFEST_VERSION = "8c09d0a71651";
4
+ export const capabilities = {
5
+ "ai": {
6
+ "$schema": "./schema.json",
7
+ "format": 1,
8
+ "component": "ai",
9
+ "title": "Claude through the Gateway",
10
+ "summary": "Text, structured output and streaming from Claude, metered against the project's budget. Apps never hold an Anthropic key.",
11
+ "status": "planned",
12
+ "surfaces": {
13
+ "gateway": [
14
+ "POST /anthropic/v1/messages",
15
+ "POST /anthropic/v1/messages/count_tokens"
16
+ ],
17
+ "package": [
18
+ "createAi",
19
+ "Ai.text",
20
+ "Ai.structured",
21
+ "Ai.stream",
22
+ "Ai.budget",
23
+ "createFakeAi"
24
+ ]
25
+ },
26
+ "config": [
27
+ {
28
+ "name": "WORKBENCH_GATEWAY_KEY",
29
+ "required": true,
30
+ "secret": true,
31
+ "environments": [
32
+ "staging",
33
+ "production"
34
+ ],
35
+ "description": "Project key with the ai scope, delivered as a Worker secret by gw_key_issue"
36
+ },
37
+ {
38
+ "name": "WORKBENCH_ENV",
39
+ "required": true,
40
+ "secret": false,
41
+ "environments": [
42
+ "development",
43
+ "staging",
44
+ "production"
45
+ ],
46
+ "description": "Drives key checks; never NODE_ENV"
47
+ }
48
+ ],
49
+ "allowed": {
50
+ "aliases": [
51
+ {
52
+ "alias": "claude-default",
53
+ "provider": "anthropic",
54
+ "default_model": "claude-sonnet-5-5"
55
+ },
56
+ {
57
+ "alias": "claude-best",
58
+ "provider": "anthropic",
59
+ "default_model": "claude-opus-5-5"
60
+ },
61
+ {
62
+ "alias": "claude-fast",
63
+ "provider": "anthropic",
64
+ "default_model": "claude-haiku-5-5"
65
+ }
66
+ ],
67
+ "explicit_models": [
68
+ "claude-sonnet-5",
69
+ "claude-opus-5"
70
+ ],
71
+ "server_tools": [
72
+ "web_search",
73
+ "web_fetch",
74
+ "code_execution"
75
+ ],
76
+ "required_headers": [
77
+ "X-Workbench-Job"
78
+ ]
79
+ },
80
+ "limits": [
81
+ {
82
+ "id": "ai.retry-attempts",
83
+ "value": 3,
84
+ "unit": "attempts",
85
+ "per": "request",
86
+ "enforced_by": "gateway"
87
+ }
88
+ ],
89
+ "errors": [
90
+ {
91
+ "code": "model_not_allowed",
92
+ "http": 400,
93
+ "retryable": false,
94
+ "meaning": "Not an alias or an allowlisted model id"
95
+ },
96
+ {
97
+ "code": "budget_exceeded",
98
+ "http": 402,
99
+ "retryable": false,
100
+ "meaning": "Enforced monthly budget reached"
101
+ }
102
+ ],
103
+ "rules": [
104
+ {
105
+ "id": "ai.no-partial-success",
106
+ "kind": "must_not",
107
+ "text": "Treat a refusal or a max_tokens truncation as a successful result.",
108
+ "enforced_by": "types",
109
+ "probe": "ai.text.truncation-throws"
110
+ },
111
+ {
112
+ "id": "ai.store-resolved-model",
113
+ "kind": "must",
114
+ "text": "Store the resolved model and request id with any output kept.",
115
+ "enforced_by": "review",
116
+ "probe": "ai.text.claude-default"
117
+ },
118
+ {
119
+ "id": "ai.aliases-only",
120
+ "kind": "must",
121
+ "text": "Ask for a Workbench alias (or an allowlisted explicit id), never an arbitrary model id.",
122
+ "enforced_by": "gateway",
123
+ "probe": "ai.model-not-allowed"
124
+ }
125
+ ],
126
+ "examples": [
127
+ {
128
+ "id": "ai.example.text",
129
+ "title": "One-shot text",
130
+ "lang": "ts",
131
+ "code": "const ai = createAi({ key: env.WORKBENCH_GATEWAY_KEY, env: workbenchEnv(env) })\nconst r = await ai.text({ model: 'claude-default', job: 'article.write', prompt })\nawait save({ text: r.text, model: r.model, requestId: r.requestId })",
132
+ "probe": "ai.text.claude-default"
133
+ }
134
+ ],
135
+ "probes": [
136
+ {
137
+ "id": "ai.text.claude-default",
138
+ "half": "runtime",
139
+ "expect": "pass",
140
+ "smoke": true,
141
+ "cost_hint_usd": 0.002
142
+ },
143
+ {
144
+ "id": "ai.text.claude-best",
145
+ "half": "runtime",
146
+ "expect": "pass",
147
+ "cost_hint_usd": 0.004
148
+ },
149
+ {
150
+ "id": "ai.text.claude-fast",
151
+ "half": "runtime",
152
+ "expect": "pass",
153
+ "cost_hint_usd": 0.001
154
+ },
155
+ {
156
+ "id": "ai.stream",
157
+ "half": "runtime",
158
+ "expect": "pass",
159
+ "cost_hint_usd": 0.002
160
+ },
161
+ {
162
+ "id": "ai.structured",
163
+ "half": "runtime",
164
+ "expect": "pass",
165
+ "cost_hint_usd": 0.003
166
+ },
167
+ {
168
+ "id": "ai.text.truncation-throws",
169
+ "half": "runtime",
170
+ "expect": "pass",
171
+ "cost_hint_usd": 0.001
172
+ },
173
+ {
174
+ "id": "ai.model-not-allowed",
175
+ "half": "runtime",
176
+ "expect": "refused_as_expected",
177
+ "refusal": "model_not_allowed",
178
+ "cost_hint_usd": 0
179
+ },
180
+ {
181
+ "id": "ai.count-tokens",
182
+ "half": "runtime",
183
+ "expect": "pass",
184
+ "smoke": true,
185
+ "cost_hint_usd": 0
186
+ }
187
+ ]
188
+ },
189
+ "config": {
190
+ "$schema": "./schema.json",
191
+ "format": 1,
192
+ "component": "config",
193
+ "title": "App configuration with workbench-config",
194
+ "summary": "Reads WORKBENCH_ENV and the app's own variables and secrets. A missing or invalid variable fails at startup, by name, and no value is ever printed.",
195
+ "status": "built",
196
+ "surfaces": {
197
+ "package": [
198
+ "workbenchEnv",
199
+ "defineConfig",
200
+ "sharedSecret",
201
+ "workerSecret",
202
+ "redact",
203
+ "redactAll",
204
+ "WorkbenchConfigError",
205
+ "capabilitySchema",
206
+ "capabilities",
207
+ "MANIFEST_VERSION",
208
+ "compareDevVars"
209
+ ]
210
+ },
211
+ "config": [
212
+ {
213
+ "name": "WORKBENCH_ENV",
214
+ "required": true,
215
+ "secret": false,
216
+ "environments": [
217
+ "development",
218
+ "staging",
219
+ "production"
220
+ ],
221
+ "description": "development, staging or production. Drives key checks and the email transport; never NODE_ENV (audit F9)"
222
+ }
223
+ ],
224
+ "allowed": {
225
+ "environments": [
226
+ "development",
227
+ "staging",
228
+ "production"
229
+ ]
230
+ },
231
+ "rules": [
232
+ {
233
+ "id": "config.fail-by-name",
234
+ "kind": "must",
235
+ "text": "Fail at startup when a required variable is missing or invalid, naming every bad variable in one error.",
236
+ "enforced_by": "types",
237
+ "probe": "config.missing-variable-named"
238
+ },
239
+ {
240
+ "id": "config.never-print-values",
241
+ "kind": "must_not",
242
+ "text": "Print, log or include in an error the value of any variable, valid or not.",
243
+ "enforced_by": "types",
244
+ "probe": "config.value-never-printed"
245
+ },
246
+ {
247
+ "id": "config.no-node-env",
248
+ "kind": "must_not",
249
+ "text": "Decide the environment from NODE_ENV or a default. WORKBENCH_ENV is required and has no fallback.",
250
+ "enforced_by": "types",
251
+ "probe": "config.env-required"
252
+ },
253
+ {
254
+ "id": "config.dev-vars-example",
255
+ "kind": "must",
256
+ "text": "Keep .dev.vars.example listing every variable .dev.vars has, with no values; check with npx workbench-config check.",
257
+ "enforced_by": "review",
258
+ "probe": "config.dev-vars-check"
259
+ }
260
+ ],
261
+ "examples": [
262
+ {
263
+ "id": "config.example.define",
264
+ "title": "Declare and read the app's variables",
265
+ "lang": "ts",
266
+ "code": "import { z } from 'zod'\nimport { defineConfig, workbenchEnv } from '@mccaskie-labs/workbench-config'\n\nconst config = defineConfig({\n WORKBENCH_GATEWAY_KEY: z.string().startsWith('wbk_'),\n SITE_NAME: z.string().default('wb-probe'),\n})\n\nexport default {\n async fetch(request: Request, env: Env) {\n const stage = workbenchEnv(env)\n const { WORKBENCH_GATEWAY_KEY, SITE_NAME } = config.parse(env)\n return new Response(`${SITE_NAME} on ${stage}`)\n },\n}",
267
+ "probe": "config.missing-variable-named"
268
+ }
269
+ ],
270
+ "probes": [
271
+ {
272
+ "id": "config.missing-variable-named",
273
+ "half": "runtime",
274
+ "expect": "pass",
275
+ "smoke": true,
276
+ "cost_hint_usd": 0
277
+ },
278
+ {
279
+ "id": "config.value-never-printed",
280
+ "half": "runtime",
281
+ "expect": "pass",
282
+ "smoke": true,
283
+ "cost_hint_usd": 0
284
+ },
285
+ {
286
+ "id": "config.env-required",
287
+ "half": "runtime",
288
+ "expect": "pass",
289
+ "cost_hint_usd": 0
290
+ },
291
+ {
292
+ "id": "config.dev-vars-check",
293
+ "half": "runtime",
294
+ "expect": "pass",
295
+ "cost_hint_usd": 0,
296
+ "note": "Manual: run npx workbench-config check in the project"
297
+ }
298
+ ]
299
+ },
300
+ "gateway": {
301
+ "$schema": "./schema.json",
302
+ "format": 1,
303
+ "component": "gateway",
304
+ "title": "Workbench Gateway: keys, headers, budgets and errors",
305
+ "summary": "What every Gateway endpoint shares: project keys by environment and scope, the request and response headers, the monthly budget check and the error codes. Apps hold one Workbench key and never a vendor key.",
306
+ "status": "planned",
307
+ "surfaces": {
308
+ "gateway": [
309
+ "GET /v1/health",
310
+ "GET /v1/usage",
311
+ "GET /v1/capabilities"
312
+ ],
313
+ "package": [
314
+ "DEFAULT_GATEWAY_URL"
315
+ ]
316
+ },
317
+ "config": [
318
+ {
319
+ "name": "WORKBENCH_GATEWAY_KEY",
320
+ "required": true,
321
+ "secret": true,
322
+ "environments": [
323
+ "staging",
324
+ "production"
325
+ ],
326
+ "description": "The project's Gateway key for this environment, delivered as a Worker secret by gw_key_issue. Never in code, .dev.vars.example or logs"
327
+ },
328
+ {
329
+ "name": "WORKBENCH_GATEWAY_URL",
330
+ "required": false,
331
+ "secret": false,
332
+ "default": "https://gateway.mccaskielabs.dev",
333
+ "environments": [
334
+ "development",
335
+ "staging",
336
+ "production"
337
+ ],
338
+ "description": "Gateway base URL. Leave unset outside tests"
339
+ }
340
+ ],
341
+ "allowed": {
342
+ "scopes": [
343
+ "ai",
344
+ "images",
345
+ "search",
346
+ "email"
347
+ ],
348
+ "key_environments": [
349
+ {
350
+ "environment": "development",
351
+ "prefix": "wbk_dev_"
352
+ },
353
+ {
354
+ "environment": "staging",
355
+ "prefix": "wbk_stg_"
356
+ },
357
+ {
358
+ "environment": "production",
359
+ "prefix": "wbk_prd_"
360
+ }
361
+ ],
362
+ "request_headers": [
363
+ {
364
+ "name": "Authorization",
365
+ "required": true,
366
+ "description": "Bearer <key>. On /anthropic/* the key goes in x-api-key instead, so the official SDK works unchanged"
367
+ },
368
+ {
369
+ "name": "X-Workbench-Job",
370
+ "required": true,
371
+ "pattern": "^[a-z0-9._-]{1,64}$",
372
+ "description": "What the call is for, e.g. article.write. Recorded with usage"
373
+ },
374
+ {
375
+ "name": "X-Workbench-Prompt-Version",
376
+ "required": false,
377
+ "description": "The app's prompt version, recorded with usage (audit F17)"
378
+ },
379
+ {
380
+ "name": "X-Workbench-Subject",
381
+ "required": false,
382
+ "pattern": "^[a-z0-9_-]+:[^\\s]{1,128}$",
383
+ "description": "type:id of the thing the call is about"
384
+ },
385
+ {
386
+ "name": "Idempotency-Key",
387
+ "required": false,
388
+ "description": "Email only: a repeat within 24 hours returns the stored result"
389
+ }
390
+ ],
391
+ "response_headers": [
392
+ "X-Workbench-Request-Id",
393
+ "Server-Timing"
394
+ ],
395
+ "server_timing_metrics": [
396
+ "gw",
397
+ "vendor_fb",
398
+ "vendor",
399
+ "total"
400
+ ]
401
+ },
402
+ "limits": [
403
+ {
404
+ "id": "gateway.key-cache",
405
+ "value": 60,
406
+ "unit": "seconds",
407
+ "per": "isolate",
408
+ "enforced_by": "gateway"
409
+ },
410
+ {
411
+ "id": "gateway.budget-cache",
412
+ "value": 60,
413
+ "unit": "seconds",
414
+ "per": "isolate",
415
+ "enforced_by": "gateway"
416
+ },
417
+ {
418
+ "id": "gateway.job-format",
419
+ "value": "[a-z0-9._-]{1,64}",
420
+ "unit": "pattern",
421
+ "per": "request",
422
+ "enforced_by": "gateway",
423
+ "refusal": "invalid_request"
424
+ }
425
+ ],
426
+ "errors": [
427
+ {
428
+ "code": "unauthorised",
429
+ "http": 401,
430
+ "retryable": false,
431
+ "meaning": "No key, a malformed or unknown key, a revoked key, or a key from the wrong environment"
432
+ },
433
+ {
434
+ "code": "forbidden_scope",
435
+ "http": 403,
436
+ "retryable": false,
437
+ "meaning": "The key doesn't carry the scope this endpoint needs"
438
+ },
439
+ {
440
+ "code": "invalid_request",
441
+ "http": 400,
442
+ "retryable": false,
443
+ "meaning": "The body or a required header is missing or malformed"
444
+ },
445
+ {
446
+ "code": "budget_exceeded",
447
+ "http": 402,
448
+ "retryable": false,
449
+ "meaning": "The environment's enforced monthly budget, or the global ceiling, is reached"
450
+ },
451
+ {
452
+ "code": "rate_limited",
453
+ "http": 429,
454
+ "retryable": true,
455
+ "meaning": "Per-key rate limit; it stops runaway loops, budgets limit spend"
456
+ },
457
+ {
458
+ "code": "not_available",
459
+ "http": 501,
460
+ "retryable": false,
461
+ "meaning": "Not built yet, e.g. bulk email"
462
+ },
463
+ {
464
+ "code": "upstream_error",
465
+ "http": 502,
466
+ "retryable": true,
467
+ "meaning": "The vendor failed after the Gateway's retries"
468
+ },
469
+ {
470
+ "code": "upstream_timeout",
471
+ "http": 504,
472
+ "retryable": true,
473
+ "meaning": "The vendor didn't answer in time"
474
+ }
475
+ ],
476
+ "rules": [
477
+ {
478
+ "id": "gateway.no-vendor-keys",
479
+ "kind": "must_not",
480
+ "text": "Hold a vendor key (Anthropic, Google, OpenAI, Serper, email) in an app. The Gateway key is the only Workbench credential an app has.",
481
+ "enforced_by": "review",
482
+ "probe": "gateway.no-vendor-keys.scan"
483
+ },
484
+ {
485
+ "id": "gateway.key-per-environment",
486
+ "kind": "must",
487
+ "text": "Use the key issued for the Worker's own environment. A staging key is refused by production and the other way round.",
488
+ "enforced_by": "gateway",
489
+ "probe": "gateway.wrong-environment"
490
+ },
491
+ {
492
+ "id": "gateway.request-id-kept",
493
+ "kind": "must",
494
+ "text": "Keep X-Workbench-Request-Id with any output stored or any error logged, so it can be matched to a usage row.",
495
+ "enforced_by": "review",
496
+ "probe": "gateway.health"
497
+ }
498
+ ],
499
+ "probes": [
500
+ {
501
+ "id": "gateway.health",
502
+ "half": "runtime",
503
+ "expect": "pass",
504
+ "smoke": true,
505
+ "cost_hint_usd": 0
506
+ },
507
+ {
508
+ "id": "gateway.bad-key",
509
+ "half": "runtime",
510
+ "expect": "refused_as_expected",
511
+ "refusal": "unauthorised",
512
+ "smoke": true,
513
+ "cost_hint_usd": 0
514
+ },
515
+ {
516
+ "id": "gateway.wrong-scope",
517
+ "half": "runtime",
518
+ "expect": "refused_as_expected",
519
+ "refusal": "forbidden_scope",
520
+ "cost_hint_usd": 0
521
+ },
522
+ {
523
+ "id": "gateway.wrong-environment",
524
+ "half": "runtime",
525
+ "expect": "refused_as_expected",
526
+ "refusal": "unauthorised",
527
+ "cost_hint_usd": 0
528
+ },
529
+ {
530
+ "id": "gateway.revoked-key",
531
+ "half": "runtime",
532
+ "expect": "refused_as_expected",
533
+ "refusal": "unauthorised",
534
+ "cost_hint_usd": 0,
535
+ "note": "Stops within 60 seconds of revocation"
536
+ },
537
+ {
538
+ "id": "gateway.budget-enforced",
539
+ "half": "runtime",
540
+ "expect": "refused_as_expected",
541
+ "refusal": "budget_exceeded",
542
+ "cost_hint_usd": 0.01,
543
+ "note": "Uses the development key's US$0.01 enforce budget (docs/phase-4-plan.md §3 Q10)"
544
+ },
545
+ {
546
+ "id": "gateway.no-vendor-keys.scan",
547
+ "half": "runtime",
548
+ "expect": "pass",
549
+ "cost_hint_usd": 0,
550
+ "note": "Manual: grep of the repo and cf_worker_secret_list show only WORKBENCH_GATEWAY_* secrets"
551
+ }
552
+ ]
553
+ }
554
+ };