@openwop/openwop-conformance 1.163.0 → 2.0.0-rc.1

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 (611) hide show
  1. package/README.md +11 -2
  2. package/dist/cli.js +262 -14
  3. package/dist/lib/certification-bundle-v3.js +142 -0
  4. package/dist/lib/paths.js +25 -3
  5. package/dist/lib/requirement-ids.js +57 -4
  6. package/dist/lib/requirement-ledger.js +1 -0
  7. package/dist/lib/scenario-disposition.js +6 -1
  8. package/dist/lib/spec-coherence.js +13 -32
  9. package/dist/spec-artifacts.lock.json +5 -0
  10. package/package.json +11 -33
  11. package/requirements.json +22892 -11701
  12. package/scenario-majors.json +1493 -0
  13. package/schemas/CORPUS-STAMP.json +137 -5
  14. package/src/cli.ts +236 -19
  15. package/src/global-setup.ts +3 -2
  16. package/src/lib/certification-bundle-v3.ts +175 -0
  17. package/src/lib/corpus-stamp.ts +29 -1
  18. package/src/lib/driver.ts +9 -1
  19. package/src/lib/era2-seed.ts +197 -0
  20. package/src/lib/paths.ts +25 -3
  21. package/src/lib/requirement-ids.ts +54 -4
  22. package/src/lib/requirement-ledger.ts +7 -1
  23. package/src/lib/scenario-disposition.ts +6 -1
  24. package/src/lib/seams.ts +45 -0
  25. package/src/lib/spec-coherence.ts +15 -32
  26. package/src/lib/v2.ts +62 -0
  27. package/src/scenarios/a2a-1-0-agent-card.test.ts +17 -16
  28. package/src/scenarios/a2a-1-0-task-roundtrip.test.ts +11 -10
  29. package/src/scenarios/a2a-card-runtime-consistency.test.ts +18 -17
  30. package/src/scenarios/a2a-peer-authority.test.ts +7 -6
  31. package/src/scenarios/a2a-task-roundtrip.test.ts +30 -29
  32. package/src/scenarios/a2a-version-negotiation.test.ts +16 -14
  33. package/src/scenarios/a2ui-surface-degrades.test.ts +5 -3
  34. package/src/scenarios/a2ui-surface-delta-transport.test.ts +35 -33
  35. package/src/scenarios/a2ui-surface-replay.test.ts +6 -4
  36. package/src/scenarios/a2ui-surface-shape.test.ts +11 -10
  37. package/src/scenarios/a2ui-surface-version-refusal.test.ts +6 -4
  38. package/src/scenarios/a2ui-untrusted-blocks-approval.test.ts +5 -3
  39. package/src/scenarios/agent-capability-degraded-projection.test.ts +12 -11
  40. package/src/scenarios/agent-channel-dispatch.test.ts +18 -16
  41. package/src/scenarios/agent-deployment-lifecycle.test.ts +20 -19
  42. package/src/scenarios/agent-deployment-shape.test.ts +22 -24
  43. package/src/scenarios/agent-eval-run.test.ts +19 -18
  44. package/src/scenarios/agent-eval-suite-shape.test.ts +20 -22
  45. package/src/scenarios/agent-live-allowlist-enforced.test.ts +6 -5
  46. package/src/scenarios/agent-live-invocation-bracket.test.ts +13 -12
  47. package/src/scenarios/agent-live-runtime-shape.test.ts +10 -12
  48. package/src/scenarios/agent-live-structured-output.test.ts +7 -6
  49. package/src/scenarios/agent-loop-iteration-monotonic.test.ts +3 -3
  50. package/src/scenarios/agent-loop-stateful-resume.test.ts +2 -2
  51. package/src/scenarios/agent-loop-version5-shape.test.ts +4 -4
  52. package/src/scenarios/agent-loop-workspace-snapshot.test.ts +3 -3
  53. package/src/scenarios/agent-manifest-role-profile.test.ts +9 -9
  54. package/src/scenarios/agent-manifest-runtime.test.ts +13 -12
  55. package/src/scenarios/agent-org-chart-scoping.test.ts +14 -13
  56. package/src/scenarios/agent-org-chart-shape.test.ts +11 -13
  57. package/src/scenarios/agent-platform-aggregate-evidence.test.ts +5 -4
  58. package/src/scenarios/agent-platform-profile.test.ts +18 -19
  59. package/src/scenarios/agent-requires-capabilities-shape.test.ts +4 -4
  60. package/src/scenarios/agent-roster-attribution.test.ts +18 -17
  61. package/src/scenarios/agent-roster-shape.test.ts +17 -19
  62. package/src/scenarios/agent-verifier-shape.test.ts +11 -11
  63. package/src/scenarios/agentConfidenceEscalation.test.ts +2 -1
  64. package/src/scenarios/agentMemoryCrossTenantIsolation.test.ts +6 -5
  65. package/src/scenarios/agentMemoryRedactionContract.test.ts +2 -1
  66. package/src/scenarios/agentMemoryRoundTrip.test.ts +2 -1
  67. package/src/scenarios/agentMemoryTtlExpiry.test.ts +4 -3
  68. package/src/scenarios/agentMessageReducer.test.ts +2 -1
  69. package/src/scenarios/agentMetadata.test.ts +2 -1
  70. package/src/scenarios/agentPackCatalog.test.ts +23 -21
  71. package/src/scenarios/agentPackExport.test.ts +4 -2
  72. package/src/scenarios/agentPackHandoffSchemaValidation.test.ts +5 -4
  73. package/src/scenarios/agentPackInstall.test.ts +4 -2
  74. package/src/scenarios/agentPackProvenance.test.ts +2 -1
  75. package/src/scenarios/agentReasoningEvents.test.ts +3 -2
  76. package/src/scenarios/agentReasoningStreaming.test.ts +11 -10
  77. package/src/scenarios/agents-run-tool-allowlist.test.ts +14 -12
  78. package/src/scenarios/ai-envelope-shape.test.ts +31 -29
  79. package/src/scenarios/aiEnvelope.capBreached.test.ts +26 -24
  80. package/src/scenarios/aiEnvelope.contractRefusal.test.ts +36 -34
  81. package/src/scenarios/aiEnvelope.correlationReplay.test.ts +17 -15
  82. package/src/scenarios/aiEnvelope.redaction.test.ts +22 -20
  83. package/src/scenarios/aiEnvelope.schemaDrift.test.ts +18 -16
  84. package/src/scenarios/aiEnvelope.trustBoundaryPropagation.test.ts +38 -36
  85. package/src/scenarios/aiEnvelope.universalKinds.test.ts +31 -29
  86. package/src/scenarios/aiproviders-input-shape.test.ts +5 -5
  87. package/src/scenarios/aiproviders-realtimevoice-shape.test.ts +14 -16
  88. package/src/scenarios/aiproviders-selfhosted-honesty.test.ts +6 -4
  89. package/src/scenarios/aiproviders-selfhosted-shape.test.ts +15 -17
  90. package/src/scenarios/aiproviders-speechsynth-shape.test.ts +6 -8
  91. package/src/scenarios/aiproviders-subscription-scope.test.ts +7 -8
  92. package/src/scenarios/anonymous-actor-audit-opaque.test.ts +9 -8
  93. package/src/scenarios/anonymous-actor-default-deny.test.ts +10 -9
  94. package/src/scenarios/anonymous-actor-egress-guarded.test.ts +6 -5
  95. package/src/scenarios/anonymous-actor-no-secret-reach.test.ts +7 -6
  96. package/src/scenarios/anonymous-actor-shape.test.ts +17 -19
  97. package/src/scenarios/anonymous-actor-write-gated.test.ts +9 -8
  98. package/src/scenarios/append-ordering.test.ts +7 -5
  99. package/src/scenarios/approval-gate-events.test.ts +4 -3
  100. package/src/scenarios/approval-gate-flow.test.ts +4 -3
  101. package/src/scenarios/approval-payload.test.ts +5 -4
  102. package/src/scenarios/artifact-auth.test.ts +7 -6
  103. package/src/scenarios/artifact-type-pack-install.test.ts +6 -6
  104. package/src/scenarios/artifact-type-pack-manifest-validation.test.ts +9 -8
  105. package/src/scenarios/artifact-type-registration-source.test.ts +20 -20
  106. package/src/scenarios/artifact-type-store-emission.test.ts +15 -18
  107. package/src/scenarios/artifact-type-store-without-render.test.ts +4 -4
  108. package/src/scenarios/audit-log-integrity.test.ts +8 -6
  109. package/src/scenarios/auth-api-key-rotation.test.ts +11 -9
  110. package/src/scenarios/auth-mtls.test.ts +22 -20
  111. package/src/scenarios/auth-oauth2-client-credentials.test.ts +16 -14
  112. package/src/scenarios/auth-oidc-user-bearer.test.ts +26 -24
  113. package/src/scenarios/auth-saml-profile.test.ts +14 -12
  114. package/src/scenarios/auth-scim-profile.test.ts +3 -2
  115. package/src/scenarios/auth-subject-link-key-class.test.ts +8 -7
  116. package/src/scenarios/auth-subject-link.test.ts +11 -10
  117. package/src/scenarios/auth.test.ts +5 -4
  118. package/src/scenarios/authorization-fail-closed.test.ts +3 -2
  119. package/src/scenarios/authorization-roles-shape.test.ts +9 -7
  120. package/src/scenarios/blob-cross-tenant-isolation.test.ts +8 -6
  121. package/src/scenarios/blob-presign-expiry.test.ts +12 -10
  122. package/src/scenarios/blob-roundtrip.test.ts +0 -0
  123. package/src/scenarios/budget-enforcement.test.ts +15 -14
  124. package/src/scenarios/budget-policy-shape.test.ts +18 -19
  125. package/src/scenarios/bulk-cancel.test.ts +9 -8
  126. package/src/scenarios/byok-auth-modes.test.ts +13 -14
  127. package/src/scenarios/byok-roundtrip.test.ts +12 -10
  128. package/src/scenarios/cache-cross-tenant-isolation.test.ts +8 -6
  129. package/src/scenarios/cache-ttl-expiry.test.ts +7 -5
  130. package/src/scenarios/callai-multimodal.test.ts +5 -3
  131. package/src/scenarios/cancellation.test.ts +6 -5
  132. package/src/scenarios/cap-breach.test.ts +15 -14
  133. package/src/scenarios/capability-arm-witness.test.ts +5 -3
  134. package/src/scenarios/certification-bundle-non-vacuous.test.ts +20 -19
  135. package/src/scenarios/certification-bundle-redaction.test.ts +11 -10
  136. package/src/scenarios/certification-bundle-v2.test.ts +11 -10
  137. package/src/scenarios/chain-compensation-expansion.test.ts +14 -13
  138. package/src/scenarios/chain-produced-var-roundtrip.test.ts +13 -14
  139. package/src/scenarios/chain-subchain-cycle-rejected.test.ts +9 -10
  140. package/src/scenarios/chain-subchain-fanout.test.ts +8 -9
  141. package/src/scenarios/chain-subchain-sibling.test.ts +14 -16
  142. package/src/scenarios/channel-presence-behavioral.test.ts +9 -7
  143. package/src/scenarios/channel-presence-shape.test.ts +11 -12
  144. package/src/scenarios/channel-ttl.test.ts +6 -5
  145. package/src/scenarios/chat-card-pack-execution.test.ts +4 -4
  146. package/src/scenarios/chat-card-pack-manifest-validation.test.ts +9 -8
  147. package/src/scenarios/commitment-fired.test.ts +6 -5
  148. package/src/scenarios/compensation-behavior.test.ts +24 -23
  149. package/src/scenarios/compensation-recovery.test.ts +28 -27
  150. package/src/scenarios/configurable-schema.test.ts +8 -6
  151. package/src/scenarios/conformance-advertised-seam-required.test.ts +6 -5
  152. package/src/scenarios/conformance-execution-witness.test.ts +9 -8
  153. package/src/scenarios/connection-pack-apihosts.test.ts +19 -17
  154. package/src/scenarios/connection-pack-manifest-valid.test.ts +12 -11
  155. package/src/scenarios/connection-pack-no-credential-material.test.ts +8 -6
  156. package/src/scenarios/connection-pack-reach-exclusive.test.ts +5 -4
  157. package/src/scenarios/connection-pack-write-reconsent.test.ts +6 -4
  158. package/src/scenarios/connection-provider-resolution.test.ts +18 -16
  159. package/src/scenarios/connector-manifest-validity.test.ts +8 -7
  160. package/src/scenarios/context-budget-transcript-bound.test.ts +17 -15
  161. package/src/scenarios/context-summarization-replay.test.ts +14 -12
  162. package/src/scenarios/contract-provenance.test.ts +18 -18
  163. package/src/scenarios/conversation-turn-model-provenance-shape.test.ts +14 -15
  164. package/src/scenarios/conversationCapabilityNegotiation.test.ts +2 -1
  165. package/src/scenarios/conversationLifecycle.test.ts +2 -1
  166. package/src/scenarios/conversationReplayDeterminism.test.ts +4 -2
  167. package/src/scenarios/conversationVsLegacySuspend.test.ts +2 -1
  168. package/src/scenarios/core-standard-profile.test.ts +8 -9
  169. package/src/scenarios/cost-attribution.test.ts +23 -21
  170. package/src/scenarios/credential-payload-redaction.test.ts +4 -3
  171. package/src/scenarios/credentials-capability-shape.test.ts +5 -4
  172. package/src/scenarios/cross-engine-append-behavior.test.ts +11 -10
  173. package/src/scenarios/cross-engine-append-ordering.test.ts +4 -3
  174. package/src/scenarios/cross-host-ancestry-endpoint.test.ts +11 -10
  175. package/src/scenarios/cross-host-causation-shape.test.ts +7 -6
  176. package/src/scenarios/cross-workspace-isolation.test.ts +4 -3
  177. package/src/scenarios/data-residency-admission.test.ts +15 -14
  178. package/src/scenarios/deadletter-capability-shape.test.ts +6 -4
  179. package/src/scenarios/deadletter-retry-exhaustion.test.ts +4 -3
  180. package/src/scenarios/debug-bundle-truncation.test.ts +9 -7
  181. package/src/scenarios/debugBundle.test.ts +25 -23
  182. package/src/scenarios/discovery-families-view.test.ts +5 -4
  183. package/src/scenarios/discovery.test.ts +30 -29
  184. package/src/scenarios/dispatch-cross-worker-handoff.test.ts +10 -8
  185. package/src/scenarios/dispatch-fanout-parallel.test.ts +24 -22
  186. package/src/scenarios/dispatch-input-mapping.test.ts +12 -10
  187. package/src/scenarios/dispatch-output-mapping.test.ts +9 -7
  188. package/src/scenarios/dispatch-per-item-input.test.ts +22 -20
  189. package/src/scenarios/dispatchLoop.test.ts +3 -2
  190. package/src/scenarios/distillation-index-roundtrip.test.ts +3 -3
  191. package/src/scenarios/distillation-secret-carryforward.test.ts +2 -2
  192. package/src/scenarios/distillation-shape.test.ts +4 -4
  193. package/src/scenarios/distillation-stable-archive.test.ts +6 -6
  194. package/src/scenarios/distillation-token-budget.test.ts +5 -5
  195. package/src/scenarios/durability-poison-exhaustion.test.ts +7 -6
  196. package/src/scenarios/egress-audience-binding.test.ts +11 -10
  197. package/src/scenarios/egress-decision-content-free.test.ts +6 -5
  198. package/src/scenarios/egress-provenance-shape.test.ts +17 -18
  199. package/src/scenarios/envelope-completion-distinguishes-truncation.test.ts +27 -25
  200. package/src/scenarios/envelope-nl-to-format-engaged.test.ts +19 -17
  201. package/src/scenarios/envelope-reasoning-secret-redaction.test.ts +28 -26
  202. package/src/scenarios/envelope-reasoning-shape.test.ts +19 -17
  203. package/src/scenarios/envelope-recovery-applied.test.ts +24 -22
  204. package/src/scenarios/envelope-refusal-shape.test.ts +31 -29
  205. package/src/scenarios/envelope-rendering-hint.test.ts +5 -4
  206. package/src/scenarios/envelope-retry-attempted.test.ts +33 -31
  207. package/src/scenarios/envelope-retry-exhausted.test.ts +22 -20
  208. package/src/scenarios/envelope-tier-one-subset-static.test.ts +9 -7
  209. package/src/scenarios/envelope-truncated.test.ts +19 -17
  210. package/src/scenarios/envelope-truncation-cap-exhaustion.test.ts +19 -17
  211. package/src/scenarios/envelope-variant-discriminator-static.test.ts +4 -3
  212. package/src/scenarios/errors.test.ts +20 -19
  213. package/src/scenarios/eventOrdering.test.ts +16 -14
  214. package/src/scenarios/exec-not-protocol-tier.test.ts +5 -7
  215. package/src/scenarios/experimental-tier-shape.test.ts +17 -15
  216. package/src/scenarios/export-bundle-portability.test.ts +17 -17
  217. package/src/scenarios/failure-path.test.ts +7 -6
  218. package/src/scenarios/feedback-capability-shape.test.ts +4 -3
  219. package/src/scenarios/feedback-correction-redaction.test.ts +2 -1
  220. package/src/scenarios/feedback-cross-tenant-isolation.test.ts +2 -1
  221. package/src/scenarios/feedback-fork-not-copied.test.ts +3 -2
  222. package/src/scenarios/feedback-on-terminal-run.test.ts +3 -2
  223. package/src/scenarios/feedback-record-and-list.test.ts +2 -1
  224. package/src/scenarios/feedback-unsupported-501.test.ts +2 -1
  225. package/src/scenarios/fixtures-gating.test.ts +29 -28
  226. package/src/scenarios/fixtures-valid.test.ts +16 -15
  227. package/src/scenarios/form-content-instantiation.test.ts +15 -14
  228. package/src/scenarios/frontend-plugin-packs.test.ts +29 -27
  229. package/src/scenarios/fs-path-traversal.test.ts +14 -12
  230. package/src/scenarios/goal-standing-continuation.test.ts +21 -21
  231. package/src/scenarios/grpc-transport.test.ts +9 -7
  232. package/src/scenarios/heartbeat-capability-shape.test.ts +3 -3
  233. package/src/scenarios/heartbeat-fires-once-per-tick.test.ts +2 -2
  234. package/src/scenarios/heartbeat-idempotent-no-spam.test.ts +3 -3
  235. package/src/scenarios/heartbeat-runtime-bound.test.ts +2 -2
  236. package/src/scenarios/highConcurrency.test.ts +14 -13
  237. package/src/scenarios/host-callback-declaration.test.ts +8 -7
  238. package/src/scenarios/http-client-ssrf.test.ts +7 -5
  239. package/src/scenarios/i18n-negotiation.test.ts +14 -12
  240. package/src/scenarios/idempotency-concurrent-claim.test.ts +14 -13
  241. package/src/scenarios/idempotency-key-determinism.test.ts +20 -18
  242. package/src/scenarios/idempotency.test.ts +8 -7
  243. package/src/scenarios/idempotencyRetry.test.ts +13 -11
  244. package/src/scenarios/identity-owner-shape.test.ts +6 -5
  245. package/src/scenarios/identity-passthrough.test.ts +4 -3
  246. package/src/scenarios/interrupt-approval.test.ts +6 -5
  247. package/src/scenarios/interrupt-approver-routing.test.ts +15 -15
  248. package/src/scenarios/interrupt-auth-required-resume.test.ts +5 -3
  249. package/src/scenarios/interrupt-clarification.test.ts +4 -3
  250. package/src/scenarios/interrupt-external-event-correlation.test.ts +6 -5
  251. package/src/scenarios/interrupt-parent-child-cascade.test.ts +4 -3
  252. package/src/scenarios/interrupt-quorum-resolution.test.ts +5 -4
  253. package/src/scenarios/interrupt-token-matrix.test.ts +8 -6
  254. package/src/scenarios/interruptRace.test.ts +12 -10
  255. package/src/scenarios/kv-atomic-increment.test.ts +11 -9
  256. package/src/scenarios/kv-cas.test.ts +14 -12
  257. package/src/scenarios/kv-cross-tenant-isolation.test.ts +12 -10
  258. package/src/scenarios/kv-ttl-expiry.test.ts +7 -5
  259. package/src/scenarios/localized-content-delivery.test.ts +24 -26
  260. package/src/scenarios/maliciousManifest.test.ts +10 -8
  261. package/src/scenarios/mcp-2026-07-28-discover.test.ts +15 -14
  262. package/src/scenarios/mcp-cache-tenant-scope.test.ts +6 -5
  263. package/src/scenarios/mcp-current-auth-boundary.test.ts +7 -6
  264. package/src/scenarios/mcp-discoverability.test.ts +9 -7
  265. package/src/scenarios/mcp-extension-opacity.test.ts +7 -6
  266. package/src/scenarios/mcp-mrtr-roundtrip.test.ts +18 -17
  267. package/src/scenarios/mcp-server-elicitation-bridge.test.ts +13 -12
  268. package/src/scenarios/mcp-server-prompt-roundtrip.test.ts +11 -10
  269. package/src/scenarios/mcp-server-resource-roundtrip.test.ts +11 -10
  270. package/src/scenarios/mcp-server-sampling-bridge.test.ts +12 -11
  271. package/src/scenarios/mcp-server-tool-roundtrip.test.ts +14 -13
  272. package/src/scenarios/mcp-server-untrusted-args.test.ts +14 -13
  273. package/src/scenarios/mcp-stateless-request.test.ts +11 -10
  274. package/src/scenarios/mcp-tool-roundtrip.test.ts +4 -3
  275. package/src/scenarios/mcp-toolcall-redaction.test.ts +4 -3
  276. package/src/scenarios/mcp-version-negotiation.test.ts +15 -14
  277. package/src/scenarios/media-url-inline-cap.test.ts +21 -19
  278. package/src/scenarios/memory-attribution-emits-on-write.test.ts +5 -5
  279. package/src/scenarios/memory-attribution-no-content.test.ts +4 -4
  280. package/src/scenarios/memory-attribution-replay-stable.test.ts +4 -3
  281. package/src/scenarios/memory-attribution-shape.test.ts +4 -3
  282. package/src/scenarios/memory-attribution-tenant-scoped.test.ts +3 -3
  283. package/src/scenarios/memory-capability-model-shape.test.ts +14 -15
  284. package/src/scenarios/memory-compaction-event-emitted.test.ts +7 -6
  285. package/src/scenarios/memory-compaction-provenance-tag.test.ts +3 -2
  286. package/src/scenarios/memory-compaction-sr1-carry-forward.test.ts +7 -6
  287. package/src/scenarios/memory-consolidation-idempotent.test.ts +5 -4
  288. package/src/scenarios/memory-consolidation-shape.test.ts +9 -11
  289. package/src/scenarios/memory-degraded-projection.test.ts +10 -9
  290. package/src/scenarios/memory-injection-budget.test.ts +15 -14
  291. package/src/scenarios/metric-emission.test.ts +4 -3
  292. package/src/scenarios/model-capability-insufficient.test.ts +19 -17
  293. package/src/scenarios/model-capability-substituted.test.ts +20 -18
  294. package/src/scenarios/multi-agent-confidence-escalation.test.ts +12 -11
  295. package/src/scenarios/multi-agent-handoff-state-machine.test.ts +10 -9
  296. package/src/scenarios/multi-agent-memory-lifecycle.test.ts +11 -10
  297. package/src/scenarios/multi-node-ordering.test.ts +4 -3
  298. package/src/scenarios/multi-party-conversation-behavioral.test.ts +6 -6
  299. package/src/scenarios/multi-party-conversation-shape.test.ts +21 -23
  300. package/src/scenarios/multi-region-idempotency-behavior.test.ts +16 -15
  301. package/src/scenarios/multi-region-idempotency.test.ts +6 -5
  302. package/src/scenarios/node-failed-payload-shape.test.ts +5 -4
  303. package/src/scenarios/node-module-required-capabilities-shape.test.ts +10 -8
  304. package/src/scenarios/oauth-authorization-code-roundtrip.test.ts +5 -4
  305. package/src/scenarios/oauth-capability-shape.test.ts +5 -4
  306. package/src/scenarios/oauth-connector-redaction.test.ts +4 -3
  307. package/src/scenarios/openapi-resolved-paths.test.ts +6 -5
  308. package/src/scenarios/orchestratorConservativePath.test.ts +2 -1
  309. package/src/scenarios/orchestratorDispatch.test.ts +2 -1
  310. package/src/scenarios/orchestratorTermination.test.ts +2 -1
  311. package/src/scenarios/org-position-no-authority-escalation.test.ts +2 -2
  312. package/src/scenarios/otel-collector-canary-inspection.test.ts +8 -7
  313. package/src/scenarios/otel-emission-grpc.test.ts +3 -2
  314. package/src/scenarios/otel-emission.test.ts +6 -5
  315. package/src/scenarios/otel-trace-propagation-subworkflow.test.ts +7 -6
  316. package/src/scenarios/otel-trace-propagation.test.ts +2 -1
  317. package/src/scenarios/owner-subject-echo.test.ts +13 -12
  318. package/src/scenarios/owner-subject-shape.test.ts +11 -10
  319. package/src/scenarios/pack-manifest-extension-opacity.test.ts +8 -8
  320. package/src/scenarios/pack-registry-isolation.test.ts +4 -3
  321. package/src/scenarios/pack-registry-publish.test.ts +65 -63
  322. package/src/scenarios/pack-registry.test.ts +38 -36
  323. package/src/scenarios/pause-resume.test.ts +18 -16
  324. package/src/scenarios/policies.test.ts +20 -18
  325. package/src/scenarios/production-backpressure.test.ts +18 -16
  326. package/src/scenarios/production-retention-expiry.test.ts +8 -6
  327. package/src/scenarios/profile-discovery-core-alias.test.ts +13 -11
  328. package/src/scenarios/profileDerivation.test.ts +34 -33
  329. package/src/scenarios/prompt-all-four-kinds-events.test.ts +11 -9
  330. package/src/scenarios/prompt-composed-secret-redaction.test.ts +11 -9
  331. package/src/scenarios/prompt-composed-trust-marker.test.ts +10 -8
  332. package/src/scenarios/prompt-end-to-end-events.test.ts +17 -15
  333. package/src/scenarios/prompt-list-and-fetch.test.ts +16 -14
  334. package/src/scenarios/prompt-mutable-lifecycle.test.ts +13 -11
  335. package/src/scenarios/prompt-mutation-workspace-membership-enforced.test.ts +6 -4
  336. package/src/scenarios/prompt-pack-install.test.ts +14 -12
  337. package/src/scenarios/prompt-prefix-cache.test.ts +19 -17
  338. package/src/scenarios/prompt-read-workspace-membership-enforced.test.ts +11 -9
  339. package/src/scenarios/prompt-render-deterministic.test.ts +19 -17
  340. package/src/scenarios/prompt-resolution-chain-agent-intrinsic.test.ts +7 -5
  341. package/src/scenarios/prompt-resolution-chain-event.test.ts +12 -10
  342. package/src/scenarios/prompt-resolution-chain-fallback-cascade.test.ts +10 -8
  343. package/src/scenarios/prompt-resolution-chain-node-wins.test.ts +8 -6
  344. package/src/scenarios/prompt-template-shape.test.ts +30 -28
  345. package/src/scenarios/proposal-reviewable-learning.test.ts +19 -19
  346. package/src/scenarios/protocol-versions-array.test.ts +10 -9
  347. package/src/scenarios/provider-usage.test.ts +17 -15
  348. package/src/scenarios/providerPolicyEnforcement.test.ts +16 -14
  349. package/src/scenarios/purpose-propagation.test.ts +23 -21
  350. package/src/scenarios/queue-ack-nack-dlq.test.ts +12 -10
  351. package/src/scenarios/queue-cross-tenant-isolation.test.ts +8 -6
  352. package/src/scenarios/queue-publish-consume-roundtrip.test.ts +10 -8
  353. package/src/scenarios/rate-limit-envelope.test.ts +6 -5
  354. package/src/scenarios/redaction.test.ts +13 -11
  355. package/src/scenarios/redactionAdversarial.test.ts +11 -9
  356. package/src/scenarios/registry-public.test.ts +12 -11
  357. package/src/scenarios/replay-divergence-at-refusal.test.ts +16 -15
  358. package/src/scenarios/replay-fanout-suppression.test.ts +16 -15
  359. package/src/scenarios/replay-fork-arbitrary.test.ts +24 -23
  360. package/src/scenarios/replay-fork.test.ts +20 -19
  361. package/src/scenarios/replay-llm-cache-key-portable.test.ts +11 -9
  362. package/src/scenarios/replay-llm-cache-key.test.ts +22 -21
  363. package/src/scenarios/replay-observable-sequence-determinism.test.ts +4 -3
  364. package/src/scenarios/replay-retention-expiry.test.ts +11 -9
  365. package/src/scenarios/replay-side-effect-suppression.test.ts +21 -19
  366. package/src/scenarios/replayDeterminism.test.ts +13 -12
  367. package/src/scenarios/requirement-ledger.test.ts +15 -14
  368. package/src/scenarios/restart-during-run.test.ts +3 -2
  369. package/src/scenarios/route-coverage.test.ts +20 -19
  370. package/src/scenarios/run-diff.test.ts +22 -20
  371. package/src/scenarios/run-execution-bounds-shape.test.ts +14 -12
  372. package/src/scenarios/run-transport-economy.test.ts +13 -11
  373. package/src/scenarios/runner-ledger.test.ts +17 -16
  374. package/src/scenarios/runs-lifecycle.test.ts +7 -6
  375. package/src/scenarios/runtime-capabilities.test.ts +12 -10
  376. package/src/scenarios/runtime-requires-install-gate.test.ts +8 -8
  377. package/src/scenarios/runtime-requires-shape.test.ts +7 -6
  378. package/src/scenarios/safefetch-behavior.test.ts +22 -21
  379. package/src/scenarios/safefetch-live-audit.test.ts +14 -13
  380. package/src/scenarios/sandbox-memory-cap.test.ts +2 -1
  381. package/src/scenarios/sandbox-mvp-behavior.test.ts +24 -23
  382. package/src/scenarios/sandbox-no-host-fs-escape.test.ts +3 -2
  383. package/src/scenarios/sandbox-timeout-cap.test.ts +2 -1
  384. package/src/scenarios/sandbox-wasm-isolation.test.ts +14 -15
  385. package/src/scenarios/sandbox-wasm-timeout.test.ts +5 -6
  386. package/src/scenarios/scheduling-capability-shape.test.ts +8 -6
  387. package/src/scenarios/scheduling-cron-fires-once.test.ts +8 -6
  388. package/src/scenarios/search-bm25-roundtrip.test.ts +8 -6
  389. package/src/scenarios/secret-leakage-otel-attribute.test.ts +22 -20
  390. package/src/scenarios/self-hosted-runner.test.ts +26 -24
  391. package/src/scenarios/semantic-digest-vectors.test.ts +8 -7
  392. package/src/scenarios/speech-synthesis-roundtrip.test.ts +9 -7
  393. package/src/scenarios/speech-synthesis-unadvertised.test.ts +5 -3
  394. package/src/scenarios/sql-injection-rejection.test.ts +8 -6
  395. package/src/scenarios/sql-transaction-atomicity.test.ts +10 -8
  396. package/src/scenarios/staleClaim.test.ts +5 -4
  397. package/src/scenarios/stream-modes-buffer.test.ts +8 -7
  398. package/src/scenarios/stream-modes-mixed.test.ts +10 -9
  399. package/src/scenarios/stream-modes.test.ts +12 -11
  400. package/src/scenarios/stream-subscribe-from-beginning.test.ts +11 -9
  401. package/src/scenarios/stream-text-fixture.test.ts +14 -13
  402. package/src/scenarios/streamReconnect.test.ts +13 -11
  403. package/src/scenarios/strict-behavior-gate.test.ts +9 -8
  404. package/src/scenarios/subrun-approval-fail-closed.test.ts +2 -2
  405. package/src/scenarios/subrun-approval-gate.test.ts +3 -3
  406. package/src/scenarios/subrun-attestation-shape.test.ts +2 -2
  407. package/src/scenarios/subrun-checksum-stable.test.ts +4 -4
  408. package/src/scenarios/subworkflow-input-mapping.test.ts +16 -14
  409. package/src/scenarios/subworkflow.test.ts +10 -9
  410. package/src/scenarios/table-cross-tenant-isolation.test.ts +8 -6
  411. package/src/scenarios/table-cursor-pagination.test.ts +8 -6
  412. package/src/scenarios/table-schema-enforcement.test.ts +7 -5
  413. package/src/scenarios/test-seam-unauthenticated.test.ts +2 -2
  414. package/src/scenarios/tool-catalog-compact-projection.test.ts +10 -8
  415. package/src/scenarios/tool-catalog-projection.test.ts +11 -9
  416. package/src/scenarios/tool-descriptor-shape.test.ts +14 -15
  417. package/src/scenarios/tool-hooks-authorization-fail-closed.test.ts +3 -3
  418. package/src/scenarios/tool-hooks-content-free.test.ts +4 -4
  419. package/src/scenarios/tool-hooks-failure-honesty.test.ts +4 -4
  420. package/src/scenarios/tool-hooks-rate-limit.test.ts +2 -2
  421. package/src/scenarios/tool-hooks-secret-redaction.test.ts +2 -2
  422. package/src/scenarios/tool-hooks-shape.test.ts +3 -3
  423. package/src/scenarios/tool-session-lifecycle.test.ts +14 -13
  424. package/src/scenarios/trigger-bridge-delivery.test.ts +14 -14
  425. package/src/scenarios/trigger-bridge-shape.test.ts +19 -20
  426. package/src/scenarios/trigger-ingestion.test.ts +19 -17
  427. package/src/scenarios/trigger-stream-cdc-sources.test.ts +16 -14
  428. package/src/scenarios/v2-approver-enforced.test.ts +97 -0
  429. package/src/scenarios/v2-assurance-downgrade-audited.test.ts +75 -0
  430. package/src/scenarios/v2-bundle-v3-signed.test.ts +96 -0
  431. package/src/scenarios/v2-capabilities-root-closed.test.ts +63 -0
  432. package/src/scenarios/v2-capability-record-shape.test.ts +104 -0
  433. package/src/scenarios/v2-chain-pin-exact.test.ts +118 -0
  434. package/src/scenarios/v2-coherence-not-in-bundle.test.ts +98 -0
  435. package/src/scenarios/v2-compensation-read-projection.test.ts +85 -0
  436. package/src/scenarios/v2-configurable-closed.test.ts +67 -0
  437. package/src/scenarios/v2-dual-stack-negotiation.test.ts +118 -0
  438. package/src/scenarios/v2-effect-identity-business-key.test.ts +132 -0
  439. package/src/scenarios/v2-effect-seam-manifest.test.ts +163 -0
  440. package/src/scenarios/v2-enum-growth-rule.test.ts +69 -0
  441. package/src/scenarios/v2-era-key.test.ts +86 -0
  442. package/src/scenarios/v2-error-registry.test.ts +89 -0
  443. package/src/scenarios/v2-event-type-closed.test.ts +97 -0
  444. package/src/scenarios/v2-fork-a-v1-run.test.ts +109 -0
  445. package/src/scenarios/v2-form-when-reuses-edge-conditions.test.ts +70 -0
  446. package/src/scenarios/v2-header-scheme.test.ts +67 -0
  447. package/src/scenarios/v2-id-grammar.test.ts +77 -0
  448. package/src/scenarios/v2-idempotency-key-grammar.test.ts +65 -0
  449. package/src/scenarios/v2-interrupt-token-scheme.test.ts +55 -0
  450. package/src/scenarios/v2-lane-issuer-advertised.test.ts +62 -0
  451. package/src/scenarios/v2-legacy-profiles-absent.test.ts +78 -0
  452. package/src/scenarios/v2-manifest-ceiling-refused.test.ts +130 -0
  453. package/src/scenarios/v2-manifest-hatch-carried.test.ts +120 -0
  454. package/src/scenarios/v2-min-client-version.test.ts +48 -0
  455. package/src/scenarios/v2-minimum-version-refused.test.ts +141 -0
  456. package/src/scenarios/v2-mrtr-rounds-ceiling.test.ts +95 -0
  457. package/src/scenarios/v2-negotiation-authenticated.test.ts +125 -0
  458. package/src/scenarios/v2-negotiation-decided-emitted.test.ts +149 -0
  459. package/src/scenarios/v2-no-transport-advertisement.test.ts +57 -0
  460. package/src/scenarios/v2-owner-subject-required.test.ts +72 -0
  461. package/src/scenarios/v2-pack-isolation.test.ts +147 -0
  462. package/src/scenarios/v2-payload-registry-closed.test.ts +110 -0
  463. package/src/scenarios/v2-peer-dependency-declared.test.ts +132 -0
  464. package/src/scenarios/v2-pinned-run-disposition.test.ts +124 -0
  465. package/src/scenarios/v2-poll-cursor-v2.test.ts +114 -0
  466. package/src/scenarios/v2-preferred-version-default.test.ts +83 -0
  467. package/src/scenarios/v2-profiles-derived-only.test.ts +70 -0
  468. package/src/scenarios/v2-provider-conflict.test.ts +115 -0
  469. package/src/scenarios/v2-refresh-sla.test.ts +78 -0
  470. package/src/scenarios/v2-relaxation-recorded.test.ts +134 -0
  471. package/src/scenarios/v2-revocation-honored.test.ts +66 -0
  472. package/src/scenarios/v2-subject-link-record.test.ts +102 -0
  473. package/src/scenarios/v2-unmapped-type-refused.test.ts +95 -0
  474. package/src/scenarios/v2-v1-events-translated.test.ts +105 -0
  475. package/src/scenarios/v2-v1-signed-webhook-accepted.test.ts +109 -0
  476. package/src/scenarios/v2-webhook-durable-delivery.test.ts +238 -0
  477. package/src/scenarios/v2-well-known-one-resource.test.ts +129 -0
  478. package/src/scenarios/vector-knn-roundtrip.test.ts +9 -7
  479. package/src/scenarios/verifier-gating.test.ts +8 -6
  480. package/src/scenarios/version-fold.test.ts +9 -8
  481. package/src/scenarios/version-negotiation.test.ts +12 -11
  482. package/src/scenarios/voice-bargein-no-partial-leak.test.ts +8 -6
  483. package/src/scenarios/voice-event-payloads-shape.test.ts +10 -11
  484. package/src/scenarios/voice-interim-not-durable.test.ts +7 -5
  485. package/src/scenarios/voice-streamref-tenant-bound.test.ts +5 -3
  486. package/src/scenarios/voice-synthesis-streaming.test.ts +11 -9
  487. package/src/scenarios/voice-transcription-streaming.test.ts +8 -6
  488. package/src/scenarios/voice-transcription-unadvertised.test.ts +5 -3
  489. package/src/scenarios/wasm-pack-abi-version-rejection.test.ts +3 -2
  490. package/src/scenarios/wasm-pack-invoke-completed.test.ts +3 -2
  491. package/src/scenarios/wasm-pack-invoke-suspended.test.ts +3 -2
  492. package/src/scenarios/wasm-pack-load.test.ts +4 -3
  493. package/src/scenarios/wasm-pack-memory-cap.test.ts +5 -4
  494. package/src/scenarios/wasm-pack-replay-determinism.test.ts +2 -1
  495. package/src/scenarios/webhook-negative.test.ts +11 -9
  496. package/src/scenarios/webhook-receiver-adversarial.test.ts +8 -7
  497. package/src/scenarios/webhook-sig-algorithm.test.ts +5 -3
  498. package/src/scenarios/webhook-signed-delivery.test.ts +16 -15
  499. package/src/scenarios/webhook-tenant-isolation.test.ts +16 -14
  500. package/src/scenarios/workflow-chain-deferred-parameters.test.ts +28 -30
  501. package/src/scenarios/workflow-chain-expansion.test.ts +21 -20
  502. package/src/scenarios/workflow-chain-host-expansion.test.ts +21 -20
  503. package/src/scenarios/workflow-chain-pack-manifest-validation.test.ts +14 -12
  504. package/src/scenarios/workflow-chain-pack-signature-verification.test.ts +5 -4
  505. package/src/scenarios/workflow-chain-unresolvable-typeid.test.ts +9 -8
  506. package/src/scenarios/workflow-primary-output-annotation.test.ts +7 -6
  507. package/src/scenarios/workflow-variable-format.test.ts +24 -24
  508. package/src/scenarios/workload-identity-behavior.test.ts +15 -13
  509. package/src/scenarios/workload-identity-chain-bounds.test.ts +18 -17
  510. package/src/scenarios/workspace-behavior.test.ts +19 -18
  511. package/src/scenarios/workspace-capability-shape.test.ts +3 -2
  512. package/src/scenarios/workspace-cross-tenant-isolation-blackbox.test.ts +6 -5
  513. package/src/scenarios/workspace-cross-tenant-isolation.test.ts +7 -6
  514. package/src/scenarios/x-openwop-form-pack-manifest.test.ts +8 -7
  515. package/src/setup.ts +44 -6
  516. package/vitest.config.ts +5 -1
  517. package/api/.redocly.lint-ignore.yaml +0 -22
  518. package/api/asyncapi.yaml +0 -834
  519. package/api/grpc/openwop.proto +0 -251
  520. package/api/openapi.yaml +0 -2693
  521. package/api/redocly.yaml +0 -23
  522. package/schemas/README.md +0 -144
  523. package/schemas/a2a-task-state.schema.json +0 -78
  524. package/schemas/a2ui-surface-delta-frame.schema.json +0 -48
  525. package/schemas/agent-deployment-transition.schema.json +0 -49
  526. package/schemas/agent-deployment.schema.json +0 -54
  527. package/schemas/agent-eval-suite.schema.json +0 -140
  528. package/schemas/agent-inventory-response.schema.json +0 -115
  529. package/schemas/agent-manifest.schema.json +0 -156
  530. package/schemas/agent-org-chart.schema.json +0 -82
  531. package/schemas/agent-ref.schema.json +0 -63
  532. package/schemas/agent-roster-entry.schema.json +0 -81
  533. package/schemas/agent-roster-response.schema.json +0 -21
  534. package/schemas/ai-envelope.schema.json +0 -134
  535. package/schemas/annotation-create.schema.json +0 -37
  536. package/schemas/annotation.schema.json +0 -56
  537. package/schemas/artifact-type-pack-manifest.schema.json +0 -244
  538. package/schemas/audit-verify-result.schema.json +0 -90
  539. package/schemas/budget-policy.schema.json +0 -18
  540. package/schemas/capabilities.schema.json +0 -4160
  541. package/schemas/certification-bundle-v2.schema.json +0 -118
  542. package/schemas/channel-presence-payload.schema.json +0 -41
  543. package/schemas/channel-written-payload.schema.json +0 -55
  544. package/schemas/chat-card-pack-manifest.schema.json +0 -253
  545. package/schemas/compact-tool-descriptor.schema.json +0 -51
  546. package/schemas/compensation-policy.schema.json +0 -64
  547. package/schemas/conformance-certification-bundle.schema.json +0 -86
  548. package/schemas/connection-pack-manifest.schema.json +0 -353
  549. package/schemas/conversation-event.schema.json +0 -168
  550. package/schemas/conversation-turn.schema.json +0 -117
  551. package/schemas/core-conformance-mock-agent-config.schema.json +0 -152
  552. package/schemas/credential-provenance.schema.json +0 -18
  553. package/schemas/credential-reference.schema.json +0 -21
  554. package/schemas/debug-bundle.schema.json +0 -196
  555. package/schemas/dispatch-config.schema.json +0 -112
  556. package/schemas/envelopes/clarification.request.schema.json +0 -52
  557. package/schemas/envelopes/error.schema.json +0 -30
  558. package/schemas/envelopes/media.audio.schema.json +0 -38
  559. package/schemas/envelopes/media.file.schema.json +0 -37
  560. package/schemas/envelopes/media.image.schema.json +0 -33
  561. package/schemas/envelopes/schema.request.schema.json +0 -26
  562. package/schemas/envelopes/schema.response.schema.json +0 -22
  563. package/schemas/envelopes/ui.a2ui-surface.schema.json +0 -154
  564. package/schemas/error-envelope.schema.json +0 -25
  565. package/schemas/eval-summary.schema.json +0 -92
  566. package/schemas/export-bundle.schema.json +0 -66
  567. package/schemas/form-content-pack-manifest.schema.json +0 -243
  568. package/schemas/frontend-plugin-manifest.schema.json +0 -109
  569. package/schemas/goal.schema.json +0 -104
  570. package/schemas/heartbeat-evaluated.schema.json +0 -14
  571. package/schemas/heartbeat-state-changed.schema.json +0 -14
  572. package/schemas/localized-content-language-settings.schema.json +0 -26
  573. package/schemas/localized-content-page-response.schema.json +0 -60
  574. package/schemas/localized-content-page.schema.json +0 -62
  575. package/schemas/localized-content-section.schema.json +0 -51
  576. package/schemas/memory-entry.schema.json +0 -36
  577. package/schemas/memory-list-options.schema.json +0 -37
  578. package/schemas/node-pack-manifest.schema.json +0 -654
  579. package/schemas/orchestrator-decision.schema.json +0 -90
  580. package/schemas/org-chart-responsibility-view.schema.json +0 -26
  581. package/schemas/pack-lockfile.schema.json +0 -108
  582. package/schemas/prompt-kind.schema.json +0 -8
  583. package/schemas/prompt-pack-manifest.schema.json +0 -117
  584. package/schemas/prompt-ref.schema.json +0 -40
  585. package/schemas/prompt-template.schema.json +0 -149
  586. package/schemas/proposal.schema.json +0 -84
  587. package/schemas/registry-version-manifest.schema.json +0 -208
  588. package/schemas/residency.schema.json +0 -16
  589. package/schemas/run-ancestry-response.schema.json +0 -54
  590. package/schemas/run-diff-response.schema.json +0 -64
  591. package/schemas/run-event-payloads.schema.json +0 -4452
  592. package/schemas/run-event.schema.json +0 -211
  593. package/schemas/run-options.schema.json +0 -80
  594. package/schemas/run-orchestrator-decided-event.schema.json +0 -20
  595. package/schemas/run-snapshot.schema.json +0 -148
  596. package/schemas/security-advisory.schema.json +0 -109
  597. package/schemas/self-hosted-runner-dispatch-frame.schema.json +0 -79
  598. package/schemas/self-hosted-runner-registration.schema.json +0 -53
  599. package/schemas/self-hosted-runner-result-frame.schema.json +0 -38
  600. package/schemas/subject.schema.json +0 -56
  601. package/schemas/suspend-request.schema.json +0 -207
  602. package/schemas/tool-descriptor.schema.json +0 -63
  603. package/schemas/trigger-event.schema.json +0 -222
  604. package/schemas/trigger-subscription-registration.schema.json +0 -67
  605. package/schemas/trigger-subscription.schema.json +0 -26
  606. package/schemas/ui-plugin-message.schema.json +0 -90
  607. package/schemas/workflow-chain-pack-manifest.schema.json +0 -615
  608. package/schemas/workflow-definition.schema.json +0 -850
  609. package/schemas/workload-identity.schema.json +0 -79
  610. package/schemas/workspace-file-create.schema.json +0 -20
  611. package/schemas/workspace-file.schema.json +0 -39
