@openwop/spec-artifacts 2.42.6 → 2.42.8

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 (69) hide show
  1. package/CORPUS-STAMP.json +70 -67
  2. package/api/seams-v2.yaml +238 -322
  3. package/api/v2/asyncapi.yaml +83 -113
  4. package/api/v2/openapi.yaml +408 -724
  5. package/package.json +1 -1
  6. package/schemas/audit-verify-result.schema.json +2 -2
  7. package/schemas/v2/audit-verify-result.schema.json +2 -2
  8. package/schemas/v2/capabilities.schema.json +4 -7
  9. package/schemas/v2/error-envelope.schema.json +144 -69
  10. package/spec/v1/alias-detectors.json +6 -0
  11. package/spec/v1/core-standard-manifest.json +2 -2
  12. package/spec/v1/deprecations.json +22 -0
  13. package/spec/v1/gaps.json +188 -1
  14. package/spec/v1/migrations.json +18 -0
  15. package/spec/v2/README.md +35 -37
  16. package/spec/v2/core/capabilities.md +186 -119
  17. package/spec/v2/core/conformance.md +78 -27
  18. package/spec/v2/core/connection-packs.md +8 -3
  19. package/spec/v2/core/conversation.md +10 -4
  20. package/spec/v2/core/errors.md +30 -9
  21. package/spec/v2/core/events.md +116 -80
  22. package/spec/v2/core/form-content-packs.md +11 -13
  23. package/spec/v2/core/headers.md +23 -14
  24. package/spec/v2/core/host-services.md +22 -5
  25. package/spec/v2/core/i18n.md +64 -0
  26. package/spec/v2/core/idempotency.md +48 -36
  27. package/spec/v2/core/identity.md +110 -42
  28. package/spec/v2/core/interop.md +82 -21
  29. package/spec/v2/core/interrupt.md +79 -40
  30. package/spec/v2/core/node-pack-runtimes.md +18 -4
  31. package/spec/v2/core/oauth.md +40 -6
  32. package/spec/v2/core/overview.md +61 -33
  33. package/spec/v2/core/packs.md +53 -28
  34. package/spec/v2/core/persistence.md +61 -80
  35. package/spec/v2/core/portability.md +44 -0
  36. package/spec/v2/core/replay.md +56 -42
  37. package/spec/v2/core/runs.md +198 -60
  38. package/spec/v2/core/security-defaults.md +57 -33
  39. package/spec/v2/core/tool-catalog.md +29 -6
  40. package/spec/v2/core/versioning.md +115 -50
  41. package/spec/v2/core/webhooks.md +61 -20
  42. package/spec/v2/core/workflow-chain-packs.md +16 -14
  43. package/spec/v2/corrections.json +17 -1
  44. package/spec/v2/corrections.schema.json +1 -1
  45. package/spec/v2/declaration.json +26 -9
  46. package/spec/v2/declaration.schema.json +10 -0
  47. package/spec/v2/errors.json +121 -4
  48. package/spec/v2/errors.schema.json +38 -0
  49. package/spec/v2/ext/README.md +36 -23
  50. package/spec/v2/ext/a2uiSurface/README.md +75 -89
  51. package/spec/v2/ext/brand/README.md +15 -15
  52. package/spec/v2/ext/canvas/README.md +15 -15
  53. package/spec/v2/ext/chat/README.md +15 -15
  54. package/spec/v2/ext/coordination/README.md +15 -15
  55. package/spec/v2/ext/dataIntegration/README.md +17 -20
  56. package/spec/v2/ext/entities/README.md +15 -15
  57. package/spec/v2/ext/grpc-transport/README.md +6 -8
  58. package/spec/v2/ext/kanban/README.md +15 -15
  59. package/spec/v2/ext/knowledge/README.md +15 -15
  60. package/spec/v2/ext/launchStudio/README.md +16 -16
  61. package/spec/v2/ext/messaging/README.md +15 -15
  62. package/spec/v2/ext/portability/README.md +3 -17
  63. package/spec/v2/ext/provider-idempotency/README.md +7 -10
  64. package/spec/v2/ext/restTransport/README.md +30 -19
  65. package/spec/v2/ext/sandbox-runtime-notes/README.md +7 -9
  66. package/spec/v2/ext/webResearch/README.md +16 -16
  67. package/spec/v2/profiles.json +6 -2
  68. package/spec/v2/release.json +2 -2
  69. package/spec/v2/surface-baseline.json +544 -2
@@ -1,6 +1,6 @@
1
1
  # Interrupt
2
2
 
3
- > **Status: Stable · RFC 0170 §E.1, RFC 0171 §A.4, RFC 0173 §B.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `interrupt`.
5
5
 
6
6
  ## Why this exists
@@ -11,25 +11,49 @@
11
11
 
12
12
  `schemas/v2/suspend-request.schema.json` (`InterruptPayload`) is closed and discriminated by `kind`; `kind`, `key` and `data` are REQUIRED.
13
13
 
14
- | Kind | `data` (required fields) | Notes |
14
+ | Kind | `data` (required fields) | Snapshot status / gate |
15
15
  | --- | --- | --- |
16
- | `approval` | `artifactId`, `artifactType`, `title`, `actions` | 5-action vocabulary below; quorum and eligibility fields |
17
- | `clarification` | `questions[]` (`id`, `question`, optional `schema`) | Snapshot status `waiting-input` |
18
- | `external-event` | `eventType`, `correlation` | Snapshot status MUST be `waiting-external` |
19
- | `custom` | `customKind`, optional `payload` | A host MUST accept and persist it; rendering is best-effort |
20
- | `conversation.start` | `conversationId` | Gated on the `conversation` capability (capabilities.md); `conversationId` MUST be tenant-unique and MUST NOT be assumed resolvable on another host |
21
- | `conversation.exchange` | `conversationId`, `prompt` | The resume value MUST validate against `outcomeSchema` when supplied |
16
+ | `approval` | `artifactId`, `artifactType`, `title`, `actions` | 5-action vocabulary, quorum and eligibility (§Approval) |
17
+ | `clarification` | `questions[]` (`id`, `question`, optional `schema`) | `waiting-input` |
18
+ | `external-event` | `eventType`, `correlation` | `waiting-external` |
19
+ | `custom` | `customKind`, optional `payload` | — |
20
+ | `conversation.start` | `conversationId` | Gated on `conversation` ([capabilities.md](capabilities.md)) |
21
+ | `conversation.exchange` | `conversationId`, `prompt` | — |
22
22
  | `conversation.close` | `conversationId` | Gated as above |
23
- | `low-confidence` | `agentId`, `threshold`, `observed` | An `agent.decided` with `confidence` below the threshold MUST be followed by `node.suspended { reason: 'low-confidence' }`; the per-run threshold is `configurable.run.escalationThreshold` (runs.md) |
24
- | `credential` | `provider`, `scopes`, `reason`, `connectUrl` | Gated on `oauth.credentialInterrupt` (oauth.md); snapshot status `waiting-input`; the resume value is `{ outcome }` and carries no credential |
23
+ | `low-confidence` | `agentId`, `threshold`, `observed` | — |
24
+ | `credential` | `provider`, `scopes`, `reason`, `connectUrl` | `waiting-input`; gated on `oauth.credentialInterrupt` ([oauth.md](oauth.md)) |
25
25
 
26
- An interrupt of any kind MUST NOT solicit credential material; a credential is acquired through `credential`.
26
+ Per-kind rules:
27
27
 
