@openwop/spec-artifacts 2.0.0-rc.38 → 2.0.0-rc.6

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 (34) hide show
  1. package/CORPUS-STAMP.json +32 -34
  2. package/api/seams-v2.yaml +1 -1
  3. package/api/v2/asyncapi.yaml +1 -1
  4. package/api/v2/openapi.yaml +10 -9
  5. package/package.json +5 -18
  6. package/schemas/capabilities.schema.json +1 -40
  7. package/schemas/certification-bundle-v2.schema.json +27 -119
  8. package/schemas/run-snapshot.schema.json +33 -103
  9. package/schemas/v2/agent-deployment-transition.schema.json +6 -5
  10. package/schemas/v2/agent-deployment.schema.json +9 -8
  11. package/schemas/v2/agent-eval-suite.schema.json +12 -11
  12. package/schemas/v2/artifact-type-pack-manifest.schema.json +18 -22
  13. package/schemas/v2/capabilities.schema.json +0 -39
  14. package/schemas/v2/certification-bundle.schema.json +1 -1
  15. package/schemas/v2/chat-card-pack-manifest.schema.json +15 -19
  16. package/schemas/v2/debug-bundle.schema.json +6 -6
  17. package/schemas/v2/eval-summary.schema.json +10 -17
  18. package/schemas/v2/goal.schema.json +5 -5
  19. package/schemas/v2/ids.schema.json +22 -120
  20. package/schemas/v2/node-pack-manifest.schema.json +31 -27
  21. package/schemas/v2/proposal.schema.json +8 -7
  22. package/schemas/v2/run-event-payloads.schema.json +242 -246
  23. package/schemas/v2/run-event.schema.json +126 -2
  24. package/schemas/v2/run-snapshot.schema.json +13 -34
  25. package/schemas/v2/workflow-chain-pack-manifest.schema.json +45 -41
  26. package/schemas/v2/workflow-definition.schema.json +36 -31
  27. package/spec/v1/core-standard-manifest.json +2 -2
  28. package/spec/v1/deprecations.json +67 -79
  29. package/spec/v1/deprecations.schema.json +7 -23
  30. package/spec/v1/extensions.json +2 -3
  31. package/spec/v2/declaration.json +0 -7
  32. package/spec/v2/release.json +2 -2
  33. package/schemas/v2/webhook-delivery.schema.json +0 -26
  34. package/spec/v2/id-field-bindings.json +0 -104
@@ -4,11 +4,7 @@
4
4
  "title": "RunSnapshot",
5
5
  "description": "Projected run state returned by `GET /v1/runs/{runId}`. Source is the run's append-only event log (run-event.schema.json) folded through the `RunProjection`. Forward-compat tolerant: readers MUST ignore unknown fields rather than reject.",
6
6
  "type": "object",
7
- "required": [
8
- "runId",
9
- "workflowId",
10
- "status"
11
- ],
7
+ "required": ["runId", "workflowId", "status"],
12
8
  "properties": {
13
9
  "runId": {
14
10
  "type": "string",
@@ -35,82 +31,50 @@
35
31
  "cancelling",
36
32
  "cancelled"
37
33
  ],
38
- "description": "Current run state. `waiting-external` MUST be used when the suspended interrupt's `kind` is `external-event` per `interrupt-profiles.md \u00a7openwop-interrupt-external-event` \u2014 distinguishes external-event waits from HITL waits at the wire level. `cancelling` (RFC 0094 \u00a7B) is the transitional state between a cancel request being accepted and the terminal `cancelled` \u2014 `rest-endpoints.md` and the OpenAPI cancel responses already document the transition; a snapshot read during the cancel cascade carries it. Forward-compat: future statuses MAY be added; readers SHOULD treat unknown values as terminal-unknown rather than throw."
34
+ "description": "Current run state. `waiting-external` MUST be used when the suspended interrupt's `kind` is `external-event` per `interrupt-profiles.md §openwop-interrupt-external-event` distinguishes external-event waits from HITL waits at the wire level. `cancelling` (RFC 0094 §B) is the transitional state between a cancel request being accepted and the terminal `cancelled` `rest-endpoints.md` and the OpenAPI cancel responses already document the transition; a snapshot read during the cancel cascade carries it. Forward-compat: future statuses MAY be added; readers SHOULD treat unknown values as terminal-unknown rather than throw."
39
35
  },
40
36
  "compensationStatus": {
41
37
  "type": "string",
42
- "enum": [
43
- "none",
44
- "pending",
45
- "running",
46
- "completed",
47
- "partial",
48
- "failed",
49
- "manual"
50
- ],
51
- "description": "RFC 0151 \u00a7D \u2014 the run's compensation (unwind) rollup, kept separate from `status` on purpose: `status` is the FORWARD execution state and a closed union exported by the SDK, so RFC 0151 forbids reinterpreting it (there is deliberately no `compensating` run status). OPTIONAL and capability-gated \u2014 a host that does not advertise `capabilities.compensation` MUST omit this field; a host that advertises it MUST include it on every snapshot, `none` when no compensation was ever requested for the run. Presence is therefore a wire witness of the advert, and a `none` cannot be read as \"unwind is monitored here\" from a host that would never unwind. Values are the deterministic fold of the six `compensation.*` events over the persisted plan, defined normatively in `spec/v1/compensation.md \u00a7\"Run rollup: compensationStatus\"`: `none` (no `compensation.requested`), `pending` (requested, no `started`), `running` (started, plan active \u2014 a \u00a7E approval pause does NOT change it; the run's own `status: waiting-approval` carries the wait), `completed` (every inverse action completed), `partial` (plan inactive; some completed and some did not \u2014 reported, never rounded), `failed` (plan inactive; none completed), `manual` (`manual_intervention_required` recorded and unresolved; takes precedence over `partial`/`failed` while unresolved). Content-free by construction. Resolves RFC 0151 UQ3: `RunSnapshot` (`GET /v1/runs/{runId}`) is the sole owner; debug bundles and the AsyncAPI `run.snapshot` reuse it by `$ref`."
38
+ "enum": ["none", "pending", "running", "completed", "partial", "failed", "manual"],
39
+ "description": "RFC 0151 §D — the run's compensation (unwind) rollup, kept separate from `status` on purpose: `status` is the FORWARD execution state and a closed union exported by the SDK, so RFC 0151 forbids reinterpreting it (there is deliberately no `compensating` run status). OPTIONAL and capability-gated — a host that does not advertise `capabilities.compensation` MUST omit this field; a host that advertises it MUST include it on every snapshot, `none` when no compensation was ever requested for the run. Presence is therefore a wire witness of the advert, and a `none` cannot be read as \"unwind is monitored here\" from a host that would never unwind. Values are the deterministic fold of the six `compensation.*` events over the persisted plan, defined normatively in `spec/v1/compensation.md §\"Run rollup: compensationStatus\"`: `none` (no `compensation.requested`), `pending` (requested, no `started`), `running` (started, plan active — a §E approval pause does NOT change it; the run's own `status: waiting-approval` carries the wait), `completed` (every inverse action completed), `partial` (plan inactive; some completed and some did not — reported, never rounded), `failed` (plan inactive; none completed), `manual` (`manual_intervention_required` recorded and unresolved; takes precedence over `partial`/`failed` while unresolved). Content-free by construction. Resolves RFC 0151 UQ3: `RunSnapshot` (`GET /v1/runs/{runId}`) is the sole owner; debug bundles and the AsyncAPI `run.snapshot` reuse it by `$ref`."
52
40
  },
