@openwop/spec-artifacts 2.0.0-rc.8 → 2.0.0

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 (40) hide show
  1. package/CORPUS-STAMP.json +41 -38
  2. package/api/seams-v2.yaml +1 -1
  3. package/api/v2/asyncapi.yaml +1 -1
  4. package/api/v2/openapi.yaml +17 -19
  5. package/package.json +18 -5
  6. package/schemas/capabilities.schema.json +40 -1
  7. package/schemas/run-snapshot.schema.json +103 -33
  8. package/schemas/v2/agent-deployment-transition.schema.json +5 -6
  9. package/schemas/v2/agent-deployment.schema.json +8 -9
  10. package/schemas/v2/agent-eval-suite.schema.json +11 -12
  11. package/schemas/v2/artifact-type-pack-manifest.schema.json +22 -18
  12. package/schemas/v2/capabilities.schema.json +39 -0
  13. package/schemas/v2/certification-bundle.schema.json +1 -1
  14. package/schemas/v2/chat-card-pack-manifest.schema.json +19 -15
  15. package/schemas/v2/debug-bundle.schema.json +6 -6
  16. package/schemas/v2/effect-ledger-projection.schema.json +4 -3
  17. package/schemas/v2/effect-seam-manifest.schema.json +30 -7
  18. package/schemas/v2/error-envelope.schema.json +185 -138
  19. package/schemas/v2/eval-summary.schema.json +17 -10
  20. package/schemas/v2/goal.schema.json +5 -5
  21. package/schemas/v2/ids.schema.json +120 -22
  22. package/schemas/v2/node-pack-manifest.schema.json +27 -31
  23. package/schemas/v2/proposal.schema.json +7 -8
  24. package/schemas/v2/run-event-payloads.schema.json +258 -245
  25. package/schemas/v2/run-event.schema.json +2 -126
  26. package/schemas/v2/run-options.schema.json +1 -1
  27. package/schemas/v2/run-snapshot.schema.json +34 -13
  28. package/schemas/v2/webhook-delivery.schema.json +26 -0
  29. package/schemas/v2/workflow-chain-pack-manifest.schema.json +41 -45
  30. package/schemas/v2/workflow-definition.schema.json +31 -36
  31. package/spec/v1/core-standard-manifest.json +2 -2
  32. package/spec/v1/deprecations.json +79 -67
  33. package/spec/v1/deprecations.schema.json +23 -7
  34. package/spec/v1/extensions.json +3 -2
  35. package/spec/v2/declaration.json +63 -32
  36. package/spec/v2/errors.json +421 -360
  37. package/spec/v2/id-field-bindings.json +105 -0
  38. package/spec/v2/profiles.json +31 -7
  39. package/spec/v2/release.json +4 -4
  40. package/spec/v2/retention-floors.json +35 -0
@@ -1,31 +1,129 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://openwop.dev/spec/v2/ids.schema.json",
4
- "title": "OpenWOP v2 identifier grammars (RFC 0170 §D.1)",
4
+ "title": "OpenWOP v2 identifier grammars (RFC 0170 \u00a7D.1)",
5
5
  "description": "One $defs entry per id kind. Every id field in every v2 schema, OpenAPI parameter and response body $refs its kind. `minted` records who mints the id: host (opaque, checkable: `^[A-Za-z0-9._~-]{16,128}$` after any tenant segment), author (a workflow/pack author chooses it; the v1 grammar stands), registry (the pack registry mints it). Tenant-bound kinds (runId, interruptId, subscriptionId, deliveryId, effectId) carry the tenant segment: a host MUST reject an id whose tenant segment is not the caller's.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "$defs": {
