@openwop/openwop-conformance 1.1.1 → 1.3.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 (109) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +2 -2
  3. package/api/redocly.yaml +15 -0
  4. package/coverage.md +27 -14
  5. package/fixtures/conformance-agent-low-confidence.json +7 -4
  6. package/fixtures/conformance-agent-pack-handoff-schema-validation.json +30 -0
  7. package/fixtures/conformance-agent-reasoning-streaming.json +37 -0
  8. package/fixtures/conformance-agent-reasoning.json +23 -4
  9. package/fixtures/conformance-dispatch-cancellable-child.json +27 -0
  10. package/fixtures/conformance-dispatch-cross-worker-handoff-child-a.json +27 -0
  11. package/fixtures/conformance-dispatch-cross-worker-handoff-child-b.json +25 -0
  12. package/fixtures/conformance-dispatch-cross-worker-handoff.json +60 -0
  13. package/fixtures/conformance-dispatch-deterministic-fail-child.json +30 -0
  14. package/fixtures/conformance-dispatch-input-mapping-child.json +25 -0
  15. package/fixtures/conformance-dispatch-input-mapping-no-default.json +49 -0
  16. package/fixtures/conformance-dispatch-input-mapping.json +49 -0
  17. package/fixtures/conformance-dispatch-output-mapping-child.json +27 -0
  18. package/fixtures/conformance-dispatch-output-mapping.json +49 -0
  19. package/fixtures/conformance-dispatch-per-worker-override.json +59 -0
  20. package/fixtures/conformance-subworkflow-input-mapping-child.json +27 -0
  21. package/fixtures/conformance-subworkflow-input-mapping-no-default.json +33 -0
  22. package/fixtures/conformance-subworkflow-input-mapping.json +33 -0
  23. package/fixtures.md +18 -2
  24. package/package.json +1 -1
  25. package/schemas/README.md +7 -0
  26. package/schemas/agent-ref.schema.json +1 -1
  27. package/schemas/ai-envelope.schema.json +106 -0
  28. package/schemas/capabilities.schema.json +264 -0
  29. package/schemas/core-conformance-mock-agent-config.schema.json +152 -0
  30. package/schemas/dispatch-config.schema.json +26 -0
  31. package/schemas/envelopes/clarification.request.schema.json +43 -0
  32. package/schemas/envelopes/error.schema.json +26 -0
  33. package/schemas/envelopes/schema.request.schema.json +22 -0
  34. package/schemas/envelopes/schema.response.schema.json +22 -0
  35. package/schemas/node-pack-manifest.schema.json +5 -0
  36. package/schemas/pack-lockfile.schema.json +16 -0
  37. package/schemas/run-event-payloads.schema.json +35 -1
  38. package/schemas/run-event.schema.json +2 -0
  39. package/schemas/workflow-chain-pack-manifest.schema.json +226 -0
  40. package/src/lib/driver.ts +15 -0
  41. package/src/lib/env.ts +51 -0
  42. package/src/lib/event-log-query.ts +62 -0
  43. package/src/lib/fixtures.ts +38 -1
  44. package/src/lib/host-toggle.ts +54 -0
  45. package/src/lib/multi-agent-capabilities.ts +10 -0
  46. package/src/lib/otel-scrape.ts +59 -0
  47. package/src/lib/webhook-receiver.ts +137 -0
  48. package/src/lib/workflow-chain-expansion.ts +213 -0
  49. package/src/scenarios/agentPackCatalog.test.ts +216 -0
  50. package/src/scenarios/agentPackHandoffSchemaValidation.test.ts +146 -0
  51. package/src/scenarios/agentReasoningEvents.test.ts +58 -7
  52. package/src/scenarios/agentReasoningStreaming.test.ts +193 -0
  53. package/src/scenarios/agents-run-tool-allowlist.test.ts +182 -0
  54. package/src/scenarios/ai-envelope-shape.test.ts +362 -0
  55. package/src/scenarios/aiEnvelope.capBreached.test.ts +261 -0
  56. package/src/scenarios/aiEnvelope.contractRefusal.test.ts +268 -0
  57. package/src/scenarios/aiEnvelope.correlationReplay.test.ts +284 -0
  58. package/src/scenarios/aiEnvelope.redaction.test.ts +253 -0
  59. package/src/scenarios/aiEnvelope.schemaDrift.test.ts +226 -0
  60. package/src/scenarios/aiEnvelope.trustBoundaryPropagation.test.ts +194 -0
  61. package/src/scenarios/aiEnvelope.universalKinds.test.ts +267 -0
  62. package/src/scenarios/append-ordering.test.ts +44 -0
  63. package/src/scenarios/artifact-auth.test.ts +58 -0
  64. package/src/scenarios/blob-cross-tenant-isolation.test.ts +66 -0
  65. package/src/scenarios/blob-presign-expiry.test.ts +99 -0
  66. package/src/scenarios/blob-roundtrip.test.ts +0 -0
  67. package/src/scenarios/cache-cross-tenant-isolation.test.ts +61 -0
  68. package/src/scenarios/cache-ttl-expiry.test.ts +73 -0
  69. package/src/scenarios/dispatch-cross-worker-handoff.test.ts +129 -0
  70. package/src/scenarios/dispatch-input-mapping.test.ts +163 -0
  71. package/src/scenarios/dispatch-output-mapping.test.ts +155 -0
  72. package/src/scenarios/fixtures-gating.test.ts +139 -1
  73. package/src/scenarios/fs-path-traversal.test.ts +124 -0
  74. package/src/scenarios/idempotency-key-determinism.test.ts +230 -0
  75. package/src/scenarios/interrupt-token-matrix.test.ts +126 -0
  76. package/src/scenarios/kv-atomic-increment.test.ts +74 -0
  77. package/src/scenarios/kv-cas.test.ts +75 -0
  78. package/src/scenarios/kv-cross-tenant-isolation.test.ts +85 -0
  79. package/src/scenarios/kv-ttl-expiry.test.ts +78 -0
  80. package/src/scenarios/mcp-server-elicitation-bridge.test.ts +92 -0
  81. package/src/scenarios/mcp-server-prompt-roundtrip.test.ts +80 -0
  82. package/src/scenarios/mcp-server-resource-roundtrip.test.ts +82 -0
  83. package/src/scenarios/mcp-server-sampling-bridge.test.ts +84 -0
  84. package/src/scenarios/mcp-server-tool-roundtrip.test.ts +107 -0
  85. package/src/scenarios/mcp-server-untrusted-args.test.ts +105 -0
  86. package/src/scenarios/otel-trace-propagation-subworkflow.test.ts +19 -0
  87. package/src/scenarios/pack-registry-publish.test.ts +231 -51
  88. package/src/scenarios/pause-resume.test.ts +43 -0
  89. package/src/scenarios/provider-usage.test.ts +185 -0
  90. package/src/scenarios/queue-ack-nack-dlq.test.ts +121 -0
  91. package/src/scenarios/queue-cross-tenant-isolation.test.ts +66 -0
  92. package/src/scenarios/queue-publish-consume-roundtrip.test.ts +88 -0
  93. package/src/scenarios/replay-llm-cache-key.test.ts +166 -25
  94. package/src/scenarios/search-bm25-roundtrip.test.ts +92 -0
  95. package/src/scenarios/spec-corpus-validity.test.ts +17 -1
  96. package/src/scenarios/sql-injection-rejection.test.ts +84 -0
  97. package/src/scenarios/sql-transaction-atomicity.test.ts +95 -0
  98. package/src/scenarios/stream-subscribe-from-beginning.test.ts +103 -0
  99. package/src/scenarios/subworkflow-input-mapping.test.ts +170 -0
  100. package/src/scenarios/table-cross-tenant-isolation.test.ts +65 -0
  101. package/src/scenarios/table-cursor-pagination.test.ts +85 -0
  102. package/src/scenarios/table-schema-enforcement.test.ts +84 -0
  103. package/src/scenarios/vector-knn-roundtrip.test.ts +88 -0
  104. package/src/scenarios/webhook-receiver-adversarial.test.ts +210 -0
  105. package/src/scenarios/workflow-chain-expansion.test.ts +366 -0
  106. package/src/scenarios/workflow-chain-host-expansion.test.ts +202 -0
  107. package/src/scenarios/workflow-chain-pack-manifest-validation.test.ts +232 -0
  108. package/src/scenarios/workflow-chain-pack-signature-verification.test.ts +138 -0
  109. package/src/scenarios/workflow-chain-unresolvable-typeid.test.ts +170 -0