53
41
  "owner": {
54
42
  "type": "object",
55
- "description": "RFC 0048. The identity triple that owns this run. Redaction-safe \u2014 `principal` is an opaque identifier, never PII or credential material. Optional: single-tenant hosts omit it. A principal scoped to one `workspace` MUST NOT read a run owned by another (`run_forbidden`).",
56
- "required": [
57
- "tenant"
58
- ],
43
+ "description": "RFC 0048. The identity triple that owns this run. Redaction-safe `principal` is an opaque identifier, never PII or credential material. Optional: single-tenant hosts omit it. A principal scoped to one `workspace` MUST NOT read a run owned by another (`run_forbidden`).",
44
+ "required": ["tenant"],
59
45
  "properties": {
60
- "tenant": {
61
- "type": "string",
62
- "minLength": 1,
63
- "description": "Top-level isolation boundary."
64
- },
65
- "workspace": {
66
- "type": "string",
67
- "minLength": 1,
68
- "description": "Optional sub-tenant within the tenant (RFC 0048 workspace)."
69
- },
70
- "principal": {
71
- "type": "string",
72
- "minLength": 1,
73
- "description": "Acting identity (user or agent) \u2014 opaque id, never PII."
74
- },
46
+ "tenant": { "type": "string", "minLength": 1, "description": "Top-level isolation boundary." },
47
+ "workspace": { "type": "string", "minLength": 1, "description": "Optional sub-tenant within the tenant (RFC 0048 workspace)." },
48
+ "principal": { "type": "string", "minLength": 1, "description": "Acting identity (user or agent) — opaque id, never PII." },
75
49
  "subject": {
76
50
  "$ref": "subject.schema.json",
77
51
  "description": "RFC 0165 \u00a7B. OPTIONAL issuer-scoped Subject record for the acting identity. When present: `subject.tenant` MUST equal `tenant`, `subject.subjectId` MUST equal `principal` when both are present, `subject.kind` MUST equal `principalKind` when both are present. A host that emits it on new runs synthesizes `issuer: \"urn:openwop:legacy\"` for runs that predate emission (\u00a7B.3) and copies it verbatim onto forks (\u00a7B.4)."
78
52
  },
79
53
  "principalKind": {
80
54
  "type": "string",
81
- "enum": [
82
- "user",
83
- "agent",
84
- "anonymous"
85
- ],
86
- "description": "RFC 0132. The kind of the acting principal. OPTIONAL and EXPLICIT \u2014 absent \u21d2 unconstrained (today's RFC 0048 behavior; a host that does not distinguish kinds omits it). `anonymous` \u21d2 the RFC 0132 \u00a7A anonymous-actor rules and the \u00a7C default-deny per-surface grant bind: the `principal` id MUST be opaque, origin-bound, ephemeral, non-cross-linkable, non-PII, and never inherit a role or a default-on tool baseline. A host that advertises `capabilities.anonymousActor` and dispatches through a public surface MUST set `anonymous` on the resulting run. Resolves RFC 0048 \u00a7Unresolved-Q1 for the anonymous case."
55
+ "enum": ["user", "agent", "anonymous"],
56
+ "description": "RFC 0132. The kind of the acting principal. OPTIONAL and EXPLICIT — absent ⇒ unconstrained (today's RFC 0048 behavior; a host that does not distinguish kinds omits it). `anonymous` ⇒ the RFC 0132 §A anonymous-actor rules and the §C default-deny per-surface grant bind: the `principal` id MUST be opaque, origin-bound, ephemeral, non-cross-linkable, non-PII, and never inherit a role or a default-on tool baseline. A host that advertises `capabilities.anonymousActor` and dispatches through a public surface MUST set `anonymous` on the resulting run. Resolves RFC 0048 §Unresolved-Q1 for the anonymous case."
87
57
  }
88
58
  },
89
59
  "additionalProperties": false
90
60
  },
91
61
  "currentNodeId": {
92
62
  "type": "string",
93
- "description": "Set when the run is suspended at a specific node (`waiting-approval` / `waiting-input` / `waiting-external`) \u2014 identifies which node holds the interrupt."
94
- },
95
- "startedAt": {
96
- "type": "string",
97
- "format": "date-time"
98
- },
99
- "completedAt": {
100
- "type": "string",
101
- "format": "date-time"
63
+ "description": "Set when the run is suspended at a specific node (`waiting-approval` / `waiting-input` / `waiting-external`) identifies which node holds the interrupt."
102
64
  },
65
+ "startedAt": { "type": "string", "format": "date-time" },
66
+ "completedAt": { "type": "string", "format": "date-time" },
103
67
  "agent": {
104
68
  "$ref": "agent-ref.schema.json",
105
- "description": "Optional run-level agent identity (Multi-Agent Shift Phase 1). When the run is driven by a single agent, this field carries that agent's `AgentRef`. In supervisor-orchestrated runs (Phase 5), this field rotates as workers hand off \u2014 it always carries the active worker for the current node. See `runOrchestrator` for the run-lifetime supervisor identity. Absent for runs with no agent provenance (legacy single-actor host)."
69
+ "description": "Optional run-level agent identity (Multi-Agent Shift Phase 1). When the run is driven by a single agent, this field carries that agent's `AgentRef`. In supervisor-orchestrated runs (Phase 5), this field rotates as workers hand off it always carries the active worker for the current node. See `runOrchestrator` for the run-lifetime supervisor identity. Absent for runs with no agent provenance (legacy single-actor host)."
106
70
  },
107
71
  "runOrchestrator": {
108
72
  "$ref": "agent-ref.schema.json",
109
- "description": "Optional orchestrator-supervisor identity (Multi-Agent Shift Phase 5). When set, this agent owns dispatch decisions across the run's lifetime; `runOrchestrator.decided` events emitted during the run carry this agent's `agentId`. Distinct from `agent` \u2014 `agent` rotates with each worker; `runOrchestrator` is set at run start (or first `core.orchestrator.supervisor` node) and MUST NOT change for the run's lifetime. In single-agent runs (no supervisor), this field is absent; in supervisor runs both fields MAY co-exist."
73
+ "description": "Optional orchestrator-supervisor identity (Multi-Agent Shift Phase 5). When set, this agent owns dispatch decisions across the run's lifetime; `runOrchestrator.decided` events emitted during the run carry this agent's `agentId`. Distinct from `agent` `agent` rotates with each worker; `runOrchestrator` is set at run start (or first `core.orchestrator.supervisor` node) and MUST NOT change for the run's lifetime. In single-agent runs (no supervisor), this field is absent; in supervisor runs both fields MAY co-exist."
110
74
  },