9
- "opaque": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{16,128}$", "description": "Host-minted opaque token: no `@`, no whitespace, no `/`; 16–128 characters." },
10
- "tenantId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}$", "x-openwop-minted": "host" },
11
- "workspaceId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}$", "x-openwop-minted": "host" },
12
- "subjectId": { "type": "string", "pattern": "^[^\\s/]{1,256}$", "description": "Issuer-scoped, stable, opaque (RFC 0170 §A); the issuer's grammar, never PII.", "x-openwop-minted": "host" },
13
- "runId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$", "description": "Tenant-bound: `<tenantId>/<opaque>`.", "x-openwop-minted": "host", "x-openwop-tenant-bound": true },
14
- "interruptId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$", "x-openwop-minted": "host", "x-openwop-tenant-bound": true },
15
- "subscriptionId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$", "x-openwop-minted": "host", "x-openwop-tenant-bound": true },
16
- "deliveryId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$", "x-openwop-minted": "host", "x-openwop-tenant-bound": true },
17
- "effectId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$", "x-openwop-minted": "host", "x-openwop-tenant-bound": true },
18
- "eventId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{16,128}$", "x-openwop-minted": "host" },
19
- "traceId": { "type": "string", "pattern": "^[0-9a-f]{32}$", "description": "W3C trace-context trace-id.", "x-openwop-minted": "host" },
20
- "spanId": { "type": "string", "pattern": "^[0-9a-f]{16}$", "description": "W3C trace-context parent-id.", "x-openwop-minted": "host" },
21
- "keyId": { "type": "string", "pattern": "^[A-Za-z0-9._~-]{1,128}$", "description": "A signing-key identifier (registry `signingKeys[].keyId`, resume-token `kid`, bundle signature `keyId`).", "x-openwop-minted": "registry" },
22
- "nodeId": { "type": "string", "pattern": "^[A-Za-z0-9._~:-]{1,128}$", "description": "Author-chosen within a workflow definition.", "x-openwop-minted": "author" },
23
- "workflowId": { "type": "string", "pattern": "^[A-Za-z0-9._~:-]{1,128}$", "x-openwop-minted": "author" },
24
- "agentId": { "type": "string", "pattern": "^[A-Za-z0-9._~:-]{1,128}$", "x-openwop-minted": "author" },
25
- "chainId": { "type": "string", "pattern": "^[A-Za-z0-9._~:-]{1,128}$", "x-openwop-minted": "author" },
26
- "pluginId": { "type": "string", "pattern": "^[A-Za-z0-9._~:-]{1,128}$", "x-openwop-minted": "author" },
27
- "templateId": { "type": "string", "pattern": "^[A-Za-z0-9._~:-]{1,128}$", "x-openwop-minted": "author" },
28
- "libraryId": { "type": "string", "pattern": "^[A-Za-z0-9._~:-]{1,128}$", "x-openwop-minted": "author" },
29
- "typeId": { "type": "string", "pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-zA-Z0-9-]*)+$", "description": "A node type id (`core.ai.callPrompt`); the RFC 0107 grammar.", "x-openwop-minted": "author" }
9
+ "opaque": {
10
+ "type": "string",
11
+ "pattern": "^[A-Za-z0-9._~-]{16,128}$",
12
+ "description": "Host-minted opaque token: no `@`, no whitespace, no `/`; 16\u2013128 characters."
13
+ },
14
+ "tenantId": {
15
+ "type": "string",
16
+ "pattern": "^(anon:)?[A-Za-z0-9._~-]{1,128}$",
17
+ "x-openwop-minted": "host",
18
+ "description": "A tenant identifier. The optional `anon:` prefix is the ONLY colon admitted, and it exists because RFC 0132 anonymous tenants are `anon:<hash>` while RFC 0170 \u00a7A.5 makes the anonymous surface first-class \u2014 without it a conforming host can serve an anonymous run with neither a valid `owner.tenant` nor a valid tenant-bound `runId`. `:` is a legal path character (RFC 3986 pchar), so this stays safe inside the `<tenantId>/<opaque>` run-id form. The alternative \u2014 renaming anonymous tenants at the cut \u2014 was rejected: it is a write to historical identity, which is the one thing the v2 migration does not do."
19
+ },
20
+ "workspaceId": {
21
+ "type": "string",
22
+ "pattern": "^[A-Za-z0-9._~-]{1,128}$",
23
+ "x-openwop-minted": "host"
24
+ },
25
+ "subjectId": {
26
+ "type": "string",
27
+ "pattern": "^[^\\s/]{1,256}$",
28
+ "description": "Issuer-scoped, stable, opaque (RFC 0170 \u00a7A); the issuer's grammar, never PII.",
29
+ "x-openwop-minted": "host"
30
+ },
31
+ "runId": {
32
+ "type": "string",
33
+ "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$",
34
+ "description": "Tenant-bound: `<tenantId>/<opaque>`.",
35
+ "x-openwop-minted": "host",
36
+ "x-openwop-tenant-bound": true
37
+ },
38
+ "interruptId": {
39
+ "type": "string",
40
+ "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$",
41
+ "x-openwop-minted": "host",
42
+ "x-openwop-tenant-bound": true
43
+ },
44
+ "subscriptionId": {
45
+ "type": "string",
46
+ "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$",
47
+ "x-openwop-minted": "host",
48
+ "x-openwop-tenant-bound": true
49
+ },
50
+ "deliveryId": {
51
+ "type": "string",
52
+ "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$",
53
+ "x-openwop-minted": "host",
54
+ "x-openwop-tenant-bound": true
55
+ },
56
+ "effectId": {
57
+ "type": "string",
58
+ "pattern": "^[A-Za-z0-9._~-]{1,128}/[A-Za-z0-9._~-]{16,128}$",
59
+ "x-openwop-minted": "host",
60
+ "x-openwop-tenant-bound": true
61
+ },
62
+ "eventId": {
63
+ "type": "string",
64
+ "pattern": "^[A-Za-z0-9._~-]{16,128}$",
65
+ "x-openwop-minted": "host"
66
+ },
67
+ "traceId": {
68
+ "type": "string",
69
+ "pattern": "^[0-9a-f]{32}$",
70
+ "description": "W3C trace-context trace-id.",
71
+ "x-openwop-minted": "host"
72
+ },
73
+ "spanId": {
74
+ "type": "string",
75
+ "pattern": "^[0-9a-f]{16}$",
76
+ "description": "W3C trace-context parent-id.",
77
+ "x-openwop-minted": "host"
78
+ },
79
+ "keyId": {
80
+ "type": "string",
81
+ "pattern": "^[A-Za-z0-9._~-]{1,128}$",
82
+ "description": "A signing-key identifier (registry `signingKeys[].keyId`, resume-token `kid`, bundle signature `keyId`).",
83
+ "x-openwop-minted": "registry"
84
+ },
85
+ "nodeId": {
86
+ "type": "string",
87
+ "pattern": "^[A-Za-z0-9._~:-]{1,128}$",
88
+ "description": "Author-chosen within a workflow definition.",
89
+ "x-openwop-minted": "author"
90
+ },
91
+ "workflowId": {
92
+ "type": "string",
93
+ "pattern": "^[A-Za-z0-9._~:-]{1,128}$",
94
+ "x-openwop-minted": "author"
95
+ },
96
+ "agentId": {
97
+ "type": "string",
98
+ "pattern": "^[A-Za-z0-9._~:-]{1,128}$",
99
+ "x-openwop-minted": "author"
100
+ },
101
+ "chainId": {
102
+ "type": "string",
103
+ "pattern": "^[A-Za-z0-9._~:-]{1,128}$",
104
+ "x-openwop-minted": "author"
105
+ },
106
+ "pluginId": {
107
+ "type": "string",
108
+ "pattern": "^[A-Za-z0-9._~:-]{1,128}$",
109
+ "x-openwop-minted": "author"
110
+ },
111
+ "templateId": {
112
+ "type": "string",
113
+ "pattern": "^[A-Za-z0-9._~:-]{1,128}$",
114
+ "x-openwop-minted": "author"
115
+ },
116
+ "libraryId": {
117
+ "type": "string",
118
+ "pattern": "^[A-Za-z0-9._~:-]{1,128}$",
119
+ "x-openwop-minted": "author"
120
+ },
121
+ "typeId": {
122
+ "type": "string",
123
+ "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z][a-zA-Z0-9_-]*)+$",
124
+ "description": "A node type id (`core.ai.callPrompt`); the RFC 0107 grammar. Underscore is admitted because node-pack-manifest.schema.json's `name` pattern admits it and a pack's node type ids are DERIVED from its name \u2014 a pack legally named `vendor.acme.my_tools` must be able to declare `vendor.acme.my_tools.echo`. Without the underscore this kind rejected type ids that a legal pack name generates, which is a constraint that cannot express a legitimate value. maxLength is present because this was the ONLY kind whose pattern left length unbounded \u2014 every other $def bounds it inside the pattern ({1,128}, {16,128}). 256 is taken from the bound node-pack-manifest already carried locally, so this relocates it rather than inventing one, and binding those properties to this kind no longer drops it.",
125
+ "x-openwop-minted": "author",
126
+ "maxLength": 256
127
+ }
30
128
  }
