@openwop/openwop-conformance 1.5.0 → 1.6.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 (61) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +2 -2
  3. package/api/asyncapi.yaml +8 -3
  4. package/api/openapi.yaml +305 -0
  5. package/coverage.md +29 -4
  6. package/fixtures/conformance-phase4-nondet-tool.json +53 -0
  7. package/fixtures/conformance-phase4-replay-divergence.json +40 -0
  8. package/fixtures.md +5 -3
  9. package/package.json +1 -1
  10. package/schemas/README.md +2 -0
  11. package/schemas/capabilities.schema.json +167 -3
  12. package/schemas/credential-reference.schema.json +21 -0
  13. package/schemas/node-pack-manifest.schema.json +112 -1
  14. package/schemas/run-diff-response.schema.json +64 -0
  15. package/schemas/run-event-payloads.schema.json +104 -2
  16. package/schemas/run-event.schema.json +8 -1
  17. package/schemas/run-snapshot.schema.json +11 -0
  18. package/src/lib/behavior-gate.ts +51 -0
  19. package/src/lib/driver.ts +13 -1
  20. package/src/lib/saml-idp.ts +179 -0
  21. package/src/scenarios/approval-gate-events.test.ts +61 -0
  22. package/src/scenarios/approval-gate-flow.test.ts +68 -0
  23. package/src/scenarios/auth-saml-profile.test.ts +119 -0
  24. package/src/scenarios/auth-scim-profile.test.ts +65 -0
  25. package/src/scenarios/authorization-fail-closed.test.ts +80 -0
  26. package/src/scenarios/authorization-roles-shape.test.ts +83 -0
  27. package/src/scenarios/connector-manifest-validity.test.ts +142 -0
  28. package/src/scenarios/credential-payload-redaction.test.ts +93 -0
  29. package/src/scenarios/credentials-capability-shape.test.ts +90 -0
  30. package/src/scenarios/cross-engine-append-behavior.test.ts +204 -0
  31. package/src/scenarios/cross-host-traceparent-propagation.test.ts +13 -6
  32. package/src/scenarios/cross-workspace-isolation.test.ts +72 -0
  33. package/src/scenarios/deadletter-capability-shape.test.ts +59 -0
  34. package/src/scenarios/deadletter-retry-exhaustion.test.ts +62 -0
  35. package/src/scenarios/experimental-tier-shape.test.ts +192 -0
  36. package/src/scenarios/identity-owner-shape.test.ts +64 -0
  37. package/src/scenarios/multi-agent-confidence-escalation.test.ts +13 -12
  38. package/src/scenarios/multi-agent-memory-lifecycle.test.ts +87 -12
  39. package/src/scenarios/multi-region-idempotency-behavior.test.ts +203 -0
  40. package/src/scenarios/oauth-capability-shape.test.ts +97 -0
  41. package/src/scenarios/oauth-connector-redaction.test.ts +91 -0
  42. package/src/scenarios/pack-registry-isolation.test.ts +108 -0
  43. package/src/scenarios/pack-registry-publish.test.ts +1 -1
  44. package/src/scenarios/prompt-mutation-workspace-membership-enforced.test.ts +126 -0
  45. package/src/scenarios/prompt-read-workspace-membership-enforced.test.ts +183 -0
  46. package/src/scenarios/replay-divergence-at-refusal.test.ts +187 -7
  47. package/src/scenarios/replay-observable-sequence-determinism.test.ts +20 -6
  48. package/src/scenarios/run-diff.test.ts +143 -0
  49. package/src/scenarios/sandbox-capability-gate-respected.test.ts +7 -1
  50. package/src/scenarios/sandbox-memory-cap.test.ts +7 -5
  51. package/src/scenarios/sandbox-mvp-behavior.test.ts +280 -0
  52. package/src/scenarios/sandbox-no-cross-pack-mutation.test.ts +7 -1
  53. package/src/scenarios/sandbox-no-host-env-leak.test.ts +5 -1
  54. package/src/scenarios/sandbox-no-host-fs-escape.test.ts +9 -1
  55. package/src/scenarios/sandbox-no-host-process-escape.test.ts +5 -1
  56. package/src/scenarios/sandbox-no-network-escape.test.ts +5 -1
  57. package/src/scenarios/sandbox-timeout-cap.test.ts +7 -5
  58. package/src/scenarios/scheduling-capability-shape.test.ts +81 -0
  59. package/src/scenarios/scheduling-cron-fires-once.test.ts +66 -0
  60. package/src/scenarios/secret-leakage-otel-attribute.test.ts +241 -0
  61. package/src/scenarios/spec-corpus-validity.test.ts +2 -2