111
75
  "nodeStates": {
112
76
  "type": "object",
113
- "description": "Per-node state map. Keys are nodeIds; values are implementation-shaped state objects. Spec doesn't constrain the inner shape \u2014 see version-negotiation.md \u00a7node-states."
77
+ "description": "Per-node state map. Keys are nodeIds; values are implementation-shaped state objects. Spec doesn't constrain the inner shape see version-negotiation.md §node-states."
114
78
  },
115
79
  "variables": {
116
80
  "type": "object",
@@ -123,40 +87,26 @@
123
87
  "error": {
124
88
  "type": "object",
125
89
  "description": "Set on terminal `failed`. Structured so consumers can route on code without parsing message text.",
126
- "required": [
127
- "code",
128
- "message"
129
- ],
90
+ "required": ["code", "message"],
130
91
  "properties": {
131
- "code": {
132
- "type": "string",
133
- "minLength": 1
134
- },
135
- "message": {
136
- "type": "string",
137
- "minLength": 1
138
- },
139
- "details": {
140
- "type": "object"
141
- }
92
+ "code": { "type": "string", "minLength": 1 },
93
+ "message": { "type": "string", "minLength": 1 },
94
+ "details": { "type": "object" }
142
95
  },
143
96
  "additionalProperties": false
144
97
  },
145
98
  "engineVersion": {
146
- "type": "number",
147
- "description": "Engine version the run was started under. Used by the projection for forward-compat folds. TYPE CORRECTED 2026-09-04: this was `string`, contradicting version-negotiation.md \u00a7Stamping \u2014 \"Every persisted run document MUST carry an `engineVersion: number` field\" \u2014 so a host obeying the prose emitted a document failing its own schema, and a host obeying the schema violated the MUST. Unimplementable as written. `spec/v1/extensions.json` already described the axis as \"a number, not a feature\", and schemas/v2 types it `integer` (RFC 0172 \u00a7B axis 3), so prose, register and v2 agreed and only this file dissented. Safe to correct: measured on both production hosts 2026-09-04, NEITHER emits the field at all, so no existing document is invalidated."
99
+ "type": "string",
100
+ "description": "Engine version the run was started under. Used by the projection for forward-compat folds."
148
101
  },
149
102
  "eventLogSchemaVersion": {
150
103
  "type": "integer",
151
104
  "minimum": 0,
152
- "description": "Per-run event-log subcollection schema version. See version-negotiation.md. `minimum: 0` is DELIBERATE and MUST NOT be raised to 2 in v1: \u00a7Legacy detection identifies a legacy run as one whose value is \"undefined or < 2\", so a legacy document carrying 0 or 1 is contemplated by the contract and MUST still validate. schemas/v2 uses `minimum: 2` correctly, because the v2 era key admits only 2 and 3."
105
+ "description": "Per-run event-log subcollection schema version. See version-negotiation.md."
153
106
  },
154
107
  "tags": {
155
108
  "type": "array",
156
- "items": {
157
- "type": "string",
158
- "maxLength": 256
159
- },
109
+ "items": { "type": "string", "maxLength": 256 },
160
110
  "maxItems": 100,
161
111
  "description": "Caller-supplied tags from `RunOptions.tags`."
162
112
  },
@@ -170,44 +120,24 @@
170
120
  },
