@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
|
@@ -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);
|
|
@@ -35,8 +35,7 @@ import { join } from 'node:path';
|
|
|
35
35
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
36
36
|
import addFormats from 'ajv-formats';
|
|
37
37
|
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
38
|
-
|
|
39
|
-
const why = (specRef: string, requirement: string): string => `${specRef} — ${requirement}`;
|
|
38
|
+
import { req } from '../lib/requirement-ids.js';
|
|
40
39
|
|
|
41
40
|
function loadSchema(name: string): Record<string, unknown> {
|
|
42
41
|
return JSON.parse(readFileSync(join(SCHEMAS_DIR, name), 'utf8')) as Record<string, unknown>;
|
|
@@ -52,17 +51,17 @@ describe('budget-policy-shape: BudgetPolicy (RFC 0084 §A, server-free)', () =>
|
|
|
52
51
|
it('a conforming budget policy validates', () => {
|
|
53
52
|
expect(
|
|
54
53
|
validate({ maxTokens: 200000, maxCostUsd: 1.0, maxToolCalls: 50, maxRetries: 10, modelAllow: ['claude-*'], modelDeny: ['gpt-4-32k'], thresholdPercent: 80, onExhaustion: 'fail' }),
|
|
55
|
-
|
|
54
|
+
req('openwop.it.budget-policy-shape.a-conforming-budget-policy-validates', 'budget-policy.md §A', 'a conforming BudgetPolicy MUST validate'),
|
|
56
55
|
).toBe(true);
|
|
57
56
|
});
|
|
58
57
|
|
|
59
58
|
it('the orthogonality guard: a wall-time field is rejected (it is RFC 0058 runTimeoutMs)', () => {
|
|
60
|
-
expect(validate({ maxCostUsd: 1.0, maxWallTimeMs: 60000 }),
|
|
59
|
+
expect(validate({ maxCostUsd: 1.0, maxWallTimeMs: 60000 }), req('openwop.it.budget-policy-shape.the-orthogonality-guard-a-wall-time-field-is-rejected-it-is-rfc-0058-runtimeoutm', 'budget-policy.md §A/§E', 'wall-time is NOT a budget dimension')).toBe(false);
|
|
61
60
|
});
|
|
62
61
|
|
|
63
62
|
it('rejects an out-of-range thresholdPercent and an out-of-enum onExhaustion', () => {
|
|
64
|
-
expect(validate({ thresholdPercent: 120 }),
|
|
65
|
-
expect(validate({ onExhaustion: 'explode' }),
|
|
63
|
+
expect(validate({ thresholdPercent: 120 }), req('openwop.it.budget-policy-shape.rejects-an-out-of-range-thresholdpercent-and-an-out-of-enum-onexhaustion', 'budget-policy.md §A', 'thresholdPercent MUST be 0..100')).toBe(false);
|
|
64
|
+
expect(validate({ onExhaustion: 'explode' }), req('openwop.it.budget-policy-shape.rejects-an-out-of-range-thresholdpercent-and-an-out-of-enum-onexhaustion', 'budget-policy.md §A', 'onExhaustion is a closed enum')).toBe(false);
|
|
66
65
|
});
|
|
67
66
|
});
|
|
68
67
|
|
|
@@ -76,21 +75,21 @@ describe('budget-policy-shape: budget.* events + cap.breached kinds (RFC 0084 §
|
|
|
76
75
|
} as Record<string, unknown>);
|
|
77
76
|
|
|
78
77
|
it('the four budget.* payloads validate conforming content-free records', () => {
|
|
79
|
-
expect(compile('budgetReserved')({ effectiveBudget: { maxCostUsd: 1.0 }, scope: 'run' }),
|
|
80
|
-
expect(compile('budgetConsumed')({ dimension: 'cost', consumed: 0.7, limit: 1.0, remaining: 0.3 }),
|
|
81
|
-
expect(compile('budgetThresholdCrossed')({ dimension: 'cost', consumed: 0.8, limit: 1.0, percent: 80 }),
|
|
82
|
-
expect(compile('budgetExhausted')({ dimension: 'cost', consumed: 1.02, limit: 1.0 }),
|
|
78
|
+
expect(compile('budgetReserved')({ effectiveBudget: { maxCostUsd: 1.0 }, scope: 'run' }), req('openwop.it.budget-policy-shape.the-four-budget-payloads-validate-conforming-content-free-records', 'budget-policy.md §C', 'budget.reserved MUST validate')).toBe(true);
|
|
79
|
+
expect(compile('budgetConsumed')({ dimension: 'cost', consumed: 0.7, limit: 1.0, remaining: 0.3 }), req('openwop.it.budget-policy-shape.the-four-budget-payloads-validate-conforming-content-free-records', 'budget-policy.md §C', 'budget.consumed MUST validate')).toBe(true);
|
|
80
|
+
expect(compile('budgetThresholdCrossed')({ dimension: 'cost', consumed: 0.8, limit: 1.0, percent: 80 }), req('openwop.it.budget-policy-shape.the-four-budget-payloads-validate-conforming-content-free-records', 'budget-policy.md §C', 'budget.threshold.crossed MUST validate')).toBe(true);
|
|
81
|
+
expect(compile('budgetExhausted')({ dimension: 'cost', consumed: 1.02, limit: 1.0 }), req('openwop.it.budget-policy-shape.the-four-budget-payloads-validate-conforming-content-free-records', 'budget-policy.md §C', 'budget.exhausted MUST validate')).toBe(true);
|
|
83
82
|
});
|
|
84
83
|
|
|
85
84
|
it('rejects an out-of-enum dimension and a missing required field', () => {
|
|
86
|
-
expect(compile('budgetConsumed')({ dimension: 'vibes', consumed: 1, limit: 2 }),
|
|
87
|
-
expect(compile('budgetExhausted')({ dimension: 'cost', consumed: 1.0 }),
|
|
85
|
+
expect(compile('budgetConsumed')({ dimension: 'vibes', consumed: 1, limit: 2 }), req('openwop.it.budget-policy-shape.rejects-an-out-of-enum-dimension-and-a-missing-required-field', 'budget-policy.md §C', 'dimension is a closed enum')).toBe(false);
|
|
86
|
+
expect(compile('budgetExhausted')({ dimension: 'cost', consumed: 1.0 }), req('openwop.it.budget-policy-shape.rejects-an-out-of-enum-dimension-and-a-missing-required-field', 'budget-policy.md §C', 'limit is REQUIRED')).toBe(false);
|
|
88
87
|
});
|
|
89
88
|
|
|
90
89
|
it('the cap.breached kind enum carries the four budget-* values', () => {
|
|
91
90
|
const kinds = ((payloads.$defs as Record<string, { properties?: Record<string, { enum?: string[] }> }>).capBreached.properties?.kind?.enum) ?? [];
|
|
92
91
|
for (const k of ['budget-tokens', 'budget-cost', 'budget-tool-calls', 'budget-retries']) {
|
|
93
|
-
expect(kinds.includes(k),
|
|
92
|
+
expect(kinds.includes(k), req('openwop.it.budget-policy-shape.the-cap-breached-kind-enum-carries-the-four-budget-values', 'budget-policy.md §D', `cap.breached.kind MUST include ${k}`)).toBe(true);
|
|
94
93
|
}
|
|
95
94
|
});
|
|
96
95
|
|
|
@@ -98,7 +97,7 @@ describe('budget-policy-shape: budget.* events + cap.breached kinds (RFC 0084 §
|
|
|
98
97
|
const runEvent = loadSchema('run-event.schema.json');
|
|
99
98
|
const enumVals = ((runEvent.$defs as Record<string, { enum?: string[] }>).RunEventType?.enum) ?? [];
|
|
100
99
|
for (const name of ['budget.reserved', 'budget.consumed', 'budget.threshold.crossed', 'budget.exhausted']) {
|
|
101
|
-
expect(enumVals.includes(name),
|
|
100
|
+
expect(enumVals.includes(name), req('openwop.it.budget-policy-shape.all-four-budget-event-names-appear-in-the-runeventtype-enum', 'run-event.schema.json', `${name} MUST be in the RunEventType enum`)).toBe(true);
|
|
102
101
|
}
|
|
103
102
|
});
|
|
104
103
|
|
|
@@ -106,7 +105,7 @@ describe('budget-policy-shape: budget.* events + cap.breached kinds (RFC 0084 §
|
|
|
106
105
|
const defs = payloads.$defs as Record<string, { properties?: Record<string, unknown> }>;
|
|
107
106
|
for (const def of ['budgetReserved', 'budgetConsumed', 'budgetThresholdCrossed', 'budgetExhausted']) {
|
|
108
107
|
for (const p of Object.keys(defs[def].properties ?? {})) {
|
|
109
|
-
expect(PRICING_PROP_NAMES.includes(p.toLowerCase()),
|
|
108
|
+
expect(PRICING_PROP_NAMES.includes(p.toLowerCase()), req('openwop.it.budget-policy-shape.the-budget-payloads-declare-no-pricing-credential-property-budget-no-pricing-lea', 'budget-no-pricing-leak', `${def} MUST NOT declare a pricing-bearing property (${p})`)).toBe(false);
|
|
110
109
|
}
|
|
111
110
|
}
|
|
112
111
|
});
|
|
@@ -114,10 +113,10 @@ describe('budget-policy-shape: budget.* events + cap.breached kinds (RFC 0084 §
|
|
|
114
113
|
it('the budget.* payloads are additionalProperties:false — a rate-card field on an INSTANCE is rejected', () => {
|
|
115
114
|
// The aggregate cost total (the user's own budget) is permitted; the host's per-unit rate card is not.
|
|
116
115
|
// additionalProperties:false makes the rejection structural, not just a declared-property check.
|
|
117
|
-
expect(compile('budgetConsumed')({ dimension: 'cost', consumed: 0.8, limit: 1.0 }),
|
|
116
|
+
expect(compile('budgetConsumed')({ dimension: 'cost', consumed: 0.8, limit: 1.0 }), req('openwop.it.budget-policy-shape.the-budget-payloads-are-additionalproperties-false-a-rate-card-field-on-an-insta', 'budget-policy.md §F', 'an aggregate cost total (the user budget) MUST validate')).toBe(true);
|
|
118
117
|
expect(
|
|
119
118
|
compile('budgetConsumed')({ dimension: 'cost', consumed: 0.8, limit: 1.0, ratePerToken: 0.000003 }),
|
|
120
|
-
|
|
119
|
+
req('openwop.it.budget-policy-shape.the-budget-payloads-are-additionalproperties-false-a-rate-card-field-on-an-insta', 'budget-no-pricing-leak', 'a rate-card / per-token-price field MUST be rejected (additionalProperties:false)'),
|
|
121
120
|
).toBe(false);
|
|
122
121
|
});
|
|
123
122
|
});
|
|
@@ -127,10 +126,10 @@ describe('budget-policy-shape: capability advertisement (RFC 0084 §E, server-fr
|
|
|
127
126
|
const caps = loadSchema('capabilities.schema.json');
|
|
128
127
|
const props = caps.properties as Record<string, { properties?: Record<string, unknown> }>;
|
|
129
128
|
for (const flag of ['supported', 'dimensions', 'enforce', 'scopes']) {
|
|
130
|
-
expect(props.budget?.properties?.[flag],
|
|
129
|
+
expect(props.budget?.properties?.[flag], req('openwop.it.budget-policy-shape.capabilities-budget-limits-maxbudget-tokens-costusd-are-declared', 'budget-policy.md §E', `capabilities.budget.${flag} MUST be declared`)).toBeDefined();
|
|
131
130
|
}
|
|
132
131
|
for (const ceiling of ['maxBudgetTokens', 'maxBudgetCostUsd']) {
|
|
133
|
-
expect(props.limits?.properties?.[ceiling],
|
|
132
|
+
expect(props.limits?.properties?.[ceiling], req('openwop.it.budget-policy-shape.capabilities-budget-limits-maxbudget-tokens-costusd-are-declared', 'budget-policy.md §E', `limits.${ceiling} MUST be declared`)).toBeDefined();
|
|
134
133
|
}
|
|
135
134
|
});
|
|
136
135
|
});
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { describe, it, expect } from 'vitest';
|
|
21
21
|
import { driver } from '../lib/driver.js';
|
|
22
22
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
23
|
+
import { req } from '../lib/requirement-ids.js';
|
|
23
24
|
|
|
24
25
|
const CANCELLABLE = 'conformance-cancellable';
|
|
25
26
|
const NOOP = 'conformance-noop';
|
|
@@ -48,22 +49,22 @@ describe.skipIf(SKIP)('bulk-cancel: POST /v1/runs:bulk-cancel', () => {
|
|
|
48
49
|
runIds: [inflightRunId, 'run-does-not-exist-xxxxxxxx'],
|
|
49
50
|
reason: 'conformance bulk-cancel test',
|
|
50
51
|
});
|
|
51
|
-
expect(res.status,
|
|
52
|
+
expect(res.status, req('openwop.it.bulk-cancel.mixed-outcome-request-returns-per-id-results-in-order',
|
|
52
53
|
'rest-endpoints.md §"POST /v1/runs:bulk-cancel"',
|
|
53
54
|
'top-level operation MUST return 200 when the request reached the host (per-id outcomes carry partial failure)',
|
|
54
55
|
)).toBe(200);
|
|
55
56
|
|
|
56
57
|
const body = res.json as { results: BulkResult[] };
|
|
57
58
|
expect(Array.isArray(body.results)).toBe(true);
|
|
58
|
-
expect(body.results.length, 'results MUST have one entry per request runId').toBe(2);
|
|
59
|
+
expect(body.results.length, req('openwop.it.bulk-cancel.mixed-outcome-request-returns-per-id-results-in-order', 'rest-endpoints.md §"POST /v1/runs:bulk-cancel"', 'results MUST have one entry per request runId')).toBe(2);
|
|
59
60
|
|
|
60
|
-
expect(body.results[0]!.runId, 'results order MUST mirror the request order').toBe(inflightRunId);
|
|
61
|
+
expect(body.results[0]!.runId, req('openwop.it.bulk-cancel.mixed-outcome-request-returns-per-id-results-in-order', 'rest-endpoints.md §"POST /v1/runs:bulk-cancel"', 'results order MUST mirror the request order')).toBe(inflightRunId);
|
|
61
62
|
expect(body.results[0]!.ok).toBe(true);
|
|
62
63
|
expect(['cancelling', 'cancelled']).toContain(body.results[0]!.status);
|
|
63
64
|
|
|
64
65
|
expect(body.results[1]!.runId).toBe('run-does-not-exist-xxxxxxxx');
|
|
65
|
-
expect(body.results[1]!.ok, 'unknown runId entry MUST have ok=false').toBe(false);
|
|
66
|
-
expect(body.results[1]!.error?.code,
|
|
66
|
+
expect(body.results[1]!.ok, req('openwop.it.bulk-cancel.mixed-outcome-request-returns-per-id-results-in-order', 'rest-endpoints.md §"POST /v1/runs:bulk-cancel"', 'unknown runId entry MUST have ok=false')).toBe(false);
|
|
67
|
+
expect(body.results[1]!.error?.code, req('openwop.it.bulk-cancel.mixed-outcome-request-returns-per-id-results-in-order',
|
|
67
68
|
'rest-endpoints.md §"POST /v1/runs:bulk-cancel"',
|
|
68
69
|
'unknown runId outcomes carry `error.code === "not_found"`',
|
|
69
70
|
)).toBe('not_found');
|
|
@@ -71,7 +72,7 @@ describe.skipIf(SKIP)('bulk-cancel: POST /v1/runs:bulk-cancel', () => {
|
|
|
71
72
|
|
|
72
73
|
it('empty runIds array returns 400 validation_error', async () => {
|
|
73
74
|
const res = await driver.post('/v1/runs:bulk-cancel', { runIds: [] });
|
|
74
|
-
expect(res.status).toBe(400);
|
|
75
|
+
expect(res.status, req('openwop.it.bulk-cancel.empty-runids-array-returns-400-validation-error', 'rest-endpoints.md §Open', 'empty runIds array returns 400 validation_error')).toBe(400);
|
|
75
76
|
const body = res.json as { error?: string };
|
|
76
77
|
expect(body.error).toBe('validation_error');
|
|
77
78
|
});
|
|
@@ -83,7 +84,7 @@ describe.skipIf(SKIP)('bulk-cancel: POST /v1/runs:bulk-cancel', () => {
|
|
|
83
84
|
expect(res.status).toBe(400);
|
|
84
85
|
const body = res.json as { error?: string; details?: { maxRunIds?: number } };
|
|
85
86
|
expect(body.error).toBe('validation_error');
|
|
86
|
-
expect(typeof body.details?.maxRunIds,
|
|
87
|
+
expect(typeof body.details?.maxRunIds, req('openwop.it.bulk-cancel.oversized-runids-array-returns-400-with-details-maxrunids',
|
|
87
88
|
'rest-endpoints.md §"POST /v1/runs:bulk-cancel"',
|
|
88
89
|
'over-cap request MUST carry details.maxRunIds disclosing the configured ceiling',
|
|
89
90
|
)).toBe('number');
|
|
@@ -103,7 +104,7 @@ describe.skipIf(SKIP)('bulk-cancel: POST /v1/runs:bulk-cancel', () => {
|
|
|
103
104
|
const second = await driver.post('/v1/runs:bulk-cancel', { runIds: [runId] });
|
|
104
105
|
expect(second.status).toBe(200);
|
|
105
106
|
const body = second.json as { results: BulkResult[] };
|
|
106
|
-
expect(body.results[0]!.ok,
|
|
107
|
+
expect(body.results[0]!.ok, req('openwop.it.bulk-cancel.re-bulk-cancel-after-first-cancel-is-idempotent',
|
|
107
108
|
'rest-endpoints.md §"POST /v1/runs:bulk-cancel" §Idempotency',
|
|
108
109
|
're-cancelling an already-cancelling/cancelled run MUST be ok: true (idempotent)',
|
|
109
110
|
)).toBe(true);
|