@openwop/openwop-conformance 1.162.0 → 2.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/cli.js +54 -1
- package/dist/lib/paths.js +25 -3
- package/dist/lib/requirement-ids.js +34 -4
- package/dist/lib/scenario-disposition.js +1 -0
- package/dist/lib/spec-coherence.js +13 -32
- package/dist/spec-artifacts.lock.json +5 -0
- package/package.json +11 -33
- package/requirements.json +20303 -11809
- package/scenario-majors.json +1343 -0
- package/schemas/CORPUS-STAMP.json +138 -6
- package/src/cli.ts +47 -1
- package/src/global-setup.ts +3 -2
- package/src/lib/corpus-stamp.ts +29 -1
- package/src/lib/paths.ts +25 -3
- package/src/lib/requirement-ids.ts +33 -4
- package/src/lib/scenario-disposition.ts +1 -0
- package/src/lib/spec-coherence.ts +15 -32
- 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/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 +4 -2
- 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 -4124
- 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
|
@@ -37,6 +37,7 @@ import { mcpServerMount } from '../lib/mcp-mount.js';
|
|
|
37
37
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
38
38
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
39
39
|
import { getMcpFakeServer } from '../lib/mcp-fake-server.js';
|
|
40
|
+
import { req } from '../lib/requirement-ids.js';
|
|
40
41
|
|
|
41
42
|
export const REQUIRES_HOST_CALLBACK = "the host issues MCP JSON-RPC calls (including the MRTR input_required round trip) to the suite's fake server via /v1/host/sample/mcp/invoke (serverUrl)";
|
|
42
43
|
|
|
@@ -69,23 +70,23 @@ describe.skipIf(!process.env.OPENWOP_BASE_URL)('RFC 0153 §C — mcp-mrtr-roundt
|
|
|
69
70
|
return seamAbsent(`host advertises mcp-2026-07-28 but the invoke seam /v1/host/sample/mcp/invoke answered ${drive.status}`);
|
|
70
71
|
}
|
|
71
72
|
const calls = server.invocations().filter((i) => i.method === 'tools/call');
|
|
72
|
-
expect(calls.length,
|
|
73
|
+
expect(calls.length, req('openwop.it.mcp-mrtr-roundtrip.input-required-gather-retry-with-inputresponses-echoed-requeststate-as-one-logic', 'RFCS/0153 §C', 'the host MUST have called the tool at least once for this leg to mean anything')).toBeGreaterThan(0);
|
|
73
74
|
const mrtr = (drive.json as { mrtr?: { inputRequiredSeen?: boolean; retried?: boolean; requestStateEchoed?: boolean; result?: unknown } }).mrtr;
|
|
74
75
|
if (mrtr === undefined) {
|
|
75
|
-
expect(mrtr,
|
|
76
|
-
return;
|
|
76
|
+
expect(mrtr, req('openwop.it.mcp-mrtr-roundtrip.input-required-gather-retry-with-inputresponses-echoed-requeststate-as-one-logic', 'host-sample-test-seams.md §23', 'the invoke seam SHOULD report `mrtr: { inputRequiredSeen, retried, requestStateEchoed, result }` for tool "needs_input"; until it does this requirement is unobservable and resolves to `blocked`')).toBeDefined();
|
|
77
|
+
return softSkip('blocked', 'precondition not met — `mrtr === undefined` returned early (seam, prior step, or fixture unavailable)');
|
|
77
78
|
}
|
|
78
|
-
expect(mrtr.inputRequiredSeen,
|
|
79
|
-
expect(mrtr.retried,
|
|
79
|
+
expect(mrtr.inputRequiredSeen, req('openwop.it.mcp-mrtr-roundtrip.input-required-gather-retry-with-inputresponses-echoed-requeststate-as-one-logic', 'mcp-integration.md §C.1', 'the host MUST recognise resultType input_required')).toBe(true);
|
|
80
|
+
expect(mrtr.retried, req('openwop.it.mcp-mrtr-roundtrip.input-required-gather-retry-with-inputresponses-echoed-requeststate-as-one-logic', 'mcp-integration.md §C.1', 'the host MUST retry the ORIGINAL request with inputResponses (a live callback is not the current profile)')).toBe(true);
|
|
80
81
|
// Cross-check from the wire: two tools/call, second carries requestState identical to what the server issued.
|
|
81
|
-
expect(calls.length,
|
|
82
|
+
expect(calls.length, req('openwop.it.mcp-mrtr-roundtrip.input-required-gather-retry-with-inputresponses-echoed-requeststate-as-one-logic', 'mcp-integration.md §C.1', 'initial + retry = two independent JSON-RPC requests on the wire')).toBeGreaterThanOrEqual(2);
|
|
82
83
|
const retry = calls[calls.length - 1]!.params as { requestState?: string; inputResponses?: Record<string, unknown> };
|
|
83
|
-
expect(typeof retry.requestState,
|
|
84
|
-
expect(retry.requestState?.startsWith('mrtr:needs_input:'), 'the echoed requestState is the value the server issued, byte-exact').toBe(true);
|
|
85
|
-
expect(retry.inputResponses?.['who'],
|
|
84
|
+
expect(typeof retry.requestState, req('openwop.it.mcp-mrtr-roundtrip.input-required-gather-retry-with-inputresponses-echoed-requeststate-as-one-logic', 'mcp-integration.md §C.1', 'requestState MUST be echoed exactly on the retry')).toBe('string');
|
|
85
|
+
expect(retry.requestState?.startsWith('mrtr:needs_input:'), req('openwop.it.mcp-mrtr-roundtrip.input-required-gather-retry-with-inputresponses-echoed-requeststate-as-one-logic', 'mcp-integration.md §C.1', 'the echoed requestState is the value the server issued, byte-exact')).toBe(true);
|
|
86
|
+
expect(retry.inputResponses?.['who'], req('openwop.it.mcp-mrtr-roundtrip.input-required-gather-retry-with-inputresponses-echoed-requeststate-as-one-logic', 'mcp-integration.md §C.1', 'inputResponses MUST answer every requested key')).toBeDefined();
|
|
86
87
|
expect(mrtr.requestStateEchoed).toBe(true);
|
|
87
88
|
for (const c of calls) {
|
|
88
|
-
expect(c.revision,
|
|
89
|
+
expect(c.revision, req('openwop.it.mcp-mrtr-roundtrip.input-required-gather-retry-with-inputresponses-echoed-requeststate-as-one-logic', 'mcp-integration.md §B', 'both calls MUST carry the current revision')).toBe('2026-07-28');
|
|
89
90
|
}
|
|
90
91
|
});
|
|
91
92
|
});
|
|
@@ -119,23 +120,23 @@ describe.skipIf(!process.env.OPENWOP_BASE_URL)('RFC 0153 §C — mcp-mrtr-roundt
|
|
|
119
120
|
if (reg.status === 404 || reg.status === 403) return seamAbsent(`host advertises mcp-2026-07-28 with a server mount but the sample-workflow seam /v1/host/sample/workflows answered ${reg.status}`);
|
|
120
121
|
// A 4xx here is a SUITE defect (the fixture this leg posts is malformed),
|
|
121
122
|
// not seam absence and not a host finding — fail loudly at the source.
|
|
122
|
-
expect(reg.status,
|
|
123
|
+
expect(reg.status, req('openwop.it.mcp-mrtr-roundtrip.a-suspending-tool-answers-input-required-with-elicitation-requeststate-the-retry', 'host-sample-test-seams.md §"sample workflows"', `the registration fixture MUST be accepted; a 4xx means the fixture this leg posts is malformed (suite defect): ${JSON.stringify(reg.json).slice(0, 200)}`)).toBeLessThan(400);
|
|
123
124
|
const hdr = { 'MCP-Protocol-Version': '2026-07-28', 'Mcp-Method': 'tools/call', 'Mcp-Name': TOOL };
|
|
124
125
|
const first = await driver.post(await mcpServerMount(), { jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: TOOL, arguments: {}, _meta: { [META_V]: '2026-07-28', [META_C]: { elicitation: {} } } } }, { headers: hdr });
|
|
125
126
|
if (first.status === 404) return seamAbsent('host advertises an MCP server mount but /v1/host/sample/mcp answered 404');
|
|
126
127
|
const r1 = first.json as { result?: { resultType?: string; inputRequests?: Record<string, { method?: string }>; requestState?: string }; error?: { code: number } };
|
|
127
|
-
expect(r1.error,
|
|
128
|
-
expect(r1.result?.resultType,
|
|
128
|
+
expect(r1.error, req('openwop.it.mcp-mrtr-roundtrip.a-suspending-tool-answers-input-required-with-elicitation-requeststate-the-retry', 'mcp-integration.md §C.2', `tools/call MUST NOT error: ${JSON.stringify(r1.error)}`)).toBeUndefined();
|
|
129
|
+
expect(r1.result?.resultType, req('openwop.it.mcp-mrtr-roundtrip.a-suspending-tool-answers-input-required-with-elicitation-requeststate-the-retry', 'mcp-integration.md §C.2', 'a run reaching waiting-input MUST answer the in-flight tools/call with resultType input_required (not a live callback)')).toBe('input_required');
|
|
129
130
|
const key = Object.keys(r1.result?.inputRequests ?? {})[0];
|
|
130
|
-
expect(key,
|
|
131
|
+
expect(key, req('openwop.it.mcp-mrtr-roundtrip.a-suspending-tool-answers-input-required-with-elicitation-requeststate-the-retry', 'mcp-integration.md §C.2', 'inputRequests MUST carry the elicitation')).toBeDefined();
|
|
131
132
|
expect(r1.result?.inputRequests?.[key!]?.method).toBe('elicitation/create');
|
|
132
|
-
expect(typeof r1.result?.requestState,
|
|
133
|
+
expect(typeof r1.result?.requestState, req('openwop.it.mcp-mrtr-roundtrip.a-suspending-tool-answers-input-required-with-elicitation-requeststate-the-retry', 'mcp-integration.md §C.2', 'requestState MUST be present (opaque, integrity-protected, bound to principal/TTL/request/runId/interrupt token)')).toBe('string');
|
|
133
134
|
const retry = await driver.post(await mcpServerMount(), { jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: TOOL, arguments: {}, requestState: r1.result!.requestState, inputResponses: { [key!]: { action: 'accept', content: { name: 'Ada' } } }, _meta: { [META_V]: '2026-07-28', [META_C]: { elicitation: {} } } } }, { headers: hdr });
|
|
134
135
|
const r2 = retry.json as { result?: { resultType?: string }; error?: { code: number } };
|
|
135
136
|
expect(r2.error).toBeUndefined();
|
|
136
|
-
expect(['complete', 'input_required'],
|
|
137
|
+
expect(['complete', 'input_required'], req('openwop.it.mcp-mrtr-roundtrip.a-suspending-tool-answers-input-required-with-elicitation-requeststate-the-retry', 'mcp-integration.md §C.2', 'the retry MUST resolve the interrupt (complete) or ask for the next pending input')).toContain(r2.result?.resultType);
|
|
137
138
|
const forged = await driver.post(await mcpServerMount(), { jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name: TOOL, arguments: {}, requestState: 'forged', inputResponses: { [key!]: { action: 'accept', content: { name: 'Eve' } } }, _meta: { [META_V]: '2026-07-28', [META_C]: { elicitation: {} } } } }, { headers: hdr });
|
|
138
139
|
const r3 = forged.json as { result?: { resultType?: string }; error?: { code: number } };
|
|
139
|
-
expect(r3.error !== undefined || forged.status >= 400,
|
|
140
|
+
expect(r3.error !== undefined || forged.status >= 400, req('openwop.it.mcp-mrtr-roundtrip.a-suspending-tool-answers-input-required-with-elicitation-requeststate-the-retry', 'mcp-integration.md §C.2', 'a requestState that fails integrity verification MUST be refused (upstream: attacker-controlled input)')).toBe(true);
|
|
140
141
|
});
|
|
141
142
|
});
|
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
import { describe, it, expect } from 'vitest';
|
|
15
15
|
import { driver } from '../lib/driver.js';
|
|
16
16
|
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
17
|
-
import { seamAbsent } from '../lib/soft-skip.js';
|
|
17
|
+
import { seamAbsent, softSkip } from '../lib/soft-skip.js';
|
|
18
18
|
import { mcpServerMount } from '../lib/mcp-mount.js';
|
|
19
|
+
import { req } from '../lib/requirement-ids.js';
|
|
19
20
|
|
|
20
21
|
interface DiscoveryDoc {
|
|
21
22
|
capabilities?: Record<string, unknown>;
|
|
@@ -32,9 +33,9 @@ async function readCap(): Promise<Record<string, unknown> | null> {
|
|
|
32
33
|
|
|
33
34
|
async function rpc(method: string, params?: Record<string, unknown>) {
|
|
34
35
|
const id = Math.floor(Math.random() * 1e6);
|
|
35
|
-
const
|
|
36
|
-
if (params !== undefined)
|
|
37
|
-
const res = await driver.post(await mcpServerMount(),
|
|
36
|
+
const reqBody: Record<string, unknown> = { jsonrpc: '2.0', id, method };
|
|
37
|
+
if (params !== undefined) reqBody.params = params;
|
|
38
|
+
const res = await driver.post(await mcpServerMount(), reqBody);
|
|
38
39
|
return { status: res.status, body: res.json as { result?: unknown; error?: { code: number; message: string } } };
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -51,11 +52,11 @@ async function registerElicitationHandlerWorkflow(): Promise<boolean> {
|
|
|
51
52
|
describe('mcp-server-elicitation-bridge: advertisement shape (RFC 0020)', () => {
|
|
52
53
|
it('elicitationBridge is a boolean when serverMount.supported', async () => {
|
|
53
54
|
const cap = await readCap();
|
|
54
|
-
if (!cap || cap.supported !== true) return;
|
|
55
|
-
if (cap.elicitationBridge === undefined) return;
|
|
55
|
+
if (!cap || cap.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!cap || cap.supported !== true` returned early');
|
|
56
|
+
if (cap.elicitationBridge === undefined) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap.elicitationBridge === undefined` returned early');
|
|
56
57
|
expect(
|
|
57
58
|
typeof cap.elicitationBridge,
|
|
58
|
-
|
|
59
|
+
req('openwop.it.mcp-server-elicitation-bridge.elicitationbridge-is-a-boolean-when-servermount-supported', 'RFC 0020 §B', 'mcp.serverMount.elicitationBridge MUST be boolean when present'),
|
|
59
60
|
).toBe('boolean');
|
|
60
61
|
});
|
|
61
62
|
});
|
|
@@ -63,8 +64,8 @@ describe('mcp-server-elicitation-bridge: advertisement shape (RFC 0020)', () =>
|
|
|
63
64
|
describe('mcp-server-elicitation-bridge: behavioral (RFC 0020 §A point 3)', () => {
|
|
64
65
|
it('elicitation/create bridges into a handle-elicitation workflow', async () => {
|
|
65
66
|
const cap = await readCap();
|
|
66
|
-
if (!cap || cap.supported !== true || cap.elicitationBridge !== true) return;
|
|
67
|
-
if (!(await registerElicitationHandlerWorkflow())) return;
|
|
67
|
+
if (!cap || cap.supported !== true || cap.elicitationBridge !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!cap || cap.supported !== true || cap.elicitationBridge !== true` returned early');
|
|
68
|
+
if (!(await registerElicitationHandlerWorkflow())) return softSkip('blocked', 'precondition not met — `!(await registerElicitationHandlerWorkflow())` returned early (seam, prior step, or fixture unavailable)');
|
|
68
69
|
|
|
69
70
|
const r = await rpc('elicitation/create', {
|
|
70
71
|
message: 'What is your name?',
|
|
@@ -75,11 +76,11 @@ describe('mcp-server-elicitation-bridge: behavioral (RFC 0020 §A point 3)', ()
|
|
|
75
76
|
},
|
|
76
77
|
});
|
|
77
78
|
if (r.status === 404) return seamAbsent(`host advertises an MCP server mount but the mount (capabilities.mcp.serverUrls[0], else /v1/host/sample/mcp) answered ${r.status} — RFC 0153 §B is unobservable at the path the host itself advertised`);
|
|
78
|
-
expect(r.status, 'JSON-RPC envelope MUST 200').toBe(200);
|
|
79
|
+
expect(r.status, req('openwop.it.mcp-server-elicitation-bridge.elicitation-create-bridges-into-a-handle-elicitation-workflow', 'RFC 0020 §A point 3', 'JSON-RPC envelope MUST 200')).toBe(200);
|
|
79
80
|
const dispatched = !!r.body.result || (!!r.body.error && r.body.error.code !== -32601);
|
|
80
81
|
expect(
|
|
81
82
|
dispatched,
|
|
82
|
-
|
|
83
|
+
req('openwop.it.mcp-server-elicitation-bridge.elicitation-create-bridges-into-a-handle-elicitation-workflow',
|
|
83
84
|
'RFC 0020 §A point 3',
|
|
84
85
|
'elicitation/create MUST dispatch to handle-elicitation workflow (not return method_not_found)',
|
|
85
86
|
),
|
|
@@ -88,7 +89,7 @@ describe('mcp-server-elicitation-bridge: behavioral (RFC 0020 §A point 3)', ()
|
|
|
88
89
|
const result = r.body.result as { action?: string };
|
|
89
90
|
expect(
|
|
90
91
|
['pending', 'accept', 'decline', 'cancel'].includes(result.action ?? ''),
|
|
91
|
-
'elicitation response action MUST be one of {pending,accept,decline,cancel}',
|
|
92
|
+
req('openwop.it.mcp-server-elicitation-bridge.elicitation-create-bridges-into-a-handle-elicitation-workflow', 'RFC 0020 §A point 3', 'elicitation response action MUST be one of {pending,accept,decline,cancel}'),
|
|
92
93
|
).toBe(true);
|
|
93
94
|
}
|
|
94
95
|
});
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
import { describe, it, expect } from 'vitest';
|
|
10
10
|
import { driver } from '../lib/driver.js';
|
|
11
11
|
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
12
|
-
import { seamAbsent } from '../lib/soft-skip.js';
|
|
12
|
+
import { seamAbsent, softSkip } from '../lib/soft-skip.js';
|
|
13
13
|
import { mcpServerMount } from '../lib/mcp-mount.js';
|
|
14
|
+
import { req } from '../lib/requirement-ids.js';
|
|
14
15
|
|
|
15
16
|
interface DiscoveryDoc {
|
|
16
17
|
capabilities?: Record<string, unknown>;
|
|
@@ -27,9 +28,9 @@ async function readCap(): Promise<Record<string, unknown> | null> {
|
|
|
27
28
|
|
|
28
29
|
async function rpc(method: string, params?: Record<string, unknown>) {
|
|
29
30
|
const id = Math.floor(Math.random() * 1e6);
|
|
30
|
-
const
|
|
31
|
-
if (params !== undefined)
|
|
32
|
-
const res = await driver.post(await mcpServerMount(),
|
|
31
|
+
const reqBody: Record<string, unknown> = { jsonrpc: '2.0', id, method };
|
|
32
|
+
if (params !== undefined) reqBody.params = params;
|
|
33
|
+
const res = await driver.post(await mcpServerMount(), reqBody);
|
|
33
34
|
return { status: res.status, body: res.json as { result?: unknown; error?: { code: number; message: string } } };
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -56,28 +57,28 @@ async function registerPromptWorkflow(): Promise<boolean> {
|
|
|
56
57
|
describe('mcp-server-prompt-roundtrip: advertisement shape (RFC 0020)', () => {
|
|
57
58
|
it('capabilities.mcp.serverMount is either absent or a well-formed object', async () => {
|
|
58
59
|
const cap = await readCap();
|
|
59
|
-
if (cap === null) return;
|
|
60
|
-
expect(typeof cap.supported).toBe('boolean');
|
|
60
|
+
if (cap === null) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap === null` returned early');
|
|
61
|
+
expect(typeof cap.supported, req('openwop.it.mcp-server-prompt-roundtrip.capabilities-mcp-servermount-is-either-absent-or-a-well-formed-object', 'RFC 0020 §A', 'capabilities.mcp.serverMount is either absent or a well-formed object')).toBe('boolean');
|
|
61
62
|
});
|
|
62
63
|
});
|
|
63
64
|
|
|
64
65
|
describe('mcp-server-prompt-roundtrip: behavioral (RFC 0020)', () => {
|
|
65
66
|
it('prompts/list returns the exposed prompt and prompts/get returns messages', async () => {
|
|
66
67
|
const cap = await readCap();
|
|
67
|
-
if (!cap || cap.supported !== true) return;
|
|
68
|
-
if (!(await registerPromptWorkflow())) return;
|
|
68
|
+
if (!cap || cap.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!cap || cap.supported !== true` returned early');
|
|
69
|
+
if (!(await registerPromptWorkflow())) return softSkip('blocked', 'precondition not met — `!(await registerPromptWorkflow())` returned early (seam, prior step, or fixture unavailable)');
|
|
69
70
|
|
|
70
71
|
const list = await rpc('prompts/list');
|
|
71
72
|
if (list.status === 404) return seamAbsent(`host advertises an MCP server mount but the mount (capabilities.mcp.serverUrls[0], else /v1/host/sample/mcp) answered ${list.status} — RFC 0153 §B is unobservable at the path the host itself advertised`);
|
|
72
73
|
const prompts = (list.body.result as { prompts?: Array<{ name: string }> } | undefined)?.prompts ?? [];
|
|
73
74
|
expect(
|
|
74
75
|
prompts.find((p) => p.name === PROMPT_NAME),
|
|
75
|
-
|
|
76
|
+
req('openwop.it.mcp-server-prompt-roundtrip.prompts-list-returns-the-exposed-prompt-and-prompts-get-returns-messages', 'RFC 0020 §A', 'prompts/list MUST include exposed prompts'),
|
|
76
77
|
).toBeDefined();
|
|
77
78
|
|
|
78
79
|
const get = await rpc('prompts/get', { name: PROMPT_NAME, arguments: { topic: 'openwop' } });
|
|
79
80
|
expect(get.status).toBe(200);
|
|
80
81
|
const messages = (get.body.result as { messages?: Array<{ role: string }> } | undefined)?.messages;
|
|
81
|
-
expect(Array.isArray(messages), 'prompts/get MUST return messages[]').toBe(true);
|
|
82
|
+
expect(Array.isArray(messages), req('openwop.it.mcp-server-prompt-roundtrip.prompts-list-returns-the-exposed-prompt-and-prompts-get-returns-messages', 'RFC 0020 §A', 'prompts/get MUST return messages[]')).toBe(true);
|
|
82
83
|
});
|
|
83
84
|
});
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
import { describe, it, expect } from 'vitest';
|
|
12
12
|
import { driver } from '../lib/driver.js';
|
|
13
13
|
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
14
|
-
import { seamAbsent } from '../lib/soft-skip.js';
|
|
14
|
+
import { seamAbsent, softSkip } from '../lib/soft-skip.js';
|
|
15
15
|
import { mcpServerMount } from '../lib/mcp-mount.js';
|
|
16
|
+
import { req } from '../lib/requirement-ids.js';
|
|
16
17
|
|
|
17
18
|
interface DiscoveryDoc {
|
|
18
19
|
capabilities?: Record<string, unknown>;
|
|
@@ -29,9 +30,9 @@ async function readCap(): Promise<Record<string, unknown> | null> {
|
|
|
29
30
|
|
|
30
31
|
async function rpc(method: string, params?: Record<string, unknown>) {
|
|
31
32
|
const id = Math.floor(Math.random() * 1e6);
|
|
32
|
-
const
|
|
33
|
-
if (params !== undefined)
|
|
34
|
-
const res = await driver.post(await mcpServerMount(),
|
|
33
|
+
const reqBody: Record<string, unknown> = { jsonrpc: '2.0', id, method };
|
|
34
|
+
if (params !== undefined) reqBody.params = params;
|
|
35
|
+
const res = await driver.post(await mcpServerMount(), reqBody);
|
|
35
36
|
return { status: res.status, body: res.json as { result?: unknown; error?: { code: number; message: string } } };
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -58,28 +59,28 @@ async function registerResourceWorkflow(): Promise<boolean> {
|
|
|
58
59
|
describe('mcp-server-resource-roundtrip: advertisement shape (RFC 0020)', () => {
|
|
59
60
|
it('capabilities.mcp.serverMount is either absent or a well-formed object', async () => {
|
|
60
61
|
const cap = await readCap();
|
|
61
|
-
if (cap === null) return;
|
|
62
|
-
expect(typeof cap.supported, 'mcp.serverMount.supported MUST be boolean').toBe('boolean');
|
|
62
|
+
if (cap === null) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap === null` returned early');
|
|
63
|
+
expect(typeof cap.supported, req('openwop.it.mcp-server-resource-roundtrip.capabilities-mcp-servermount-is-either-absent-or-a-well-formed-object', 'RFC 0020 §A', 'mcp.serverMount.supported MUST be boolean')).toBe('boolean');
|
|
63
64
|
});
|
|
64
65
|
});
|
|
65
66
|
|
|
66
67
|
describe('mcp-server-resource-roundtrip: behavioral (RFC 0020)', () => {
|
|
67
68
|
it('resources/list returns the exposed resource and resources/read returns content', async () => {
|
|
68
69
|
const cap = await readCap();
|
|
69
|
-
if (!cap || cap.supported !== true) return;
|
|
70
|
-
if (!(await registerResourceWorkflow())) return;
|
|
70
|
+
if (!cap || cap.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!cap || cap.supported !== true` returned early');
|
|
71
|
+
if (!(await registerResourceWorkflow())) return softSkip('blocked', 'precondition not met — `!(await registerResourceWorkflow())` returned early (seam, prior step, or fixture unavailable)');
|
|
71
72
|
|
|
72
73
|
const list = await rpc('resources/list');
|
|
73
74
|
if (list.status === 404) return seamAbsent(`host advertises an MCP server mount but the mount (capabilities.mcp.serverUrls[0], else /v1/host/sample/mcp) answered ${list.status} — RFC 0153 §B is unobservable at the path the host itself advertised`);
|
|
74
75
|
const resources = (list.body.result as { resources?: Array<{ uri: string }> } | undefined)?.resources ?? [];
|
|
75
76
|
expect(
|
|
76
77
|
resources.find((r) => r.uri === RESOURCE_URI),
|
|
77
|
-
|
|
78
|
+
req('openwop.it.mcp-server-resource-roundtrip.resources-list-returns-the-exposed-resource-and-resources-read-returns-content', 'RFC 0020 §A', 'resources/list MUST include exposed resources'),
|
|
78
79
|
).toBeDefined();
|
|
79
80
|
|
|
80
81
|
const read = await rpc('resources/read', { uri: RESOURCE_URI });
|
|
81
82
|
expect(read.status).toBe(200);
|
|
82
83
|
const contents = (read.body.result as { contents?: Array<{ uri: string }> } | undefined)?.contents;
|
|
83
|
-
expect(Array.isArray(contents), 'resources/read MUST return contents[]').toBe(true);
|
|
84
|
+
expect(Array.isArray(contents), req('openwop.it.mcp-server-resource-roundtrip.resources-list-returns-the-exposed-resource-and-resources-read-returns-content', 'RFC 0020 §A', 'resources/read MUST return contents[]')).toBe(true);
|
|
84
85
|
});
|
|
85
86
|
});
|
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
import { describe, it, expect } from 'vitest';
|
|
18
18
|
import { driver } from '../lib/driver.js';
|
|
19
19
|
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
20
|
-
import { seamAbsent } from '../lib/soft-skip.js';
|
|
20
|
+
import { seamAbsent, softSkip } from '../lib/soft-skip.js';
|
|
21
21
|
import { mcpServerMount } from '../lib/mcp-mount.js';
|
|
22
|
+
import { req } from '../lib/requirement-ids.js';
|
|
22
23
|
|
|
23
24
|
interface DiscoveryDoc {
|
|
24
25
|
capabilities?: Record<string, unknown>;
|
|
@@ -35,9 +36,9 @@ async function readCap(): Promise<Record<string, unknown> | null> {
|
|
|
35
36
|
|
|
36
37
|
async function rpc(method: string, params?: Record<string, unknown>) {
|
|
37
38
|
const id = Math.floor(Math.random() * 1e6);
|
|
38
|
-
const
|
|
39
|
-
if (params !== undefined)
|
|
40
|
-
const res = await driver.post(await mcpServerMount(),
|
|
39
|
+
const reqBody: Record<string, unknown> = { jsonrpc: '2.0', id, method };
|
|
40
|
+
if (params !== undefined) reqBody.params = params;
|
|
41
|
+
const res = await driver.post(await mcpServerMount(), reqBody);
|
|
41
42
|
return { status: res.status, body: res.json as { result?: unknown; error?: { code: number; message: string } } };
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -54,11 +55,11 @@ async function registerSamplingHandlerWorkflow(): Promise<boolean> {
|
|
|
54
55
|
describe('mcp-server-sampling-bridge: advertisement shape (RFC 0020)', () => {
|
|
55
56
|
it('samplingBridge is a boolean when serverMount.supported', async () => {
|
|
56
57
|
const cap = await readCap();
|
|
57
|
-
if (!cap || cap.supported !== true) return;
|
|
58
|
-
if (cap.samplingBridge === undefined) return;
|
|
58
|
+
if (!cap || cap.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!cap || cap.supported !== true` returned early');
|
|
59
|
+
if (cap.samplingBridge === undefined) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap.samplingBridge === undefined` returned early');
|
|
59
60
|
expect(
|
|
60
61
|
typeof cap.samplingBridge,
|
|
61
|
-
|
|
62
|
+
req('openwop.it.mcp-server-sampling-bridge.samplingbridge-is-a-boolean-when-servermount-supported', 'RFC 0020 §B', 'mcp.serverMount.samplingBridge MUST be boolean when present'),
|
|
62
63
|
).toBe('boolean');
|
|
63
64
|
});
|
|
64
65
|
});
|
|
@@ -66,19 +67,19 @@ describe('mcp-server-sampling-bridge: advertisement shape (RFC 0020)', () => {
|
|
|
66
67
|
describe('mcp-server-sampling-bridge: behavioral (RFC 0020 §A point 3)', () => {
|
|
67
68
|
it('sampling/createMessage bridges into a handle-sampling workflow', async () => {
|
|
68
69
|
const cap = await readCap();
|
|
69
|
-
if (!cap || cap.supported !== true || cap.samplingBridge !== true) return;
|
|
70
|
-
if (!(await registerSamplingHandlerWorkflow())) return;
|
|
70
|
+
if (!cap || cap.supported !== true || cap.samplingBridge !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!cap || cap.supported !== true || cap.samplingBridge !== true` returned early');
|
|
71
|
+
if (!(await registerSamplingHandlerWorkflow())) return softSkip('blocked', 'precondition not met — `!(await registerSamplingHandlerWorkflow())` returned early (seam, prior step, or fixture unavailable)');
|
|
71
72
|
|
|
72
73
|
const r = await rpc('sampling/createMessage', {
|
|
73
74
|
messages: [{ role: 'user', content: { type: 'text', text: 'ping' } }],
|
|
74
75
|
maxTokens: 16,
|
|
75
76
|
});
|
|
76
77
|
if (r.status === 404) return seamAbsent(`host advertises an MCP server mount but the mount (capabilities.mcp.serverUrls[0], else /v1/host/sample/mcp) answered ${r.status} — RFC 0153 §B is unobservable at the path the host itself advertised`);
|
|
77
|
-
expect(r.status, 'JSON-RPC envelope MUST 200').toBe(200);
|
|
78
|
+
expect(r.status, req('openwop.it.mcp-server-sampling-bridge.sampling-createmessage-bridges-into-a-handle-sampling-workflow', 'RFC 0020 §A point 3', 'JSON-RPC envelope MUST 200')).toBe(200);
|
|
78
79
|
const dispatched = !!r.body.result || (!!r.body.error && r.body.error.code !== -32601);
|
|
79
80
|
expect(
|
|
80
81
|
dispatched,
|
|
81
|
-
|
|
82
|
+
req('openwop.it.mcp-server-sampling-bridge.sampling-createmessage-bridges-into-a-handle-sampling-workflow',
|
|
82
83
|
'RFC 0020 §A point 3',
|
|
83
84
|
'sampling/createMessage MUST dispatch to handle-sampling workflow (not return method_not_found)',
|
|
84
85
|
),
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
import { describe, it, expect } from 'vitest';
|
|
16
16
|
import { driver } from '../lib/driver.js';
|
|
17
17
|
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
18
|
-
import { seamAbsent } from '../lib/soft-skip.js';
|
|
18
|
+
import { seamAbsent, softSkip } from '../lib/soft-skip.js';
|
|
19
19
|
import { mcpServerMount } from '../lib/mcp-mount.js';
|
|
20
|
+
import { req } from '../lib/requirement-ids.js';
|
|
20
21
|
|
|
21
22
|
interface DiscoveryDoc {
|
|
22
23
|
capabilities?: Record<string, unknown>;
|
|
@@ -33,9 +34,9 @@ async function readCap(): Promise<Record<string, unknown> | null> {
|
|
|
33
34
|
|
|
34
35
|
async function rpc(method: string, params?: Record<string, unknown>): Promise<{ status: number; body: { result?: unknown; error?: { code: number; message: string } } }> {
|
|
35
36
|
const id = Math.floor(Math.random() * 1e6);
|
|
36
|
-
const
|
|
37
|
-
if (params !== undefined)
|
|
38
|
-
const res = await driver.post(await mcpServerMount(),
|
|
37
|
+
const reqBody: Record<string, unknown> = { jsonrpc: '2.0', id, method };
|
|
38
|
+
if (params !== undefined) reqBody.params = params;
|
|
39
|
+
const res = await driver.post(await mcpServerMount(), reqBody);
|
|
39
40
|
return { status: res.status, body: res.json as { result?: unknown; error?: { code: number; message: string } } };
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -67,10 +68,10 @@ async function registerToolWorkflow(): Promise<boolean> {
|
|
|
67
68
|
describe('mcp-server-tool-roundtrip: advertisement shape (RFC 0020)', () => {
|
|
68
69
|
it('capabilities.mcp.serverMount is either absent or a well-formed object', async () => {
|
|
69
70
|
const cap = await readCap();
|
|
70
|
-
if (cap === null) return;
|
|
71
|
+
if (cap === null) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap === null` returned early');
|
|
71
72
|
expect(
|
|
72
73
|
typeof cap.supported,
|
|
73
|
-
|
|
74
|
+
req('openwop.it.mcp-server-tool-roundtrip.capabilities-mcp-servermount-is-either-absent-or-a-well-formed-object',
|
|
74
75
|
'capabilities.schema.json §mcp.serverMount',
|
|
75
76
|
'capabilities.mcp.serverMount.supported MUST be a boolean when present',
|
|
76
77
|
),
|
|
@@ -81,30 +82,30 @@ describe('mcp-server-tool-roundtrip: advertisement shape (RFC 0020)', () => {
|
|
|
81
82
|
describe('mcp-server-tool-roundtrip: behavioral (RFC 0020 §A points 1-2)', () => {
|
|
82
83
|
it('tools/list returns the exposed workflow + tools/call returns a CallToolResult', async () => {
|
|
83
84
|
const cap = await readCap();
|
|
84
|
-
if (!cap || cap.supported !== true) return;
|
|
85
|
+
if (!cap || cap.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!cap || cap.supported !== true` returned early');
|
|
85
86
|
const registered = await registerToolWorkflow();
|
|
86
|
-
if (!registered) return; // host doesn't expose workflow registration
|
|
87
|
+
if (!registered) return softSkip('blocked', 'precondition not met — `!registered` returned early (host doesn\'t expose workflow registration) (seam, prior step, or fixture unavailable)'); // host doesn't expose workflow registration
|
|
87
88
|
|
|
88
89
|
const list = await rpc('tools/list');
|
|
89
90
|
if (list.status === 404) return seamAbsent(`host advertises an MCP server mount but the mount (capabilities.mcp.serverUrls[0], else /v1/host/sample/mcp) answered ${list.status} — RFC 0153 §B is unobservable at the path the host itself advertised`);
|
|
90
|
-
expect(list.status, 'tools/list MUST 200').toBe(200);
|
|
91
|
+
expect(list.status, req('openwop.it.mcp-server-tool-roundtrip.tools-list-returns-the-exposed-workflow-tools-call-returns-a-calltoolresult', 'RFC 0020 §A point 2', 'tools/list MUST 200')).toBe(200);
|
|
91
92
|
const tools = (list.body.result as { tools?: Array<{ name: string }> } | undefined)?.tools ?? [];
|
|
92
93
|
const found = tools.find((t) => t.name === TEST_TOOL_NAME);
|
|
93
94
|
expect(
|
|
94
95
|
found,
|
|
95
|
-
|
|
96
|
+
req('openwop.it.mcp-server-tool-roundtrip.tools-list-returns-the-exposed-workflow-tools-call-returns-a-calltoolresult',
|
|
96
97
|
'RFC 0020 §A point 2',
|
|
97
98
|
'tools/list MUST include workflows exposed via core.openwop.mcp.expose-tool',
|
|
98
99
|
),
|
|
99
100
|
).toBeDefined();
|
|
100
101
|
|
|
101
102
|
const call = await rpc('tools/call', { name: TEST_TOOL_NAME, arguments: { text: 'hello' } });
|
|
102
|
-
expect(call.status, 'tools/call MUST 200').toBe(200);
|
|
103
|
+
expect(call.status, req('openwop.it.mcp-server-tool-roundtrip.tools-list-returns-the-exposed-workflow-tools-call-returns-a-calltoolresult', 'RFC 0020 §A point 2', 'tools/call MUST 200')).toBe(200);
|
|
103
104
|
const result = call.body.result as { content?: Array<{ type: string }>; isError?: boolean } | undefined;
|
|
104
105
|
expect(
|
|
105
106
|
Array.isArray(result?.content),
|
|
106
|
-
|
|
107
|
+
req('openwop.it.mcp-server-tool-roundtrip.tools-list-returns-the-exposed-workflow-tools-call-returns-a-calltoolresult', 'RFC 0020 §C', 'CallToolResult MUST contain content[]'),
|
|
107
108
|
).toBe(true);
|
|
108
|
-
expect(typeof result?.isError, 'CallToolResult.isError MUST be boolean').toBe('boolean');
|
|
109
|
+
expect(typeof result?.isError, req('openwop.it.mcp-server-tool-roundtrip.tools-list-returns-the-exposed-workflow-tools-call-returns-a-calltoolresult', 'RFC 0020 §C', 'CallToolResult.isError MUST be boolean')).toBe('boolean');
|
|
109
110
|
});
|
|
110
111
|
});
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
import { describe, it, expect } from 'vitest';
|
|
14
14
|
import { driver } from '../lib/driver.js';
|
|
15
15
|
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
16
|
-
import { seamAbsent } from '../lib/soft-skip.js';
|
|
16
|
+
import { seamAbsent, softSkip } from '../lib/soft-skip.js';
|
|
17
17
|
import { mcpServerMount } from '../lib/mcp-mount.js';
|
|
18
|
+
import { req } from '../lib/requirement-ids.js';
|
|
18
19
|
|
|
19
20
|
interface DiscoveryDoc {
|
|
20
21
|
capabilities?: Record<string, unknown>;
|
|
@@ -31,9 +32,9 @@ async function readCap(): Promise<Record<string, unknown> | null> {
|
|
|
31
32
|
|
|
32
33
|
async function rpc(method: string, params?: Record<string, unknown>) {
|
|
33
34
|
const id = Math.floor(Math.random() * 1e6);
|
|
34
|
-
const
|
|
35
|
-
if (params !== undefined)
|
|
36
|
-
const res = await driver.post(await mcpServerMount(),
|
|
35
|
+
const reqBody: Record<string, unknown> = { jsonrpc: '2.0', id, method };
|
|
36
|
+
if (params !== undefined) reqBody.params = params;
|
|
37
|
+
const res = await driver.post(await mcpServerMount(), reqBody);
|
|
37
38
|
return { status: res.status, body: res.json as { result?: unknown; error?: { code: number; message: string; data?: unknown } } };
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -65,36 +66,36 @@ async function registerStrictWorkflow(): Promise<boolean> {
|
|
|
65
66
|
describe('mcp-server-untrusted-args: advertisement shape (RFC 0020)', () => {
|
|
66
67
|
it('capabilities.mcp.serverMount is well-formed when present', async () => {
|
|
67
68
|
const cap = await readCap();
|
|
68
|
-
if (cap === null) return;
|
|
69
|
-
expect(typeof cap.supported).toBe('boolean');
|
|
69
|
+
if (cap === null) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap === null` returned early');
|
|
70
|
+
expect(typeof cap.supported, req('openwop.it.mcp-server-untrusted-args.capabilities-mcp-servermount-is-well-formed-when-present', 'RFC 0020 §D', 'capabilities.mcp.serverMount is well-formed when present')).toBe('boolean');
|
|
70
71
|
});
|
|
71
72
|
});
|
|
72
73
|
|
|
73
74
|
describe('mcp-server-untrusted-args: behavioral (RFC 0020 §D)', () => {
|
|
74
75
|
it('tools/call with malformed arguments is rejected with JSON-RPC -32602 BEFORE workflow start', async () => {
|
|
75
76
|
const cap = await readCap();
|
|
76
|
-
if (!cap || cap.supported !== true) return;
|
|
77
|
-
if (!(await registerStrictWorkflow())) return;
|
|
77
|
+
if (!cap || cap.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!cap || cap.supported !== true` returned early');
|
|
78
|
+
if (!(await registerStrictWorkflow())) return softSkip('blocked', 'precondition not met — `!(await registerStrictWorkflow())` returned early (seam, prior step, or fixture unavailable)');
|
|
78
79
|
|
|
79
80
|
const r = await rpc('tools/call', {
|
|
80
81
|
name: TEST_TOOL_NAME,
|
|
81
82
|
arguments: { wrongField: 'no' },
|
|
82
83
|
});
|
|
83
84
|
if (r.status === 404) return seamAbsent(`host advertises an MCP server mount but the mount (capabilities.mcp.serverUrls[0], else /v1/host/sample/mcp) answered ${r.status} — RFC 0153 §B is unobservable at the path the host itself advertised`);
|
|
84
|
-
expect(r.status, 'JSON-RPC envelope MUST 200').toBe(200);
|
|
85
|
+
expect(r.status, req('openwop.it.mcp-server-untrusted-args.tools-call-with-malformed-arguments-is-rejected-with-json-rpc-32602-before-workf', 'SECURITY/invariants.yaml mcp-server-untrusted-args', 'JSON-RPC envelope MUST 200')).toBe(200);
|
|
85
86
|
expect(
|
|
86
87
|
r.body.error?.code,
|
|
87
|
-
|
|
88
|
+
req('openwop.it.mcp-server-untrusted-args.tools-call-with-malformed-arguments-is-rejected-with-json-rpc-32602-before-workf',
|
|
88
89
|
'SECURITY/invariants.yaml mcp-server-untrusted-args',
|
|
89
90
|
'malformed arguments MUST be rejected with -32602 invalid params before workflow start',
|
|
90
91
|
),
|
|
91
92
|
).toBe(-32602);
|
|
92
|
-
expect(r.body.error?.data, 'error.data MUST carry validation violations').toBeDefined();
|
|
93
|
+
expect(r.body.error?.data, req('openwop.it.mcp-server-untrusted-args.tools-call-with-malformed-arguments-is-rejected-with-json-rpc-32602-before-workf', 'SECURITY/invariants.yaml mcp-server-untrusted-args', 'error.data MUST carry validation violations')).toBeDefined();
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
it('tools/call with valid arguments is accepted', async () => {
|
|
96
97
|
const cap = await readCap();
|
|
97
|
-
if (!cap || cap.supported !== true) return;
|
|
98
|
+
if (!cap || cap.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!cap || cap.supported !== true` returned early');
|
|
98
99
|
const r = await rpc('tools/call', {
|
|
99
100
|
name: TEST_TOOL_NAME,
|
|
100
101
|
arguments: { text: 'hello' },
|
|
@@ -102,7 +103,7 @@ describe('mcp-server-untrusted-args: behavioral (RFC 0020 §D)', () => {
|
|
|
102
103
|
if (r.status === 404) return seamAbsent(`host advertises an MCP server mount but the mount (capabilities.mcp.serverUrls[0], else /v1/host/sample/mcp) answered ${r.status} — RFC 0153 §B is unobservable at the path the host itself advertised`);
|
|
103
104
|
expect(r.status).toBe(200);
|
|
104
105
|
if (r.body.error) {
|
|
105
|
-
expect(r.body.error.code, 'valid args MUST NOT trigger -32602').not.toBe(-32602);
|
|
106
|
+
expect(r.body.error.code, req('openwop.it.mcp-server-untrusted-args.tools-call-with-valid-arguments-is-accepted', 'RFC 0020 §D', 'valid args MUST NOT trigger -32602')).not.toBe(-32602);
|
|
106
107
|
}
|
|
107
108
|
});
|
|
108
109
|
});
|
|
@@ -24,6 +24,7 @@ import { mcpServerMount } from '../lib/mcp-mount.js';
|
|
|
24
24
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
25
25
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
26
26
|
import { softSkip, seamAbsent } from '../lib/soft-skip.js';
|
|
27
|
+
import { req } from '../lib/requirement-ids.js';
|
|
27
28
|
|
|
28
29
|
const PROFILE = 'mcp-2026-07-28';
|
|
29
30
|
const META_V = 'io.modelcontextprotocol/protocolVersion';
|
|
@@ -52,12 +53,12 @@ describe.skipIf(!process.env.OPENWOP_BASE_URL)('RFC 0153 §B — mcp-header-body
|
|
|
52
53
|
// (a) Mcp-Method header disagrees with the JSON-RPC method
|
|
53
54
|
const m = await driver.post(await mcpServerMount(), { jsonrpc: '2.0', id: 1, method: 'tools/list', params: { _meta: meta } }, { headers: { 'MCP-Protocol-Version': '2026-07-28', 'Mcp-Method': 'resources/list' } });
|
|
54
55
|
if (m.status === 404 || m.status === 403) return softSkip('blocked', `MCP server mount /v1/host/sample/mcp answered ${m.status}`);
|
|
55
|
-
expect(m.status,
|
|
56
|
-
expect((m.json as { error?: { code?: number } }).error?.code,
|
|
56
|
+
expect(m.status, req('openwop.it.mcp-stateless-request.mcp-method-body-method-and-mcp-name-params-name-are-refused-400-32020-headermism', 'mcp-integration.md §B', 'Mcp-Method MUST equal the body method; disagreement MUST be refused 400 (mcp-header-body-consistent)')).toBe(400);
|
|
57
|
+
expect((m.json as { error?: { code?: number } }).error?.code, req('openwop.it.mcp-stateless-request.mcp-method-body-method-and-mcp-name-params-name-are-refused-400-32020-headermism', 'mcp-integration.md §B', 'the refusal is HeaderMismatchError -32020')).toBe(-32020);
|
|
57
58
|
// (b) Mcp-Name header disagrees with params.name on tools/call
|
|
58
59
|
const n = await driver.post(await mcpServerMount(), { jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'echo', arguments: {}, _meta: meta } }, { headers: { 'MCP-Protocol-Version': '2026-07-28', 'Mcp-Method': 'tools/call', 'Mcp-Name': 'not-echo' } });
|
|
59
|
-
expect(n.status,
|
|
60
|
-
expect((n.json as { error?: { code?: number } }).error?.code,
|
|
60
|
+
expect(n.status, req('openwop.it.mcp-stateless-request.mcp-method-body-method-and-mcp-name-params-name-are-refused-400-32020-headermism', 'mcp-integration.md §B', 'Mcp-Name MUST equal params.name; disagreement MUST be refused 400 (mcp-header-body-consistent)')).toBe(400);
|
|
61
|
+
expect((n.json as { error?: { code?: number } }).error?.code, req('openwop.it.mcp-stateless-request.mcp-method-body-method-and-mcp-name-params-name-are-refused-400-32020-headermism', 'mcp-integration.md §B', 'the refusal is HeaderMismatchError -32020')).toBe(-32020);
|
|
61
62
|
});
|
|
62
63
|
});
|
|
63
64
|
|
|
@@ -66,13 +67,13 @@ describe.skipIf(!process.env.OPENWOP_BASE_URL)('RFC 0153 §B — mcp-stateless-r
|
|
|
66
67
|
if (!behaviorGate(PROFILE, await claimsCurrent())) return;
|
|
67
68
|
const a = await list();
|
|
68
69
|
if (a.status === 404 || a.status === 403) return seamAbsent(`host advertises an MCP server mount but the mount (capabilities.mcp.serverUrls[0], else /v1/host/sample/mcp) answered ${a.status} — RFC 0153 §B is unobservable at the path the host itself advertised`);
|
|
69
|
-
expect(a.status,
|
|
70
|
-
expect(a.body.error,
|
|
71
|
-
expect(a.body.result?.resultType,
|
|
72
|
-
expect(typeof a.body.result?.ttlMs,
|
|
70
|
+
expect(a.status, req('openwop.it.mcp-stateless-request.tools-list-succeeds-with-no-initialize-and-no-session-result-carries-resulttype', 'mcp-integration.md §B', 'a core request MUST succeed without a prior initialize or a session header')).toBe(200);
|
|
71
|
+
expect(a.body.error, req('openwop.it.mcp-stateless-request.tools-list-succeeds-with-no-initialize-and-no-session-result-carries-resulttype', 'mcp-integration.md §B', `stateless tools/list MUST NOT error: ${JSON.stringify(a.body.error)}`)).toBeUndefined();
|
|
72
|
+
expect(a.body.result?.resultType, req('openwop.it.mcp-stateless-request.tools-list-succeeds-with-no-initialize-and-no-session-result-carries-resulttype', 'mcp-integration.md §B', 'every current-revision result carries resultType')).toBe('complete');
|
|
73
|
+
expect(typeof a.body.result?.ttlMs, req('openwop.it.mcp-stateless-request.tools-list-succeeds-with-no-initialize-and-no-session-result-carries-resulttype', 'mcp-integration.md §D', 'tools/list MUST carry ttlMs (>= 0)')).toBe('number');
|
|
73
74
|
expect((a.body.result?.ttlMs ?? -1) >= 0).toBe(true);
|
|
74
|
-
expect(['public', 'private'],
|
|
75
|
+
expect(['public', 'private'], req('openwop.it.mcp-stateless-request.tools-list-succeeds-with-no-initialize-and-no-session-result-carries-resulttype', 'mcp-integration.md §D', 'cacheScope MUST be public|private')).toContain(a.body.result?.cacheScope);
|
|
75
76
|
const b = await list();
|
|
76
|
-
expect(JSON.stringify(b.body.result?.tools),
|
|
77
|
+
expect(JSON.stringify(b.body.result?.tools), req('openwop.it.mcp-stateless-request.tools-list-succeeds-with-no-initialize-and-no-session-result-carries-resulttype', 'mcp-integration.md §B', 'list results MUST NOT vary per connection — two independent requests from the same caller MUST agree')).toBe(JSON.stringify(a.body.result?.tools));
|
|
77
78
|
});
|
|
78
79
|
});
|
|
@@ -66,6 +66,7 @@ import { driver } from '../lib/driver.js';
|
|
|
66
66
|
import { getMcpFakeServer } from '../lib/mcp-fake-server.js';
|
|
67
67
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
68
68
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
69
|
+
import { req } from '../lib/requirement-ids.js';
|
|
69
70
|
|
|
70
71
|
/**
|
|
71
72
|
* Callback-shaped: the host issues MCP JSON-RPC calls to the suite's fake server.
|
|
@@ -210,7 +211,7 @@ describe('mcp-tool-roundtrip: server wire shape', () => {
|
|
|
210
211
|
},
|
|
211
212
|
1,
|
|
212
213
|
);
|
|
213
|
-
expect(init.status).toBe(200);
|
|
214
|
+
expect(init.status, req('openwop.it.mcp-tool-roundtrip.initialize-tools-list-tools-call-round-trip-per-mcp-json-rpc-contract', 'spec/v1/mcp-integration.md', 'initialize + tools/list + tools/call round-trip per MCP JSON-RPC contract')).toBe(200);
|
|
214
215
|
const initResult = (init.json.result ?? {}) as { protocolVersion?: string };
|
|
215
216
|
expect(typeof initResult.protocolVersion).toBe('string');
|
|
216
217
|
// Capture session id from initialize so real SDK-based servers can
|
|
@@ -310,7 +311,7 @@ describe('mcp-tool-roundtrip: host-mediated tool invocation', () => {
|
|
|
310
311
|
|
|
311
312
|
const invocations = server.invocations();
|
|
312
313
|
const toolCalls = invocations.filter((i) => i.method === 'tools/call');
|
|
313
|
-
expect(toolCalls.length,
|
|
314
|
+
expect(toolCalls.length, req('openwop.it.mcp-tool-roundtrip.host-invokes-the-configured-mcp-server-and-surfaces-the-tool-response-in-the-eve',
|
|
314
315
|
'mcp-integration.md §"Tool invocation"',
|
|
315
316
|
'host MUST invoke `tools/call` on the configured MCP server during the fixture run',
|
|
316
317
|
)).toBeGreaterThan(0);
|
|
@@ -325,7 +326,7 @@ describe('mcp-tool-roundtrip: host-mediated tool invocation', () => {
|
|
|
325
326
|
const events = await driver.get(`/v1/runs/${encodeURIComponent(runId)}/events`);
|
|
326
327
|
const list = (events.json as { events?: Array<{ type: string; payload?: unknown }> }).events ?? [];
|
|
327
328
|
const haystack = JSON.stringify(list).toLowerCase();
|
|
328
|
-
expect(haystack.includes('echo'),
|
|
329
|
+
expect(haystack.includes('echo'), req('openwop.it.mcp-tool-roundtrip.host-invokes-the-configured-mcp-server-and-surfaces-the-tool-response-in-the-eve',
|
|
329
330
|
'mcp-integration.md + threat-model-prompt-injection.md §"UNTRUSTED marker"',
|
|
330
331
|
'host event log MUST surface the MCP tool invocation so observers can audit the trust boundary',
|
|
331
332
|
)).toBe(true);
|