171
121
  "metrics": {
172
122
  "type": "object",
173
- "description": "Aggregate run-level metrics. Forward-compat: readers MUST tolerate missing/unknown fields. Fields are populated lazily as the engine emits them \u2014 absence does NOT mean zero. Implementation-specific fields (e.g., legacy `cost: number` estimates) MAY appear alongside the spec-canonical fields below.",
123
+ "description": "Aggregate run-level metrics. Forward-compat: readers MUST tolerate missing/unknown fields. Fields are populated lazily as the engine emits them absence does NOT mean zero. Implementation-specific fields (e.g., legacy `cost: number` estimates) MAY appear alongside the spec-canonical fields below.",
174
124
  "properties": {
175
125
  "openwopCost": {
176
126
  "type": "object",
177
- "description": "Spec-canonical cost rollup aggregated from per-node `recordCost()` calls. Keys mirror the `openwop.cost.*` OTel attribute allowlist (see `observability.md` \u00a7Cost attribution). Named `openwopCost` rather than `cost` because some implementations carry a legacy `metrics.cost: number` estimate that predates the typed rollup; using a distinct name avoids collision. Multi-provider runs report `provider`/`model` of the LAST contributing call; SDKs that need per-call detail SHOULD subscribe to OTel spans instead of reading this rollup.",
127
+ "description": "Spec-canonical cost rollup aggregated from per-node `recordCost()` calls. Keys mirror the `openwop.cost.*` OTel attribute allowlist (see `observability.md` §Cost attribution). Named `openwopCost` rather than `cost` because some implementations carry a legacy `metrics.cost: number` estimate that predates the typed rollup; using a distinct name avoids collision. Multi-provider runs report `provider`/`model` of the LAST contributing call; SDKs that need per-call detail SHOULD subscribe to OTel spans instead of reading this rollup.",
178
128
  "properties": {
179
- "usd": {
180
- "type": "number",
181
- "minimum": 0,
182
- "description": "Total USD cost across all node-level recordCost emissions."
183
- },
129
+ "usd": { "type": "number", "minimum": 0, "description": "Total USD cost across all node-level recordCost emissions." },
184
130
  "tokens": {
185
131
  "type": "object",
186
132
  "properties": {
187
- "input": {
188
- "type": "integer",
189
- "minimum": 0
190
- },
191
- "output": {
192
- "type": "integer",
193
- "minimum": 0
194
- }
133
+ "input": { "type": "integer", "minimum": 0 },
134
+ "output": { "type": "integer", "minimum": 0 }
195
135
  },
196
136
  "additionalProperties": false
197
137
  },
198
- "model": {
199
- "type": "string",
200
- "description": "Model identifier of the most recent recordCost emission (e.g., `claude-opus-4-7`). For multi-model runs, surfaces the last value."
201
- },
202
- "provider": {
203
- "type": "string",
204
- "description": "Normalized provider name (e.g., `anthropic`, `openai`, `gemini`)."
205
- },
206
- "duration_ms": {
207
- "type": "integer",
208
- "minimum": 0,
209
- "description": "Sum of per-call durations across all recordCost emissions in this run."
210
- }
138
+ "model": { "type": "string", "description": "Model identifier of the most recent recordCost emission (e.g., `claude-opus-4-7`). For multi-model runs, surfaces the last value." },
139
+ "provider": { "type": "string", "description": "Normalized provider name (e.g., `anthropic`, `openai`, `gemini`)." },
140
+ "duration_ms": { "type": "integer", "minimum": 0, "description": "Sum of per-call durations across all recordCost emissions in this run." }
211
141
  },
212
142
  "additionalProperties": false
213
143
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://openwop.dev/spec/v2/agent-deployment-transition.schema.json",
4
4
  "title": "AgentDeploymentTransition",
5
- "description": "RFC 0082 \u00a7E. The request body of POST /v1/agents/{agentId}/deployments \u2014 a deployment state-transition request. The host authorizes it fail-closed against the RFC 0049 `deploy:*` scope, runs any configured RFC 0051 approvalGate, enforces the RFC 0081 eval evidence when the gate requires it, and on success emits the matching `deployment.*` event and returns the updated `agent-deployment.schema.json` record. Carries no manifest body or credential material (SR-1).",
5
+ "description": "RFC 0082 §E. The request body of POST /v1/agents/{agentId}/deployments a deployment state-transition request. The host authorizes it fail-closed against the RFC 0049 `deploy:*` scope, runs any configured RFC 0051 approvalGate, enforces the RFC 0081 eval evidence when the gate requires it, and on success emits the matching `deployment.*` event and returns the updated `agent-deployment.schema.json` record. Carries no manifest body or credential material (SR-1).",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": [
@@ -24,7 +24,7 @@
24
24
  "rollback",
25
25
  "adjust-canary"
26
26
  ],
27
- "description": "The lifecycle operation. `promote`: advance toward production (draft\u2192test\u2192staged\u2192active) into `toState`. `pause`: active\u2192paused. `deprecate`: active\u2192deprecated. `rollback`: active\u2192rolled-back, restoring a prior version. `adjust-canary`: change the active version's `canaryPercent`. Legal transitions + the seven-state machine are normative in `agent-deployment.md` \u00a7C."
27
+ "description": "The lifecycle operation. `promote`: advance toward production (draft→test→staged→active) into `toState`. `pause`: active→paused. `deprecate`: active→deprecated. `rollback`: active→rolled-back, restoring a prior version. `adjust-canary`: change the active version's `canaryPercent`. Legal transitions + the seven-state machine are normative in `agent-deployment.md` §C."
28
28
  },
29
29
  "toState": {
30
30
  "type": "string",
@@ -43,7 +43,7 @@
43
43
  "type": "string",
44
44
  "minLength": 1,
45
45
  "maxLength": 64,
46
- "description": "MAY. The named deployment channel this transition targets (e.g. `stable`). The version becomes resolvable by `AgentRef.channel` = this value (RFC 0082 \u00a7A/\u00a7B)."
46
+ "description": "MAY. The named deployment channel this transition targets (e.g. `stable`). The version becomes resolvable by `AgentRef.channel` = this value (RFC 0082 §A/§B)."
47
47
  },
48
48
  "canaryPercent": {
49
49
  "type": "integer",
@@ -52,8 +52,9 @@
52
52
  "description": "MAY. For `promote` to `active` or `adjust-canary`, the share of channel traffic this version takes. A host advertising `capabilities.agents.deployment.canary: false` MUST reject any value < 100."
53
53
  },
54
54
  "evalRunId": {
55
- "$ref": "ids.schema.json#/$defs/runId",
56
- "description": "MAY. The RFC 0081 eval run whose `EvalSummary.passed` the gate verifies before emitting `deployment.promoted` (the \u00a7E evidence). Required when the target `approvalGate` is configured with `requiredEval`; an unmet eval gate is rejected with `eval_gate_unmet`."
55
+ "type": "string",
56
+ "minLength": 1,
57
+ "description": "MAY. The RFC 0081 eval run whose `EvalSummary.passed` the gate verifies before emitting `deployment.promoted` (the §E evidence). Required when the target `approvalGate` is configured with `requiredEval`; an unmet eval gate is rejected with `eval_gate_unmet`."
57
58
  },
58
59
  "reason": {
59
60
  "type": "string",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://openwop.dev/spec/v2/agent-deployment.schema.json",
4
4
  "title": "AgentDeployment",
5
- "description": "RFC 0082 \u00a7C. A per-(agentId, version) deployment record: the host-runtime lifecycle state of one version of a manifest agent (RFC 0003/0070), distinct from the immutable AgentManifest (which is the pack-distribution descriptor) and from the registry's published semver tags (which version EXISTS, not which version SERVES). Holds the deployment `state`, the named `channels` that resolve to this version, the canary traffic share, the rollback pointer, and the provenance (`evalRunId`/`approvalGateId`) of the last transition. Content-free of any manifest body or credential material (SR-1). The record itself is host-internal; this schema is the canonical wire shape a host exposes via GET /v1/agents/{agentId}/deployments (the endpoint lands at Active \u2192 Accepted per RFC 0082 \u00a7Conformance).",
5
+ "description": "RFC 0082 §C. A per-(agentId, version) deployment record: the host-runtime lifecycle state of one version of a manifest agent (RFC 0003/0070), distinct from the immutable AgentManifest (which is the pack-distribution descriptor) and from the registry's published semver tags (which version EXISTS, not which version SERVES). Holds the deployment `state`, the named `channels` that resolve to this version, the canary traffic share, the rollback pointer, and the provenance (`evalRunId`/`approvalGateId`) of the last transition. Content-free of any manifest body or credential material (SR-1). The record itself is host-internal; this schema is the canonical wire shape a host exposes via GET /v1/agents/{agentId}/deployments (the endpoint lands at Active Accepted per RFC 0082 §Conformance).",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": [
@@ -18,7 +18,7 @@
18
18
  "version": {
19
19
  "type": "string",
20
20
  "maxLength": 64,
21
- "description": "The concrete agent-definition version this record governs (matches `AgentManifest.version`). A deployment record is per-(agentId, version); a `@channel` reference resolves to exactly one such version per the RFC 0082 \u00a7B pin."
21
+ "description": "The concrete agent-definition version this record governs (matches `AgentManifest.version`). A deployment record is per-(agentId, version); a `@channel` reference resolves to exactly one such version per the RFC 0082 §B pin."
22
22
  },
23
23
  "state": {
24
24
  "type": "string",
@@ -31,13 +31,13 @@
31
31
  "deprecated",
32
32
  "rolled-back"
33
33
  ],
34
- "description": "RFC 0082 \u00a7C lifecycle state. `draft`: authored, not yet evaluated. `test`: undergoing eval (RFC 0081). `staged`: eval-passed, awaiting production promotion. `active`: serving (optionally at `canaryPercent < 100`). `paused`: temporarily withdrawn, recoverable. `deprecated`: sunset \u2014 no new traffic, existing pins honored. `rolled-back`: superseded; `rollbackPointer` names the version that replaced it. Legal transitions: draft\u2192test\u2192staged\u2192active (promotion); active\u2194paused (operational); active\u2192deprecated (terminal); active\u2192rolled-back (recovery, with `rollbackPointer`)."
34
+ "description": "RFC 0082 §C lifecycle state. `draft`: authored, not yet evaluated. `test`: undergoing eval (RFC 0081). `staged`: eval-passed, awaiting production promotion. `active`: serving (optionally at `canaryPercent < 100`). `paused`: temporarily withdrawn, recoverable. `deprecated`: sunset no new traffic, existing pins honored. `rolled-back`: superseded; `rollbackPointer` names the version that replaced it. Legal transitions: draft→test→staged→active (promotion); active↔paused (operational); active→deprecated (terminal); active→rolled-back (recovery, with `rollbackPointer`)."
35
35
  },
36
36
  "canaryPercent": {
37
37
  "type": "integer",
38
38
  "minimum": 0,
39
39
  "maximum": 100,
40
- "description": "MAY. For an `active` version, the share (0\u2013100) of channel traffic the \u00a7B pin draw assigns to THIS version; the remainder goes to the prior `active` version on the same channel. Absent \u21d2 100 (full traffic). A host advertising `agents.deployment.canary: false` MUST reject any value < 100. The per-run draw outcome is the recorded-fact `resolvedAgentVersion` (RFC 0082 \u00a7B) \u2014 never re-rolled on replay."
40
+ "description": "MAY. For an `active` version, the share (0–100) of channel traffic the §B pin draw assigns to THIS version; the remainder goes to the prior `active` version on the same channel. Absent 100 (full traffic). A host advertising `agents.deployment.canary: false` MUST reject any value < 100. The per-run draw outcome is the recorded-fact `resolvedAgentVersion` (RFC 0082 §B) never re-rolled on replay."
41
41
  },
42
42
  "rollbackPointer": {
43
43
  "type": "string",
@@ -51,16 +51,17 @@
51
51
  "type": "string",
52
52
  "minLength": 1
53
53
  },
54
- "description": "MAY. The named deployment channels (e.g. `stable`, `canary`) that resolve to this version. A version MAY be on more than one channel (RFC 0082 UQ#4 \u2014 a promoted-to-`stable` version is also resolvable by the reserved `latest` channel = highest active semver). Channel names are host-advertised in `capabilities.agents.deployment.channels`."
54
+ "description": "MAY. The named deployment channels (e.g. `stable`, `canary`) that resolve to this version. A version MAY be on more than one channel (RFC 0082 UQ#4 a promoted-to-`stable` version is also resolvable by the reserved `latest` channel = highest active semver). Channel names are host-advertised in `capabilities.agents.deployment.channels`."
55
55
  },