28
- `key` is the deterministic re-entry key: a host MUST invoke an interrupt with key `K` at most once for the lifetime of the run. On recovery the engine MUST consult the event log, find the prior `interrupt.resolved`, and return the persisted `resumeValue` without emitting a second `interrupt.requested`; an in-memory cache MAY serve in-process replays but MUST NOT replace the event log for cross-process replays. A host MUST validate the resume value against `resumeSchema` when one is declared and MUST refuse a failing value with `400 validation_error`. `timeoutMs`, when set, is the interrupt's own deadline.
28
+ - `external-event`: the snapshot status MUST be `waiting-external`.
29
+ - `custom`: a host MUST accept and persist it; rendering is best-effort.
30
+ - `conversation.start`: `conversationId` MUST be tenant-unique and MUST NOT be assumed resolvable on another host.
31
+ - `conversation.exchange`: the resume value MUST validate against `outcomeSchema` when supplied.
32
+ - `low-confidence`: an `agent.decided` with `confidence` below the threshold MUST be followed by `node.suspended { reason: 'low-confidence' }`. The per-run threshold is `configurable.run.escalationThreshold` ([runs.md](runs.md)).
33
+ - `credential`: the resume value is `{ outcome }` and carries no credential.
34
+ - An interrupt of any kind MUST NOT solicit credential material; a credential is acquired through `credential`.
35
+
36
+ ### Re-entry and resume values
37
+
38
+ `key` is the deterministic re-entry key.
39
+
40
+ - A host MUST invoke an interrupt with key `K` at most once for the lifetime of the run.
41
+ - On recovery the engine MUST consult the event log, find the prior `interrupt.resolved`, and return the persisted `resumeValue` without emitting a second `interrupt.requested`.
42
+ - An in-memory cache MAY serve in-process replays but MUST NOT replace the event log for cross-process replays.
43
+ - A host MUST validate the resume value against `resumeSchema` when one is declared, and MUST refuse a failing value with `400 validation_error`.
44
+
45
+ `timeoutMs`, when set, is the interrupt's own deadline.
29
46
 
30
47
  ## Events
31
48
 
32
- Every kind uses two registered types (events.md): `interrupt.requested`, whose payload is the `InterruptPayload` verbatim, and `interrupt.resolved`, whose closed payload is `interruptResolved`; resolving an approval-kind interrupt MUST record the applied `action` there, with the field §Approval requires. The legacy `approval.*` and `clarification.*` types remain registered; their payload definitions in `schemas/v2/run-event-payloads.schema.json` are `$ref` aliases of `interruptRequested` and `interruptResolved` (RFC 0171 §A.4 E4), so there is one shape per direction. A host emitting `interrupt.requested` SHOULD also emit the legacy kind-specific type until its consumers migrate. Both events are durable and appear in the `updates` and `debug` stream modes. While suspended, `RunSnapshot.currentNodeId` names the node and `status` is `waiting-approval`, `waiting-input` or `waiting-external`.
49
+ Every kind uses two registered types ([events.md](events.md)):
50
+
51
+ - `interrupt.requested` — the payload is the `InterruptPayload` verbatim.
52
+ - `interrupt.resolved` — the closed payload is `interruptResolved`. Resolving an approval-kind interrupt MUST record the applied `action` there, with the field §Approval requires.
53
+
54
+ The kind-specific `approval.*` and `clarification.*` types remain registered. Their payload definitions in `schemas/v2/run-event-payloads.schema.json` are `$ref` aliases of `interruptRequested` and `interruptResolved`, so there is one shape per direction. A host emitting `interrupt.requested` SHOULD also emit the kind-specific type until its consumers migrate.
55
+
56
+ Both events are durable and appear in the `updates` and `debug` stream modes. While suspended, `RunSnapshot.currentNodeId` names the node and `status` is `waiting-approval`, `waiting-input` or `waiting-external`.
33
57
 
34
58
  ## Resolve surfaces
35
59
 
@@ -39,28 +63,40 @@ Every kind uses two registered types (events.md): `interrupt.requested`, whose p
39
63
  | `inspectInterruptByToken` | `GET /interrupts/{token}` | the token | — (returns the `InterruptPayload`) |
40
64
  | `resolveInterruptByToken` | `POST /interrupts/{token}` | the token | `{ resumeValue }` (closed) |
41
65
 
42
- A host MUST expose the run-scoped surface and SHOULD expose the signed-token surface for callers not authenticated to the protocol. Every resolve MUST honor `Idempotency-Key` (idempotency.md). Exactly one of two concurrent resolves MUST succeed; the other MUST receive `409 interrupt_already_resolved`.
66
+ - A host MUST expose the run-scoped surface, and SHOULD expose the signed-token surface for callers not authenticated to the protocol.
67
+ - Every resolve MUST honor `Idempotency-Key` ([idempotency.md](idempotency.md)).
68
+ - Of two concurrent resolves, exactly one MUST succeed; the other MUST receive `409 interrupt_already_resolved`.
69
+
70
+ ### Callback delivery
71
+
72
+ `createRun.callbackUrl` names where a host that advertises `interrupt.callbackDelivery: true` delivers notice of an interrupt, so its holder can resolve it through the token surface. The payload, timing and signing are host-defined.
73
+
74
+ A host advertising the facet:
43
75
 
44
- **Callback delivery (RFC 0196).** `createRun.callbackUrl` names where a host that advertises `interrupt.callbackDelivery: true` delivers notice of an interrupt, so its holder can resolve it through the token surface. The payload, timing and signing are host-defined in this revision. A host advertising the facet MUST refuse at `createRun`, with `400 validation_error` and `details.field: "callbackUrl"`, a URL the `webhooks.md` §Egress registration guard would refuse, MUST re-validate every resolved address at delivery, and MUST NOT follow a redirect. A host that does not advertise it SHOULD refuse the member and MUST NOT claim delivery it does not perform.
76
+ - MUST refuse at `createRun`, with `400 validation_error` and `details.field: "callbackUrl"`, a URL the `webhooks.md` §Egress registration guard would refuse;
77
+ - MUST re-validate every resolved address at delivery;
78
+ - MUST NOT follow a redirect.
79
+
80
+ A host that does not advertise it SHOULD refuse the member and MUST NOT claim delivery it does not perform.
81
+
82
+ ### Errors
45
83
 
46
84
  | Status | Code | Condition |
47
85
  | --- | --- | --- |
48
- | `400` | `validation_error` | `resumeValue` fails `resumeSchema` or the approval action is not in `actions` |
86
+ | `400` | `validation_error` | `resumeValue` fails `resumeSchema`, or the approval action is not in `actions` |
49
87
  | `401` | `interrupt_token_invalid` | MAC, `alg` or `kid` not accepted |
50
88
  | `404` | `not_found` | No such run or node |
51
- | `409` | `interrupt_already_resolved` | Already resolved, or the run is cancelled or completed (both surfaces); or a token invalidated by resolution, cancellation or completion |
89
+ | `409` | `interrupt_already_resolved` | Already resolved; the run is cancelled or completed (both surfaces); or the token was invalidated |
52
90
  | `410` | `interrupt_expired` | Token past `expiresAt` (token surface only) |
53
91
 
54
92
  ## Tokens
55
93
 
56
- The token grammar, the `interrupt.tokenAlgs[]` / `kid` check and the v1 drain are identity.md §4 (`401 interrupt_token_invalid`).
94
+ The token grammar and the `interrupt.tokenAlgs[]` / `kid` check are [identity.md](identity.md) §4 (`401 interrupt_token_invalid`).
57
95
 
