@openwop/openwop-conformance 1.1.1 → 1.2.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 (86) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +2 -2
  3. package/coverage.md +26 -14
  4. package/fixtures/conformance-agent-low-confidence.json +7 -4
  5. package/fixtures/conformance-agent-pack-handoff-schema-validation.json +30 -0
  6. package/fixtures/conformance-agent-reasoning.json +23 -4
  7. package/fixtures/conformance-dispatch-cross-worker-handoff-child-a.json +27 -0
  8. package/fixtures/conformance-dispatch-cross-worker-handoff-child-b.json +25 -0
  9. package/fixtures/conformance-dispatch-cross-worker-handoff.json +60 -0
  10. package/fixtures/conformance-dispatch-input-mapping-child.json +25 -0
  11. package/fixtures/conformance-dispatch-input-mapping.json +49 -0
  12. package/fixtures/conformance-dispatch-output-mapping-child.json +27 -0
  13. package/fixtures/conformance-dispatch-output-mapping.json +49 -0
  14. package/fixtures/conformance-subworkflow-input-mapping-child.json +27 -0
  15. package/fixtures/conformance-subworkflow-input-mapping.json +33 -0
  16. package/fixtures.md +12 -2
  17. package/package.json +1 -1
  18. package/schemas/README.md +7 -0
  19. package/schemas/agent-ref.schema.json +1 -1
  20. package/schemas/ai-envelope.schema.json +106 -0
  21. package/schemas/capabilities.schema.json +248 -0
  22. package/schemas/core-conformance-mock-agent-config.schema.json +147 -0
  23. package/schemas/dispatch-config.schema.json +26 -0
  24. package/schemas/envelopes/clarification.request.schema.json +43 -0
  25. package/schemas/envelopes/error.schema.json +26 -0
  26. package/schemas/envelopes/schema.request.schema.json +22 -0
  27. package/schemas/envelopes/schema.response.schema.json +22 -0
  28. package/schemas/node-pack-manifest.schema.json +5 -0
  29. package/schemas/pack-lockfile.schema.json +16 -0
  30. package/schemas/workflow-chain-pack-manifest.schema.json +226 -0
  31. package/src/lib/webhook-receiver.ts +137 -0
  32. package/src/lib/workflow-chain-expansion.ts +213 -0
  33. package/src/scenarios/agentPackCatalog.test.ts +216 -0
  34. package/src/scenarios/agentPackHandoffSchemaValidation.test.ts +146 -0
  35. package/src/scenarios/agentReasoningEvents.test.ts +58 -7
  36. package/src/scenarios/agents-run-tool-allowlist.test.ts +182 -0
  37. package/src/scenarios/ai-envelope-shape.test.ts +362 -0
  38. package/src/scenarios/aiEnvelope.capBreached.test.ts +173 -0
  39. package/src/scenarios/aiEnvelope.contractRefusal.test.ts +150 -0
  40. package/src/scenarios/aiEnvelope.correlationReplay.test.ts +69 -0
  41. package/src/scenarios/aiEnvelope.redaction.test.ts +73 -0
  42. package/src/scenarios/aiEnvelope.schemaDrift.test.ts +87 -0
  43. package/src/scenarios/aiEnvelope.trustBoundaryPropagation.test.ts +143 -0
  44. package/src/scenarios/aiEnvelope.universalKinds.test.ts +176 -0
  45. package/src/scenarios/append-ordering.test.ts +44 -0
  46. package/src/scenarios/artifact-auth.test.ts +58 -0
  47. package/src/scenarios/blob-cross-tenant-isolation.test.ts +66 -0
  48. package/src/scenarios/blob-presign-expiry.test.ts +66 -0
  49. package/src/scenarios/blob-roundtrip.test.ts +48 -0
  50. package/src/scenarios/cache-cross-tenant-isolation.test.ts +61 -0
  51. package/src/scenarios/cache-ttl-expiry.test.ts +47 -0
  52. package/src/scenarios/dispatch-cross-worker-handoff.test.ts +98 -0
  53. package/src/scenarios/dispatch-input-mapping.test.ts +94 -0
  54. package/src/scenarios/dispatch-output-mapping.test.ts +65 -0
  55. package/src/scenarios/fs-path-traversal.test.ts +124 -0
  56. package/src/scenarios/idempotency-key-determinism.test.ts +230 -0
  57. package/src/scenarios/interrupt-token-matrix.test.ts +126 -0
  58. package/src/scenarios/kv-atomic-increment.test.ts +74 -0
  59. package/src/scenarios/kv-cas.test.ts +75 -0
  60. package/src/scenarios/kv-cross-tenant-isolation.test.ts +85 -0
  61. package/src/scenarios/kv-ttl-expiry.test.ts +47 -0
  62. package/src/scenarios/mcp-server-elicitation-bridge.test.ts +92 -0
  63. package/src/scenarios/mcp-server-prompt-roundtrip.test.ts +80 -0
  64. package/src/scenarios/mcp-server-resource-roundtrip.test.ts +82 -0
  65. package/src/scenarios/mcp-server-sampling-bridge.test.ts +84 -0
  66. package/src/scenarios/mcp-server-tool-roundtrip.test.ts +107 -0
  67. package/src/scenarios/mcp-server-untrusted-args.test.ts +105 -0
  68. package/src/scenarios/pause-resume.test.ts +43 -0
  69. package/src/scenarios/queue-ack-nack-dlq.test.ts +67 -0
  70. package/src/scenarios/queue-cross-tenant-isolation.test.ts +66 -0
  71. package/src/scenarios/queue-publish-consume-roundtrip.test.ts +48 -0
  72. package/src/scenarios/search-bm25-roundtrip.test.ts +47 -0
  73. package/src/scenarios/spec-corpus-validity.test.ts +17 -1
  74. package/src/scenarios/sql-injection-rejection.test.ts +84 -0
  75. package/src/scenarios/sql-transaction-atomicity.test.ts +66 -0
  76. package/src/scenarios/stream-subscribe-from-beginning.test.ts +66 -0
  77. package/src/scenarios/subworkflow-input-mapping.test.ts +100 -0
  78. package/src/scenarios/table-cross-tenant-isolation.test.ts +65 -0
  79. package/src/scenarios/table-cursor-pagination.test.ts +47 -0
  80. package/src/scenarios/table-schema-enforcement.test.ts +47 -0
  81. package/src/scenarios/vector-knn-roundtrip.test.ts +48 -0
  82. package/src/scenarios/webhook-receiver-adversarial.test.ts +210 -0
  83. package/src/scenarios/workflow-chain-expansion.test.ts +366 -0
  84. package/src/scenarios/workflow-chain-pack-manifest-validation.test.ts +232 -0
  85. package/src/scenarios/workflow-chain-pack-signature-verification.test.ts +138 -0
  86. package/src/scenarios/workflow-chain-unresolvable-typeid.test.ts +170 -0