56
56
  "evalRunId": {
57
- "$ref": "ids.schema.json#/$defs/runId",
58
- "description": "MAY. The RFC 0081 eval run whose `EvalSummary.passed` gated the last promotion into this state (the \u00a7E evidence). Content-free reference, not the eval body."
57
+ "type": "string",
58
+ "minLength": 1,
59
+ "description": "MAY. The RFC 0081 eval run whose `EvalSummary.passed` gated the last promotion into this state (the §E evidence). Content-free reference, not the eval body."
59
60
  },
60
61
  "approvalGateId": {
61
62
  "type": "string",
62
63
  "minLength": 1,
63
- "description": "MAY. The RFC 0051 `approvalGate` that authorized the last transition (the \u00a7E human gate). Content-free reference."
64
+ "description": "MAY. The RFC 0051 `approvalGate` that authorized the last transition (the §E human gate). Content-free reference."
64
65
  }
65
66
  },
66
67
  "x-openwop-seeded-from": "v1"
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://openwop.dev/spec/v2/agent-eval-suite.schema.json",
4
4
  "title": "AgentEvalSuite",
5
- "description": "RFC 0081 \u00a7A. A portable, host-agnostic evaluation suite for a manifest agent (RFC 0003/0070): the tasks, the expected outputs or rubrics, the deterministic tool/memory fixtures, the allowed model classes, and the pass/fail thresholds that answer \"is this agent good enough to deploy?\". Distributed inside a pack tarball and referenced by URI exactly like `systemPromptRef` / `handoff.*SchemaRef` (RFC 0003 \u00a7C/\u00a7D) \u2014 NOT embedded in `AgentManifest`. A host advertising `capabilities.agents.evalSuite.supported: true` executes a suite as an eval run (a `mode: \"eval\"` projection over `POST /v1/runs`, RFC 0081 \u00a7B) and terminates with an `eval-summary.schema.json` scorecard. The suite carries NO secret material and NO host-internal identifiers (it is authored offline and shipped in a signed pack).",
5
+ "description": "RFC 0081 §A. A portable, host-agnostic evaluation suite for a manifest agent (RFC 0003/0070): the tasks, the expected outputs or rubrics, the deterministic tool/memory fixtures, the allowed model classes, and the pass/fail thresholds that answer \"is this agent good enough to deploy?\". Distributed inside a pack tarball and referenced by URI exactly like `systemPromptRef` / `handoff.*SchemaRef` (RFC 0003 §C/§D) NOT embedded in `AgentManifest`. A host advertising `capabilities.agents.evalSuite.supported: true` executes a suite as an eval run (a `mode: \"eval\"` projection over `POST /v1/runs`, RFC 0081 §B) and terminates with an `eval-summary.schema.json` scorecard. The suite carries NO secret material and NO host-internal identifiers (it is authored offline and shipped in a signed pack).",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": [
@@ -20,11 +20,12 @@
20
20
  "version": {
21
21
  "type": "string",
22
22
  "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
23
- "description": "SemVer of the suite. A suite version is pinned on an eval run (carried on `eval.started.suiteVersion` and `eval-summary.suiteVersion`) so a regression comparison (\u00a7D `regression` mode) is between like versions."
23
+ "description": "SemVer of the suite. A suite version is pinned on an eval run (carried on `eval.started.suiteVersion` and `eval-summary.suiteVersion`) so a regression comparison (§D `regression` mode) is between like versions."
24
24
  },
25
25
  "targetAgentId": {
26
- "$ref": "ids.schema.json#/$defs/agentId",
27
- "description": "MAY. The `AgentManifest.id` this suite is authored for. Absent \u21d2 the suite is agent-agnostic and MAY be pointed at any `agentId` at run time (the run request carries the `agentId`). When present, a host SHOULD reject an eval run whose target `agentId` differs, unless the caller explicitly overrides."
26
+ "type": "string",
27
+ "minLength": 1,
28
+ "description": "MAY. The `AgentManifest.id` this suite is authored for. Absent ⇒ the suite is agent-agnostic and MAY be pointed at any `agentId` at run time (the run request carries the `agentId`). When present, a host SHOULD reject an eval run whose target `agentId` differs, unless the caller explicitly overrides."
28
29
  },
