@mccaskie-labs/workbench-config 0.1.2 → 0.1.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @mccaskie-labs/workbench-config
2
2
 
3
+ ## 0.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - The `gateway` manifest is now `built` (step 4.5): the `X-Workbench-Env` request header, the per-key rate limit (300 a minute), the `not_found` and `internal_error` codes, and the `gateway.server-timing` probe.
8
+
3
9
  ## 0.1.2
4
10
 
5
11
  ### Patch Changes
@@ -4,7 +4,7 @@
4
4
  "component": "gateway",
5
5
  "title": "Workbench Gateway: keys, headers, budgets and errors",
6
6
  "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.",
7
- "status": "planned",
7
+ "status": "built",
8
8
  "surfaces": {
9
9
  "gateway": ["GET /v1/health", "GET /v1/usage", "GET /v1/capabilities"],
10
10
  "package": ["DEFAULT_GATEWAY_URL"]
@@ -38,7 +38,8 @@
38
38
  { "name": "X-Workbench-Job", "required": true, "pattern": "^[a-z0-9._-]{1,64}$", "description": "What the call is for, e.g. article.write. Recorded with usage" },
39
39
  { "name": "X-Workbench-Prompt-Version", "required": false, "description": "The app's prompt version, recorded with usage (audit F17)" },
40
40
  { "name": "X-Workbench-Subject", "required": false, "pattern": "^[a-z0-9_-]+:[^\\s]{1,128}$", "description": "type:id of the thing the call is about" },
41
- { "name": "Idempotency-Key", "required": false, "description": "Email only: a repeat within 24 hours returns the stored result" }
41
+ { "name": "Idempotency-Key", "required": false, "description": "Email only: a repeat within 24 hours returns the stored result" },
42
+ { "name": "X-Workbench-Env", "required": false, "pattern": "^(development|staging|production)$", "description": "The caller's WORKBENCH_ENV. The packages send it; a key from another environment is then refused with unauthorised" }
42
43
  ],
43
44
  "response_headers": ["X-Workbench-Request-Id", "Server-Timing"],
44
45
  "server_timing_metrics": ["gw", "vendor_fb", "vendor", "total"]
@@ -46,6 +47,7 @@
46
47
  "limits": [
47
48
  { "id": "gateway.key-cache", "value": 60, "unit": "seconds", "per": "isolate", "enforced_by": "gateway" },
48
49
  { "id": "gateway.budget-cache", "value": 60, "unit": "seconds", "per": "isolate", "enforced_by": "gateway" },
50
+ { "id": "gateway.rate-limit", "value": 300, "unit": "requests", "per": "minute per key", "enforced_by": "gateway", "refusal": "rate_limited" },
49
51
  { "id": "gateway.job-format", "value": "[a-z0-9._-]{1,64}", "unit": "pattern", "per": "request", "enforced_by": "gateway", "refusal": "invalid_request" }
50
52
  ],
51
53
  "errors": [
@@ -56,15 +58,19 @@
56
58
  { "code": "rate_limited", "http": 429, "retryable": true, "meaning": "Per-key rate limit; it stops runaway loops, budgets limit spend" },
57
59
  { "code": "not_available", "http": 501, "retryable": false, "meaning": "Not built yet, e.g. bulk email" },
58
60
  { "code": "upstream_error", "http": 502, "retryable": true, "meaning": "The vendor failed after the Gateway's retries" },
59
- { "code": "upstream_timeout", "http": 504, "retryable": true, "meaning": "The vendor didn't answer in time" }
61
+ { "code": "upstream_timeout", "http": 504, "retryable": true, "meaning": "The vendor didn't answer in time" },
62
+ { "code": "not_found", "http": 404, "retryable": false, "meaning": "No such path. Every documented endpoint checks the key and scope first, even one not built yet" },
63
+ { "code": "internal_error", "http": 500, "retryable": true, "meaning": "The Gateway failed, for example it couldn't read the platform database; nothing was sent to a vendor" }
60
64
  ],
61
65
  "rules": [
62
66
  { "id": "gateway.no-vendor-keys", "kind": "must_not", "text": "Hold a vendor key (Anthropic, Google, OpenAI, Serper, email) in an app. The Gateway key is the only Workbench credential an app has.", "enforced_by": "review", "probe": "gateway.no-vendor-keys.scan" },
63
67
  { "id": "gateway.key-per-environment", "kind": "must", "text": "Use the key issued for the Worker's own environment. A staging key is refused by production and the other way round.", "enforced_by": "gateway", "probe": "gateway.wrong-environment" },
68
+ { "id": "gateway.timing-headers", "kind": "must", "text": "Every Gateway response carries X-Workbench-Request-Id and Server-Timing with gw, vendor_fb, vendor and total, refusals included.", "enforced_by": "gateway", "probe": "gateway.server-timing" },
64
69
  { "id": "gateway.request-id-kept", "kind": "must", "text": "Keep X-Workbench-Request-Id with any output stored or any error logged, so it can be matched to a usage row.", "enforced_by": "review", "probe": "gateway.health" }
65
70
  ],