@@ -0,0 +1,147 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/core-conformance-mock-agent-config.schema.json",
4
+ "title": "CoreConformanceMockAgentConfig",
5
+ "description": "Config shape for the `core.conformance.mock-agent` typeId (RFC 0023). Conformance-only — hosts MUST refuse this typeId for production tenants unless `capabilities.conformance.mockAgent` is advertised. Emission contract is normative per RFC 0023 §B: when set, the host fires `agent.reasoned`, `agent.toolCalled`/`agent.toolReturned` pairs, `agent.handoff`, and `agent.decided` in the order documented, then evaluates the resolved escalation threshold against any `confidence` value and follows with `node.suspended { reason: 'low-confidence' }` when below threshold (`spec/v1/interrupt.md` §`kind: \"low-confidence\"`).",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "agentId": {
10
+ "type": "string",
11
+ "minLength": 3,
12
+ "description": "Optional override for the `agentId` field carried on emitted `agent.*` events. Resolution order: this field → `nodes[].agent.agentId` (the workflow-level pin) → a host-minted synthetic id (e.g., `host:mock-agent:<nodeId>`). Hosts SHOULD prefer the workflow-level pin so conformance fixtures stay self-describing."
13
+ },
14
+ "mockReasoning": {
15
+ "oneOf": [
16
+ { "type": "boolean" },
17
+ {
18
+ "type": "object",
19
+ "required": ["summary"],
20
+ "additionalProperties": false,
21
+ "properties": {
22
+ "summary": {
23
+ "type": "string",
24
+ "description": "Short one-line digest, projected onto `agent.reasoned.payload.summary` per RFC 0002 §B."
25
+ },
26
+ "trace": {
27
+ "type": "string",
28
+ "description": "Full reasoning text. Hosts SHOULD redact when the run advertises BYOK + redaction per RFC 0002 §B (`agent.reasoned`)."
29
+ },
30
+ "tokenCount": {
31
+ "type": "integer",
32
+ "minimum": 0,
33
+ "description": "Optional reasoning-token count for observability/cost attribution."
34
+ }
35
+ }
36
+ }
37
+ ],
38
+ "description": "When set, emit `agent.reasoned`. Boolean `true` MUST surface a host-generated stub summary; an object is projected onto the `agent.reasoned` payload shape from RFC 0002 §B. When unset, no `agent.reasoned` event is emitted."
39
+ },
40
+ "mockToolCalls": {
41
+ "type": "array",
42
+ "description": "Emit `agent.toolCalled` + `agent.toolReturned` pairs in array order. Each pair shares a host-minted `callId`; the `agent.toolReturned.causationId` MUST equal the corresponding `agent.toolCalled.eventId` per RFC 0002 §B. An entry without `result` and without `error` is invalid and MUST be rejected at registration.",
43
+ "items": {
44
+ "type": "object",
45
+ "required": ["toolId"],
46
+ "additionalProperties": false,
47
+ "properties": {
48
+ "toolId": {
49
+ "type": "string",
50
+ "description": "Tool identifier in `<scope>:<tool-id>` form, projected onto `agent.toolCalled.payload.toolId` and `agent.toolReturned.payload.toolId`."
51
+ },
52
+ "arguments": {
53
+ "description": "Arguments object projected onto `agent.toolCalled.payload.arguments`. Any JSON value."
54
+ },
55
+ "result": {
56
+ "description": "Success result projected onto `agent.toolReturned.payload.result`. Any JSON value. Mutually exclusive with `error` (validators that need stricter enforcement SHOULD layer it host-side)."
57
+ },
58
+ "error": {
59
+ "$ref": "#/$defs/ErrorEnvelope",
60
+ "description": "Failure projected onto `agent.toolReturned.payload.error`. Shape matches `error-envelope.schema.json` (inlined here as `$defs.ErrorEnvelope` so the spec-corpus validity test can compile this schema in isolation). When present, the paired `agent.toolReturned` event MUST omit `result`."
61
+ },
62
+ "durationMs": {
63
+ "type": "integer",
64
+ "minimum": 0,
65
+ "description": "Optional duration projected onto `agent.toolReturned.payload.durationMs`."
66
+ }
67
+ }
68
+ }
69
+ },
70
+ "mockHandoff": {
71
+ "type": "object",
72
+ "required": ["toAgentId"],
73
+ "additionalProperties": false,
74
+ "description": "When set, emit `agent.handoff` with `from` taken from `nodes[].agent` (the current node's pin) and `to.agentId` taken from `toAgentId`. When `nodes[].agent` is absent, hosts MUST use a synthetic `from` AgentRef referencing the resolved emitter `agentId`.",
75
+ "properties": {
76
+ "toAgentId": {
77
+ "type": "string",
78
+ "minLength": 3,
79
+ "description": "Destination agent identifier; projected onto `agent.handoff.payload.to.agentId` per RFC 0002 §B (`agent.handoff`)."
80
+ },
81
+ "reason": {
82
+ "type": "string",
83
+ "description": "Optional free-form reason; projected onto `agent.handoff.payload.reason`."
84
+ },
85
+ "context": {
86
+ "description": "Optional payload value handed across the boundary; projected onto `agent.handoff.payload.context`."
87
+ }
88
+ }
89
+ },
90
+ "mockDecision": {
91
+ "type": "object",
92
+ "required": ["decision"],
93
+ "additionalProperties": false,
94
+ "description": "When set, emit `agent.decided`. When `confidence` is present AND below the resolved escalation threshold (`RunOptions.configurable.escalationThreshold` overrides `agent-manifest.confidence.defaultThreshold` overrides the spec default `0.7`), the host MUST follow the `agent.decided` event with `node.suspended { reason: 'low-confidence' }` and transition the run to `'waiting-approval'` per `spec/v1/interrupt.md:278`.",
95
+ "properties": {
96
+ "decision": {
97
+ "description": "Host-defined decision payload projected onto `agent.decided.payload.decision`. Any JSON value."
98
+ },
99
+ "confidence": {
100
+ "type": "number",
101
+ "minimum": 0,
102
+ "maximum": 1,
103
+ "description": "Optional confidence in `[0.0, 1.0]`. Triggers the §F escalation contract from RFC 0002 when below the resolved threshold."
104
+ },
105
+ "reasoning": {
106
+ "type": "string",
107
+ "description": "Optional rationale projected onto `agent.decided.payload.reasoning`."
108
+ }
109
+ }
110
+ },
111
+ "mockConfidence": {
112
+ "type": "number",
113
+ "minimum": 0,
114
+ "maximum": 1,
115
+ "description": "Shorthand. When set AND `mockDecision` is absent, the host emits `agent.decided` with a synthetic decision payload (host-defined; the conformance suite does not assert on its shape) carrying this confidence. Triggers the same escalation contract as `mockDecision.confidence`. Retained for compatibility with the pre-RFC-0023 fixture surface; new fixtures SHOULD prefer the explicit `mockDecision` form."
116
+ }
117
+ },
118
+ "examples": [
119
+ {
120
+ "mockReasoning": { "summary": "Considered three options; chose A.", "tokenCount": 42 },
121
+ "mockToolCalls": [
122
+ { "toolId": "openwop.search.web", "arguments": { "q": "openwop" }, "result": ["hit-1", "hit-2"], "durationMs": 12 }
123
+ ],
124
+ "mockDecision": { "decision": { "next": "summarize" }, "confidence": 0.92 }
125
+ },
126
+ {
127
+ "mockDecision": { "decision": { "kind": "stub-low-conf" }, "confidence": 0.5 }
128
+ },
129
+ {
130
+ "mockConfidence": 0.5
131
+ }
132
+ ],
133
+ "$defs": {
134
+ "ErrorEnvelope": {
135
+ "title": "ErrorEnvelope",
136
+ "description": "Inlined from `error-envelope.schema.json` for in-isolation Ajv compile. Spec corpus schemas compile under the conformance suite's per-file Ajv harness which does not preload sibling schemas — cross-file `$ref`s would fail. The wire shape MUST match `https://openwop.dev/spec/v1/error-envelope.schema.json`; this $defs entry is a copy, not a divergence.",
137
+ "type": "object",
138
+ "required": ["error", "message"],
139
+ "properties": {
140
+ "error": { "type": "string", "minLength": 1 },
141
+ "message": { "type": "string", "minLength": 1 },
142
+ "details": { "type": "object" }
143
+ },
144
+ "additionalProperties": false
145
+ }
146
+ }
147
+ }
@@ -28,6 +28,32 @@
28
28
  "type": "integer",