@@ -49,6 +49,23 @@
49
49
  "additionalProperties": false,
50
50
  "description": "Hard limits enforced by the engine. See capabilities.md §3."
51
51
  },
52
+ "envelopeStrictness": {
53
+ "type": "string",
54
+ "enum": ["warn", "strict"],
55
+ "description": "AI Envelope schema-version drift handling per `spec/v1/ai-envelope.md` §\"Capability handshake integration\" (DRAFT v1.x). Optional in v1.x; default when absent is `warn`. Under `warn`, the engine MUST attempt validation against the advertised version of a kind and log `envelope_schema_version_drift` when the emitted `schemaVersion` is lower than advertised. Under `strict`, the same condition MUST cause refusal with `unknown_schema_version`. Emitted `schemaVersion` higher than advertised MUST refuse regardless of strictness."
56
+ },
57
+ "envelopeContracts": {
58
+ "type": "object",
59
+ "description": "AI Envelope contract-gating advertisement per `spec/v1/ai-envelope.md` §\"Capability handshake integration\" (DRAFT v1.x). Optional in v1.x. When `advertised: true`, the host's node-pack manifests carry `EnvelopeContract` blocks per `ai-envelope.md` §\"Envelope Contract\"; tooling and conformance scenarios gate on this flag. When `advertised: false` or the block is absent, hosts MAY accept envelopes without per-typeId `accepts[]` enforcement.",
60
+ "required": ["advertised"],
61
+ "properties": {
62
+ "advertised": {
63
+ "type": "boolean",
64
+ "description": "Host's node-pack manifests carry `EnvelopeContract` blocks."
65
+ }
66
+ },
67
+ "additionalProperties": false
68
+ },
52
69
  "extensions": {
53
70
  "type": "object",
54
71
  "description": "Optional per-canvas-type or per-workflow extensions (additional envelope types, override limits)."
@@ -193,6 +210,17 @@
193
210
  "uniqueItems": true,
194
211
  "description": "Optional v1 host-advertised opaque capability ids that NodeModules may declare in `NodeModule.requires`. Naming convention: dotted, domain-scoped (`chat.sendPrompt`, `canvas.write`, `secrets.byok`). Provider value shapes are documented per-capability alongside consumers, NOT in the protocol package — the protocol owns the *check*, not domain provider contracts. A client that submits a workflow whose nodes declare a `requires` entry SHOULD first verify the host advertises that capability; a host that lacks a capability MUST refuse to dispatch nodes that declare it, terminating the run with `RunSnapshot.error.code = 'capability_not_provided'`. See `capabilities.md` §\"Runtime capabilities\"."
195
212
  },