66
71
  "probes": [
67
72
  { "id": "gateway.health", "half": "runtime", "expect": "pass", "smoke": true, "cost_hint_usd": 0 },
73
+ { "id": "gateway.server-timing", "half": "runtime", "expect": "pass", "smoke": true, "cost_hint_usd": 0 },
68
74
  { "id": "gateway.bad-key", "half": "runtime", "expect": "refused_as_expected", "refusal": "unauthorised", "smoke": true, "cost_hint_usd": 0 },
69
75
  { "id": "gateway.wrong-scope", "half": "runtime", "expect": "refused_as_expected", "refusal": "forbidden_scope", "cost_hint_usd": 0 },
70
76
  { "id": "gateway.wrong-environment", "half": "runtime", "expect": "refused_as_expected", "refusal": "unauthorised", "cost_hint_usd": 0 },
@@ -1,4 +1,4 @@
1
- export declare const MANIFEST_VERSION: "ce5a8067dcd6";
1
+ export declare const MANIFEST_VERSION: "491c578e91bc";
2
2
  export declare const capabilities: {
3
3
  readonly ai: {
4
4
  readonly $schema: "./schema.json";
@@ -214,7 +214,7 @@ export declare const capabilities: {
214
214
  readonly component: "gateway";
215
215
  readonly title: "Workbench Gateway: keys, headers, budgets and errors";
216
216
  readonly 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.";
217
- readonly status: "planned";
217
+ readonly status: "built";
218
218
  readonly surfaces: {
219
219
  readonly gateway: readonly ["GET /v1/health", "GET /v1/usage", "GET /v1/capabilities"];
220
220
  readonly package: readonly ["DEFAULT_GATEWAY_URL"];
@@ -267,6 +267,11 @@ export declare const capabilities: {
267
267
  readonly name: "Idempotency-Key";
268
268
  readonly required: false;
269
269
  readonly description: "Email only: a repeat within 24 hours returns the stored result";
270
+ }, {
271
+ readonly name: "X-Workbench-Env";
272
+ readonly required: false;
273
+ readonly pattern: "^(development|staging|production)$";
274
+ readonly description: "The caller's WORKBENCH_ENV. The packages send it; a key from another environment is then refused with unauthorised";
270
275
  }];
271
276
  readonly response_headers: readonly ["X-Workbench-Request-Id", "Server-Timing"];
272
277
  readonly server_timing_metrics: readonly ["gw", "vendor_fb", "vendor", "total"];
@@ -283,6 +288,13 @@ export declare const capabilities: {
283
288
  readonly unit: "seconds";
284
289
  readonly per: "isolate";
285
290
  readonly enforced_by: "gateway";
291
+ }, {
292
+ readonly id: "gateway.rate-limit";
293
+ readonly value: 300;
294
+ readonly unit: "requests";
295
+ readonly per: "minute per key";
296
+ readonly enforced_by: "gateway";
297
+ readonly refusal: "rate_limited";
286
298
  }, {
287
299
  readonly id: "gateway.job-format";
288
300
  readonly value: "[a-z0-9._-]{1,64}";
@@ -331,6 +343,16 @@ export declare const capabilities: {
331
343
  readonly http: 504;
332
344
  readonly retryable: true;
333
345
  readonly meaning: "The vendor didn't answer in time";
346
+ }, {
347
+ readonly code: "not_found";
348
+ readonly http: 404;
349
+ readonly retryable: false;
350
+ readonly meaning: "No such path. Every documented endpoint checks the key and scope first, even one not built yet";
351
+ }, {
352
+ readonly code: "internal_error";
353
+ readonly http: 500;
354
+ readonly retryable: true;
355
+ readonly meaning: "The Gateway failed, for example it couldn't read the platform database; nothing was sent to a vendor";
334
356
  }];
335
357
  readonly rules: readonly [{
336
358
  readonly id: "gateway.no-vendor-keys";
@@ -344,6 +366,12 @@ export declare const capabilities: {
344
366
  readonly text: "Use the key issued for the Worker's own environment. A staging key is refused by production and the other way round.";
345
367
  readonly enforced_by: "gateway";
346
368
  readonly probe: "gateway.wrong-environment";
369
+ }, {
370
+ readonly id: "gateway.timing-headers";
371
+ readonly kind: "must";
372
+ readonly text: "Every Gateway response carries X-Workbench-Request-Id and Server-Timing with gw, vendor_fb, vendor and total, refusals included.";
373
+ readonly enforced_by: "gateway";
374
+ readonly probe: "gateway.server-timing";
347
375
  }, {
348
376
  readonly id: "gateway.request-id-kept";
349
377
  readonly kind: "must";
@@ -357,6 +385,12 @@ export declare const capabilities: {
357
385
  readonly expect: "pass";
358
386
  readonly smoke: true;
359
387
  readonly cost_hint_usd: 0;
388
+ }, {
389
+ readonly id: "gateway.server-timing";
390
+ readonly half: "runtime";
391
+ readonly expect: "pass";
392
+ readonly smoke: true;
393
+ readonly cost_hint_usd: 0;
360
394
  }, {
361
395
  readonly id: "gateway.bad-key";
362
396
  readonly half: "runtime";
@@ -1,6 +1,6 @@
1
1
  // Generated by `npm run capabilities:build` from capabilities/*.json. Do not edit.
2
2
  // check:capabilities fails in Checks when this file is stale.
3
- export const MANIFEST_VERSION = "ce5a8067dcd6";
3
+ export const MANIFEST_VERSION = "491c578e91bc";
4
4
  export const capabilities = {
5
5
  "ai": {
6
6
  "$schema": "./schema.json",
@@ -303,7 +303,7 @@ export const capabilities = {
303
303
  "component": "gateway",
304
304
  "title": "Workbench Gateway: keys, headers, budgets and errors",
305
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",
306
+ "status": "built",
307
307
  "surfaces": {
308
308
  "gateway": [
309
309
  "GET /v1/health",
@@ -386,6 +386,12 @@ export const capabilities = {
386
386
  "name": "Idempotency-Key",
387
387
  "required": false,
388
388
  "description": "Email only: a repeat within 24 hours returns the stored result"
389
+ },
390
+ {
391
+ "name": "X-Workbench-Env",
392
+ "required": false,
393
+ "pattern": "^(development|staging|production)$",
394
+ "description": "The caller's WORKBENCH_ENV. The packages send it; a key from another environment is then refused with unauthorised"
389
395
  }
390
396
  ],
391
397
  "response_headers": [
@@ -414,6 +420,14 @@ export const capabilities = {
414
420
  "per": "isolate",
415
421
  "enforced_by": "gateway"
416
422
  },
423
+ {
424
+ "id": "gateway.rate-limit",
425
+ "value": 300,
426
+ "unit": "requests",
427
+ "per": "minute per key",
428
+ "enforced_by": "gateway",
429
+ "refusal": "rate_limited"
430
+ },
417
431
  {
418
432
  "id": "gateway.job-format",
419
433
  "value": "[a-z0-9._-]{1,64}",
@@ -471,6 +485,18 @@ export const capabilities = {
471
485
  "http": 504,
472
486
  "retryable": true,
473
487
  "meaning": "The vendor didn't answer in time"
488
+ },
489
+ {
490
+ "code": "not_found",
491
+ "http": 404,
492
+ "retryable": false,
493
+ "meaning": "No such path. Every documented endpoint checks the key and scope first, even one not built yet"
494
+ },
495
+ {
496
+ "code": "internal_error",
497
+ "http": 500,
498
+ "retryable": true,
499
+ "meaning": "The Gateway failed, for example it couldn't read the platform database; nothing was sent to a vendor"
474
500
  }
475
501
  ],
476
502
  "rules": [
@@ -488,6 +514,13 @@ export const capabilities = {
488
514
  "enforced_by": "gateway",
489
515
  "probe": "gateway.wrong-environment"
490
516
  },
517
+ {
518
+ "id": "gateway.timing-headers",
519
+ "kind": "must",
520
+ "text": "Every Gateway response carries X-Workbench-Request-Id and Server-Timing with gw, vendor_fb, vendor and total, refusals included.",
521
+ "enforced_by": "gateway",
522
+ "probe": "gateway.server-timing"
523
+ },
491
524
  {
492
525
  "id": "gateway.request-id-kept",
493
526
  "kind": "must",
@@ -504,6 +537,13 @@ export const capabilities = {
504
537
  "smoke": true,
505
538
  "cost_hint_usd": 0
506
539
  },
540
+ {
541
+ "id": "gateway.server-timing",
542
+ "half": "runtime",
543
+ "expect": "pass",
544
+ "smoke": true,
545
+ "cost_hint_usd": 0
546
+ },
507
547
  {
508
548
  "id": "gateway.bad-key",
509
549
  "half": "runtime",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mccaskie-labs/workbench-config",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Workbench app configuration: WORKBENCH_ENV, typed variables that fail by name, secrets that are never printed, and the capabilities manifest.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",