@@ -0,0 +1,53 @@
1
+ {
2
+ "id": "conformance-phase4-nondet-tool",
3
+ "name": "Conformance: RFC 0041 §C observable-output-sequence determinism (Phase 4)",
4
+ "version": "1.0",
5
+ "description": "Two-node workflow exercising a nondeterministic tool followed by a structured-output node, used by `replay-observable-sequence-determinism.test.ts` to verify RFC 0041 §C — across original + replay runs of the same workflow against the same engine, the observable RunEventDoc sequence prefix MUST be identical up to and including the nondeterministic-tool node's `node.completed` event. The host's replay path MUST replay the original event log entries (rather than re-executing the tool) for nodes whose `core.tool.*` config carries `nondeterministic: true`. Phase 4 hosts advertising `multiAgent.executionModel.replayDeterminism.supported: true` MUST honor this contract; non-Phase-4 hosts MAY re-execute the tool freely (and consequently observe sequence drift the conformance scenario will not assert against).",
6
+ "nodes": [
7
+ {
8
+ "id": "nondet-tool",
9
+ "typeId": "core.noop",
10
+ "name": "Nondeterministic tool (proxied via core.noop for sample-grade)",
11
+ "position": { "x": 0, "y": 0 },
12
+ "config": {
13
+ "nondeterministic": true,
14
+ "phase4Probe": true
15
+ },
16
+ "inputs": {}
17
+ },
18
+ {
19
+ "id": "structured-call",
20
+ "typeId": "core.ai.structuredOutput",
21
+ "name": "Structured output via mock provider (consumes nondet-tool result)",
22
+ "position": { "x": 200, "y": 0 },
23
+ "config": {
24
+ "provider": "mock",
25
+ "model": "mock-mini",
26
+ "outputSchema": {
27
+ "type": "object",
28
+ "required": ["valid"],
29
+ "properties": { "valid": { "type": "boolean" } }
30
+ }
31
+ },
32
+ "inputs": {
33
+ "messages": {
34
+ "type": "static",
35
+ "value": [
36
+ { "role": "user", "content": "Please emit a valid envelope." }
37
+ ]
38
+ }
39
+ }
40
+ }
41
+ ],
42
+ "edges": [
43
+ { "id": "e1", "sourceNodeId": "nondet-tool", "targetNodeId": "structured-call" }
44
+ ],
45
+ "triggers": [
46
+ { "id": "manual", "type": "manual", "enabled": true }
47
+ ],
48
+ "variables": [],
49
+ "metadata": {
50
+ "tags": ["conformance", "rfc-0041", "phase-4", "observable-sequence-determinism", "multi-agent-execution"]
51
+ },
52
+ "settings": { "timeout": 30000 }
53
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "id": "conformance-phase4-replay-divergence",
3
+ "name": "Conformance: RFC 0041 §B replay-divergence-at-refusal (Phase 4)",
4
+ "version": "1.0",
5
+ "description": "Single `core.ai.structuredOutput` node against the conformance `mock` provider. Conformance scenario `replay-divergence-at-refusal.test.ts` pre-seeds the mock with a two-entry program via `POST /v1/host/sample/test/mock-ai/program` keyed on the structured-call nodeId: entry [0] returns a valid envelope (consumed by the original run); entry [1] returns `stopReason: 'safety'` + `refusalText` (consumed by the `:fork mode: replay`). Phase 4 hosts advertising `multiAgent.executionModel.replayDeterminism.refusalDivergenceEmission: true` MUST detect the divergence at replay time, emit a `replay.divergedAtRefusal` event with `originalEnvelopeKind: 'valid'` + `replayEnvelopeKind: 'refusal'`, and fail the replay with HTTP `422` + `error.code: 'replay_diverged_at_refusal'` per `spec/v1/rest-endpoints.md §\"Common error codes\"`. Silent substitution of the refusal for the original envelope is non-conformant.",
6
+ "nodes": [
7
+ {
8
+ "id": "structured-call",
9
+ "typeId": "core.ai.structuredOutput",
10
+ "name": "Structured output via mock provider (Phase 4 replay-divergence probe)",
11
+ "position": { "x": 0, "y": 0 },
12
+ "config": {
13
+ "provider": "mock",
14
+ "model": "mock-mini",
15
+ "outputSchema": {
16
+ "type": "object",
17
+ "required": ["valid"],
18
+ "properties": { "valid": { "type": "boolean" } }
19
+ }
20
+ },
21
+ "inputs": {
22
+ "messages": {
23
+ "type": "static",
24
+ "value": [
25
+ { "role": "user", "content": "Please emit a valid envelope." }
26
+ ]
27
+ }
28
+ }
29
+ }
30
+ ],
31
+ "edges": [],
32
+ "triggers": [
33
+ { "id": "manual", "type": "manual", "enabled": true }
34
+ ],
35
+ "variables": [],
36
+ "metadata": {
37
+ "tags": ["conformance", "rfc-0041", "phase-4", "replay-divergence", "multi-agent-execution"]
38
+ },
39
+ "settings": { "timeout": 30000 }
40
+ }
package/fixtures.md CHANGED
@@ -84,9 +84,9 @@ All fixtures MUST advertise:
84
84
  | Dispatch Per-Worker Mapping Override | `conformance-dispatch-per-worker-override` | RFC 0022 §A / HVMAP-1c-override — parent with BOTH a default `inputMapping` (`{ input: 'defaultX' }`) AND `perWorkerInputMappings.child-b: { input: 'sharedVar' }`. Verifies `effectiveInputMapping` precedence per §A: child-a receives the default, child-b receives the override. Reuses `conformance-dispatch-cross-worker-handoff-child-a` + `-child-b`. | `completed` | ≤ 30s |
85
85
  | Dispatch deterministic-fail child | `conformance-dispatch-deterministic-fail-child` | RFC 0022 §B / HVMAP-1b-failed — child workflow that ALWAYS terminates `failed` via `core.fail`. Used by `conformance-dispatch-output-mapping` to verify the parent's `outputMapping` is SKIPPED when the child fails terminally. | `failed` | ≤ 5s |
86
86
  | Dispatch cancellable child | `conformance-dispatch-cancellable-child` | RFC 0022 §B / HVMAP-1b-cancelled — child workflow with a long `core.delay` so the test cancels it externally via `POST /v1/runs/{childRunId}/cancel`. Verifies the parent's `outputMapping` is SKIPPED when the child terminates `cancelled`. | `cancelled` | ≤ 60s |