@@ -47,7 +47,8 @@ import { pollUntilTerminal, pollUntilStatus } from '../lib/polling.js';
47
47
  import { SCHEMAS_DIR } from '../lib/paths.js';
48
48
  import { behaviorGate } from '../lib/behavior-gate.js';
49
49
  import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
50
- import { seamAbsent } from '../lib/soft-skip.js';
50
+ import { seamAbsent, softSkip } from '../lib/soft-skip.js';
51
+ import { req } from '../lib/requirement-ids.js';
51
52
 
52
53
  /**
53
54
  * Callback-shaped: the host issues A2A JSON-RPC calls to the suite's fake peer.
@@ -113,7 +114,7 @@ describe('a2a-task-roundtrip: AgentCard + task lifecycle', () => {
113
114
  '[a2a-task-roundtrip] no A2A endpoint configured; set OPENWOP_A2A_FAKE_PEER=true ' +
114
115
  'or OPENWOP_A2A_REAL_PEER_URL=<base-url>',
115
116
  );
116
- return;
117
+ return softSkip('blocked', 'precondition not met — `!probe` returned early (seam, prior step, or fixture unavailable)');
117
118
  }
118
119
  if (!probe.isReal) getA2AFakePeer()!.reset();
119
120
 
@@ -137,7 +138,7 @@ describe('a2a-task-roundtrip: AgentCard + task lifecycle', () => {
137
138
  cardJson.supportedInterfaces?.find((i) => typeof i.protocolVersion === 'string')?.protocolVersion;
138
139
  expect(
139
140
  typeof advertisedVersion,
140
- 'the card MUST advertise a protocol version — top-level (0.3) or per interface (1.0)',
141
+ req('openwop.it.a2a-task-roundtrip.agentcard-exposes-a-protocol-version-0-3-top-level-or-1-0-per-interface-skills-m', 'spec/v1/a2a-integration.md', 'the card MUST advertise a protocol version — top-level (0.3) or per interface (1.0)'),
141
142
  ).toBe('string');
142
143
  expect(Array.isArray(cardJson.skills)).toBe(true);
143
144
  expect((cardJson.skills ?? []).length).toBeGreaterThan(0);
@@ -181,7 +182,7 @@ describe('a2a-task-roundtrip: AgentCard + task lifecycle', () => {
181
182
  `[a2a-task-roundtrip] real-peer interop OK against ${probe.url} ` +
182
183
  `(skill=${firstSkill?.id ?? firstSkill?.name}, kind=${kind})`,
183
184
  );
184
- return;
185
+ return softSkip('blocked', 'precondition not met — `probe.isReal` returned early (seam, prior step, or fixture unavailable)');
185
186
  }
186
187
 
187
188
  // Fake-peer path: deterministic state forcing, assert verbatim.
@@ -224,14 +225,14 @@ describe('a2a-task-roundtrip: drift point #3 — AUTH_REQUIRED projects to waiti
224
225
  if (!peer) {
225
226
  // eslint-disable-next-line no-console
226
227
  console.warn('[a2a-task-roundtrip] peer not started; skipping drift-point #3 subtest');
227
- return;
228
+ return softSkip('blocked', 'precondition not met — `!peer` returned early ([a2a-task-roundtrip] peer not started; skipping drift-point #3 subtest) (seam, prior step, or fixture unavailable)');
228
229
  }
229
230
  if (!isFixtureAdvertised(ROUNDTRIP_FIXTURE)) {
230
231
  // eslint-disable-next-line no-console
231
232
  console.warn(
232
233
  `[a2a-task-roundtrip] fixture ${ROUNDTRIP_FIXTURE} not advertised; skipping drift-point #3 subtest`,
233
234
  );
234
- return;
235
+ return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!isFixtureAdvertised(ROUNDTRIP_FIXTURE)` returned early ([a2a-task-roundtrip] fixture … not advertised; skipping drift-point #3 subtest)');
235
236
  }
236
237
  peer.reset();
237
238
  peer.setNextState('AUTH_REQUIRED');
@@ -246,7 +247,7 @@ describe('a2a-task-roundtrip: drift point #3 — AUTH_REQUIRED projects to waiti
246
247
  // Host should project AUTH_REQUIRED into `waiting-input` per
247
248
  // a2a-integration.md §"State projection (reverse)".
248
249
  const snapshot = await pollUntilStatus(runId, 'waiting-input', { timeoutMs: 15_000 });
249
- expect(snapshot.status, driver.describe(
250
+ expect(snapshot.status, req('openwop.it.a2a-task-roundtrip.host-consuming-an-a2a-peer-that-returns-auth-required-projects-to-waiting-input',
250
251
  'a2a-integration.md §"State projection" drift point #3',
251
252
  "A2A AUTH_REQUIRED MUST project to openwop 'waiting-input' (no native auth-required kind in v1)",
252
253
  )).toBe('waiting-input');
@@ -264,10 +265,10 @@ describe('a2a-task-roundtrip: drift point #4 — REJECTED projects to failed', (
264
265
  if (!peer) {
265
266
  // eslint-disable-next-line no-console
266
267
  console.warn('[a2a-task-roundtrip] peer not started; skipping drift-point #4 subtest');
267
- return;
268
+ return softSkip('blocked', 'precondition not met — `!peer` returned early ([a2a-task-roundtrip] peer not started; skipping drift-point #4 subtest) (seam, prior step, or fixture unavailable)');
268
269
  }
269
270
  if (!isFixtureAdvertised(ROUNDTRIP_FIXTURE)) {
270
- return;
271
+ return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!isFixtureAdvertised(ROUNDTRIP_FIXTURE)` returned early');
271
272
  }
272
273
  peer.reset();
273
274
  peer.setNextState('REJECTED');
@@ -280,7 +281,7 @@ describe('a2a-task-roundtrip: drift point #4 — REJECTED projects to failed', (
280
281
  const runId = (create.json as { runId: string }).runId;
281
282
 
282
283
  const terminal = await pollUntilTerminal(runId, { timeoutMs: 15_000 });
283
- expect(terminal.status, driver.describe(
284
+ expect(terminal.status, req('openwop.it.a2a-task-roundtrip.host-consuming-an-a2a-peer-that-returns-rejected-projects-to-failed-with-rejecte',
284
285
  'a2a-integration.md §"State projection" drift point #4',
285
286
  'A2A REJECTED MUST project to openwop terminal status `failed`',
286
287
  )).toBe('failed');
@@ -289,7 +290,7 @@ describe('a2a-task-roundtrip: drift point #4 — REJECTED projects to failed', (
289
290
  // snapshot, the final node payload, or the run-level error envelope.
290
291
  // We accept any of those: stringify the snapshot and search.
291
292
  const haystack = JSON.stringify(terminal).toLowerCase();
292
- expect(haystack.includes('rejected'), driver.describe(
293
+ expect(haystack.includes('rejected'), req('openwop.it.a2a-task-roundtrip.host-consuming-an-a2a-peer-that-returns-rejected-projects-to-failed-with-rejecte',
293
294
  'a2a-integration.md §"State projection" drift point #4',
294
295
  "host SHOULD surface 'rejected_by_remote' (or equivalent) so observers can attribute the failure to the remote A2A peer",
295
296
  )).toBe(true);
@@ -322,14 +323,14 @@ describe('a2a-task-roundtrip: A2ATaskState + a2a capability shape (always-on, se
322
323
  };
323
324
  expect(
324
325
  validateTaskState(ok),
325
- `a2a-task-state.schema.json MUST accept a conforming record. Errors: ${JSON.stringify(validateTaskState.errors)}`,
326
+ req('openwop.it.a2a-task-roundtrip.a-conforming-a2ataskstate-validates-with-the-lowercase-hyphen-state-enum-and-tas', 'spec/v1/a2a-integration.md', `a2a-task-state.schema.json MUST accept a conforming record. Errors: ${JSON.stringify(validateTaskState.errors)}`),
326
327
  ).toBe(true);
327
328
  });
328
329
 
329
330
  it('an UPPERCASE state fails (the persisted/wire form is the A2A v0.3 lowercase-hyphen variant)', () => {
330
331
  expect(
331
332
  validateTaskState({ taskId: 'r', runId: 'r', state: 'WORKING', updatedAt: '2026-06-13T19:00:00Z' }),
332
- 'a2a-integration.md spelling-drift note — the persisted A2ATaskState.state MUST be the lowercase-hyphen form',
333
+ req('openwop.it.a2a-task-roundtrip.an-uppercase-state-fails-the-persisted-wire-form-is-the-a2a-v0-3-lowercase-hyphe', 'spec/v1/a2a-integration.md', 'a2a-integration.md spelling-drift note — the persisted A2ATaskState.state MUST be the lowercase-hyphen form'),
333
334
  ).toBe(false);
334
335
  });
335
336
 
@@ -342,7 +343,7 @@ describe('a2a-task-roundtrip: A2ATaskState + a2a capability shape (always-on, se
342
343
  updatedAt: '2026-06-13T19:00:00Z',
343
344
  inputs: { secret: 'x' },
344
345
  }),
345
- 'SECURITY a2a-push-egress-ssrf / SR-1 — the persisted record MUST NOT carry run inputs/outputs/artifacts inline',
346
+ req('openwop.it.a2a-task-roundtrip.an-a2ataskstate-carrying-run-inputs-artifacts-inline-fails-additionalproperties', 'spec/v1/a2a-integration.md', 'SECURITY a2a-push-egress-ssrf / SR-1 — the persisted record MUST NOT carry run inputs/outputs/artifacts inline'),
346
347
  ).toBe(false);
347
348
  });
348
349
 
@@ -351,27 +352,27 @@ describe('a2a-task-roundtrip: A2ATaskState + a2a capability shape (always-on, se
351
352
  $ref: 'https://openwop.dev/spec/v1/a2a-task-state.schema.json#/$defs/PushConfig',
352
353
  $defs: taskStateSchema.$defs,
353
354
  });
354
- expect(validatePush({ tokenFingerprint: 'a1b2' }), 'PushConfig MUST require `url`').toBe(false);
355
+ expect(validatePush({ tokenFingerprint: 'a1b2' }), req('openwop.it.a2a-task-roundtrip.a-pushconfig-requires-url-and-structurally-rejects-a-raw-non-truncated-push-toke', 'spec/v1/a2a-integration.md', 'PushConfig MUST require `url`')).toBe(false);
355
356
  expect(
356
357
  validatePush({ url: 'https://caller.example.com/push', tokenFingerprint: 'a'.repeat(33) }),
357
- 'SECURITY a2a-push-egress-ssrf — tokenFingerprint maxLength:32 structurally rejects a full-length raw token (SR-1)',
358
+ req('openwop.it.a2a-task-roundtrip.a-pushconfig-requires-url-and-structurally-rejects-a-raw-non-truncated-push-toke', 'spec/v1/a2a-integration.md', 'SECURITY a2a-push-egress-ssrf — tokenFingerprint maxLength:32 structurally rejects a full-length raw token (SR-1)'),
358
359
  ).toBe(false);
359
360
  expect(
360
361
  validatePush({ url: 'https://caller.example.com/push', tokenFingerprint: 'a1b2c3d4' }),
361
- 'a truncated fingerprint + uri url MUST validate',
362
+ req('openwop.it.a2a-task-roundtrip.a-pushconfig-requires-url-and-structurally-rejects-a-raw-non-truncated-push-toke', 'spec/v1/a2a-integration.md', 'a truncated fingerprint + uri url MUST validate'),
362
363
  ).toBe(true);
363
364
  });
364
365
 
365
366
  it('the capabilities.a2a block shape is declared (supported + agentCardUrl required; three optional booleans)', () => {
366
- expect(a2aBlockSchema, 'capabilities.schema.json MUST declare the a2a block').toBeDefined();
367
+ expect(a2aBlockSchema, req('openwop.it.a2a-task-roundtrip.the-capabilities-a2a-block-shape-is-declared-supported-agentcardurl-required-thr', 'spec/v1/a2a-integration.md', 'capabilities.schema.json MUST declare the a2a block')).toBeDefined();
367
368
  expect(a2aBlockSchema.required).toEqual(expect.arrayContaining(['supported', 'agentCardUrl']));
368
369
  expect(a2aBlockSchema.additionalProperties).toBe(false);
369
370
  const validateA2A = ajv.compile({ ...a2aBlockSchema, $id: 'urn:test:a2a-block' });
370
371
  expect(
371
372
  validateA2A({ supported: true, agentCardUrl: 'https://example.com/.well-known/agent-card.json', durableTasks: true }),
372
- `a conforming a2a block MUST validate. Errors: ${JSON.stringify(validateA2A.errors)}`,
373
+ req('openwop.it.a2a-task-roundtrip.the-capabilities-a2a-block-shape-is-declared-supported-agentcardurl-required-thr', 'spec/v1/a2a-integration.md', `a conforming a2a block MUST validate. Errors: ${JSON.stringify(validateA2A.errors)}`),
373
374
  ).toBe(true);
374
- expect(validateA2A({ supported: true }), 'agentCardUrl is required').toBe(false);
375
+ expect(validateA2A({ supported: true }), req('openwop.it.a2a-task-roundtrip.the-capabilities-a2a-block-shape-is-declared-supported-agentcardurl-required-thr', 'spec/v1/a2a-integration.md', 'agentCardUrl is required')).toBe(false);
375
376
  });
376
377
  });
377
378
 
@@ -386,20 +387,20 @@ describe.skipIf(HTTP_SKIP)('a2a-task-roundtrip: durable tasks/get after disconne
386
387
  const start = await driver.post('/v1/host/sample/a2a/tasks/start', {
387
388
  scenario: 'paused-at-approval',
388
389
  });
389
- if (start.status === 404 || start.status === 403) return; // seam unwired — soft-skip
390
+ if (start.status === 404 || start.status === 403) return softSkip('blocked', 'precondition not met — `start.status === 404 || start.status === 403` returned early (seam unwired — soft-skip) (seam, prior step, or fixture unavailable)'); // seam unwired — soft-skip
390
391
  const taskId = (start.json as { taskId?: string })?.taskId;
391
- if (!taskId) return;
392
+ if (!taskId) return softSkip('blocked', 'precondition not met — `!taskId` returned early (seam, prior step, or fixture unavailable)');
392
393
 
393
394
  const read = await driver.get(`/v1/host/sample/a2a/tasks/${encodeURIComponent(taskId)}`);
394
- if (read.status === 404 || read.status === 403) return;
395
+ if (read.status === 404 || read.status === 403) return softSkip('blocked', 'precondition not met — `read.status === 404 || read.status === 403` returned early (seam, prior step, or fixture unavailable)');
395
396
  const state = read.json as { state?: string; runId?: string; metadata?: { openwop?: { interrupt?: { kind?: string } } } };
396
397
  expect(
397
398
  state.state,
398
- driver.describe('a2a-integration.md §"Async / durable Tasks"', 'tasks/get after disconnect MUST return the live input-required projection (not a stale working)'),
399
+ req('openwop.it.a2a-task-roundtrip.a-paused-at-hitl-run-projects-a-live-input-required-task-on-a-later-tasks-get-re', 'a2a-integration.md §"Async / durable Tasks"', 'tasks/get after disconnect MUST return the live input-required projection (not a stale working)'),
399
400
  ).toBe('input-required');
400
401
  expect(
401
402
  state.runId,
402
- driver.describe('a2a-task-state.schema.json', 'taskId MUST equal the backing runId'),
403
+ req('openwop.it.a2a-task-roundtrip.a-paused-at-hitl-run-projects-a-live-input-required-task-on-a-later-tasks-get-re', 'a2a-task-state.schema.json', 'taskId MUST equal the backing runId'),
403
404
  ).toBe(taskId);
404
405
  });
405
406
  });
@@ -451,10 +452,10 @@ describe.skipIf(HTTP_SKIP)('a2a-task-roundtrip: push-config SSRF, two-sided (gat
451
452
  // `https` on purpose: this probe is refusable ONLY by the address arm, so a
452
453
  // host that implements the scheme arm alone cannot pass it by accident.
453
454
  const res = await registerPush('https://10.0.0.5/push');
454
- if (res === null) return;
455
+ if (res === null) return softSkip('blocked', 'precondition not met — `res === null` returned early (seam, prior step, or fixture unavailable)');
455
456
  expect(
456
457
  res.status >= 400,
457
- driver.describe(
458
+ req('openwop.it.a2a-task-roundtrip.address-arm-an-https-pushconfig-url-at-a-private-address-is-refused',
458
459
  'a2a-integration.md §D.6 (address arm)',
459
460
  'a2a-push-egress-ssrf — a pushConfig.url at a private/loopback address MUST be refused before any push, even over https',
460
461
  ),
@@ -464,10 +465,10 @@ describe.skipIf(HTTP_SKIP)('a2a-task-roundtrip: push-config SSRF, two-sided (gat
464
465
  it('SCHEME arm: an http pushConfig.url at a public host is refused', async () => {
465
466
  // Public hostname on purpose: refusable ONLY by the scheme arm.
466
467
  const res = await registerPush('http://push.example.com/push');
467
- if (res === null) return;
468
+ if (res === null) return softSkip('blocked', 'precondition not met — `res === null` returned early (seam, prior step, or fixture unavailable)');
468
469
  expect(
469
470
  res.status >= 400,
470
- driver.describe(
471
+ req('openwop.it.a2a-task-roundtrip.scheme-arm-an-http-pushconfig-url-at-a-public-host-is-refused',
471
472
  'a2a-integration.md §D.6 (scheme arm)',
472
473
  'a2a-push-egress-ssrf — a plaintext `http://` pushConfig.url MUST be refused before any push. '
473
474
  + 'The RFC 0093 webhook-egress guard is the `webhooks.md` §"SSRF protection" list IN FULL, whose first entry is '
@@ -29,6 +29,8 @@ import { behaviorGate } from '../lib/behavior-gate.js';
29
29
  import { capabilityFamily } from '../lib/discovery-capabilities.js';
30
30
  import { getA2AFakePeer } from '../lib/a2a-fake-peer.js';
31
31
  import { readErrorCode } from '../lib/error-envelope.js';
32
+ import { req } from '../lib/requirement-ids.js';
33
+ import { softSkip } from '../lib/soft-skip.js';
32
34
 
33
35
  /**
34
36
  * Callback-shaped: the host issues A2A calls to the suite's fake peer, which records the negotiated version header.
@@ -63,7 +65,7 @@ describe('RFC 0152 §B — A2A version negotiation', () => {
63
65
  const caps = await a2a();
64
66
  expect(
65
67
  caps?.protocolVersions ?? [],
66
- driver.describe(
68
+ req('openwop.it.a2a-version-negotiation.the-advertised-preferred-version-is-one-the-host-actually-claims',
67
69
  'RFCS/0152-a2a-1-0-versioned-composition.md §A',
68
70
  '`preferredVersion` MUST be present in `protocolVersions`. Preferring a version you do ' +
69
71
  'not list is a claim no peer can act on.',
@@ -74,28 +76,28 @@ describe('RFC 0152 §B — A2A version negotiation', () => {
74
76
  it('outbound calls carry an explicit A2A-Version header', async () => {
75
77
  if (!behaviorGate(PROFILE, await negotiationAdvertised())) return;
76
78
  const peer = getA2AFakePeer();
77
- if (peer === null) return; // no fake peer wired in this run
79
+ if (peer === null) return softSkip('blocked', 'precondition not met — `peer === null` returned early (no fake peer wired in this run) (seam, prior step, or fixture unavailable)'); // no fake peer wired in this run
78
80
  peer.reset();
79
81
  const drive = await driver.post('/v1/host/sample/a2a/invoke', { peerUrl: peer.endpoint() });
80
82
  if (drive.status === 404 || drive.status === 403) {
81
83
  // Seam absent. RFC 0148 §A: unobservable resolves to `blocked`, not a pass.
82
84
  expect(
83
85
  drive.status,
84
- driver.describe(
86
+ req('openwop.it.a2a-version-negotiation.outbound-calls-carry-an-explicit-a2a-version-header',
85
87
  'RFCS/0152 §B',
86
88
  'a host advertising A2A version negotiation MUST expose an invoke seam so the negotiated ' +
87
89
  'version is observable. Without it the requirement cannot be witnessed and resolves to ' +
88
90
  '`blocked` per RFC 0148 §A.',
89
91
  ),
90
92
  ).not.toBe(404);
91
- return;
93
+ return softSkip('blocked', 'precondition not met — `drive.status === 404 || drive.status === 403` returned early (seam, prior step, or fixture unavailable)');
92
94
  }
93
95
  const calls = peer.invocations().filter((i: { method: string }) => i.method !== 'GET');
94
- expect(calls.length, 'the host MUST have called the peer').toBeGreaterThan(0);
96
+ expect(calls.length, req('openwop.it.a2a-version-negotiation.outbound-calls-carry-an-explicit-a2a-version-header', 'RFCS/0152 §B', 'the host MUST have called the peer')).toBeGreaterThan(0);
95
97
  for (const c of calls) {
96
98
  expect(
97
99
  c.headers['a2a-version'],
98
- driver.describe(
100
+ req('openwop.it.a2a-version-negotiation.outbound-calls-carry-an-explicit-a2a-version-header',
99
101
  'RFCS/0152 §B',
100
102
  'the sender MUST send `A2A-Version`. An absent header leaves the receiver guessing, and ' +
101
103
  'a guess that happens to be right is not a negotiation.',
@@ -107,7 +109,7 @@ describe('RFC 0152 §B — A2A version negotiation', () => {
107
109
  it('an authenticated request is not silently downgraded', async () => {
108
110
  if (!behaviorGate(PROFILE, await negotiationAdvertised())) return;
109
111
  const peer = getA2AFakePeer();
110
- if (peer === null) return;
112
+ if (peer === null) return softSkip('blocked', 'precondition not met — `peer === null` returned early (seam, prior step, or fixture unavailable)');
111
113
  const caps = await a2a();
112
114
  const preferred = caps?.preferredVersion;
113
115
  peer.reset();
@@ -116,7 +118,7 @@ describe('RFC 0152 §B — A2A version negotiation', () => {
116
118
  authenticated: true,
117
119
  peerOffersOnly: '0.3',
118
120
  });
119
- if (drive.status === 404 || drive.status === 403) return; // covered above
121
+ if (drive.status === 404 || drive.status === 403) return softSkip('blocked', 'precondition not met — `drive.status === 404 || drive.status === 403` returned early (covered above) (seam, prior step, or fixture unavailable)'); // covered above
120
122
 
121
123
  // THE load-bearing leg. A silent downgrade is dangerous precisely because it
122
124
  // SUCCEEDS: the caller believes it negotiated `preferred`, the peer answered
@@ -127,7 +129,7 @@ describe('RFC 0152 §B — A2A version negotiation', () => {
127
129
  const negotiated = (drive.json as { negotiatedVersion?: string }).negotiatedVersion;
128
130
  expect(
129
131
  negotiated,
130
- driver.describe(
132
+ req('openwop.it.a2a-version-negotiation.an-authenticated-request-is-not-silently-downgraded',
131
133
  'RFCS/0152 §B',
132
134
  'a host that proceeds after a downgrade MUST report the version it actually negotiated. ' +
133
135
  'Reporting the preferred version while having used a lower one is the silent downgrade ' +
@@ -138,7 +140,7 @@ describe('RFC 0152 §B — A2A version negotiation', () => {
138
140
  for (const c of calls) {
139
141
  expect(
140
142
  c.headers['a2a-version'],
141
- driver.describe('RFCS/0152 §B', 'the wire header MUST match the reported negotiated version'),
143
+ req('openwop.it.a2a-version-negotiation.an-authenticated-request-is-not-silently-downgraded', 'RFCS/0152 §B', 'the wire header MUST match the reported negotiated version'),
142
144
  ).toBe(negotiated);
143
145
  }
144
146
  }
@@ -147,18 +149,18 @@ describe('RFC 0152 §B — A2A version negotiation', () => {
147
149
  it('an unsupported version fails through the canonical interop error envelope', async () => {
148
150
  if (!behaviorGate(PROFILE, await negotiationAdvertised())) return;
149
151
  const peer = getA2AFakePeer();
150
- if (peer === null) return;
152
+ if (peer === null) return softSkip('blocked', 'precondition not met — `peer === null` returned early (seam, prior step, or fixture unavailable)');
151
153
  const drive = await driver.post('/v1/host/sample/a2a/invoke', {
152
154
  peerUrl: peer.endpoint(),
153
155
  requestVersion: '99.0',
154
156
  });
155
- if (drive.status === 404 || drive.status === 403) return;
156
- expect(drive.status >= 400, driver.describe('RFCS/0152 §B', 'an unsupported version MUST fail')).toBe(true);
157
+ if (drive.status === 404 || drive.status === 403) return softSkip('blocked', 'precondition not met — `drive.status === 404 || drive.status === 403` returned early (seam, prior step, or fixture unavailable)');
158
+ expect(drive.status >= 400, req('openwop.it.a2a-version-negotiation.an-unsupported-version-fails-through-the-canonical-interop-error-envelope', 'RFCS/0152 §B', 'an unsupported version MUST fail')).toBe(true);
157
159
  // S28: canonical envelope is FLAT (`error: "<code>"`, error-envelope.schema.json);
158
160
  // the nested `error.code` is tolerated by the lib only through the deprecation window.
159
161
  expect(
160
162
  readErrorCode(drive.json),
161
- driver.describe(
163
+ req('openwop.it.a2a-version-negotiation.an-unsupported-version-fails-through-the-canonical-interop-error-envelope',
162
164
  'RFCS/0152 §B',
163
165
  'the upstream version error MUST be projected through the canonical OpenWOP interop error ' +
164
166
  'envelope when the failure crosses an OpenWOP boundary — a raw upstream body leaves the ' +
@@ -17,6 +17,8 @@
17
17
 
18
18
  import { describe, it, expect } from 'vitest';
19
19
  import { driver } from '../lib/driver.js';
20
+ import { req } from '../lib/requirement-ids.js';
21
+ import { softSkip } from '../lib/soft-skip.js';
20
22
 
21
23
  const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
22
24
  const UNIVERSAL_KINDS = ['clarification.request', 'schema.request', 'schema.response', 'error'];
@@ -25,7 +27,7 @@ describe('a2ui-surface-degrades: optional-advertised, not universal (RFC 0102 §
25
27
  it('ui.a2ui-surface is NOT a MUST-recognize universal kind', () => {
26
28
  expect(
27
29
  UNIVERSAL_KINDS.includes('ui.a2ui-surface'),
28
- 'ai-envelope.md §"A2UI surfaces": ui.a2ui-surface MUST be optional/advertised so an unrecognizing consumer may store-without-render',
30
+ req('openwop.it.a2ui-surface-degrades.ui-a2ui-surface-is-not-a-must-recognize-universal-kind', 'RFC 0102 §A', 'ai-envelope.md §"A2UI surfaces": ui.a2ui-surface MUST be optional/advertised so an unrecognizing consumer may store-without-render'),
29
31
  ).toBe(false);
30
32
  });
31
33
  });
@@ -43,12 +45,12 @@ describe.skipIf(HTTP_SKIP)('a2ui-surface-degrades: unadvertised kind is gated, r
43
45
  },
44
46
  hostSupportedEnvelopes: ['clarification.request'], // does not advertise ui.a2ui-surface
45
47
  });
46
- if (res.status === 404) return; // seam absent — soft-skip
48
+ if (res.status === 404) return softSkip('blocked', 'precondition not met — `res.status === 404` returned early (seam absent — soft-skip) (seam, prior step, or fixture unavailable)'); // seam absent — soft-skip
47
49
  expect(res.status).toBe(200);
48
50
  const body = res.json as { status?: string };
49
51
  expect(
50
52
  body.status,
51
- driver.describe('RFC 0102 §A (N6)', 'an unadvertised ui.a2ui-surface MUST be gated, never crash the run'),
53
+ req('openwop.it.a2ui-surface-degrades.posting-ui-a2ui-surface-to-a-host-that-does-not-advertise-it-gated-not-failed', 'RFC 0102 §A (N6)', 'an unadvertised ui.a2ui-surface MUST be gated, never crash the run'),
52
54
  ).toBe('gated');
53
55
  });
54
56
  });
@@ -47,6 +47,8 @@ import { join } from 'node:path';
47
47
  import { SCHEMAS_DIR } from '../lib/paths.js';
48
48
  import { driver } from '../lib/driver.js';
49
49
  import { capabilityFamily } from '../lib/discovery-capabilities.js';
50
+ import { req } from '../lib/requirement-ids.js';
51
+ import { softSkip } from '../lib/soft-skip.js';
50
52
 
51
53
  const ajv = new Ajv2020({ strict: false, allErrors: true });
52
54
 
@@ -270,7 +272,7 @@ function fullSurfaceMaterializedAfterUpdates(): { [key: string]: JsonValue } {
270
272
 
271
273
  describe('a2ui-surface-delta-transport: frame schema + op set (RFC 0114)', () => {
272
274
  it('a2ui-surface-delta-frame.schema.json compiles under Ajv2020', () => {
273
- expect(validateFrame, 'RFC 0114: the delta-frame schema MUST compile').toBeTypeOf('function');
275
+ expect(validateFrame, req('openwop.it.a2ui-surface-delta-transport.a2ui-surface-delta-frame-schema-json-compiles-under-ajv2020', 'RFC 0114 §"Delta transport', 'RFC 0114: the delta-frame schema MUST compile')).toBeTypeOf('function');
274
276
  });
275
277
 
276
278
  it('accepts a positive delta frame', () => {
@@ -281,18 +283,18 @@ describe('a2ui-surface-delta-transport: frame schema + op set (RFC 0114)', () =>
281
283
  };
282
284
  expect(
283
285
  validateFrame(frame),
284
- `RFC 0114: a positive delta frame MUST validate; errors: ${JSON.stringify(validateFrame.errors)}`,
286
+ req('openwop.it.a2ui-surface-delta-transport.accepts-a-positive-delta-frame', 'RFC 0114 §"Delta transport', `RFC 0114: a positive delta frame MUST validate; errors: ${JSON.stringify(validateFrame.errors)}`),
285
287
  ).toBe(true);
286
288
  });
287
289
 
288
290
  it('rejects a frame missing the required `surfaceRef`/`catalogVersion`/`patch`', () => {
289
- expect(validateFrame({ catalogVersion: CATALOG_VERSION, patch: [{ op: 'replace', path: '/x' }] })).toBe(false);
291
+ expect(validateFrame({ catalogVersion: CATALOG_VERSION, patch: [{ op: 'replace', path: '/x' }] }), req('openwop.it.a2ui-surface-delta-transport.rejects-a-frame-missing-the-required-surfaceref-catalogversion-patch', 'RFC 0114 §"Delta transport', 'rejects a frame missing the required `surfaceRef`/`catalogVersion`/`patch`')).toBe(false);
290
292
  expect(validateFrame({ surfaceRef: 'e', patch: [{ op: 'replace', path: '/x' }] })).toBe(false);
291
293
  expect(validateFrame({ surfaceRef: 'e', catalogVersion: CATALOG_VERSION })).toBe(false);
292
294
  });
293
295
 
294
296
  it('rejects an empty patch (minItems: 1)', () => {
295
- expect(validateFrame({ surfaceRef: 'e', catalogVersion: CATALOG_VERSION, patch: [] })).toBe(false);
297
+ expect(validateFrame({ surfaceRef: 'e', catalogVersion: CATALOG_VERSION, patch: [] }), req('openwop.it.a2ui-surface-delta-transport.rejects-an-empty-patch-minitems-1', 'RFC 0114 §"Delta transport', 'rejects an empty patch (minItems: 1)')).toBe(false);
296
298
  });
297
299
 
298
300
  it('EXCLUDES the `test` op from the op enum', () => {
@@ -303,7 +305,7 @@ describe('a2ui-surface-delta-transport: frame schema + op set (RFC 0114)', () =>
303
305
  };
304
306
  expect(
305
307
  validateFrame(frame),
306
- 'RFC 0114: a fire-and-forget transport frame cannot act on a failed conditional; `test` is excluded',
308
+ req('openwop.it.a2ui-surface-delta-transport.excludes-the-test-op-from-the-op-enum', 'RFC 0114 §"Delta transport', 'RFC 0114: a fire-and-forget transport frame cannot act on a failed conditional; `test` is excluded'),
307
309
  ).toBe(false);
308
310
  });
309
311
 
@@ -313,7 +315,7 @@ describe('a2ui-surface-delta-transport: frame schema + op set (RFC 0114)', () =>
313
315
  catalogVersion: CATALOG_VERSION,
314
316
  patch: [{ op: 'replace', path: '/x', value: 1, onApply: "fetch('https://evil')" }],
315
317
  };
316
- expect(validateFrame(frame)).toBe(false);
318
+ expect(validateFrame(frame), req('openwop.it.a2ui-surface-delta-transport.rejects-a-patch-item-carrying-an-out-of-set-property-additionalproperties-false', 'RFC 0114 §"Delta transport', 'rejects a patch item carrying an out-of-set property (additionalProperties:false)')).toBe(false);
317
319
  });
318
320
  });
319
321
 
@@ -337,11 +339,11 @@ describe('a2ui-surface-delta-transport: full + deltas reconstruct the materializ
337
339
  ];
338
340
 
339
341
  for (const frame of frames) {
340
- expect(validateFrame(frame), `RFC 0114: each delta frame MUST validate; ${JSON.stringify(validateFrame.errors)}`).toBe(true);
342
+ expect(validateFrame(frame), req('openwop.it.a2ui-surface-delta-transport.a-full-surface-delta-frames-reconstruct-the-tree-and-equal-the-non-negotiating-f', 'RFC 0114 §"Delta transport', `RFC 0114: each delta frame MUST validate; ${JSON.stringify(validateFrame.errors)}`)).toBe(true);
341
343
  // catalogVersion on a delta MUST equal the referenced full surface's.
342
344
  expect(
343
345
  frame.catalogVersion,
344
- 'RFC 0114: a delta frame\'s catalogVersion MUST equal the referenced full surface\'s',
346
+ req('openwop.it.a2ui-surface-delta-transport.a-full-surface-delta-frames-reconstruct-the-tree-and-equal-the-non-negotiating-f', 'RFC 0114 §"Delta transport', 'RFC 0114: a delta frame\'s catalogVersion MUST equal the referenced full surface\'s'),
345
347
  ).toBe(full.catalogVersion);
346
348
  }
347
349
 
@@ -354,13 +356,13 @@ describe('a2ui-surface-delta-transport: full + deltas reconstruct the materializ
354
356
  // (a) the reconstruction equals the full surface a non-negotiating subscriber materializes.
355
357
  expect(
356
358
  reconstructed,
357
- 'RFC 0114: the delta reconstruction MUST equal the full surface the host materializes for a non-negotiating subscriber',
359
+ req('openwop.it.a2ui-surface-delta-transport.a-full-surface-delta-frames-reconstruct-the-tree-and-equal-the-non-negotiating-f', 'RFC 0114 §"Delta transport', 'RFC 0114: the delta reconstruction MUST equal the full surface the host materializes for a non-negotiating subscriber'),
358
360
  ).toEqual(fullSurfaceMaterializedAfterUpdates());
359
361
 
360
362
  // (b) the reconstruction re-validates against the closed catalog before render.
361
363
  expect(
362
364
  validateRecorded(reconstructed),
363
- `RFC 0114: the post-patch surface MUST re-validate against the closed catalog; ${JSON.stringify(validateRecorded.errors)}`,
365
+ req('openwop.it.a2ui-surface-delta-transport.a-full-surface-delta-frames-reconstruct-the-tree-and-equal-the-non-negotiating-f', 'RFC 0114 §"Delta transport', `RFC 0114: the post-patch surface MUST re-validate against the closed catalog; ${JSON.stringify(validateRecorded.errors)}`),
364
366
  ).toBe(true);
365
367
  });
366
368
  });
@@ -390,7 +392,7 @@ describe('a2ui-surface-delta-transport: out-of-catalog delta fails closed (RFC 0
390
392
  // The `a2ui-surface-no-code-exec` boundary holds on the post-patch surface.
391
393
  expect(
392
394
  validateRecorded(postPatch),
393
- 'RFC 0114 / a2ui-surface-no-code-exec: an out-of-catalog component reached by a delta MUST fail closed-catalog validation post-patch',
395
+ req('openwop.it.a2ui-surface-delta-transport.a-delta-that-add-s-an-out-of-catalog-component-yields-a-post-patch-surface-that', 'RFC 0114 §"Delta transport', 'RFC 0114 / a2ui-surface-no-code-exec: an out-of-catalog component reached by a delta MUST fail closed-catalog validation post-patch'),
394
396
  ).toBe(false);
395
397
  });
396
398
 
@@ -411,7 +413,7 @@ describe('a2ui-surface-delta-transport: out-of-catalog delta fails closed (RFC 0
411
413
  const postPatch = applyPatch(full, frame.patch);
412
414
  expect(
413
415
  validateRecorded(postPatch),
414
- 'RFC 0114: a delta MUST NOT be a path by which a smuggled code field reaches render',
416
+ req('openwop.it.a2ui-surface-delta-transport.a-delta-replace-smuggling-a-script-bearing-property-also-fails-closed-catalog-va', 'RFC 0114 §"Delta transport', 'RFC 0114: a delta MUST NOT be a path by which a smuggled code field reaches render'),
415
417
  ).toBe(false);
416
418
  });
417
419
  });
@@ -425,14 +427,14 @@ describe('a2ui-surface-delta-transport: the recorded envelope is always full (RF
425
427
  };
426
428
  expect(
427
429
  validateRecorded(frame),
428
- 'RFC 0114: the recorded envelope is NEVER a delta — a delta frame MUST NOT validate as a recorded ui.a2ui-surface payload',
430
+ req('openwop.it.a2ui-surface-delta-transport.a-delta-frame-does-not-validate-against-the-recorded-ui-a2ui-surface-envelope-sc', 'RFC 0114 §"Delta transport', 'RFC 0114: the recorded envelope is NEVER a delta — a delta frame MUST NOT validate as a recorded ui.a2ui-surface payload'),
429
431
  ).toBe(false);
430
432
  });
431
433
 
432
434
  it('the full surface validates as the recorded ui.a2ui-surface payload (event-log read / replay shape)', () => {
433
435
  expect(
434
436
  validateRecorded(fullSurfaceV0()),
435
- 'RFC 0114: the event-log read / replay always sees the FULL surface, which MUST validate as the recorded payload',
437
+ req('openwop.it.a2ui-surface-delta-transport.the-full-surface-validates-as-the-recorded-ui-a2ui-surface-payload-event-log-rea', 'RFC 0114 §"Delta transport', 'RFC 0114: the event-log read / replay always sees the FULL surface, which MUST validate as the recorded payload'),
436
438
  ).toBe(true);
437
439
  });
438
440
  });
@@ -507,9 +509,9 @@ async function getEvents(runId: string, deltaOptIn: boolean): Promise<unknown> {
507
509
  describe.skipIf(HTTP_SKIP)('a2ui-surface-delta-transport: live host delta transport (RFC 0114, gated)', () => {
508
510
  it('drives the emit-surface seam: a ?a2uiDelta=1 subscriber reconstruction equals the non-negotiating full', async () => {
509
511
  const disco = await driver.get('/.well-known/openwop');
510
- if (disco.status !== 200) return; // no discovery — soft-skip
512
+ if (disco.status !== 200) return softSkip('blocked', 'precondition not met — `disco.status !== 200` returned early (no discovery — soft-skip) (seam, prior step, or fixture unavailable)'); // no discovery — soft-skip
511
513
  const cap = capabilityFamily<A2uiSurfaceCap>(disco.json, 'a2uiSurface');
512
- if (cap?.deltaTransport !== true) return; // capability not advertised — soft-skip
514
+ if (cap?.deltaTransport !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap?.deltaTransport !== true` returned early (capability not advertised — soft-skip)'); // capability not advertised — soft-skip
513
515
 
514
516
  const runId = `conf-a2ui-delta-${Date.now()}`;
515
517
  const surfaceA = fullSurfaceV0();
@@ -517,53 +519,53 @@ describe.skipIf(HTTP_SKIP)('a2ui-surface-delta-transport: live host delta transp
517
519
 
518
520
  // Baseline (surface A) through the host's REAL surface-emit path.
519
521
  const emitA = await driver.post('/v1/host/sample/a2ui/emit-surface', { runId, surface: surfaceA });
520
- if (emitA.status === 404 || emitA.status === 405) return; // seam absent — soft-skip the live leg
522
+ if (emitA.status === 404 || emitA.status === 405) return softSkip('blocked', 'precondition not met — `emitA.status === 404 || emitA.status === 405` returned early (seam absent — soft-skip the live leg) (seam, prior step, or fixture unavailable)'); // seam absent — soft-skip the live leg
521
523
  expect(
522
524
  emitA.status,
523
- driver.describe('RFC 0114 §15', 'the emit-surface seam MUST record the baseline full surface'),
525
+ req('openwop.it.a2ui-surface-delta-transport.drives-the-emit-surface-seam-a-a2uidelta-1-subscriber-reconstruction-equals-the', 'RFC 0114 §15', 'the emit-surface seam MUST record the baseline full surface'),
524
526
  ).toBeLessThan(300);
525
527
 
526
528
  // Second full surface (surface B) — recorded full AND transported as a delta
527
529
  // to any ?a2uiDelta=1 subscriber.
528
530
  const emitB = await driver.post('/v1/host/sample/a2ui/emit-surface', { runId, surface: surfaceB });
529
- if (emitB.status === 404 || emitB.status === 405) return;
530
- expect(emitB.status, driver.describe('RFC 0114 §15', 'the second emit MUST succeed')).toBeLessThan(300);
531
+ if (emitB.status === 404 || emitB.status === 405) return softSkip('blocked', 'precondition not met — `emitB.status === 404 || emitB.status === 405` returned early (seam, prior step, or fixture unavailable)');
532
+ expect(emitB.status, req('openwop.it.a2ui-surface-delta-transport.drives-the-emit-surface-seam-a-a2uidelta-1-subscriber-reconstruction-equals-the', 'RFC 0114 §15', 'the second emit MUST succeed')).toBeLessThan(300);
531
533
  const refB = (emitB.json as EmitSurfaceResponse)?.surfaceRef;
532
534
 
533
535
  // ?a2uiDelta=1 subscriber: locate the delta frame the host transported.
534
536
  const deltaEvents = await getEvents(runId, true);
535
- if (deltaEvents === null) return; // events stream unavailable in JSON — soft-skip
537
+ if (deltaEvents === null) return softSkip('blocked', 'precondition not met — `deltaEvents === null` returned early (events stream unavailable in JSON — soft-skip) (seam, prior step, or fixture unavailable)'); // events stream unavailable in JSON — soft-skip
536
538
  const frames = collectMatching(deltaEvents, validateFrame)
537
539
  .map(readFrame)
538
540
  .filter((f): f is { surfaceRef: string; catalogVersion: string; patch: PatchOp[] } => f !== null)
539
541
  .filter((f) => refB === undefined || f.surfaceRef === refB);
540
- if (frames.length === 0) return; // host streams SSE-only or buffered the frame — soft-skip
542
+ if (frames.length === 0) return softSkip('blocked', 'precondition not met — `frames.length === 0` returned early (host streams SSE-only or buffered the frame — soft-skip) (seam, prior step, or fixture unavailable)'); // host streams SSE-only or buffered the frame — soft-skip
541
543
  const frame = frames[frames.length - 1];
542
544
 
543
545
  // catalogVersion on the delta MUST equal the baseline full surface's.
544
546
  expect(
545
547
  frame.catalogVersion,
546
- driver.describe('RFC 0114', 'a delta frame catalogVersion MUST equal the referenced full surface'),
548
+ req('openwop.it.a2ui-surface-delta-transport.drives-the-emit-surface-seam-a-a2uidelta-1-subscriber-reconstruction-equals-the', 'RFC 0114', 'a delta frame catalogVersion MUST equal the referenced full surface'),
547
549
  ).toBe(surfaceA.catalogVersion);
548
550
 
549
551
  // Reconstruct: apply the host's real delta to the baseline, re-validate against the closed catalog.
550
552
  const reconstructed = applyPatch(surfaceA, frame.patch);
551
553
  expect(
552
554
  validateRecorded(reconstructed),
553
- driver.describe('RFC 0114 §"Delta transport"', 'the post-patch surface MUST re-validate against the closed catalog'),
555
+ req('openwop.it.a2ui-surface-delta-transport.drives-the-emit-surface-seam-a-a2uidelta-1-subscriber-reconstruction-equals-the', 'RFC 0114 §"Delta transport"', 'the post-patch surface MUST re-validate against the closed catalog'),
554
556
  ).toBe(true);
555
557
 
556
558
  // Non-negotiating subscriber: the host materializes the FULL surface for the same update.
557
559
  const fullEvents = await getEvents(runId, false);
558
- if (fullEvents === null) return;
560
+ if (fullEvents === null) return softSkip('blocked', 'precondition not met — `fullEvents === null` returned early (seam, prior step, or fixture unavailable)');
559
561
  const fulls = collectMatching(fullEvents, validateRecorded);
560
- if (fulls.length === 0) return; // soft-skip — no full surface observed on the non-delta stream
562
+ if (fulls.length === 0) return softSkip('blocked', 'precondition not met — `fulls.length === 0` returned early (soft-skip — no full surface observed on the non-delta stream) (seam, prior step, or fixture unavailable)'); // soft-skip — no full surface observed on the non-delta stream
561
563
  const materializedFull = fulls[fulls.length - 1];
562
564
 
563
565
  // delta and full agree — the core RFC 0114 transport guarantee, witnessed live.
564
566
  expect(
565
567
  reconstructed,
566
- driver.describe(
568
+ req('openwop.it.a2ui-surface-delta-transport.drives-the-emit-surface-seam-a-a2uidelta-1-subscriber-reconstruction-equals-the',
567
569
  'RFC 0114 §"Delta transport"',
568
570
  'a ?a2uiDelta=1 reconstruction MUST equal the full surface a non-negotiating subscriber materializes',
569
571
  ),
@@ -572,14 +574,14 @@ describe.skipIf(HTTP_SKIP)('a2ui-surface-delta-transport: live host delta transp
572
574
 
573
575
  it('the emit-surface seam rejects an out-of-catalog surface fail-closed (real catalog gate)', async () => {
574
576
  const disco = await driver.get('/.well-known/openwop');
575
- if (disco.status !== 200) return;
577
+ if (disco.status !== 200) return softSkip('blocked', 'precondition not met — `disco.status !== 200` returned early (seam, prior step, or fixture unavailable)');
576
578
  const cap = capabilityFamily<A2uiSurfaceCap>(disco.json, 'a2uiSurface');
577
- if (cap?.deltaTransport !== true) return;
579
+ if (cap?.deltaTransport !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap?.deltaTransport !== true` returned early');
578
580
 
579
581
  const runId = `conf-a2ui-delta-oob-${Date.now()}`;
580
582
  const baseline = await driver.post('/v1/host/sample/a2ui/emit-surface', { runId, surface: fullSurfaceV0() });
581
- if (baseline.status === 404 || baseline.status === 405) return; // seam absent — soft-skip
582
- expect(baseline.status, driver.describe('RFC 0114 §15', 'baseline emit MUST succeed')).toBeLessThan(300);
583
+ if (baseline.status === 404 || baseline.status === 405) return softSkip('blocked', 'precondition not met — `baseline.status === 404 || baseline.status === 405` returned early (seam absent — soft-skip) (seam, prior step, or fixture unavailable)'); // seam absent — soft-skip
584
+ expect(baseline.status, req('openwop.it.a2ui-surface-delta-transport.the-emit-surface-seam-rejects-an-out-of-catalog-surface-fail-closed-real-catalog', 'RFC 0114 §15', 'baseline emit MUST succeed')).toBeLessThan(300);
583
585
 
584
586
  // An out-of-catalog surface MUST be rejected by the host's REAL closed-catalog
585
587
  // validator — no delta transported, the a2ui-surface-no-code-exec boundary holds.
@@ -588,10 +590,10 @@ describe.skipIf(HTTP_SKIP)('a2ui-surface-delta-transport: live host delta transp
588
590
  surface: { components: [{ component: 'iframe', src: 'https://evil.example/x' }] },
589
591
  };
590
592
  const rejected = await driver.post('/v1/host/sample/a2ui/emit-surface', { runId, surface: outOfCatalog });
591
- if (rejected.status === 404 || rejected.status === 405) return;
593
+ if (rejected.status === 404 || rejected.status === 405) return softSkip('blocked', 'precondition not met — `rejected.status === 404 || rejected.status === 405` returned early (seam, prior step, or fixture unavailable)');
592
594
  expect(
593
595
  rejected.status,
594
- driver.describe(
596
+ req('openwop.it.a2ui-surface-delta-transport.the-emit-surface-seam-rejects-an-out-of-catalog-surface-fail-closed-real-catalog',
595
597
  'RFC 0114 §15 / a2ui-surface-no-code-exec',
596
598
  'an out-of-catalog surface MUST be rejected fail-closed by the host real catalog validator',
597
599
  ),
@@ -21,6 +21,8 @@ import { readFileSync } from 'node:fs';
21
21
  import { join } from 'node:path';
22
22
  import { driver } from '../lib/driver.js';
23
23
  import { SCHEMAS_DIR } from '../lib/paths.js';
24
+ import { req } from '../lib/requirement-ids.js';
25
+ import { softSkip } from '../lib/soft-skip.js';
24
26
 
25
27
  const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
26
28
  const schema = JSON.parse(
@@ -45,7 +47,7 @@ describe('a2ui-surface-replay: self-contained surface (RFC 0102 §A.6)', () => {
45
47
  const external = refs.filter((r) => !r.startsWith('#'));
46
48
  expect(
47
49
  external,
48
- 'RFC 0102 §A.6: a stored surface MUST be self-contained for deterministic :fork/replay (no live external-catalog ref)',
50
+ req('openwop.it.a2ui-surface-replay.all-refs-are-internal-surface-renders-from-the-payload-alone-on-replay', 'RFC 0102 §A.6', 'RFC 0102 §A.6: a stored surface MUST be self-contained for deterministic :fork/replay (no live external-catalog ref)'),
49
51
  ).toEqual([]);
50
52
  });
51
53
  });
@@ -63,7 +65,7 @@ describe.skipIf(HTTP_SKIP)('a2ui-surface-replay: correlationId conflict on type
63
65
  envelope: { ...base, type: 'ui.a2ui-surface', envelopeId: 'env-a2ui-rep-1' },
64
66
  hostSupportedEnvelopes: ['ui.a2ui-surface', 'clarification.request'],
65
67
  });
66
- if (first.status === 404) return; // seam absent — soft-skip
68
+ if (first.status === 404) return softSkip('blocked', 'precondition not met — `first.status === 404` returned early (seam absent — soft-skip) (seam, prior step, or fixture unavailable)'); // seam absent — soft-skip
67
69
 
68
70
  const conflict = await driver.post('/v1/host/sample/envelope/accept', {
69
71
  envelope: {
@@ -74,11 +76,11 @@ describe.skipIf(HTTP_SKIP)('a2ui-surface-replay: correlationId conflict on type
74
76
  },
75
77
  hostSupportedEnvelopes: ['ui.a2ui-surface', 'clarification.request'],
76
78
  });
77
- if (conflict.status === 404) return;
79
+ if (conflict.status === 404) return softSkip('blocked', 'precondition not met — `conflict.status === 404` returned early (seam, prior step, or fixture unavailable)');
78
80
  const body = conflict.json as { status?: string; reason?: string };
79
81
  expect(
80
82
  body.reason ?? '',
81
- driver.describe('ai-envelope.md §"Replay determinism"', 'same correlationId + divergent type MUST conflict'),
83
+ req('openwop.it.a2ui-surface-replay.re-emission-with-same-correlationid-different-type-envelope-correlation-conflict', 'ai-envelope.md §"Replay determinism"', 'same correlationId + divergent type MUST conflict'),
82
84
  ).toContain('envelope_correlation_conflict');
83
85
  });
84
86
  });