@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,7 @@
|
|
|
53
53
|
import { describe, it, expect } from 'vitest';
|
|
54
54
|
import { driver } from '../lib/driver.js';
|
|
55
55
|
import { softSkip } from '../lib/soft-skip.js';
|
|
56
|
+
import { req } from '../lib/requirement-ids.js';
|
|
56
57
|
|
|
57
58
|
const SEAM = '/v1/host/sample/test/idempotency/concurrent-claim';
|
|
58
59
|
|
|
@@ -64,12 +65,12 @@ type ClaimResult = {
|
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
/** Is the §25 seam mounted? 404/403/501 mean not mounted — anything else is a real answer. */
|
|
67
|
-
async function seamPresent(): Promise<ClaimResult | null> {
|
|
68
|
+
async function seamPresent(requirementId: string): Promise<ClaimResult | null> {
|
|
68
69
|
const res = await driver.post(SEAM, { executors: 2 });
|
|
69
70
|
if (res.status === 404 || res.status === 403 || res.status === 501) return null;
|
|
70
71
|
expect(
|
|
71
72
|
res.status,
|
|
72
|
-
|
|
73
|
+
req(requirementId,
|
|
73
74
|
'host-sample-test-seams.md §25',
|
|
74
75
|
'a mounted concurrent-claim seam MUST answer 200; a non-200 that is not 404/403/501 is a broken seam, not an absent one',
|
|
75
76
|
),
|
|
@@ -79,28 +80,28 @@ async function seamPresent(): Promise<ClaimResult | null> {
|
|
|
79
80
|
|
|
80
81
|
describe('idempotency-concurrent-claim: two executors of one run, one effect (RFC 0150 §B, G17)', () => {
|
|
81
82
|
it('every executor mints the SAME logicalInvocationId — without this, one delivery proves nothing', async () => {
|
|
82
|
-
const out = await seamPresent();
|
|
83
|
+
const out = await seamPresent('openwop.it.idempotency-concurrent-claim.every-executor-mints-the-same-logicalinvocationid-without-this-one-delivery-prov');
|
|
83
84
|
if (out === null) return softSkip('blocked', `${SEAM} not mounted (host-sample-test-seams.md §25)`);
|
|
84
85
|
|
|
85
86
|
const minted = out.mintedIds;
|
|
86
87
|
expect(
|
|
87
88
|
Array.isArray(minted),
|
|
88
|
-
|
|
89
|
+
req('openwop.it.idempotency-concurrent-claim.every-executor-mints-the-same-logicalinvocationid-without-this-one-delivery-prov', 'host-sample-test-seams.md §25', 'the seam MUST report mintedIds, one per executor'),
|
|
89
90
|
).toBe(true);
|
|
90
91
|
const ids = minted as unknown[];
|
|
91
92
|
expect(
|
|
92
93
|
ids.length,
|
|
93
|
-
|
|
94
|
+
req('openwop.it.idempotency-concurrent-claim.every-executor-mints-the-same-logicalinvocationid-without-this-one-delivery-prov', 'host-sample-test-seams.md §25', 'mintedIds length MUST equal the executors that reached the chokepoint'),
|
|
94
95
|
).toBe(out.attempted);
|
|
95
96
|
expect(
|
|
96
97
|
ids.length >= 2,
|
|
97
|
-
|
|
98
|
+
req('openwop.it.idempotency-concurrent-claim.every-executor-mints-the-same-logicalinvocationid-without-this-one-delivery-prov', 'host-sample-test-seams.md §25', 'fewer than two executors is not a race and cannot witness the rule'),
|
|
98
99
|
).toBe(true);
|
|
99
100
|
|
|
100
101
|
const unique = new Set(ids.map((x) => JSON.stringify(x)));
|
|
101
102
|
expect(
|
|
102
103
|
unique.size,
|
|
103
|
-
|
|
104
|
+
req('openwop.it.idempotency-concurrent-claim.every-executor-mints-the-same-logicalinvocationid-without-this-one-delivery-prov',
|
|
104
105
|
'idempotency.md §"Idempotency key composition" (Across a recovery boundary)',
|
|
105
106
|
'all executors MUST mint one identity — differing ids mean they never collided, so a single delivery is a vacuous pass rather than a working claim',
|
|
106
107
|
),
|
|
@@ -108,16 +109,16 @@ describe('idempotency-concurrent-claim: two executors of one run, one effect (RF
|
|
|
108
109
|
});
|
|
109
110
|
|
|
110
111
|
it('exactly one effect escapes, however many executors attempt it', async () => {
|
|
111
|
-
const out = await seamPresent();
|
|
112
|
+
const out = await seamPresent('openwop.it.idempotency-concurrent-claim.exactly-one-effect-escapes-however-many-executors-attempt-it');
|
|
112
113
|
if (out === null) return softSkip('blocked', `${SEAM} not mounted (host-sample-test-seams.md §25)`);
|
|
113
114
|
|
|
114
115
|
expect(
|
|
115
116
|
typeof out.attempted === 'number' && (out.attempted as number) >= 2,
|
|
116
|
-
|
|
117
|
+
req('openwop.it.idempotency-concurrent-claim.exactly-one-effect-escapes-however-many-executors-attempt-it', 'host-sample-test-seams.md §25', 'the seam MUST report how many executors attempted the effect'),
|
|
117
118
|
).toBe(true);
|
|
118
119
|
expect(
|
|
119
120
|
out.delivered,
|
|
120
|
-
|
|
121
|
+
req('openwop.it.idempotency-concurrent-claim.exactly-one-effect-escapes-however-many-executors-attempt-it',
|
|
121
122
|
'idempotency.md §"Concurrent duplicates (Layer 2)"',
|
|
122
123
|
'the engine MUST ensure at most one concurrent executor performs the external effect — the guarding persist MUST be an atomic claim, not a read-then-write',
|
|
123
124
|
),
|
|
@@ -125,7 +126,7 @@ describe('idempotency-concurrent-claim: two executors of one run, one effect (RF
|
|
|
125
126
|
});
|
|
126
127
|
|
|
127
128
|
it('the race is real at higher concurrency too — a claim that only holds at 2 is not a claim', async () => {
|
|
128
|
-
const probe = await seamPresent();
|
|
129
|
+
const probe = await seamPresent('openwop.it.idempotency-concurrent-claim.the-race-is-real-at-higher-concurrency-too-a-claim-that-only-holds-at-2-is-not-a');
|
|
129
130
|
if (probe === null) return softSkip('blocked', `${SEAM} not mounted (host-sample-test-seams.md §25)`);
|
|
130
131
|
|
|
131
132
|
const res = await driver.post(SEAM, { executors: 5 });
|
|
@@ -138,7 +139,7 @@ describe('idempotency-concurrent-claim: two executors of one run, one effect (RF
|
|
|
138
139
|
const out = res.json as ClaimResult;
|
|
139
140
|
expect(
|
|
140
141
|
out.delivered,
|
|
141
|
-
|
|
142
|
+
req('openwop.it.idempotency-concurrent-claim.the-race-is-real-at-higher-concurrency-too-a-claim-that-only-holds-at-2-is-not-a',
|
|
142
143
|
'idempotency.md §"Concurrent duplicates (Layer 2)"',
|
|
143
144
|
'exactly one effect regardless of how many executors race — at most one wins the compare-and-set, the rest observe the hit',
|
|
144
145
|
),
|
|
@@ -147,7 +148,7 @@ describe('idempotency-concurrent-claim: two executors of one run, one effect (RF
|
|
|
147
148
|
if (ids.length >= 2) {
|
|
148
149
|
expect(
|
|
149
150
|
new Set(ids.map((x) => JSON.stringify(x))).size,
|
|
150
|
-
|
|
151
|
+
req('openwop.it.idempotency-concurrent-claim.the-race-is-real-at-higher-concurrency-too-a-claim-that-only-holds-at-2-is-not-a', 'idempotency.md §"Idempotency key composition"', 'one identity across all executors at any concurrency'),
|
|
151
152
|
).toBe(1);
|
|
152
153
|
}
|
|
153
154
|
});
|
|
@@ -44,6 +44,8 @@ import { createHash } from 'node:crypto';
|
|
|
44
44
|
import { existsSync } from 'node:fs';
|
|
45
45
|
import { dirname, resolve } from 'node:path';
|
|
46
46
|
import { fileURLToPath } from 'node:url';
|
|
47
|
+
import { req } from '../lib/requirement-ids.js';
|
|
48
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
47
49
|
|
|
48
50
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
49
51
|
const PACK_PATH = resolve(__dirname, '../../../packs/core.openwop.http/index.mjs');
|
|
@@ -114,14 +116,14 @@ describe('category: core.openwop.http.idempotency-key — determinism contract',
|
|
|
114
116
|
// — the scenario soft-skips rather than failing.
|
|
115
117
|
if (!packAvailable) {
|
|
116
118
|
console.warn(`[idempotency-key-determinism] packs/core.openwop.http/index.mjs not present; skipping`);
|
|
117
|
-
expect(packAvailable).toBe(false);
|
|
118
|
-
return;
|
|
119
|
+
expect(packAvailable, req('openwop.it.idempotency-key-determinism.skips-cleanly-when-packs-is-not-bundled', 'idempotency.md §"Idempotency-Key', 'skips cleanly when packs/ is not bundled')).toBe(false);
|
|
120
|
+
return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!packAvailable` returned early ([idempotency-key-determinism] packs/core.openwop.http/index.mjs not present; skipping)');
|
|
119
121
|
}
|
|
120
122
|
expect(packAvailable).toBe(true);
|
|
121
123
|
});
|
|
122
124
|
|
|
123
125
|
it('default mode (composite) — identical (runId, nodeId, payload) produces identical keys', async () => {
|
|
124
|
-
if (!packAvailable) return;
|
|
126
|
+
if (!packAvailable) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!packAvailable` returned early');
|
|
125
127
|
const ctx: IdempotencyKeyCtx = {
|
|
126
128
|
runId: 'run-1',
|
|
127
129
|
nodeId: 'node-1',
|
|
@@ -129,45 +131,45 @@ describe('category: core.openwop.http.idempotency-key — determinism contract',
|
|
|
129
131
|
};
|
|
130
132
|
const a = await idempotencyKey(ctx);
|
|
131
133
|
const b = await idempotencyKey(ctx);
|
|
132
|
-
expect(a.outputs.key, 'idempotency.md §Idempotency-Key: same logical request MUST produce same key').toBe(b.outputs.key);
|
|
134
|
+
expect(a.outputs.key, req('openwop.it.idempotency-key-determinism.default-mode-composite-identical-runid-nodeid-payload-produces-identical-keys', 'idempotency.md §"Idempotency-Key', 'idempotency.md §Idempotency-Key: same logical request MUST produce same key')).toBe(b.outputs.key);
|
|
133
135
|
expect(a.outputs.key).toMatch(KEY_PATTERN);
|
|
134
136
|
});
|
|
135
137
|
|
|
136
138
|
it('payload sensitivity — different payload produces different key', async () => {
|
|
137
|
-
if (!packAvailable) return;
|
|
139
|
+
if (!packAvailable) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!packAvailable` returned early');
|
|
138
140
|
const baseCtx = { runId: 'run-1', nodeId: 'node-1' };
|
|
139
141
|
const a = await idempotencyKey({ ...baseCtx, inputs: { payload: { hello: 'world' } } });
|
|
140
142
|
const b = await idempotencyKey({ ...baseCtx, inputs: { payload: { hello: 'CHANGED' } } });
|
|
141
|
-
expect(a.outputs.key).not.toBe(b.outputs.key);
|
|
143
|
+
expect(a.outputs.key, req('openwop.it.idempotency-key-determinism.payload-sensitivity-different-payload-produces-different-key', 'idempotency.md §"Idempotency-Key', 'payload sensitivity — different payload produces different key')).not.toBe(b.outputs.key);
|
|
142
144
|
});
|
|
143
145
|
|
|
144
146
|
it('run isolation — different runId produces different key', async () => {
|
|
145
|
-
if (!packAvailable) return;
|
|
147
|
+
if (!packAvailable) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!packAvailable` returned early');
|
|
146
148
|
const payload = { hello: 'world' };
|
|
147
149
|
const a = await idempotencyKey({ runId: 'run-1', nodeId: 'node-1', inputs: { payload } });
|
|
148
150
|
const b = await idempotencyKey({ runId: 'run-2', nodeId: 'node-1', inputs: { payload } });
|
|
149
|
-
expect(a.outputs.key).not.toBe(b.outputs.key);
|
|
151
|
+
expect(a.outputs.key, req('openwop.it.idempotency-key-determinism.run-isolation-different-runid-produces-different-key', 'idempotency.md §"Idempotency-Key', 'run isolation — different runId produces different key')).not.toBe(b.outputs.key);
|
|
150
152
|
});
|
|
151
153
|
|
|
152
154
|
it('node isolation — different nodeId produces different key', async () => {
|
|
153
|
-
if (!packAvailable) return;
|
|
155
|
+
if (!packAvailable) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!packAvailable` returned early');
|
|
154
156
|
const payload = { hello: 'world' };
|
|
155
157
|
const a = await idempotencyKey({ runId: 'run-1', nodeId: 'node-A', inputs: { payload } });
|
|
156
158
|
const b = await idempotencyKey({ runId: 'run-1', nodeId: 'node-B', inputs: { payload } });
|
|
157
|
-
expect(a.outputs.key).not.toBe(b.outputs.key);
|
|
159
|
+
expect(a.outputs.key, req('openwop.it.idempotency-key-determinism.node-isolation-different-nodeid-produces-different-key', 'idempotency.md §"Idempotency-Key', 'node isolation — different nodeId produces different key')).not.toBe(b.outputs.key);
|
|
158
160
|
});
|
|
159
161
|
|
|
160
162
|
it('hash mode is deterministic in the payload alone (run/node ignored)', async () => {
|
|
161
|
-
if (!packAvailable) return;
|
|
163
|
+
if (!packAvailable) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!packAvailable` returned early');
|
|
162
164
|
const payload = { request: 'payload-here', id: 42 };
|
|
163
165
|
const a = await idempotencyKey({ config: { mode: 'hash' }, runId: 'run-1', nodeId: 'node-1', inputs: { payload } });
|
|
164
166
|
const b = await idempotencyKey({ config: { mode: 'hash' }, runId: 'run-2', nodeId: 'node-2', inputs: { payload } });
|
|
165
|
-
expect(a.outputs.key, 'hash mode MUST ignore run/node — useful for global remote-dedup caches').toBe(b.outputs.key);
|
|
167
|
+
expect(a.outputs.key, req('openwop.it.idempotency-key-determinism.hash-mode-is-deterministic-in-the-payload-alone-run-node-ignored', 'idempotency.md §"Idempotency-Key', 'hash mode MUST ignore run/node — useful for global remote-dedup caches')).toBe(b.outputs.key);
|
|
166
168
|
expect(a.outputs.key).toMatch(KEY_PATTERN);
|
|
167
169
|
});
|
|
168
170
|
|
|
169
171
|
it('output shape — every emitted key matches openwop-<sha256-prefix-16>', async () => {
|
|
170
|
-
if (!packAvailable) return;
|
|
172
|
+
if (!packAvailable) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!packAvailable` returned early');
|
|
171
173
|
const samples: IdempotencyKeyCtx[] = [
|
|
172
174
|
{ runId: 'r', nodeId: 'n', inputs: { payload: null } },
|
|
173
175
|
{ runId: 'r', nodeId: 'n', inputs: { payload: 'string-payload' } },
|
|
@@ -177,12 +179,12 @@ describe('category: core.openwop.http.idempotency-key — determinism contract',
|
|
|
177
179
|
];
|
|
178
180
|
for (const ctx of samples) {
|
|
179
181
|
const result = await idempotencyKey(ctx);
|
|
180
|
-
expect(result.outputs.key, `key shape for ctx=${JSON.stringify(ctx)}`).toMatch(KEY_PATTERN);
|
|
182
|
+
expect(result.outputs.key, req('openwop.it.idempotency-key-determinism.output-shape-every-emitted-key-matches-openwop-sha256-prefix-16', 'idempotency.md §"Idempotency-Key', `key shape for ctx=${JSON.stringify(ctx)}`)).toMatch(KEY_PATTERN);
|
|
181
183
|
}
|
|
182
184
|
});
|
|
183
185
|
|
|
184
186
|
it('uuid mode rejects with CONFIG_INVALID (safety-fix per 1.1.2)', async () => {
|
|
185
|
-
if (!packAvailable) return;
|
|
187
|
+
if (!packAvailable) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!packAvailable` returned early');
|
|
186
188
|
// The removed mode names the safety-fix in its error message so
|
|
187
189
|
// pinned-version callers diagnosing a regression find the fix.
|
|
188
190
|
let caught: unknown;
|
|
@@ -191,13 +193,13 @@ describe('category: core.openwop.http.idempotency-key — determinism contract',
|
|
|
191
193
|
} catch (err) {
|
|
192
194
|
caught = err;
|
|
193
195
|
}
|
|
194
|
-
expect(caught, 'mode: uuid MUST be rejected — the 1.1.0/1.1.1 non-deterministic default was removed').toBeInstanceOf(Error);
|
|
196
|
+
expect(caught, req('openwop.it.idempotency-key-determinism.uuid-mode-rejects-with-config-invalid-safety-fix-per-1-1-2', 'idempotency.md §"Idempotency-Key', 'mode: uuid MUST be rejected — the 1.1.0/1.1.1 non-deterministic default was removed')).toBeInstanceOf(Error);
|
|
195
197
|
expect((caught as Error & { code?: string }).code).toBe('CONFIG_INVALID');
|
|
196
198
|
expect((caught as Error).message).toMatch(/uuid.*removed|safety-fix/i);
|
|
197
199
|
});
|
|
198
200
|
|
|
199
201
|
it('cross-impl invariant — pack output equals canonical SHA-256 formula', async () => {
|
|
200
|
-
if (!packAvailable) return;
|
|
202
|
+
if (!packAvailable) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!packAvailable` returned early');
|
|
201
203
|
// Five fixed vectors. A third-party core.openwop.http reimplementation
|
|
202
204
|
// can run the same scenario against its runtime — agreement on these
|
|
203
205
|
// vectors proves wire-level interop on the Idempotency-Key shape.
|
|
@@ -217,7 +219,7 @@ describe('category: core.openwop.http.idempotency-key — determinism contract',
|
|
|
217
219
|
const expected = canonicalCompositeKey(v.runId, v.nodeId, v.payload);
|
|
218
220
|
expect(
|
|
219
221
|
packed.outputs.key,
|
|
220
|
-
`vector ${JSON.stringify(v)} — pack output MUST match canonical sha256(runId\\0nodeId\\0JSON(payload))
|
|
222
|
+
req('openwop.it.idempotency-key-determinism.cross-impl-invariant-pack-output-equals-canonical-sha-256-formula', 'idempotency.md §"Idempotency-Key', `vector ${JSON.stringify(v)} — pack output MUST match canonical sha256(runId\\0nodeId\\0JSON(payload))`),
|
|
221
223
|
).toBe(expected);
|
|
222
224
|
}
|
|
223
225
|
|
|
@@ -18,6 +18,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
18
18
|
import { driver } from '../lib/driver.js';
|
|
19
19
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
20
20
|
import { readErrorCode } from '../lib/error-envelope.js';
|
|
21
|
+
import { req } from '../lib/requirement-ids.js';
|
|
21
22
|
|
|
22
23
|
const WORKFLOW_ID = 'conformance-idempotent';
|
|
23
24
|
const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
|
|
@@ -34,7 +35,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('idempotency: same key + same body replays per
|
|
|
34
35
|
const first = await driver.post('/v1/runs', body, {
|
|
35
36
|
headers: { 'Idempotency-Key': key },
|
|
36
37
|
});
|
|
37
|
-
expect(first.status,
|
|
38
|
+
expect(first.status, req('openwop.it.idempotency.returns-same-runid-twice-and-sets-openwop-idempotent-replay-on-the-replay',
|
|
38
39
|
'rest-endpoints.md',
|
|
39
40
|
'first POST /v1/runs MUST return 201',
|
|
40
41
|
)).toBe(201);
|
|
@@ -45,20 +46,20 @@ describe.skipIf(SKIP_NO_FIXTURE)('idempotency: same key + same body replays per
|
|
|
45
46
|
});
|
|
46
47
|
expect(
|
|
47
48
|
[200, 201].includes(replay.status),
|
|
48
|
-
|
|
49
|
+
req('openwop.it.idempotency.returns-same-runid-twice-and-sets-openwop-idempotent-replay-on-the-replay',
|
|
49
50
|
'idempotency.md §Layer 1',
|
|
50
51
|
'replay request with same key + same body MUST return success status (200/201)',
|
|
51
52
|
),
|
|
52
53
|
).toBe(true);
|
|
53
54
|
|
|
54
55
|
const replayRunId = (replay.json as { runId: string }).runId;
|
|
55
|
-
expect(replayRunId,
|
|
56
|
+
expect(replayRunId, req('openwop.it.idempotency.returns-same-runid-twice-and-sets-openwop-idempotent-replay-on-the-replay',
|
|
56
57
|
'idempotency.md §Layer 1',
|
|
57
58
|
'replay MUST return the SAME runId (no new run created)',
|
|
58
59
|
)).toBe(firstRunId);
|
|
59
60
|
|
|
60
61
|
const replayHeader = replay.headers.get('openwop-idempotent-replay');
|
|
61
|
-
expect(replayHeader,
|
|
62
|
+
expect(replayHeader, req('openwop.it.idempotency.returns-same-runid-twice-and-sets-openwop-idempotent-replay-on-the-replay',
|
|
62
63
|
'rest-endpoints.md POST /v1/runs response headers',
|
|
63
64
|
'openwop-Idempotent-Replay header MUST be set on cache-served responses',
|
|
64
65
|
)).toBeTruthy();
|
|
@@ -82,7 +83,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('idempotency: same key + different body conflic
|
|
|
82
83
|
{ headers: { 'Idempotency-Key': key } },
|
|
83
84
|
);
|
|
84
85
|
|
|
85
|
-
expect(conflict.status,
|
|
86
|
+
expect(conflict.status, req('openwop.it.idempotency.returns-409-when-the-body-changes-under-the-same-key',
|
|
86
87
|
'idempotency.md §Layer 1',
|
|
87
88
|
'same Idempotency-Key with a different body MUST return 409',
|
|
88
89
|
)).toBe(409);
|
|
@@ -107,7 +108,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('idempotency: same key + different body conflic
|
|
|
107
108
|
const LEGACY = ['idempotency_key_conflict', 'idempotency_key_replay_mismatch'];
|
|
108
109
|
expect(
|
|
109
110
|
code === 'idempotency_key_mismatch' || LEGACY.includes(code ?? ''),
|
|
110
|
-
|
|
111
|
+
req('openwop.it.idempotency.returns-409-when-the-body-changes-under-the-same-key',
|
|
111
112
|
'idempotency.md §"Record shape, digest, and lease"',
|
|
112
113
|
`a different request digest under the same scoped key MUST fail with the canonical ` +
|
|
113
114
|
`\`idempotency_key_mismatch\` (legacy \`${LEGACY.join('` / `')}\` tolerated through the ` +
|
|
@@ -120,7 +121,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('idempotency: same key + different body conflic
|
|
|
120
121
|
const conflictRunId = (conflict.json as { runId?: unknown } | undefined)?.runId;
|
|
121
122
|
expect(
|
|
122
123
|
conflictRunId,
|
|
123
|
-
|
|
124
|
+
req('openwop.it.idempotency.returns-409-when-the-body-changes-under-the-same-key',
|
|
124
125
|
'idempotency.md §"Record shape, digest, and lease"',
|
|
125
126
|
'a digest mismatch MUST NOT return the cached body (no runId on the 409)',
|
|
126
127
|
),
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
import { describe, it, expect } from 'vitest';
|
|
27
27
|
import { driver } from '../lib/driver.js';
|
|
28
28
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
29
|
+
import { req } from '../lib/requirement-ids.js';
|
|
30
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
29
31
|
|
|
30
32
|
const WORKFLOW_ID = 'conformance-idempotent';
|
|
31
33
|
const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
|
|
@@ -40,7 +42,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('idempotency-retry: openwop-Idempotent-Replay h
|
|
|
40
42
|
const body = { workflowId: WORKFLOW_ID, inputs: { nonce: 'replay-test' } };
|
|
41
43
|
|
|
42
44
|
const first = await driver.post('/v1/runs', body, { headers: { 'Idempotency-Key': key } });
|
|
43
|
-
expect(first.status,
|
|
45
|
+
expect(first.status, req('openwop.it.idempotencyRetry.first-request-with-new-key-returns-false-or-absent-should-per-current-spec-repla',
|
|
44
46
|
'rest-endpoints.md',
|
|
45
47
|
'first POST /v1/runs returns 201',
|
|
46
48
|
)).toBe(201);
|
|
@@ -50,7 +52,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('idempotency-retry: openwop-Idempotent-Replay h
|
|
|
50
52
|
// set to "false". Pre-RFC spec only requires it on the replay.
|
|
51
53
|
// Permissive assertion: if present, MUST be "false" or "true".
|
|
52
54
|
if (firstReplay !== null) {
|
|
53
|
-
expect(['false', 'true'].includes(firstReplay),
|
|
55
|
+
expect(['false', 'true'].includes(firstReplay), req('openwop.it.idempotencyRetry.first-request-with-new-key-returns-false-or-absent-should-per-current-spec-repla',
|
|
54
56
|
'idempotency.md §Server responsibilities',
|
|
55
57
|
'openwop-Idempotent-Replay value MUST be "true" or "false"',
|
|
56
58
|
)).toBe(true);
|
|
@@ -59,18 +61,18 @@ describe.skipIf(SKIP_NO_FIXTURE)('idempotency-retry: openwop-Idempotent-Replay h
|
|
|
59
61
|
const replay = await driver.post('/v1/runs', body, { headers: { 'Idempotency-Key': key } });
|
|
60
62
|
expect(
|
|
61
63
|
[200, 201].includes(replay.status),
|
|
62
|
-
|
|
64
|
+
req('openwop.it.idempotencyRetry.first-request-with-new-key-returns-false-or-absent-should-per-current-spec-repla', 'idempotency.md §Layer 1', 'replay returns 200 or 201'),
|
|
63
65
|
).toBe(true);
|
|
64
66
|
|
|
65
67
|
const replayHeader = replay.headers.get('openwop-idempotent-replay');
|
|
66
68
|
// Per idempotency.md §Server responsibilities #2: SHOULD be set.
|
|
67
69
|
// RFC 0002 §1 promotes to MUST. Today's strictness: present on replay.
|
|
68
|
-
expect(replayHeader,
|
|
70
|
+
expect(replayHeader, req('openwop.it.idempotencyRetry.first-request-with-new-key-returns-false-or-absent-should-per-current-spec-repla',
|
|
69
71
|
'idempotency.md §Server responsibilities #2',
|
|
70
72
|
'openwop-Idempotent-Replay SHOULD be set on idempotent replay responses',
|
|
71
73
|
)).not.toBeNull();
|
|
72
74
|
if (replayHeader !== null) {
|
|
73
|
-
expect(replayHeader,
|
|
75
|
+
expect(replayHeader, req('openwop.it.idempotencyRetry.first-request-with-new-key-returns-false-or-absent-should-per-current-spec-repla',
|
|
74
76
|
'idempotency.md §Server responsibilities #2',
|
|
75
77
|
'openwop-Idempotent-Replay on replay MUST be "true"',
|
|
76
78
|
)).toBe('true');
|
|
@@ -93,7 +95,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('idempotency-retry: 5-retry budget per scale-pr
|
|
|
93
95
|
for (const res of responses) {
|
|
94
96
|
expect(
|
|
95
97
|
[200, 201].includes(res.status),
|
|
96
|
-
|
|
98
|
+
req('openwop.it.idempotencyRetry.5-retries-100ms-apart-with-same-key-all-return-the-same-runid',
|
|
97
99
|
'scale-profiles.md §Retry semantics',
|
|
98
100
|
'host MUST handle ≥5 retries 100ms apart without losing the cached response',
|
|
99
101
|
),
|
|
@@ -101,7 +103,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('idempotency-retry: 5-retry budget per scale-pr
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
const runIds = new Set(responses.map((r) => (r.json as { runId?: string })?.runId));
|
|
104
|
-
expect(runIds.size,
|
|
106
|
+
expect(runIds.size, req('openwop.it.idempotencyRetry.5-retries-100ms-apart-with-same-key-all-return-the-same-runid',
|
|
105
107
|
'idempotency.md §Layer 1',
|
|
106
108
|
'5 retries with same key MUST collapse to exactly one runId',
|
|
107
109
|
)).toBe(1);
|
|
@@ -114,18 +116,18 @@ describe('idempotency-retry: limits.idempotencyAckTimeoutSec contract per idempo
|
|
|
114
116
|
expect(res.status).toBe(200);
|
|
115
117
|
|
|
116
118
|
const limits = (res.json as { limits?: Record<string, unknown> })?.limits;
|
|
117
|
-
if (!limits) return; // limits required per capabilities.md §3 — covered elsewhere
|
|
119
|
+
if (!limits) return softSkip('blocked', 'precondition not met — `!limits` returned early (limits required per capabilities.md §3 — covered elsewhere) (seam, prior step, or fixture unavailable)'); // limits required per capabilities.md §3 — covered elsewhere
|
|
118
120
|
const ack = limits.idempotencyAckTimeoutSec;
|
|
119
121
|
if (ack === undefined) {
|
|
120
122
|
// Per idempotency.md, the field is optional; absence implies the
|
|
121
123
|
// 5-second floor. Nothing to assert.
|
|
122
|
-
return;
|
|
124
|
+
return softSkip('blocked', 'precondition not met — `ack === undefined` returned early (Per idempotency.md, the field is optional; absence implies the 5-second floor. Nothing to assert.) (seam, prior step, or fixture unavailable)');
|
|
123
125
|
}
|
|
124
|
-
expect(typeof ack === 'number' && Number.isInteger(ack),
|
|
126
|
+
expect(typeof ack === 'number' && Number.isInteger(ack), req('openwop.it.idempotencyRetry.host-advertising-idempotencyacktimeoutsec-sets-integer-5',
|
|
125
127
|
'idempotency.md',
|
|
126
128
|
'limits.idempotencyAckTimeoutSec MUST be an integer when advertised',
|
|
127
129
|
)).toBe(true);
|
|
128
|
-
expect(ack as number,
|
|
130
|
+
expect(ack as number, req('openwop.it.idempotencyRetry.host-advertising-idempotencyacktimeoutsec-sets-integer-5',
|
|
129
131
|
'idempotency.md',
|
|
130
132
|
'limits.idempotencyAckTimeoutSec MUST be ≥ 5',
|
|
131
133
|
)).toBeGreaterThanOrEqual(5);
|
|
@@ -22,6 +22,7 @@ import { readFileSync } from 'node:fs';
|
|
|
22
22
|
import { join } from 'node:path';
|
|
23
23
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
24
24
|
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
25
|
+
import { req } from '../lib/requirement-ids.js';
|
|
25
26
|
|
|
26
27
|
interface SnapshotSchema {
|
|
27
28
|
$schema: string;
|
|
@@ -37,7 +38,7 @@ describe('category: identity owner-triple shape (RFC 0048 §C)', () => {
|
|
|
37
38
|
it('run-snapshot.schema.json defines an optional owner triple', () => {
|
|
38
39
|
expect(
|
|
39
40
|
snapshot.properties.owner,
|
|
40
|
-
'RFC 0048 §C: RunSnapshot MUST define an optional `owner` object',
|
|
41
|
+
req('openwop.it.identity-owner-shape.run-snapshot-schema-json-defines-an-optional-owner-triple', 'RFC 0048 §C', 'RFC 0048 §C: RunSnapshot MUST define an optional `owner` object'),
|
|
41
42
|
).toBeDefined();
|
|
42
43
|
});
|
|
43
44
|
|
|
@@ -48,21 +49,21 @@ describe('category: identity owner-triple shape (RFC 0048 §C)', () => {
|
|
|
48
49
|
const validate = ajv.compile(ownerSchema);
|
|
49
50
|
|
|
50
51
|
it('positive: tenant-only owner validates', () => {
|
|
51
|
-
expect(validate({ tenant: 'acme' }), JSON.stringify(validate.errors)).toBe(true);
|
|
52
|
+
expect(validate({ tenant: 'acme' }), req('openwop.it.identity-owner-shape.positive-tenant-only-owner-validates', 'RFC 0048 §C', JSON.stringify(validate.errors))).toBe(true);
|
|
52
53
|
});
|
|
53
54
|
|
|
54
55
|
it('positive: full triple validates', () => {
|
|
55
56
|
expect(
|
|
56
57
|
validate({ tenant: 'acme', workspace: 'ws-eng', principal: 'user_42' }),
|
|
57
|
-
JSON.stringify(validate.errors),
|
|
58
|
+
req('openwop.it.identity-owner-shape.positive-full-triple-validates', 'RFC 0048 §C', JSON.stringify(validate.errors)),
|
|
58
59
|
).toBe(true);
|
|
59
60
|
});
|
|
60
61
|
|
|
61
62
|
it('negative: owner missing tenant is rejected (tenant is required)', () => {
|
|
62
|
-
expect(validate({ workspace: 'ws-eng' })).toBe(false);
|
|
63
|
+
expect(validate({ workspace: 'ws-eng' }), req('openwop.it.identity-owner-shape.negative-owner-missing-tenant-is-rejected-tenant-is-required', 'RFC 0048 §C', 'negative: owner missing tenant is rejected (tenant is required)')).toBe(false);
|
|
63
64
|
});
|
|
64
65
|
|
|
65
66
|
it('negative: unknown owner property is rejected (additionalProperties:false)', () => {
|
|
66
|
-
expect(validate({ tenant: 'acme', role: 'admin' })).toBe(false);
|
|
67
|
+
expect(validate({ tenant: 'acme', role: 'admin' }), req('openwop.it.identity-owner-shape.negative-unknown-owner-property-is-rejected-additionalproperties-false', 'RFC 0048 §C', 'negative: unknown owner property is rejected (additionalProperties:false)')).toBe(false);
|
|
67
68
|
});
|
|
68
69
|
});
|
|
@@ -13,6 +13,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
13
13
|
import { driver } from '../lib/driver.js';
|
|
14
14
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
15
15
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
16
|
+
import { req } from '../lib/requirement-ids.js';
|
|
16
17
|
|
|
17
18
|
const WORKFLOW_ID = 'conformance-identity';
|
|
18
19
|
const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
|
|
@@ -33,7 +34,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('identity: conformance-identity fixture echoes
|
|
|
33
34
|
workflowId: WORKFLOW_ID,
|
|
34
35
|
inputs: { payload },
|
|
35
36
|
});
|
|
36
|
-
expect(create.status,
|
|
37
|
+
expect(create.status, req('openwop.it.identity-passthrough.arbitrary-nested-json-payload-round-trips-through-inputs-variables',
|
|
37
38
|
'rest-endpoints.md',
|
|
38
39
|
'POST /v1/runs MUST return 201',
|
|
39
40
|
)).toBe(201);
|
|
@@ -41,12 +42,12 @@ describe.skipIf(SKIP_NO_FIXTURE)('identity: conformance-identity fixture echoes
|
|
|
41
42
|
|
|
42
43
|
const terminal = await pollUntilTerminal(runId);
|
|
43
44
|
|
|
44
|
-
expect(terminal.status,
|
|
45
|
+
expect(terminal.status, req('openwop.it.identity-passthrough.arbitrary-nested-json-payload-round-trips-through-inputs-variables',
|
|
45
46
|
'fixtures.md conformance-identity',
|
|
46
47
|
'identity fixture MUST reach terminal `completed`',
|
|
47
48
|
)).toBe('completed');
|
|
48
49
|
|
|
49
|
-
expect(terminal.variables?.payload,
|
|
50
|
+
expect(terminal.variables?.payload, req('openwop.it.identity-passthrough.arbitrary-nested-json-payload-round-trips-through-inputs-variables',
|
|
50
51
|
'fixtures.md conformance-identity §Expected behavior',
|
|
51
52
|
'RunSnapshot.variables.payload MUST deep-equal the input payload',
|
|
52
53
|
)).toEqual(payload);
|
|
@@ -11,6 +11,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
11
11
|
import { driver } from '../lib/driver.js';
|
|
12
12
|
import { pollUntilStatus, pollUntilTerminal } from '../lib/polling.js';
|
|
13
13
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
14
|
+
import { req } from '../lib/requirement-ids.js';
|
|
14
15
|
|
|
15
16
|
const WORKFLOW_ID = 'conformance-approval';
|
|
16
17
|
const NODE_ID = 'gate';
|
|
@@ -23,7 +24,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('interrupt: approval accept resumes to `complet
|
|
|
23
24
|
const runId = (create.json as { runId: string }).runId;
|
|
24
25
|
|
|
25
26
|
const suspended = await pollUntilStatus(runId, 'waiting-approval', { timeoutMs: 10_000 });
|
|
26
|
-
expect(suspended.currentNodeId,
|
|
27
|
+
expect(suspended.currentNodeId, req('openwop.it.interrupt-approval.run-suspends-at-gate-accept-resolution-drives-terminal-completed',
|
|
27
28
|
'fixtures.md conformance-approval',
|
|
28
29
|
'suspended run MUST report currentNodeId === "gate"',
|
|
29
30
|
)).toBe(NODE_ID);
|
|
@@ -32,13 +33,13 @@ describe.skipIf(SKIP_NO_FIXTURE)('interrupt: approval accept resumes to `complet
|
|
|
32
33
|
`/v1/runs/${encodeURIComponent(runId)}/interrupts/${encodeURIComponent(NODE_ID)}`,
|
|
33
34
|
{ resumeValue: { action: 'accept' } },
|
|
34
35
|
);
|
|
35
|
-
expect(resolve.status,
|
|
36
|
+
expect(resolve.status, req('openwop.it.interrupt-approval.run-suspends-at-gate-accept-resolution-drives-terminal-completed',
|
|
36
37
|
'rest-endpoints.md POST /v1/runs/{runId}/interrupts/{nodeId}',
|
|
37
38
|
'valid approval resolve MUST return 200',
|
|
38
39
|
)).toBe(200);
|
|
39
40
|
|
|
40
41
|
const terminal = await pollUntilTerminal(runId, { timeoutMs: 10_000 });
|
|
41
|
-
expect(terminal.status,
|
|
42
|
+
expect(terminal.status, req('openwop.it.interrupt-approval.run-suspends-at-gate-accept-resolution-drives-terminal-completed',
|
|
42
43
|
'fixtures.md conformance-approval §Terminal status',
|
|
43
44
|
'fixture after accept MUST reach terminal `completed`',
|
|
44
45
|
)).toBe('completed');
|
|
@@ -59,7 +60,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('interrupt: invalid resolve payload rejected pe
|
|
|
59
60
|
);
|
|
60
61
|
expect(
|
|
61
62
|
[400, 422].includes(resolve.status),
|
|
62
|
-
|
|
63
|
+
req('openwop.it.interrupt-approval.400-or-422-when-action-is-not-in-accept-reject',
|
|
63
64
|
'interrupt.md + resumeSchema validation',
|
|
64
65
|
'resolve payload that violates resumeSchema MUST return 400 or 422',
|
|
65
66
|
),
|
|
@@ -85,7 +86,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('interrupt: resolving an unknown interrupt retu
|
|
|
85
86
|
`/v1/runs/${encodeURIComponent(runId)}/interrupts/no-such-node`,
|
|
86
87
|
{ resumeValue: { action: 'accept' } },
|
|
87
88
|
);
|
|
88
|
-
expect(resolve.status,
|
|
89
|
+
expect(resolve.status, req('openwop.it.interrupt-approval.400-404-when-nodeid-does-not-match-an-active-interrupt',
|
|
89
90
|
'rest-endpoints.md POST /v1/runs/{runId}/interrupts/{nodeId}',
|
|
90
91
|
'resolving an unknown nodeId MUST return 404',
|
|
91
92
|
)).toBe(404);
|
|
@@ -35,8 +35,8 @@ import addFormats from 'ajv-formats';
|
|
|
35
35
|
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
36
36
|
import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
|
|
37
37
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
39
|
+
import { req } from '../lib/requirement-ids.js';
|
|
40
40
|
function loadSchema(name: string): Record<string, unknown> {
|
|
41
41
|
return JSON.parse(readFileSync(join(SCHEMAS_DIR, name), 'utf8')) as Record<string, unknown>;
|
|
42
42
|
}
|
|
@@ -81,12 +81,12 @@ describe('interrupt-approver-routing: capability advertisement shape (capabiliti
|
|
|
81
81
|
const interrupt = (caps.properties as Record<string, { properties?: Record<string, { required?: string[]; properties?: Record<string, unknown> }> }>).interrupt;
|
|
82
82
|
|
|
83
83
|
it('capabilities schema declares interrupt.approverRouting', () => {
|
|
84
|
-
expect(interrupt,
|
|
84
|
+
expect(interrupt, req('openwop.it.interrupt-approver-routing.capabilities-schema-declares-interrupt-approverrouting', 'capabilities.schema.json §interrupt', 'the interrupt block MUST be declared')).toBeDefined();
|
|
85
85
|
const ar = interrupt?.properties?.approverRouting;
|
|
86
|
-
expect(ar,
|
|
87
|
-
expect(ar?.required,
|
|
86
|
+
expect(ar, req('openwop.it.interrupt-approver-routing.capabilities-schema-declares-interrupt-approverrouting', 'RFC 0104', 'interrupt.approverRouting MUST be declared')).toBeDefined();
|
|
87
|
+
expect(ar?.required, req('openwop.it.interrupt-approver-routing.capabilities-schema-declares-interrupt-approverrouting', 'RFC 0104', 'approverRouting.supported MUST be required')).toEqual(expect.arrayContaining(['supported']));
|
|
88
88
|
for (const f of ['supported', 'refKinds', 'audience']) {
|
|
89
|
-
expect(ar?.properties?.[f],
|
|
89
|
+
expect(ar?.properties?.[f], req('openwop.it.interrupt-approver-routing.capabilities-schema-declares-interrupt-approverrouting', 'RFC 0104', `approverRouting.${f} MUST be declared`)).toBeDefined();
|
|
90
90
|
}
|
|
91
91
|
});
|
|
92
92
|
});
|
|
@@ -104,7 +104,7 @@ describe('interrupt-approver-routing: ApprovalData additive optionality (interru
|
|
|
104
104
|
};
|
|
105
105
|
|
|
106
106
|
it('an approval payload WITHOUT the routing fields still validates (additive, optional)', () => {
|
|
107
|
-
expect(validate(baseApproval),
|
|
107
|
+
expect(validate(baseApproval), req('openwop.it.interrupt-approver-routing.an-approval-payload-without-the-routing-fields-still-validates-additive-optional', 'RFC 0104 Compatibility', `the fields are optional — Errors: ${JSON.stringify(validate.errors)}`)).toBe(true);
|
|
108
108
|
});
|
|
109
109
|
it('an approval payload WITH group/role refs + audience validates', () => {
|
|
110
110
|
const withRouting = {
|
|
@@ -116,11 +116,11 @@ describe('interrupt-approver-routing: ApprovalData additive optionality (interru
|
|
|
116
116
|
audience: { groups: ['grp:finance-approvers'], roles: ['role:controller'], subjects: ['user:cfo'] },
|
|
117
117
|
},
|
|
118
118
|
};
|
|
119
|
-
expect(validate(withRouting),
|
|
119
|
+
expect(validate(withRouting), req('openwop.it.interrupt-approver-routing.an-approval-payload-with-group-role-refs-audience-validates', 'RFC 0104 §Proposal', `routing fields MUST validate — Errors: ${JSON.stringify(validate.errors)}`)).toBe(true);
|
|
120
120
|
});
|
|
121
121
|
it('an audience with an unknown key is rejected (audience object is closed)', () => {
|
|
122
122
|
const badAudience = { ...baseApproval, data: { ...baseApproval.data, audience: { teams: ['grp:x'] } } };
|
|
123
|
-
expect(validate(badAudience),
|
|
123
|
+
expect(validate(badAudience), req('openwop.it.interrupt-approver-routing.an-audience-with-an-unknown-key-is-rejected-audience-object-is-closed', 'RFC 0104', 'audience MUST be additionalProperties:false')).toBe(false);
|
|
124
124
|
});
|
|
125
125
|
});
|
|
126
126
|
|
|
@@ -128,17 +128,17 @@ describe('interrupt-approver-routing: audience default/override rule (interrupt.
|
|
|
128
128
|
it('omitted audience ⇒ notify the resolved eligibility union', () => {
|
|
129
129
|
expect(
|
|
130
130
|
notifyTargets({ approversList: ['user:a'], approverGroupRefs: ['grp:fin'], approverRoleRefs: ['role:ctrl'] }),
|
|
131
|
-
|
|
131
|
+
req('openwop.it.interrupt-approver-routing.omitted-audience-notify-the-resolved-eligibility-union', 'RFC 0104', 'omitted audience MUST default to the eligibility union'),
|
|
132
132
|
).toEqual(['user:a', 'grp:fin', 'role:ctrl']);
|
|
133
133
|
});
|
|
134
134
|
it('present audience ⇒ overrides the eligibility union', () => {
|
|
135
135
|
expect(
|
|
136
136
|
notifyTargets({ approverGroupRefs: ['grp:fin'], audience: { subjects: ['user:cfo'], groups: ['grp:audit'] } }),
|
|
137
|
-
|
|
137
|
+
req('openwop.it.interrupt-approver-routing.present-audience-overrides-the-eligibility-union', 'RFC 0104', 'present audience MUST override the default'),
|
|
138
138
|
).toEqual(['user:cfo', 'grp:audit']);
|
|
139
139
|
});
|
|
140
140
|
it('no eligibility refs and no audience ⇒ empty target set', () => {
|
|
141
|
-
expect(notifyTargets({}),
|
|
141
|
+
expect(notifyTargets({}), req('openwop.it.interrupt-approver-routing.no-eligibility-refs-and-no-audience-empty-target-set', 'RFC 0104', 'nothing to route when no refs')).toEqual([]);
|
|
142
142
|
});
|
|
143
143
|
});
|
|
144
144
|
|
|
@@ -148,7 +148,7 @@ describe('interrupt-approver-routing: audience default/override rule (interrupt.
|
|
|
148
148
|
|
|
149
149
|
describe('interrupt-approver-routing: advertised shape is honest (capability-gated)', () => {
|
|
150
150
|
it('an advertising host advertises a coherent approverRouting block', async () => {
|
|
151
|
-
if (!process.env.OPENWOP_BASE_URL) return; // no live host → nothing to read
|
|
151
|
+
if (!process.env.OPENWOP_BASE_URL) return softSkip('blocked', 'precondition not met — `!process.env.OPENWOP_BASE_URL` returned early (no live host → nothing to read) (seam, prior step, or fixture unavailable)'); // no live host → nothing to read
|
|
152
152
|
const interrupt = await readCapabilityFamily<InterruptCap>('interrupt');
|
|
153
153
|
const ar = interrupt?.approverRouting;
|
|
154
154
|
// Soft-skip: host does not advertise the capability (fields ignored; still conformant).
|
|
@@ -157,10 +157,10 @@ describe('interrupt-approver-routing: advertised shape is honest (capability-gat
|
|
|
157
157
|
// Opt-in established (supported === true): the advertised shape MUST be honest.
|
|
158
158
|
const allowed = new Set(['group', 'role']);
|
|
159
159
|
for (const k of ar?.refKinds ?? []) {
|
|
160
|
-
expect(allowed.has(k),
|
|
160
|
+
expect(allowed.has(k), req('openwop.it.interrupt-approver-routing.an-advertising-host-advertises-a-coherent-approverrouting-block', 'RFC 0104 capabilities.md', `refKinds MUST be a subset of {group, role} — saw ${k}`)).toBe(true);
|
|
161
161
|
}
|
|
162
162
|
if (ar?.audience !== undefined) {
|
|
163
|
-
expect(typeof ar.audience,
|
|
163
|
+
expect(typeof ar.audience, req('openwop.it.interrupt-approver-routing.an-advertising-host-advertises-a-coherent-approverrouting-block', 'RFC 0104 capabilities.md', 'approverRouting.audience MUST be boolean')).toBe('boolean');
|
|
164
164
|
}
|
|
165
165
|
});
|
|
166
166
|
});
|