@openwop/spec-artifacts 2.44.5 → 2.44.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CORPUS-STAMP.json +49 -47
  2. package/api/seams-v2.yaml +1 -1
  3. package/api/v2/asyncapi.yaml +1 -1
  4. package/api/v2/openapi.yaml +31 -122
  5. package/package.json +1 -1
  6. package/schemas/v2/agent-ref.schema.json +2 -3
  7. package/schemas/v2/budget-policy.schema.json +2 -3
  8. package/schemas/v2/capabilities.schema.json +42 -38
  9. package/schemas/v2/debug-bundle.schema.json +2 -2
  10. package/schemas/v2/error-envelope.schema.json +282 -4
  11. package/schemas/v2/node-pack-manifest.schema.json +2 -2
  12. package/schemas/v2/prompt-ref.schema.json +2 -3
  13. package/schemas/v2/prompt-template.schema.json +3 -3
  14. package/schemas/v2/run-event-payloads.schema.json +5 -5
  15. package/spec/v1/alias-detectors.json +25 -1
  16. package/spec/v1/core-standard-manifest.json +2 -2
  17. package/spec/v1/deprecations.json +89 -1
  18. package/spec/v1/gaps.json +46 -12
  19. package/spec/v1/migrations.json +57 -1
  20. package/spec/v2/core/artifact-type-packs.md +5 -1
  21. package/spec/v2/core/capabilities.md +20 -22
  22. package/spec/v2/core/conformance.md +24 -7
  23. package/spec/v2/core/errors.md +17 -119
  24. package/spec/v2/core/events.md +7 -7
  25. package/spec/v2/core/execution.md +56 -16
  26. package/spec/v2/core/host-services.md +57 -58
  27. package/spec/v2/core/i18n.md +10 -10
  28. package/spec/v2/core/idempotency.md +3 -3
  29. package/spec/v2/core/identity.md +11 -9
  30. package/spec/v2/core/interop.md +6 -8
  31. package/spec/v2/core/interrupt.md +8 -8
  32. package/spec/v2/core/oauth.md +4 -4
  33. package/spec/v2/core/packs.md +11 -5
  34. package/spec/v2/core/persistence.md +11 -13
  35. package/spec/v2/core/portability.md +4 -4
  36. package/spec/v2/core/replay.md +8 -10
  37. package/spec/v2/core/runs.md +20 -7
  38. package/spec/v2/core/security-defaults.md +0 -2
  39. package/spec/v2/core/storage.md +18 -20
  40. package/spec/v2/core/versioning.md +4 -9
  41. package/spec/v2/core/webhooks.md +13 -8
  42. package/spec/v2/declaration.json +3 -3
  43. package/spec/v2/errors.json +206 -2
  44. package/spec/v2/facets/agents.schema.json +367 -0
  45. package/spec/v2/facets/aiProviders.schema.json +2 -2
  46. package/spec/v2/generated/error-codes.md +130 -0
  47. package/spec/v2/release.json +2 -2
  48. package/spec/v2/surface-baseline.json +38 -2
package/spec/v1/gaps.json CHANGED
@@ -7279,8 +7279,8 @@
7279
7279
  "surface": "`openwop.requirement.replay.suppression-execution-ordinal` (openwop#1718) has no witness on any host, positive or negative. `conformance-replay-ordinal-loop` needs a host that both runs cycles and suppresses a side-effecting node in replay. The v2 reference host does neither for this fixture: it does not loop, it does not execute `core.conformance.side-effect`, and its `effectOutcome` (`store.ts:586` at openwop-examples `f56f092`) selects the latest outcome regardless of ordinal, which would fail the row if it looped. openwop-app does not index by ordinal (`nodeAttempts` is never reset), and MyndHyve does not seed `replaySourceOutcomes` from the prefix.",
7280
7280
  "witness": "unclassified",
7281
7281
  "requirementId": null,
7282
- "disposition": "externally-gated",
7283
- "target": "host-loops-with-replay-suppression",
7282
+ "disposition": "closed",
7283
+ "target": null,
7284
7284
  "sources": [
7285
7285
  {
7286
7286
  "file": "RFCS/registers/0140-replay-side-effect-suppression.gaps.md",
@@ -10688,6 +10688,23 @@
10688
10688
  }
10689
10689
  ]
10690
10690
  },
10691
+ {
10692
+ "id": "openwop.gap.0189.17",
10693
+ "rfc": "0189",
10694
+ "local": "G17",
10695
+ "section": "—",
10696
+ "surface": "`spec/v1/multi-agent-execution.md` (RFC 0039 MAE-2) names an opt-in `memoryScopeIsolation: \"isolated\"` field on the dispatch config, but no v1 schema ever defined it. Found while restating `multiAgent` levels 2–6 in `execution.md` (#1802).",
10697
+ "witness": "unclassified",
10698
+ "requirementId": null,
10699
+ "disposition": "closed",
10700
+ "target": null,
10701
+ "sources": [
10702
+ {
10703
+ "file": "RFCS/registers/0189-normative-home.gaps.md",
10704
+ "token": "G17"
10705
+ }
10706
+ ]
10707
+ },
10691
10708
  {
10692
10709
  "id": "openwop.gap.0190.1",
10693
10710
  "rfc": "0190",
@@ -13433,8 +13450,8 @@
13433
13450
  "surface": "*(Renumbered from a second `G9` on 2026-09-28: two rows shared the local ID, so `spec/v1/gaps.json` carried `openwop.gap.0223.9` twice.)* `openwop.requirement.0223.reject-loopback-reasks` (openwop #1697) has no positive witness on any host. The v2 reference host does not run cycles. MyndHyve runs request-changes loops over condition edges on a successful gate output, and does not route a failed node over `any_failed` (its Cloud Run executor has no `triggerRule` handling), so the row stays `inapplicable` there. openwop-app rejects cycles. The row exists and is negatively controlled: registering the fixture on a host that does not loop records `executed-fail`.",
13434
13451
  "witness": "unclassified",
13435
13452
  "requirementId": null,
13436
- "disposition": "externally-gated",
13437
- "target": "host-routes-failed-gate-over-cycle",
13453
+ "disposition": "closed",
13454
+ "target": null,
13438
13455
  "sources": [
13439
13456
  {
13440
13457
  "file": "RFCS/registers/0223-approval-reject-disposition.gaps.md",
@@ -13510,6 +13527,23 @@
13510
13527
  }
13511
13528
  ]
13512
13529
  },
