@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.
- package/README.md +11 -2
- package/dist/cli.js +262 -14
- package/dist/lib/certification-bundle-v3.js +142 -0
- package/dist/lib/paths.js +25 -3
- package/dist/lib/requirement-ids.js +57 -4
- package/dist/lib/requirement-ledger.js +1 -0
- package/dist/lib/scenario-disposition.js +6 -1
- package/dist/lib/spec-coherence.js +13 -32
- package/dist/spec-artifacts.lock.json +5 -0
- package/package.json +11 -33
- package/requirements.json +22892 -11701
- package/scenario-majors.json +1493 -0
- package/schemas/CORPUS-STAMP.json +137 -5
- package/src/cli.ts +236 -19
- package/src/global-setup.ts +3 -2
- package/src/lib/certification-bundle-v3.ts +175 -0
- package/src/lib/corpus-stamp.ts +29 -1
- package/src/lib/driver.ts +9 -1
- package/src/lib/era2-seed.ts +197 -0
- package/src/lib/paths.ts +25 -3
- package/src/lib/requirement-ids.ts +54 -4
- package/src/lib/requirement-ledger.ts +7 -1
- package/src/lib/scenario-disposition.ts +6 -1
- package/src/lib/seams.ts +45 -0
- package/src/lib/spec-coherence.ts +15 -32
- package/src/lib/v2.ts +62 -0
- package/src/scenarios/a2a-1-0-agent-card.test.ts +17 -16
- package/src/scenarios/a2a-1-0-task-roundtrip.test.ts +11 -10
- package/src/scenarios/a2a-card-runtime-consistency.test.ts +18 -17
- package/src/scenarios/a2a-peer-authority.test.ts +7 -6
- package/src/scenarios/a2a-task-roundtrip.test.ts +30 -29
- package/src/scenarios/a2a-version-negotiation.test.ts +16 -14
- package/src/scenarios/a2ui-surface-degrades.test.ts +5 -3
- package/src/scenarios/a2ui-surface-delta-transport.test.ts +35 -33
- package/src/scenarios/a2ui-surface-replay.test.ts +6 -4
- package/src/scenarios/a2ui-surface-shape.test.ts +11 -10
- package/src/scenarios/a2ui-surface-version-refusal.test.ts +6 -4
- package/src/scenarios/a2ui-untrusted-blocks-approval.test.ts +5 -3
- package/src/scenarios/agent-capability-degraded-projection.test.ts +12 -11
- package/src/scenarios/agent-channel-dispatch.test.ts +18 -16
- package/src/scenarios/agent-deployment-lifecycle.test.ts +20 -19
- package/src/scenarios/agent-deployment-shape.test.ts +22 -24
- package/src/scenarios/agent-eval-run.test.ts +19 -18
- package/src/scenarios/agent-eval-suite-shape.test.ts +20 -22
- package/src/scenarios/agent-live-allowlist-enforced.test.ts +6 -5
- package/src/scenarios/agent-live-invocation-bracket.test.ts +13 -12
- package/src/scenarios/agent-live-runtime-shape.test.ts +10 -12
- package/src/scenarios/agent-live-structured-output.test.ts +7 -6
- package/src/scenarios/agent-loop-iteration-monotonic.test.ts +3 -3
- package/src/scenarios/agent-loop-stateful-resume.test.ts +2 -2
- package/src/scenarios/agent-loop-version5-shape.test.ts +4 -4
- package/src/scenarios/agent-loop-workspace-snapshot.test.ts +3 -3
- package/src/scenarios/agent-manifest-role-profile.test.ts +9 -9
- package/src/scenarios/agent-manifest-runtime.test.ts +13 -12
- package/src/scenarios/agent-org-chart-scoping.test.ts +14 -13
- package/src/scenarios/agent-org-chart-shape.test.ts +11 -13
- package/src/scenarios/agent-platform-aggregate-evidence.test.ts +5 -4
- package/src/scenarios/agent-platform-profile.test.ts +18 -19
- package/src/scenarios/agent-requires-capabilities-shape.test.ts +4 -4
- package/src/scenarios/agent-roster-attribution.test.ts +18 -17
- package/src/scenarios/agent-roster-shape.test.ts +17 -19
- package/src/scenarios/agent-verifier-shape.test.ts +11 -11
- package/src/scenarios/agentConfidenceEscalation.test.ts +2 -1
- package/src/scenarios/agentMemoryCrossTenantIsolation.test.ts +6 -5
- package/src/scenarios/agentMemoryRedactionContract.test.ts +2 -1
- package/src/scenarios/agentMemoryRoundTrip.test.ts +2 -1
- package/src/scenarios/agentMemoryTtlExpiry.test.ts +4 -3
- package/src/scenarios/agentMessageReducer.test.ts +2 -1
- package/src/scenarios/agentMetadata.test.ts +2 -1
- package/src/scenarios/agentPackCatalog.test.ts +23 -21
- package/src/scenarios/agentPackExport.test.ts +4 -2
- package/src/scenarios/agentPackHandoffSchemaValidation.test.ts +5 -4
- package/src/scenarios/agentPackInstall.test.ts +4 -2
- package/src/scenarios/agentPackProvenance.test.ts +2 -1
- package/src/scenarios/agentReasoningEvents.test.ts +3 -2
- package/src/scenarios/agentReasoningStreaming.test.ts +11 -10
- package/src/scenarios/agents-run-tool-allowlist.test.ts +14 -12
- package/src/scenarios/ai-envelope-shape.test.ts +31 -29
- package/src/scenarios/aiEnvelope.capBreached.test.ts +26 -24
- package/src/scenarios/aiEnvelope.contractRefusal.test.ts +36 -34
- package/src/scenarios/aiEnvelope.correlationReplay.test.ts +17 -15
- package/src/scenarios/aiEnvelope.redaction.test.ts +22 -20
- package/src/scenarios/aiEnvelope.schemaDrift.test.ts +18 -16
- package/src/scenarios/aiEnvelope.trustBoundaryPropagation.test.ts +38 -36
- package/src/scenarios/aiEnvelope.universalKinds.test.ts +31 -29
- package/src/scenarios/aiproviders-input-shape.test.ts +5 -5
- package/src/scenarios/aiproviders-realtimevoice-shape.test.ts +14 -16
- package/src/scenarios/aiproviders-selfhosted-honesty.test.ts +6 -4
- package/src/scenarios/aiproviders-selfhosted-shape.test.ts +15 -17
- package/src/scenarios/aiproviders-speechsynth-shape.test.ts +6 -8
- package/src/scenarios/aiproviders-subscription-scope.test.ts +7 -8
- package/src/scenarios/anonymous-actor-audit-opaque.test.ts +9 -8
- package/src/scenarios/anonymous-actor-default-deny.test.ts +10 -9
- package/src/scenarios/anonymous-actor-egress-guarded.test.ts +6 -5
- package/src/scenarios/anonymous-actor-no-secret-reach.test.ts +7 -6
- package/src/scenarios/anonymous-actor-shape.test.ts +17 -19
- package/src/scenarios/anonymous-actor-write-gated.test.ts +9 -8
- package/src/scenarios/append-ordering.test.ts +7 -5
- package/src/scenarios/approval-gate-events.test.ts +4 -3
- package/src/scenarios/approval-gate-flow.test.ts +4 -3
- package/src/scenarios/approval-payload.test.ts +5 -4
- package/src/scenarios/artifact-auth.test.ts +7 -6
- package/src/scenarios/artifact-type-pack-install.test.ts +6 -6
- package/src/scenarios/artifact-type-pack-manifest-validation.test.ts +9 -8
- package/src/scenarios/artifact-type-registration-source.test.ts +20 -20
- package/src/scenarios/artifact-type-store-emission.test.ts +15 -18
- package/src/scenarios/artifact-type-store-without-render.test.ts +4 -4
- package/src/scenarios/audit-log-integrity.test.ts +8 -6
- package/src/scenarios/auth-api-key-rotation.test.ts +11 -9
- package/src/scenarios/auth-mtls.test.ts +22 -20
- package/src/scenarios/auth-oauth2-client-credentials.test.ts +16 -14
- package/src/scenarios/auth-oidc-user-bearer.test.ts +26 -24
- package/src/scenarios/auth-saml-profile.test.ts +14 -12
- package/src/scenarios/auth-scim-profile.test.ts +3 -2
- package/src/scenarios/auth-subject-link-key-class.test.ts +8 -7
- package/src/scenarios/auth-subject-link.test.ts +11 -10
- package/src/scenarios/auth.test.ts +5 -4
- package/src/scenarios/authorization-fail-closed.test.ts +3 -2
- package/src/scenarios/authorization-roles-shape.test.ts +9 -7
- package/src/scenarios/blob-cross-tenant-isolation.test.ts +8 -6
- package/src/scenarios/blob-presign-expiry.test.ts +12 -10
- package/src/scenarios/blob-roundtrip.test.ts +0 -0
- package/src/scenarios/budget-enforcement.test.ts +15 -14
- package/src/scenarios/budget-policy-shape.test.ts +18 -19
- package/src/scenarios/bulk-cancel.test.ts +9 -8
- package/src/scenarios/byok-auth-modes.test.ts +13 -14
- package/src/scenarios/byok-roundtrip.test.ts +12 -10
- package/src/scenarios/cache-cross-tenant-isolation.test.ts +8 -6
- package/src/scenarios/cache-ttl-expiry.test.ts +7 -5
- package/src/scenarios/callai-multimodal.test.ts +5 -3
- package/src/scenarios/cancellation.test.ts +6 -5
- package/src/scenarios/cap-breach.test.ts +15 -14
- package/src/scenarios/capability-arm-witness.test.ts +5 -3
- package/src/scenarios/certification-bundle-non-vacuous.test.ts +20 -19
- package/src/scenarios/certification-bundle-redaction.test.ts +11 -10
- package/src/scenarios/certification-bundle-v2.test.ts +11 -10
- package/src/scenarios/chain-compensation-expansion.test.ts +14 -13
- package/src/scenarios/chain-produced-var-roundtrip.test.ts +13 -14
- package/src/scenarios/chain-subchain-cycle-rejected.test.ts +9 -10
- package/src/scenarios/chain-subchain-fanout.test.ts +8 -9
- package/src/scenarios/chain-subchain-sibling.test.ts +14 -16
- package/src/scenarios/channel-presence-behavioral.test.ts +9 -7
- package/src/scenarios/channel-presence-shape.test.ts +11 -12
- package/src/scenarios/channel-ttl.test.ts +6 -5
- package/src/scenarios/chat-card-pack-execution.test.ts +4 -4
- package/src/scenarios/chat-card-pack-manifest-validation.test.ts +9 -8
- package/src/scenarios/commitment-fired.test.ts +6 -5
- package/src/scenarios/compensation-behavior.test.ts +24 -23
- package/src/scenarios/compensation-recovery.test.ts +28 -27
- package/src/scenarios/configurable-schema.test.ts +8 -6
- package/src/scenarios/conformance-advertised-seam-required.test.ts +6 -5
- package/src/scenarios/conformance-execution-witness.test.ts +9 -8
- package/src/scenarios/connection-pack-apihosts.test.ts +19 -17
- package/src/scenarios/connection-pack-manifest-valid.test.ts +12 -11
- package/src/scenarios/connection-pack-no-credential-material.test.ts +8 -6
- package/src/scenarios/connection-pack-reach-exclusive.test.ts +5 -4
- package/src/scenarios/connection-pack-write-reconsent.test.ts +6 -4
- package/src/scenarios/connection-provider-resolution.test.ts +18 -16
- package/src/scenarios/connector-manifest-validity.test.ts +8 -7
- package/src/scenarios/context-budget-transcript-bound.test.ts +17 -15
- package/src/scenarios/context-summarization-replay.test.ts +14 -12
- package/src/scenarios/contract-provenance.test.ts +18 -18
- package/src/scenarios/conversation-turn-model-provenance-shape.test.ts +14 -15
- package/src/scenarios/conversationCapabilityNegotiation.test.ts +2 -1
- package/src/scenarios/conversationLifecycle.test.ts +2 -1
- package/src/scenarios/conversationReplayDeterminism.test.ts +4 -2
- package/src/scenarios/conversationVsLegacySuspend.test.ts +2 -1
- package/src/scenarios/core-standard-profile.test.ts +8 -9
- package/src/scenarios/cost-attribution.test.ts +23 -21
- package/src/scenarios/credential-payload-redaction.test.ts +4 -3
- package/src/scenarios/credentials-capability-shape.test.ts +5 -4
- package/src/scenarios/cross-engine-append-behavior.test.ts +11 -10
- package/src/scenarios/cross-engine-append-ordering.test.ts +4 -3
- package/src/scenarios/cross-host-ancestry-endpoint.test.ts +11 -10
- package/src/scenarios/cross-host-causation-shape.test.ts +7 -6
- package/src/scenarios/cross-workspace-isolation.test.ts +4 -3
- package/src/scenarios/data-residency-admission.test.ts +15 -14
- package/src/scenarios/deadletter-capability-shape.test.ts +6 -4
- package/src/scenarios/deadletter-retry-exhaustion.test.ts +4 -3
- package/src/scenarios/debug-bundle-truncation.test.ts +9 -7
- package/src/scenarios/debugBundle.test.ts +25 -23
- package/src/scenarios/discovery-families-view.test.ts +5 -4
- package/src/scenarios/discovery.test.ts +30 -29
- package/src/scenarios/dispatch-cross-worker-handoff.test.ts +10 -8
- package/src/scenarios/dispatch-fanout-parallel.test.ts +24 -22
- package/src/scenarios/dispatch-input-mapping.test.ts +12 -10
- package/src/scenarios/dispatch-output-mapping.test.ts +9 -7
- package/src/scenarios/dispatch-per-item-input.test.ts +22 -20
- package/src/scenarios/dispatchLoop.test.ts +3 -2
- package/src/scenarios/distillation-index-roundtrip.test.ts +3 -3
- package/src/scenarios/distillation-secret-carryforward.test.ts +2 -2
- package/src/scenarios/distillation-shape.test.ts +4 -4
- package/src/scenarios/distillation-stable-archive.test.ts +6 -6
- package/src/scenarios/distillation-token-budget.test.ts +5 -5
- package/src/scenarios/durability-poison-exhaustion.test.ts +7 -6
- package/src/scenarios/egress-audience-binding.test.ts +11 -10
- package/src/scenarios/egress-decision-content-free.test.ts +6 -5
- package/src/scenarios/egress-provenance-shape.test.ts +17 -18
- package/src/scenarios/envelope-completion-distinguishes-truncation.test.ts +27 -25
- package/src/scenarios/envelope-nl-to-format-engaged.test.ts +19 -17
- package/src/scenarios/envelope-reasoning-secret-redaction.test.ts +28 -26
- package/src/scenarios/envelope-reasoning-shape.test.ts +19 -17
- package/src/scenarios/envelope-recovery-applied.test.ts +24 -22
- package/src/scenarios/envelope-refusal-shape.test.ts +31 -29
- package/src/scenarios/envelope-rendering-hint.test.ts +5 -4
- package/src/scenarios/envelope-retry-attempted.test.ts +33 -31
- package/src/scenarios/envelope-retry-exhausted.test.ts +22 -20
- package/src/scenarios/envelope-tier-one-subset-static.test.ts +9 -7
- package/src/scenarios/envelope-truncated.test.ts +19 -17
- package/src/scenarios/envelope-truncation-cap-exhaustion.test.ts +19 -17
- package/src/scenarios/envelope-variant-discriminator-static.test.ts +4 -3
- package/src/scenarios/errors.test.ts +20 -19
- package/src/scenarios/eventOrdering.test.ts +16 -14
- package/src/scenarios/exec-not-protocol-tier.test.ts +5 -7
- package/src/scenarios/experimental-tier-shape.test.ts +17 -15
- package/src/scenarios/export-bundle-portability.test.ts +17 -17
- package/src/scenarios/failure-path.test.ts +7 -6
- package/src/scenarios/feedback-capability-shape.test.ts +4 -3
- package/src/scenarios/feedback-correction-redaction.test.ts +2 -1
- package/src/scenarios/feedback-cross-tenant-isolation.test.ts +2 -1
- package/src/scenarios/feedback-fork-not-copied.test.ts +3 -2
- package/src/scenarios/feedback-on-terminal-run.test.ts +3 -2
- package/src/scenarios/feedback-record-and-list.test.ts +2 -1
- package/src/scenarios/feedback-unsupported-501.test.ts +2 -1
- package/src/scenarios/fixtures-gating.test.ts +29 -28
- package/src/scenarios/fixtures-valid.test.ts +16 -15
- package/src/scenarios/form-content-instantiation.test.ts +15 -14
- package/src/scenarios/frontend-plugin-packs.test.ts +29 -27
- package/src/scenarios/fs-path-traversal.test.ts +14 -12
- package/src/scenarios/goal-standing-continuation.test.ts +21 -21
- package/src/scenarios/grpc-transport.test.ts +9 -7
- package/src/scenarios/heartbeat-capability-shape.test.ts +3 -3
- package/src/scenarios/heartbeat-fires-once-per-tick.test.ts +2 -2
- package/src/scenarios/heartbeat-idempotent-no-spam.test.ts +3 -3
- package/src/scenarios/heartbeat-runtime-bound.test.ts +2 -2
- package/src/scenarios/highConcurrency.test.ts +14 -13
- package/src/scenarios/host-callback-declaration.test.ts +8 -7
- package/src/scenarios/http-client-ssrf.test.ts +7 -5
- package/src/scenarios/i18n-negotiation.test.ts +14 -12
- package/src/scenarios/idempotency-concurrent-claim.test.ts +14 -13
- package/src/scenarios/idempotency-key-determinism.test.ts +20 -18
- package/src/scenarios/idempotency.test.ts +8 -7
- package/src/scenarios/idempotencyRetry.test.ts +13 -11
- package/src/scenarios/identity-owner-shape.test.ts +6 -5
- package/src/scenarios/identity-passthrough.test.ts +4 -3
- package/src/scenarios/interrupt-approval.test.ts +6 -5
- package/src/scenarios/interrupt-approver-routing.test.ts +15 -15
- package/src/scenarios/interrupt-auth-required-resume.test.ts +5 -3
- package/src/scenarios/interrupt-clarification.test.ts +4 -3
- package/src/scenarios/interrupt-external-event-correlation.test.ts +6 -5
- package/src/scenarios/interrupt-parent-child-cascade.test.ts +4 -3
- package/src/scenarios/interrupt-quorum-resolution.test.ts +5 -4
- package/src/scenarios/interrupt-token-matrix.test.ts +8 -6
- package/src/scenarios/interruptRace.test.ts +12 -10
- package/src/scenarios/kv-atomic-increment.test.ts +11 -9
- package/src/scenarios/kv-cas.test.ts +14 -12
- package/src/scenarios/kv-cross-tenant-isolation.test.ts +12 -10
- package/src/scenarios/kv-ttl-expiry.test.ts +7 -5
- package/src/scenarios/localized-content-delivery.test.ts +24 -26
- package/src/scenarios/maliciousManifest.test.ts +10 -8
- package/src/scenarios/mcp-2026-07-28-discover.test.ts +15 -14
- package/src/scenarios/mcp-cache-tenant-scope.test.ts +6 -5
- package/src/scenarios/mcp-current-auth-boundary.test.ts +7 -6
- package/src/scenarios/mcp-discoverability.test.ts +9 -7
- package/src/scenarios/mcp-extension-opacity.test.ts +7 -6
- package/src/scenarios/mcp-mrtr-roundtrip.test.ts +18 -17
- package/src/scenarios/mcp-server-elicitation-bridge.test.ts +13 -12
- package/src/scenarios/mcp-server-prompt-roundtrip.test.ts +11 -10
- package/src/scenarios/mcp-server-resource-roundtrip.test.ts +11 -10
- package/src/scenarios/mcp-server-sampling-bridge.test.ts +12 -11
- package/src/scenarios/mcp-server-tool-roundtrip.test.ts +14 -13
- package/src/scenarios/mcp-server-untrusted-args.test.ts +14 -13
- package/src/scenarios/mcp-stateless-request.test.ts +11 -10
- package/src/scenarios/mcp-tool-roundtrip.test.ts +4 -3
- package/src/scenarios/mcp-toolcall-redaction.test.ts +4 -3
- package/src/scenarios/mcp-version-negotiation.test.ts +15 -14
- package/src/scenarios/media-url-inline-cap.test.ts +21 -19
- package/src/scenarios/memory-attribution-emits-on-write.test.ts +5 -5
- package/src/scenarios/memory-attribution-no-content.test.ts +4 -4
- package/src/scenarios/memory-attribution-replay-stable.test.ts +4 -3
- package/src/scenarios/memory-attribution-shape.test.ts +4 -3
- package/src/scenarios/memory-attribution-tenant-scoped.test.ts +3 -3
- package/src/scenarios/memory-capability-model-shape.test.ts +14 -15
- package/src/scenarios/memory-compaction-event-emitted.test.ts +7 -6
- package/src/scenarios/memory-compaction-provenance-tag.test.ts +3 -2
- package/src/scenarios/memory-compaction-sr1-carry-forward.test.ts +7 -6
- package/src/scenarios/memory-consolidation-idempotent.test.ts +5 -4
- package/src/scenarios/memory-consolidation-shape.test.ts +9 -11
- package/src/scenarios/memory-degraded-projection.test.ts +10 -9
- package/src/scenarios/memory-injection-budget.test.ts +15 -14
- package/src/scenarios/metric-emission.test.ts +4 -3
- package/src/scenarios/model-capability-insufficient.test.ts +19 -17
- package/src/scenarios/model-capability-substituted.test.ts +20 -18
- package/src/scenarios/multi-agent-confidence-escalation.test.ts +12 -11
- package/src/scenarios/multi-agent-handoff-state-machine.test.ts +10 -9
- package/src/scenarios/multi-agent-memory-lifecycle.test.ts +11 -10
- package/src/scenarios/multi-node-ordering.test.ts +4 -3
- package/src/scenarios/multi-party-conversation-behavioral.test.ts +6 -6
- package/src/scenarios/multi-party-conversation-shape.test.ts +21 -23
- package/src/scenarios/multi-region-idempotency-behavior.test.ts +16 -15
- package/src/scenarios/multi-region-idempotency.test.ts +6 -5
- package/src/scenarios/node-failed-payload-shape.test.ts +5 -4
- package/src/scenarios/node-module-required-capabilities-shape.test.ts +10 -8
- package/src/scenarios/oauth-authorization-code-roundtrip.test.ts +5 -4
- package/src/scenarios/oauth-capability-shape.test.ts +5 -4
- package/src/scenarios/oauth-connector-redaction.test.ts +4 -3
- package/src/scenarios/openapi-resolved-paths.test.ts +6 -5
- package/src/scenarios/orchestratorConservativePath.test.ts +2 -1
- package/src/scenarios/orchestratorDispatch.test.ts +2 -1
- package/src/scenarios/orchestratorTermination.test.ts +2 -1
- package/src/scenarios/org-position-no-authority-escalation.test.ts +2 -2
- package/src/scenarios/otel-collector-canary-inspection.test.ts +8 -7
- package/src/scenarios/otel-emission-grpc.test.ts +3 -2
- package/src/scenarios/otel-emission.test.ts +6 -5
- package/src/scenarios/otel-trace-propagation-subworkflow.test.ts +7 -6
- package/src/scenarios/otel-trace-propagation.test.ts +2 -1
- package/src/scenarios/owner-subject-echo.test.ts +13 -12
- package/src/scenarios/owner-subject-shape.test.ts +11 -10
- package/src/scenarios/pack-manifest-extension-opacity.test.ts +8 -8
- package/src/scenarios/pack-registry-isolation.test.ts +4 -3
- package/src/scenarios/pack-registry-publish.test.ts +65 -63
- package/src/scenarios/pack-registry.test.ts +38 -36
- package/src/scenarios/pause-resume.test.ts +18 -16
- package/src/scenarios/policies.test.ts +20 -18
- package/src/scenarios/production-backpressure.test.ts +18 -16
- package/src/scenarios/production-retention-expiry.test.ts +8 -6
- package/src/scenarios/profile-discovery-core-alias.test.ts +13 -11
- package/src/scenarios/profileDerivation.test.ts +34 -33
- package/src/scenarios/prompt-all-four-kinds-events.test.ts +11 -9
- package/src/scenarios/prompt-composed-secret-redaction.test.ts +11 -9
- package/src/scenarios/prompt-composed-trust-marker.test.ts +10 -8
- package/src/scenarios/prompt-end-to-end-events.test.ts +17 -15
- package/src/scenarios/prompt-list-and-fetch.test.ts +16 -14
- package/src/scenarios/prompt-mutable-lifecycle.test.ts +13 -11
- package/src/scenarios/prompt-mutation-workspace-membership-enforced.test.ts +6 -4
- package/src/scenarios/prompt-pack-install.test.ts +14 -12
- package/src/scenarios/prompt-prefix-cache.test.ts +19 -17
- package/src/scenarios/prompt-read-workspace-membership-enforced.test.ts +11 -9
- package/src/scenarios/prompt-render-deterministic.test.ts +19 -17
- package/src/scenarios/prompt-resolution-chain-agent-intrinsic.test.ts +7 -5
- package/src/scenarios/prompt-resolution-chain-event.test.ts +12 -10
- package/src/scenarios/prompt-resolution-chain-fallback-cascade.test.ts +10 -8
- package/src/scenarios/prompt-resolution-chain-node-wins.test.ts +8 -6
- package/src/scenarios/prompt-template-shape.test.ts +30 -28
- package/src/scenarios/proposal-reviewable-learning.test.ts +19 -19
- package/src/scenarios/protocol-versions-array.test.ts +10 -9
- package/src/scenarios/provider-usage.test.ts +17 -15
- package/src/scenarios/providerPolicyEnforcement.test.ts +16 -14
- package/src/scenarios/purpose-propagation.test.ts +23 -21
- package/src/scenarios/queue-ack-nack-dlq.test.ts +12 -10
- package/src/scenarios/queue-cross-tenant-isolation.test.ts +8 -6
- package/src/scenarios/queue-publish-consume-roundtrip.test.ts +10 -8
- package/src/scenarios/rate-limit-envelope.test.ts +6 -5
- package/src/scenarios/redaction.test.ts +13 -11
- package/src/scenarios/redactionAdversarial.test.ts +11 -9
- package/src/scenarios/registry-public.test.ts +12 -11
- package/src/scenarios/replay-divergence-at-refusal.test.ts +16 -15
- package/src/scenarios/replay-fanout-suppression.test.ts +16 -15
- package/src/scenarios/replay-fork-arbitrary.test.ts +24 -23
- package/src/scenarios/replay-fork.test.ts +20 -19
- package/src/scenarios/replay-llm-cache-key-portable.test.ts +11 -9
- package/src/scenarios/replay-llm-cache-key.test.ts +22 -21
- package/src/scenarios/replay-observable-sequence-determinism.test.ts +4 -3
- package/src/scenarios/replay-retention-expiry.test.ts +11 -9
- package/src/scenarios/replay-side-effect-suppression.test.ts +21 -19
- package/src/scenarios/replayDeterminism.test.ts +13 -12
- package/src/scenarios/requirement-ledger.test.ts +15 -14
- package/src/scenarios/restart-during-run.test.ts +3 -2
- package/src/scenarios/route-coverage.test.ts +20 -19
- package/src/scenarios/run-diff.test.ts +22 -20
- package/src/scenarios/run-execution-bounds-shape.test.ts +14 -12
- package/src/scenarios/run-transport-economy.test.ts +13 -11
- package/src/scenarios/runner-ledger.test.ts +17 -16
- package/src/scenarios/runs-lifecycle.test.ts +7 -6
- package/src/scenarios/runtime-capabilities.test.ts +12 -10
- package/src/scenarios/runtime-requires-install-gate.test.ts +8 -8
- package/src/scenarios/runtime-requires-shape.test.ts +7 -6
- package/src/scenarios/safefetch-behavior.test.ts +22 -21
- package/src/scenarios/safefetch-live-audit.test.ts +14 -13
- package/src/scenarios/sandbox-memory-cap.test.ts +2 -1
- package/src/scenarios/sandbox-mvp-behavior.test.ts +24 -23
- package/src/scenarios/sandbox-no-host-fs-escape.test.ts +3 -2
- package/src/scenarios/sandbox-timeout-cap.test.ts +2 -1
- package/src/scenarios/sandbox-wasm-isolation.test.ts +14 -15
- package/src/scenarios/sandbox-wasm-timeout.test.ts +5 -6
- package/src/scenarios/scheduling-capability-shape.test.ts +8 -6
- package/src/scenarios/scheduling-cron-fires-once.test.ts +8 -6
- package/src/scenarios/search-bm25-roundtrip.test.ts +8 -6
- package/src/scenarios/secret-leakage-otel-attribute.test.ts +22 -20
- package/src/scenarios/self-hosted-runner.test.ts +26 -24
- package/src/scenarios/semantic-digest-vectors.test.ts +8 -7
- package/src/scenarios/speech-synthesis-roundtrip.test.ts +9 -7
- package/src/scenarios/speech-synthesis-unadvertised.test.ts +5 -3
- package/src/scenarios/sql-injection-rejection.test.ts +8 -6
- package/src/scenarios/sql-transaction-atomicity.test.ts +10 -8
- package/src/scenarios/staleClaim.test.ts +5 -4
- package/src/scenarios/stream-modes-buffer.test.ts +8 -7
- package/src/scenarios/stream-modes-mixed.test.ts +10 -9
- package/src/scenarios/stream-modes.test.ts +12 -11
- package/src/scenarios/stream-subscribe-from-beginning.test.ts +11 -9
- package/src/scenarios/stream-text-fixture.test.ts +14 -13
- package/src/scenarios/streamReconnect.test.ts +13 -11
- package/src/scenarios/strict-behavior-gate.test.ts +9 -8
- package/src/scenarios/subrun-approval-fail-closed.test.ts +2 -2
- package/src/scenarios/subrun-approval-gate.test.ts +3 -3
- package/src/scenarios/subrun-attestation-shape.test.ts +2 -2
- package/src/scenarios/subrun-checksum-stable.test.ts +4 -4
- package/src/scenarios/subworkflow-input-mapping.test.ts +16 -14
- package/src/scenarios/subworkflow.test.ts +10 -9
- package/src/scenarios/table-cross-tenant-isolation.test.ts +8 -6
- package/src/scenarios/table-cursor-pagination.test.ts +8 -6
- package/src/scenarios/table-schema-enforcement.test.ts +7 -5
- package/src/scenarios/test-seam-unauthenticated.test.ts +2 -2
- package/src/scenarios/tool-catalog-compact-projection.test.ts +10 -8
- package/src/scenarios/tool-catalog-projection.test.ts +11 -9
- package/src/scenarios/tool-descriptor-shape.test.ts +14 -15
- package/src/scenarios/tool-hooks-authorization-fail-closed.test.ts +3 -3
- package/src/scenarios/tool-hooks-content-free.test.ts +4 -4
- package/src/scenarios/tool-hooks-failure-honesty.test.ts +4 -4
- package/src/scenarios/tool-hooks-rate-limit.test.ts +2 -2
- package/src/scenarios/tool-hooks-secret-redaction.test.ts +2 -2
- package/src/scenarios/tool-hooks-shape.test.ts +3 -3
- package/src/scenarios/tool-session-lifecycle.test.ts +14 -13
- package/src/scenarios/trigger-bridge-delivery.test.ts +14 -14
- package/src/scenarios/trigger-bridge-shape.test.ts +19 -20
- package/src/scenarios/trigger-ingestion.test.ts +19 -17
- package/src/scenarios/trigger-stream-cdc-sources.test.ts +16 -14
- package/src/scenarios/v2-approver-enforced.test.ts +97 -0
- package/src/scenarios/v2-assurance-downgrade-audited.test.ts +75 -0
- package/src/scenarios/v2-bundle-v3-signed.test.ts +96 -0
- package/src/scenarios/v2-capabilities-root-closed.test.ts +63 -0
- package/src/scenarios/v2-capability-record-shape.test.ts +104 -0
- package/src/scenarios/v2-chain-pin-exact.test.ts +118 -0
- package/src/scenarios/v2-coherence-not-in-bundle.test.ts +98 -0
- package/src/scenarios/v2-compensation-read-projection.test.ts +85 -0
- package/src/scenarios/v2-configurable-closed.test.ts +67 -0
- package/src/scenarios/v2-dual-stack-negotiation.test.ts +118 -0
- package/src/scenarios/v2-effect-identity-business-key.test.ts +132 -0
- package/src/scenarios/v2-effect-seam-manifest.test.ts +163 -0
- package/src/scenarios/v2-enum-growth-rule.test.ts +69 -0
- package/src/scenarios/v2-era-key.test.ts +86 -0
- package/src/scenarios/v2-error-registry.test.ts +89 -0
- package/src/scenarios/v2-event-type-closed.test.ts +97 -0
- package/src/scenarios/v2-fork-a-v1-run.test.ts +109 -0
- package/src/scenarios/v2-form-when-reuses-edge-conditions.test.ts +70 -0
- package/src/scenarios/v2-header-scheme.test.ts +67 -0
- package/src/scenarios/v2-id-grammar.test.ts +77 -0
- package/src/scenarios/v2-idempotency-key-grammar.test.ts +65 -0
- package/src/scenarios/v2-interrupt-token-scheme.test.ts +55 -0
- package/src/scenarios/v2-lane-issuer-advertised.test.ts +62 -0
- package/src/scenarios/v2-legacy-profiles-absent.test.ts +78 -0
- package/src/scenarios/v2-manifest-ceiling-refused.test.ts +130 -0
- package/src/scenarios/v2-manifest-hatch-carried.test.ts +120 -0
- package/src/scenarios/v2-min-client-version.test.ts +48 -0
- package/src/scenarios/v2-minimum-version-refused.test.ts +141 -0
- package/src/scenarios/v2-mrtr-rounds-ceiling.test.ts +95 -0
- package/src/scenarios/v2-negotiation-authenticated.test.ts +125 -0
- package/src/scenarios/v2-negotiation-decided-emitted.test.ts +149 -0
- package/src/scenarios/v2-no-transport-advertisement.test.ts +57 -0
- package/src/scenarios/v2-owner-subject-required.test.ts +72 -0
- package/src/scenarios/v2-pack-isolation.test.ts +147 -0
- package/src/scenarios/v2-payload-registry-closed.test.ts +110 -0
- package/src/scenarios/v2-peer-dependency-declared.test.ts +132 -0
- package/src/scenarios/v2-pinned-run-disposition.test.ts +124 -0
- package/src/scenarios/v2-poll-cursor-v2.test.ts +114 -0
- package/src/scenarios/v2-preferred-version-default.test.ts +83 -0
- package/src/scenarios/v2-profiles-derived-only.test.ts +70 -0
- package/src/scenarios/v2-provider-conflict.test.ts +115 -0
- package/src/scenarios/v2-refresh-sla.test.ts +78 -0
- package/src/scenarios/v2-relaxation-recorded.test.ts +134 -0
- package/src/scenarios/v2-revocation-honored.test.ts +66 -0
- package/src/scenarios/v2-subject-link-record.test.ts +102 -0
- package/src/scenarios/v2-unmapped-type-refused.test.ts +95 -0
- package/src/scenarios/v2-v1-events-translated.test.ts +105 -0
- package/src/scenarios/v2-v1-signed-webhook-accepted.test.ts +109 -0
- package/src/scenarios/v2-webhook-durable-delivery.test.ts +238 -0
- package/src/scenarios/v2-well-known-one-resource.test.ts +129 -0
- package/src/scenarios/vector-knn-roundtrip.test.ts +9 -7
- package/src/scenarios/verifier-gating.test.ts +8 -6
- package/src/scenarios/version-fold.test.ts +9 -8
- package/src/scenarios/version-negotiation.test.ts +12 -11
- package/src/scenarios/voice-bargein-no-partial-leak.test.ts +8 -6
- package/src/scenarios/voice-event-payloads-shape.test.ts +10 -11
- package/src/scenarios/voice-interim-not-durable.test.ts +7 -5
- package/src/scenarios/voice-streamref-tenant-bound.test.ts +5 -3
- package/src/scenarios/voice-synthesis-streaming.test.ts +11 -9
- package/src/scenarios/voice-transcription-streaming.test.ts +8 -6
- package/src/scenarios/voice-transcription-unadvertised.test.ts +5 -3
- package/src/scenarios/wasm-pack-abi-version-rejection.test.ts +3 -2
- package/src/scenarios/wasm-pack-invoke-completed.test.ts +3 -2
- package/src/scenarios/wasm-pack-invoke-suspended.test.ts +3 -2
- package/src/scenarios/wasm-pack-load.test.ts +4 -3
- package/src/scenarios/wasm-pack-memory-cap.test.ts +5 -4
- package/src/scenarios/wasm-pack-replay-determinism.test.ts +2 -1
- package/src/scenarios/webhook-negative.test.ts +11 -9
- package/src/scenarios/webhook-receiver-adversarial.test.ts +8 -7
- package/src/scenarios/webhook-sig-algorithm.test.ts +5 -3
- package/src/scenarios/webhook-signed-delivery.test.ts +16 -15
- package/src/scenarios/webhook-tenant-isolation.test.ts +16 -14
- package/src/scenarios/workflow-chain-deferred-parameters.test.ts +28 -30
- package/src/scenarios/workflow-chain-expansion.test.ts +21 -20
- package/src/scenarios/workflow-chain-host-expansion.test.ts +21 -20
- package/src/scenarios/workflow-chain-pack-manifest-validation.test.ts +14 -12
- package/src/scenarios/workflow-chain-pack-signature-verification.test.ts +5 -4
- package/src/scenarios/workflow-chain-unresolvable-typeid.test.ts +9 -8
- package/src/scenarios/workflow-primary-output-annotation.test.ts +7 -6
- package/src/scenarios/workflow-variable-format.test.ts +24 -24
- package/src/scenarios/workload-identity-behavior.test.ts +15 -13
- package/src/scenarios/workload-identity-chain-bounds.test.ts +18 -17
- package/src/scenarios/workspace-behavior.test.ts +19 -18
- package/src/scenarios/workspace-capability-shape.test.ts +3 -2
- package/src/scenarios/workspace-cross-tenant-isolation-blackbox.test.ts +6 -5
- package/src/scenarios/workspace-cross-tenant-isolation.test.ts +7 -6
- package/src/scenarios/x-openwop-form-pack-manifest.test.ts +8 -7
- package/src/setup.ts +44 -6
- package/vitest.config.ts +5 -1
- package/api/.redocly.lint-ignore.yaml +0 -22
- package/api/asyncapi.yaml +0 -834
- package/api/grpc/openwop.proto +0 -251
- package/api/openapi.yaml +0 -2693
- package/api/redocly.yaml +0 -23
- package/schemas/README.md +0 -144
- package/schemas/a2a-task-state.schema.json +0 -78
- package/schemas/a2ui-surface-delta-frame.schema.json +0 -48
- package/schemas/agent-deployment-transition.schema.json +0 -49
- package/schemas/agent-deployment.schema.json +0 -54
- package/schemas/agent-eval-suite.schema.json +0 -140
- package/schemas/agent-inventory-response.schema.json +0 -115
- package/schemas/agent-manifest.schema.json +0 -156
- package/schemas/agent-org-chart.schema.json +0 -82
- package/schemas/agent-ref.schema.json +0 -63
- package/schemas/agent-roster-entry.schema.json +0 -81
- package/schemas/agent-roster-response.schema.json +0 -21
- package/schemas/ai-envelope.schema.json +0 -134
- package/schemas/annotation-create.schema.json +0 -37
- package/schemas/annotation.schema.json +0 -56
- package/schemas/artifact-type-pack-manifest.schema.json +0 -244
- package/schemas/audit-verify-result.schema.json +0 -90
- package/schemas/budget-policy.schema.json +0 -18
- package/schemas/capabilities.schema.json +0 -4160
- package/schemas/certification-bundle-v2.schema.json +0 -118
- package/schemas/channel-presence-payload.schema.json +0 -41
- package/schemas/channel-written-payload.schema.json +0 -55
- package/schemas/chat-card-pack-manifest.schema.json +0 -253
- package/schemas/compact-tool-descriptor.schema.json +0 -51
- package/schemas/compensation-policy.schema.json +0 -64
- package/schemas/conformance-certification-bundle.schema.json +0 -86
- package/schemas/connection-pack-manifest.schema.json +0 -353
- package/schemas/conversation-event.schema.json +0 -168
- package/schemas/conversation-turn.schema.json +0 -117
- package/schemas/core-conformance-mock-agent-config.schema.json +0 -152
- package/schemas/credential-provenance.schema.json +0 -18
- package/schemas/credential-reference.schema.json +0 -21
- package/schemas/debug-bundle.schema.json +0 -196
- package/schemas/dispatch-config.schema.json +0 -112
- package/schemas/envelopes/clarification.request.schema.json +0 -52
- package/schemas/envelopes/error.schema.json +0 -30
- package/schemas/envelopes/media.audio.schema.json +0 -38
- package/schemas/envelopes/media.file.schema.json +0 -37
- package/schemas/envelopes/media.image.schema.json +0 -33
- package/schemas/envelopes/schema.request.schema.json +0 -26
- package/schemas/envelopes/schema.response.schema.json +0 -22
- package/schemas/envelopes/ui.a2ui-surface.schema.json +0 -154
- package/schemas/error-envelope.schema.json +0 -25
- package/schemas/eval-summary.schema.json +0 -92
- package/schemas/export-bundle.schema.json +0 -66
- package/schemas/form-content-pack-manifest.schema.json +0 -243
- package/schemas/frontend-plugin-manifest.schema.json +0 -109
- package/schemas/goal.schema.json +0 -104
- package/schemas/heartbeat-evaluated.schema.json +0 -14
- package/schemas/heartbeat-state-changed.schema.json +0 -14
- package/schemas/localized-content-language-settings.schema.json +0 -26
- package/schemas/localized-content-page-response.schema.json +0 -60
- package/schemas/localized-content-page.schema.json +0 -62
- package/schemas/localized-content-section.schema.json +0 -51
- package/schemas/memory-entry.schema.json +0 -36
- package/schemas/memory-list-options.schema.json +0 -37
- package/schemas/node-pack-manifest.schema.json +0 -654
- package/schemas/orchestrator-decision.schema.json +0 -90
- package/schemas/org-chart-responsibility-view.schema.json +0 -26
- package/schemas/pack-lockfile.schema.json +0 -108
- package/schemas/prompt-kind.schema.json +0 -8
- package/schemas/prompt-pack-manifest.schema.json +0 -117
- package/schemas/prompt-ref.schema.json +0 -40
- package/schemas/prompt-template.schema.json +0 -149
- package/schemas/proposal.schema.json +0 -84
- package/schemas/registry-version-manifest.schema.json +0 -208
- package/schemas/residency.schema.json +0 -16
- package/schemas/run-ancestry-response.schema.json +0 -54
- package/schemas/run-diff-response.schema.json +0 -64
- package/schemas/run-event-payloads.schema.json +0 -4452
- package/schemas/run-event.schema.json +0 -211
- package/schemas/run-options.schema.json +0 -80
- package/schemas/run-orchestrator-decided-event.schema.json +0 -20
- package/schemas/run-snapshot.schema.json +0 -148
- package/schemas/security-advisory.schema.json +0 -109
- package/schemas/self-hosted-runner-dispatch-frame.schema.json +0 -79
- package/schemas/self-hosted-runner-registration.schema.json +0 -53
- package/schemas/self-hosted-runner-result-frame.schema.json +0 -38
- package/schemas/subject.schema.json +0 -56
- package/schemas/suspend-request.schema.json +0 -207
- package/schemas/tool-descriptor.schema.json +0 -63
- package/schemas/trigger-event.schema.json +0 -222
- package/schemas/trigger-subscription-registration.schema.json +0 -67
- package/schemas/trigger-subscription.schema.json +0 -26
- package/schemas/ui-plugin-message.schema.json +0 -90
- package/schemas/workflow-chain-pack-manifest.schema.json +0 -615
- package/schemas/workflow-definition.schema.json +0 -850
- package/schemas/workload-identity.schema.json +0 -79
- package/schemas/workspace-file-create.schema.json +0 -20
- 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,
|
|
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,
|
|
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'),
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
});
|