87
- | Multi-Agent Handoff (parent) | `conformance-multi-agent-handoff` | RFC 0037 Phase 1 — exercises the planner→worker handoff state machine. Supervisor decides one `next-worker`, dispatch spawns the child, harvests outputMapping. Conformance reads the event log for the 4 `core.workflowChain.event` transition records in causation-chained order (`dispatch.began → dispatch.succeeded → child.completed → output.harvested`). Capability-gated on `capabilities.multiAgent.executionModel.supported`. | `completed` | ≤ 30s |
88
- | Multi-Agent Handoff (child) | `conformance-multi-agent-handoff-child` | RFC 0037 Phase 1 — child for `conformance-multi-agent-handoff`. Declares `childOutcome.defaultValue='handoff-complete'`; the parent's outputMapping harvests it onto `parentResult`, triggering the `output.harvested` transition event. | `completed` | ≤ 5s |
89
- | Multi-Agent Confidence Escalation | `conformance-multi-agent-confidence-escalation` | RFC 0039 §A — exercises the Phase 2 confidence-floor escalation contract. Supervisor's `mockDispatchPlan` carries ONE decision with `confidence: 0.3` (below the 0.5 spec floor). The host MUST emit `core.workflowChain.confidence-escalated` AND suspend with a clarification interrupt BEFORE any dispatch.began fires; conformance asserts zero `core.workflowChain.event` records (no dispatch). Capability-gated on `capabilities.multiAgent.executionModel.version >= 2`. | `waiting-clarification` | ≤ 30s |
87
+ | Multi-Agent Handoff (parent) | `conformance-multi-agent-handoff` | RFC 0037 (`version: 1`) — exercises the planner→worker handoff state machine. Supervisor decides one `next-worker`, dispatch spawns the child, harvests outputMapping. Conformance reads the event log for the 4 `core.workflowChain.event` transition records in causation-chained order (`dispatch.began → dispatch.succeeded → child.completed → output.harvested`). Capability-gated on `capabilities.multiAgent.executionModel.supported`. | `completed` | ≤ 30s |
88
+ | Multi-Agent Handoff (child) | `conformance-multi-agent-handoff-child` | RFC 0037 (`version: 1`) — child for `conformance-multi-agent-handoff`. Declares `childOutcome.defaultValue='handoff-complete'`; the parent's outputMapping harvests it onto `parentResult`, triggering the `output.harvested` transition event. | `completed` | ≤ 5s |
89
+ | Multi-Agent Confidence Escalation | `conformance-multi-agent-confidence-escalation` | RFC 0039 §A (`version: 2`) — exercises the confidence-floor escalation contract. Supervisor's `mockDispatchPlan` carries ONE decision with `confidence: 0.3` (below the 0.5 spec floor). The host MUST emit `core.workflowChain.confidence-escalated` AND suspend with a clarification interrupt BEFORE any dispatch.began fires; conformance asserts zero `core.workflowChain.event` records (no dispatch). Capability-gated on `capabilities.multiAgent.executionModel.version >= 2`. | `waiting-clarification` | ≤ 30s |
90
90
  | Agent Memory Round-Trip | `conformance-agent-memory-roundtrip` | Phase 3 — `MemoryAdapter.list/get` write → read | `completed` | ≤ 15s |
91
91
  | Agent Memory Cross-Tenant | `conformance-agent-memory-cross-tenant` | Phase 3 / CTI-1 — cross-tenant probe MUST return `[]` / `null` | `completed` | ≤ 10s |
92
92
  | Agent Memory Redaction | `conformance-agent-memory-redaction` | Phase 3 / SR-1 — BYOK plaintext surfaces as `[REDACTED:<id>]` on read | `completed` | ≤ 15s |
@@ -113,6 +113,8 @@ All fixtures MUST advertise:
113
113
  | Envelope Refusal | `conformance-envelope-refusal` | RFC 0032 §B.3 + RFC 0033 §D + §F end-to-end refusal — mock provider returns `stopReason: 'safety'` with `refusalText`. Host MUST emit exactly one `envelope.refusal` event, NOT retry (RFC 0033 §D), fail with `error.code: 'envelope_refused_by_provider'`, AND keep refusalText off `RunSnapshot.error.message` (SECURITY invariant `envelope-refusal-no-prompt-leak`). | `failed` (`error.code='envelope_refused_by_provider'`) | ≤ 10s |
114
114
  | Envelope Recovery Applied | `conformance-envelope-recovery-applied` | RFC 0032 §B.6 lenient-parse — mock returns a markdown-fenced JSON envelope (```json\\n...\\n```). Host's `dispatchStructured()` lenient-parse fallback (`tryLenientParse()`) strips the fence, emits exactly one `envelope.recovery.applied` with `path: 'markdown-fence'`, and accepts the parsed value WITHOUT counting against the retry budget per RFC 0033 §D. | `completed` | ≤ 10s |
115
115
  | Envelope NL-to-Format Engaged | `conformance-envelope-nl-to-format-engaged` | RFC 0032 §B.5 NL-to-Format fallback — mock returns natural-language prose on the first 3 attempts (exhausting the retry budget); the host detects the NL shape after exhaustion, emits exactly one `envelope.nlToFormat.engaged { originalEnvelopeType, fallbackCalls: 1 }`, then fires ONE additional dispatch with a corrective coercion fragment. The 4th program entry returns valid JSON; the schema validates; the run terminates `completed`. | `completed` | ≤ 10s |
116
+ | Phase 4 Replay Divergence | `conformance-phase4-replay-divergence` | RFC 0041 §B — single `core.ai.structuredOutput` node against mock provider. Conformance scenario pre-seeds a 2-entry program via the existing mock-AI program seam: entry [0] returns a valid envelope (original run consumes); entry [1] returns `stopReason: 'safety'` + `refusalText` (`:fork mode: replay` consumes). Phase 4 hosts advertising `multiAgent.executionModel.replayDeterminism.refusalDivergenceEmission: true` MUST emit `replay.divergedAtRefusal` + fail replay with `error.code: 'replay_diverged_at_refusal'`. Silent substitution is non-conformant. Pairs with `replay-divergence-at-refusal.test.ts`. | original: `completed`; replay: `failed` (`error.code='replay_diverged_at_refusal'`) | ≤ 10s |
117
+ | Phase 4 Nondeterministic Tool | `conformance-phase4-nondet-tool` | RFC 0041 §C — two-node workflow (`core.noop` proxied as a nondeterministic tool → `core.ai.structuredOutput`). Used by `replay-observable-sequence-determinism.test.ts` to verify that across original + replay runs, the observable `RunEventDoc` sequence prefix is identical up to and including the nondeterministic-tool node's `node.completed` event. The host's replay path MUST replay the original event log entries (rather than re-executing the tool) for nodes whose `core.tool.*` config carries `nondeterministic: true`. Phase 4 hosts advertising `multiAgent.executionModel.replayDeterminism.supported: true` honor this contract. | original + replay: `completed`; observable prefixes equal up to the nondet boundary | ≤ 10s |
116
118
 