58
- | Rule | Requirement |
59
- | --- | --- |
60
- | Expiry | Every token MUST carry `expiresAt`. The default SHOULD be 30 minutes; a host MUST cap the lifetime at the interrupt's `timeoutMs` when one exists. A token MUST NOT outlive the interrupt it resolves; past `expiresAt` the host MUST answer `410 interrupt_expired`. |
61
- | Invalidation | A token MUST be invalidated when its interrupt is resolved or its run is cancelled or completed; later use MUST answer `409 interrupt_already_resolved`. |
62
- | Verification | MAC comparison MUST be constant-time. `kid` selects the verification secret so secrets rotate without orphaning outstanding tokens. |
63
- | Intent | A token minted with `intent: resolve` authorizes both operations; a host MAY mint `intent: inspect` tokens, and a resolve with one MUST be refused with `403`. |
96
+ - **Expiry.** Every token MUST carry `expiresAt`. The default SHOULD be 30 minutes, and a host MUST cap the lifetime at the interrupt's `timeoutMs` when one exists. A token MUST NOT outlive the interrupt it resolves; past `expiresAt` the host MUST answer `410 interrupt_expired`.
97
+ - **Invalidation.** A token MUST be invalidated when its interrupt is resolved or its run is cancelled or completed. Later use MUST answer `409 interrupt_already_resolved`.
98
+ - **Verification.** MAC comparison MUST be constant-time. `kid` selects the verification secret, so secrets rotate without orphaning outstanding tokens.
99
+ - **Intent.** A token minted with `intent: resolve` authorizes both operations. A host MAY mint `intent: inspect` tokens; a resolve with one MUST be refused with `403`.
64
100
 
65
101
  ## Approval
66
102
 
@@ -73,23 +109,26 @@ The token grammar, the `interrupt.tokenAlgs[]` / `kid` check and the v1 drain ar
73
109
  | `refine` | `refineFeedback { scope: whole \| section \| items, sectionPath?, itemIds?, tags?, text? }` |
74
110
  | `edit-accept` | `editedArtifactData` |
75
111
 
76
- Every resume carries `decidedAt`; `decidedBy` MAY be omitted by an authenticated caller, and every consumer MUST treat it as an opaque string. `requiredApprovals` sets the quorum (default 1) and `rejectionPolicy` is `single-veto` (default) or `majority`; when `overrideBypassesQuorum` is `true` a configured override principal MAY release the gate alone, otherwise its vote counts once.
112
+ - Every resume carries `decidedAt`. `decidedBy` MAY be omitted by an authenticated caller, and every consumer MUST treat it as an opaque string.
113
+ - `requiredApprovals` sets the quorum (default 1). `rejectionPolicy` is `single-veto` (default) or `majority`.
114
+ - When `overrideBypassesQuorum` is `true`, a configured override principal MAY release the gate alone; otherwise its vote counts once.
77
115
 
78
116
  ## Approver enforcement
79
117
 
80
- Enforcement is an obligation of the fields, not a discovery flag (RFC 0173 §B). The facet `spec/v2/facets/interrupt.schema.json` carries `tokenAlgs[]` (REQUIRED) and `refKinds[]` ⊆ `principal`, `group`, `role`.
118
+ Enforcement is an obligation of the fields, not a discovery flag. The facet `spec/v2/facets/interrupt.schema.json` carries `tokenAlgs[]` (REQUIRED) and `refKinds[]` ⊆ `principal`, `group`, `role`.
81
119
 
82
- | Field | Binds |
83
- | --- | --- |
84
- | `approversList` (explicit principals) | Everywhere: a host advertising `interrupt` MUST refuse a resolver not in the list |
85
- | `approverGroupRefs` | Only where `refKinds` includes `group`: the host MUST surface the field unchanged and MUST resolve and enforce its members as eligible approvers |
86
- | `approverRoleRefs` | Only where `refKinds` includes `role`: as for groups, with holders |
87
- | `audience` | A notification hint, never eligibility; omitted ⇒ the host SHOULD notify the union of the eligibility refs |
88
-
89
- Eligibility binds every WRITER of the suspension record, not every route. A
90
- host whose durable store is writable by a principal other than the engine MUST
91
- enforce the same eligibility at the store, or MUST NOT expose the record to
92
- that principal for write: a rule enforced per route is only as complete as the
93
- census of writers (RFC 0187 §C.1).
94
-
95
- Refs are opaque to the engine; the host resolves them against its own identity model. Membership MUST be resolved at decision time and MUST NOT be re-resolved during replay or `forkRun`: the recorded eligibility decision is fixed history (replay.md). A host that does not advertise a ref kind MUST ignore that field.
120
+ - **`approversList`** (explicit principals) binds everywhere: a host advertising `interrupt` MUST refuse a resolver not in the list.
121
+ - **`approverGroupRefs`** binds only where `refKinds` includes `group`: the host MUST surface the field unchanged and MUST resolve and enforce its members as eligible approvers.
122
+ - **`approverRoleRefs`** binds only where `refKinds` includes `role`: as for groups, with holders.
123
+ - **`audience`** is a notification hint, never eligibility. When omitted, the host SHOULD notify the union of the eligibility refs.
124
+ - A host that does not advertise a ref kind MUST ignore that field.
125
+
126
+ Eligibility binds every writer of the suspension record, not every route. A host whose durable store is writable by a principal other than the engine MUST enforce the same eligibility at the store, or MUST NOT expose the record to that principal for write.
127
+
128
+ Refs are opaque to the engine; the host resolves them against its own identity model. Membership MUST be resolved at decision time and MUST NOT be re-resolved during replay or `forkRun`: the recorded eligibility decision is fixed history ([replay.md](replay.md)).
129
+
130
+ ## During the v1 overlap
131
+
132
+ The v1 interrupt-token drain is [identity.md](identity.md) §4.
133
+
134
+ *Sources: RFC 0170, RFC 0171, RFC 0173, RFC 0187, RFC 0196.*
@@ -1,11 +1,11 @@
1
1
  # Node-pack runtimes
2
2
 
3
- > **Status: Stable · RFC 0008.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `nodePackRuntimes`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- v2 carried this family only by pointing at `spec/v1/node-packs.md`. This is its v2 contract, and it lets a `remote` pack name its MCP server by an inline MCP Registry record instead of a bare URL. The shape is `$defs/Runtime` in `schemas/v2/node-pack-manifest.schema.json`.
8
+ This is the v2 contract for the language a node pack runs in. It also lets a `remote` pack name its MCP server by an inline MCP Registry record instead of a bare URL. The shape is `$defs/Runtime` in `schemas/v2/node-pack-manifest.schema.json`.
9
9
 
10
10
  ## Languages
11
11
 
@@ -13,8 +13,22 @@ v2 carried this family only by pointing at `spec/v1/node-packs.md`. This is its
13
13
 
14
14
  ## WASM
15
15
 
16
- A host that loads `wasm` packs MUST advertise `nodePackRuntimes.wasm` with at least one `abiVersions[]` entry (RFC 0008), and MUST reject at load a pack whose `openwop_abi_version()` is not listed. When it advertises `maxMemoryBytes` it MUST enforce it and emit `cap.breached` with `kind: "wasm-memory"` on a breach. `nodePackRuntimes.wasmComponent` advertises `wasm-component` loading; its interfaces are reserved for a later RFC.
16
+ - A host that loads `wasm` packs MUST advertise `nodePackRuntimes.wasm` with at least one `abiVersions[]` entry, and MUST reject at load a pack whose `openwop_abi_version()` is not listed.
17
+ - When it advertises `maxMemoryBytes` it MUST enforce it and emit `cap.breached` with `kind: "wasm-memory"` on a breach.
18
+ - `nodePackRuntimes.wasmComponent` advertises `wasm-component` loading; its interfaces are reserved for a later RFC.
17
19
 