29
30
  "modes": {
30
31
  "type": "array",
@@ -40,7 +41,7 @@
40
41
  "live-shadow"
41
42
  ]
42
43
  },
43
- "description": "The eval modes this suite exercises (RFC 0081 \u00a7D closed vocabulary). `golden`: exact / contains / json-match against each task's `expected`. `rubric`: a host-chosen judge scores against weighted criteria (nondeterministic \u2014 a recorded-fact score). `adversarial`: tasks probe for unsafe / jailbreak behavior; `safetyFindings` is the primary output. `regression`: re-run against a new agent/model/prompt version and diff scores vs a `baselineRunId` (composes RFC 0054 `:diff`). `live-shadow`: run against LIVE tools/memory instead of `fixtures` \u2014 the only mode that bypasses fixture injection; explicitly nondeterministic. A run MUST request only modes the suite declares here AND the host advertises (`capabilities.agents.evalSuite.modes`); an unadvertised mode is rejected at run-create with `400 validation_error`."
44
+ "description": "The eval modes this suite exercises (RFC 0081 §D closed vocabulary). `golden`: exact / contains / json-match against each task's `expected`. `rubric`: a host-chosen judge scores against weighted criteria (nondeterministic a recorded-fact score). `adversarial`: tasks probe for unsafe / jailbreak behavior; `safetyFindings` is the primary output. `regression`: re-run against a new agent/model/prompt version and diff scores vs a `baselineRunId` (composes RFC 0054 `:diff`). `live-shadow`: run against LIVE tools/memory instead of `fixtures` the only mode that bypasses fixture injection; explicitly nondeterministic. A run MUST request only modes the suite declares here AND the host advertises (`capabilities.agents.evalSuite.modes`); an unadvertised mode is rejected at run-create with `400 validation_error`."
44
45
  },
45
46
  "allowedModels": {
46
47
  "type": "array",
@@ -56,18 +57,18 @@
56
57
  "general"
57
58
  ]
58
59
  },
59
- "description": "MAY. The `AgentManifest.modelClass` values (RFC 0002) the suite is valid for. Absent \u21d2 valid for any class. A host SHOULD record the `evaluatedModelClass` on the summary so a score is interpreted against the model it was produced with."
60
+ "description": "MAY. The `AgentManifest.modelClass` values (RFC 0002) the suite is valid for. Absent valid for any class. A host SHOULD record the `evaluatedModelClass` on the summary so a score is interpreted against the model it was produced with."
60
61
  },