117
119
  The `messages`-mode stream fixture (AI token streaming) is covered by the deterministic mock-provider surface in `spec/v1/run-options.md`. Hosts that do not advertise `Capabilities.testing.mockProviders` skip-equivalent on those scenarios.
118
120
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
5
5
  "repository": {
6
6
  "type": "git",
package/schemas/README.md CHANGED
@@ -17,6 +17,7 @@
17
17
  | `conversation-event.schema.json` | `channels-and-reducers.md` + conversation RFC | Multi-turn conversation event shape for orchestrator-driven HITL flows |
18
18
  | `conversation-turn.schema.json` | `channels-and-reducers.md` + conversation RFC | Conversation turn shape for user/agent/system messages |
19
19
  | `core-conformance-mock-agent-config.schema.json` | `node-packs.md` + RFC 0023 | Config shape for the conformance-only `core.conformance.mock-agent` typeId — drives `agent.*` event emission on cue (`mockReasoning` / `mockToolCalls` / `mockHandoff` / `mockDecision` / `mockConfidence`). Hosts MUST refuse this typeId for production tenants unless `capabilities.conformance.mockAgent` is advertised. |
20
+ | `credential-reference.schema.json` | `host-capabilities.md` §host.credentials + RFC 0046 | Opaque `{ ref, scope }` handle to a host-stored credential — the only credential artifact on the wire; never carries key material |
20
21
  | `debug-bundle.schema.json` | `debug-bundle.md` | Portable run diagnostic export from `GET /v1/runs/{runId}/debug-bundle` |
21
22
  | `dispatch-config.schema.json` | `node-packs.md` + dispatch RFC | Configuration shape for `core.dispatch` / sub-workflow routing |
22
23
  | `error-envelope.schema.json` | `rest-endpoints.md` + `auth.md` | Canonical `{error, message, details?}` shape returned on every non-2xx |
@@ -31,6 +32,7 @@
31
32
  | `registry-version-manifest.schema.json` | `registry-operations.md` | Registry-augmented version manifest served at `GET /v1/packs/{name}/-/{version}.json`. Extends the bare pack-manifest contract with registry-side metadata (integrity hash, signing-block polymorphism, lifecycle flags). Enforced by the `Validate version manifests against registry-version-manifest schema` step in `.github/workflows/registry-publish.yml`. |
32
33
  | `orchestrator-decision.schema.json` | `node-packs.md` + orchestrator RFC | Decision output shape for orchestrator routing nodes |
33
34
  | `run-ancestry-response.schema.json` | `multi-agent-execution.md` + RFC 0040 | Response body for `GET /v1/runs/{runId}/ancestry` — names the run's immediate parent in the cross-host composition chain (or `parent: null` for top-level runs). Capability-gated on `capabilities.multiAgent.executionModel.crossHostCausation.ancestryEndpointSupported`. |
35
+ | `run-diff-response.schema.json` | `rest-endpoints.md` + RFC 0054 | Response body for `GET /v1/runs/{runId}:diff?against={otherRunId}` — deterministic, replay-aware structured diff of two runs (`divergedAtSeq` + `eventDiffs[]` + `stateDiff`). |
34
36
  | `run-event-payloads.schema.json` | `run-event.schema.json` §RunEventType | Per-RunEventType payload contracts, indexed by `$defs.<typeId>` for opt-in strict validation |
35
37
  | `run-event.schema.json` | `version-negotiation.md` + `RunEventDoc` | Event log envelope + event type enum |
36
38
  | `run-options.schema.json` | `run-options.md` | Per-run input overlay (configurable + tags + metadata) on `POST /v1/runs` |
@@ -370,10 +370,10 @@
370
370
  "type": "array",
371
371
  "items": {
372
372
  "type": "string",
373
- "enum": ["tenant", "user", "run"]
373
+ "enum": ["tenant", "user", "run", "workspace"]
374
374
  },
375
375
  "uniqueItems": true,
376
- "description": "Subset of scopes the host implements. Tenant-scoped secrets are workspace-shared; user-scoped are per-end-user; run-scoped are ephemeral per-run."
376
+ "description": "Subset of scopes the host implements. Tenant-scoped secrets are workspace-shared; user-scoped are per-end-user; run-scoped are ephemeral per-run; `workspace` (RFC 0046/0048) is the explicit sub-tenant scope. Appended `workspace` is additive — hosts that omit it are unaffected."
377
377
  },
378
378
  "resolution": {
379
379
  "type": "string",
@@ -383,6 +383,90 @@
383
383
  },
384
384
  "additionalProperties": false
385
385
  },