29
29
  "minimum": 1,
30
30
  "description": "Optional per-run hard cap on dispatch-node executions (across all dispatch nodes in the same run). Independent of `RunOptions.configurable.recursionLimit` / `Capabilities.limits.maxNodeExecutions` — when set, the engine MUST surface a `cap.breached` event with `kind: 'dispatch-iterations'` once exceeded and transition the run to `'failed'`. When absent, the run-level `recursionLimit` cap applies normally (each dispatch counts as one node execution against the run total)."
31
+ },
32
+ "inputMapping": {
33
+ "type": "object",
34
+ "additionalProperties": { "type": "string" },
35
+ "description": "RFC 0022. Default input mapping applied to every dispatched child on the `next-worker` path. Keys are CHILD variable names; values are PARENT variable names. Child receives `inputs[childKey] = parentVariables[parentKey]`. Per-worker overrides (see `perWorkerInputMappings`) take precedence. Mirrors `core.control.subWorkflow.inputMapping` semantics. Gated on `capabilities.agents.dispatchMapping: true`; hosts without that advertisement MUST surface `validation_error` at registration if this field is non-empty."
36
+ },
37
+ "outputMapping": {
38
+ "type": "object",
39
+ "additionalProperties": { "type": "string" },
40
+ "description": "RFC 0022. Default output mapping applied to every completed child on the `next-worker` path. Keys are PARENT variable names; values are CHILD variable names. After a child reaches terminal `completed`, parent's `parentKey` is set to `childVariables[childKey]`. Failed / cancelled children skip the mapping. Per-worker overrides (see `perWorkerOutputMappings`) take precedence. Mirrors `core.control.subWorkflow.outputMapping` semantics. Gated on `capabilities.agents.dispatchMapping: true`."
41
+ },
42
+ "perWorkerInputMappings": {
43
+ "type": "object",
44
+ "additionalProperties": {
45
+ "type": "object",
46
+ "additionalProperties": { "type": "string" }
47
+ },
48
+ "description": "RFC 0022. Per-worker input mapping overrides, keyed by the child `workflowId` that the supervisor may select. When the supervisor's `next-worker` decision names a `workerId` present in this map, the dispatch node uses `perWorkerInputMappings[workerId]` instead of the default `inputMapping`. Lets authors wire distinct variable subsets per potential child."
49
+ },
50
+ "perWorkerOutputMappings": {
51
+ "type": "object",
52
+ "additionalProperties": {
53
+ "type": "object",
54
+ "additionalProperties": { "type": "string" }
55
+ },
56
+ "description": "RFC 0022. Per-worker output mapping overrides, same fallback semantics as `perWorkerInputMappings`."
31
57
  }