31
129
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://openwop.dev/spec/v2/node-pack-manifest.schema.json",
4
4
  "title": "NodePackManifest",
5
- "description": "Manifest for a published OpenWOP node pack `pack.json` at the pack root. Language-neutral. See node-packs.md for the canonical contract.",
5
+ "description": "Manifest for a published OpenWOP node pack \u2014 `pack.json` at the pack root. Language-neutral. See node-packs.md for the canonical contract.",
6
6
  "type": "object",
7
7
  "required": [
8
8
  "engines",
@@ -39,11 +39,11 @@
39
39
  "kind": {
40
40
  "type": "string",
41
41
  "const": "node",
42
- "description": "Pack kind discriminator. For node packs (the default and original kind) `kind` is either omitted entirely OR set to the literal string `\"node\"`. Manifests carrying `kind: \"workflow-chain\"` validate against `workflow-chain-pack-manifest.schema.json` instead see workflow-chain-packs.md and RFC 0013."
42
+ "description": "Pack kind discriminator. For node packs (the default and original kind) `kind` is either omitted entirely OR set to the literal string `\"node\"`. Manifests carrying `kind: \"workflow-chain\"` validate against `workflow-chain-pack-manifest.schema.json` instead \u2014 see workflow-chain-packs.md and RFC 0013."
43
43
  },
44
44
  "name": {
45
45
  "type": "string",
46
- "description": "Reverse-DNS pack name. Reserved scopes: `core.*` (spec-canonical), `vendor.<org>.*` (vendor-published), `community.<author>.*` (individual), `private.<host>.*` (host-internal MUST NOT appear in `packs.openwop.dev`). `local.*` is for in-repo unpublished packs and MUST NOT appear in any registry. See node-packs.md §Naming for the full reservation table.",
46
+ "description": "Reverse-DNS pack name. Reserved scopes: `core.*` (spec-canonical), `vendor.<org>.*` (vendor-published), `community.<author>.*` (individual), `private.<host>.*` (host-internal \u2014 MUST NOT appear in `packs.openwop.dev`). `local.*` is for in-repo unpublished packs and MUST NOT appear in any registry. See node-packs.md \u00a7Naming for the full reservation table.",
47
47
  "pattern": "^(core|vendor|community|private)\\.[a-z][a-z0-9_-]*(\\.[a-z][a-zA-Z0-9_-]*)+$",
48
48
  "minLength": 1,
49
49
  "maxLength": 256
@@ -90,7 +90,7 @@
90
90
  "openwop": {
91
91
  "type": "string",
92
92
  "pattern": "^>=\\d+(\\.\\d+){0,2} <\\d+\\.0\\.0$",
93
- "description": "RFC 0177 §A.1 explicit upper bound required."
93
+ "description": "RFC 0177 \u00a7A.1 \u2014 explicit upper bound required."
94
94
  }
95
95
  }
96
96
  },
