@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
|
@@ -28,6 +28,7 @@ import { driver } from '../lib/driver.js';
|
|
|
28
28
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
29
29
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
30
30
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
31
|
+
import { req } from '../lib/requirement-ids.js';
|
|
31
32
|
|
|
32
33
|
const CAP_BREACH_FIXTURE = 'conformance-wasm-pack-memory-cap-breach';
|
|
33
34
|
|
|
@@ -41,7 +42,7 @@ describe('wasm-pack-memory-cap: host advertises maxMemoryBytes', () => {
|
|
|
41
42
|
|
|
42
43
|
// The cap is REQUIRED to enforce §K. Hosts MUST surface the configured
|
|
43
44
|
// ceiling so clients can size their packs accordingly.
|
|
44
|
-
expect(typeof wasm.maxMemoryBytes,
|
|
45
|
+
expect(typeof wasm.maxMemoryBytes, req('openwop.it.wasm-pack-memory-cap.capabilities-nodepackruntimes-wasm-maxmemorybytes-is-a-plausible-number',
|
|
45
46
|
'RFCS/0008-wasm-abi.md §K',
|
|
46
47
|
'capabilities.nodePackRuntimes.wasm.maxMemoryBytes MUST be advertised as a number when WASM is supported',
|
|
47
48
|
)).toBe('number');
|
|
@@ -75,7 +76,7 @@ describe('wasm-pack-memory-cap: positive path via misbehaving pack', () => {
|
|
|
75
76
|
const runId = (create.json as { runId: string }).runId;
|
|
76
77
|
|
|
77
78
|
const terminal = await pollUntilTerminal(runId, { timeoutMs: 15_000 });
|
|
78
|
-
expect(terminal.status,
|
|
79
|
+
expect(terminal.status, req('openwop.it.wasm-pack-memory-cap.misbehaving-pack-triggers-cap-breached-with-kind-wasm-memory-and-terminal-failed',
|
|
79
80
|
'RFCS/0008-wasm-abi.md §K',
|
|
80
81
|
'WASM memory-cap breach MUST drive terminal `failed` (RFC 0008 §K: "kill the instance, emit cap.breached")',
|
|
81
82
|
)).toBe('failed');
|
|
@@ -83,14 +84,14 @@ describe('wasm-pack-memory-cap: positive path via misbehaving pack', () => {
|
|
|
83
84
|
const events = await driver.get(`/v1/runs/${encodeURIComponent(runId)}/events`);
|
|
84
85
|
const list = (events.json as { events?: Array<{ type: string; payload?: unknown }> }).events ?? [];
|
|
85
86
|
const breachEvent = list.find((e) => e.type === 'cap.breached');
|
|
86
|
-
expect(breachEvent,
|
|
87
|
+
expect(breachEvent, req('openwop.it.wasm-pack-memory-cap.misbehaving-pack-triggers-cap-breached-with-kind-wasm-memory-and-terminal-failed',
|
|
87
88
|
'RFCS/0008-wasm-abi.md §K',
|
|
88
89
|
'host MUST emit cap.breached when a WASM module exceeds its memory ceiling',
|
|
89
90
|
)).toBeDefined();
|
|
90
91
|
// Event detail rides under the canonical `payload` envelope field (per
|
|
91
92
|
// run-event-payloads.schema.json + every other event scenario), not `data`.
|
|
92
93
|
const breachKind = (breachEvent?.payload as { kind?: string } | undefined)?.kind;
|
|
93
|
-
expect(breachKind,
|
|
94
|
+
expect(breachKind, req('openwop.it.wasm-pack-memory-cap.misbehaving-pack-triggers-cap-breached-with-kind-wasm-memory-and-terminal-failed',
|
|
94
95
|
'RFCS/0008-wasm-abi.md §K',
|
|
95
96
|
'cap.breached payload MUST carry kind: "wasm-memory" for memory-ceiling breaches',
|
|
96
97
|
)).toBe('wasm-memory');
|
|
@@ -16,6 +16,7 @@ import { driver } from '../lib/driver.js';
|
|
|
16
16
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
17
17
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
18
18
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
19
|
+
import { req } from '../lib/requirement-ids.js';
|
|
19
20
|
|
|
20
21
|
const FIXTURE = 'conformance-wasm-pack-roundtrip';
|
|
21
22
|
|
|
@@ -53,7 +54,7 @@ describe('wasm-pack-replay-determinism: same inputs → same output', () => {
|
|
|
53
54
|
const a = await run();
|
|
54
55
|
const b = await run();
|
|
55
56
|
|
|
56
|
-
expect(a,
|
|
57
|
+
expect(a, req('openwop.it.wasm-pack-replay-determinism.two-independent-runs-with-same-inputs-produce-same-wasm-output',
|
|
57
58
|
'RFCS/0008-wasm-abi.md §G',
|
|
58
59
|
'WASM-node output MUST be reproducible given the same inputs',
|
|
59
60
|
)).not.toBeNull();
|
|
@@ -24,6 +24,8 @@ import { describe, it, expect } from 'vitest';
|
|
|
24
24
|
import { driver } from '../lib/driver.js';
|
|
25
25
|
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
26
26
|
import { discoverOwnedTenant } from '../lib/webhook-receiver.js';
|
|
27
|
+
import { req } from '../lib/requirement-ids.js';
|
|
28
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
27
29
|
|
|
28
30
|
async function isWebhookSupported(): Promise<boolean> {
|
|
29
31
|
const disco = await driver.get('/.well-known/openwop');
|
|
@@ -36,7 +38,7 @@ describe('webhook-negative: SSRF guard rejects private destinations', () => {
|
|
|
36
38
|
if (!(await isWebhookSupported())) {
|
|
37
39
|
// eslint-disable-next-line no-console
|
|
38
40
|
console.warn('[webhook-negative] host does not advertise webhook support; skipping');
|
|
39
|
-
return;
|
|
41
|
+
return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isWebhookSupported())` returned early ([webhook-negative] host does not advertise webhook support; skipping)');
|
|
40
42
|
}
|
|
41
43
|
// Spec-complete except for the private URL, so validation passes and the
|
|
42
44
|
// request reaches the SSRF guard. `{ url }` alone 400s `validation_error`
|
|
@@ -59,9 +61,9 @@ describe('webhook-negative: SSRF guard rejects private destinations', () => {
|
|
|
59
61
|
if (body.subscriptionId) {
|
|
60
62
|
await driver.delete(`/v1/webhooks/${encodeURIComponent(body.subscriptionId)}`);
|
|
61
63
|
}
|
|
62
|
-
return;
|
|
64
|
+
return softSkip('blocked', 'precondition not met — `reg.status === 201` returned early ([webhook-negative] host accepts loopback destinations; SSRF guard not enforced) (seam, prior step, or fixture unavailable)');
|
|
63
65
|
}
|
|
64
|
-
expect(reg.status,
|
|
66
|
+
expect(reg.status, req('openwop.it.webhook-negative.host-with-ssrf-guard-returns-400-webhook-url-rejected-for-loopback',
|
|
65
67
|
'webhooks.md + review §"Webhook SSRF guard"',
|
|
66
68
|
'host with SSRF guard MUST return 400 for loopback / RFC1918 / link-local destinations',
|
|
67
69
|
)).toBe(400);
|
|
@@ -72,17 +74,17 @@ describe('webhook-negative: SSRF guard rejects private destinations', () => {
|
|
|
72
74
|
|
|
73
75
|
describe('webhook-negative: validation errors', () => {
|
|
74
76
|
it('malformed url returns 400 validation_error', async () => {
|
|
75
|
-
if (!(await isWebhookSupported())) return;
|
|
77
|
+
if (!(await isWebhookSupported())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isWebhookSupported())` returned early');
|
|
76
78
|
const reg = await driver.post('/v1/webhooks', { url: 'not a url' });
|
|
77
|
-
expect([400, 422]).toContain(reg.status);
|
|
79
|
+
expect([400, 422], req('openwop.it.webhook-negative.malformed-url-returns-400-validation-error', 'webhooks.md', 'malformed url returns 400 validation_error')).toContain(reg.status);
|
|
78
80
|
const body = reg.json as { error?: string };
|
|
79
81
|
expect(['validation_error', 'webhook_url_rejected']).toContain(body.error);
|
|
80
82
|
});
|
|
81
83
|
|
|
82
84
|
it('missing url returns 400 validation_error', async () => {
|
|
83
|
-
if (!(await isWebhookSupported())) return;
|
|
85
|
+
if (!(await isWebhookSupported())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isWebhookSupported())` returned early');
|
|
84
86
|
const reg = await driver.post('/v1/webhooks', { eventTypes: ['run.completed'] });
|
|
85
|
-
expect(reg.status).toBe(400);
|
|
87
|
+
expect(reg.status, req('openwop.it.webhook-negative.missing-url-returns-400-validation-error', 'webhooks.md', 'missing url returns 400 validation_error')).toBe(400);
|
|
86
88
|
const body = reg.json as { error?: string };
|
|
87
89
|
expect(body.error).toBe('validation_error');
|
|
88
90
|
});
|
|
@@ -90,9 +92,9 @@ describe('webhook-negative: validation errors', () => {
|
|
|
90
92
|
|
|
91
93
|
describe('webhook-negative: unregister of unknown subscription', () => {
|
|
92
94
|
it('DELETE /v1/webhooks/{unknown} returns 404 subscription_not_found', async () => {
|
|
93
|
-
if (!(await isWebhookSupported())) return;
|
|
95
|
+
if (!(await isWebhookSupported())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isWebhookSupported())` returned early');
|
|
94
96
|
const del = await driver.delete('/v1/webhooks/wh-does-not-exist');
|
|
95
|
-
expect(del.status).toBe(404);
|
|
97
|
+
expect(del.status, req('openwop.it.webhook-negative.delete-v1-webhooks-unknown-returns-404-subscription-not-found', 'webhooks.md', 'DELETE /v1/webhooks/{unknown} returns 404 subscription_not_found')).toBe(404);
|
|
96
98
|
const body = del.json as { error?: string };
|
|
97
99
|
expect(body.error).toBe('subscription_not_found');
|
|
98
100
|
});
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
verifyWebhookDelivery,
|
|
45
45
|
SIGNATURE_PREFIX,
|
|
46
46
|
} from '../lib/webhook-receiver.js';
|
|
47
|
+
import { req } from '../lib/requirement-ids.js';
|
|
47
48
|
|
|
48
49
|
describe('webhook-receiver-adversarial: receiver rejects five canonical attacks', () => {
|
|
49
50
|
const secret = 'test-secret-do-not-use-in-prod';
|
|
@@ -65,7 +66,7 @@ describe('webhook-receiver-adversarial: receiver rejects five canonical attacks'
|
|
|
65
66
|
);
|
|
66
67
|
expect(
|
|
67
68
|
result.accepted,
|
|
68
|
-
'webhooks.md §"Signature recipe": valid signature + fresh timestamp + correct algorithm MUST be accepted',
|
|
69
|
+
req('openwop.it.webhook-receiver-adversarial.positive-control-receiver-accepts-a-freshly-signed-valid-delivery', 'spec/v1/webhooks.md', 'webhooks.md §"Signature recipe": valid signature + fresh timestamp + correct algorithm MUST be accepted'),
|
|
69
70
|
).toBe(true);
|
|
70
71
|
});
|
|
71
72
|
|
|
@@ -86,7 +87,7 @@ describe('webhook-receiver-adversarial: receiver rejects five canonical attacks'
|
|
|
86
87
|
if (!result.accepted) {
|
|
87
88
|
expect(
|
|
88
89
|
result.reason,
|
|
89
|
-
'webhooks.md §"Signature recipe": tampered body MUST be rejected with signature_mismatch',
|
|
90
|
+
req('openwop.it.webhook-receiver-adversarial.case-1-tampered-body-signature-mismatch', 'spec/v1/webhooks.md', 'webhooks.md §"Signature recipe": tampered body MUST be rejected with signature_mismatch'),
|
|
90
91
|
).toBe('signature_mismatch');
|
|
91
92
|
}
|
|
92
93
|
});
|
|
@@ -113,7 +114,7 @@ describe('webhook-receiver-adversarial: receiver rejects five canonical attacks'
|
|
|
113
114
|
state,
|
|
114
115
|
{ nowSeconds: nowSec },
|
|
115
116
|
);
|
|
116
|
-
expect(result.accepted).toBe(false);
|
|
117
|
+
expect(result.accepted, req('openwop.it.webhook-receiver-adversarial.case-2-tampered-hmac-signature-mismatch', 'spec/v1/webhooks.md', 'case 2: tampered HMAC → signature_mismatch')).toBe(false);
|
|
117
118
|
if (!result.accepted) {
|
|
118
119
|
expect(result.reason).toBe('signature_mismatch');
|
|
119
120
|
}
|
|
@@ -136,7 +137,7 @@ describe('webhook-receiver-adversarial: receiver rejects five canonical attacks'
|
|
|
136
137
|
if (!result.accepted) {
|
|
137
138
|
expect(
|
|
138
139
|
result.reason,
|
|
139
|
-
'webhooks.md §"Replay-attack resistance": timestamp older than freshness window MUST be rejected with timestamp_expired',
|
|
140
|
+
req('openwop.it.webhook-receiver-adversarial.case-3-stale-timestamp-timestamp-expired', 'spec/v1/webhooks.md', 'webhooks.md §"Replay-attack resistance": timestamp older than freshness window MUST be rejected with timestamp_expired'),
|
|
140
141
|
).toBe('timestamp_expired');
|
|
141
142
|
}
|
|
142
143
|
});
|
|
@@ -170,7 +171,7 @@ describe('webhook-receiver-adversarial: receiver rejects five canonical attacks'
|
|
|
170
171
|
if (!replay.accepted) {
|
|
171
172
|
expect(
|
|
172
173
|
replay.reason,
|
|
173
|
-
'webhooks.md §"Replay-attack resistance": replay of an already-accepted signature MUST be rejected with duplicate_signature',
|
|
174
|
+
req('openwop.it.webhook-receiver-adversarial.case-4-replayed-signature-duplicate-signature', 'spec/v1/webhooks.md', 'webhooks.md §"Replay-attack resistance": replay of an already-accepted signature MUST be rejected with duplicate_signature'),
|
|
174
175
|
).toBe('duplicate_signature');
|
|
175
176
|
}
|
|
176
177
|
});
|
|
@@ -191,7 +192,7 @@ describe('webhook-receiver-adversarial: receiver rejects five canonical attacks'
|
|
|
191
192
|
if (!result.accepted) {
|
|
192
193
|
expect(
|
|
193
194
|
result.reason,
|
|
194
|
-
'webhooks.md §"Signature algorithm versioning": algorithm other than v1 MUST be rejected with wrong_algorithm by a v1-only receiver',
|
|
195
|
+
req('openwop.it.webhook-receiver-adversarial.case-5-wrong-algorithm-wrong-algorithm', 'spec/v1/webhooks.md', 'webhooks.md §"Signature algorithm versioning": algorithm other than v1 MUST be rejected with wrong_algorithm by a v1-only receiver'),
|
|
195
196
|
).toBe('wrong_algorithm');
|
|
196
197
|
}
|
|
197
198
|
});
|
|
@@ -208,7 +209,7 @@ describe('webhook-receiver-adversarial: receiver rejects five canonical attacks'
|
|
|
208
209
|
state,
|
|
209
210
|
{ nowSeconds: nowSec },
|
|
210
211
|
);
|
|
211
|
-
expect(result.accepted).toBe(false);
|
|
212
|
+
expect(result.accepted, req('openwop.it.webhook-receiver-adversarial.case-6-malformed-signature-header-malformed-signature-header', 'spec/v1/webhooks.md', 'case 6: malformed signature header → malformed_signature_header')).toBe(false);
|
|
212
213
|
if (!result.accepted) {
|
|
213
214
|
expect(result.reason).toBe('malformed_signature_header');
|
|
214
215
|
}
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
import { describe, it, expect } from 'vitest';
|
|
17
17
|
import { driver } from '../lib/driver.js';
|
|
18
18
|
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
19
|
+
import { req } from '../lib/requirement-ids.js';
|
|
20
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
19
21
|
|
|
20
22
|
interface DiscoveryWebhooks {
|
|
21
23
|
webhooks?: {
|
|
@@ -33,7 +35,7 @@ describe('webhook-sig-algorithm: host advertises supported algorithm set', () =>
|
|
|
33
35
|
if (!webhooks?.supported) {
|
|
34
36
|
// eslint-disable-next-line no-console
|
|
35
37
|
console.warn('[webhook-sig-algorithm] host does not advertise webhook support; skipping');
|
|
36
|
-
return;
|
|
38
|
+
return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!webhooks?.supported` returned early ([webhook-sig-algorithm] host does not advertise webhook support; skipping)');
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
const algos = webhooks.signatureAlgorithms;
|
|
@@ -45,10 +47,10 @@ describe('webhook-sig-algorithm: host advertises supported algorithm set', () =>
|
|
|
45
47
|
console.warn(
|
|
46
48
|
'[webhook-sig-algorithm] host does not advertise webhooks.signatureAlgorithms (pre-v1.1); skipping shape check',
|
|
47
49
|
);
|
|
48
|
-
return;
|
|
50
|
+
return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!Array.isArray(algos)` returned early ([webhook-sig-algorithm] host does not advertise webhooks.signatureAlgorithms (pre-v1.1); skipping shape check)');
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
expect(algos.includes('v1'),
|
|
53
|
+
expect(algos.includes('v1'), req('openwop.it.webhook-sig-algorithm.discovery-surfaces-a-webhooks-signaturealgorithms-array-including-v1',
|
|
52
54
|
'webhooks.md §"Signature algorithm versioning"',
|
|
53
55
|
'webhooks.signatureAlgorithms MUST include "v1" when surfaced (canonical baseline)',
|
|
54
56
|
)).toBe(true);
|
|
@@ -108,6 +108,7 @@ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
|
108
108
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
109
109
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
110
110
|
import { discoverOwnedTenant, resolveRegistrationUrl } from '../lib/webhook-receiver.js';
|
|
111
|
+
import { req } from '../lib/requirement-ids.js';
|
|
111
112
|
|
|
112
113
|
interface DeliveredRequest {
|
|
113
114
|
readonly headers: Record<string, string>;
|
|
@@ -116,13 +117,13 @@ interface DeliveredRequest {
|
|
|
116
117
|
|
|
117
118
|
async function startReceiver(): Promise<{ server: Server; url: string; received: DeliveredRequest[] }> {
|
|
118
119
|
const received: DeliveredRequest[] = [];
|
|
119
|
-
const server = createServer((
|
|
120
|
+
const server = createServer((reqBody: IncomingMessage, res: ServerResponse) => {
|
|
120
121
|
const chunks: Buffer[] = [];
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
reqBody.on('data', (c: Buffer) => chunks.push(c));
|
|
123
|
+
reqBody.on('end', () => {
|
|
123
124
|
const body = Buffer.concat(chunks).toString('utf8');
|
|
124
125
|
const headers: Record<string, string> = {};
|
|
125
|
-
for (const [k, v] of Object.entries(
|
|
126
|
+
for (const [k, v] of Object.entries(reqBody.headers)) {
|
|
126
127
|
if (typeof v === 'string') headers[k.toLowerCase()] = v;
|
|
127
128
|
else if (Array.isArray(v)) headers[k.toLowerCase()] = v.join(',');
|
|
128
129
|
}
|
|
@@ -225,7 +226,7 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
228
|
|
|
228
|
-
expect(reg.status,
|
|
229
|
+
expect(reg.status, req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature',
|
|
229
230
|
'webhooks.md §"Register"',
|
|
230
231
|
'POST /v1/webhooks MUST return 201 with webhookId + secret on success',
|
|
231
232
|
)).toBe(201);
|
|
@@ -240,7 +241,7 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
240
241
|
// what the spec shows. The postgres reference host returns both names, which
|
|
241
242
|
// is why nothing went red here.
|
|
242
243
|
const sub = reg.json as { webhookId: string; secret: string };
|
|
243
|
-
expect(typeof sub.webhookId,
|
|
244
|
+
expect(typeof sub.webhookId, req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature',
|
|
244
245
|
'api/openapi.yaml registerWebhook 201',
|
|
245
246
|
'the 201 body MUST carry `webhookId` (required) — `subscriptionId` is not in the contract',
|
|
246
247
|
)).toBe('string');
|
|
@@ -276,7 +277,7 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
276
277
|
// has a subscriber; if nothing arrived HERE, the delivery went somewhere
|
|
277
278
|
// this process cannot see and every assertion below it would be vacuous.
|
|
278
279
|
// It fails — it must never soft-skip.
|
|
279
|
-
expect(ourDeliveries.length,
|
|
280
|
+
expect(ourDeliveries.length, req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature',
|
|
280
281
|
'webhooks.md §"Delivery"',
|
|
281
282
|
registration.tunnelled
|
|
282
283
|
? 'host MUST POST at least one event for THIS run to the registered subscriber. ' +
|
|
@@ -289,7 +290,7 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
289
290
|
// Validate the FIRST delivery's signature contract. Other deliveries
|
|
290
291
|
// share the same signing rules; checking one is sufficient.
|
|
291
292
|
const first = ourDeliveries[0]!;
|
|
292
|
-
expect(first.headers['x-openwop-signature-algorithm'],
|
|
293
|
+
expect(first.headers['x-openwop-signature-algorithm'], req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature',
|
|
293
294
|
'webhooks.md §"Signature algorithm versioning"',
|
|
294
295
|
'every delivery MUST set X-openwop-Signature-Algorithm: v1',
|
|
295
296
|
)).toBe('v1');
|
|
@@ -301,26 +302,26 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
301
302
|
// `X-openwop-Signature: sha256={hex}`. (Suite↔spec divergence fixed 2026-08-09.)
|
|
302
303
|
expect(
|
|
303
304
|
first.headers['x-openwop-webhook-id'],
|
|
304
|
-
|
|
305
|
+
req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature', 'webhooks.md §"Delivery headers"', 'X-openwop-Webhook-Id MUST carry the subscription id'),
|
|
305
306
|
).toBe(sub.webhookId);
|
|
306
307
|
|
|
307
308
|
const timestamp = first.headers['x-openwop-timestamp'];
|
|
308
309
|
expect(
|
|
309
310
|
typeof timestamp === 'string' && timestamp.length > 0,
|
|
310
|
-
|
|
311
|
+
req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature', 'webhooks.md §"Delivery headers"', 'X-openwop-Timestamp MUST be a Unix-seconds integer string'),
|
|
311
312
|
).toBe(true);
|
|
312
313
|
|
|
313
314
|
const signature = first.headers['x-openwop-signature'] ?? '';
|
|
314
315
|
expect(
|
|
315
316
|
signature.startsWith('sha256='),
|
|
316
|
-
|
|
317
|
+
req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature', 'webhooks.md §"Delivery headers"', 'X-openwop-Signature MUST carry the `sha256=` prefix'),
|
|
317
318
|
).toBe(true);
|
|
318
319
|
const expected = createHmac('sha256', sub.secret)
|
|
319
320
|
.update(`${timestamp}.${first.body}`, 'utf8')
|
|
320
321
|
.digest('hex');
|
|
321
322
|
expect(
|
|
322
323
|
signature.replace('sha256=', ''),
|
|
323
|
-
|
|
324
|
+
req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature', 'webhooks.md §"Delivery headers"', 'X-openwop-Signature MUST be sha256=HMAC-SHA256(secret, `${X-openwop-Timestamp}.${rawBody}`)'),
|
|
324
325
|
).toBe(expected);
|
|
325
326
|
|
|
326
327
|
// RFC 0165 §C.1 — dual emission of the `OpenWOP-*` family. SHOULD-level and
|
|
@@ -337,11 +338,11 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
337
338
|
['openwop-signature', 'x-openwop-signature'],
|
|
338
339
|
['openwop-signature-algorithm', 'x-openwop-signature-algorithm'],
|
|
339
340
|
] as const) {
|
|
340
|
-
expect(first.headers[neu],
|
|
341
|
+
expect(first.headers[neu], req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature', 'RFC 0165 §C.1', `${neu} MUST equal ${old} when the OpenWOP-* family is emitted`)).toBe(first.headers[old]);
|
|
341
342
|
}
|
|
342
343
|
expect(
|
|
343
344
|
String(dual).replace('sha256=', ''),
|
|
344
|
-
|
|
345
|
+
req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature', 'RFC 0165 §C.1', 'OpenWOP-Signature MUST verify over the same bytes as X-openwop-Signature'),
|
|
345
346
|
).toBe(expected);
|
|
346
347
|
}
|
|
347
348
|
|
|
@@ -404,7 +405,7 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
404
405
|
}
|
|
405
406
|
}
|
|
406
407
|
|
|
407
|
-
expect(reg.status,
|
|
408
|
+
expect(reg.status, req('openwop.it.webhook-signed-delivery.control-with-a-tunnel-wired-the-host-still-refuses-the-loopback-receiver',
|
|
408
409
|
'webhooks.md §"SSRF protection"',
|
|
409
410
|
'the server MUST reject loopback subscription URLs at registration time. ' +
|
|
410
411
|
'It was accepted here, so this run cannot attribute the tunnelled test\'s pass to ' +
|
|
@@ -38,6 +38,8 @@ import { describe, it, expect } from 'vitest';
|
|
|
38
38
|
import { driver, type OpenWOPResponse } from '../lib/driver.js';
|
|
39
39
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
40
40
|
import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
|
|
41
|
+
import { req } from '../lib/requirement-ids.js';
|
|
42
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
41
43
|
|
|
42
44
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
43
45
|
|
|
@@ -66,26 +68,26 @@ async function gateOnWebhooks(): Promise<boolean> {
|
|
|
66
68
|
describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: advertisement shape (webhooks.md)', () => {
|
|
67
69
|
it('the webhooks block is either absent or carries a boolean supported flag', async () => {
|
|
68
70
|
const cap = await readCapabilityFamily<WebhooksCap>('webhooks');
|
|
69
|
-
if (cap === undefined) return; // host doesn't advertise webhooks — conformant
|
|
71
|
+
if (cap === undefined) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap === undefined` returned early (host doesn\'t advertise webhooks — conformant)'); // host doesn't advertise webhooks — conformant
|
|
70
72
|
expect(
|
|
71
73
|
typeof cap.supported,
|
|
72
|
-
|
|
74
|
+
req('openwop.it.webhook-tenant-isolation.the-webhooks-block-is-either-absent-or-carries-a-boolean-supported-flag', 'capabilities.schema.json §webhooks', 'webhooks.supported MUST be a boolean when present'),
|
|
73
75
|
).toBe('boolean');
|
|
74
76
|
});
|
|
75
77
|
});
|
|
76
78
|
|
|
77
79
|
describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: two-tenant proof via the test seam (RFC 0093 §A.3)', () => {
|
|
78
80
|
it('a subscription registered under tenant A is invisible to tenant B', async () => {
|
|
79
|
-
if (!(await gateOnWebhooks())) return;
|
|
81
|
+
if (!(await gateOnWebhooks())) return softSkip('blocked', 'precondition not met — `!(await gateOnWebhooks())` returned early (seam, prior step, or fixture unavailable)');
|
|
80
82
|
|
|
81
83
|
const reg = await seam('tenant-a', 'register', {
|
|
82
84
|
url: SAFE_URL,
|
|
83
85
|
events: ['run.completed'],
|
|
84
86
|
});
|
|
85
|
-
if (reg.status === 404) return; // host doesn't expose the test seam — soft-skip
|
|
86
|
-
expect(reg.status,
|
|
87
|
+
if (reg.status === 404) return softSkip('blocked', 'precondition not met — `reg.status === 404` returned early (host doesn\'t expose the test seam — soft-skip) (seam, prior step, or fixture unavailable)'); // host doesn't expose the test seam — soft-skip
|
|
88
|
+
expect(reg.status, req('openwop.it.webhook-tenant-isolation.a-subscription-registered-under-tenant-a-is-invisible-to-tenant-b', 'RFC 0093 §A.3', 'seam register under tenant A MUST succeed')).toBe(200);
|
|
87
89
|
const webhookId = (reg.json as { webhookId?: string }).webhookId;
|
|
88
|
-
expect(typeof webhookId, 'seam register MUST return the new webhookId').toBe('string');
|
|
90
|
+
expect(typeof webhookId, req('openwop.it.webhook-tenant-isolation.a-subscription-registered-under-tenant-a-is-invisible-to-tenant-b', 'RFC 0093 §A.3', 'seam register MUST return the new webhookId')).toBe('string');
|
|
89
91
|
|
|
90
92
|
try {
|
|
91
93
|
const listB = await seam('tenant-b', 'list', {});
|
|
@@ -94,7 +96,7 @@ describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: two-tenant proof via the t
|
|
|
94
96
|
.map((w) => w.webhookId);
|
|
95
97
|
expect(
|
|
96
98
|
idsB,
|
|
97
|
-
|
|
99
|
+
req('openwop.it.webhook-tenant-isolation.a-subscription-registered-under-tenant-a-is-invisible-to-tenant-b',
|
|
98
100
|
'SECURITY/invariants.yaml webhook-cross-tenant-isolation',
|
|
99
101
|
'tenant B MUST NOT see tenant A subscriptions in its list',
|
|
100
102
|
),
|
|
@@ -105,7 +107,7 @@ describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: two-tenant proof via the t
|
|
|
105
107
|
.map((w) => w.webhookId);
|
|
106
108
|
expect(
|
|
107
109
|
idsA,
|
|
108
|
-
|
|
110
|
+
req('openwop.it.webhook-tenant-isolation.a-subscription-registered-under-tenant-a-is-invisible-to-tenant-b', 'RFC 0093 §A.3', 'tenant A MUST see its own subscription (same-tenant control)'),
|
|
109
111
|
).toContain(webhookId);
|
|
110
112
|
} finally {
|
|
111
113
|
await seam('tenant-a', 'unregister', { webhookId });
|
|
@@ -115,7 +117,7 @@ describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: two-tenant proof via the t
|
|
|
115
117
|
|
|
116
118
|
describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: black-box registration-surface scoping (webhooks.md)', () => {
|
|
117
119
|
it('registering under a tenant the caller is not a member of is refused', async () => {
|
|
118
|
-
if (!(await gateOnWebhooks())) return;
|
|
120
|
+
if (!(await gateOnWebhooks())) return softSkip('blocked', 'precondition not met — `!(await gateOnWebhooks())` returned early (seam, prior step, or fixture unavailable)');
|
|
119
121
|
|
|
120
122
|
const foreignTenant = `openwop-conformance-foreign-${uniqueSuffix()}`;
|
|
121
123
|
const reg = await driver.post('/v1/webhooks', {
|
|
@@ -135,7 +137,7 @@ describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: black-box registration-sur
|
|
|
135
137
|
}
|
|
136
138
|
expect(
|
|
137
139
|
reg.status,
|
|
138
|
-
|
|
140
|
+
req('openwop.it.webhook-tenant-isolation.registering-under-a-tenant-the-caller-is-not-a-member-of-is-refused',
|
|
139
141
|
'webhooks.md §Endpoints ("the caller MUST be a member of the tenant") + RFC 0093 §A.3',
|
|
140
142
|
'registration under a tenant the caller is not a member of MUST be refused (403/404/400), never 201',
|
|
141
143
|
),
|
|
@@ -143,7 +145,7 @@ describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: black-box registration-sur
|
|
|
143
145
|
});
|
|
144
146
|
|
|
145
147
|
it('a held subscription cannot be unregistered through a foreign tenant scope', async () => {
|
|
146
|
-
if (!(await gateOnWebhooks())) return;
|
|
148
|
+
if (!(await gateOnWebhooks())) return softSkip('blocked', 'precondition not met — `!(await gateOnWebhooks())` returned early (seam, prior step, or fixture unavailable)');
|
|
147
149
|
|
|
148
150
|
// Register under the caller's own tenant (host defaults the scope from
|
|
149
151
|
// the API key when tenantId is omitted — the same registration shape the
|
|
@@ -159,10 +161,10 @@ describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: black-box registration-sur
|
|
|
159
161
|
console.warn(
|
|
160
162
|
`[webhook-tenant-isolation] could not register a probe subscription (${reg.status}); skipping foreign-unregister leg`,
|
|
161
163
|
);
|
|
162
|
-
return;
|
|
164
|
+
return softSkip('blocked', 'precondition not met — `reg.status !== 201` returned early ([webhook-tenant-isolation] could not register a probe subscription (…); skipping foreign-unregister leg) (seam, prior step, or fixture unavailable)');
|
|
163
165
|
}
|
|
164
166
|
const webhookId = (reg.json as { webhookId?: string }).webhookId;
|
|
165
|
-
expect(typeof webhookId, 'registration MUST return webhookId').toBe('string');
|
|
167
|
+
expect(typeof webhookId, req('openwop.it.webhook-tenant-isolation.a-held-subscription-cannot-be-unregistered-through-a-foreign-tenant-scope', 'webhooks.md §Unregister (403 non-member / 404 not-in-scope) + RFC 0093 §A.3', 'registration MUST return webhookId')).toBe('string');
|
|
166
168
|
|
|
167
169
|
try {
|
|
168
170
|
const foreignTenant = `openwop-conformance-foreign-${uniqueSuffix()}`;
|
|
@@ -171,7 +173,7 @@ describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: black-box registration-sur
|
|
|
171
173
|
);
|
|
172
174
|
expect(
|
|
173
175
|
del.status !== 204 && del.status < 500,
|
|
174
|
-
|
|
176
|
+
req('openwop.it.webhook-tenant-isolation.a-held-subscription-cannot-be-unregistered-through-a-foreign-tenant-scope',
|
|
175
177
|
'webhooks.md §Unregister (403 non-member / 404 not-in-scope) + RFC 0093 §A.3',
|
|
176
178
|
`unregister through a foreign tenant scope MUST NOT succeed (got ${del.status})`,
|
|
177
179
|
),
|