32
58
  },
33
59
  "additionalProperties": false,
@@ -0,0 +1,43 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/envelopes/clarification.request.schema.json",
4
+ "title": "ClarificationRequestPayload",
5
+ "description": "Payload for the universal `clarification.request` AI Envelope kind. Emitted by an LLM when it needs additional information from the user before continuing. The engine MUST lift this payload to a `kind: \"clarification\"` `InterruptPayload` per `spec/v1/interrupt.md` §\"`kind: 'clarification'`\" and pause the node, OR refuse the run with `not-applicable` if the host does not implement clarification (clarifications are part of the `openwop-interrupts` profile per `profiles.md`). Counts against `Capabilities.limits.clarificationRounds`; on breach the engine emits `cap.breached { kind: 'clarification' }` and fails the node per `capabilities.md` §\"Engine-enforced limits\".",
6
+ "type": "object",
7
+ "required": ["questions"],
8
+ "properties": {
9
+ "questions": {
10
+ "type": "array",
11
+ "minItems": 1,
12
+ "description": "One or more clarification questions to surface to the user. The engine MUST preserve ordering when lifting to the interrupt's `ClarificationData.questions[]`.",
13
+ "items": {
14
+ "type": "object",
15
+ "required": ["id", "question"],
16
+ "properties": {
17
+ "id": {
18
+ "type": "string",
19
+ "minLength": 1,
20
+ "maxLength": 128,
21
+ "description": "Stable id for this question within the envelope. Used by the matching `ClarificationResume.answers[].id`. SHOULD be deterministic across re-emissions of the same logical clarification (so replay can match answers to questions)."
22
+ },
23
+ "question": {
24
+ "type": "string",
25
+ "minLength": 1,
26
+ "description": "Human-readable question text. Localization is host-handled; the spec does not normate a per-locale variant on the envelope."
27
+ },
28
+ "schema": {
29
+ "description": "Optional JSON Schema constraining the answer shape (choices, free-text length, etc.). When present, the host's resolve endpoint SHOULD validate the answer against this schema before returning it to the engine.",
30
+ "type": "object"
31
+ }
32
+ },
33
+ "additionalProperties": false
34
+ }
35
+ },
36
+ "contextType": {
37
+ "type": "string",
38
+ "maxLength": 128,
39
+ "description": "Optional UI hint for the host (e.g., `\"approval-feedback\"`, `\"form-field\"`). Not normative; passed through to the lifted `ClarificationData.contextType`."
40
+ }
41
+ },
42
+ "additionalProperties": false
43
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/envelopes/error.schema.json",
4
+ "title": "ErrorEnvelopePayload",
5
+ "description": "Payload for the universal `error` AI Envelope kind. The LLM's error report — the model said `I couldn't do that`. Distinct from the HTTP-level `ErrorEnvelope` in `error-envelope.schema.json`, which is the host's error report. Hosts MUST NOT collapse these two surfaces — an `error` envelope is a successful turn whose payload happens to be an error report, and the recommended `RunEventDoc.type` is `log.appended` (level `error`), NOT `node.failed`. The model deliberately surfaced the failure rather than guessing; treating it as a node failure penalizes correct behavior.",
6
+ "type": "object",
7
+ "required": ["code", "message"],
8
+ "properties": {
9
+ "code": {
10
+ "type": "string",
11
+ "minLength": 1,
12
+ "maxLength": 128,
13
+ "description": "Namespaced machine-readable error code. SHOULD use snake_case. Recommended codes: `validation_failed` (LLM tried but couldn't satisfy the requested contract), `tool_call_refused` (LLM declined to invoke a tool per a safety guideline), `context_exhausted` (LLM ran out of context window mid-emission), `ambiguous_intent` (LLM couldn't disambiguate the user's request). Vendor codes MAY be namespaced as `<vendor>.<code>` per `host-extensions.md`."
14
+ },
15
+ "message": {
16
+ "type": "string",
17
+ "minLength": 1,
18
+ "description": "Human-readable error description from the LLM's perspective. Surfaces to users when the host's UI renders error envelopes."
19
+ },
20
+ "details": {
21
+ "type": "object",
22
+ "description": "Optional structured context. Schema varies by `code` — consumers MUST tolerate missing/unknown fields. Examples: `{requestedKind, requiredFields}` for `validation_failed`, `{toolName, refusalReason}` for `tool_call_refused`."
23
+ }
24
+ },
25
+ "additionalProperties": false
26
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/envelopes/schema.request.schema.json",
4
+ "title": "SchemaRequestPayload",
5
+ "description": "Payload for the universal `schema.request` AI Envelope kind. Emitted by an LLM when it wants the JSON Schema of a kind it doesn't have memorized (or wants to verify a kind's current shape before emitting). The engine's response is NOT a `RunEventDoc` — it is an out-of-band addition to the LLM's context for the next turn (returned via the same mechanism the host uses to inject system-prompt content). The response document SHOULD include the per-kind JSON Schema at `Capabilities.schemaVersions[envelopeType]`. Counts against `Capabilities.limits.schemaRounds`; on breach the engine emits `cap.breached { kind: 'schema' }` and fails the node per `capabilities.md` §\"Engine-enforced limits\".",
6
+ "type": "object",
7
+ "required": ["envelopeType"],
8
+ "properties": {
9
+ "envelopeType": {
10
+ "type": "string",
11
+ "minLength": 1,
12
+ "maxLength": 256,
13
+ "description": "Envelope kind the LLM is asking about. MUST be present in the host's `Capabilities.supportedEnvelopes` advertisement; engines MUST refuse `schema.request` for unknown kinds with `unknown_envelope_kind` (consistent with the production-flow ordering in `ai-envelope.md`)."
14
+ },
15
+ "reason": {
16
+ "type": "string",
17
+ "maxLength": 1024,
18
+ "description": "Optional diagnostic explaining why the LLM is requesting the schema (e.g., `\"I'm not sure my last emission matched\"`). Surfaces in OTel spans and debug bundles; never persisted into security-relevant payloads."
19
+ }
20
+ },
21
+ "additionalProperties": false
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/envelopes/schema.response.schema.json",
4
+ "title": "SchemaResponsePayload",
5
+ "description": "Payload for the universal `schema.response` AI Envelope kind. Side-channel acknowledgement that the LLM received a `schema.request` reply. Never surfaces to users. Engines MAY count this against `Capabilities.limits.envelopesPerTurn` or exempt it; conformance does not lock this choice (see `ai-envelope.md` §\"Universal kinds\").",
6
+ "type": "object",
7
+ "required": ["envelopeType", "ack"],
8
+ "properties": {
9
+ "envelopeType": {
10
+ "type": "string",
11
+ "minLength": 1,
12
+ "maxLength": 256,
13
+ "description": "Envelope kind whose schema delivery the LLM is acknowledging. SHOULD match the `envelopeType` from the preceding `schema.request`. Engines MAY refuse mismatches with `envelope_payload_invalid` but are not required to (the LLM's acknowledgement is advisory)."
14
+ },
15
+ "ack": {
16
+ "type": "boolean",
17
+ "const": true,
18
+ "description": "Always `true`. The field exists so the payload validates structurally; an `ack: false` is meaningless (the LLM would simply not emit this envelope)."
19
+ }
20
+ },
21
+ "additionalProperties": false
22
+ }
@@ -10,6 +10,11 @@
10
10
  { "properties": { "agents": { "type": "array", "minItems": 1 } }, "required": ["agents"] }