18
20
  ## The MCP registry record
19
21
 
20
- RFC 0203 (`Accepted`): a `remote` runtime MAY carry `mcpServer`, an inline subset of an MCP Registry `server.json` (schema `2025-12-11`): `name`, `description`, `version`, optional `title`, `websiteUrl` and `repository`, and exactly one `remotes[]` entry of `type: "streamable-http"` with an `https://` URL. It has no `packages[]`, `headers`, `variables` or `_meta`, so it carries no install instruction and no credential. `mcpServer` under any other language, or an `entry` that differs from `remotes[0].url`, makes the manifest invalid (`pack_validation_failed`). The record is by value; this contract defines no registry lookup. A host SHOULD NOT treat `name` as a verified identity: an inline record carries no proof of namespace ownership. A host that authenticates to the server does so through the node's `requiredCredentials` or `auth`.
22
+ A `remote` runtime MAY carry `mcpServer`, an inline subset of an MCP Registry `server.json` (schema `2025-12-11`):
23
+
24
+ - `name`, `description`, `version`;
25
+ - optional `title`, `websiteUrl` and `repository`;
26
+ - exactly one `remotes[]` entry of `type: "streamable-http"` with an `https://` URL.
27
+
28
+ It has no `packages[]`, `headers`, `variables` or `_meta`, so it carries no install instruction and no credential. The record is by value; this contract defines no registry lookup.
29
+
30
+ - `mcpServer` under any other language, or an `entry` that differs from `remotes[0].url`, makes the manifest invalid (`pack_validation_failed`).
31
+ - A host SHOULD NOT treat `name` as a verified identity: an inline record carries no proof of namespace ownership.
32
+ - A host that authenticates to the server does so through the node's `requiredCredentials` or `auth`.
33
+
34
+ *Sources: RFC 0008, RFC 0203.*
@@ -1,19 +1,31 @@
1
1
  # OAuth
2
2
 
3
- > **Status: Stable · RFC 0046, RFC 0047.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `oauth`, `credentials`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- A connector node needs a token a user granted to a third party. The host obtains it, stores it, refreshes it and hands it to the node's sandbox, so a pack names a provider and scopes and never touches the grant. Here the host is an OAuth client; identity.md covers the host as a protected resource. The client rules, the MCP-reach paragraph and the credential interrupt below are RFC 0199 (`Active`).
8
+ A connector node needs a token a user granted to a third party. The host obtains, stores and refreshes it and hands it to the node's sandbox, so a pack names a provider and scopes and never touches the grant. Here the host is an OAuth client; [identity.md](identity.md) covers the host as a protected resource.
9
9
 
10
10
  ## Credentials
11
11
 