61
62
  "thresholds": {
62
63
  "type": "object",
63
64
  "additionalProperties": false,
64
- "description": "MAY. The pass/fail bar for the suite. A task or the aggregate `passed` flag is computed against these. Absent \u21d2 the host's default bar (the summary still carries raw scores).",
65
+ "description": "MAY. The pass/fail bar for the suite. A task or the aggregate `passed` flag is computed against these. Absent the host's default bar (the summary still carries raw scores).",
65
66
  "properties": {
66
67
  "passScore": {
67
68
  "type": "number",
68
69
  "minimum": 0,
69
70
  "maximum": 1,
70
- "description": "The minimum aggregate score (0.0\u20131.0) for `EvalSummary.passed: true`."
71
+ "description": "The minimum aggregate score (0.0–1.0) for `EvalSummary.passed: true`."
71
72
  },
72
73
  "maxCostUsd": {
73
74
  "type": "number",
@@ -100,7 +101,7 @@
100
101
  "description": "Suite-unique task identifier (kebab-case). Carried verbatim on `eval.scored.taskId` and the per-task summary entry."
101
102
  },
102
103
  "input": {
103
- "description": "The run input for the task, validated against the agent's input schema by the host. An opaque object/value \u2014 content is task-defined."
104
+ "description": "The run input for the task, validated against the agent's input schema by the host. An opaque object/value content is task-defined."
104
105
  },
105
106
  "expected": {
106
107
  "type": "object",
@@ -144,7 +145,7 @@
144
145
  "rubric": {
145
146
  "type": "array",
146
147
  "minItems": 1,
147
- "description": "Present when `kind: \"rubric\"`. Weighted criteria a judge scores the output against; the task score is the weighted sum of met criteria, normalized to 0.0\u20131.0. Judge selection + scoring is host-internal (nondeterministic \u2014 the score is a recorded fact).",
148
+ "description": "Present when `kind: \"rubric\"`. Weighted criteria a judge scores the output against; the task score is the weighted sum of met criteria, normalized to 0.0–1.0. Judge selection + scoring is host-internal (nondeterministic the score is a recorded fact).",
148
149
  "items": {
149
150
  "type": "object",
150
151
  "additionalProperties": false,
@@ -172,7 +173,7 @@
172
173
  "fixtures": {
173
174
  "type": "object",
174
175
  "additionalProperties": false,
175
- "description": "MAY. Deterministic substitutes for live tool/memory I/O so a `golden`/`regression` eval is reproducible. When present, the eval host MUST inject `toolResponses` in place of live tool calls and seed `memorySeed` before the invocation. The `live-shadow` mode is the explicit exception \u2014 it ignores `fixtures` and runs against live tools/memory.",
176
+ "description": "MAY. Deterministic substitutes for live tool/memory I/O so a `golden`/`regression` eval is reproducible. When present, the eval host MUST inject `toolResponses` in place of live tool calls and seed `memorySeed` before the invocation. The `live-shadow` mode is the explicit exception it ignores `fixtures` and runs against live tools/memory.",
176
177
  "properties": {
177
178
  "toolResponses": {
178
179
  "type": "array",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://openwop.dev/spec/v2/artifact-type-pack-manifest.schema.json",
4
4
  "title": "ArtifactTypePackManifest",
5
- "description": "Manifest for a published OpenWOP artifact-type pack \u2014 `pack.json` at the pack root with `kind: \"artifact-type\"`. Peer to `node-pack-manifest.schema.json` (RFC 0003), `workflow-chain-pack-manifest.schema.json` (RFC 0013), and `prompt-pack-manifest.schema.json` (RFC 0028); disjoint from all three via the `kind` discriminator. See `spec/v1/artifact-type-packs.md` for the canonical contract and RFC 0071 for the rationale.\n\nArtifact-type packs distribute typed artifact definitions \u2014 the JSON Schema, rendering hint, lifecycle, and export-format hints for the rich outputs workflow nodes produce \u2014 via the same signed-tarball + Ed25519 + SRI pipeline that already serves node, workflow-chain, and prompt packs. When a host installs an artifact-type pack and advertises `host.artifactTypes: { supported: true }`, it validates produced artifacts of the declared `artifactTypeId`s against their `schemaRef` before emitting `artifact.created`.",
5
+ "description": "Manifest for a published OpenWOP artifact-type pack `pack.json` at the pack root with `kind: \"artifact-type\"`. Peer to `node-pack-manifest.schema.json` (RFC 0003), `workflow-chain-pack-manifest.schema.json` (RFC 0013), and `prompt-pack-manifest.schema.json` (RFC 0028); disjoint from all three via the `kind` discriminator. See `spec/v1/artifact-type-packs.md` for the canonical contract and RFC 0071 for the rationale.\n\nArtifact-type packs distribute typed artifact definitions the JSON Schema, rendering hint, lifecycle, and export-format hints for the rich outputs workflow nodes produce via the same signed-tarball + Ed25519 + SRI pipeline that already serves node, workflow-chain, and prompt packs. When a host installs an artifact-type pack and advertises `host.artifactTypes: { supported: true }`, it validates produced artifacts of the declared `artifactTypeId`s against their `schemaRef` before emitting `artifact.created`.",
6
6
  "type": "object",
7
7
  "required": [
8
8
  "name",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "name": {
22
22
  "type": "string",
23
- "description": "Reverse-DNS pack name per `node-packs.md` \u00a7Naming. Reserved scopes are identical (`core.*` / `vendor.<org>.*` / `community.<author>.*` / `private.<host>.*`). Mirror of `prompt-pack-manifest.schema.json#/properties/name`.",
23
+ "description": "Reverse-DNS pack name per `node-packs.md` §Naming. Reserved scopes are identical (`core.*` / `vendor.<org>.*` / `community.<author>.*` / `private.<host>.*`). Mirror of `prompt-pack-manifest.schema.json#/properties/name`.",
24
24
  "pattern": "^(core|vendor|community|private)\\.[a-z][a-z0-9_-]*(\\.[a-z][a-zA-Z0-9_-]*)+$",
25
25
  "minLength": 1,
26
26
  "maxLength": 256
@@ -65,7 +65,7 @@
65
65
  "properties": {
66
66
  "openwop": {
67
67
  "type": "string",
68
- "description": "Semver range \u2014 which openwop protocol versions this pack works against. Example: `>=1.1 <2.0.0`."
68
+ "description": "Semver range which openwop protocol versions this pack works against. Example: `>=1.1 <2.0.0`."
69
69
  }
70
70
  },
71
71
  "additionalProperties": false
@@ -75,7 +75,7 @@
75
75
  "additionalProperties": {
76
76
  "type": "string"
77
77
  },
78
- "description": "Other packs this pack depends on. Map of pack name \u2192 semver range. Resolved transitively at workflow-register time."
78
+ "description": "Other packs this pack depends on. Map of pack name semver range. Resolved transitively at workflow-register time."
79
79
  },
80
80
  "peerDependencies": {
81
81
  "type": "object",
@@ -106,25 +106,21 @@
106
106
  "additionalProperties": false,
107
107
  "properties": {
108
108
  "artifactTypeId": {
109
- "allOf": [
110
- {
111
- "$ref": "ids.schema.json#/$defs/typeId"
112
- },
113
- {
114
- "pattern": "^(core|vendor|community|private)\\.[a-z][a-z0-9_-]*(\\.[a-z][a-zA-Z0-9_-]*)+$"
115
- }
116
- ],
117
- "description": "Reverse-DNS artifact-type identifier. Same pattern and reserved scopes as a pack `name`. This is the value `WorkflowNode.artifactType`, `nodes[].artifact.typeId`, and `artifact.created.artifactType` reference. Third parties MUST NOT publish under `core.*`."
109
+ "type": "string",
110
+ "description": "Reverse-DNS artifact-type identifier. Same pattern and reserved scopes as a pack `name`. This is the value `WorkflowNode.artifactType`, `nodes[].artifact.typeId`, and `artifact.created.artifactType` reference. Third parties MUST NOT publish under `core.*`.",
111
+ "pattern": "^(core|vendor|community|private)\\.[a-z][a-z0-9_-]*(\\.[a-z][a-zA-Z0-9_-]*)+$",
112
+ "minLength": 1,
113
+ "maxLength": 256
118
114
  },
119
115
  "schemaRef": {
120
116
  "type": "string",
121
117
  "minLength": 1,
122
- "description": "Path inside the pack tarball to the artifact's JSON Schema (Draft 2020-12). The target schema MUST set `additionalProperties: false` at its top level and declare an `$id` under `{HostBase}/schemas/artifacts/{artifactTypeId}.schema.json`, mirroring the envelope convention in `ai-envelope.md` \u00a7\"Canonical schema location\"."
118
+ "description": "Path inside the pack tarball to the artifact's JSON Schema (Draft 2020-12). The target schema MUST set `additionalProperties: false` at its top level and declare an `$id` under `{HostBase}/schemas/artifacts/{artifactTypeId}.schema.json`, mirroring the envelope convention in `ai-envelope.md` §\"Canonical schema location\"."
123
119
  },
124
120
  "schemaVersion": {
125
121
  "type": "integer",
126
122
  "minimum": 0,
127
- "description": "Non-negative integer artifact-schema version, parallel to the per-kind integer in `capabilities.schemaVersions`. Absent \u21d2 treated as 0. Bumped when the artifact schema changes shape. A version *declaration*, not a validation guarantee (RFC 0075 / P1-2)."
123
+ "description": "Non-negative integer artifact-schema version, parallel to the per-kind integer in `capabilities.schemaVersions`. Absent treated as 0. Bumped when the artifact schema changes shape. A version *declaration*, not a validation guarantee (RFC 0075 / P1-2)."
128
124
  },
129
125
  "validation": {
130
126
  "type": "string",
@@ -132,7 +128,7 @@
132
128
  "open",
133
129
  "closed"
134
130
  ],
135
- "description": "RFC 0075. Strictness contract of the artifact's `schemaRef` schema. `closed` \u21d2 closed-world (`additionalProperties: false`); a consumer MAY rely on the absence of unknown fields. `open` (recommended for AI/LLM-produced artifacts) \u21d2 the schema tolerates extra fields to absorb model drift; consumers MUST ignore unknown fields per COMPATIBILITY.md \u00a72.1. Absent \u21d2 `open` (the forward-compatible default). The `additionalProperties:false` requirement on `schemaRef` was relaxed MUST \u2192 SHOULD by RFC 0075: a closed-world artifact contract contradicts the protocol's own forward-compat mandate and the first AI-native adopter cannot meet it."
131
+ "description": "RFC 0075. Strictness contract of the artifact's `schemaRef` schema. `closed` closed-world (`additionalProperties: false`); a consumer MAY rely on the absence of unknown fields. `open` (recommended for AI/LLM-produced artifacts) the schema tolerates extra fields to absorb model drift; consumers MUST ignore unknown fields per COMPATIBILITY.md §2.1. Absent `open` (the forward-compatible default). The `additionalProperties:false` requirement on `schemaRef` was relaxed MUST SHOULD by RFC 0075: a closed-world artifact contract contradicts the protocol's own forward-compat mandate and the first AI-native adopter cannot meet it."
136
132
  },
137
133
  "displayName": {
138
134
  "type": "string",
@@ -150,7 +146,7 @@
150
146
  "pattern": "^([a-z][a-z0-9]*|vendor\\.[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*|x-[a-z][a-z0-9-]*)$"
151
147
  },
152
148
  "uniqueItems": true,
153
- "description": "Export-format identifiers (hints) a renderer MAY offer. Spec-reserved core identifiers carry interoperable meaning (the lowercase file-extension / common name): `pdf`, `pptx`, `docx`, `xlsx`, `md`, `html`, `txt`, `csv`, `json`, `png`, `svg`, `jpeg`, `step`, `stl`, `dxf`. Domain-specific formats outside the core set MUST be `vendor.<org>.<format>`- or `x-<format>`-prefixed (mirrors the `requiredModelCapabilities` reserved-core + extension idiom). Advisory: this spec assigns no byte-level production semantics to any identifier \u2014 it standardizes the identifier so two hosts agree what `pptx` names, not how the bytes are produced."
149
+ "description": "Export-format identifiers (hints) a renderer MAY offer. Spec-reserved core identifiers carry interoperable meaning (the lowercase file-extension / common name): `pdf`, `pptx`, `docx`, `xlsx`, `md`, `html`, `txt`, `csv`, `json`, `png`, `svg`, `jpeg`, `step`, `stl`, `dxf`. Domain-specific formats outside the core set MUST be `vendor.<org>.<format>`- or `x-<format>`-prefixed (mirrors the `requiredModelCapabilities` reserved-core + extension idiom). Advisory: this spec assigns no byte-level production semantics to any identifier it standardizes the identifier so two hosts agree what `pptx` names, not how the bytes are produced."
154
150
  },
155
151
  "syncOn": {
156
152
  "type": "string",
@@ -171,19 +167,19 @@
171
167
  },
172
168
  "diffable": {
173
169
  "type": "boolean",
174
- "description": "True if the artifact's schema supports structural diffing (informs run-diff tooling per rest-endpoints.md \u00a7:diff). Non-normative."
170
+ "description": "True if the artifact's schema supports structural diffing (informs run-diff tooling per rest-endpoints.md §:diff). Non-normative."
175
171
  }
176
172
  },
177
173
  "patternProperties": {
178
174
  "^(openwop-|x-|vendor\\.)": {
179
- "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."
175
+ "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."
180
176
  }
181
177
  }
182
178
  },
183
179
  "RenderingHint": {
184
180
  "type": "object",
185
181
  "additionalProperties": false,
186
- "description": "Advisory rendering hint. Reuses the closed vocabulary defined in ai-envelope.md \u00a7\"Rendering hints\" (RFC 0055). Advisory only \u2014 consumers MUST degrade gracefully and MUST NOT treat this as a validation input. The `card` display value is reserved for envelopes and is excluded here (durable artifacts are not transient chat cards).",
182
+ "description": "Advisory rendering hint. Reuses the closed vocabulary defined in ai-envelope.md §\"Rendering hints\" (RFC 0055). Advisory only consumers MUST degrade gracefully and MUST NOT treat this as a validation input. The `card` display value is reserved for envelopes and is excluded here (durable artifacts are not transient chat cards).",
187
183
  "properties": {
188
184
  "display": {
189
185
  "type": "string",
@@ -218,7 +214,7 @@
218
214
  },
219
215
  "Signing": {
220
216
  "type": "object",
221
- "description": "Optional signing metadata. See node-packs.md \u00a7signing.",
217
+ "description": "Optional signing metadata. See node-packs.md §signing.",
222
218
  "additionalProperties": false,
223
219
  "properties": {
224
220
  "publicKeyRef": {
@@ -242,7 +238,7 @@
242
238
  },
243
239
  "patternProperties": {
244
240
  "^(openwop-|x-|vendor\\.)": {
245
- "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."
241
+ "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."
246
242
  }
247
243
  },
248
244
  "x-openwop-seeded-from": "v1"
@@ -232,45 +232,6 @@
232
232
  "additionalProperties": true,
233
233
  "x-openwop-seeded-from": "v1"
234
234
  },
235
- "signingKeys": {
236
- "type": "array",
237
- "description": "RFC 0168 §E.2 — the public keys this host signs certification bundles with. A bundle signature names a keyId; a verifier resolves it HERE, in the discovery document of the host the bundle is about, and checks the Ed25519 signature with the matching publicKey. Without this array a signature attests integrity only: it proves the bundle was not altered after signing and says nothing about who signed it, because a signer can mint a keypair and a keyId at will. RFC 0168 disposed of \"an Ed25519 attestation without a key registry is a signature nobody can check\" by naming this surface; this is that surface.",
238
- "items": {
239
- "type": "object",
240
- "additionalProperties": false,
241
- "required": [
242
- "keyId",
243
- "alg",
244
- "publicKey"
245
- ],
246
- "properties": {
247
- "keyId": {
248
- "$ref": "ids.schema.json#/$defs/keyId",
249
- "description": "The identifier a bundle signature carries in signature.keyId. Unique within this array."
250
- },
251
- "alg": {
252
- "const": "ed25519",
253
- "description": "RFC 0168 §E.2 fixes the attestation algorithm; no other value is defined."
254
- },
255
- "publicKey": {
256
- "type": "string",
257
- "pattern": "^[A-Za-z0-9_-]{43}$",
258
- "description": "The Ed25519 public key, base64url, unpadded (32 bytes) — the half that verifies, never the half that signs."
259
- },
260
- "use": {
261
- "enum": [
262
- "certification-bundle"
263
- ],
264
- "description": "What the key signs. Closed, so a future use is an explicit addition rather than a reinterpretation of a key already published for something else."
265
- },
266
- "retiredAt": {
267
- "type": "string",
268
- "format": "date-time",
269
- "description": "When the key stopped signing. A retired key stays listed so bundles it already signed remain verifiable; dropping it would silently invalidate historical evidence, which is the opposite of what an evidence trail is for."
270
- }
271
- }
272
- }
273
- },
274
235
  "discovery": {
275
236
  "type": "object",
276
237
  "description": "Discovery advertisement (capabilities-change-detection.md). Optional in v1; absence means the host serves only the public unauthenticated payload at /.well-known/openwop. Landed by RFC 0011.",