11
11
  ],
12
12
  "properties": {
13
+ "kind": {
14
+ "type": "string",
15
+ "const": "node",
16
+ "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."
17
+ },
13
18
  "name": {
14
19
  "type": "string",
15
20
  "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.",
@@ -25,6 +25,22 @@
25
25
  "type": "array",
26
26
  "description": "Resolved pack records, one per pack referenced (transitively) by the workspace's workflow definitions. Order is informational only; resolvers MUST NOT rely on order. Empty arrays are allowed (a workspace with no packs).",
27
27
  "items": { "$ref": "#/$defs/ResolvedPack" }
28
+ },
29
+ "overrides": {
30
+ "type": "object",
31
+ "description": "Optional override map per `node-packs.md` §\"Transitive dependency resolution\" §\"Override pinning\". Keys are pack names; values are exact pinned versions that resolvers MUST honor ahead of normal range resolution. The override version MUST still satisfy at least one parent's declared range — silently breaking the contract fails with `pack_dependency_conflict`. Use sparingly: security patches, conflict resolution, supply-chain pinning.",
32
+ "additionalProperties": {
33
+ "type": "string",
34
+ "minLength": 1,
35
+ "maxLength": 64,
36
+ "description": "Exact pinned version that overrides this pack's normal range resolution."
37
+ }
38
+ },
39
+ "fallbackRegistries": {
40
+ "type": "array",
41
+ "items": { "type": "string", "format": "uri" },
42
+ "uniqueItems": true,
43
+ "description": "Optional ordered list of fallback registry base URLs per `registry-operations.md` §\"Registry mirror + federation\". When a pack is not found in the primary `registry`, resolvers MUST consult each fallback in order. The first registry returning a 200 for the version manifest wins. Trust roots are per-registry — a fallback being listed here does NOT imply trust transitivity. Each pack's signature is verified against the issuing registry's `signingKeys[]` allow-list."
28
44
  }
29
45
  },
