@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
|
@@ -53,6 +53,8 @@ import Ajv2020 from 'ajv/dist/2020.js';
|
|
|
53
53
|
import addFormats from 'ajv-formats';
|
|
54
54
|
import { SCHEMAS_DIR, FIXTURES_DIR } from '../lib/paths.js';
|
|
55
55
|
import { driver } from '../lib/driver.js';
|
|
56
|
+
import { req } from '../lib/requirement-ids.js';
|
|
57
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
56
58
|
|
|
57
59
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
58
60
|
const SEAM = '/v1/host/sample/purpose-propagation/forward';
|
|
@@ -79,30 +81,30 @@ describe('purpose-propagation: label schema (always-on, server-free)', () => {
|
|
|
79
81
|
|
|
80
82
|
it('a labelled TriggerEvent validates; a non-array label fails', () => {
|
|
81
83
|
const ev = JSON.parse(readFileSync(FIXTURE, 'utf8'));
|
|
82
|
-
expect(validate(ev), `a labelled TriggerEvent MUST validate (RFC 0128 §1). Errors: ${JSON.stringify(validate.errors)}`).toBe(true);
|
|
84
|
+
expect(validate(ev), req('openwop.it.purpose-propagation.a-labelled-triggerevent-validates-a-non-array-label-fails', 'RFC 0128', `a labelled TriggerEvent MUST validate (RFC 0128 §1). Errors: ${JSON.stringify(validate.errors)}`)).toBe(true);
|
|
83
85
|
|
|
84
86
|
ev.permittedPurposes = 'analytics';
|
|
85
|
-
expect(validate(ev), 'RFC 0128 §1 — permittedPurposes MUST be string[]').toBe(false);
|
|
87
|
+
expect(validate(ev), req('openwop.it.purpose-propagation.a-labelled-triggerevent-validates-a-non-array-label-fails', 'RFC 0128', 'RFC 0128 §1 — permittedPurposes MUST be string[]')).toBe(false);
|
|
86
88
|
});
|
|
87
89
|
|
|
88
90
|
it('absent and [] are BOTH wire-valid — distinct states (unlabelled vs no-onward-use)', () => {
|
|
89
91
|
const ev = JSON.parse(readFileSync(FIXTURE, 'utf8'));
|
|
90
92
|
delete ev.permittedPurposes;
|
|
91
|
-
expect(validate(ev), 'RFC 0128 §1 — an unlabelled event (absent label) MUST validate').toBe(true);
|
|
93
|
+
expect(validate(ev), req('openwop.it.purpose-propagation.absent-and-are-both-wire-valid-distinct-states-unlabelled-vs-no-onward-use', 'RFC 0128', 'RFC 0128 §1 — an unlabelled event (absent label) MUST validate')).toBe(true);
|
|
92
94
|
ev.permittedPurposes = [];
|
|
93
|
-
expect(validate(ev), 'RFC 0128 §1 — a []-labelled event MUST validate (behavioral meaning: no onward use)').toBe(true);
|
|
95
|
+
expect(validate(ev), req('openwop.it.purpose-propagation.absent-and-are-both-wire-valid-distinct-states-unlabelled-vs-no-onward-use', 'RFC 0128', 'RFC 0128 §1 — a []-labelled event MUST validate (behavioral meaning: no onward use)')).toBe(true);
|
|
94
96
|
});
|
|
95
97
|
|
|
96
98
|
it('the capabilities.purposePropagation family requires `supported` and closes its shape', () => {
|
|
97
99
|
const caps = JSON.parse(readFileSync(join(SCHEMAS_DIR, 'capabilities.schema.json'), 'utf8'));
|
|
98
100
|
const fam = caps.properties?.purposePropagation;
|
|
99
|
-
expect(fam, 'capabilities.schema.json MUST define the purposePropagation family (RFC 0128 §2)').toBeDefined();
|
|
100
|
-
expect(fam.required, 'purposePropagation MUST require `supported`').toContain('supported');
|
|
101
|
-
expect(fam.additionalProperties, 'purposePropagation MUST close its shape').toBe(false);
|
|
101
|
+
expect(fam, req('openwop.it.purpose-propagation.the-capabilities-purposepropagation-family-requires-supported-and-closes-its-sha', 'RFC 0128', 'capabilities.schema.json MUST define the purposePropagation family (RFC 0128 §2)')).toBeDefined();
|
|
102
|
+
expect(fam.required, req('openwop.it.purpose-propagation.the-capabilities-purposepropagation-family-requires-supported-and-closes-its-sha', 'RFC 0128', 'purposePropagation MUST require `supported`')).toContain('supported');
|
|
103
|
+
expect(fam.additionalProperties, req('openwop.it.purpose-propagation.the-capabilities-purposepropagation-family-requires-supported-and-closes-its-sha', 'RFC 0128', 'purposePropagation MUST close its shape')).toBe(false);
|
|
102
104
|
const ajvFam = new Ajv2020({ allErrors: true, strict: false });
|
|
103
105
|
const vf = ajvFam.compile(fam);
|
|
104
|
-
expect(vf({ supported: true, propagatesOnward: true }), 'the canonical advert MUST validate').toBe(true);
|
|
105
|
-
expect(vf({ propagatesOnward: true }), 'an advert without `supported` MUST fail').toBe(false);
|
|
106
|
+
expect(vf({ supported: true, propagatesOnward: true }), req('openwop.it.purpose-propagation.the-capabilities-purposepropagation-family-requires-supported-and-closes-its-sha', 'RFC 0128', 'the canonical advert MUST validate')).toBe(true);
|
|
107
|
+
expect(vf({ propagatesOnward: true }), req('openwop.it.purpose-propagation.the-capabilities-purposepropagation-family-requires-supported-and-closes-its-sha', 'RFC 0128', 'an advert without `supported` MUST fail')).toBe(false);
|
|
106
108
|
});
|
|
107
109
|
});
|
|
108
110
|
|
|
@@ -127,17 +129,17 @@ describe.skipIf(HTTP_SKIP)('purpose-propagation: two-hop onward behavior (seam-g
|
|
|
127
129
|
it('a forwarded label survives ⊆ the received set (re-emit; MAY narrow, MUST NOT widen)', async () => {
|
|
128
130
|
const input = ['analytics', 'marketing-email'];
|
|
129
131
|
const res = await seamPost({ mode: 'forward', records: [{ id: 'r1', permittedPurposes: input, data: { k: 1 } }] });
|
|
130
|
-
if (res === null) return;
|
|
132
|
+
if (res === null) return softSkip('blocked', 'precondition not met — `res === null` returned early (seam, prior step, or fixture unavailable)');
|
|
131
133
|
|
|
132
134
|
const onward = res.onward ?? [];
|
|
133
135
|
expect(
|
|
134
136
|
onward.length > 0,
|
|
135
|
-
|
|
137
|
+
req('openwop.it.purpose-propagation.a-forwarded-label-survives-the-received-set-re-emit-may-narrow-must-not-widen', 'RFC 0128 §3', 'an advertising host MUST re-emit a received label on the onward hop — silent loss fails (the promise is propagation)'),
|
|
136
138
|
).toBe(true);
|
|
137
139
|
for (const o of onward) {
|
|
138
140
|
expect(
|
|
139
141
|
Array.isArray(o.permittedPurposes) && subsetOf(o.permittedPurposes, input),
|
|
140
|
-
|
|
142
|
+
req('openwop.it.purpose-propagation.a-forwarded-label-survives-the-received-set-re-emit-may-narrow-must-not-widen', 'RFC 0128 §3', `the onward label MUST be a subset of what the host received — got ${JSON.stringify(o.permittedPurposes)} vs input ${JSON.stringify(input)} (widening is the testable violation)`),
|
|
141
143
|
).toBe(true);
|
|
142
144
|
}
|
|
143
145
|
});
|
|
@@ -150,13 +152,13 @@ describe.skipIf(HTTP_SKIP)('purpose-propagation: two-hop onward behavior (seam-g
|
|
|
150
152
|
{ id: 'b', permittedPurposes: ['analytics'], data: { k: 2 } },
|
|
151
153
|
],
|
|
152
154
|
});
|
|
153
|
-
if (res === null) return;
|
|
155
|
+
if (res === null) return softSkip('blocked', 'precondition not met — `res === null` returned early (seam, prior step, or fixture unavailable)');
|
|
154
156
|
const onward = res.onward ?? [];
|
|
155
|
-
expect(onward.length > 0,
|
|
157
|
+
expect(onward.length > 0, req('openwop.it.purpose-propagation.a-derived-merged-output-arrives-the-intersection-of-contributing-labelled-inputs', 'RFC 0128 §3', 'a merge of forwardable labelled inputs MUST produce an onward emission')).toBe(true);
|
|
156
158
|
for (const o of onward) {
|
|
157
159
|
expect(
|
|
158
160
|
subsetOf(o.permittedPurposes, ['analytics']),
|
|
159
|
-
|
|
161
|
+
req('openwop.it.purpose-propagation.a-derived-merged-output-arrives-the-intersection-of-contributing-labelled-inputs', 'RFC 0128 §3', `a derived output MUST NOT carry a purpose absent from any contributing labelled input — transformation does not launder a grant; got ${JSON.stringify(o.permittedPurposes)}, allowed ⊆ ["analytics"]`),
|
|
160
162
|
).toBe(true);
|
|
161
163
|
}
|
|
162
164
|
});
|
|
@@ -169,11 +171,11 @@ describe.skipIf(HTTP_SKIP)('purpose-propagation: two-hop onward behavior (seam-g
|
|
|
169
171
|
{ id: 'b', data: { k: 2 } },
|
|
170
172
|
],
|
|
171
173
|
});
|
|
172
|
-
if (res === null) return;
|
|
174
|
+
if (res === null) return softSkip('blocked', 'precondition not met — `res === null` returned early (seam, prior step, or fixture unavailable)');
|
|
173
175
|
for (const o of res.onward ?? []) {
|
|
174
176
|
expect(
|
|
175
177
|
subsetOf(o.permittedPurposes, ['analytics']),
|
|
176
|
-
|
|
178
|
+
req('openwop.it.purpose-propagation.an-unlabelled-input-adds-no-constraint-to-a-merge', 'RFC 0128 §3', 'an unlabelled input asserts no constraint — the derived label is still bounded by the labelled input(s)'),
|
|
177
179
|
).toBe(true);
|
|
178
180
|
}
|
|
179
181
|
});
|
|
@@ -186,20 +188,20 @@ describe.skipIf(HTTP_SKIP)('purpose-propagation: two-hop onward behavior (seam-g
|
|
|
186
188
|
{ id: 'control', data: { k: 1 } },
|
|
187
189
|
],
|
|
188
190
|
});
|
|
189
|
-
if (res === null) return;
|
|
191
|
+
if (res === null) return softSkip('blocked', 'precondition not met — `res === null` returned early (seam, prior step, or fixture unavailable)');
|
|
190
192
|
|
|
191
193
|
const onwardIds = (res.onward ?? []).map((o) => o.recordId);
|
|
192
194
|
expect(
|
|
193
195
|
!onwardIds.includes('blocked'),
|
|
194
|
-
|
|
196
|
+
req('openwop.it.purpose-propagation.labelled-data-is-fail-closed-dropped-from-onward-emission-positive-control-unlab', 'RFC 0128 §3', 'permittedPurposes: [] means no onward use — a conformant host MUST NOT forward []-labelled data to a further sink at all'),
|
|
195
197
|
).toBe(true);
|
|
196
198
|
expect(
|
|
197
199
|
(res.dropped ?? []).includes('blocked'),
|
|
198
|
-
|
|
200
|
+
req('openwop.it.purpose-propagation.labelled-data-is-fail-closed-dropped-from-onward-emission-positive-control-unlab', 'RFC 0128 §3', 'the []-labelled record MUST be reported dropped (fail-closed, observable)'),
|
|
199
201
|
).toBe(true);
|
|
200
202
|
expect(
|
|
201
203
|
onwardIds.includes('control'),
|
|
202
|
-
|
|
204
|
+
req('openwop.it.purpose-propagation.labelled-data-is-fail-closed-dropped-from-onward-emission-positive-control-unlab', 'RFC 0128 §3', 'the unlabelled twin (positive control) MUST forward — proving the non-arrival of the []-labelled record is the rule firing, not a dead seam'),
|
|
203
205
|
).toBe(true);
|
|
204
206
|
});
|
|
205
207
|
});
|
|
@@ -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('queue-ack-nack-dlq: advertisement shape (RFC 0017)', () => {
|
|
33
35
|
it('capabilities.queueBus 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.queue-ack-nack-dlq.capabilities-queuebus-is-either-absent-or-a-well-formed-object',
|
|
39
41
|
'capabilities.schema.json §queueBus',
|
|
40
42
|
'capabilities.queueBus.supported MUST be a boolean when present',
|
|
41
43
|
),
|
|
@@ -44,17 +46,17 @@ describe('queue-ack-nack-dlq: advertisement shape (RFC 0017)', () => {
|
|
|
44
46
|
|
|
45
47
|
it('deadLetterSupported 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 = ["deadLetterSupported"];
|
|
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.queue-ack-nack-dlq.deadlettersupported-is-a-boolean-when-set',
|
|
58
60
|
'RFC 0017 §A',
|
|
59
61
|
'queueBus.deadLetterSupported MUST be boolean when present',
|
|
60
62
|
),
|
|
@@ -69,7 +71,7 @@ async function call(op: string, args: Record<string, unknown>) {
|
|
|
69
71
|
describe('queue-ack-nack-dlq: behavioral (RFC 0017 §B point 2 — nack + DLQ)', () => {
|
|
70
72
|
it('nack(requeue=true) → message is redelivered on next consume with deliveryCount incremented', async () => {
|
|
71
73
|
const probe = await call('consume', { subject: '__probe__' });
|
|
72
|
-
if (probe.status === 404) return;
|
|
74
|
+
if (probe.status === 404) return softSkip('blocked', 'precondition not met — `probe.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
73
75
|
const subject = `q-nack-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
74
76
|
await call('publish', { subject, payload: { v: 'redeliver-me' } });
|
|
75
77
|
|
|
@@ -83,18 +85,18 @@ describe('queue-ack-nack-dlq: behavioral (RFC 0017 §B point 2 — nack + DLQ)',
|
|
|
83
85
|
const secondBody = second.json as { found?: boolean; payload?: unknown; deliveryCount?: number };
|
|
84
86
|
expect(
|
|
85
87
|
secondBody.found,
|
|
86
|
-
|
|
88
|
+
req('openwop.it.queue-ack-nack-dlq.nack-requeue-true-message-is-redelivered-on-next-consume-with-deliverycount-incr', 'RFC 0017 §B point 2', 'nack(requeue=true) MUST make the message available to next consume'),
|
|
87
89
|
).toBe(true);
|
|
88
90
|
expect(secondBody.payload).toEqual(firstBody.payload);
|
|
89
91
|
expect(
|
|
90
92
|
secondBody.deliveryCount,
|
|
91
|
-
|
|
93
|
+
req('openwop.it.queue-ack-nack-dlq.nack-requeue-true-message-is-redelivered-on-next-consume-with-deliverycount-incr', 'RFC 0017 §B point 2', 'redelivered message MUST have incremented deliveryCount'),
|
|
92
94
|
).toBe(2);
|
|
93
95
|
});
|
|
94
96
|
|
|
95
97
|
it('deadLetter → message appears on the <subject>.dlq subject; original subject is empty', async () => {
|
|
96
98
|
const probe = await call('consume', { subject: '__probe__' });
|
|
97
|
-
if (probe.status === 404) return;
|
|
99
|
+
if (probe.status === 404) return softSkip('blocked', 'precondition not met — `probe.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
98
100
|
const subject = `q-dlq-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
99
101
|
await call('publish', { subject, payload: { v: 'poison' } });
|
|
100
102
|
|
|
@@ -114,7 +116,7 @@ describe('queue-ack-nack-dlq: behavioral (RFC 0017 §B point 2 — nack + DLQ)',
|
|
|
114
116
|
const dlqBody = dlqMsg.json as { found?: boolean; payload?: { original?: unknown; deadLetterReason?: string } };
|
|
115
117
|
expect(
|
|
116
118
|
dlqBody.found,
|
|
117
|
-
|
|
119
|
+
req('openwop.it.queue-ack-nack-dlq.deadletter-message-appears-on-the-subject-dlq-subject-original-subject-is-empty', 'RFC 0017 §B point 2', 'deadLetter MUST route the message to the <subject>.dlq subject'),
|
|
118
120
|
).toBe(true);
|
|
119
121
|
expect(dlqBody.payload?.deadLetterReason).toBe('unparseable_payload');
|
|
120
122
|
expect(dlqBody.payload?.original).toEqual({ v: 'poison' });
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
import { describe, it, expect } from 'vitest';
|
|
13
13
|
import { driver } from '../lib/driver.js';
|
|
14
14
|
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
15
|
+
import { req } from '../lib/requirement-ids.js';
|
|
16
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
15
17
|
|
|
16
18
|
interface DiscoveryDoc {
|
|
17
19
|
capabilities?: Record<string, unknown>;
|
|
@@ -32,10 +34,10 @@ async function call(tenantId: string, op: string, args: Record<string, unknown>)
|
|
|
32
34
|
describe('queue-cross-tenant-isolation: advertisement shape (RFC 0017)', () => {
|
|
33
35
|
it('capabilities.queueBus is either absent or a well-formed object', async () => {
|
|
34
36
|
const cap = await readCap();
|
|
35
|
-
if (cap === null) return;
|
|
37
|
+
if (cap === null) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap === null` returned early');
|
|
36
38
|
expect(
|
|
37
39
|
typeof cap.supported,
|
|
38
|
-
|
|
40
|
+
req('openwop.it.queue-cross-tenant-isolation.capabilities-queuebus-is-either-absent-or-a-well-formed-object',
|
|
39
41
|
'capabilities.schema.json §queueBus',
|
|
40
42
|
'capabilities.queueBus.supported MUST be a boolean when present',
|
|
41
43
|
),
|
|
@@ -46,19 +48,19 @@ describe('queue-cross-tenant-isolation: advertisement shape (RFC 0017)', () => {
|
|
|
46
48
|
describe('queue-cross-tenant-isolation: behavioral (RFC 0017 §C)', () => {
|
|
47
49
|
it('publish under tenant A → consume under tenant B returns not-found', async () => {
|
|
48
50
|
const cap = await readCap();
|
|
49
|
-
if (!cap || cap.supported !== true) return;
|
|
51
|
+
if (!cap || cap.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!cap || cap.supported !== true` returned early');
|
|
50
52
|
const topic = `xtenant.${Date.now()}.${Math.random().toString(36).slice(2, 6)}`;
|
|
51
53
|
|
|
52
54
|
const pubRes = await call('tenant-a', 'publish', { topic, payload: { hello: 'A' } });
|
|
53
|
-
if (pubRes.status === 404) return;
|
|
54
|
-
expect(pubRes.status, 'publish MUST succeed').toBe(200);
|
|
55
|
+
if (pubRes.status === 404) return softSkip('blocked', 'precondition not met — `pubRes.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
56
|
+
expect(pubRes.status, req('openwop.it.queue-cross-tenant-isolation.publish-under-tenant-a-consume-under-tenant-b-returns-not-found', 'SECURITY/invariants.yaml queue-cross-tenant-isolation', 'publish MUST succeed')).toBe(200);
|
|
55
57
|
|
|
56
58
|
const consRes = await call('tenant-b', 'consume', { topic, timeoutMs: 100 });
|
|
57
59
|
expect(consRes.status).toBe(200);
|
|
58
60
|
const body = consRes.json as { found?: boolean };
|
|
59
61
|
expect(
|
|
60
62
|
body.found,
|
|
61
|
-
|
|
63
|
+
req('openwop.it.queue-cross-tenant-isolation.publish-under-tenant-a-consume-under-tenant-b-returns-not-found',
|
|
62
64
|
'SECURITY/invariants.yaml queue-cross-tenant-isolation',
|
|
63
65
|
'tenant B MUST NOT consume tenant A messages on the same topic',
|
|
64
66
|
),
|
|
@@ -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('queue-publish-consume-roundtrip: advertisement shape (RFC 0017)', () => {
|
|
33
35
|
it('capabilities.queueBus 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.queue-publish-consume-roundtrip.capabilities-queuebus-is-either-absent-or-a-well-formed-object',
|
|
39
41
|
'capabilities.schema.json §queueBus',
|
|
40
42
|
'capabilities.queueBus.supported MUST be a boolean when present',
|
|
41
43
|
),
|
|
@@ -50,7 +52,7 @@ async function call(op: string, args: Record<string, unknown>) {
|
|
|
50
52
|
describe('queue-publish-consume-roundtrip: behavioral (RFC 0017 §B point 2)', () => {
|
|
51
53
|
it('publish → consume returns the same payload + subject', async () => {
|
|
52
54
|
const probe = await call('consume', { subject: '__probe__' });
|
|
53
|
-
if (probe.status === 404) return; // seam not exposed
|
|
55
|
+
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
|
|
54
56
|
const subject = `q-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
55
57
|
const payload = { event: 'order.created', orderId: 42 };
|
|
56
58
|
const pub = await call('publish', { subject, payload });
|
|
@@ -59,18 +61,18 @@ describe('queue-publish-consume-roundtrip: behavioral (RFC 0017 §B point 2)', (
|
|
|
59
61
|
const got = await call('consume', { subject });
|
|
60
62
|
expect(got.status).toBe(200);
|
|
61
63
|
const body = got.json as { found?: boolean; subject?: string; payload?: unknown; deliveryToken?: string };
|
|
62
|
-
expect(body.found, 'consume MUST find the just-published message').toBe(true);
|
|
64
|
+
expect(body.found, req('openwop.it.queue-publish-consume-roundtrip.publish-consume-returns-the-same-payload-subject', 'RFC 0017 §B point 2', 'consume MUST find the just-published message')).toBe(true);
|
|
63
65
|
expect(body.subject).toBe(subject);
|
|
64
66
|
expect(
|
|
65
67
|
body.payload,
|
|
66
|
-
|
|
68
|
+
req('openwop.it.queue-publish-consume-roundtrip.publish-consume-returns-the-same-payload-subject', 'RFC 0017 §B point 2', 'consume MUST return the exact published payload'),
|
|
67
69
|
).toEqual(payload);
|
|
68
|
-
expect(typeof body.deliveryToken, 'consume MUST return a deliveryToken for ack/nack').toBe('string');
|
|
70
|
+
expect(typeof body.deliveryToken, req('openwop.it.queue-publish-consume-roundtrip.publish-consume-returns-the-same-payload-subject', 'RFC 0017 §B point 2', 'consume MUST return a deliveryToken for ack/nack')).toBe('string');
|
|
69
71
|
});
|
|
70
72
|
|
|
71
73
|
it('ack removes the message; subsequent consume on empty queue returns found:false', async () => {
|
|
72
74
|
const probe = await call('consume', { subject: '__probe__' });
|
|
73
|
-
if (probe.status === 404) return;
|
|
75
|
+
if (probe.status === 404) return softSkip('blocked', 'precondition not met — `probe.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
74
76
|
const subject = `q-ack-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
75
77
|
await call('publish', { subject, payload: { v: 1 } });
|
|
76
78
|
const got = await call('consume', { subject });
|
|
@@ -83,7 +85,7 @@ describe('queue-publish-consume-roundtrip: behavioral (RFC 0017 §B point 2)', (
|
|
|
83
85
|
const emptyBody = empty.json as { found?: boolean };
|
|
84
86
|
expect(
|
|
85
87
|
emptyBody.found,
|
|
86
|
-
|
|
88
|
+
req('openwop.it.queue-publish-consume-roundtrip.ack-removes-the-message-subsequent-consume-on-empty-queue-returns-found-false', 'RFC 0017 §B point 2', 'consume after ack MUST surface as found:false'),
|
|
87
89
|
).toBe(false);
|
|
88
90
|
});
|
|
89
91
|
});
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import { describe, it, expect } from 'vitest';
|
|
24
24
|
import { softSkip } from '../lib/soft-skip.js';
|
|
25
25
|
import { driver } from '../lib/driver.js';
|
|
26
|
+
import { req } from '../lib/requirement-ids.js';
|
|
26
27
|
|
|
27
28
|
const ALLOWED_SCOPES = new Set(['tenant', 'route', 'global', 'key']);
|
|
28
29
|
const FORCE = process.env.OPENWOP_FORCE_RATE_LIMIT === 'true';
|
|
@@ -48,7 +49,7 @@ describe('rate-limit-envelope: 429 conforms to canonical shape', () => {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
const retryAfter = last.headers.get('retry-after');
|
|
51
|
-
expect(retryAfter,
|
|
52
|
+
expect(retryAfter, req('openwop.it.rate-limit-envelope.when-a-429-is-observed-the-response-body-satisfies-the-v1-1-contract',
|
|
52
53
|
'rest-endpoints.md §"429 Too Many Requests envelope"',
|
|
53
54
|
'429 response MUST set Retry-After header',
|
|
54
55
|
)).not.toBeNull();
|
|
@@ -60,7 +61,7 @@ describe('rate-limit-envelope: 429 conforms to canonical shape', () => {
|
|
|
60
61
|
[k: string]: unknown;
|
|
61
62
|
};
|
|
62
63
|
|
|
63
|
-
expect(body.error,
|
|
64
|
+
expect(body.error, req('openwop.it.rate-limit-envelope.when-a-429-is-observed-the-response-body-satisfies-the-v1-1-contract',
|
|
64
65
|
'rest-endpoints.md §"429 Too Many Requests envelope"',
|
|
65
66
|
"429 body MUST carry error === 'rate_limited'",
|
|
66
67
|
)).toBe('rate_limited');
|
|
@@ -70,13 +71,13 @@ describe('rate-limit-envelope: 429 conforms to canonical shape', () => {
|
|
|
70
71
|
const topKeys = Object.keys(body).filter(
|
|
71
72
|
(k) => !['error', 'message', 'details'].includes(k),
|
|
72
73
|
);
|
|
73
|
-
expect(topKeys,
|
|
74
|
+
expect(topKeys, req('openwop.it.rate-limit-envelope.when-a-429-is-observed-the-response-body-satisfies-the-v1-1-contract',
|
|
74
75
|
'error-envelope.schema.json additionalProperties: false',
|
|
75
76
|
'429 envelope MUST NOT carry top-level keys outside {error, message, details}',
|
|
76
77
|
)).toEqual([]);
|
|
77
78
|
|
|
78
79
|
if (body.details?.scope !== undefined) {
|
|
79
|
-
expect(ALLOWED_SCOPES.has(body.details.scope),
|
|
80
|
+
expect(ALLOWED_SCOPES.has(body.details.scope), req('openwop.it.rate-limit-envelope.when-a-429-is-observed-the-response-body-satisfies-the-v1-1-contract',
|
|
80
81
|
'rest-endpoints.md §"429 Too Many Requests envelope"',
|
|
81
82
|
"details.scope MUST be one of {'tenant','route','global','key'} when present",
|
|
82
83
|
)).toBe(true);
|
|
@@ -89,7 +90,7 @@ describe('rate-limit-envelope: 429 conforms to canonical shape', () => {
|
|
|
89
90
|
retryAfterSec > 0
|
|
90
91
|
) {
|
|
91
92
|
const diff = Math.abs(body.details.retryAfterMs / 1000 - retryAfterSec);
|
|
92
|
-
expect(diff,
|
|
93
|
+
expect(diff, req('openwop.it.rate-limit-envelope.when-a-429-is-observed-the-response-body-satisfies-the-v1-1-contract',
|
|
93
94
|
'rest-endpoints.md §"429 Too Many Requests envelope"',
|
|
94
95
|
'details.retryAfterMs MUST be consistent with Retry-After header (±1s)',
|
|
95
96
|
)).toBeLessThanOrEqual(1.5);
|
|
@@ -55,6 +55,8 @@ import {
|
|
|
55
55
|
getCanary,
|
|
56
56
|
} from '../lib/canaries.js';
|
|
57
57
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
58
|
+
import { req } from '../lib/requirement-ids.js';
|
|
59
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
58
60
|
|
|
59
61
|
const NOOP_WORKFLOW_ID = 'conformance-noop';
|
|
60
62
|
const SKIP_NO_NOOP = !isFixtureAdvertised(NOOP_WORKFLOW_ID);
|
|
@@ -71,7 +73,7 @@ describe('redaction: /.well-known/openwop secrets+aiProviders shape contract', (
|
|
|
71
73
|
|
|
72
74
|
if (secrets === undefined) {
|
|
73
75
|
// Optional v1 field — hosts MAY omit. Spec-allowed; nothing to assert.
|
|
74
|
-
return;
|
|
76
|
+
return softSkip('blocked', 'precondition not met — `secrets === undefined` returned early (Optional v1 field — hosts MAY omit. Spec-allowed; nothing to assert.) (seam, prior step, or fixture unavailable)');
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
// Per capabilities.schema.json: `secrets.supported` is REQUIRED
|
|
@@ -81,7 +83,7 @@ describe('redaction: /.well-known/openwop secrets+aiProviders shape contract', (
|
|
|
81
83
|
scopes?: unknown;
|
|
82
84
|
resolution?: unknown;
|
|
83
85
|
};
|
|
84
|
-
expect(typeof s.supported,
|
|
86
|
+
expect(typeof s.supported, req('openwop.it.redaction.secrets-is-well-formed-regardless-of-supported-value',
|
|
85
87
|
'capabilities.md §"Secrets"',
|
|
86
88
|
'secrets.supported MUST be a boolean',
|
|
87
89
|
)).toBe('boolean');
|
|
@@ -90,12 +92,12 @@ describe('redaction: /.well-known/openwop secrets+aiProviders shape contract', (
|
|
|
90
92
|
// (a host claiming secrets must declare at least one scope) AND
|
|
91
93
|
// resolution MUST be 'host-managed' (only allowed value in v1.x).
|
|
92
94
|
if (s.supported === true) {
|
|
93
|
-
expect(Array.isArray(s.scopes),
|
|
95
|
+
expect(Array.isArray(s.scopes), req('openwop.it.redaction.secrets-is-well-formed-regardless-of-supported-value',
|
|
94
96
|
'capabilities.md §"Secrets"',
|
|
95
97
|
'when secrets.supported is true, scopes MUST be a string[]',
|
|
96
98
|
)).toBe(true);
|
|
97
99
|
const scopes = s.scopes as string[];
|
|
98
|
-
expect(scopes.length,
|
|
100
|
+
expect(scopes.length, req('openwop.it.redaction.secrets-is-well-formed-regardless-of-supported-value',
|
|
99
101
|
'capabilities.md §"Secrets"',
|
|
100
102
|
'when secrets.supported is true, scopes MUST be non-empty',
|
|
101
103
|
)).toBeGreaterThanOrEqual(1);
|
|
@@ -105,7 +107,7 @@ describe('redaction: /.well-known/openwop secrets+aiProviders shape contract', (
|
|
|
105
107
|
// sub-tenant scope — additive; hosts that advertise it (e.g. MyndHyve) are conformant.
|
|
106
108
|
expect(['tenant', 'user', 'run', 'workspace']).toContain(scope);
|
|
107
109
|
}
|
|
108
|
-
expect(s.resolution,
|
|
110
|
+
expect(s.resolution, req('openwop.it.redaction.secrets-is-well-formed-regardless-of-supported-value',
|
|
109
111
|
'capabilities.md §"Secrets"',
|
|
110
112
|
'resolution MUST be "host-managed" in v1.x',
|
|
111
113
|
)).toBe('host-managed');
|
|
@@ -119,7 +121,7 @@ describe('redaction: /.well-known/openwop secrets+aiProviders shape contract', (
|
|
|
119
121
|
| { supported?: unknown; byok?: unknown }
|
|
120
122
|
| undefined;
|
|
121
123
|
|
|
122
|
-
if (ap === undefined) return; // Optional v1 field — hosts MAY omit.
|
|
124
|
+
if (ap === undefined) return softSkip('blocked', 'precondition not met — `ap === undefined` returned early (Optional v1 field — hosts MAY omit.) (seam, prior step, or fixture unavailable)'); // Optional v1 field — hosts MAY omit.
|
|
123
125
|
|
|
124
126
|
if (ap.supported !== undefined) {
|
|
125
127
|
expect(Array.isArray(ap.supported)).toBe(true);
|
|
@@ -130,7 +132,7 @@ describe('redaction: /.well-known/openwop secrets+aiProviders shape contract', (
|
|
|
130
132
|
expect(Array.isArray(ap.byok)).toBe(true);
|
|
131
133
|
const byok = ap.byok as string[];
|
|
132
134
|
for (const id of byok) {
|
|
133
|
-
expect(supportedSet.has(id),
|
|
135
|
+
expect(supportedSet.has(id), req('openwop.it.redaction.aiproviders-is-well-formed-when-present-byok-supported',
|
|
134
136
|
'capabilities.md §"aiProviders"',
|
|
135
137
|
`byok entry "${id}" MUST appear in aiProviders.supported`,
|
|
136
138
|
)).toBe(true);
|
|
@@ -177,7 +179,7 @@ describe('redaction: 401 response MUST NOT echo invalid Bearer token (NFR-7)', (
|
|
|
177
179
|
},
|
|
178
180
|
);
|
|
179
181
|
const responseText = captureToText(res.json) + '\n' + res.text;
|
|
180
|
-
expect(responseText).not.toContain(CANARY_MARKER);
|
|
182
|
+
expect(responseText, req('openwop.it.redaction.the-marker-substring-alone-never-appears-in-a-401-body-universal', 'capabilities.md', 'the marker substring alone never appears in a 401 body (universal)')).not.toContain(CANARY_MARKER);
|
|
181
183
|
});
|
|
182
184
|
});
|
|
183
185
|
|
|
@@ -193,8 +195,8 @@ describe.skipIf(SKIP_NO_NOOP)('redaction: credentialRef value MUST NOT appear in
|
|
|
193
195
|
if (supported !== true) {
|
|
194
196
|
// Spec-allowed — this scenario only applies to hosts that opt
|
|
195
197
|
// into BYOK. Pass trivially.
|
|
196
|
-
expect(supported).not.toBe(true);
|
|
197
|
-
return;
|
|
198
|
+
expect(supported, req('openwop.it.redaction.skips-when-host-does-not-advertise-secrets-supported', 'capabilities.md', 'skips when host does NOT advertise secrets.supported')).not.toBe(true);
|
|
199
|
+
return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `supported !== true` returned early');
|
|
198
200
|
}
|
|
199
201
|
|
|
200
202
|
// Real assertion path: plant a canary as credentialRef on a noop
|
|
@@ -217,7 +219,7 @@ describe.skipIf(SKIP_NO_NOOP)('redaction: credentialRef value MUST NOT appear in
|
|
|
217
219
|
// full scenario; if the key is missing or invalid, the suite's
|
|
218
220
|
// earlier auth scenarios already catch that. Bail with a non-
|
|
219
221
|
// assertion — this scenario is opt-in.
|
|
220
|
-
return;
|
|
222
|
+
return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `create.status !== 201` returned early (Auth-required hosts may 401 here without an API key. The conformance suite is expected to provide OPENWOP_API_KEY for th…');
|
|
221
223
|
}
|
|
222
224
|
const runId = (create.json as { runId: string }).runId;
|
|
223
225
|
|
|
@@ -47,6 +47,8 @@ import { driver } from '../lib/driver.js';
|
|
|
47
47
|
import { CANARIES, CANARY_MARKER, getCanary } from '../lib/canaries.js';
|
|
48
48
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
49
49
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
50
|
+
import { req } from '../lib/requirement-ids.js';
|
|
51
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
50
52
|
|
|
51
53
|
const NOOP_WORKFLOW_ID = 'conformance-noop';
|
|
52
54
|
const SKIP_NO_NOOP = !isFixtureAdvertised(NOOP_WORKFLOW_ID);
|
|
@@ -66,7 +68,7 @@ describe.skipIf(SKIP_NO_NOOP)('redaction-adversarial: workflow input canaries',
|
|
|
66
68
|
userSuppliedField: canary,
|
|
67
69
|
},
|
|
68
70
|
});
|
|
69
|
-
expect(create.status,
|
|
71
|
+
expect(create.status, req('openwop.it.redactionAdversarial.openai-shaped-canary-in-arbitrary-input-field-must-not-appear-verbatim-in-any-ev', 'rest-endpoints.md', 'noop run create returns 201')).toBe(201);
|
|
70
72
|
const runId = (create.json as { runId: string }).runId;
|
|
71
73
|
|
|
72
74
|
await pollUntilTerminal(runId);
|
|
@@ -76,15 +78,15 @@ describe.skipIf(SKIP_NO_NOOP)('redaction-adversarial: workflow input canaries',
|
|
|
76
78
|
);
|
|
77
79
|
if (events.status !== 200) {
|
|
78
80
|
// Host doesn't expose polling; SSE is the alternative. Skip-equivalent.
|
|
79
|
-
return;
|
|
81
|
+
return softSkip('blocked', 'precondition not met — `events.status !== 200` returned early (Host doesn\'t expose polling; SSE is the alternative. Skip-equivalent.) (seam, prior step, or fixture unavailable)');
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
const eventsText = events.text;
|
|
83
|
-
expect(eventsText.includes(canary),
|
|
85
|
+
expect(eventsText.includes(canary), req('openwop.it.redactionAdversarial.openai-shaped-canary-in-arbitrary-input-field-must-not-appear-verbatim-in-any-ev',
|
|
84
86
|
'capabilities.md §"Secrets" + NFR-7',
|
|
85
87
|
'canary value submitted as workflow input MUST NOT appear verbatim in event poll response',
|
|
86
88
|
)).toBe(false);
|
|
87
|
-
expect(eventsText.includes(CANARY_MARKER),
|
|
89
|
+
expect(eventsText.includes(CANARY_MARKER), req('openwop.it.redactionAdversarial.openai-shaped-canary-in-arbitrary-input-field-must-not-appear-verbatim-in-any-ev',
|
|
88
90
|
'NFR-7',
|
|
89
91
|
'canary marker substring MUST NOT appear in event poll response',
|
|
90
92
|
)).toBe(false);
|
|
@@ -100,7 +102,7 @@ describe('redaction-adversarial: error-envelope canaries', () => {
|
|
|
100
102
|
});
|
|
101
103
|
expect(
|
|
102
104
|
[400, 404].includes(res.status),
|
|
103
|
-
|
|
105
|
+
req('openwop.it.redactionAdversarial.jwt-shaped-canary-in-workflowid-of-a-404-failing-request-must-not-echo-verbatim', 'rest-endpoints.md', 'unknown workflowId returns 400 or 404'),
|
|
104
106
|
).toBe(true);
|
|
105
107
|
|
|
106
108
|
const responseText = res.text;
|
|
@@ -110,7 +112,7 @@ describe('redaction-adversarial: error-envelope canaries', () => {
|
|
|
110
112
|
// SECURITY/invariants.yaml `secret-leakage-error-envelope`, the
|
|
111
113
|
// host SHOULD sanitize entropy-shaped substrings even when echoing
|
|
112
114
|
// the workflowId.
|
|
113
|
-
expect(responseText.includes(canary),
|
|
115
|
+
expect(responseText.includes(canary), req('openwop.it.redactionAdversarial.jwt-shaped-canary-in-workflowid-of-a-404-failing-request-must-not-echo-verbatim',
|
|
114
116
|
'SECURITY/invariants.yaml secret-leakage-error-envelope',
|
|
115
117
|
'JWT-shaped canary submitted in workflowId SHOULD NOT echo verbatim in 4xx error envelope',
|
|
116
118
|
)).toBe(false);
|
|
@@ -137,9 +139,9 @@ describe.skipIf(SKIP_NO_NOOP)('redaction-adversarial: bearer-shaped strings roun
|
|
|
137
139
|
const events = await driver.get(
|
|
138
140
|
`/v1/runs/${encodeURIComponent(runId)}/events/poll`,
|
|
139
141
|
);
|
|
140
|
-
if (events.status !== 200) return;
|
|
142
|
+
if (events.status !== 200) return softSkip('blocked', 'precondition not met — `events.status !== 200` returned early (seam, prior step, or fixture unavailable)');
|
|
141
143
|
|
|
142
|
-
expect(events.text.includes(canary),
|
|
144
|
+
expect(events.text.includes(canary), req('openwop.it.redactionAdversarial.anthropic-shaped-canary-embedded-in-non-credential-input-must-not-appear-verbati',
|
|
143
145
|
'capabilities.md §"Secrets" + NFR-7',
|
|
144
146
|
'Anthropic-shaped canary embedded in arbitrary input MUST NOT appear verbatim in events',
|
|
145
147
|
)).toBe(false);
|
|
@@ -153,7 +155,7 @@ describe('redaction-adversarial: every canary fixture carries the marker substri
|
|
|
153
155
|
// If this fails the canary harness has a bug — every other
|
|
154
156
|
// adversarial scenario depends on this property.
|
|
155
157
|
for (const c of CANARIES) {
|
|
156
|
-
expect(c.value.includes(CANARY_MARKER),
|
|
158
|
+
expect(c.value.includes(CANARY_MARKER), req('openwop.it.redactionAdversarial.every-canary-in-canaries-has-the-marker-substring',
|
|
157
159
|
'lib/canaries.ts',
|
|
158
160
|
`every canary fixture MUST contain CANARY_MARKER (offender label: ${c.label})`,
|
|
159
161
|
)).toBe(true);
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import { describe, it, expect } from 'vitest';
|
|
29
29
|
import { softSkip } from '../lib/soft-skip.js';
|
|
30
30
|
import { createHash, createPublicKey, verify as cryptoVerify } from 'node:crypto';
|
|
31
|
+
import { req } from '../lib/requirement-ids.js';
|
|
31
32
|
|
|
32
33
|
const REGISTRY_BASE = 'https://packs.openwop.dev';
|
|
33
34
|
const ENABLED = process.env.OPENWOP_TEST_PUBLIC_REGISTRY === 'true';
|
|
@@ -59,7 +60,7 @@ describe('registry-public: packs.openwop.dev discovery document', () => {
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
const res = await get('/.well-known/openwop-registry');
|
|
62
|
-
expect(res.status).toBe(200);
|
|
63
|
+
expect(res.status, req('openwop.it.registry-public.get-well-known-openwop-registry-returns-a-valid-discovery-payload', 'spec/v1/registry-operations.md', 'GET /.well-known/openwop-registry returns a valid discovery payload')).toBe(200);
|
|
63
64
|
|
|
64
65
|
const body = res.json as {
|
|
65
66
|
registryVersion?: string;
|
|
@@ -105,8 +106,8 @@ describe('registry-public: packs.openwop.dev index', () => {
|
|
|
105
106
|
expect(body.packs?.length ?? 0).toBeGreaterThan(0);
|
|
106
107
|
|
|
107
108
|
for (const p of body.packs ?? []) {
|
|
108
|
-
expect(p.name, `pack name must match reverse-DNS pattern: ${p.name}`).toMatch(PACK_NAME_RE);
|
|
109
|
-
expect(p.latestVersion, `pack version must be semver: ${p.latestVersion}`).toMatch(SEMVER_RE);
|
|
109
|
+
expect(p.name, req('openwop.it.registry-public.get-v1-index-json-returns-a-non-empty-pack-list-with-valid-name-version-shapes', 'spec/v1/registry-operations.md', `pack name must match reverse-DNS pattern: ${p.name}`)).toMatch(PACK_NAME_RE);
|
|
110
|
+
expect(p.latestVersion, req('openwop.it.registry-public.get-v1-index-json-returns-a-non-empty-pack-list-with-valid-name-version-shapes', 'spec/v1/registry-operations.md', `pack version must be semver: ${p.latestVersion}`)).toMatch(SEMVER_RE);
|
|
110
111
|
}
|
|
111
112
|
});
|
|
112
113
|
});
|
|
@@ -123,7 +124,7 @@ describe('registry-public: spec-canonical pack manifests resolve', () => {
|
|
|
123
124
|
if (!ENABLED) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!ENABLED` returned early');
|
|
124
125
|
|
|
125
126
|
const res = await get(`/v1/packs/${name}/-/${version}.json`);
|
|
126
|
-
expect(res.status).toBe(200);
|
|
127
|
+
expect(res.status, req('openwop.it.registry-public.get-v1-packs-json-returns-a-valid-manifest', 'spec/v1/registry-operations.md', 'GET /v1/packs/ /-/ .json returns a valid manifest')).toBe(200);
|
|
127
128
|
|
|
128
129
|
const manifest = res.json as { name?: string; version?: string };
|
|
129
130
|
expect(manifest.name).toBe(name);
|
|
@@ -178,26 +179,26 @@ describe('registry-public: tarball + signature + Ed25519 verify roundtrip', () =
|
|
|
178
179
|
|
|
179
180
|
// 2. Tarball.
|
|
180
181
|
const tarball = await getBinary(`/v1/packs/${PACK_NAME}/-/${PACK_VERSION}.tgz`);
|
|
181
|
-
expect(tarball.status, 'tarball MUST be retrievable').toBe(200);
|
|
182
|
-
expect(tarball.bytes.byteLength, 'tarball MUST be non-empty').toBeGreaterThan(0);
|
|
182
|
+
expect(tarball.status, req('openwop.it.registry-public.tarball-sig-public-key-all-retrievable-sri-matches-ed25519-verifies-for', 'spec/v1/registry-operations.md', 'tarball MUST be retrievable')).toBe(200);
|
|
183
|
+
expect(tarball.bytes.byteLength, req('openwop.it.registry-public.tarball-sig-public-key-all-retrievable-sri-matches-ed25519-verifies-for', 'spec/v1/registry-operations.md', 'tarball MUST be non-empty')).toBeGreaterThan(0);
|
|
183
184
|
|
|
184
185
|
// 3. Detached signature (64-byte raw Ed25519).
|
|
185
186
|
const sig = await getBinary(`/v1/packs/${PACK_NAME}/-/${PACK_VERSION}.sig`);
|
|
186
|
-
expect(sig.status, 'signature MUST be retrievable').toBe(200);
|
|
187
|
-
expect(sig.bytes.byteLength, 'Ed25519 detached signature MUST be 64 bytes').toBe(64);
|
|
187
|
+
expect(sig.status, req('openwop.it.registry-public.tarball-sig-public-key-all-retrievable-sri-matches-ed25519-verifies-for', 'spec/v1/registry-operations.md', 'signature MUST be retrievable')).toBe(200);
|
|
188
|
+
expect(sig.bytes.byteLength, req('openwop.it.registry-public.tarball-sig-public-key-all-retrievable-sri-matches-ed25519-verifies-for', 'spec/v1/registry-operations.md', 'Ed25519 detached signature MUST be 64 bytes')).toBe(64);
|
|
188
189
|
|
|
189
190
|
// 4. Public key — fetch from the publisher-declared URL when present,
|
|
190
191
|
// else fall back to the canonical `/keys/<keyId>.pub` shape.
|
|
191
192
|
const keyUrl = manifest.signing!.publicKeyUrl ?? `/keys/${manifest.signing!.keyId}.pub`;
|
|
192
193
|
const keyRes = await getText(keyUrl);
|
|
193
|
-
expect(keyRes.status, `public key MUST be retrievable at ${keyUrl}`).toBe(200);
|
|
194
|
+
expect(keyRes.status, req('openwop.it.registry-public.tarball-sig-public-key-all-retrievable-sri-matches-ed25519-verifies-for', 'spec/v1/registry-operations.md', `public key MUST be retrievable at ${keyUrl}`)).toBe(200);
|
|
194
195
|
const publicKey = createPublicKey(keyRes.body);
|
|
195
196
|
|
|
196
197
|
// 5. SRI integrity check: `sha256-<base64>=` MUST match a fresh
|
|
197
198
|
// sha256 of the tarball bytes per registry-operations.md.
|
|
198
199
|
expect(manifest.integrity).toMatch(/^sha256-[A-Za-z0-9+/]+=*$/);
|
|
199
200
|
const expectedSri = `sha256-${createHash('sha256').update(tarball.bytes).digest('base64')}`;
|
|
200
|
-
expect(expectedSri, 'SRI integrity in manifest MUST match a fresh sha256 of the tarball').toBe(
|
|
201
|
+
expect(expectedSri, req('openwop.it.registry-public.tarball-sig-public-key-all-retrievable-sri-matches-ed25519-verifies-for', 'spec/v1/registry-operations.md', 'SRI integrity in manifest MUST match a fresh sha256 of the tarball')).toBe(
|
|
201
202
|
manifest.integrity,
|
|
202
203
|
);
|
|
203
204
|
|
|
@@ -217,7 +218,7 @@ describe('registry-public: tarball + signature + Ed25519 verify roundtrip', () =
|
|
|
217
218
|
if (method !== 'ed25519') return softSkip('blocked', 'precondition not met — `method !== \'ed25519\'` returned early (seam, prior step, or fixture unavailable)');
|
|
218
219
|
|
|
219
220
|
const verified = cryptoVerify(null, tarball.bytes, publicKey, sig.bytes);
|
|
220
|
-
expect(verified, `Ed25519 signature over ${PACK_NAME}@${PACK_VERSION}.tgz MUST verify against ${manifest.signing!.keyId}`)
|
|
221
|
+
expect(verified, req('openwop.it.registry-public.tarball-sig-public-key-all-retrievable-sri-matches-ed25519-verifies-for', 'spec/v1/registry-operations.md', `Ed25519 signature over ${PACK_NAME}@${PACK_VERSION}.tgz MUST verify against ${manifest.signing!.keyId}`))
|
|
221
222
|
.toBe(true);
|
|
222
223
|
});
|
|
223
224
|
});
|