@@ -99,18 +99,18 @@
99
99
  "additionalProperties": {
100
100
  "type": "string"
101
101
  },
102
- "description": "Other node packs this pack depends on. Map of pack name semver range. Engine resolves transitively at workflow-register time."
102
+ "description": "Other node packs this pack depends on. Map of pack name \u2192 semver range. Engine resolves transitively at workflow-register time."
103
103
  },
104
104
  "peerDependencies": {
105
105
  "type": "object",
106
106
  "additionalProperties": {
107
107
  "type": "string"
108
108
  },
109
- "description": "Engine-supplied capabilities the pack consumes (e.g., a particular AI provider extension). Resolved against `Capabilities` at register time. Each entry is REQUIRED by default an unmet entry fails install with `pack_peer_dependency_missing` unless marked optional in `peerDependenciesMeta` (RFC 0072 §C)."
109
+ "description": "Engine-supplied capabilities the pack consumes (e.g., a particular AI provider extension). Resolved against `Capabilities` at register time. Each entry is REQUIRED by default \u2014 an unmet entry fails install with `pack_peer_dependency_missing` \u2014 unless marked optional in `peerDependenciesMeta` (RFC 0072 \u00a7C)."
110
110
  },
111
111
  "peerDependenciesMeta": {
112
112
  "type": "object",
113
- "description": "Per-peer-dependency metadata (RFC 0072 §C). Keys mirror `peerDependencies` keys. Marking an entry `optional: true` makes it degrade-if-unmet instead of refuse-if-unmet: a host that does not satisfy it MUST install the pack with that capability inert AND surface it in the affected agents' inventory `degraded[]` (RFC 0072 §A); it MUST NOT silently ignore the declared dependency. Default (absent entry) is required. Packs published before RFC 0072 validate unchanged.",
113
+ "description": "Per-peer-dependency metadata (RFC 0072 \u00a7C). Keys mirror `peerDependencies` keys. Marking an entry `optional: true` makes it degrade-if-unmet instead of refuse-if-unmet: a host that does not satisfy it MUST install the pack with that capability inert AND surface it in the affected agents' inventory `degraded[]` (RFC 0072 \u00a7A); it MUST NOT silently ignore the declared dependency. Default (absent entry) is required. Packs published before RFC 0072 validate unchanged.",
114
114
  "additionalProperties": {
115
115
  "type": "object",
116
116
  "additionalProperties": false,
@@ -128,7 +128,7 @@
128
128
  "type": "string",
129
129
  "pattern": "^[a-z][A-Za-z0-9]*$"
130
130
  },
131
- "description": "Facets of the peer family the pack needs (packs.md §Peer dependencies; RFC 0177 §B.1). Each MUST be a facet the declaration file lists for that family; the family key alone means the family's presence."
131
+ "description": "Facets of the peer family the pack needs (packs.md \u00a7Peer dependencies; RFC 0177 \u00a7B.1). Each MUST be a facet the declaration file lists for that family; the family key alone means the family's presence."
132
132
  }
133
133
  }
134
134
  }
@@ -138,14 +138,14 @@
138
138
  "items": {
139
139
  "$ref": "#/$defs/PackNode"
140
140
  },
141
- "description": "Node typeIds the pack contributes. Each MUST have a unique `typeId` within the pack. A pack MAY omit `nodes` entirely if it ships only `agents[]` the top-level `anyOf` constraint requires at least one of `nodes` or `agents` to be non-empty."
141
+ "description": "Node typeIds the pack contributes. Each MUST have a unique `typeId` within the pack. A pack MAY omit `nodes` entirely if it ships only `agents[]` \u2014 the top-level `anyOf` constraint requires at least one of `nodes` or `agents` to be non-empty."
142
142
  },