213
+ "providerUsage": {
214
+ "type": "object",
215
+ "description": "RFC 0026 (Draft). Hosts that emit `provider.usage` events after every LLM provider invocation per RFC 0026 §B. The event carries per-call token counts in the durable event log; cost rollup remains in `RunSnapshot.metrics.openwopCost`. Old hosts ignore.",
216
+ "properties": {
217
+ "supported": { "type": "boolean", "description": "Host emits one `provider.usage` event per LLM provider call." },
218
+ "costEstimates": { "type": "boolean", "description": "When true, the host includes `costEstimateUsd` on `provider.usage` events using its internal rate table. When false/absent, only token counts are emitted." },
219
+ "currency": { "type": "string", "pattern": "^[A-Z]{3}$", "description": "Default ISO 4217 currency for `costEstimateUsd` values. When absent, USD is assumed." }
220
+ },
221
+ "required": ["supported"],
222
+ "additionalProperties": false
223
+ },
196
224
  "aiProviders": {
197
225
  "type": "object",
198
226
  "description": "Optional v1 companion to `secrets`. Advertises which AI providers the host's AI-proxy can route to and which permit BYOK.",
@@ -266,6 +294,17 @@
266
294
  },
267
295
  "additionalProperties": true
268
296
  },
297
+ "conformance": {
298
+ "type": "object",
299
+ "description": "Conformance-only capability block (RFC 0023). Advertises which conformance-only typeIds (`core.conformance.*`) the host has registered. Hosts that don't ship conformance-only typeIds omit this block entirely. Production deployments SHOULD omit this block; conformance-only typeIds carry test hooks (e.g., `mockReasoning`, `mockConfidence`) that MUST NOT be reachable from production tenants.",
300
+ "properties": {
301
+ "mockAgent": {
302
+ "type": "boolean",
303
+ "description": "RFC 0023 §B.2. When `true`, the host has registered the `core.conformance.mock-agent` typeId. The scenarios `agentReasoningEvents.test.ts` and `agentConfidenceEscalation.test.ts` rely on the typeId being reachable. Hosts that register the typeId only for workflow ids matching the conformance fixture prefix (`conformance-*`) and refuse it for other tenants MAY still advertise `true` — the advertisement says only that the typeId is reachable from the conformance suite, not that it is reachable from arbitrary workflows."
304
+ }
305
+ },
306
+ "additionalProperties": false
307
+ },
269
308
  "fixtures": {
270
309
  "type": "array",
271
310
  "items": { "type": "string", "minLength": 1 },
@@ -311,6 +350,11 @@
311
350
  "type": "boolean",
312
351
  "description": "Phase 6. When `true`, host advertises that it implements the `core.dispatch` Core typeId AND honors the conservative-path commitment CP-2 (`core.dispatch` MUST NOT mutate the run's DAG mid-run). Implies (but does NOT require) `agents.orchestrator: true`."
313
352
  },