386
+ "credentials": {
387
+ "type": "object",
388
+ "description": "RFC 0046 (`Draft`). Portable credential resolution + lifecycle contract — sibling to `secrets`, first-class store-at-rest + workspace sharing + two-key-overlap rotation. A pack references a credential by `{ ref, scope }` (see `credential-reference.schema.json`); the host resolves it into the node sandbox ONLY — never into inputs, persisted variables, channels, any run.* event payload, the debug bundle, or replay state (SECURITY invariant `credential-payload-redaction`). Supersedes the informal BYOK annex; the `secrets` advertisement stays valid.",
389
+ "required": ["supported"],
390
+ "properties": {
391
+ "supported": {
392
+ "type": "boolean",
393
+ "description": "Host implements the host.credentials resolution + lifecycle contract."
394
+ },
395
+ "scopes": {
396
+ "type": "array",
397
+ "items": { "type": "string", "enum": ["user", "workspace", "tenant"] },
398
+ "uniqueItems": true,
399
+ "description": "Subset of resolution scopes the host implements. `workspace` is the RFC 0048 sub-tenant; `tenant` and `user` align with the `secrets.scopes` vocabulary."
400
+ },
401
+ "encryptionAtRest": {
402
+ "type": "boolean",
403
+ "description": "Host encrypts stored credential material at rest."
404
+ },
405
+ "rotation": {
406
+ "type": "string",
407
+ "enum": ["none", "two-key-overlap"],
408
+ "description": "`two-key-overlap`: old + new credential both resolve as valid during a grace window, then the old fails with `credential_not_found` (mirrors `openwop-auth-api-key-rotation`). `none`: no rotation surface."
409
+ },
410
+ "sharing": {
411
+ "type": "boolean",
412
+ "description": "A single stored credential can be referenced by many workflows within a scope (e.g. a workspace-shared key) without copying material between references."
413
+ }
414
+ },
415
+ "additionalProperties": false
416
+ },
417
+ "oauth": {
418
+ "type": "object",
419
+ "description": "RFC 0047 (`Draft`). Host performs OAuth 2.0 grants (authorization-code + refresh) on a user's behalf for connector nodes, stores the acquired token as a `host.credentials` (RFC 0046) entry, refreshes it transparently, and resolves it into the node sandbox as a bearer token. Token material NEVER crosses the wire (SECURITY invariant `credential-payload-redaction`). Distinct from `auth` host-authentication profiles (RFC 0010 = who is the caller; this = what third-party token a node holds).",
420
+ "required": ["supported"],
421
+ "properties": {
422
+ "supported": { "type": "boolean", "description": "Host implements the host.oauth third-party token acquisition + refresh contract." },
423
+ "grants": {
424
+ "type": "array",
425
+ "items": { "type": "string", "enum": ["authorization_code", "client_credentials", "refresh_token"] },
426
+ "uniqueItems": true,
427
+ "description": "OAuth 2.0 grant types the host performs on a node's behalf."
428
+ },
429
+ "providers": {
430
+ "type": "array",
431
+ "description": "Provider catalog the host can acquire tokens for. A connector node's `auth.provider` MUST match an `id` here.",
432
+ "items": {
433
+ "type": "object",
434
+ "required": ["id"],
435
+ "properties": {
436
+ "id": { "type": "string", "minLength": 1, "description": "Stable provider id, e.g. `slack`, `google`." },
437
+ "authUrl": { "type": "string", "format": "uri", "description": "Authorization endpoint." },
438
+ "tokenUrl": { "type": "string", "format": "uri", "description": "Token endpoint." },
439
+ "scopesSupported": { "type": "array", "items": { "type": "string" }, "description": "Scopes this provider exposes." }
440
+ },
441
+ "additionalProperties": false
442
+ }
443
+ }
444
+ },
445
+ "additionalProperties": false
446
+ },
447
+ "authorization": {
448
+ "type": "object",
449
+ "description": "RFC 0049 (`Draft`). Maps an RFC 0048 principal's role to scopes (reusing the API-key scope grammar in `auth.md`) and surfaces authorization decisions as `authorization.decided` events. Fail-closed: an absent/unseeded role denies (SECURITY invariant `authorization-fail-closed`).",
450
+ "required": ["supported"],
451
+ "properties": {
452
+ "supported": { "type": "boolean", "description": "Host implements the role→scope authorization-decision contract." },
453
+ "failClosed": { "const": true, "description": "Absent/unseeded role denies; resolver errors deny. MUST be true when present — see SECURITY invariant `authorization-fail-closed`." },
454
+ "roles": {
455
+ "type": "array",
456
+ "description": "Role catalog. A principal's role resolves to this scope set; a request is authorized when any role-derived scope matches the required scope per the API-key scope-match semantics.",
457
+ "items": {
458
+ "type": "object",
459
+ "required": ["role", "scopes"],
460
+ "properties": {
461
+ "role": { "type": "string", "minLength": 1 },
462
+ "scopes": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true }
463
+ },
464
+ "additionalProperties": false
465
+ }
466
+ }
467
+ },
468
+ "additionalProperties": false
469
+ },
386
470
  "runtimeCapabilities": {
387
471
  "type": "array",
388
472
  "items": { "type": "string", "minLength": 1 },
@@ -398,11 +482,30 @@
398
482
  "type": "object",
399
483
  "additionalProperties": false,
400
484
  "required": ["supported", "version"],
485
+ "if": {
486
+ "properties": { "tier": { "const": "experimental" } },
487
+ "required": ["tier"]
488
+ },
489
+ "then": {
490
+ "required": ["experimentalUntil"]
491
+ },
401
492
  "properties": {
402
493
  "supported": {
403
494
  "type": "boolean",
404
495
  "description": "Host implements the execution loop + handoff state machine per spec/v1/multi-agent-execution.md §\"Execution loop\" + §\"Handoff state machine\". When true, the host MUST emit `core.workflowChain.event` records on every handoff transition per the §\"Transition events\" table."
405
496
  },
497
+ "tier": {
498
+ "type": "string",
499
+ "enum": ["stable", "experimental"],
500
+ "default": "stable",
501
+ "description": "RFC 0042 — stability claim for this capability advertisement. `stable` (the default when absent) means the host commits to the wire shape across v1.x minors. `experimental` means the host advertises the surface as a preview; the wire shape MAY shift compatibly without notice until the underlying RFC graduates to `Accepted` and the host re-advertises as `stable`. Hosts MUST omit the field for capabilities whose underlying RFC is already `Accepted`."
502
+ },
503
+ "experimentalUntil": {
504
+ "type": "string",
505
+ "format": "date",
506
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
507
+ "description": "RFC 0042 §B — required when `tier` is `experimental`. ISO-8601 `YYYY-MM-DD` no more than 12 months past the discovery response date. Reaching this date without graduating the underlying RFC to `Accepted` MUST result in the host either flipping tier to `stable` OR retracting the capability advertisement (or — with an open deprecation RFC — extending with a new `experimentalUntil` per §B sub-block 2)."
508
+ },
406
509
  "version": {
407
510
  "type": "integer",
408
511
  "minimum": 1,
@@ -836,6 +939,29 @@
836
939
  },
837
940
  "additionalProperties": false
838
941
  },
942
+ "scheduling": {
943
+ "type": "object",
944
+ "description": "RFC 0052 (`Draft`). Time-based run initiation behind the `schedule` trigger — gives the trigger a portable, durable, once-per-tick execution contract. Composes with `queueBus` (RFC 0017) where the host backs scheduling with a queue; orthogonal to the in-DAG `core.control.delay` primitive (which delays a node mid-run, not run initiation).",
945
+ "required": ["supported"],
946
+ "properties": {
947
+ "supported": { "type": "boolean" },
948
+ "cron": { "type": "boolean", "description": "Host honors cron-expression schedules." },
949
+ "delayed": { "type": "boolean", "description": "Host honors one-shot delayed execution." },
950
+ "calendar": { "type": "boolean", "description": "Host honors calendar-reference schedules." },
951
+ "maxFutureHorizon": { "type": "string", "description": "ISO-8601 duration (e.g. `P90D`); the farthest-future a run may be scheduled. Schedules beyond it MUST be rejected with `schedule_horizon_exceeded`." }
952
+ },
953
+ "additionalProperties": false
954
+ },
955
+ "deadLetter": {
956
+ "type": "object",
957
+ "description": "RFC 0053 (`Draft`). Run-level dead-letter sink for terminally-failed runs/nodes. On retry exhaustion (RFC 0009), the run is routed to a durable, inspectable sink and a `run.dead_lettered` event is emitted; dead-lettered runs remain fork-eligible (RFC 0011) for the retention window. Distinct from `queueBus.deadLetterSupported`, which dead-letters transport *messages*, not *runs*.",
958
+ "required": ["supported"],
959
+ "properties": {
960
+ "supported": { "type": "boolean" },
961
+ "retentionDays": { "type": "integer", "minimum": 1, "description": "Days a dead-lettered run is retained for inspection/fork before purge." }
962
+ },
963
+ "additionalProperties": false
964
+ },
839
965
  "sql": {
840
966
  "type": "object",
841
967
  "description": "RFC 0018 (`Active`). SQL database adapter with parametric-only enforcement. Hosts MUST reject non-parametric queries that inline user input (`sql-parametric-only` invariant — guards against SQL injection across every workflow).",
@@ -919,6 +1045,44 @@
919
1045
  "required": ["supported"],
920
1046
  "additionalProperties": false
921
1047
  },
1048
+ "packs": {
1049
+ "type": "object",
1050
+ "description": "RFC 0025 (`Active`). Pack-registry surface advertisement. The baseline `/v1/packs/*` read surface (per `spec/v1/node-packs.md` §\"Registry HTTP API\") is unconditional for hosts that ship a pack catalog and does NOT require a capability flag; this object carries optional sub-blocks (currently the test-mode mirror namespace). Hosts that don't expose any optional pack-registry sub-block MAY omit this block entirely.",
1051
+ "properties": {
1052
+ "testMode": {
1053
+ "type": "object",
1054
+ "description": "RFC 0025 §A. Optional `/v1/packs-test/*` mirror surface that exposes the production publish/get/delete/sig contract against an isolated catalog. Lets the conformance suite (`pack-registry-publish.test.ts`) exercise the documented 19-code publish error catalog without `packs:publish` scope on the real registry. Hosts that advertise `supported: true` MUST honor the §C isolation guarantees and MUST surface the same error envelopes and HTTP statuses as the production `/v1/packs/*` surface.",
1055
+ "properties": {
1056
+ "supported": {
1057
+ "type": "boolean",
1058
+ "description": "Host exposes `/v1/packs-test/*` per RFC 0025 §B. When `true`, the conformance suite drives publish-error-catalog assertions through the test namespace; when `false` or absent, the 26 scenarios in `pack-registry-publish.test.ts` soft-skip cleanly."
1059
+ },
1060
+ "isolated": {
1061
+ "type": "boolean",
1062
+ "description": "RFC 0025 §C point 1. MUST be `true` when `supported` is `true` — guarantees the test catalog is persisted distinctly from the production catalog and that a pack PUT'd via `/v1/packs-test/*` MUST NOT appear in `/v1/packs/*` listings."
1063
+ },
1064
+ "catalogResetEndpoint": {
1065
+ "type": "string",
1066
+ "description": "RFC 0025 §C point 4. Optional URL path (e.g. `/v1/packs-test/reset`) that clears the entire test catalog. When advertised, conformance-suite teardown SHOULD call it; the endpoint MUST be idempotent. Hosts MAY omit; in that case the suite leaves disposable timestamped pack names in place and relies on the next host restart to clear in-memory state.",
1067
+ "pattern": "^/"
1068
+ },
1069
+ "scopes": {
1070
+ "type": "array",
1071
+ "items": {
1072
+ "type": "string",
1073
+ "enum": ["core", "vendor", "community", "private", "local"]
1074
+ },
1075
+ "uniqueItems": true,
1076
+ "minItems": 1,
1077
+ "description": "RFC 0025 §A. Which namespace scopes the test catalog accepts in pack names. Public test catalogs SHOULD refuse `private` and `local` (matching the production-registry rule for `packs.openwop.dev`); private dev catalogs MAY accept all five. When omitted, the test catalog defaults to the same scope set as the production namespace it mirrors."
1078
+ }
1079
+ },
1080
+ "required": ["supported"],
1081
+ "additionalProperties": false
1082
+ }
1083
+ },
1084
+ "additionalProperties": false
1085
+ },
922
1086
  "mcp": {
923
1087
  "type": "object",
924
1088
  "description": "RFC 0020 (`Active`). MCP (Model Context Protocol) composition surface. The client half is consumed implicitly via `host.mcp` host-surface; this block adds the optional server half — workflow exposed AS an MCP server with bidirectional sampling/elicitation bridges.",
@@ -1126,7 +1290,7 @@
1126
1290
  "type": "array",
1127
1291
  "items": { "type": "string", "minLength": 1 },
1128
1292
  "uniqueItems": true,
1129
- "description": "Auth profiles the host claims. Canonical ids: `openwop-audit-log-integrity` (auth-profiles.md §Audit-log integrity), `openwop-auth-api-key-rotation`, `openwop-auth-oauth2-client-credentials`, `openwop-auth-oidc-user-bearer`, `openwop-auth-mtls`. Clients SHOULD tolerate unknown profile ids."
1293
+ "description": "Auth profiles the host claims. Canonical ids: `openwop-audit-log-integrity` (auth-profiles.md §Audit-log integrity), `openwop-auth-api-key-rotation`, `openwop-auth-oauth2-client-credentials`, `openwop-auth-oidc-user-bearer`, `openwop-auth-mtls`, `openwop-auth-saml` + `openwop-auth-scim` + `openwop-auth-ldap` (RFC 0050 enterprise identity). Clients SHOULD tolerate unknown profile ids."
1130
1294
  },
1131
1295
  "rotation": {
1132
1296
  "type": "object",
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/credential-reference.schema.json",
4
+ "title": "CredentialReference",
5
+ "description": "RFC 0046. An opaque, host-issued handle to a stored credential. This is the ONLY credential artifact permitted on the wire — it NEVER carries key material. The host's host.credentials resolver dereferences it into the node sandbox at execution time (SECURITY invariant `credential-payload-redaction`).",
6
+ "type": "object",
7
+ "required": ["ref"],
8
+ "properties": {
9
+ "ref": {
10
+ "type": "string",
11
+ "minLength": 1,
12
+ "description": "Opaque host-issued identifier, e.g. `cred_a3b9c2`. Hosts MUST NOT encode secret material in the ref."
13
+ },
14
+ "scope": {
15
+ "type": "string",
16
+ "enum": ["user", "workspace", "tenant"],
17
+ "description": "Resolution scope. MUST match a scope in `capabilities.credentials.scopes`. Absent ⇒ the host's default scope."
18
+ }
19
+ },
20
+ "additionalProperties": false
21
+ }
@@ -69,7 +69,8 @@
69
69
  "description": "Optional agent manifests shipped alongside this pack. Each entry is an AgentManifest (see agent-manifest.schema.json + RFC 0003 §`agents[]` extension). Pure-agent packs MUST set `runtime.language: 'remote'` — agents are interpreted by the host, not bundled as executable artifacts. Mixed packs (nodes + agents) declare the runtime that loads the node implementations; agents remain host-interpreted."
70
70
  },