30
46
  "additionalProperties": false,
@@ -0,0 +1,226 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/workflow-chain-pack-manifest.schema.json",
4
+ "title": "WorkflowChainPackManifest",
5
+ "description": "Manifest for a published OpenWOP workflow-chain pack — `pack.json` at the pack root with `kind: \"workflow-chain\"`. Distinct from node-pack-manifest.schema.json. See workflow-chain-packs.md for the canonical contract and RFC 0013 for the rationale. Chain packs are workflow-edit-time abstractions: a host editor expands each declared chain inline into the parent workflow at author time, so the dispatching runtime sees only concrete `core.*` (or published-vendor) typeIds.",
6
+ "type": "object",
7
+ "required": ["name", "version", "kind", "engines", "chains"],
8
+ "properties": {
9
+ "name": {
10
+ "type": "string",
11
+ "description": "Reverse-DNS pack name per node-packs.md §Naming. Reserved scopes are identical (`core.*` / `vendor.<org>.*` / `community.<author>.*` / `private.<host>.*` / `local.*`).",
12
+ "pattern": "^(core|vendor|community|private)\\.[a-z][a-z0-9_-]*(\\.[a-z][a-zA-Z0-9_-]*)+$",
13
+ "minLength": 1,
14
+ "maxLength": 256
15
+ },
16
+ "version": {
17
+ "type": "string",
18
+ "description": "Pack-level version per Semantic Versioning 2.0.0.",
19
+ "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$"
20
+ },
21
+ "kind": {
22
+ "type": "string",
23
+ "const": "workflow-chain",
24
+ "description": "Pack kind discriminator. MUST be the literal string `\"workflow-chain\"` for this schema. Manifests carrying `kind: \"node\"` (or omitting `kind`) validate against `node-pack-manifest.schema.json` instead."
25
+ },
26
+ "description": { "type": "string", "maxLength": 1024 },
27
+ "author": { "type": "string" },
28
+ "license": { "type": "string", "description": "SPDX license identifier (e.g., `Apache-2.0`)." },
29
+ "homepage": { "type": "string", "format": "uri" },
30
+ "repository": { "type": "string", "format": "uri" },
31
+ "keywords": {
32
+ "type": "array",
33
+ "items": { "type": "string", "maxLength": 64 },
34
+ "maxItems": 50
35
+ },
36
+ "engines": {
37
+ "type": "object",
38
+ "required": ["openwop"],
39
+ "properties": {
40
+ "openwop": {
41
+ "type": "string",
42
+ "description": "Semver range — which openwop protocol versions this pack works against."
43
+ }
44
+ },
45
+ "additionalProperties": true,
46
+ "$comment": "Open by design — packs MAY advertise extra engine constraints (`node`, `python`, etc.) that consumer hosts ignore but operator tooling consumes. Mirrors the shape used in node-pack-manifest.schema.json."
47
+ },
48
+ "dependencies": {
49
+ "type": "object",
50
+ "additionalProperties": { "type": "string" },
51
+ "description": "Other node packs whose typeIds this pack's chains reference. Map of pack name → semver range. The host editor uses this map at expansion time to verify referenced typeIds resolve."
52
+ },
53
+ "chains": {
54
+ "type": "array",
55
+ "minItems": 1,
56
+ "items": { "$ref": "#/$defs/WorkflowChain" },
57
+ "description": "Chains the pack contributes. Each MUST have a unique `chainId` within the pack."
58
+ },
59
+ "signing": { "$ref": "#/$defs/Signing" }
60
+ },
61
+ "additionalProperties": false,
62
+ "$defs": {
63
+ "WorkflowChain": {
64
+ "type": "object",
65
+ "required": ["chainId", "version", "label", "description", "parameters", "dag"],
66
+ "description": "A single workflow-chain entry — a pre-configured DAG fragment + parameter schema that the host editor expands inline at author time. See workflow-chain-packs.md §Chain entry shape.",
67
+ "properties": {
68
+ "chainId": {
69
+ "type": "string",
70
+ "description": "Canonical chain id — namespaced like a node typeId (reverse-DNS pattern). The pack's `name` prefix is recommended (e.g., pack `vendor.acme.editor-presets` exposing `vendor.acme.generatePRD`).",
71
+ "pattern": "^[a-z][a-zA-Z0-9._-]*$",
72
+ "minLength": 1,
73
+ "maxLength": 256
74
+ },
75
+ "version": {
76
+ "type": "string",
77
+ "description": "Per-chain semver. MAY differ from the pack's overall version so a single pack can ship multiple chains that evolve independently.",
78
+ "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$"
79
+ },
80
+ "label": {
81
+ "type": "string",
82
+ "minLength": 1,
83
+ "description": "Human-readable display label for the host editor's drag-tile catalog."
84
+ },
85
+ "description": {
86
+ "type": "string",
87
+ "description": "One-paragraph description of what the chain produces. Surfaced in host editor tile hover-text."
88
+ },
89
+ "parameters": {
90
+ "type": "object",
91
+ "description": "JSON Schema 2020-12 fragment describing the parameter values the host editor MUST collect from the author at drop time. Authors-supplied values are validated against this schema before expansion proceeds; invalid input MUST be rejected with `chain_parameter_invalid`.",
92
+ "additionalProperties": true,
93
+ "$comment": "Open by design — this field IS a JSON Schema document, so it must accept any of the 30+ JSON Schema 2020-12 keywords (`type`, `properties`, `required`, `oneOf`, `allOf`, etc.). Strict closure would require importing the JSON Schema meta-schema."
94
+ },
95
+ "dag": { "$ref": "#/$defs/WorkflowDefinitionFragment" },
96
+ "outputs": {
97
+ "type": "object",
98
+ "additionalProperties": { "$ref": "#/$defs/ChainOutput" },
99
+ "description": "Declared outputs the chain surfaces to the parent workflow. Keys are output names; values declare type + description."
100
+ },
101
+ "capabilities": {
102
+ "type": "array",
103
+ "items": {
104
+ "type": "string",
105
+ "enum": ["streamable", "cacheable", "side-effectful", "mcp-exportable"]
106
+ },
107
+ "uniqueItems": true,
108
+ "description": "Capability traits to propagate to every expanded node. Hosts MUST copy this array into each expanded `WorkflowNode.capabilities` so existing capability gates apply uniformly."
109
+ }
110
+ },
111
+ "additionalProperties": false
112
+ },
113
+ "ChainOutput": {
114
+ "type": "object",
115
+ "required": ["type", "description"],
116
+ "properties": {
117
+ "type": {
118
+ "type": "string",
119
+ "description": "JSON Schema type token (`string` / `number` / `boolean` / `object` / `array`)."
120
+ },
121
+ "description": {
122
+ "type": "string",
123
+ "description": "One-line description of the output's meaning."
124
+ }
125
+ },
126
+ "additionalProperties": false
127
+ },
128
+ "WorkflowDefinitionFragment": {
129
+ "type": "object",
130
+ "required": ["nodes"],
131
+ "description": "Subset of workflow-definition.schema.json. `id`/`name`/`version`/`triggers`/`settings`/`metadata` MUST be omitted (host generates per-expansion); `variables` is replaced by the chain's top-level `parameters`. See workflow-chain-packs.md §WorkflowDefinitionFragment.",
132
+ "properties": {
133
+ "nodes": {
134
+ "type": "array",
135
+ "minItems": 1,
136
+ "items": { "$ref": "#/$defs/FragmentNode" },
137
+ "description": "Nodes in the fragment. Every node's `typeId` MUST reference a published node-pack typeId or a reserved `core.*` typeId."
138
+ },
139
+ "edges": {
140
+ "type": "array",
141
+ "items": { "$ref": "#/$defs/FragmentEdge" },
142
+ "description": "Edges between fragment nodes. Required when `nodes.length > 1`."
143
+ }
144
+ },
145
+ "additionalProperties": false
146
+ },
147
+ "FragmentNode": {
148
+ "type": "object",
149
+ "description": "Mirror of `workflow-definition.schema.json#/$defs/WorkflowNode` with relaxed `required[]` (chain authors MAY omit `name`/`position`/`config`/`inputs` for trivial pass-through nodes). Maintenance note: when fields are added to `WorkflowNode` in `workflow-definition.schema.json`, mirror the addition here so chain packs can express the same shapes. Drift here means chain-pack authors can't use new node features.",
150
+ "required": ["id", "typeId"],
151
+ "properties": {
152
+ "id": {
153
+ "type": "string",
154
+ "minLength": 1,
155
+ "description": "Node id, unique within the fragment. Hosts MUST rewrite these to globally-unique ids at expansion time."
156
+ },
157
+ "typeId": {
158
+ "type": "string",
159
+ "pattern": "^[a-z][a-zA-Z0-9._-]*$",
160
+ "minLength": 1,
161
+ "maxLength": 256
162
+ },
163
+ "name": { "type": "string" },
164
+ "position": {
165
+ "type": "object",
166
+ "properties": {
167
+ "x": { "type": "number" },
168
+ "y": { "type": "number" }
169
+ },
170
+ "additionalProperties": false
171
+ },
172
+ "config": {
173
+ "type": "object",
174
+ "description": "Node config — host-validated against the referenced typeId's config schema. String fields MAY contain `{{params.<name>}}` placeholders that the host MUST substitute at expansion time.",
175
+ "additionalProperties": true,
176
+ "$comment": "Open by design — node config shapes are per-typeId and only known to the host at expansion time when the referenced typeId's config schema is resolved. Cross-typeId enforcement happens at the expansion step, not at manifest-validation time."
177
+ },
178
+ "inputs": {
179
+ "type": "object",
180
+ "description": "Per-port input wiring. String values MAY contain `{{params.<name>}}` placeholders.",
181
+ "additionalProperties": true,
182
+ "$comment": "Open by design — port shapes are per-typeId, same reasoning as `config` above."
183
+ }
184
+ },
185
+ "additionalProperties": false
186
+ },
187
+ "FragmentEdge": {
188
+ "type": "object",
189
+ "required": ["from", "to"],
190
+ "properties": {
191
+ "from": {
192
+ "type": "string",
193
+ "description": "Source node id (must reference a node in `nodes[]`). MAY use `nodeId.outputPort` syntax to bind a specific output port."
194
+ },
195
+ "to": {
196
+ "type": "string",
197
+ "description": "Target node id (must reference a node in `nodes[]`). MAY use `nodeId.inputPort` syntax."
198
+ },
199
+ "condition": {
200
+ "type": "string",
201
+ "description": "Optional edge condition expression — same shape as a top-level workflow edge's condition."
202
+ }
203
+ },
204
+ "additionalProperties": false
205
+ },
206
+ "Signing": {
207
+ "type": "object",
208
+ "description": "Optional signing metadata. Reuses node-packs.md §signing unchanged.",
209
+ "properties": {
210
+ "publicKeyRef": {
211
+ "type": "string",
212
+ "description": "Path inside the tarball to the Ed25519 public key (PEM-encoded)."
213
+ },
214
+ "signatureRef": {
215
+ "type": "string",
216
+ "description": "Path to the detached signature over `pack.json`."
217
+ },
218
+ "method": {
219
+ "type": "string",
220
+ "enum": ["manual", "sigstore"]
221
+ }
222
+ },
223
+ "additionalProperties": false
224
+ }
225
+ }
226
+ }