353
+ "dispatchMapping": {
354
+ "type": "boolean",
355
+ "default": false,
356
+ "description": "Phase 6.1 (RFC 0022 §A). When `true`, host honors `inputMapping` / `outputMapping` / `perWorkerInputMappings` / `perWorkerOutputMappings` on `DispatchConfig` — building child inputs from parent variables before dispatch and harvesting child variables into parent variables on completion. Implies (but does NOT require) `agents.dispatch: true`. Hosts that set `agents.dispatch: true` but omit / `false` this flag MUST refuse workflows that carry non-empty mapping fields at registration with `validation_error` + `details.requiredCapability: 'agents.dispatchMapping'`."
357
+ },
314
358
  "reasoning": {
315
359
  "type": "object",
316
360
  "description": "Phase 1 reasoning-event verbosity configuration.",
@@ -324,6 +368,11 @@
324
368
  "type": "integer",
325
369
  "minimum": 0,
326
370
  "description": "Effective cap on reasoning trace length when verbosity is `summary`. Default 512 tokens."
371
+ },
372
+ "streaming": {
373
+ "type": "boolean",
374
+ "default": false,
375
+ "description": "RFC 0024. When `true`, the host MAY emit `agent.reasoning.delta` events while a reasoning block is still open, in addition to the final `agent.reasoned`. Hosts that omit / `false` this flag emit only the final `agent.reasoned`. Consumers MUST tolerate both modes."
327
376
  }
328
377
  },
329
378
  "additionalProperties": false
@@ -384,6 +433,221 @@
384
433
  "type": "boolean",
385
434
  "description": "Multi-Agent Shift Phase 4. When `true`, host advertises that it implements the `core.conversationGate` typeId AND honors the `conversation.start` / `conversation.exchange` / `conversation.close` suspend variants. Hosts that don't claim this fall back to `clarification.requested` interrupts for multi-turn user interjections."
386
435
  },
