@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
|
@@ -25,6 +25,8 @@ import { describe, it, expect } from 'vitest';
|
|
|
25
25
|
import { driver } from '../lib/driver.js';
|
|
26
26
|
import { type SamlVariant } from '../lib/saml-idp.js';
|
|
27
27
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
28
|
+
import { req } from '../lib/requirement-ids.js';
|
|
29
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
28
30
|
|
|
29
31
|
const SAML_PROFILE = 'openwop-auth-saml';
|
|
30
32
|
|
|
@@ -46,10 +48,10 @@ async function readProfiles(): Promise<string[] | null> {
|
|
|
46
48
|
describe('auth-saml-profile: advertisement shape (RFC 0050)', () => {
|
|
47
49
|
it('auth.profiles, when present, is an array of non-empty strings', async () => {
|
|
48
50
|
const profiles = await readProfiles();
|
|
49
|
-
if (profiles === null) return; // host advertises no auth profiles
|
|
51
|
+
if (profiles === null) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `profiles === null` returned early (host advertises no auth profiles)'); // host advertises no auth profiles
|
|
50
52
|
expect(
|
|
51
53
|
Array.isArray(profiles),
|
|
52
|
-
|
|
54
|
+
req('openwop.it.auth-saml-profile.auth-profiles-when-present-is-an-array-of-non-empty-strings', 'auth-profiles.md §Discovery', 'capabilities.auth.profiles MUST be an array'),
|
|
53
55
|
).toBe(true);
|
|
54
56
|
for (const p of profiles) {
|
|
55
57
|
expect(typeof p === 'string' && p.length > 0).toBe(true);
|
|
@@ -58,10 +60,10 @@ describe('auth-saml-profile: advertisement shape (RFC 0050)', () => {
|
|
|
58
60
|
|
|
59
61
|
it('claims openwop-auth-saml as a well-formed profile id when advertised', async () => {
|
|
60
62
|
const profiles = await readProfiles();
|
|
61
|
-
if (profiles === null || !profiles.includes(SAML_PROFILE)) return; // profile not claimed
|
|
63
|
+
if (profiles === null || !profiles.includes(SAML_PROFILE)) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `profiles === null || !profiles.includes(SAML_PROFILE)` returned early (profile not claimed)'); // profile not claimed
|
|
62
64
|
expect(
|
|
63
65
|
profiles.includes(SAML_PROFILE),
|
|
64
|
-
|
|
66
|
+
req('openwop.it.auth-saml-profile.claims-openwop-auth-saml-as-a-well-formed-profile-id-when-advertised', 'RFC 0050 §A', 'openwop-auth-saml MUST appear verbatim in capabilities.auth.profiles when claimed'),
|
|
65
67
|
).toBe(true);
|
|
66
68
|
});
|
|
67
69
|
});
|
|
@@ -83,13 +85,13 @@ describe('auth-saml-profile: assertion validation (RFC 0050 §A — opt-in)', ()
|
|
|
83
85
|
|
|
84
86
|
it('ACCEPTS a valid signed, in-window, non-wrapped assertion (synthetic IdP required)', async () => {
|
|
85
87
|
const profiles = await readProfiles();
|
|
86
|
-
if (profiles === null || !profiles.includes(SAML_PROFILE)) return; // capability-gated
|
|
87
|
-
if (!gated()) return; // opt-in: synthetic-IdP harness not provided
|
|
88
|
+
if (profiles === null || !profiles.includes(SAML_PROFILE)) return softSkip('blocked', 'precondition not met — `profiles === null || !profiles.includes(SAML_PROFILE)` returned early (capability-gated) (seam, prior step, or fixture unavailable)'); // capability-gated
|
|
89
|
+
if (!gated()) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!gated()` returned early (opt-in: synthetic-IdP harness not provided)'); // opt-in: synthetic-IdP harness not provided
|
|
88
90
|
const res = await driver.post('/v1/host/sample/auth/saml/validate', { idpUrl, variant: 'valid' });
|
|
89
|
-
if (res.status === 404) return; // seam unwired
|
|
91
|
+
if (res.status === 404) return softSkip('blocked', 'precondition not met — `res.status === 404` returned early (seam unwired) (seam, prior step, or fixture unavailable)'); // seam unwired
|
|
90
92
|
expect(
|
|
91
93
|
res.status,
|
|
92
|
-
|
|
94
|
+
req('openwop.it.auth-saml-profile.accepts-a-valid-signed-in-window-non-wrapped-assertion-synthetic-idp-required', 'RFC 0050 §A', 'a valid signed, in-window, non-wrapped SAML assertion MUST be accepted (2xx)'),
|
|
93
95
|
).toBeLessThan(400);
|
|
94
96
|
});
|
|
95
97
|
|
|
@@ -108,11 +110,11 @@ describe('auth-saml-profile: assertion validation (RFC 0050 §A — opt-in)', ()
|
|
|
108
110
|
for (const [variant, requirement] of negatives) {
|
|
109
111
|
it(`REJECTS the ${variant} assertion over the seam (synthetic IdP required)`, async () => {
|
|
110
112
|
const profiles = await readProfiles();
|
|
111
|
-
if (profiles === null || !profiles.includes(SAML_PROFILE)) return; // capability-gated
|
|
112
|
-
if (!gated()) return; // opt-in: synthetic-IdP harness not provided
|
|
113
|
+
if (profiles === null || !profiles.includes(SAML_PROFILE)) return softSkip('blocked', 'precondition not met — `profiles === null || !profiles.includes(SAML_PROFILE)` returned early (capability-gated) (seam, prior step, or fixture unavailable)'); // capability-gated
|
|
114
|
+
if (!gated()) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!gated()` returned early (opt-in: synthetic-IdP harness not provided)'); // opt-in: synthetic-IdP harness not provided
|
|
113
115
|
const res = await driver.post('/v1/host/sample/auth/saml/validate', { idpUrl, variant });
|
|
114
|
-
if (res.status === 404) return; // seam unwired
|
|
115
|
-
expect(res.status,
|
|
116
|
+
if (res.status === 404) return softSkip('blocked', 'precondition not met — `res.status === 404` returned early (seam unwired) (seam, prior step, or fixture unavailable)'); // seam unwired
|
|
117
|
+
expect(res.status, req('openwop.it.auth-saml-profile.rejects-the-assertion-over-the-seam-synthetic-idp-required', 'RFC 0050 §A', requirement)).toBeGreaterThanOrEqual(400);
|
|
116
118
|
});
|
|
117
119
|
}
|
|
118
120
|
});
|
|
@@ -19,6 +19,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
19
19
|
import { softSkip } from '../lib/soft-skip.js';
|
|
20
20
|
import { driver } from '../lib/driver.js';
|
|
21
21
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
22
|
+
import { req } from '../lib/requirement-ids.js';
|
|
22
23
|
|
|
23
24
|
const SCIM_PROFILE = 'openwop-auth-scim';
|
|
24
25
|
|
|
@@ -43,7 +44,7 @@ describe('auth-scim-profile: advertisement shape (RFC 0050)', () => {
|
|
|
43
44
|
if (profiles === null || !profiles.includes(SCIM_PROFILE)) return softSkip('inapplicable', 'profile not claimed');
|
|
44
45
|
expect(
|
|
45
46
|
profiles.includes(SCIM_PROFILE),
|
|
46
|
-
|
|
47
|
+
req('openwop.it.auth-scim-profile.claims-openwop-auth-scim-as-a-well-formed-profile-id-when-advertised', 'RFC 0050 §B', 'openwop-auth-scim MUST appear verbatim in capabilities.auth.profiles when claimed'),
|
|
47
48
|
).toBe(true);
|
|
48
49
|
});
|
|
49
50
|
});
|
|
@@ -61,7 +62,7 @@ describe('auth-scim-profile: provisioning roundtrip (RFC 0050 §B — opt-in)',
|
|
|
61
62
|
if (res.status === 404) return softSkip('blocked', 'seam unwired');
|
|
62
63
|
expect(
|
|
63
64
|
res.status,
|
|
64
|
-
|
|
65
|
+
req('openwop.it.auth-scim-profile.provisions-a-scim-user-principal-scim-endpoint-required', 'RFC 0050 §B', 'a SCIM user provisioning MUST succeed (2xx) and upsert an RFC 0048 principal'),
|
|
65
66
|
).toBeLessThan(400);
|
|
66
67
|
});
|
|
67
68
|
});
|
|
@@ -42,6 +42,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
42
42
|
import { softSkip } from '../lib/soft-skip.js';
|
|
43
43
|
import { driver } from '../lib/driver.js';
|
|
44
44
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
45
|
+
import { req } from '../lib/requirement-ids.js';
|
|
45
46
|
|
|
46
47
|
const SAML_PROFILE = 'openwop-auth-saml';
|
|
47
48
|
const SCIM_PROFILE = 'openwop-auth-scim';
|
|
@@ -81,7 +82,7 @@ describe('auth-subject-link-key-class: advertisement shape (RFC 0163 §A)', () =
|
|
|
81
82
|
// profiles) MUST advertise a subjectLinkKey.
|
|
82
83
|
expect(
|
|
83
84
|
typeof auth.subjectLinkKey === 'string' && auth.subjectLinkKey.length > 0,
|
|
84
|
-
|
|
85
|
+
req('openwop.it.auth-subject-link-key-class.subjectlinking-true-requires-a-subjectlinkkey-drawn-from-the-closed-safe-class-e',
|
|
85
86
|
'auth-profiles.md §Subject linking',
|
|
86
87
|
'RFC 0163 §A.1: a host advertising capabilities.auth.subjectLinking:true MUST also advertise capabilities.auth.subjectLinkKey',
|
|
87
88
|
),
|
|
@@ -93,7 +94,7 @@ describe('auth-subject-link-key-class: advertisement shape (RFC 0163 §A)', () =
|
|
|
93
94
|
// host cannot name one.
|
|
94
95
|
expect(
|
|
95
96
|
(SUBJECT_LINK_KEY_CLASSES as readonly string[]).includes(auth.subjectLinkKey ?? ''),
|
|
96
|
-
|
|
97
|
+
req('openwop.it.auth-subject-link-key-class.subjectlinking-true-requires-a-subjectlinkkey-drawn-from-the-closed-safe-class-e',
|
|
97
98
|
'auth-profiles.md §Subject linking',
|
|
98
99
|
`RFC 0163 §A.3: subjectLinkKey MUST be one of ${SUBJECT_LINK_KEY_CLASSES.join(', ')} — a closed enum of allowed CLASSES (not attributes); a mutable/PII key is inexpressible`,
|
|
99
100
|
),
|
|
@@ -108,7 +109,7 @@ describe('auth-subject-link-key-class: advertisement shape (RFC 0163 §A)', () =
|
|
|
108
109
|
const profiles = auth.profiles ?? [];
|
|
109
110
|
expect(
|
|
110
111
|
profiles.includes(SAML_PROFILE) && profiles.includes(SCIM_PROFILE),
|
|
111
|
-
|
|
112
|
+
req('openwop.it.auth-subject-link-key-class.subjectlinkkey-is-only-claimed-alongside-both-saml-and-scim-profiles-rides-the-r',
|
|
112
113
|
'auth-profiles.md §Subject linking',
|
|
113
114
|
'RFC 0163: subjectLinkKey is meaningful only when both openwop-auth-saml and openwop-auth-scim are advertised (it names the class the two lanes are joined on)',
|
|
114
115
|
),
|
|
@@ -146,13 +147,13 @@ describe('auth-subject-link-key-class: same-IdP trust root (RFC 0163 §B — beh
|
|
|
146
147
|
userName: 'r.smith',
|
|
147
148
|
});
|
|
148
149
|
if (provSame.status === 404) return softSkip('blocked', 'SCIM provisioning seam unwired');
|
|
149
|
-
expect(provSame.status,
|
|
150
|
+
expect(provSame.status, req('openwop.it.auth-subject-link-key-class.a-same-idp-link-forms-control-but-a-cross-idp-collision-must-not-link', 'auth-profiles.md §Subject linking', 'SCIM provisioning MUST succeed')).toBeLessThan(400);
|
|
150
151
|
|
|
151
152
|
const sameIdp = await driver.post('/v1/host/sample/auth/saml/validate', { idpUrl: idpAUrl, variant: 'valid', nameId: sameId });
|
|
152
153
|
if (sameIdp.status === 404) return softSkip('blocked', 'SAML validate seam unwired');
|
|
153
154
|
expect(
|
|
154
155
|
(sameIdp.json as { authenticated?: boolean } | undefined)?.authenticated,
|
|
155
|
-
|
|
156
|
+
req('openwop.it.auth-subject-link-key-class.a-same-idp-link-forms-control-but-a-cross-idp-collision-must-not-link', 'auth-profiles.md §Subject linking', 'RFC 0163 §B positive control: a SAML assertion from the SAME IdP trust root as the SCIM lane links and authenticates'),
|
|
156
157
|
).toBe(true);
|
|
157
158
|
|
|
158
159
|
// THE §B.1 MUST — cross-IdP identifier collision. Provision a SCIM user fed
|
|
@@ -168,13 +169,13 @@ describe('auth-subject-link-key-class: same-IdP trust root (RFC 0163 §B — beh
|
|
|
168
169
|
externalId: collideId,
|
|
169
170
|
userName: 'a.other',
|
|
170
171
|
});
|
|
171
|
-
expect(provCross.status,
|
|
172
|
+
expect(provCross.status, req('openwop.it.auth-subject-link-key-class.a-same-idp-link-forms-control-but-a-cross-idp-collision-must-not-link', 'auth-profiles.md §Subject linking', 'SCIM provisioning MUST succeed')).toBeLessThan(400);
|
|
172
173
|
|
|
173
174
|
const crossIdp = await driver.post('/v1/host/sample/auth/saml/validate', { idpUrl: idpBUrl, variant: 'valid', nameId: collideId });
|
|
174
175
|
if (crossIdp.status === 404) return softSkip('blocked', 'SAML validate seam unwired');
|
|
175
176
|
expect(
|
|
176
177
|
(crossIdp.json as { authenticated?: boolean } | undefined)?.authenticated === true,
|
|
177
|
-
|
|
178
|
+
req('openwop.it.auth-subject-link-key-class.a-same-idp-link-forms-control-but-a-cross-idp-collision-must-not-link',
|
|
178
179
|
'auth-profiles.md §Subject linking',
|
|
179
180
|
'RFC 0163 §B.1: a SAML assertion from IdP-B colliding on an identifier the SCIM lane provisioned from IdP-A MUST NOT form a cross-lane link (no cross-IdP identifier collision joins two principals)',
|
|
180
181
|
),
|
|
@@ -27,6 +27,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
27
27
|
import { softSkip, seamAbsent } from '../lib/soft-skip.js';
|
|
28
28
|
import { driver } from '../lib/driver.js';
|
|
29
29
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
30
|
+
import { req } from '../lib/requirement-ids.js';
|
|
30
31
|
|
|
31
32
|
const SAML_PROFILE = 'openwop-auth-saml';
|
|
32
33
|
const SCIM_PROFILE = 'openwop-auth-scim';
|
|
@@ -65,7 +66,7 @@ describe('auth-subject-link: advertisement shape (RFC 0159 §B + RFC 0164 §A.3)
|
|
|
65
66
|
const profiles = auth.profiles ?? [];
|
|
66
67
|
expect(
|
|
67
68
|
profiles.includes(SAML_PROFILE) && profiles.includes(SCIM_PROFILE),
|
|
68
|
-
|
|
69
|
+
req('openwop.it.auth-subject-link.subjectlinking-true-is-only-claimed-alongside-both-saml-and-scim-profiles',
|
|
69
70
|
'auth-profiles.md §Subject linking',
|
|
70
71
|
'a host MUST NOT set capabilities.auth.subjectLinking:true unless BOTH openwop-auth-saml and openwop-auth-scim are advertised',
|
|
71
72
|
),
|
|
@@ -83,14 +84,14 @@ describe('auth-subject-link: advertisement shape (RFC 0159 §B + RFC 0164 §A.3)
|
|
|
83
84
|
// absence alongside both profiles is a conformance FAILURE.
|
|
84
85
|
expect(
|
|
85
86
|
auth.subjectLinking,
|
|
86
|
-
|
|
87
|
+
req('openwop.it.auth-subject-link.advertising-both-profiles-requires-subjectlinking-true-a-subjectlinkkey-rfc-0164',
|
|
87
88
|
'auth-profiles.md §Subject linking (RFC 0164)',
|
|
88
89
|
'RFC 0164 §A.3: a host advertising BOTH openwop-auth-saml and openwop-auth-scim MUST advertise capabilities.auth.subjectLinking:true — the leaver contract follows the profile pair; omitting the flag is the vulnerable pre-0164 shape',
|
|
89
90
|
),
|
|
90
91
|
).toBe(true);
|
|
91
92
|
expect(
|
|
92
93
|
typeof auth.subjectLinkKey === 'string' && SUBJECT_LINK_KEY_CLASSES.includes(auth.subjectLinkKey),
|
|
93
|
-
|
|
94
|
+
req('openwop.it.auth-subject-link.advertising-both-profiles-requires-subjectlinking-true-a-subjectlinkkey-rfc-0164',
|
|
94
95
|
'auth-profiles.md §Subject linking (RFC 0164)',
|
|
95
96
|
`RFC 0164 §A.3 / RFC 0163 §A.1: a host advertising both profiles MUST advertise subjectLinkKey ∈ {${SUBJECT_LINK_KEY_CLASSES.join(', ')}}`,
|
|
96
97
|
),
|
|
@@ -116,7 +117,7 @@ describe('auth-subject-link: cross-lane deactivation (RFC 0159 §A.3 — opt-in)
|
|
|
116
117
|
userName: 'r.smith',
|
|
117
118
|
});
|
|
118
119
|
if (provision.status === 404) return softSkip('blocked', 'seam unwired');
|
|
119
|
-
expect(provision.status,
|
|
120
|
+
expect(provision.status, req('openwop.it.auth-subject-link.a-scim-deactivation-fail-closes-the-linked-saml-identity', 'auth-profiles.md §Subject linking', 'SCIM provisioning MUST succeed')).toBeLessThan(400);
|
|
120
121
|
|
|
121
122
|
// 2. A valid SAML assertion whose persistent NameID equals the externalId
|
|
122
123
|
// links to the same subject and authenticates.
|
|
@@ -124,19 +125,19 @@ describe('auth-subject-link: cross-lane deactivation (RFC 0159 §A.3 — opt-in)
|
|
|
124
125
|
if (before.status === 404) return softSkip('blocked', 'seam unwired');
|
|
125
126
|
expect(
|
|
126
127
|
(before.json as { authenticated?: boolean } | undefined)?.authenticated,
|
|
127
|
-
|
|
128
|
+
req('openwop.it.auth-subject-link.a-scim-deactivation-fail-closes-the-linked-saml-identity', 'auth-profiles.md §Subject linking', 'a valid linked SAML assertion authenticates before deactivation'),
|
|
128
129
|
).toBe(true);
|
|
129
130
|
|
|
130
131
|
// 3. SCIM-deactivate the provisioned user.
|
|
131
132
|
const deactivate = await driver.post('/v1/host/sample/auth/scim/provision', { scimUrl, op: 'deactivate-user', externalId });
|
|
132
|
-
expect(deactivate.status,
|
|
133
|
+
expect(deactivate.status, req('openwop.it.auth-subject-link.a-scim-deactivation-fail-closes-the-linked-saml-identity', 'auth-profiles.md §Subject linking', 'SCIM deactivation MUST succeed')).toBeLessThan(400);
|
|
133
134
|
|
|
134
135
|
// 4. THE CONTRACT: a subsequent SAML assertion for the linked subject MUST
|
|
135
136
|
// NOT yield an authorized decision (fail-closed across the link).
|
|
136
137
|
const after = await driver.post('/v1/host/sample/auth/saml/validate', { idpUrl, variant: 'valid', nameId: externalId });
|
|
137
138
|
expect(
|
|
138
139
|
(after.json as { authenticated?: boolean } | undefined)?.authenticated === true,
|
|
139
|
-
|
|
140
|
+
req('openwop.it.auth-subject-link.a-scim-deactivation-fail-closes-the-linked-saml-identity',
|
|
140
141
|
'auth-profiles.md §Subject linking',
|
|
141
142
|
'RFC 0159 §A.3: after a SCIM deactivation the LINKED SAML identity MUST be denied (a provisioned leaver cannot still SSO in)',
|
|
142
143
|
),
|
|
@@ -169,9 +170,9 @@ describe('auth-subject-link: link-key hygiene (RFC 0159 §A.2 — opt-in)', () =
|
|
|
169
170
|
// Conforming: the host rejected a mutable-key link outright.
|
|
170
171
|
expect(
|
|
171
172
|
link.status,
|
|
172
|
-
|
|
173
|
+
req('openwop.it.auth-subject-link.a-mutable-pii-link-key-email-never-produces-a-cross-lane-pass', 'auth-profiles.md §Subject linking', 'RFC 0159 §A.2: a link on a mutable/PII key (email) MUST be rejected'),
|
|
173
174
|
).toBeGreaterThanOrEqual(400);
|
|
174
|
-
return;
|
|
175
|
+
return softSkip('blocked', 'precondition not met — `link.status >= 400` returned early (seam, prior step, or fixture unavailable)');
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
// If the host accepted the request, it MUST NOT have formed a cross-lane
|
|
@@ -183,7 +184,7 @@ describe('auth-subject-link: link-key hygiene (RFC 0159 §A.2 — opt-in)', () =
|
|
|
183
184
|
const other = await driver.post('/v1/host/sample/auth/saml/validate', { idpUrl, variant: 'valid', nameId: 'idp-op-DIFFERENT' });
|
|
184
185
|
expect(
|
|
185
186
|
(other.json as { linkedDenied?: boolean } | undefined)?.linkedDenied === true,
|
|
186
|
-
|
|
187
|
+
req('openwop.it.auth-subject-link.a-mutable-pii-link-key-email-never-produces-a-cross-lane-pass',
|
|
187
188
|
'auth-profiles.md §Subject linking',
|
|
188
189
|
'RFC 0159 §A.2: a mutable-key (email) operation MUST NOT drive a cross-lane deny on any opaque subject',
|
|
189
190
|
),
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { describe, it, expect } from 'vitest';
|
|
10
10
|
import { driver } from '../lib/driver.js';
|
|
11
|
+
import { req } from '../lib/requirement-ids.js';
|
|
11
12
|
|
|
12
13
|
const KNOWN_AUTHED_PATH = '/v1/runs';
|
|
13
14
|
|
|
@@ -19,17 +20,17 @@ describe('auth: missing credential', () => {
|
|
|
19
20
|
{ authenticated: false },
|
|
20
21
|
);
|
|
21
22
|
|
|
22
|
-
expect(res.status,
|
|
23
|
+
expect(res.status, req('openwop.it.auth.returns-401-with-canonical-error-envelope-per-auth-md-3',
|
|
23
24
|
'auth.md §3',
|
|
24
25
|
'request without Authorization header MUST return 401',
|
|
25
26
|
)).toBe(401);
|
|
26
27
|
|
|
27
28
|
const body = res.json as { error?: unknown; message?: unknown } | undefined;
|
|
28
|
-
expect(typeof body?.error,
|
|
29
|
+
expect(typeof body?.error, req('openwop.it.auth.returns-401-with-canonical-error-envelope-per-auth-md-3',
|
|
29
30
|
'auth.md §3 + rest-endpoints.md error envelope',
|
|
30
31
|
'response body MUST include `error` (machine code) string',
|
|
31
32
|
)).toBe('string');
|
|
32
|
-
expect(typeof body?.message,
|
|
33
|
+
expect(typeof body?.message, req('openwop.it.auth.returns-401-with-canonical-error-envelope-per-auth-md-3',
|
|
33
34
|
'auth.md §3 + rest-endpoints.md error envelope',
|
|
34
35
|
'response body MUST include `message` (human description) string',
|
|
35
36
|
)).toBe('string');
|
|
@@ -47,7 +48,7 @@ describe('auth: invalid credential', () => {
|
|
|
47
48
|
},
|
|
48
49
|
);
|
|
49
50
|
|
|
50
|
-
expect(res.status,
|
|
51
|
+
expect(res.status, req('openwop.it.auth.returns-401-not-200-not-403-per-auth-md-3',
|
|
51
52
|
'auth.md §3',
|
|
52
53
|
'request with invalid Authorization MUST return 401, not 403',
|
|
53
54
|
)).toBe(401);
|
|
@@ -26,6 +26,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
26
26
|
import { softSkip } from '../lib/soft-skip.js';
|
|
27
27
|
import { driver } from '../lib/driver.js';
|
|
28
28
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
29
|
+
import { req } from '../lib/requirement-ids.js';
|
|
29
30
|
|
|
30
31
|
interface DiscoveryAuthorization {
|
|
31
32
|
supported?: boolean;
|
|
@@ -50,7 +51,7 @@ describe('authorization-fail-closed: advertisement shape (RFC 0049 §C)', () =>
|
|
|
50
51
|
if (!authz?.supported || authz.failClosed === undefined) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!authz?.supported || authz.failClosed === undefined` returned early');
|
|
51
52
|
expect(
|
|
52
53
|
authz.failClosed,
|
|
53
|
-
|
|
54
|
+
req('openwop.it.authorization-fail-closed.failclosed-is-exactly-true-when-authorization-is-supported', 'RFC 0049 §C', 'capabilities.authorization.failClosed MUST be `true`'),
|
|
54
55
|
).toBe(true);
|
|
55
56
|
});
|
|
56
57
|
});
|
|
@@ -73,7 +74,7 @@ describe('authorization-fail-closed: absent/unseeded role MUST deny (RFC 0049 §
|
|
|
73
74
|
const decision = res.json as { allowed?: boolean } | undefined;
|
|
74
75
|
expect(
|
|
75
76
|
decision?.allowed,
|
|
76
|
-
|
|
77
|
+
req('openwop.it.authorization-fail-closed.a-decision-for-an-unseeded-role-principal-resolves-allowed-false',
|
|
77
78
|
'SECURITY/invariants.yaml authorization-fail-closed',
|
|
78
79
|
'an absent/unseeded role MUST deny (allowed=false); the host MUST NOT default-allow',
|
|
79
80
|
),
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
import { describe, it, expect } from 'vitest';
|
|
22
22
|
import { driver } from '../lib/driver.js';
|
|
23
23
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
24
|
+
import { req } from '../lib/requirement-ids.js';
|
|
25
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
24
26
|
|
|
25
27
|
interface DiscoveryRole {
|
|
26
28
|
role?: string;
|
|
@@ -48,10 +50,10 @@ async function readAuthorization(): Promise<DiscoveryAuthorization | null> {
|
|
|
48
50
|
describe('authorization-roles-shape: advertisement shape (RFC 0049 §A)', () => {
|
|
49
51
|
it('capabilities.authorization is either absent or well-formed', async () => {
|
|
50
52
|
const authz = await readAuthorization();
|
|
51
|
-
if (authz === null) return; // host doesn't advertise authorization at all
|
|
53
|
+
if (authz === null) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `authz === null` returned early (host doesn\'t advertise authorization at all)'); // host doesn't advertise authorization at all
|
|
52
54
|
expect(
|
|
53
55
|
typeof authz.supported,
|
|
54
|
-
|
|
56
|
+
req('openwop.it.authorization-roles-shape.capabilities-authorization-is-either-absent-or-well-formed',
|
|
55
57
|
'capabilities.schema.json §authorization',
|
|
56
58
|
'capabilities.authorization.supported MUST be a boolean when authorization is advertised',
|
|
57
59
|
),
|
|
@@ -60,24 +62,24 @@ describe('authorization-roles-shape: advertisement shape (RFC 0049 §A)', () =>
|
|
|
60
62
|
|
|
61
63
|
it('failClosed, when present, is exactly true (RFC 0049 §C)', async () => {
|
|
62
64
|
const authz = await readAuthorization();
|
|
63
|
-
if (!authz?.supported || authz.failClosed === undefined) return;
|
|
65
|
+
if (!authz?.supported || authz.failClosed === undefined) return softSkip('blocked', 'precondition not met — `!authz?.supported || authz.failClosed === undefined` returned early (seam, prior step, or fixture unavailable)');
|
|
64
66
|
expect(
|
|
65
67
|
authz.failClosed,
|
|
66
|
-
|
|
68
|
+
req('openwop.it.authorization-roles-shape.failclosed-when-present-is-exactly-true-rfc-0049-c', 'RFC 0049 §C', 'capabilities.authorization.failClosed MUST be `true` (fail-closed)'),
|
|
67
69
|
).toBe(true);
|
|
68
70
|
});
|
|
69
71
|
|
|
70
72
|
it('every advertised role has a non-empty role name + a scopes array', async () => {
|
|
71
73
|
const authz = await readAuthorization();
|
|
72
|
-
if (!authz?.supported || authz.roles === undefined) return;
|
|
74
|
+
if (!authz?.supported || authz.roles === undefined) return softSkip('blocked', 'precondition not met — `!authz?.supported || authz.roles === undefined` returned early (seam, prior step, or fixture unavailable)');
|
|
73
75
|
for (const entry of authz.roles) {
|
|
74
76
|
expect(
|
|
75
77
|
typeof entry.role === 'string' && entry.role.length > 0,
|
|
76
|
-
|
|
78
|
+
req('openwop.it.authorization-roles-shape.every-advertised-role-has-a-non-empty-role-name-a-scopes-array', 'RFC 0049 §A', 'each capabilities.authorization.roles[] entry MUST declare a non-empty role'),
|
|
77
79
|
).toBe(true);
|
|
78
80
|
expect(
|
|
79
81
|
Array.isArray(entry.scopes),
|
|
80
|
-
|
|
82
|
+
req('openwop.it.authorization-roles-shape.every-advertised-role-has-a-non-empty-role-name-a-scopes-array', 'RFC 0049 §A', 'each role MUST declare a scopes array'),
|
|
81
83
|
).toBe(true);
|
|
82
84
|
}
|
|
83
85
|
});
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
import { describe, it, expect } from 'vitest';
|
|
11
11
|
import { driver } from '../lib/driver.js';
|
|
12
12
|
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
13
|
+
import { req } from '../lib/requirement-ids.js';
|
|
14
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
13
15
|
|
|
14
16
|
interface DiscoveryDoc {
|
|
15
17
|
capabilities?: Record<string, unknown>;
|
|
@@ -30,10 +32,10 @@ async function call(tenantId: string, op: string, args: Record<string, unknown>)
|
|
|
30
32
|
describe('blob-cross-tenant-isolation: advertisement shape (RFC 0019)', () => {
|
|
31
33
|
it('capabilities.blobStorage is either absent or a well-formed object', async () => {
|
|
32
34
|
const cap = await readCap();
|
|
33
|
-
if (cap === null) return;
|
|
35
|
+
if (cap === null) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap === null` returned early');
|
|
34
36
|
expect(
|
|
35
37
|
typeof cap.supported,
|
|
36
|
-
|
|
38
|
+
req('openwop.it.blob-cross-tenant-isolation.capabilities-blobstorage-is-either-absent-or-a-well-formed-object',
|
|
37
39
|
'capabilities.schema.json §blobStorage',
|
|
38
40
|
'capabilities.blobStorage.supported MUST be a boolean when present',
|
|
39
41
|
),
|
|
@@ -44,7 +46,7 @@ describe('blob-cross-tenant-isolation: advertisement shape (RFC 0019)', () => {
|
|
|
44
46
|
describe('blob-cross-tenant-isolation: behavioral (RFC 0019 §B point 1)', () => {
|
|
45
47
|
it('put under tenant A → get under tenant B with same key returns found:false', async () => {
|
|
46
48
|
const cap = await readCap();
|
|
47
|
-
if (!cap || cap.supported !== true) return;
|
|
49
|
+
if (!cap || cap.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!cap || cap.supported !== true` returned early');
|
|
48
50
|
const key = `xtenant-blob-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
49
51
|
|
|
50
52
|
const putRes = await call('tenant-a', 'put', {
|
|
@@ -53,15 +55,15 @@ describe('blob-cross-tenant-isolation: behavioral (RFC 0019 §B point 1)', () =>
|
|
|
53
55
|
contentBase64: Buffer.from('from-A').toString('base64'),
|
|
54
56
|
contentType: 'text/plain',
|
|
55
57
|
});
|
|
56
|
-
if (putRes.status === 404) return;
|
|
57
|
-
expect(putRes.status, 'put MUST succeed').toBe(200);
|
|
58
|
+
if (putRes.status === 404) return softSkip('blocked', 'precondition not met — `putRes.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
59
|
+
expect(putRes.status, req('openwop.it.blob-cross-tenant-isolation.put-under-tenant-a-get-under-tenant-b-with-same-key-returns-found-false', 'RFC 0019 §B point 1', 'put MUST succeed')).toBe(200);
|
|
58
60
|
|
|
59
61
|
const getRes = await call('tenant-b', 'get', { bucket: 'default', key });
|
|
60
62
|
expect(getRes.status).toBe(200);
|
|
61
63
|
const body = getRes.json as { found?: boolean };
|
|
62
64
|
expect(
|
|
63
65
|
body.found,
|
|
64
|
-
|
|
66
|
+
req('openwop.it.blob-cross-tenant-isolation.put-under-tenant-a-get-under-tenant-b-with-same-key-returns-found-false', 'RFC 0019 §B point 1', 'tenant B MUST NOT retrieve tenant A blob at same key'),
|
|
65
67
|
).toBe(false);
|
|
66
68
|
});
|
|
67
69
|
});
|
|
@@ -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 DiscoveryDoc {
|
|
21
23
|
capabilities?: Record<string, unknown>;
|
|
@@ -32,10 +34,10 @@ async function readCap(): Promise<Record<string, unknown> | null> {
|
|
|
32
34
|
describe('blob-presign-expiry: advertisement shape (RFC 0019)', () => {
|
|
33
35
|
it('capabilities.blobStorage is either absent or a well-formed object', async () => {
|
|
34
36
|
const cap = await readCap();
|
|
35
|
-
if (cap === null) return; // host doesn't advertise — skip
|
|
37
|
+
if (cap === null) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap === null` returned early (host doesn\'t advertise — skip)'); // host doesn't advertise — skip
|
|
36
38
|
expect(
|
|
37
39
|
typeof cap.supported,
|
|
38
|
-
|
|
40
|
+
req('openwop.it.blob-presign-expiry.capabilities-blobstorage-is-either-absent-or-a-well-formed-object',
|
|
39
41
|
'capabilities.schema.json §blobStorage',
|
|
40
42
|
'capabilities.blobStorage.supported MUST be a boolean when present',
|
|
41
43
|
),
|
|
@@ -44,17 +46,17 @@ describe('blob-presign-expiry: advertisement shape (RFC 0019)', () => {
|
|
|
44
46
|
|
|
45
47
|
it('presignSupported is a boolean when set', async () => {
|
|
46
48
|
const cap = await readCap();
|
|
47
|
-
if (!cap || cap.supported !== true) return;
|
|
49
|
+
if (!cap || cap.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!cap || cap.supported !== true` returned early');
|
|
48
50
|
const subParts = ["presignSupported"];
|
|
49
51
|
let sub: unknown = cap;
|
|
50
52
|
for (const p of subParts) {
|
|
51
53
|
if (sub && typeof sub === 'object') sub = (sub as Record<string, unknown>)[p];
|
|
52
54
|
else { sub = undefined; break; }
|
|
53
55
|
}
|
|
54
|
-
if (sub === undefined) return; // optional sub-field
|
|
56
|
+
if (sub === undefined) return softSkip('blocked', 'precondition not met — `sub === undefined` returned early (optional sub-field) (seam, prior step, or fixture unavailable)'); // optional sub-field
|
|
55
57
|
expect(
|
|
56
58
|
typeof sub,
|
|
57
|
-
|
|
59
|
+
req('openwop.it.blob-presign-expiry.presignsupported-is-a-boolean-when-set',
|
|
58
60
|
'RFC 0019 §A',
|
|
59
61
|
'blobStorage.presignSupported MUST be boolean when present',
|
|
60
62
|
),
|
|
@@ -69,7 +71,7 @@ async function call(op: string, args: Record<string, unknown>) {
|
|
|
69
71
|
describe('blob-presign-expiry: behavioral (RFC 0019 §B point 1)', () => {
|
|
70
72
|
it('presigned URL MUST resolve to the blob inside its TTL window and return 403 after expiry', async () => {
|
|
71
73
|
const probe = await call('get', { key: '__probe__' });
|
|
72
|
-
if (probe.status === 404) return; // seam not exposed
|
|
74
|
+
if (probe.status === 404) return softSkip('blocked', 'precondition not met — `probe.status === 404` returned early (seam not exposed) (seam, prior step, or fixture unavailable)'); // seam not exposed
|
|
73
75
|
const key = `pre-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
74
76
|
const contentBase64 = Buffer.from('presigned-payload').toString('base64');
|
|
75
77
|
await call('put', { key, contentBase64, contentType: 'text/plain' });
|
|
@@ -78,14 +80,14 @@ describe('blob-presign-expiry: behavioral (RFC 0019 §B point 1)', () => {
|
|
|
78
80
|
const presign = await call('presign', { key, expiresInSeconds: 2 });
|
|
79
81
|
expect(presign.status).toBe(200);
|
|
80
82
|
const body = presign.json as { url?: string; expiresAtMs?: number };
|
|
81
|
-
expect(typeof body.url, 'presign MUST return a URL').toBe('string');
|
|
83
|
+
expect(typeof body.url, req('openwop.it.blob-presign-expiry.presigned-url-must-resolve-to-the-blob-inside-its-ttl-window-and-return-403-afte', 'RFC 0019 §B point 1', 'presign MUST return a URL')).toBe('string');
|
|
82
84
|
|
|
83
85
|
// Fetch within the window — MUST return 200 + the bytes
|
|
84
86
|
const within = await driver.get(body.url!);
|
|
85
|
-
if (within.status === 404) return; // host doesn't expose the resolver route — soft-skip the expiry side too
|
|
87
|
+
if (within.status === 404) return softSkip('blocked', 'precondition not met — `within.status === 404` returned early (host doesn\'t expose the resolver route — soft-skip the expiry side too) (seam, prior step, or fixture unavailable)'); // host doesn't expose the resolver route — soft-skip the expiry side too
|
|
86
88
|
expect(
|
|
87
89
|
within.status,
|
|
88
|
-
|
|
90
|
+
req('openwop.it.blob-presign-expiry.presigned-url-must-resolve-to-the-blob-inside-its-ttl-window-and-return-403-afte', 'RFC 0019 §B point 1', 'presigned URL MUST resolve to 200 within its TTL window'),
|
|
89
91
|
).toBe(200);
|
|
90
92
|
|
|
91
93
|
// Wait past expiry (TTL=2s + 1s buffer)
|
|
@@ -94,7 +96,7 @@ describe('blob-presign-expiry: behavioral (RFC 0019 §B point 1)', () => {
|
|
|
94
96
|
const after = await driver.get(body.url!);
|
|
95
97
|
expect(
|
|
96
98
|
after.status,
|
|
97
|
-
|
|
99
|
+
req('openwop.it.blob-presign-expiry.presigned-url-must-resolve-to-the-blob-inside-its-ttl-window-and-return-403-afte', 'RFC 0019 §B point 1', 'presigned URL MUST return 403 after TTL expiry'),
|
|
98
100
|
).toBe(403);
|
|
99
101
|
});
|
|
100
102
|
});
|
|
Binary file
|
|
@@ -29,11 +29,12 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import { describe, it, expect } from 'vitest';
|
|
32
|
-
import { driver } from '../lib/driver.js';
|
|
33
32
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
34
33
|
import { readBudgetCap, driveBudgetRun, BUDGET_CAP_KINDS, BUDGET_CONTENT_FORBIDDEN } from '../lib/budgetPolicy.js';
|
|
35
34
|
import { queryTestEvents, isEventLogSeamAvailable, resetTestSeam } from '../lib/event-log-query.js';
|
|
36
35
|
import type { TestEvent } from '../lib/event-log-query.js';
|
|
36
|
+
import { req } from '../lib/requirement-ids.js';
|
|
37
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
37
38
|
|
|
38
39
|
function seq(events: TestEvent[], type: string): number {
|
|
39
40
|
const e = events.find((x) => x.type === type);
|
|
@@ -45,7 +46,7 @@ function expectContentFree(events: TestEvent[]): void {
|
|
|
45
46
|
for (const f of BUDGET_CONTENT_FORBIDDEN) {
|
|
46
47
|
expect(
|
|
47
48
|
!(f in e.payload),
|
|
48
|
-
|
|
49
|
+
req('openwop.it.budget-enforcement.runs-the-reserved-consumed-threshold-exhausted-cap-breached-run-failed-chain-ref', 'RFC 0084 §F (SR-1) / budget-no-pricing-leak', `budget.* MUST be content-free (no ${f})`),
|
|
49
50
|
).toBe(true);
|
|
50
51
|
}
|
|
51
52
|
}
|
|
@@ -55,11 +56,11 @@ describe('budget-enforcement (RFC 0084 §C/§D)', () => {
|
|
|
55
56
|
it('runs the reserved→consumed→threshold→exhausted→cap.breached→run.failed chain, refuses denied models, and honors advisory mode', async () => {
|
|
56
57
|
const cap = await readBudgetCap();
|
|
57
58
|
if (!behaviorGate('openwop-budget-enforcement', cap?.supported === true)) return;
|
|
58
|
-
if (!(await isEventLogSeamAvailable())) return; // event-log seam absent — soft-skip
|
|
59
|
+
if (!(await isEventLogSeamAvailable())) return softSkip('blocked', 'precondition not met — `!(await isEventLogSeamAvailable())` returned early (event-log seam absent — soft-skip) (seam, prior step, or fixture unavailable)'); // event-log seam absent — soft-skip
|
|
59
60
|
|
|
60
61
|
// ---- Leg 1: hard cost exhaust (§C/§D) -------------------------------
|
|
61
62
|
const hard = await driveBudgetRun({ scenario: 'hard-cost-exhaust' });
|
|
62
|
-
if (hard === null) return; // budget seam absent — soft-skip the whole behavior
|
|
63
|
+
if (hard === null) return softSkip('blocked', 'precondition not met — `hard === null` returned early (budget seam absent — soft-skip the whole behavior) (seam, prior step, or fixture unavailable)'); // budget seam absent — soft-skip the whole behavior
|
|
63
64
|
if (hard.runId) {
|
|
64
65
|
const q = await queryTestEvents(hard.runId);
|
|
65
66
|
if (q.ok) {
|
|
@@ -72,39 +73,39 @@ describe('budget-enforcement (RFC 0084 §C/§D)', () => {
|
|
|
72
73
|
|
|
73
74
|
expect(
|
|
74
75
|
reserved >= 0 && exhausted >= 0,
|
|
75
|
-
|
|
76
|
+
req('openwop.it.budget-enforcement.runs-the-reserved-consumed-threshold-exhausted-cap-breached-run-failed-chain-ref', 'budget-policy.md §C', 'a hard budget run MUST emit budget.reserved + budget.exhausted'),
|
|
76
77
|
).toBe(true);
|
|
77
78
|
// §C ordering: reserved < threshold.crossed < exhausted < run.failed.
|
|
78
79
|
if (threshold >= 0) {
|
|
79
80
|
expect(
|
|
80
81
|
reserved < threshold && threshold < exhausted,
|
|
81
|
-
|
|
82
|
+
req('openwop.it.budget-enforcement.runs-the-reserved-consumed-threshold-exhausted-cap-breached-run-failed-chain-ref', 'RFC 0084 §C', 'ordering MUST be reserved < threshold.crossed < exhausted'),
|
|
82
83
|
).toBe(true);
|
|
83
84
|
const tc = ev.find((e) => e.type === 'budget.threshold.crossed');
|
|
84
85
|
expect(
|
|
85
86
|
typeof tc?.payload.percent === 'number',
|
|
86
|
-
|
|
87
|
+
req('openwop.it.budget-enforcement.runs-the-reserved-consumed-threshold-exhausted-cap-breached-run-failed-chain-ref', 'run-event-payloads.schema.json#budgetThresholdCrossed', 'threshold.crossed MUST carry a numeric percent'),
|
|
87
88
|
).toBe(true);
|
|
88
89
|
}
|
|
89
90
|
// §D hard-stop: exhausted → cap.breached{budget-*} → run.failed{budget_exhausted}.
|
|
90
91
|
expect(
|
|
91
92
|
capBreached !== undefined,
|
|
92
|
-
|
|
93
|
+
req('openwop.it.budget-enforcement.runs-the-reserved-consumed-threshold-exhausted-cap-breached-run-failed-chain-ref', 'RFC 0084 §D', 'exhaustion MUST emit cap.breached with a budget-* kind'),
|
|
93
94
|
).toBe(true);
|
|
94
95
|
if (capBreached) {
|
|
95
96
|
expect(
|
|
96
97
|
BUDGET_CAP_KINDS.includes(capBreached.payload.kind as string),
|
|
97
|
-
|
|
98
|
+
req('openwop.it.budget-enforcement.runs-the-reserved-consumed-threshold-exhausted-cap-breached-run-failed-chain-ref', 'RFC 0084 §D', 'cap.breached.kind MUST be in the closed budget vocabulary'),
|
|
98
99
|
).toBe(true);
|
|
99
100
|
expect(
|
|
100
101
|
exhausted <= capBreached.sequence && capBreached.sequence <= failed,
|
|
101
|
-
|
|
102
|
+
req('openwop.it.budget-enforcement.runs-the-reserved-consumed-threshold-exhausted-cap-breached-run-failed-chain-ref', 'RFC 0084 §D', 'ordering MUST be exhausted ≤ cap.breached ≤ run.failed'),
|
|
102
103
|
).toBe(true);
|
|
103
104
|
}
|
|
104
105
|
const failedEvt = ev.find((e) => e.type === 'run.failed');
|
|
105
106
|
expect(
|
|
106
107
|
failedEvt?.payload.error === 'budget_exhausted',
|
|
107
|
-
|
|
108
|
+
req('openwop.it.budget-enforcement.runs-the-reserved-consumed-threshold-exhausted-cap-breached-run-failed-chain-ref', 'RFC 0084 §D', 'a hard-budget overrun MUST fail the run with error budget_exhausted'),
|
|
108
109
|
).toBe(true);
|
|
109
110
|
expectContentFree(ev);
|
|
110
111
|
}
|
|
@@ -115,11 +116,11 @@ describe('budget-enforcement (RFC 0084 §C/§D)', () => {
|
|
|
115
116
|
if (denied !== null) {
|
|
116
117
|
expect(
|
|
117
118
|
denied.error === 'budget_model_denied',
|
|
118
|
-
|
|
119
|
+
req('openwop.it.budget-enforcement.runs-the-reserved-consumed-threshold-exhausted-cap-breached-run-failed-chain-ref', 'RFC 0084 §D', 'a model violating the budget allow/deny list MUST be refused with budget_model_denied'),
|
|
119
120
|
).toBe(true);
|
|
120
121
|
expect(
|
|
121
122
|
denied.modelCalled !== true,
|
|
122
|
-
|
|
123
|
+
req('openwop.it.budget-enforcement.runs-the-reserved-consumed-threshold-exhausted-cap-breached-run-failed-chain-ref', 'RFC 0084 §D', 'a denied model MUST be refused BEFORE the provider call (fail-closed)'),
|
|
123
124
|
).toBe(true);
|
|
124
125
|
}
|
|
125
126
|
|
|
@@ -139,7 +140,7 @@ describe('budget-enforcement (RFC 0084 §C/§D)', () => {
|
|
|
139
140
|
if (hasBudgetEvents) {
|
|
140
141
|
expect(
|
|
141
142
|
!stopped,
|
|
142
|
-
|
|
143
|
+
req('openwop.it.budget-enforcement.runs-the-reserved-consumed-threshold-exhausted-cap-breached-run-failed-chain-ref', 'RFC 0084 §D', 'advisory enforcement MUST emit budget.* events without stopping the run'),
|
|
143
144
|
).toBe(true);
|
|
144
145
|
}
|
|
145
146
|
expectContentFree(ev);
|