13530
+ {
13531
+ "id": "openwop.gap.0227.1",
13532
+ "rfc": "0227",
13533
+ "local": "G1",
13534
+ "section": "§A",
13535
+ "surface": "Other registries with generated restatements inside `spec/v2/core/` (for example `headers.md`, which `derive-v2-api.py` writes) are not moved by this RFC.",
13536
+ "witness": "unclassified",
13537
+ "requirementId": null,
13538
+ "disposition": "closed",
13539
+ "target": null,
13540
+ "sources": [
13541
+ {
13542
+ "file": "RFCS/registers/0227-generated-registry-tables-outside-the-budget.gaps.md",
13543
+ "token": "G1"
13544
+ }
13545
+ ]
13546
+ },
13513
13547
  {
13514
13548
  "id": "openwop.gap.0228.1",
13515
13549
  "rfc": "0228",
@@ -13519,7 +13553,7 @@
13519
13553
  "witness": "unclassified",
13520
13554
  "requirementId": null,
13521
13555
  "disposition": "externally-gated",
13522
- "target": "rfc-0228-active",
13556
+ "target": "myndhyve-host-cut",
13523
13557
  "sources": [
13524
13558
  {
13525
13559
  "file": "RFCS/registers/0228-v1-host-service-error-codes.gaps.md",
@@ -13536,7 +13570,7 @@
13536
13570
  "witness": "unclassified",
13537
13571
  "requirementId": null,
13538
13572
  "disposition": "externally-gated",
13539
- "target": "rfc-0228-active",
13573
+ "target": "v2-reference-recut",
13540
13574
  "sources": [
13541
13575
  {
13542
13576
  "file": "RFCS/registers/0228-v1-host-service-error-codes.gaps.md",
@@ -13553,7 +13587,7 @@
13553
13587
  "witness": "unclassified",
13554
13588
  "requirementId": null,
13555
13589
  "disposition": "externally-gated",
13556
- "target": "rfc-0228-active",
13590
+ "target": "openwop-app-host-cut",
13557
13591
  "sources": [
13558
13592
  {
13559
13593
  "file": "RFCS/registers/0228-v1-host-service-error-codes.gaps.md",
@@ -13570,7 +13604,7 @@
13570
13604
  "witness": "unclassified",
13571
13605
  "requirementId": null,
13572
13606
  "disposition": "externally-gated",
13573
- "target": "rfc-0228-active",
13607
+ "target": "v2-host-service-ports",
13574
13608
  "sources": [
13575
13609
  {
13576
13610
  "file": "RFCS/registers/0228-v1-host-service-error-codes.gaps.md",
@@ -13604,7 +13638,7 @@
13604
13638
  "witness": "unclassified",
13605
13639
  "requirementId": null,
13606
13640
  "disposition": "externally-gated",
13607
- "target": "rfc-0228-active",
13641
+ "target": "openwop-app-host-cut",
13608
13642
  "sources": [
13609
13643
  {
13610
13644
  "file": "RFCS/registers/0228-v1-host-service-error-codes.gaps.md",
@@ -13621,7 +13655,7 @@
13621
13655
  "witness": "unclassified",
13622
13656
  "requirementId": null,
13623
13657
  "disposition": "externally-gated",
13624
- "target": "rfc-0228-active",
13658
+ "target": "myndhyve-host-cut",
13625
13659
  "sources": [
13626
13660
  {
13627
13661
  "file": "RFCS/registers/0228-v1-host-service-error-codes.gaps.md",
@@ -13637,8 +13671,8 @@
13637
13671
  "surface": "`api/v2/openapi.yaml` is derived from the v1 document by `scripts/derive-v2-api.py`, which copies v1's `501` responses and the descriptions naming unregistered codes.",
13638
13672
  "witness": "unclassified",
13639
13673
  "requirementId": null,
13640
- "disposition": "externally-gated",
13641
- "target": "rfc-0228-active",
13674
+ "disposition": "closed",
13675
+ "target": null,
13642
13676
  "sources": [
13643
13677
  {
13644
13678
  "file": "RFCS/registers/0228-v1-host-service-error-codes.gaps.md",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "./migrations.schema.json",
3
3
  "version": 1,
4
- "updated": "2026-09-03",
4
+ "updated": "2026-09-29",
5
5
  "rows": [
6
6
  {
7
7
  "id": "openwop.migration.C2.1",
@@ -485,6 +485,44 @@
485
485
  "deprecationId": null,
486
486
  "note": "A host emitting it fails v2 schema validation; MyndHyve drops it in its Phase 4 leg."
487
487
  },
488
+ {
489
+ "id": "openwop.migration.C2.11",
490
+ "child": "C2",
491
+ "kind": "retype",
492
+ "v1": {
493
+ "surface": "`aiProviders.selfHosted[]`, `realtimeVoice.{transcription, synthesis, turnDetection, bargeIn}`, `promptPrefixCache.{supported, providers[]}`, per-provider `authModes` map",
494
+ "path": "spec/v1/capabilities.md"
495
+ },
496
+ "v2": {
497
+ "surface": "`selfHosted`, `realtimeVoice`, `promptPrefixCache` booleans; `authModes` one list of modes (spec/v2/core/host-services.md §aiProviders)",
498
+ "path": "schemas/v2/capabilities.schema.json"
499
+ },
500
+ "codemod": "openwop.codemod.ai-providers-v2-narrow",
501
+ "persistedData": "not-persisted",
502
+ "requirementIds": [],
503
+ "gapIds": [],
504
+ "deprecationId": "openwop.deprecation.ai-providers-per-provider-facets",
505
+ "note": "Recorded 2026-09-28: v2 accepts the narrowing. Seven v1 rules with no v2 seat retire: `selfHosted[]` ⊆ `supported[]` (capabilities.md A.1); per-provider `authModes` rules 1, 2, 3, 5 and 7 (keys ⊆ `supported`, `apiKey` and `subscription` ⇒ `byok`, `[\"none\"]` ⇒ not `byok`, the absent-map defaults); the `promptPrefixCache.providers` list (v2: per routed provider); and the `realtimeVoice` sub-flags (v2: one boolean)."
506
+ },
507
+ {
508
+ "id": "openwop.migration.C2.12",
509
+ "child": "C2",
510
+ "kind": "rename",
511
+ "v1": {
512
+ "surface": "`aiProviders.supported` (the providers the host's AI proxy routes to)",
513
+ "path": "spec/v1/capabilities.md"
514
+ },
515
+ "v2": {
516
+ "surface": "`aiProviders.providers` (spec/v2/core/host-services.md §aiProviders)",
517
+ "path": "schemas/v2/capabilities.schema.json"
518
+ },
519
+ "codemod": "openwop.codemod.ai-providers-v2-narrow",
520
+ "persistedData": "not-persisted",
521
+ "requirementIds": [],
522
+ "gapIds": [],
523
+ "deprecationId": "openwop.deprecation.ai-providers-supported-rename",
524
+ "note": "Recorded 2026-09-29: the v2 facet renamed the list without a row. Same values, same meaning; `byok` stays a subset of it. The codemod refuses a record carrying both names with different values."
525
+ },
488
526
  {
489
527
  "id": "openwop.migration.C5.3",
490
528
  "child": "C5",
@@ -1057,6 +1095,24 @@
1057
1095
  "deprecationId": "openwop.deprecation.interrupt-locale-field",
1058
1096
  "note": "Recorded 2026-09-27: the v2 schema closure (C4.9) left the v1 field no place, and it is dropped rather than re-added. A v1 `interrupt.requested` event carrying `data.locale` is read through the C.9 adapter like any other extra key."
1059
1097
  },
1098
+ {
1099
+ "id": "openwop.migration.C4.18",
1100
+ "child": "C4",
1101
+ "kind": "remove",
1102
+ "v1": {
1103
+ "surface": "`RunSnapshot.parentRunId` and `RunSnapshot.parentNodeId` on a `core.subWorkflow` child run (node-packs.md §Parent linkage)",
1104
+ "path": "spec/v1/node-packs.md"
1105
+ },
1106
+ "v2": {
1107
+ "surface": "none on the child snapshot — `parentRunId` is fork lineage; the child's parent is `getRunAncestry` `parent.runId` with `cause: \"core.subWorkflow\"` (spec/v2/core/execution.md §subWorkflow)"
1108
+ },
1109
+ "codemod": "openwop.codemod.subworkflow-parent-link-drop",
1110
+ "persistedData": "translated",
1111
+ "requirementIds": [],
1112
+ "gapIds": [],
1113
+ "deprecationId": "openwop.deprecation.subworkflow-parent-link-fields",
1114
+ "note": "Recorded 2026-09-28. Ancestry names the parent run, not the dispatching node; that node is the one whose `node.completed` carries the child's id in `outputs.childRunId`. A host migrating a persisted v1 child keeps the link in whatever backs its ancestry answer before the codemod drops the fields."
1115
+ },
1060
1116
  {
1061
1117
  "id": "openwop.migration.C6.1",
1062
1118
  "child": "C6",
@@ -16,7 +16,11 @@ The schema at `schemaRef`, inside the signed tarball, is the source of truth. It
16
16
  - A host advertising `artifactTypes` SHOULD serve each installed type's schema there, and MUST for a host-registered type whose `schemaVersion` it advertises.
17
17
  - A tarball copy and a served copy of one `(artifactTypeId, schemaVersion)` MUST be byte-identical.
18
18
 
19
- At registry publish and at install, a host MUST reject, with `pack_validation_failed`, an artifact schema exceeding its bounds on serialized size, `$ref` depth or keyword/subschema count, and MUST compile under a wall-clock timeout. It SHOULD reject a `pattern` it cannot evaluate in linear time (invariant `artifact-schema-compile-bounded`).
19
+ At registry publish and at install, a host (invariant `artifact-schema-compile-bounded`):
20
+
21
+ - MUST reject, with `pack_validation_failed`, an artifact schema exceeding its bounds on serialized size, `$ref` depth or keyword/subschema count;
22
+ - MUST compile it under a wall-clock timeout;
23
+ - SHOULD reject a `pattern` it cannot evaluate in linear time.
20
24
 
21
25
  ## Registration
22
26
 
@@ -115,15 +115,15 @@ Operation paths live in `spec/v2/path-manifest.json` ([`versioning.md`](versioni
115
115
 
116
116
  Each heading below is a `spec/v2/declaration.json` row with `anchor: core`. `scripts/check-declaration.mjs` MUST fail when a heading here, a root key in the generated schema, or a pack peer-dependency identifier names a family the declaration does not. The peer-dependency identifier is identical to the key ([`packs.md`](packs.md)).
117
117
 
118
- Under each heading: the family's witness class and, where `spec/v2/facets/<key>.schema.json` exists, its hand-decided facets. Maturity axes are §8; the owning RFC is the row's `owningRfc`.
118
+ Under each heading: the family's witness class. Its facets are named in its normative home (§2 says where they are decided). Maturity axes are §8; the owning RFC is the row's `owningRfc`.
119
119
 
120
120
  ### § supportedEnvelopes
121
121
 
122
- Witness `witnessable-gated`. Facets: `kinds`.
122
+ Witness `witnessable-gated`.
123
123
 
124
124
  ### § schemaVersions
125
125
 
126
- Witness `witnessable-gated`. Facets: `kinds`.
126
+ Witness `witnessable-gated`.
127
127
 
128
128
  ### § limits
129
129
 
@@ -131,7 +131,7 @@ Witness `witnessable-gated`.
131
131
 
132
132
  ### § envelopeStrictness
133
133
 
134
- Witness `claims-check`. Facets: `mode`.
134
+ Witness `claims-check`.
135
135
 
136
136
  ### § envelopeContracts
137
137
 
@@ -183,7 +183,7 @@ Witness `witnessable-gated`.
183
183
 
184
184
  ### § replay
185
185
 
186
- Witness `witnessable-gated`. Facets: `modes`, `retention`, `effectSeamsManifest`.
186
+ Witness `witnessable-gated`.
187
187
 
188
188
  ### § oauth
189
189
 
@@ -219,7 +219,7 @@ Witness `witnessable-gated`.
219
219
 
220
220
  ### § aiProviders
221
221
 
222
- Witness `witnessable-gated`. Facets: `providers`, `byok`, `selfHosted`, `speechSynthesis`, `imageGeneration`, `videoGeneration`, `realtimeVoice`, `promptPrefixCache`, `maxInlineMediaBytes`, `authModes`, `policies`, `input`.
222
+ Witness `witnessable-gated`.
223
223
 
224
224
  ### § agents
225
225
 
@@ -259,7 +259,7 @@ Witness `witnessable-gated`.
259
259
 
260
260
  ### § heartbeat
261
261
 
262
- Witness `witnessable-gated`. Facets: `minIntervalSec`, `maxRuntimeMs`, `deliveryChannel`.
262
+ Witness `witnessable-gated`.
263
263
 
264
264
  ### § toolHooks
265
265
 
@@ -299,7 +299,7 @@ Witness `witnessable-gated`.
299
299
 
300
300
  ### § webhooks
301
301
 
302
- Witness `witnessable-gated`. Facets: `deadLetter`, `retryPolicy`, `secretRotation`, `signatureAlgorithms`.
302
+ Witness `witnessable-gated`.
303
303
 
304
304
  ### § triggerBridge
305
305
 
@@ -307,7 +307,7 @@ Witness `witnessable-gated`.
307
307
 
308
308
  ### § a2a
309
309
 
310
- Witness `seam-gated`. Facets: `versions`, `preferredVersion`, `minimumVersion`, `refreshedAt`, `profiles`, `agentCardUrl`, `streaming`, `pushNotifications`, `durableTasks`, `agentCards`.
310
+ Witness `seam-gated`.
311
311
 
312
312
  A facet MAY name a URL on another origin; that is a claim about the facet, not the origin. `agentCardUrl` (and `mcp.serverUrls[]`) are `format: uri` with no origin constraint.
313
313
 
@@ -358,21 +358,21 @@ Witness `witnessable-gated`.
358
358
 
359
359
  ### § workflowChainPacks
360
360
 
361
- Witness `witnessable-gated`. Facets: `subChains`.
361
+ Witness `witnessable-gated`.
362
362
 
363
363
  ### § packs
364
364
 
365
- Witness `claims-check`. Facets: `testMode`.
365
+ Witness `claims-check`.
366
366
 
367
367
  ### § mcp
368
368
 
369
- Witness `seam-gated`. Facets: `revisions`, `preferredVersion`, `minimumRevision`, `refreshedAt`, `profiles`, `features`, `serverUrls`, `serverMount`, `mrtr`, `client`.
369
+ Witness `seam-gated`.
370
370
 
371
371
  `serverUrls[]` MAY name other origins; the off-origin rule under § a2a applies.
372
372
 
373
373
  ### § sandbox
374
374
 
375
- Witness `witnessable-gated`. Facets: `isolationModel`, `allowedHostCalls`, `memoryLimitBytes`, `wallClockLimitMs`.
375
+ Witness `witnessable-gated`.
376
376
 
377
377
  ### § compensation
378
378
 
@@ -392,11 +392,11 @@ Witness `witnessable-gated`.
392
392
 
393
393
  ### § auth
394
394
 
395
- Witness `seam-gated`. Facets: `lanes`, `subjectLinkKey`.
395
+ Witness `seam-gated`.
396
396
 
397
397
  ### § auditLogIntegrity
398
398
 
399
- Witness `witnessable-gated`. Facets: `checkpointSignatureAlgorithm`, `checkpointPublicKey`, `checkpointIntervalEntries`, `checkpointIntervalSeconds` ([security-defaults.md](security-defaults.md) §Audit-log integrity).
399
+ Witness `witnessable-gated`; see [security-defaults.md](security-defaults.md) §Audit-log integrity.
400
400
 
401
401
  ### § i18n
402
402
 
@@ -412,11 +412,11 @@ Witness `witnessable-gated`.
412
412
 
413
413
  ### § interrupt
414
414
 
415
- Witness `witnessable-gated`. Facets: `refKinds`, `tokenAlgs`, `callbackDelivery`.
415
+ Witness `witnessable-gated`.
416
416
 
417
417
  ### § runList
418
418
 
419
- Witness `witnessable-gated`. Facets: `maxPageSize`, `filters` ([runs.md](runs.md) §List).
419
+ Witness `witnessable-gated`; see [runs.md](runs.md) §List.
420
420
 
421
421
 
422
422
  ## 6. Extension families (13)
@@ -438,10 +438,8 @@ A profile is a predicate over the declaration file, published in `spec/v2/profil
438
438
  | `openwop-conformance-seams-v2` | The seams profile ([`conformance.md`](conformance.md)); forbidden from the capability namespace |
439
439
 
440
440
  - The v2 root has no `profiles[]`; a host that emits one MUST fail schema validation (§3).
441
- - The facet `auth.lanes[]` ([`identity.md`](identity.md)) replaces `auth.profiles`.
442
- - `a2a.profiles[]` and `mcp.profiles[]` are facets that admit no `-legacy` id; the offered versions are `a2a.versions[]` and `mcp.revisions[]` ([`interop.md`](interop.md)).
443
- - The discovery-only id is `openwop-discovery-core`; the `openwop-core` alias is deleted (row `C2.3`).
444
- - The claim vocabulary is in [`overview.md`](overview.md). The invariant `profile-claim-floor-not-overstated` is registered in `SECURITY/invariants.yaml` with its test.
441
+ - The facet `auth.lanes[]` ([`identity.md`](identity.md)) replaces `auth.profiles`. `a2a.profiles[]` and `mcp.profiles[]` are facets too ([`interop.md`](interop.md)).
442
+ - The claim vocabulary is in [`overview.md`](overview.md) (invariant `profile-claim-floor-not-overstated`).
445
443
 
446
444
  ## 8. Maturity axes
447
445
 
@@ -465,6 +463,6 @@ A profile is a predicate over the declaration file, published in `spec/v2/profil
465
463
 
466
464
  ## 10. Migration rows
467
465
 
468
- Rows `C2.1`–`C2.10` are `spec/v1/migrations.json` entries. `openwop.codemod.discovery-document-v2` transforms `C2.2`–`C2.8`: it drops a family with `supported: false` and promotes a dotted-only declared family to its plain key. Certification bundles naming `openwop-core` are never upgraded; they remain valid v1 evidence at their version.
466
+ Rows `C2.1`–`C2.10` are `spec/v1/migrations.json` entries. `openwop.codemod.discovery-document-v2` transforms `C2.2`–`C2.8`: it drops a family with `supported: false` and promotes a dotted-only declared family to its plain key.
469
467
 
470
468
  *Sources: RFCs 0144, 0169, 0175, 0176, 0179, 0197. Each family's owning RFC is its `owningRfc` in [`declaration.json`](../declaration.json).*
@@ -1,10 +1,11 @@
1
1
  # Conformance
2
2
 
3
3
  > **Status: Stable.**
4
+ > **Normative home:** `production`.
4
5
 
5
6
  ## Why this exists
6
7
 
7
- The v2 evidence contract: how a requirement is asserted, how every requirement declares what can witness it, how the seams are mounted, what the suite ships, and what a bundle proves. Profiles are in [overview.md](overview.md); the capability vocabulary the suite gates on is in [capabilities.md](capabilities.md).
8
+ The v2 evidence contract: how a requirement is asserted and witnessed, how the seams are mounted, what the suite ships, and what a bundle proves. Profiles are in [overview.md](overview.md); the capability vocabulary the suite gates on is in [capabilities.md](capabilities.md).
8
9
 
9
10
  ## Requirement ids
10
11
 
@@ -46,7 +47,7 @@ Every family in `spec/v2/declaration.json`, every requirement in `conformance/re
46
47
 
47
48
  ## The seams profile
48
49
 
49
- Test seams are the profile `openwop-conformance-seams-v2` (`spec/v2/profiles.json`), described by `api/seams-v2.yaml` with schemas under `schemas/v2/seams/`, in the path space `/conformance/seams/…`. The seam schemas `$ref` the canonical error and event schemas with no tolerance path. The profile is versioned with the suite (`seams-v2` for 2.x).
50
+ Test seams are the profile `openwop-conformance-seams-v2` (`spec/v2/profiles.json`), described by `api/seams-v2.yaml` with schemas under `schemas/v2/seams/`, at `/conformance/seams/…`. The seam schemas `$ref` the canonical error and event schemas with no tolerance path. The profile is versioned with the suite (`seams-v2` for 2.x).
50
51
 
51
52
  - A host that mounts the seams MUST advertise the profile as `conformance.seamsProfile: "openwop-conformance-seams-v2"` at the discovery root (the closed root has no `profiles[]`; [capabilities.md](capabilities.md) §3).
52
53
  - A host MUST NOT advertise a `testSeams` capability flag.
@@ -87,7 +88,7 @@ A certification bundle validates against `schemas/v2/certification-bundle.schema
87
88
  - A verifier MUST resolve `keyId` there — in the discovery document of the host the bundle is *about* — and MUST verify the attestation under the published key.
88
89
  - A retired key MUST stay listed.
89
90
 
90
- A signature that cannot be resolved to a published key attests **integrity only**, and the bundle MUST NOT be read as attributable evidence. A gate MUST distinguish three outcomes that a presence check collapses into one: *no discovery document was read*, *read and the key is not published*, and *the attestation does not verify*.
91
+ A signature that cannot be resolved to a published key attests **integrity only**, and the bundle MUST NOT be read as attributable evidence. A gate MUST distinguish three outcomes: *no discovery document was read*, *read and the key is not published*, and *the attestation does not verify*.
91
92
 
92
93
  `evidenceTier: independent` MUST carry a `verifierKeyId` distinct from the host's signing key. The verifier MUST refuse, not warn, on a missing or self-signed independent claim.
93
94
 
@@ -101,7 +102,7 @@ A signature that cannot be resolved to a published key attests **integrity only*
101
102
 
102
103
  ### Colocated companion
103
104
 
104
- A *colocated companion* is the served host's image run beside the suite, so it can trust a suite-held trust anchor.
105
+ A *colocated companion* is the served host's image run beside the suite, trusting a suite-held trust anchor.
105
106
 
106
107
  - A bundle cut from one MUST carry `host.deployment: "colocated-companion"`, which the preimage covers (§"Canonical JSON").
107
108
  - A host serving production traffic MUST NOT list a suite-held trust anchor among the trust roots it advertises.
@@ -126,7 +127,7 @@ The value MUST be I-JSON (RFC 7493):
126
127
 
127
128
  ### Recovery evidence
128
129
 
129
- Recovery evidence rides on rows: a row's `evidence` enters `witnessSha256` only when present.
130
+ A row's `evidence` enters `witnessSha256` only when present.
130
131
 
131
132
  | Row | `evidence` member |
132
133
  | --- | --- |
@@ -139,10 +140,26 @@ Recovery evidence rides on rows: a row's `evidence` enters `witnessSha256` only
139
140
 
140
141
  `durability.rung` is outside the attestation, so a verifier MUST re-derive it and MUST reject (`rung-not-derivable`) a claim it cannot derive. Derivation requires every row of the rung to be `executed-pass`, and each kill row's `class` to name a declared entry whose `bound` equals its `boundMs` and is not exceeded by its `observedMs`. Only `durable-single-instance` is derivable in this revision; a higher claim is refused.
141
142
 
142
- This proves the arithmetic and that recovery ran once inside the bound. It does not prove the kill landed in the class it names; killing only once the execution claim is held is the host's obligation.
143
+ The evidence does not prove the kill landed in the class it names; killing only once the execution claim is held is the host's obligation.
144
+
145
+ ## Production profile
146
+
147
+ `production` is the operational bar for a public host. A host claiming it:
148
+
149
+ - MUST pass `openwop-core-standard`, publish the suite version and command used, and document every optional profile it claims;
150
+ - MUST serve the events channel or poll, and SHOULD serve both;
151
+ - MUST persist run state and event logs outside process memory, replayable after restart, including stale-claim recovery;
152
+ - MUST tolerate at least five retries of one `Idempotency-Key` within its retention;
153
+ - MUST log run id, tenant or project id, terminal status, error code and correlation id, and SHOULD export the `openwop.*` OTel spans and metrics.
154
+
155
+ Its facets:
156
+
157
+ - **`backpressure`.** At capacity it MUST answer `503 service_unavailable` with `Retry-After`. A hold beyond 24 hours SHOULD omit the header.
158
+ - **`retention`.** It MUST document event-log retention, at least 7 days for snapshots and events unless labelled development-only. An expired run MUST answer `404 not_found` or `410 run_expired`, and SHOULD answer `410` when expiry is known.
159
+ - **`debugBundle`.** A debug bundle (`schemas/v2/debug-bundle.schema.json`) MUST redact secrets and tokens and MUST mark truncation explicitly; the host MUST document its truncation limits.
143
160
 
144
161
  ## Corpus-gate evidence
145
162
 
146
163
  An RFC whose acceptance criteria are corpus gates rather than host scenarios records the evidence label **corpus gate — no host tier** in its `Updated` line. For such an RFC the accepted-predicate check reads `(corpus)` rows from `evidence/corpus-ledger.json` and MUST NOT require a host bundle.
147
164
 
148
- *Sources: RFCs 0158, 0168, 0212, 0216.*
165
+ *Sources: RFCs 0009, 0158, 0168, 0212, 0216, 0228.*
@@ -8,7 +8,7 @@ Every error a v2 host returns is a row in one registry. A client routes on `erro
8
8
 
9
9
  ## The registry
10
10
 
11
- `spec/v2/errors.json` holds one row per code, defined by `spec/v2/errors.schema.json`. It registers **111** codes. `schemas/v2/error-envelope.schema.json` is GENERATED from it and MUST NOT be edited by hand.
11
+ `spec/v2/errors.json` holds one row per code, defined by `spec/v2/errors.schema.json`. It registers **120** codes. `schemas/v2/error-envelope.schema.json` is GENERATED from it and MUST NOT be edited by hand.
12
12
 
13
13
  - A host MUST emit a registered code, or a vendor code, wherever it emits an error code: the `error` of every error response, and `error.code` on `run.failed`, `node.failed` and the snapshot's `error` (overview.md §0). A recorded event re-emitted by replay or `:fork` is carried as recorded ([replay.md](replay.md)).
14
14
  - A vendor code MUST match `^(?!openwop\.)[a-z][a-z0-9]*(-[a-z0-9]+)*\.[a-z][a-z0-9_]*$`, with its first segment an org registered in `spec/v2/declaration.json`. `openwop.` is reserved.
@@ -32,7 +32,7 @@ Retry timing lives in the `Retry-After` header only.
32
32
  - A host MUST NOT emit `details.retryAfter`, `details.retryAfterMs` or `details.retryAfterSeconds`.
33
33
  - A `429 rate_limited` response MUST set `Retry-After`.
34
34
 
35
- The retriable rows are `residency_unavailable`, `rate_limited`, `internal_error`, `pack_registry_unreachable`, `runner_unavailable`.
35
+ The retriable rows are `residency_unavailable`, `rate_limited`, `internal_error`, `pack_registry_unreachable`, `runner_unavailable`, `service_unavailable`, `upstream_unavailable`.
36
36
 
37
37
  ## One code per state
38
38
 
@@ -45,122 +45,20 @@ An interrupt has one code per state ([interrupt.md](interrupt.md), [identity.md]
45
45
 
46
46
  The idempotency mismatch code is `idempotency_key_mismatch` only ([idempotency.md](idempotency.md)).
47
47
 
48
+ ## Host-service refusals
49
+
50
+ A `ctx.*` call that rejects MUST use a registered or vendor code. A host MAY carry an uncaught rejection unchanged as the `node.failed` code.
51
+
52
+ - A generic code (`not_found`, `forbidden`, `validation_error`, `rate_limited`, `credential_not_found`, `credential_forbidden`) MUST carry `details.service`, the family key.
53
+ - `details.reason` MAY name a finer cause in lower-kebab. A client MUST NOT route on it.
54
+ - A vendor code MUST NOT stand for a state a registered code names.
55
+
56
+ ## Unadvertised operations
57
+
58
+ An operation gated on a family or facet the host does not advertise MUST answer `404 not_found`.
59
+
48
60
  ## Codes by HTTP status
49
61
 
50
- Generated from `spec/v2/errors.json` (111 codes; `retriable` and `statusSource` are in the registry).
51
-
52
- Code | Status
53
- --- | ---
54
- `connection_provider_unresolved` | 400
55
- `connector_action_unresolved` | 400
56
- `credential_scope_unsupported` | 400
57
- `delegation_chain_cyclic` | 400
58
- `delegation_chain_too_long` | 400
59
- `idempotency_key_invalid` | 400
60
- `interop_version_unsupported` | 400
61
- `oauth_provider_unsupported` | 400
62
- `oauth_scope_unsupported` | 400
63
- `pack_dependency_cycle` | 400
64
- `pack_engine_unsupported` | 400
65
- `pack_integrity_failure` | 400
66
- `pack_kind_invalid` | 400
67
- `pack_lockfile_incomplete` | 400
68
- `pack_peer_dependency_missing` | 400
69
- `pack_peer_dependency_undefined` | 400
70
- `pack_signature_invalid` | 400
71
- `pack_validation_failed` | 400
72
- `protocol_version_mismatch` | 400
73
- `schedule_horizon_exceeded` | 400
74
- `sub_chain_cycle` | 400
75
- `sub_chain_depth_exceeded` | 400
76
- `unsupported_runtime` | 400
77
- `unsupported_stream_mode` | 400
78
- `until_in_past` | 400
79
- `validation_error` | 400
80
- `webhook_endpoint_unverified` | 400
81
- `webhook_url_rejected` | 400
82
- `audience_mismatch` | 401
83
- `connector_auth_declined` | 401
84
- `connector_auth_expired` | 401
85
- `credential_lifetime_exceeded` | 401
86
- `credential_revoked` | 401
87
- `delegation_expired` | 401
88
- `identity_unresolvable` | 401
89
- `identity_unverified` | 401
90
- `interrupt_token_invalid` | 401
91
- `key_revoked` | 401
92
- `sender_constraint_missing` | 401
93
- `unauthenticated` | 401
94
- `credential_forbidden` | 403
95
- `credential_scope_forbidden` | 403
96
- `delegation_scope_amplified` | 403
97
- `forbidden` | 403
98
- `force_engine_version_forbidden` | 403
99
- `id_tenant_mismatch` | 403
100
- `mock_provider_forbidden` | 403
101
- `pack_namespace_unauthorized` | 403
102
- `run_forbidden` | 403
103
- `sandbox_capability_denied` | 403
104
- `sandbox_escape_attempt` | 403
105
- `workspace_membership_required` | 403
106
- `credential_not_found` | 404
107
- `interrupt_not_found` | 404
108
- `not_found` | 404
109
- `pack_version_not_found` | 404
110
- `replay_source_missing` | 404
111
- `signature_not_available` | 404
112
- `protocol_version_unsupported` | 406
113
- `connection_provider_conflict` | 409
114
- `envelope_correlation_conflict` | 409
115
- `idempotency_in_flight` | 409
116
- `idempotency_key_mismatch` | 409
117
- `interrupt_already_resolved` | 409
118
- `pack_dependency_conflict` | 409
119
- `pack_integrity_mismatch` | 409
120
- `replay_context_summary_unavailable` | 409
121
- `replay_diverged_at_refusal` | 409
122
- `replay_memory_snapshot_unavailable` | 409
123
- `run_already_active` | 409
124
- `run_state_conflict` | 409
125
- `run_terminal` | 409
126
- `version_conflict` | 409
127
- `workspace_conflict` | 409
128
- `interrupt_cancelled` | 410
129
- `interrupt_expired` | 410
130
- `payload_too_large` | 413
131
- `workspace_too_large` | 413
132
- `unsupported_media_type` | 415
133
- `approval_rejected` | 422
134
- `capability_not_provided` | 422
135
- `capability_required` | 422
136
- `connection_auth_metadata_mismatch` | 422
137
- `credential_required` | 422
138
- `envelope_invalid` | 422
139
- `envelope_refusal` | 422
140
- `envelope_truncation_unrecoverable` | 422
141
- `fork_point_invalid` | 422
142
- `loop_limit_exceeded` | 422
143
- `mcp_mrtr_rounds_exceeded` | 422
144
- `node_config_invalid` | 422
145
- `pack_runtime_requirement_unmet` | 422
146
- `recursion_limit_exceeded` | 422
147
- `residency_unavailable` | 422
148
- `run_timeout` | 422
149
- `sandbox_invocation_error` | 422
150
- `sandbox_memory_exceeded` | 422
151
- `sandbox_timeout` | 422
152
- `token_budget_exceeded` | 422
153
- `unknown_envelope_kind` | 422
154
- `unknown_schema_version` | 422
155
- `client_version_unsupported` | 426
156
- `rate_limited` | 429
157
- `event_type_unmapped` | 500
158
- `internal_error` | 500
159
- `pack_load_failure` | 500
160
- `payload_unprojectable` | 500
161
- `credential_unavailable` | 501
162
- `mcp_error` | 502
163
- `pack_registry_unreachable` | 503
164
- `runner_unavailable` | 503
165
-
166
- *Sources: RFCs 0171, 0213.*
62
+ Every registered code, by HTTP status, is listed in [error-codes.md](../generated/error-codes.md), generated from `spec/v2/errors.json` (120 codes).
63
+
64
+ *Sources: RFCs 0171, 0213, 0227, 0228.*
@@ -123,7 +123,7 @@ With `bufferMs` (0..5000) the host accumulates events into one `event: batch` fr
123
123
 
124
124
  ### Host events
125
125
 
126
- `hostEvents` carries the heartbeat messages (`schemas/v2/heartbeat-evaluated.schema.json`, `schemas/v2/heartbeat-state-changed.schema.json`) at `/host/events` (`streamHostEvents`), the documented default. A host MAY declare another address under `heartbeat.deliveryChannel` ([capabilities.md](capabilities.md)). The channel is content-free of run data. There is no channel without an address.
126
+ `hostEvents` carries the heartbeat messages (`schemas/v2/heartbeat-evaluated.schema.json`, `schemas/v2/heartbeat-state-changed.schema.json`) at the default address `/host/events` (`streamHostEvents`). A host MAY declare another address under `heartbeat.deliveryChannel` ([capabilities.md](capabilities.md)); every channel has an address. The channel carries no run data.
127
127
 
128
128
  #### `heartbeat`
129
129
 
@@ -131,7 +131,7 @@ A heartbeat evaluates a predicate on an interval (at least `minIntervalSec`) and
131
131
 
132
132
  - skip, not queue, a tick while the prior evaluation still runs;
133
133
  - bound the evaluation by `maxRuntimeMs`, itself capped by `limits.maxRunDurationMs`, terminating an overrun with `status: timeout`;
134
- - pass the predicate the prior tick's state, and perform no side effect itself; the predicate MUST be a pure function of observed and prior state;
134
+ - pass the predicate the prior tick's state, and perform no side effect itself. The predicate MUST be a pure function of observed and prior state;
135
135
  - emit `heartbeat.evaluated`;
136
136
  - on a transition only, emit `heartbeat.stateChanged` and, if the predicate asks, call `createRun`; never on an unchanged tick.
137
137
 
@@ -196,15 +196,12 @@ An absent `kinds` is not an empty catalog and is not an unrestricted one: a host
196
196
 
197
197
  ## Envelope, feedback and usage facets
198
198
 
199
- - **`envelopes.tierOneSubsetCompliance`** advertises that the host accepts the Tier 1 structured-output subset shared across major providers. A host advertising it MUST accept an envelope restricted to that subset from any provider it advertises, rather than refusing on provider-specific grounds.
200
- - **`feedback.targets`** names the resources an annotation may be attached to. A host MUST refuse an annotation whose target is outside the advertised set, and MUST NOT write it to the replayable run event log.
201
- - **`providerUsage.costEstimates`** advertises that the host stamps a derived cost on the `provider.usage` event. That figure is an estimate from the host's own rate table; a consumer MUST NOT treat it as a billed amount.
202
-
203
199
  ### `feedback`
204
200
 
205
- `feedback.signals` lists the signal kinds a host accepts; absent means all four. A host advertising `feedback` MUST:
201
+ `feedback.signals` lists the signal kinds a host accepts; absent means all four. `feedback.targets` names the resources an annotation may be attached to. A host advertising `feedback` MUST:
206
202
 
207
203
  - accept an annotation on a terminal run;
204
+ - refuse an annotation whose target is outside the advertised `targets`, and MUST NOT write it to the replayable run event log;
208
205
  - keep annotations visible only within the run's tenant (invariant `annotation-cross-tenant-isolation`);
209
206
  - redact secret-shaped material in `signal.correction` and `note` before persistence, listing and export (invariant `annotation-content-redaction`);
210
207
  - audit-log each recording with the acting principal.
@@ -214,11 +211,14 @@ An absent `kinds` is not an empty catalog and is not an unrestricted one: a host
214
211
  A host advertising `providerUsage` MUST emit exactly one `provider.usage` per LLM provider invocation, before that node's `node.completed`. A host that does not advertise it omits the event.
215
212
 
216
213
  - `inputTokens` and `outputTokens` MUST replay identically; `costEstimateUsd` MAY be omitted on replay.
214
+ - **`providerUsage.costEstimates`** advertises that the host stamps a derived cost on the event. That figure is an estimate from the host's own rate table; a consumer MUST NOT treat it as a billed amount.
217
215
  - The payload MUST NOT carry credential refs, hashed credential identifiers, or prompt or response text (invariant `provider-usage-no-credential-leak`).
218
216
  - `providerUsage.currency` is the ISO 4217 currency of `costEstimateUsd`; absent means USD.
219
217
 
220
218
  ### `envelopes`
221
219
 
220
+ **`envelopes.tierOneSubsetCompliance`** advertises that the host accepts the Tier 1 structured-output subset shared across major providers. A host advertising it MUST accept an envelope restricted to that subset from any provider it advertises, rather than refusing on provider-specific grounds.
221
+
222
222
  `envelopes.reasoning` advertises the host's prompt posture for the optional payload field `reasoning`.
223
223
 
224
224
  - `reasoning` SHOULD be the first property where the schema dialect orders properties. A vendor kind whose payload needs multi-step reasoning SHOULD declare it as optional.