436
+ "subWorkflow": {
437
+ "type": "object",
438
+ "description": "Capability surface for `core.subWorkflow` extensions. The baseline `core.subWorkflow` contract (RFC 0007 + `node-packs.md` §contract) is unconditional and does NOT require a capability flag; this object carries the additive extensions a host MAY support. Added by RFC 0022 §B + §C.",
439
+ "properties": {
440
+ "inputMapping": {
441
+ "type": "boolean",
442
+ "default": false,
443
+ "description": "RFC 0022 §B. When `true`, host honors the `inputMapping` field on `core.subWorkflow` configs — seeding the child workflow's initial variable bag from `parentVariables[parentKey]` projections, overriding any matching `variables[].defaultValue` declaration on the child. When `false` or absent, hosts MUST refuse workflows that carry a non-empty `inputMapping` at registration with `validation_error` + `details.requiredCapability: 'subWorkflow.inputMapping'`. Silent ignore is NOT conformant."
444
+ }
445
+ },
446
+ "additionalProperties": false
447
+ },
448
+ "fs": {
449
+ "type": "object",
450
+ "description": "RFC 0014 (`Active`). Filesystem capability — read/write/list/stat/delete inside a sandbox root. Required by the `core.openwop.files` pack. Hosts MUST resolve every input path relative to `sandboxRoot`, reject any path that escapes via `..` segments or symlinks, and enforce `maxFileSizeBytes` on write. Path-traversal rejection is normative — see `SECURITY/invariants.yaml` row `fs-path-traversal`.",
451
+ "properties": {
452
+ "supported": {
453
+ "type": "boolean",
454
+ "description": "Host advertises `ctx.fs.{read,write,delete,stat,list}`. `false` (or omission) signals the host does NOT expose a filesystem surface; `core.openwop.files` registration MUST refuse on such hosts."
455
+ },
456
+ "sandboxRoot": {
457
+ "type": "string",
458
+ "description": "Absolute path. Every fs operation is resolved relative to this root. MUST be set when `supported: true`."
459
+ },
460
+ "maxFileSizeBytes": {
461
+ "type": "integer",
462
+ "minimum": 0,
463
+ "description": "Per-file size cap enforced on write. 0 = unlimited (NOT recommended). Reads of files larger than this MAY return `file_too_large`."
464
+ },
465
+ "image": {
466
+ "type": "object",
467
+ "description": "Image-processing sub-capability. Optional.",
468
+ "properties": {
469
+ "supported": { "type": "boolean" },
470
+ "formats": {
471
+ "type": "array",
472
+ "items": { "type": "string", "enum": ["jpeg", "png", "webp", "avif", "gif"] }
473
+ }
474
+ },
475
+ "additionalProperties": false
476
+ },
477
+ "pdf": {
478
+ "type": "object",
479
+ "description": "PDF-processing sub-capability. Optional.",
480
+ "properties": {
481
+ "supported": { "type": "boolean" }
482
+ },
483
+ "additionalProperties": false
484
+ },
485
+ "transport": {
486
+ "type": "object",
487
+ "description": "Network file-transport sub-capabilities. Optional.",
488
+ "properties": {
489
+ "ftp": { "type": "boolean" },
490
+ "sftp": { "type": "boolean" },
491
+ "ssh": { "type": "boolean" }
492
+ },
493
+ "additionalProperties": false
494
+ }
495
+ },
496
+ "if": { "properties": { "supported": { "const": true } }, "required": ["supported"] },
497
+ "then": { "required": ["supported", "sandboxRoot"] },
498
+ "additionalProperties": false
499
+ },
500
+ "kvStorage": {
501
+ "type": "object",
502
+ "description": "RFC 0015 (`Active`). TTL-aware key-value store with atomic increment + compare-and-swap. Required by `core.openwop.storage` kv-* nodes. Hosts MUST partition values by tenant (`kv-cross-tenant-isolation` invariant) and atomically apply increments + CAS when those flags are advertised.",
503
+ "properties": {
504
+ "supported": { "type": "boolean" },
505
+ "maxKeyBytes": { "type": "integer", "minimum": 0 },
506
+ "maxValueBytes": { "type": "integer", "minimum": 0 },
507
+ "maxTtlSeconds": { "type": "integer", "minimum": 0 },
508
+ "atomicIncrement": { "type": "boolean" },
509
+ "compareAndSwap": { "type": "boolean" }
510
+ },
511
+ "additionalProperties": false
512
+ },
513
+ "tableStorage": {
514
+ "type": "object",
515
+ "description": "RFC 0016 (`Active`). Structured-record store with user-defined schemas. Sibling to kvStorage. Cross-tenant isolation enforced (mirrors RFC 0015 invariant).",
516
+ "properties": {
517
+ "supported": { "type": "boolean" },
518
+ "maxRowsPerTable": { "type": "integer", "minimum": 0 },
519
+ "maxColumnsPerRow": { "type": "integer", "minimum": 0 },
520
+ "indexable": { "type": "boolean" },
521
+ "fullTextSearch": { "type": "boolean" }
522
+ },
523
+ "additionalProperties": false
524
+ },
525
+ "queueBus": {
526
+ "type": "object",
527
+ "description": "RFC 0017 (`Active`). Inbound queue + stream capability — publish, consume (trigger), ack/nack/dead-letter. Cross-tenant message isolation invariant (`queue-cross-tenant-isolation`). Sibling to host.messaging (which is outbound-egress-only).",
528
+ "properties": {
529
+ "supported": { "type": "boolean" },
530
+ "backends": {
531
+ "type": "array",
532
+ "items": { "type": "string", "enum": ["rabbitmq", "kafka", "sqs", "sns", "pubsub", "mqtt", "nats", "redis-streams", "in-memory"] }
533
+ },
534
+ "deadLetterSupported": { "type": "boolean" },
535
+ "stream": {
536
+ "type": "object",
537
+ "properties": {
538
+ "supported": { "type": "boolean" },
539
+ "fromBeginning": { "type": "boolean" }
540
+ },
541
+ "additionalProperties": false
542
+ }
543
+ },
544
+ "additionalProperties": false
545
+ },
546
+ "sql": {
547
+ "type": "object",
548
+ "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).",
549
+ "properties": {
550
+ "supported": { "type": "boolean" },
551
+ "datasources": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
552
+ "transactions": { "type": "boolean" },
553
+ "drivers": {
554
+ "type": "array",
555
+ "items": { "type": "string", "enum": ["postgres", "mysql", "mariadb", "sqlite", "mssql", "clickhouse", "snowflake", "bigquery", "duckdb"] }
556
+ }
557
+ },
558
+ "additionalProperties": false
559
+ },
560
+ "nosql": {
561
+ "type": "object",
562
+ "description": "RFC 0018 (`Active`). MongoDB-shape document store adapter.",
563
+ "properties": {
564
+ "supported": { "type": "boolean" },
565
+ "datasources": { "type": "array" },
566
+ "drivers": { "type": "array", "items": { "type": "string", "enum": ["mongodb", "dynamodb", "cosmosdb", "firestore"] } }
567
+ },
568
+ "additionalProperties": false
569
+ },
570
+ "vectorStore": {
571
+ "type": "object",
572
+ "description": "RFC 0018 (`Active`). Vector-DB capability for k-NN search.",
573
+ "properties": {
574
+ "supported": { "type": "boolean" },
575
+ "collections": { "type": "array" },
576
+ "backends": {
577
+ "type": "array",
578
+ "items": { "type": "string", "enum": ["pinecone", "qdrant", "weaviate", "milvus", "pgvector", "redis", "mongodb-atlas", "chroma", "azure-ai-search", "in-memory"] }
579
+ }
580
+ },
581
+ "additionalProperties": false
582
+ },
583
+ "searchIndex": {
584
+ "type": "object",
585
+ "description": "RFC 0018 (`Active`). Full-text search index adapter.",
586
+ "properties": {
587
+ "supported": { "type": "boolean" },
588
+ "indexes": { "type": "array" },
589
+ "backends": {
590
+ "type": "array",
591
+ "items": { "type": "string", "enum": ["elasticsearch", "opensearch", "meilisearch", "typesense", "algolia"] }
592
+ }
593
+ },
594
+ "additionalProperties": false
595
+ },
596
+ "blobStorage": {
597
+ "type": "object",
598
+ "description": "RFC 0019 (`Active`). Binary artifact store with presigned URLs. Per-bucket tenant isolation. Presigned URLs MUST expire at the advertised TTL.",
599
+ "properties": {
600
+ "supported": { "type": "boolean" },
601
+ "buckets": { "type": "array" },
602
+ "presignSupported": { "type": "boolean" },
603
+ "maxObjectBytes": { "type": "integer", "minimum": 0 }
604
+ },
605
+ "additionalProperties": false
606
+ },
607
+ "cache": {
608
+ "type": "object",
609
+ "description": "RFC 0019 (`Active`). TTL cache for HTTP / AI response memoization. Per-tenant scoping; TTL drift ≤ 1s.",
610
+ "properties": {
611
+ "supported": { "type": "boolean" },
612
+ "maxValueBytes": { "type": "integer", "minimum": 0 },
613
+ "maxTtlSeconds": { "type": "integer", "minimum": 0 }
614
+ },
615
+ "additionalProperties": false
616
+ },
617
+ "workflowChainPacks": {
618
+ "type": "object",
619
+ "description": "RFC 0013 (Phase 1, `Draft`). When `supported: true`, the host's workflow editor implements workflow-chain pack expansion per `workflow-chain-packs.md` — author drops a chain tile, host resolves the pack, prompts for `parameters`, substitutes `{{params.<name>}}` placeholders, rewrites node ids, splices the resulting DAG into the parent workflow. Hosts that don't implement expansion omit this block (or set `supported: false`); conformance scenarios under `conformance/src/scenarios/workflow-chain-*.test.ts` skip cleanly against those hosts.",
620
+ "properties": {
621
+ "supported": {
622
+ "type": "boolean",
623
+ "description": "Whether the host's workflow editor implements chain expansion at author time. `false` (or omission) signals the host does NOT consume workflow-chain packs."
624
+ }
625
+ },
626
+ "required": ["supported"],
627
+ "additionalProperties": false
628
+ },
629
+ "mcp": {
630
+ "type": "object",
631
+ "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.",
632
+ "properties": {
633
+ "supported": { "type": "boolean", "description": "Host advertises a client-side MCP surface (ctx.mcp.*). See spec/v1/mcp-integration.md." },
634
+ "serverMount": {
635
+ "type": "object",
636
+ "description": "Server-side MCP composition (workflow IS an MCP server). When supported, the host mounts an MCP endpoint and routes inbound tools/call, resources/read, prompts/get into workflows. Inbound MUST be treated as `trustBoundary: 'untrusted'`.",
637
+ "properties": {
638
+ "supported": { "type": "boolean" },
639
+ "transports": {
640
+ "type": "array",
641
+ "items": { "type": "string", "enum": ["stdio", "streamable-http"] }
642
+ },
643
+ "samplingBridge": { "type": "boolean", "description": "Inbound sampling/createMessage bridges to the workflow's ctx.callAI." },
644
+ "elicitationBridge": { "type": "boolean", "description": "Inbound elicitation/create bridges to ctx.suspend." }
645
+ },
646
+ "additionalProperties": false
647
+ }
648
+ },
649
+ "additionalProperties": false
650
+ },
387
651
  "compliance": {
388
652
  "type": "object",
389
653
  "description": "Privacy / compliance behavior advertised to clients (closes O5). Lets consumers know what masking mode is in effect so a `\"[REDACTED]\"` value in event log payloads is recognizable as a server-side mask vs an upstream null.",
@@ -0,0 +1,152 @@
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
+ "streamChunks": {
36
+ "type": "array",
37
+ "items": { "type": "string" },
38
+ "description": "RFC 0024. When present, the host MUST emit one `agent.reasoning.delta` event per array entry (in order; `sequence` starts at 0 and increments by 1), THEN emit the closing `agent.reasoned` event whose `reasoning` field equals the concatenation of all `streamChunks`. When absent, only the closing `agent.reasoned` event fires (the pre-RFC-0024 behavior). Gated at conformance test time on `capabilities.agents.reasoning.streaming: true`."
39
+ }
40
+ }
41
+ }
42
+ ],
43
+ "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."
44
+ },
45
+ "mockToolCalls": {
46
+ "type": "array",
47
+ "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.",
48
+ "items": {
49
+ "type": "object",
50
+ "required": ["toolId"],
51
+ "additionalProperties": false,
52
+ "properties": {
53
+ "toolId": {
54
+ "type": "string",
55
+ "description": "Tool identifier in `<scope>:<tool-id>` form, projected onto `agent.toolCalled.payload.toolId` and `agent.toolReturned.payload.toolId`."
56
+ },
57
+ "arguments": {
58
+ "description": "Arguments object projected onto `agent.toolCalled.payload.arguments`. Any JSON value."
59
+ },
60
+ "result": {
61
+ "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)."
62
+ },
63
+ "error": {
64
+ "$ref": "#/$defs/ErrorEnvelope",
65
+ "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`."
66
+ },
67
+ "durationMs": {
68
+ "type": "integer",
69
+ "minimum": 0,
70
+ "description": "Optional duration projected onto `agent.toolReturned.payload.durationMs`."
71
+ }
72
+ }
73
+ }
74
+ },
75
+ "mockHandoff": {
76
+ "type": "object",
77
+ "required": ["toAgentId"],
78
+ "additionalProperties": false,
79
+ "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`.",
80
+ "properties": {
81
+ "toAgentId": {
82
+ "type": "string",
83
+ "minLength": 3,
84
+ "description": "Destination agent identifier; projected onto `agent.handoff.payload.to.agentId` per RFC 0002 §B (`agent.handoff`)."
85
+ },
86
+ "reason": {
87
+ "type": "string",
88
+ "description": "Optional free-form reason; projected onto `agent.handoff.payload.reason`."
89
+ },
90
+ "context": {
91
+ "description": "Optional payload value handed across the boundary; projected onto `agent.handoff.payload.context`."
92
+ }
93
+ }
94
+ },
95
+ "mockDecision": {
96
+ "type": "object",
97
+ "required": ["decision"],
98
+ "additionalProperties": false,
99
+ "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`.",
100
+ "properties": {
101
+ "decision": {
102
+ "description": "Host-defined decision payload projected onto `agent.decided.payload.decision`. Any JSON value."
103
+ },
104
+ "confidence": {
105
+ "type": "number",
106
+ "minimum": 0,
107
+ "maximum": 1,
108
+ "description": "Optional confidence in `[0.0, 1.0]`. Triggers the §F escalation contract from RFC 0002 when below the resolved threshold."
109
+ },
110
+ "reasoning": {
111
+ "type": "string",
112
+ "description": "Optional rationale projected onto `agent.decided.payload.reasoning`."
113
+ }
114
+ }
115
+ },
116
+ "mockConfidence": {
117
+ "type": "number",
118
+ "minimum": 0,
119
+ "maximum": 1,
120
+ "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."
121
+ }
122
+ },
123
+ "examples": [
124
+ {
125
+ "mockReasoning": { "summary": "Considered three options; chose A.", "tokenCount": 42 },
126
+ "mockToolCalls": [
127
+ { "toolId": "openwop.search.web", "arguments": { "q": "openwop" }, "result": ["hit-1", "hit-2"], "durationMs": 12 }
128
+ ],
129
+ "mockDecision": { "decision": { "next": "summarize" }, "confidence": 0.92 }
130
+ },
131
+ {
132
+ "mockDecision": { "decision": { "kind": "stub-low-conf" }, "confidence": 0.5 }
133
+ },
134
+ {
135
+ "mockConfidence": 0.5
136
+ }
137
+ ],
138
+ "$defs": {
139
+ "ErrorEnvelope": {
140
+ "title": "ErrorEnvelope",
141
+ "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.",
142
+ "type": "object",
143
+ "required": ["error", "message"],
144
+ "properties": {
145
+ "error": { "type": "string", "minLength": 1 },
146
+ "message": { "type": "string", "minLength": 1 },
147
+ "details": { "type": "object" }
148
+ },
149
+ "additionalProperties": false
150
+ }
151
+ }
152
+ }
@@ -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.",