71
71
  "runtime": { "$ref": "#/$defs/Runtime" },
72
- "signing": { "$ref": "#/$defs/Signing" }
72
+ "signing": { "$ref": "#/$defs/Signing" },
73
+ "connector": { "$ref": "#/$defs/Connector" }
73
74
  },
74
75
  "additionalProperties": false,
75
76
  "$defs": {
@@ -161,6 +162,15 @@
161
162
  "items": { "$ref": "#/$defs/SecretRequirement" },
162
163
  "description": "Secrets the node needs to execute. Resolved by the host's secret-resolution adapter at dispatch time. Hosts that don't advertise `Capabilities.secrets.supported` MUST refuse to dispatch a node with non-empty `requiresSecrets` and return `credential_unavailable`."
163
164
  },
165
+ "requiredCredentials": {
166
+ "type": "array",
167
+ "items": { "$ref": "#/$defs/CredentialRequirement" },
168
+ "description": "RFC 0046. Credentials the node needs, resolved by the host's `host.credentials` resolver at dispatch time (distinct from `requiresSecrets`, which targets the informal BYOK annex). Hosts that don't advertise `Capabilities.credentials.supported` MUST refuse to dispatch a node with non-empty `requiredCredentials` and return `credential_unavailable` (peerDependency `credentials: 'supported'`)."
169
+ },
170
+ "auth": {
171
+ "$ref": "#/$defs/NodeAuth",
172
+ "description": "RFC 0047. The node's third-party authentication need. The host acquires + refreshes the token via the `host.oauth` flow and resolves it into the node sandbox as a bearer token. Hosts that don't advertise `Capabilities.oauth.supported` (or lack the named provider/scope) MUST refuse to register the pack (`oauth_provider_unsupported` / `oauth_scope_unsupported`)."
173
+ },
164
174
  "requiredModelCapabilities": {
165
175
  "type": "array",
166
176
  "items": {
@@ -219,6 +229,107 @@
219
229
  },
220
230
  "additionalProperties": false
221
231
  },