12
- A host advertising `credentials` MUST resolve a `{ ref, scope }` reference (`schemas/v2/credential-reference.schema.json`) at node execution and inject the material into the node sandbox only; it MUST NOT appear in inputs, variables, events, the debug bundle or replay state (invariant `credential-payload-redaction`). A failed resolution is `credential_not_found`, `credential_forbidden` (outside the caller's scope; fail closed) or `credential_scope_unsupported` (a scope not in `credentials.scopes`). With `credentials.sharing`, every reference within a scope resolves one stored credential. With `credentials.rotation` `two-key-overlap`, old and new material both resolve during the grace window and the old fails `credential_not_found` after it. `credentials.encryptionAtRest` is a claim about storage; it gates nothing.
12
+ A host advertising `credentials` MUST resolve a `{ ref, scope }` reference (`schemas/v2/credential-reference.schema.json`) at node execution and inject the material into the node sandbox only.
13
+
14
+ - The material MUST NOT appear in inputs, variables, events, the debug bundle or replay state (invariant `credential-payload-redaction`).
15
+ - A failed resolution is `credential_not_found`, `credential_forbidden` (outside the caller's scope; fail closed) or `credential_scope_unsupported` (a scope not in `credentials.scopes`).
16
+ - With `credentials.sharing`, every reference within a scope resolves one stored credential.
17
+ - With `credentials.rotation` `two-key-overlap`, old and new material both resolve during the grace window; after it the old fails `credential_not_found`.
18
+ - `credentials.encryptionAtRest` is a claim about storage; it gates nothing.
13
19
 
14
20
  ## Token lifecycle
15
21
 
16
- A host advertising `oauth` MUST perform only the grants in `oauth.grants` and MUST refuse to register a node whose `auth.provider` or scope is not in `oauth.providers` (`oauth_provider_unsupported`, `oauth_scope_unsupported`). It drives the redirect and callback host-side; the code, redirect URI, `state` and PKCE verifier MUST NOT enter a run-visible surface. Tokens persist as a `credentials` entry at scope `user` or `workspace` and are refreshed host-side. On terminal refresh failure the host MUST emit `connector.auth-expired` and fail the node with `connector_auth_expired`, unless it advertises `oauth.credentialInterrupt`.
22
+ A host advertising `oauth`:
23
+
24
+ - MUST perform only the grants in `oauth.grants`.
25
+ - MUST refuse to register a node whose `auth.provider` or scope is not in `oauth.providers` (`oauth_provider_unsupported`, `oauth_scope_unsupported`).
26
+ - Drives the redirect and callback host-side. The code, redirect URI, `state` and PKCE verifier MUST NOT enter a run-visible surface.
27
+ - Persists tokens as a `credentials` entry at scope `user` or `workspace`, and refreshes them host-side.
28
+ - On terminal refresh failure, MUST emit `connector.auth-expired` and fail the node with `connector_auth_expired`, unless it advertises `oauth.credentialInterrupt`.
17
29
 
18
30
  ## The authorization-code client
19
31
 
@@ -25,8 +37,30 @@ On every `authorization_code` grant the host MUST:
25
37
  4. validate `iss` per RFC 9207 §2.4 where the provider's issuer is known (`oauth.providers[].issuer`), and otherwise give the provider a redirect URI no other provider shares (RFC 9700 §4.4.2);
26
38
  5. use one fixed, registered redirect URI per provider.
27
39
 
28
- Where the provider is reached as an MCP server, the host MUST also send `resource` (RFC 8707), the server's canonical URI, in both requests, and MUST refuse a provider whose authorization-server metadata omits `S256`. It fetches the server's Protected Resource Metadata (RFC 9728) only from URLs derived from the manifest's server URL. Discovery verifies and never selects: a discovered issuer or endpoint that differs from the manifest's, or from the tuple pinned at registration, MUST be refused `connection_auth_metadata_mismatch`, as MUST a grant for such a provider whose manifest declares no `issuer`. This binds a provider the host itself configures as well as one a connection pack defines: the configured server URL and issuer stand in for the manifest's, and the tuple is pinned when the host loads that configuration.
40
+ ### MCP-reach providers
41
+
42
+ Where the provider is reached as an MCP server:
43
+
44
+ - The host MUST also send `resource` (RFC 8707), the server's canonical URI, in both requests.
45
+ - The host MUST refuse a provider whose authorization-server metadata omits `S256`.
46
+ - It fetches the server's Protected Resource Metadata (RFC 9728) only from URLs derived from the manifest's server URL.
47
+ - Discovery verifies and never selects. A discovered issuer or endpoint that differs from the manifest's, or from the tuple pinned at registration, MUST be refused `connection_auth_metadata_mismatch`. A grant for such a provider whose manifest declares no `issuer` MUST be refused the same way.
48
+
49
+ This binds a provider the host configures itself as well as one a connection pack defines: the configured server URL and issuer stand in for the manifest's, and the tuple is pinned when the host loads that configuration.
29
50
 
30
51
  ## The credential interrupt
31
52
 
32
- A host advertising `oauth.credentialInterrupt` MUST suspend the node with a `credential` interrupt (interrupt.md) instead of failing it when a node declaring `auth: { type: "oauth2", provider, scopes }` is about to run and no credential resolves for the Subject, provider and scopes (`reason: "missing"`), one resolves with fewer scopes (`"insufficient_scope"`), or refresh failed terminally (`"expired"`). `connectUrl` MUST be host-owned, MUST NOT be pre-authenticated, and MUST complete only for the initiating Subject. The host resolves the interrupt when the grant completes; a resolve of `authorized` MUST be refused `400 validation_error` unless a credential now resolves, and `declined` fails the node with `connector_auth_declined`. A host that binds a node to one credential reference (for example, a connection) reads "resolves" as that reference resolving with the node's scopes, carries it as `credentialRef`, and still completes `connectUrl` only for the initiating Subject.
53
+ A host advertising `oauth.credentialInterrupt` MUST suspend the node with a `credential` interrupt ([interrupt.md](interrupt.md)) instead of failing it when a node declaring `auth: { type: "oauth2", provider, scopes }` is about to run and:
54
+
55
+ - no credential resolves for the Subject, provider and scopes (`reason: "missing"`);
56
+ - one resolves with fewer scopes (`"insufficient_scope"`); or
57
+ - refresh failed terminally (`"expired"`).
58
+
59
+ Then:
60
+
61
+ - `connectUrl` MUST be host-owned, MUST NOT be pre-authenticated, and MUST complete only for the initiating Subject.
62
+ - The host resolves the interrupt when the grant completes. A resolve of `authorized` MUST be refused `400 validation_error` unless a credential now resolves.
63
+ - `declined` fails the node with `connector_auth_declined`.
64
+ - A host that binds a node to one credential reference (for example, a connection) reads "resolves" as that reference resolving with the node's scopes, carries it as `credentialRef`, and still completes `connectUrl` only for the initiating Subject.
65
+
66
+ *Sources: RFC 0046, RFC 0047, RFC 0199.*
@@ -1,41 +1,47 @@
1
1
  # OpenWOP v2 Core — Overview
2
2
 
3
- > **Status: Stable · RFC 0167, 0168, 0169, 0171, 0174.**
3
+ > **Status: Stable.**
4
4
 
5
5
  ## Why this exists
6
6
 
7
- `spec/v2/core/` is the front door a host implements to pass the 2.0.0 floor. This document fixes the reading order, restates the six axioms, and states once the rules other documents only reference.
7
+ `spec/v2/core/` is what a host implements to pass the 2.0.0 floor. This document sets the reading order, states the six axioms, and holds the rules other documents only reference.
8
8
 
9
9
  ## Reading order
10
10
 
11
- 1. `overview.md` — axioms, §0, §0a, claim vocabulary, `ext/` rule
12
- 2. `versioning.md` — major negotiation, `OpenWOP-Version`, the 18 axes, release identity
13
- 3. `capabilities.md` — one well-known resource, record type, closed root, derived profiles
14
- 4. `identity.md` — Subject, lanes, `SubjectLink`, id grammars, resume tokens
15
- 5. `runs.md` — create / get / cancel / fork, `configurable`, snapshot, owner
16
- 6. `events.md`, `errors.md`, `headers.md` — event `oneOf`, payload and error registries, `OpenWOP-*`
17
- 7. `interrupt.md`, `idempotency.md`, `replay.md`, `conversation.md` — run-side surfaces
18
- 8. `persistence.md` — era key, v1 reader rule, pinned runs
19
- 9. `security-defaults.md`, `webhooks.md`, `interop.md`, `host-services.md` — obligations of a surface, signatures, A2A / MCP, host services
20
- 10. `packs.md`, `connection-packs.md`, `form-content-packs.md`, `workflow-chain-packs.md` — pack identity, engines ceiling
21
- 11. `conformance.md` — requirement ids, witness classes, bundle v3, seams profile
11
+ 1. [`overview.md`](overview.md) — axioms, §0, §0a, claim vocabulary, the `ext/` rule
12
+ 2. [`versioning.md`](versioning.md) — major negotiation, `OpenWOP-Version`, the 18 axes, release identity
13
+ 3. [`capabilities.md`](capabilities.md) — the well-known resource, record type, closed root, derived profiles
14
+ 4. [`identity.md`](identity.md) — Subject, lanes, `SubjectLink`, id grammars, resume tokens
15
+ 5. [`runs.md`](runs.md) — create, get, cancel, fork; `configurable`; snapshot; owner
16
+ 6. [`events.md`](events.md), [`errors.md`](errors.md), [`headers.md`](headers.md) — event `oneOf`, payload and error registries, `OpenWOP-*` headers
17
+ 7. [`interrupt.md`](interrupt.md), [`idempotency.md`](idempotency.md), [`replay.md`](replay.md), [`conversation.md`](conversation.md) — run-side surfaces
18
+ 8. [`persistence.md`](persistence.md) — era key, v1 reader rule, pinned runs
19
+ 9. [`security-defaults.md`](security-defaults.md), [`webhooks.md`](webhooks.md), [`interop.md`](interop.md), [`host-services.md`](host-services.md), [`tool-catalog.md`](tool-catalog.md), [`i18n.md`](i18n.md), [`portability.md`](portability.md) — surface obligations, signatures, A2A and MCP, host services, locale, estate export
20
+ 10. [`packs.md`](packs.md), [`node-pack-runtimes.md`](node-pack-runtimes.md), [`connection-packs.md`](connection-packs.md), [`form-content-packs.md`](form-content-packs.md), [`workflow-chain-packs.md`](workflow-chain-packs.md) — pack identity, engines ceiling
21
+ 11. [`conformance.md`](conformance.md) — requirement ids, witness classes, bundle v3, seams profile
22
22
 
23
- ## Axioms in force (RFC 0167 §A)
23
+ ## Axioms in force
24
24
 
25
25
  1. A MUST without a witness class is not a requirement.
26
- 2. One name per thing; every alias has a removal date in `spec/v1/deprecations.json` and a codemod id.
27
- 3. Closed by default: discovery root, event envelope, payload and error registries, bundle, and `configurable` are `additionalProperties: false`; vendor extension is one positive pattern in one namespace.
28
- 4. Registers are data: gaps, risks, deprecations, migrations, witness classes, and dispositions are files with schemas and gates; prose is checked against them, never the reverse.
29
- 5. Security defaults are obligations of the surface: a protecting behavior binds when the surface is advertised, never when a flag is set.
30
- 6. Nothing persisted under v1 is orphaned: every v1 artifact has a disposition in the migration register.
26
+ 2. One name per thing. Every alias has a removal date in `spec/v1/deprecations.json` and a codemod id.
27
+ 3. Closed by default. The discovery root, event envelope, payload and error registries, bundle, and `configurable` are `additionalProperties: false`. Vendor extension is one positive pattern in one namespace.
28
+ 4. Registers are data. Gaps, risks, deprecations, migrations, witness classes, and dispositions are files with schemas and gates; prose is checked against them, never the reverse.
29
+ 5. Security defaults are obligations of the surface. A protecting behavior binds when the surface is advertised, never when a flag is set.
30
+ 6. Nothing persisted under v1 is orphaned. Every v1 artifact has a disposition in the migration register.
31
31
 
32
- ## §0 Closed-enum growth rule (RFC 0171 §A.5)
32
+ ## §0 Closed-enum growth rule
33
33
 
34
- A registry-backed enum (event types, error codes, envelope kinds, reason vocabularies, lanes) grows by adding a row to its registry and regenerating. Consumers MUST accept an unknown member of a registry-backed enum and MUST NOT act on it. Producers MUST NOT emit an unregistered member. Adding a member is additive in v2.x; renaming one is a major, and removing one is a major except under §0a.
34
+ A registry-backed enum (event types, error codes, envelope kinds, reason vocabularies, lanes) grows by adding a row to its registry and regenerating.
35
35
 
36
- ## §0a Retiring a v2 surface (RFC 0197)
36
+ - Consumers MUST accept an unknown member of a registry-backed enum and MUST NOT act on it.
37
+ - Producers MUST NOT emit an unregistered member.
38
+ - Adding a member is additive in v2.x. Renaming one is a major; removing one is a major except under §0a.
37
39
 
38
- A 2.x minor MUST NOT change the shape of an existing v2 surface; a new shape is a new surface added beside the old one. A 2.x minor MAY remove a surface only when `scripts/check-v2-retirement.mjs` proves all of the following; otherwise the removal waits for 3.0.
40
+ ## §0a Retiring a v2 surface
41
+
42
+ A 2.x minor MUST NOT change the shape of an existing v2 surface; a new shape is a new surface added beside the old one.
43
+
44
+ A 2.x minor MAY remove a surface only when `scripts/check-v2-retirement.mjs` proves all of the following; otherwise the removal waits for 3.0:
39
45
 
40
46
  1. Its replacement shipped in an earlier 2.x minor.
41
47
  2. A `v2-minor` row in `spec/v1/deprecations.json` named the removal minor at least two minors and 30 days earlier.
@@ -46,26 +52,48 @@ A 2.x minor MUST NOT change the shape of an existing v2 surface; a new shape is
46
52
 
47
53
  Readers MUST keep accepting a retired shape on replay, fork and poll; only emission narrows.
48
54
 
49
- ## v1 end-of-support (RFC 0174 §B.4)
55
+ ## v1 end-of-support
56
+
57
+ v1 support ends at the later of:
58
+
59
+ - (a) every INTEROP-MATRIX host's non-vacuous v2 bundle, plus 90 days;
60
+ - (b) 18 months from the v2 release — applied if and only if an independent host is in the matrix at release.
61
+
62
+ The date is computed from the matrix; nothing else MAY set it.
50
63
 
51
- v1 support ends at the later of (a) every INTEROP-MATRIX host's non-vacuous v2 bundle plus 90 days and (b) 18 months from the v2 release, where (b) applies if and only if an independent host is in the matrix at release. Phase 5 computes the date from the matrix; nothing else MAY set it. The hosts counted under (a) are those with a row in the INTEROP-MATRIX v2 table; a reference host that stays on the 1.x line through the overlap (the matrix says which) is not a v2 host and does not count. A host's bundle is "non-vacuous" when at least one claimed profile carries `witnessCount ≥ 1`. The anchor for a host is the date its signed bundle was committed to `evidence/v2-host-bundles/` in the spec repository, read from the public history (`git log --diff-filter=A`), never from `generatedAt` inside the bundle, which nothing signs; a later re-certification replaces the file and does not move the anchor. `evidence/v1-end-of-support.json` is the computed date and is GENERATED (`scripts/generate-v1-eos-clock.mjs`); `check-removal-dates.mjs` reads it and fails the v1-tree sources of every `v1-end-of-support` row on or after it.
64
+ - **Counted hosts.** Those with a row in the INTEROP-MATRIX v2 table. A reference host that stays on the 1.x line through the overlap (the matrix says which) is not a v2 host and does not count.
65
+ - **Non-vacuous.** At least one claimed profile carries `witnessCount ≥ 1`.
66
+ - **Anchor.** The date the host's signed bundle was committed to `evidence/v2-host-bundles/` in the spec repository, read from the public history (`git log --diff-filter=A`). Never `generatedAt` inside the bundle, which nothing signs. A later re-certification replaces the file and does not move the anchor.
67
+ - **The computed date.** `evidence/v1-end-of-support.json`, GENERATED by `scripts/generate-v1-eos-clock.mjs`. `check-removal-dates.mjs` reads it and fails the v1-tree sources of every `v1-end-of-support` row on or after it.
52
68
 
53
- **Old-major retention floors.** Every published old-major artifact — the npm packages `@openwop/openwop` (1.x) and `@openwop/openwop-conformance` (1.x), the PyPI package `openwop-client` (1.x) and the Go module `github.com/openwop/openwop-sdks/go` (v1.x) — MUST remain installable at its last 1.x version for 12 months from the 2.0.0 publish (the `v2.0.0` tag's commit date), independent of v1 end-of-support, which may come first; a consumer pinned to the old major MUST be able to rebuild through that window. The identities and the last 1.x versions are `spec/v2/retention-floors.json`; `scripts/check-retention-floors.mjs` prints the floor state and, with `--network`, probes each registry for the pinned version. Unpublishing, deprecating-with-removal, or retracting a listed version inside the window is a Phase 5 exit failure.
69
+ ### Old-major retention floors
54
70
 
55
- ## Profile claim vocabulary (RFC 0169 §C.3; RFC 0155 §A unchanged)
71
+ Every published old-major artifact MUST remain installable at its last 1.x version for 12 months from the 2.0.0 publish (the `v2.0.0` tag's commit date). This holds independent of v1 end-of-support, which may come first. A consumer pinned to the old major MUST be able to rebuild through that window.
56
72
 
57
- Normative for any public conformance statement:
73
+ The artifacts are the npm packages `@openwop/openwop` (1.x) and `@openwop/openwop-conformance` (1.x), the PyPI package `openwop-client` (1.x), and the Go module `github.com/openwop/openwop-sdks/go` (v1.x). Their identities and last 1.x versions are in `spec/v2/retention-floors.json`. `scripts/check-retention-floors.mjs` prints the floor state and, with `--network`, probes each registry for the pinned version.
74
+
75
+ Unpublishing, deprecating-with-removal, or retracting a listed version inside the window is a Phase 5 exit failure.
76
+
77
+ ## Profile claim vocabulary
78
+
79
+ These rules bind any public conformance statement:
58
80
 
59
81
  - An unqualified "OpenWOP conformant" or "OpenWOP compatible" statement MUST mean `openwop-core-standard`, the executable floor, never the discovery predicate.
60
82
  - A discovery-only claim MUST say `openwop-discovery-core` and MUST NOT use the same badge as `openwop-core-standard`.
61
83
  - Every claim MUST state every additional profile it relies on; an omitted profile is an unclaimed one.
62
- - A certification bundle MUST name canonical profile ids; `openwop-core` is deleted (see `capabilities.md`).
84
+ - A certification bundle MUST name canonical profile ids; `openwop-core` is deleted (see [`capabilities.md`](capabilities.md)).
63
85
  - A vendor extension MUST NOT use an `openwop-*` id without an accepted RFC.
64
86
 
65
- ## What is `ext/` (RFC 0174 §E.2; RFC 0169 §B.3)
87
+ ## What is `ext/`
88
+
89
+ `spec/v2/core/` stays within the word budget that `scripts/check-core-budget.mjs` enforces; `spec/v2/ext/` holds the rest.
90
+
91
+ - Every `spec/v2/ext/<key>/` document MUST declare `witness` and both maturity axes (`technical`, `adoption`) in its header.
92
+ - A MUST with `witness: unwitnessable` MUST NOT appear in `core/`. A document whose only witness is "deferred to Active → Accepted" enters `ext/` or is deleted.
93
+ - An `ext/` family is advertised only under a wire-legal witness class (see [`capabilities.md`](capabilities.md)).
66
94
 
67
- `spec/v2/core/` stays within the word budget `scripts/check-core-budget.mjs` enforces (RFC 0190). Every `spec/v2/ext/<key>/` document MUST declare `witness` and both maturity axes (`technical`, `adoption`) in its header. A MUST with `witness: unwitnessable` MUST NOT appear in `core/`; a document whose only witness is "deferred to Active → Accepted" enters `ext/` or is deleted. An `ext/` family is advertised only under a wire-legal witness class (see `capabilities.md`).
95
+ ## What a MUST means (Axiom 1)
68
96
 
69
- ## What a MUST means (RFC 0168 §B.1; Axiom 1)
97
+ Every MUST, SHOULD, and MAY in `core/` is a requirement with an id in `requirements.json` and a `witness` from `witnessable-unaided | witnessable-gated | seam-gated | claims-check | negative-existence`. A seam-gated MUST is governed by [`conformance.md`](conformance.md) §"Witness class".
70
98
 
71
- Every MUST, SHOULD, and MAY in `core/` is a requirement with an id in `requirements.json` and a `witness` from `witnessable-unaided | witnessable-gated | seam-gated | claims-check | negative-existence`. A seam-gated MUST is governed by `conformance.md` §"Witness class".
99
+ *Sources: RFC 0155, RFC 0167, RFC 0168, RFC 0169, RFC 0171, RFC 0174, RFC 0190, RFC 0197.*
@@ -1,39 +1,56 @@
1
1
  # Packs
2
2
 
3
- > **Status: Stable · RFC 0177.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `packs`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- The v2 contract for pack manifests, the registry tree, peer-dependency identifiers, and signing. The per-kind rules live in connection-packs.md, form-content-packs.md, and workflow-chain-packs.md; the capability vocabulary a pack requires is capabilities.md.
8
+ The v2 contract for pack manifests, the registry tree, peer-dependency identifiers, and signing. The per-kind rules live in [connection-packs.md](connection-packs.md), [form-content-packs.md](form-content-packs.md), and [workflow-chain-packs.md](workflow-chain-packs.md); the capability vocabulary a pack requires is [capabilities.md](capabilities.md).
9
9
 
10
10
  ## The engine range
11
11
 
12
- A manifest's `engines.openwop` MUST match the grammar in `schemas/v2/node-pack-manifest.schema.json`: a `>=` lower bound and an explicit `<` major ceiling (`^>=\d+(\.\d+){0,2} <\d+\.0\.0$`). A v2 host MUST treat a range with no upper bound as bounded by `<2.0.0`. A host MUST refuse to install a version whose range does not admit the host's protocol major with `pack_engine_unsupported` (`spec/v2/errors.json`); `pack_runtime_requirement_unmet` remains a runtime-requirement code and MUST NOT be used for the protocol major. The range is a claim about the pack's own surface, not about run semantics: admitting major M asserts that the manifest validates against the `schemas/v2/` manifest schema for its `kind`, that every `peerDependencies` key resolves (§"Peer-dependency identifiers"), and that the version carries a §Signing signature. Each conjunct keeps its own refusal code; a mechanical ceiling bump is not a verification. Both checks MUST run at install on every publication path — the canonical registry, a vendor registry's write API, and a mirror ingest — so no registry-side artifact can bypass it.
12
+ A manifest's `engines.openwop` MUST match the grammar in `schemas/v2/node-pack-manifest.schema.json`: a `>=` lower bound and an explicit `<` major ceiling (`^>=\d+(\.\d+){0,2} <\d+\.0\.0$`).
13
+
14
+ - A v2 host MUST treat a range with no upper bound as bounded by `<2.0.0`.
15
+ - A host MUST refuse to install a version whose range does not admit the host's protocol major with `pack_engine_unsupported` (`spec/v2/errors.json`).
16
+ - `pack_runtime_requirement_unmet` remains a runtime-requirement code and MUST NOT be used for the protocol major.
17
+ - Both checks MUST run at install on every publication path — the canonical registry, a vendor registry's write API, and a mirror ingest — so no registry-side artifact can bypass it.
18
+
19
+ The range is a claim about the pack's own surface, not about run semantics. Admitting major M asserts that:
20
+
21
+ - the manifest validates against the `schemas/v2/` manifest schema for its `kind`;
22
+ - every `peerDependencies` key resolves (§"Peer-dependency identifiers");
23
+ - the version carries a §Signing signature.
24
+
25
+ Each conjunct keeps its own refusal code. A mechanical ceiling bump is not a verification.
13
26
 
14
27
  ## The `packs` capability
15
28
 
16
- A host advertises `packs` when it serves the registry surface above. The record
17
- is the advertisement: a host MUST NOT advertise `packs` unless it resolves pack
18
- references through a registry reachable from its discovery document, and a
19
- client MUST treat an absent record as "this host installs no packs" rather than
20
- as an unknown.
29
+ A host advertises `packs` when it serves the registry surface above. The record is the advertisement:
21
30
 
22
- `testMode` is DEPRECATED and MUST NOT be relied on by a client. It advertises
23
- the v1 `/v1/packs-test/*` mirror, a conformance seam (`conformance.md`
24
- §"The seams profile"), and remains advertisable through the overlap because hosts already publish it; it is
25
- removed at 3.0. A host mounting a test catalog SHOULD advertise the seams
26
- profile instead, and MUST NOT treat `testMode` as a second way to claim one.
31
+ - A host MUST NOT advertise `packs` unless it resolves pack references through a registry reachable from its discovery document.
32
+ - A client MUST treat an absent record as "this host installs no packs", not as an unknown.
33
+
34
+ `testMode` is DEPRECATED and MUST NOT be relied on by a client (see §"During the v1 overlap"). A host mounting a test catalog SHOULD advertise the seams profile instead, and MUST NOT treat `testMode` as a second way to claim one.
27
35
 
28
36
  ## The registry tree
29
37
 
30
- The registry is versioned by tree, not header. It publishes `registry/v2/packs/<name>/-/<version>.{json,sbom.json,sig,tgz}` as a parallel tree of re-signed manifests with regenerated SBOMs and index; the v1 tree is frozen through the overlap, deliberately behind this one. A signed compatibility overlay MUST be rejected: signatures authorize by namespace, and a mirror re-derives the signer at ingest.
38
+ The registry is versioned by tree, not header. It publishes `registry/v2/packs/<name>/-/<version>.{json,sbom.json,sig,tgz}` as a parallel tree of re-signed manifests with regenerated SBOMs and index.
31
39
 
32
- `.well-known/openwop-registry.json` `endpoints` is the negotiation: it names both trees, and a client MUST resolve every registry path through it rather than construct one. `publicKey` is unversioned: keys are not protocol-versioned.
40
+ - A signed compatibility overlay MUST be rejected: signatures authorize by namespace, and a mirror re-derives the signer at ingest.
41
+ - `.well-known/openwop-registry.json` `endpoints` is the negotiation: it names both trees, and a client MUST resolve every registry path through it rather than construct one.
42
+ - `publicKey` is unversioned: keys are not protocol-versioned.
33
43
 
34
44
  ## Peer-dependency identifiers
35
45
 
36
- A `peerDependencies` key MUST be a `families[].key` in `spec/v2/declaration.json` whose `anchor` is not `deleted` — equivalently a root key of the generated `schemas/v2/capabilities.schema.json` — or carry a row in `spec/v2/peer-dependency-aliases.json`, which is how a v1-era key reaches a v2 family through the overlap; the declaration key, the peer-dependency identifier, and the capabilities.md section anchor are one identifier. A host MUST refuse a key the declaration file does not name with `pack_peer_dependency_undefined`. Facet paths are not identifiers: a pack requires a family by its key and names facets in `peerDependenciesMeta.<family>.facets[]`.
46
+ A `peerDependencies` key MUST either:
47
+
48
+ - be a `families[].key` in `spec/v2/declaration.json` whose `anchor` is not `deleted` — equivalently a root key of the generated `schemas/v2/capabilities.schema.json`; or
49
+ - carry a row in `spec/v2/peer-dependency-aliases.json` (§"The alias table").
50
+
51
+ The declaration key, the peer-dependency identifier, and the capabilities.md section anchor are one identifier. A host MUST refuse a key the declaration file does not name with `pack_peer_dependency_undefined`.
52
+
53
+ Facet paths are not identifiers: a pack requires a family by its key and names facets in `peerDependenciesMeta.<family>.facets[]`.
37
54
 
38
55
  ```jsonc
39
56
  "peerDependencies": { "aiProviders": "required" },
@@ -42,11 +59,13 @@ A `peerDependencies` key MUST be a `families[].key` in `spec/v2/declaration.json
42
59
 
43
60
  ## The alias table
44
61
 
45
- `spec/v2/peer-dependency-aliases.json` is generated from the declaration file and the published-manifest inventory, never hand-kept; each of its 23 rows is `{ alias, family, facets?, publishedUses, removalTrigger }` and covers a v1 grammar found in the wild (`host.*` dotted twins, `openwop.agents.memoryBackends`, facet paths such as `aiProviders.imageGeneration`). A v2 host MAY resolve an alias through the table during the overlap and MUST NOT resolve one after v1 end-of-support (`removalTrigger: v1-end-of-support`). A row the declaration file cannot explain fails the corpus gate.
62
+ `spec/v2/peer-dependency-aliases.json` is generated from the declaration file and the published-manifest inventory, never hand-kept. It is how a v1-era key reaches a v2 family through the overlap. Each row is `{ alias, family, facets?, publishedUses, removalTrigger }` and covers a v1 grammar found in the wild (`host.*` dotted twins, `openwop.agents.memoryBackends`, facet paths such as `aiProviders.imageGeneration`).
63
+
64
+ A v2 host MAY resolve an alias through the table during the overlap, and MUST NOT resolve one after v1 end-of-support (`removalTrigger: v1-end-of-support`).
46
65
 
47
66
  ## The manifest schema family
48
67
 
49
- The 13 manifest schemas carry `$id` under `https://openwop.dev/spec/v2/`; the v1 `$id`s are immutable and served read-only.
68
+ The manifest schemas carry `$id` under `https://openwop.dev/spec/v2/`; the v1 `$id`s are immutable and served read-only.
50
69
 
51
70
  | Schema (`schemas/v2/…`) | Author | Vendor hatch |
52
71
  | --- | --- | --- |
@@ -56,25 +75,24 @@ The 13 manifest schemas carry `$id` under `https://openwop.dev/spec/v2/`; the v1
56
75
  | `security-advisory` | registry | closed |
57
76
  | `prompt-ref` | leaf | none |
58
77
 
59
- Every pack-authored document MUST admit `patternProperties` `^(openwop-|x-|vendor\.)`. A consumer that does not recognize a hatch property MUST ignore it and MUST NOT reject the document; the value is pack-authored and therefore untrusted (security-defaults.md).
78
+ Every pack-authored document MUST admit `patternProperties` `^(openwop-|x-|vendor\.)`. A consumer that does not recognize a hatch property MUST ignore it and MUST NOT reject the document; the value is pack-authored and therefore untrusted ([security-defaults.md](security-defaults.md)).
60
79
 
61
80
  ## Signing
62
81
 
63
- There is one signing scheme. `signing` on a version manifest (`schemas/v2/registry-version-manifest.schema.json`) is `{ keyId, scheme }`, both REQUIRED:
82
+ There is one signing scheme. `signing` on a version manifest (`schemas/v2/registry-version-manifest.schema.json`) is the closed object `{ keyId, scheme }`, both REQUIRED:
64
83
 
65
- | Field | Rule |
66
- | --- | --- |
67
- | `scheme` | MUST be `ed25519-canonical-json`: a detached 64-byte Ed25519 signature over the RFC 8785 (JCS) bytes of `pack.json` inside a deterministic tarball; the input MUST satisfy `conformance.md` §"Canonical JSON" (RFC 0212) |
68
- | `keyId` | the signing key id; `publicKeyRef` does not exist |
69
- | `method` | does not exist; a manifest carrying it fails validation |
84
+ - **`scheme`** MUST be `ed25519-canonical-json`: a detached 64-byte Ed25519 signature over the RFC 8785 (JCS) bytes of `pack.json` inside a deterministic tarball. The input MUST satisfy `conformance.md` §"Canonical JSON".
85
+ - **`keyId`** is the signing key id.
86
+ - A block carrying `method`, `publicKeyRef` or `signatureRef` fails validation.
70
87
 
71
- The same block applies to a **bare manifest** — the `pack.json` inside the tarball, which is the document the signature covers. `signing` is OPTIONAL there, because an authoring-time `pack.json` exists before it is signed; when present it MUST be the identical closed `{ keyId, scheme }` object, and the v1 `{ publicKeyRef, signatureRef, method }` block fails validation on every bare manifest kind (migration row `openwop.migration.C10.1`).
88
+ The same block applies to a **bare manifest** — the `pack.json` inside the tarball, which is the document the signature covers. `signing` is OPTIONAL there, because an authoring-time `pack.json` exists before it is signed. When present it MUST be the identical closed `{ keyId, scheme }` object, on every bare manifest kind.
72
89
 
73
- A verifier MUST verify the signature against the issuing registry's key for `keyId` and MUST check the pack name against that key's `permittedNamespaces`. A signature over tarball bytes is not a v2 signature; such a pack MUST be re-signed, not relabeled.
90
+ - A verifier MUST verify the signature against the issuing registry's key for `keyId`, and MUST check the pack name against that key's `permittedNamespaces`.
91
+ - A signature over tarball bytes is not a v2 signature; such a pack MUST be re-signed, not relabeled.
74
92
 
75
93
  ## Version manifests
76
94
 
77
- `kind` is REQUIRED on every version manifest and every bare manifest; the v1 "absent means `node`" reading does not exist. A deprecated version is flagged `versionDeprecated: true`; the registry continues to serve it and a consumer MAY refuse to install it.
95
+ `kind` is REQUIRED on every version manifest and every bare manifest. A deprecated version is flagged `versionDeprecated: true`; the registry continues to serve it, and a consumer MAY refuse to install it.
78
96
 
79
97
  ## The registry's own schemas
80
98
 
@@ -87,3 +105,10 @@ A registry MUST validate submissions against vendored copies of these schemas pi
87
105
  | `pack_engine_unsupported` | the range does not admit the host's protocol major (install, every path) |
88
106
  | `pack_peer_dependency_undefined` | a peer-dependency key is not a declaration-file key or an overlap alias |
89
107
  | `pack_signature_invalid` | the signature, key, or namespace check fails |
108
+
109
+ ## During the v1 overlap
110
+
111
+ - The v1 registry tree is frozen through the overlap, behind the v2 tree.
112
+ - `testMode` advertises the v1 `/v1/packs-test/*` mirror, a conformance seam ([conformance.md §"The seams profile"](conformance.md)). It remains advertisable through the overlap and is removed at 3.0.
113
+
114
+ *Sources: RFC 0177, RFC 0212.*