143
143
  "agents": {
144
144
  "type": "array",
145
145
  "items": {
146
146
  "$ref": "agent-manifest.schema.json"
147
147
  },
148
- "description": "Optional agent manifests shipped alongside this pack. Each entry is an AgentManifest (see agent-manifest.schema.json + RFC 0003 §`agents[]` extension). Pure-agent packs MUST set `runtime.language: 'remote'` agents are interpreted by the host, not bundled as executable artifacts. Mixed packs (nodes + agents) declare the runtime that loads the node implementations; agents remain host-interpreted."
148
+ "description": "Optional agent manifests shipped alongside this pack. Each entry is an AgentManifest (see agent-manifest.schema.json + RFC 0003 \u00a7`agents[]` extension). Pure-agent packs MUST set `runtime.language: 'remote'` \u2014 agents are interpreted by the host, not bundled as executable artifacts. Mixed packs (nodes + agents) declare the runtime that loads the node implementations; agents remain host-interpreted."
149
149
  },
150
150
  "runtime": {
151
151
  "$ref": "#/$defs/Runtime"
@@ -169,15 +169,12 @@
169
169
  ],
170
170
  "properties": {
171
171
  "typeId": {
172
- "type": "string",
173
- "description": "Canonical node type ID. MUST match the same pattern as `WorkflowNode.typeId` in workflow-definition.schema.json. The pack's `name` prefix is recommended (e.g., a pack `vendor.acme.salesforce-tools` exposing `vendor.acme.salesforce.upsert`).",
174
- "pattern": "^[a-z][a-zA-Z0-9._-]*$",
175
- "minLength": 1,
176
- "maxLength": 256
172
+ "$ref": "ids.schema.json#/$defs/typeId",
173
+ "description": "Canonical node type ID. MUST match the same pattern as `WorkflowNode.typeId` in workflow-definition.schema.json. The pack's `name` prefix is recommended (e.g., a pack `vendor.acme.salesforce-tools` exposing `vendor.acme.salesforce.upsert`)."
177
174
  },
178
175
  "version": {
179
176
  "type": "string",
180
- "description": "Per-node semver. MAY differ from the pack's overall version useful when a single pack ships multiple nodes that evolve independently."
177
+ "description": "Per-node semver. MAY differ from the pack's overall version \u2014 useful when a single pack ships multiple nodes that evolve independently."
181
178
  },
182
179
  "label": {
183
180
  "type": "string",
@@ -236,7 +233,7 @@
236
233
  "sensitive": {
237
234
  "type": "boolean",
238
235
  "default": false,
239
- "description": "When true, the engine masks this output port's value in `node.completed` event payloads. Use when a NodeModule ALWAYS handles sensitive data (e.g., a Salesforce upsert always touches PII). Workflows MAY override per-instance via `WorkflowNode.outputSensitivity`. See observability.md §Privacy classification (closes O5)."
236
+ "description": "When true, the engine masks this output port's value in `node.completed` event payloads. Use when a NodeModule ALWAYS handles sensitive data (e.g., a Salesforce upsert always touches PII). Workflows MAY override per-instance via `WorkflowNode.outputSensitivity`. See observability.md \u00a7Privacy classification (closes O5)."
240
237
  }
241
238
  },
242
239
  "additionalProperties": false
@@ -251,7 +248,7 @@
251
248
  "type": "object",
252
249
  "properties": {
253
250
  "typeId": {
254
- "type": "string"
251
+ "$ref": "ids.schema.json#/$defs/typeId"
255
252
  },
256
253
  "syncOn": {
257
254
  "type": "string",
@@ -305,7 +302,7 @@
305
302
  },
306
303
  "uniqueItems": true,
307
304
  "maxItems": 32,
308
- "description": "RFC 0031 §B. Model capabilities this NodeModule requires the active model to advertise in `capabilities.modelCapabilities.advertised[]`. Distinct from `requires`, which gates on HOST capabilities (`capabilities.runtimeCapabilities[]`) this field gates on MODEL capabilities. Spec-reserved identifiers: `structured-output`, `discriminator-enum`, `long-context`, `reasoning` (model-native thinking-tokens), `function-calling`. Host-private extensions MUST prefix with `x-host-<host>-` per `host-extensions.md` §\"Canonical-prefix table\". Empty array (or absent field) means no model-capability requirements. When the active model does not satisfy the declared set, the host MUST follow the dispatch flow in RFC 0031 §B: substitute to `fallbackModel` (emit `model.capability.substituted`) or refuse (emit `model.capability.insufficient` + terminate with `capability_not_provided`)."
305
+ "description": "RFC 0031 \u00a7B. Model capabilities this NodeModule requires the active model to advertise in `capabilities.modelCapabilities.advertised[]`. Distinct from `requires`, which gates on HOST capabilities (`capabilities.runtimeCapabilities[]`) \u2014 this field gates on MODEL capabilities. Spec-reserved identifiers: `structured-output`, `discriminator-enum`, `long-context`, `reasoning` (model-native thinking-tokens), `function-calling`. Host-private extensions MUST prefix with `x-host-<host>-` per `host-extensions.md` \u00a7\"Canonical-prefix table\". Empty array (or absent field) means no model-capability requirements. When the active model does not satisfy the declared set, the host MUST follow the dispatch flow in RFC 0031 \u00a7B: substitute to `fallbackModel` (emit `model.capability.substituted`) or refuse (emit `model.capability.insufficient` + terminate with `capability_not_provided`)."
309
306
  },
310
307
  "fallbackModel": {
311
308
  "type": "object",
@@ -314,7 +311,7 @@
314
311
  "provider",
315
312
  "model"
316
313
  ],
317
- "description": "RFC 0031 §B. Substitute model coordinates the host MAY use if the active model lacks the declared `requiredModelCapabilities`. When the host substitutes, it MUST emit `model.capability.substituted` per RFC 0031 §D. Absent means no substitution permitted; the host MUST refuse to dispatch with `capability_not_provided` when capabilities are unmet. The fallback is single-shot if the fallback itself fails capability checks or authentication, the host emits `model.capability.insufficient` with `fallbackAttempted: true` and refuses (no recursive fallback chains per RFC 0031 §\"Unresolved questions\" #3).",
314
+ "description": "RFC 0031 \u00a7B. Substitute model coordinates the host MAY use if the active model lacks the declared `requiredModelCapabilities`. When the host substitutes, it MUST emit `model.capability.substituted` per RFC 0031 \u00a7D. Absent means no substitution permitted; the host MUST refuse to dispatch with `capability_not_provided` when capabilities are unmet. The fallback is single-shot \u2014 if the fallback itself fails capability checks or authentication, the host emits `model.capability.insufficient` with `fallbackAttempted: true` and refuses (no recursive fallback chains per RFC 0031 \u00a7\"Unresolved questions\" #3).",
318
315
  "properties": {
319
316
  "provider": {
320
317
  "type": "string",
@@ -332,7 +329,7 @@
332
329
  "additionalProperties": false,
333
330
  "patternProperties": {
334
331
  "^(openwop-|x-|vendor\\.)": {
335
- "description": "Vendor / host extension escape hatch (RFC 0138). A property matching `^(x-|vendor\\.)` is a HOST EXTENSION: its semantics are unspecified by this protocol, and a consumer that does not recognize one MUST ignore it rather than reject the pack. Resolves the contradiction in `host-extensions.md` §\"Vendor-prefixed namespaces\", which requires a client receiving an unknown vendor-prefixed field to treat it as opaque a field that `additionalProperties: false` previously made impossible to carry. SECURITY: an extension value is PACK-AUTHORED, therefore untrusted; \"ignore\" means ignore. See `node-packs.md` §\"Vendor extensions on pack manifests\" and the `pack-manifest-extension-opaque` invariant."
332
+ "description": "Vendor / host extension escape hatch (RFC 0138). A property matching `^(x-|vendor\\.)` is a HOST EXTENSION: its semantics are unspecified by this protocol, and a consumer that does not recognize one MUST ignore it rather than reject the pack. Resolves the contradiction in `host-extensions.md` \u00a7\"Vendor-prefixed namespaces\", which requires a client receiving an unknown vendor-prefixed field to treat it as opaque \u2014 a field that `additionalProperties: false` previously made impossible to carry. SECURITY: an extension value is PACK-AUTHORED, therefore untrusted; \"ignore\" means ignore. See `node-packs.md` \u00a7\"Vendor extensions on pack manifests\" and the `pack-manifest-extension-opaque` invariant."
336
333
  }
337
334
  }
338
335
  },
@@ -478,7 +475,7 @@
478
475
  "id",
479
476
  "displayName"
480
477
  ],
481
- "description": "RFC 0045. Declares this pack a named connector a typed integration exposing actions (and reusing the existing trigger model). Optional; packs without it remain plain node packs. Actions are normal side-effectful nodes from this pack's `nodes[]` annotated with scheduler hints; the connector block adds metadata, not a new execution kind.",
478
+ "description": "RFC 0045. Declares this pack a named connector \u2014 a typed integration exposing actions (and reusing the existing trigger model). Optional; packs without it remain plain node packs. Actions are normal side-effectful nodes from this pack's `nodes[]` annotated with scheduler hints; the connector block adds metadata, not a new execution kind.",
482
479
  "properties": {
483
480
  "id": {
484
481
  "type": "string",
@@ -504,8 +501,7 @@
504
501
  ],
505
502
  "properties": {
506
503
  "typeId": {
507
- "type": "string",
508
- "minLength": 1,
504
+ "$ref": "ids.schema.json#/$defs/typeId",
509
505
  "description": "MUST match a `nodes[].typeId` in this manifest."
510
506
  },
511
507
  "displayName": {
@@ -514,7 +510,7 @@
514
510
  },
515
511
  "idempotent": {
516
512
  "type": "boolean",
517
- "description": "Action is safe to auto-retry without an idempotency key. Absent/false the host MUST NOT auto-retry without one (composes with idempotency.md)."
513
+ "description": "Action is safe to auto-retry without an idempotency key. Absent/false \u21d2 the host MUST NOT auto-retry without one (composes with idempotency.md)."
518
514
  },
519
515
  "rateLimit": {
520
516
  "type": "object",
@@ -567,7 +563,7 @@
567
563
  "wasm-component",
568
564
  "remote"
569
565
  ],
570
- "description": "How the engine loads the pack. See node-packs.md §runtime formats. `wasm` is the core-module WASM ABI (RFC 0008). `wasm-component` is the WASM Component Model variant (WIT-defined interfaces); hosts that advertise `capabilities.nodePackRuntimes.wasmComponent.supported: true` load it via wasmtime / wasmer Component Model runtimes. Both share the openwop ABI envelope; the wasm-component variant uses WIT interfaces instead of hand-rolled imports/exports."
566
+ "description": "How the engine loads the pack. See node-packs.md \u00a7runtime formats. `wasm` is the core-module WASM ABI (RFC 0008). `wasm-component` is the WASM Component Model variant (WIT-defined interfaces); hosts that advertise `capabilities.nodePackRuntimes.wasmComponent.supported: true` load it via wasmtime / wasmer Component Model runtimes. Both share the openwop ABI envelope; the wasm-component variant uses WIT interfaces instead of hand-rolled imports/exports."
571
567
  },
572
568
  "entry": {
573
569
  "type": "string",
@@ -593,7 +589,7 @@
593
589
  "requires": {
594
590
  "type": "array",
595
591
  "uniqueItems": true,
596
- "description": "RFC 0076. Abstract platform primitives the pack's runtime code exercises, for install-time sandbox gating. Runtime-agnostic (not language builtin names). Absent or [] no elevated platform needs. A sandbox host MUST evaluate this at install time and refuse (`pack_runtime_requirement_unmet`) any primitive it will not grant; see node-packs.md §\"Runtime platform requirements\".",
592
+ "description": "RFC 0076. Abstract platform primitives the pack's runtime code exercises, for install-time sandbox gating. Runtime-agnostic (not language builtin names). Absent or [] \u21d2 no elevated platform needs. A sandbox host MUST evaluate this at install time and refuse (`pack_runtime_requirement_unmet`) any primitive it will not grant; see node-packs.md \u00a7\"Runtime platform requirements\".",
597
593
  "items": {
598
594
  "oneOf": [
599
595
  {
@@ -626,7 +622,7 @@
626
622
  },
627
623
  {
628
624
  "const": "clock",
629
- "description": "Reads wall-clock time as a behavioral input gated for REPLAY determinism, not access control. A pack that branches on the clock is non-deterministic on replay (replay.md)."
625
+ "description": "Reads wall-clock time as a behavioral input \u2014 gated for REPLAY determinism, not access control. A pack that branches on the clock is non-deterministic on replay (replay.md)."
630
626
  }
631
627
  ]
632
628
  }
@@ -636,7 +632,7 @@
636
632
  },
637
633
  "Signing": {
638
634
  "type": "object",
639
- "description": "Optional signing metadata. See node-packs.md §signing.",
635
+ "description": "Optional signing metadata. See node-packs.md \u00a7signing.",
640
636
  "properties": {
641
637
  "publicKeyRef": {
642
638
  "type": "string",
@@ -660,8 +656,8 @@
660
656
  },
661
657
  "patternProperties": {
662
658
  "^(openwop-|x-|vendor\\.)": {
663
- "description": "Vendor / host extension escape hatch (RFC 0138). A property matching `^(x-|vendor\\.)` is a HOST EXTENSION: its semantics are unspecified by this protocol, and a consumer that does not recognize one MUST ignore it rather than reject the pack. Resolves the contradiction in `host-extensions.md` §\"Vendor-prefixed namespaces\", which requires a client receiving an unknown vendor-prefixed field to treat it as opaque a field that `additionalProperties: false` previously made impossible to carry. SECURITY: an extension value is PACK-AUTHORED, therefore untrusted; \"ignore\" means ignore. See `node-packs.md` §\"Vendor extensions on pack manifests\" and the `pack-manifest-extension-opaque` invariant."
659
+ "description": "Vendor / host extension escape hatch (RFC 0138). A property matching `^(x-|vendor\\.)` is a HOST EXTENSION: its semantics are unspecified by this protocol, and a consumer that does not recognize one MUST ignore it rather than reject the pack. Resolves the contradiction in `host-extensions.md` \u00a7\"Vendor-prefixed namespaces\", which requires a client receiving an unknown vendor-prefixed field to treat it as opaque \u2014 a field that `additionalProperties: false` previously made impossible to carry. SECURITY: an extension value is PACK-AUTHORED, therefore untrusted; \"ignore\" means ignore. See `node-packs.md` \u00a7\"Vendor extensions on pack manifests\" and the `pack-manifest-extension-opaque` invariant."
664
660
  }
665
661
  },
666
- "x-openwop-anyOf-note": "constraint fragments (kind-conditional requirements) closure is the root's"
662
+ "x-openwop-anyOf-note": "constraint fragments (kind-conditional requirements) \u2014 closure is the root's"
667
663
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://openwop.dev/spec/v2/proposal.schema.json",
4
4
  "title": "Proposal",
5
- "description": "RFC 0096 §B. A reviewable-learning proposal an INERT, draft-state reusable artifact (skill/pack/template/automation) the host synthesized from run/tool traces, that MUST NOT influence the resolution, planning, or execution of any run while in any state other than `applied`. Activation is delegated to RFC 0051 approval-gate or RFC 0049 RBAC (no new authorization path). On `apply` the installed artifact MUST byte-match the last-persisted `artifact` (no silent re-synthesis). `rationale` and `provenance.sourceRunIds` are SR-1 redaction-safe (no secrets/PII). Gated on the `agents.proposals` capability.",
5
+ "description": "RFC 0096 \u00a7B. A reviewable-learning proposal \u2014 an INERT, draft-state reusable artifact (skill/pack/template/automation) the host synthesized from run/tool traces, that MUST NOT influence the resolution, planning, or execution of any run while in any state other than `applied`. Activation is delegated to RFC 0051 approval-gate or RFC 0049 RBAC (no new authorization path). On `apply` the installed artifact MUST byte-match the last-persisted `artifact` (no silent re-synthesis). `rationale` and `provenance.sourceRunIds` are SR-1 redaction-safe (no secrets/PII). Gated on the `agents.proposals` capability.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": [
@@ -47,12 +47,12 @@
47
47
  },
48
48
  "rationale": {
49
49
  "type": "string",
50
- "description": "Plain-language justification. SR-1 redaction-safe no secrets/PII."
50
+ "description": "Plain-language justification. SR-1 redaction-safe \u2014 no secrets/PII."
51
51
  },
52
52
  "artifact": {
53
53
  "type": "object",
54
- "description": "The proposed artifact, shaped by `kind` (an RFC 0003 pack manifest, an RFC 0013 chain pack, an RFC 0027 template, an RFC 0052 job spec). Inert until applied. Deliberately open (`additionalProperties` not constrained here) because the payload conforms to the kind's own existing schema, validated by the host at `apply` (malformed-for-kind 422).",
55
- "$comment": "Intentionally open object the artifact body is validated against the kind's own schema, not this envelope.",
54
+ "description": "The proposed artifact, shaped by `kind` (an RFC 0003 pack manifest, an RFC 0013 chain pack, an RFC 0027 template, an RFC 0052 job spec). Inert until applied. Deliberately open (`additionalProperties` not constrained here) because the payload conforms to the kind's own existing schema, validated by the host at `apply` (malformed-for-kind \u21d2 422).",
55
+ "$comment": "Intentionally open object \u2014 the artifact body is validated against the kind's own schema, not this envelope.",
56
56
  "additionalProperties": true
57
57
  },
58
58
  "provenance": {
@@ -66,8 +66,7 @@
66
66
  "sourceRunIds": {
67
67
  "type": "array",
68
68
  "items": {
69
- "type": "string",
70
- "minLength": 1
69
+ "$ref": "ids.schema.json#/$defs/runId"
71
70
  },
72
71
  "description": "Runs whose traces produced this proposal (RFC 0040 causation-compatible). SR-1 redaction-safe."
73
72
  },
@@ -91,7 +90,7 @@
91
90
  "required": [
92
91
  "tenant"
93
92
  ],
94
- "description": "RFC 0048 identity triple that owns this proposal. Redaction-safe `principal` is an opaque id, never PII or credential material. Single-tenant hosts populate at least `tenant`.",
93
+ "description": "RFC 0048 identity triple that owns this proposal. Redaction-safe \u2014 `principal` is an opaque id, never PII or credential material. Single-tenant hosts populate at least `tenant`.",
95
94
  "properties": {
96
95
  "tenant": {
97
96
  "type": "string",
@@ -106,7 +105,7 @@
106
105
  "principal": {
107
106
  "type": "string",
108
107
  "minLength": 1,
109
- "description": "Acting identity (user or agent) opaque id, never PII."
108
+ "description": "Acting identity (user or agent) \u2014 opaque id, never PII."
110
109
  }
111
110
  }
112
111
  },