232
+ "CredentialRequirement": {
233
+ "type": "object",
234
+ "required": ["key"],
235
+ "description": "RFC 0046. A credential the node needs, resolved by the host's `host.credentials` resolver into the node sandbox at dispatch time. The node declares only the requirement; raw key material NEVER reaches the protocol surface (events, logs, traces, replay) per the `credential-payload-redaction` invariant.",
236
+ "properties": {
237
+ "key": {
238
+ "type": "string",
239
+ "minLength": 1,
240
+ "description": "Stable identifier the node executor uses to look up the resolved credential (e.g., `'slack-bot-token'`)."
241
+ },
242
+ "scope": {
243
+ "type": "string",
244
+ "enum": ["user", "workspace", "tenant"],
245
+ "description": "Resolution scope. MUST match a scope in `Capabilities.credentials.scopes`. Defaults to the host's default scope when omitted."
246
+ },
247
+ "displayName": {
248
+ "type": "string",
249
+ "description": "Optional human-readable label for credential-management UIs."
250
+ }
251
+ },
252
+ "additionalProperties": false
253
+ },
254
+ "NodeAuth": {
255
+ "type": "object",
256
+ "required": ["type", "provider"],
257
+ "description": "RFC 0047. A node's third-party authentication declaration. The node declares only which provider + scopes it needs; the host performs the OAuth dance and resolves the token in-sandbox. Raw token material NEVER reaches the protocol surface (`credential-payload-redaction` invariant).",
258
+ "properties": {
259
+ "type": {
260
+ "type": "string",
261
+ "enum": ["oauth2"],
262
+ "description": "Authentication mechanism. `oauth2` routes through the `host.oauth` flow."
263
+ },
264
+ "provider": {
265
+ "type": "string",
266
+ "minLength": 1,
267
+ "description": "Provider id; MUST match an advertised `capabilities.oauth.providers[].id` (e.g. `slack`, `google`)."
268
+ },
269
+ "scopes": {
270
+ "type": "array",
271
+ "items": { "type": "string" },
272
+ "description": "OAuth scopes the node requires; each MUST be in the provider's advertised `scopesSupported`."
273
+ }
274
+ },
275
+ "additionalProperties": false
276
+ },
277
+ "ConnectorAuth": {
278
+ "description": "RFC 0045. The auth declaration shared by a connector's actions. Either an RFC 0047 OAuth2 declaration or an RFC 0046 stored-credential reference.",
279
+ "oneOf": [
280
+ { "$ref": "#/$defs/NodeAuth" },
281
+ {
282
+ "type": "object",
283
+ "required": ["type", "key"],
284
+ "properties": {
285
+ "type": { "type": "string", "enum": ["credential"], "description": "Static stored-credential auth via the RFC 0046 host.credentials resolver." },
286
+ "key": { "type": "string", "minLength": 1, "description": "CredentialRequirement key the host resolves into the node sandbox." },
287
+ "scope": { "type": "string", "enum": ["user", "workspace", "tenant"], "description": "Resolution scope; MUST match a scope in `capabilities.credentials.scopes`." }
288
+ },
289
+ "additionalProperties": false
290
+ }
291
+ ]
292
+ },
293
+ "Connector": {
294
+ "type": "object",
295
+ "required": ["id", "displayName"],
296
+ "description": "RFC 0045. Declares this pack a named connector — a typed integration exposing actions (and reusing the existing trigger model). Optional; packs without it remain plain node packs. Actions are normal side-effectful nodes from this pack's `nodes[]` annotated with scheduler hints; the connector block adds metadata, not a new execution kind.",
297
+ "properties": {
298
+ "id": { "type": "string", "pattern": "^[a-z][a-z0-9.-]*$", "description": "Stable connector id, e.g. `salesforce`." },
299
+ "displayName": { "type": "string", "minLength": 1 },
300
+ "auth": { "$ref": "#/$defs/ConnectorAuth", "description": "RFC 0047/0046 auth declaration shared by the connector's actions." },
301
+ "actions": {
302
+ "type": "array",
303
+ "description": "Typed actions the connector exposes. Each `typeId` MUST resolve to a node typeId defined in this pack's `nodes[]` (validation error `connector_action_unresolved` otherwise).",
304
+ "items": {
305
+ "type": "object",
306
+ "required": ["typeId", "displayName"],
307
+ "properties": {
308
+ "typeId": { "type": "string", "minLength": 1, "description": "MUST match a `nodes[].typeId` in this manifest." },
309
+ "displayName": { "type": "string", "minLength": 1 },
310
+ "idempotent": { "type": "boolean", "description": "Action is safe to auto-retry without an idempotency key. Absent/false ⇒ the host MUST NOT auto-retry without one (composes with idempotency.md)." },
311
+ "rateLimit": {
312
+ "type": "object",
313
+ "properties": {
314
+ "requests": { "type": "integer", "minimum": 1 },
315
+ "perSeconds": { "type": "integer", "minimum": 1 }
316
+ },
317
+ "additionalProperties": false,
318
+ "description": "Advertised rate-limit hint the host scheduler SHOULD honor. Does not change the node's wire shape."
319
+ },
320
+ "paginated": { "type": "boolean", "description": "Action returns paginated results." }
321
+ },
322
+ "additionalProperties": false
323
+ }
324
+ },
325
+ "triggers": {
326
+ "type": "array",
327
+ "items": { "type": "string", "minLength": 1 },
328
+ "description": "typeIds of triggers (from the existing trigger model) this connector exposes. Each MUST resolve to a node/trigger typeId in this pack."
329
+ }
330
+ },
331
+ "additionalProperties": false
332
+ },
222
333
  "Runtime": {
223
334
  "type": "object",
224
335
  "required": ["language", "entry"],
@@ -0,0 +1,64 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/run-diff-response.schema.json",
4
+ "title": "RunDiffResponse",
5
+ "description": "RFC 0054 — deterministic, replay-aware structured diff of two runs' event sequences and terminal states, returned by GET /v1/runs/{runId}:diff?against={otherRunId}. The diff is a pure function of the two event logs (see spec/v1/replay.md determinism contract).",
6
+ "type": "object",
7
+ "required": ["a", "b", "eventDiffs", "stateDiff"],
8
+ "properties": {
9
+ "a": {
10
+ "type": "string",
11
+ "description": "The {runId} run (the path resource)."
12
+ },
13
+ "b": {
14
+ "type": "string",
15
+ "description": "The {against} run (the query parameter)."
16
+ },
17
+ "divergedAtSeq": {
18
+ "type": ["integer", "null"],
19
+ "minimum": 0,
20
+ "description": "Event sequence number at which the two logs first diverge; null if identical. Aligns with replay.diverged.divergencePoint in spec/v1/replay.md."
21
+ },
22
+ "eventDiffs": {
23
+ "type": "array",
24
+ "description": "Ordered per-sequence differences. Empty when the logs are identical.",
25
+ "items": {
26
+ "type": "object",
27
+ "required": ["seq", "op"],
28
+ "properties": {
29
+ "seq": {
30
+ "type": "integer",
31
+ "minimum": 0,
32
+ "description": "Event sequence number this diff entry applies to."
33
+ },
34
+ "op": {
35
+ "type": "string",
36
+ "enum": ["added", "removed", "changed"],
37
+ "description": "added = present in b only; removed = present in a only; changed = present in both at this seq but differ by canonical comparison."
38
+ },
39
+ "aEvent": {
40
+ "type": "object",
41
+ "additionalProperties": true,
42
+ "description": "The run-a event at this seq (omitted when op = added). Shape per run-event.schema.json."
43
+ },
44
+ "bEvent": {
45
+ "type": "object",
46
+ "additionalProperties": true,
47
+ "description": "The run-b event at this seq (omitted when op = removed). Shape per run-event.schema.json."
48
+ }
49
+ },
50
+ "additionalProperties": false
51
+ }
52
+ },
53
+ "stateDiff": {
54
+ "type": "object",
55
+ "additionalProperties": true,
56
+ "description": "Diff of terminal RunSnapshot states (status, variables, channels) — redaction-safe (never carries credential material)."
57
+ },
58
+ "truncated": {
59
+ "type": "boolean",
60
+ "description": "True if either run was in-flight and only a prefix was compared."
61
+ }
62
+ },
63
+ "additionalProperties": false
64
+ }