@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
|
@@ -22,6 +22,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
22
22
|
import { driver } from '../lib/driver.js';
|
|
23
23
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
24
24
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
25
|
+
import { req } from '../lib/requirement-ids.js';
|
|
25
26
|
|
|
26
27
|
const WORKFLOW_ID = 'conformance-cap-breach';
|
|
27
28
|
const RECURSION_LIMIT = 3;
|
|
@@ -42,7 +43,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('cap-breach: conformance-cap-breach fixture fai
|
|
|
42
43
|
workflowId: WORKFLOW_ID,
|
|
43
44
|
configurable: { recursionLimit: RECURSION_LIMIT },
|
|
44
45
|
});
|
|
45
|
-
expect(create.status,
|
|
46
|
+
expect(create.status, req('openwop.it.cap-breach.emits-cap-breached-transitions-to-terminal-failed-when-configurable-recursionlim',
|
|
46
47
|
'rest-endpoints.md POST /v1/runs',
|
|
47
48
|
'run creation MUST accept the request even when configurable.recursionLimit is below the workflow size',
|
|
48
49
|
)).toBe(201);
|
|
@@ -50,17 +51,17 @@ describe.skipIf(SKIP_NO_FIXTURE)('cap-breach: conformance-cap-breach fixture fai
|
|
|
50
51
|
|
|
51
52
|
const terminal = await pollUntilTerminal(runId);
|
|
52
53
|
|
|
53
|
-
expect(terminal.status,
|
|
54
|
+
expect(terminal.status, req('openwop.it.cap-breach.emits-cap-breached-transitions-to-terminal-failed-when-configurable-recursionlim',
|
|
54
55
|
'fixtures.md conformance-cap-breach §Terminal status',
|
|
55
56
|
'fixture MUST reach terminal `failed` when recursion limit is exceeded',
|
|
56
57
|
)).toBe('failed');
|
|
57
58
|
|
|
58
|
-
expect(terminal.error?.code,
|
|
59
|
+
expect(terminal.error?.code, req('openwop.it.cap-breach.emits-cap-breached-transitions-to-terminal-failed-when-configurable-recursionlim',
|
|
59
60
|
'run-options.md §recursionLimit',
|
|
60
61
|
'RunSnapshot.error.code MUST equal "recursion_limit_exceeded"',
|
|
61
62
|
)).toBe('recursion_limit_exceeded');
|
|
62
63
|
|
|
63
|
-
expect(typeof terminal.error?.message,
|
|
64
|
+
expect(typeof terminal.error?.message, req('openwop.it.cap-breach.emits-cap-breached-transitions-to-terminal-failed-when-configurable-recursionlim',
|
|
64
65
|
'rest-endpoints.md RunSnapshot.error.message',
|
|
65
66
|
'RunSnapshot.error.message MUST be a string',
|
|
66
67
|
)).toBe('string');
|
|
@@ -72,7 +73,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('cap-breach: conformance-cap-breach fixture fai
|
|
|
72
73
|
const events = (eventsRes.json as { events?: RunEvent[] } | undefined)?.events ?? [];
|
|
73
74
|
|
|
74
75
|
const capBreachEvents = events.filter((e) => e.type === 'cap.breached');
|
|
75
|
-
expect(capBreachEvents.length,
|
|
76
|
+
expect(capBreachEvents.length, req('openwop.it.cap-breach.emits-cap-breached-transitions-to-terminal-failed-when-configurable-recursionlim',
|
|
76
77
|
'observability.md §cap.breached',
|
|
77
78
|
'exactly one cap.breached event MUST be emitted on recursion-limit exceedance',
|
|
78
79
|
)).toBe(1);
|
|
@@ -82,23 +83,23 @@ describe.skipIf(SKIP_NO_FIXTURE)('cap-breach: conformance-cap-breach fixture fai
|
|
|
82
83
|
| { kind?: string; limit?: number; observed?: number; nodeId?: string }
|
|
83
84
|
| undefined;
|
|
84
85
|
|
|
85
|
-
expect(payload?.kind,
|
|
86
|
+
expect(payload?.kind, req('openwop.it.cap-breach.emits-cap-breached-transitions-to-terminal-failed-when-configurable-recursionlim',
|
|
86
87
|
'run-event-payloads.schema.json §capBreached.kind',
|
|
87
88
|
'cap.breached payload MUST carry kind="node-executions"',
|
|
88
89
|
)).toBe('node-executions');
|
|
89
90
|
|
|
90
|
-
expect(payload?.limit,
|
|
91
|
+
expect(payload?.limit, req('openwop.it.cap-breach.emits-cap-breached-transitions-to-terminal-failed-when-configurable-recursionlim',
|
|
91
92
|
'run-event-payloads.schema.json §capBreached.limit',
|
|
92
93
|
'cap.breached payload MUST carry the resolved limit (3 from configurable.recursionLimit)',
|
|
93
94
|
)).toBe(RECURSION_LIMIT);
|
|
94
95
|
|
|
95
|
-
expect(typeof payload?.observed,
|
|
96
|
+
expect(typeof payload?.observed, req('openwop.it.cap-breach.emits-cap-breached-transitions-to-terminal-failed-when-configurable-recursionlim',
|
|
96
97
|
'run-event-payloads.schema.json §capBreached.observed',
|
|
97
98
|
'cap.breached payload MUST carry the observed count as a number',
|
|
98
99
|
)).toBe('number');
|
|
99
100
|
expect(payload?.observed).toBeGreaterThan(RECURSION_LIMIT);
|
|
100
101
|
|
|
101
|
-
expect(typeof payload?.nodeId,
|
|
102
|
+
expect(typeof payload?.nodeId, req('openwop.it.cap-breach.emits-cap-breached-transitions-to-terminal-failed-when-configurable-recursionlim',
|
|
102
103
|
'run-event-payloads.schema.json §capBreached.nodeId',
|
|
103
104
|
'cap.breached payload MUST carry the offending nodeId for node-executions kind',
|
|
104
105
|
)).toBe('string');
|
|
@@ -121,10 +122,10 @@ describe.skipIf(SKIP_NO_FIXTURE)('cap-breach: conformance-cap-breach fixture fai
|
|
|
121
122
|
const capBreach = events.find((e) => e.type === 'cap.breached');
|
|
122
123
|
const runFailed = events.find((e) => e.type === 'run.failed');
|
|
123
124
|
|
|
124
|
-
expect(capBreach, 'cap.breached MUST be emitted').toBeDefined();
|
|
125
|
-
expect(runFailed, 'run.failed MUST be emitted').toBeDefined();
|
|
125
|
+
expect(capBreach, req('openwop.it.cap-breach.cap-breached-precedes-run-failed-in-the-event-sequence-breach-detected-before-ov', 'observability.md §event ordering', 'cap.breached MUST be emitted')).toBeDefined();
|
|
126
|
+
expect(runFailed, req('openwop.it.cap-breach.cap-breached-precedes-run-failed-in-the-event-sequence-breach-detected-before-ov', 'observability.md §event ordering', 'run.failed MUST be emitted')).toBeDefined();
|
|
126
127
|
|
|
127
|
-
expect(capBreach!.sequence,
|
|
128
|
+
expect(capBreach!.sequence, req('openwop.it.cap-breach.cap-breached-precedes-run-failed-in-the-event-sequence-breach-detected-before-ov',
|
|
128
129
|
'observability.md §event ordering',
|
|
129
130
|
'cap.breached MUST precede run.failed in sequence (breach detected BEFORE over-limit node fires)',
|
|
130
131
|
)).toBeLessThan(runFailed!.sequence);
|
|
@@ -137,11 +138,11 @@ describe.skipIf(SKIP_NO_FIXTURE)('cap-breach: conformance-cap-breach fixture fai
|
|
|
137
138
|
// short) — those would emit fewer than `RECURSION_LIMIT` started
|
|
138
139
|
// events while still satisfying the invariant.
|
|
139
140
|
const nodeStarted = events.filter((e) => e.type === 'node.started');
|
|
140
|
-
expect(nodeStarted.length,
|
|
141
|
+
expect(nodeStarted.length, req('openwop.it.cap-breach.cap-breached-precedes-run-failed-in-the-event-sequence-breach-detected-before-ov',
|
|
141
142
|
'run-options.md §recursionLimit',
|
|
142
143
|
'at most `limit` node.started events MUST be emitted; the over-limit node MUST NOT receive node.started',
|
|
143
144
|
)).toBeLessThanOrEqual(RECURSION_LIMIT);
|
|
144
|
-
expect(nodeStarted.length,
|
|
145
|
+
expect(nodeStarted.length, req('openwop.it.cap-breach.cap-breached-precedes-run-failed-in-the-event-sequence-breach-detected-before-ov',
|
|
145
146
|
'run-options.md §recursionLimit',
|
|
146
147
|
'at least one node MUST start before the breach (otherwise the workflow never executed)',
|
|
147
148
|
)).toBeGreaterThan(0);
|
|
@@ -45,6 +45,8 @@ import { readFileSync } from 'node:fs';
|
|
|
45
45
|
import { join } from 'node:path';
|
|
46
46
|
import { driver } from '../lib/driver.js';
|
|
47
47
|
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
48
|
+
import { req } from '../lib/requirement-ids.js';
|
|
49
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
48
50
|
|
|
49
51
|
type Arm = 'root-plain' | 'root-dotted' | 'wrapper-plain' | 'wrapper-dotted';
|
|
50
52
|
|
|
@@ -84,11 +86,11 @@ describe('capability-arm-witness: which resolver arm the host lands on (RFC 0144
|
|
|
84
86
|
const res = await driver.get('/.well-known/openwop');
|
|
85
87
|
// No discovery document ⇒ inapplicable. Other scenarios own that failure; this leg is
|
|
86
88
|
// about WHERE families sit, not whether the host has any.
|
|
87
|
-
if (res.status !== 200 || res.json === undefined || res.json === null) return;
|
|
89
|
+
if (res.status !== 200 || res.json === undefined || res.json === null) return softSkip('blocked', 'precondition not met — `res.status !== 200 || res.json === undefined || res.json === null` returned early (No discovery document ⇒ inapplicable. Other scenarios own that failure; this leg is about WHERE families sit, …');
|
|
88
90
|
|
|
89
91
|
const doc = res.json as Record<string, unknown>;
|
|
90
92
|
const placements = classify(doc, declaredFamilies());
|
|
91
|
-
if (placements.length === 0) return; // advertises no declared family — inapplicable
|
|
93
|
+
if (placements.length === 0) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `placements.length === 0` returned early (advertises no declared family — inapplicable)'); // advertises no declared family — inapplicable
|
|
92
94
|
|
|
93
95
|
const canonical = placements.filter((p) => p.arms.includes('root-plain'));
|
|
94
96
|
const rootDotted = placements.filter(
|
|
@@ -110,7 +112,7 @@ describe('capability-arm-witness: which resolver arm the host lands on (RFC 0144
|
|
|
110
112
|
// ASSERT — the existing MUST, not a new one.
|
|
111
113
|
expect(
|
|
112
114
|
wrapperOnly.map((p) => p.family),
|
|
113
|
-
|
|
115
|
+
req('openwop.it.capability-arm-witness.no-capability-family-is-reachable-only-under-the-deprecated-capabilities-wrapper',
|
|
114
116
|
'capabilities.md §"Document-root layout (normative — RFC 0073)"',
|
|
115
117
|
'every family MUST appear at the document root; a host that serves families EXCLUSIVELY under the deprecated `capabilities` wrapper is non-conformant and is graded as such. The suite\'s helpers fall back to the wrapper, so without this leg such a host reads exactly like a migrated one — which is RFC 0144 G1',
|
|
116
118
|
),
|
|
@@ -34,7 +34,8 @@ import {
|
|
|
34
34
|
type BundleV2Like,
|
|
35
35
|
type BundleV2Requirement,
|
|
36
36
|
} from '../lib/certification-bundle-verify.js';
|
|
37
|
-
import { UNCLASSIFIED_RETURN_DETAIL } from '../lib/soft-skip.js';
|
|
37
|
+
import { UNCLASSIFIED_RETURN_DETAIL, softSkip } from '../lib/soft-skip.js';
|
|
38
|
+
import { req } from '../lib/requirement-ids.js';
|
|
38
39
|
|
|
39
40
|
export const HOST_CALLBACK_NOT_REQUIRED = 'server-free: verifies bundle documents built in-process; no host is contacted';
|
|
40
41
|
|
|
@@ -103,7 +104,7 @@ describe('RFC 0148 §A/§C — certification-bundle-non-vacuous (consumer re-der
|
|
|
103
104
|
const b = bundle(witnessedRows());
|
|
104
105
|
expect(schemaValid(b), JSON.stringify(schemaValid.errors)).toBe(true);
|
|
105
106
|
const v = verifyBundleV2(b);
|
|
106
|
-
expect(v.rejections).toEqual([]);
|
|
107
|
+
expect(v.rejections, req('openwop.it.certification-bundle-non-vacuous.valid-v2-every-required-row-witnessed-evidence-valid-and-certified-and-the-vecto', 'RFC 0148 §A/§C', 'valid-v2: every required row witnessed → evidence valid AND certified (and the vector is schema-valid)')).toEqual([]);
|
|
107
108
|
expect(v.evidenceValid).toBe(true);
|
|
108
109
|
expect(v.certified).toBe(true);
|
|
109
110
|
const p = v.profiles.find((x) => x.profile === PROFILE);
|
|
@@ -115,7 +116,7 @@ describe('RFC 0148 §A/§C — certification-bundle-non-vacuous (consumer re-der
|
|
|
115
116
|
const rows = witnessedRows();
|
|
116
117
|
rows[0] = { ...(rows[0] as BundleV2Requirement), assertionCount: 0 };
|
|
117
118
|
const v = verifyBundleV2(bundle(rows));
|
|
118
|
-
expect(v.evidenceValid).toBe(false);
|
|
119
|
+
expect(v.evidenceValid, req('openwop.it.certification-bundle-non-vacuous.early-return-a-required-row-that-is-executed-pass-with-assertioncount-0-rejects', 'RFC 0148 §A/§C', 'early-return: a required row that is executed-pass with assertionCount 0 REJECTS the evidence')).toBe(false);
|
|
119
120
|
expect(v.certified).toBe(false);
|
|
120
121
|
const p = v.profiles[0];
|
|
121
122
|
expect(p?.rejections.map((r) => r.kind)).toEqual(['vacuous-pass']);
|
|
@@ -127,7 +128,7 @@ describe('RFC 0148 §A/§C — certification-bundle-non-vacuous (consumer re-der
|
|
|
127
128
|
const { assertionCount: _drop, ...noCount } = rows[1] as BundleV2Requirement & { assertionCount: number };
|
|
128
129
|
rows[1] = noCount;
|
|
129
130
|
const v = verifyBundleV2(bundle(rows));
|
|
130
|
-
expect(v.evidenceValid).toBe(false);
|
|
131
|
+
expect(v.evidenceValid, req('openwop.it.certification-bundle-non-vacuous.early-return-no-count-at-all-an-executed-pass-with-no-assertioncount-is-unwitnes', 'RFC 0148 §A/§C', 'early-return (no count at all): an executed-pass with NO assertionCount is unwitnessed and REJECTS')).toBe(false);
|
|
131
132
|
expect(v.profiles[0]?.rejections[0]?.kind).toBe('vacuous-pass');
|
|
132
133
|
});
|
|
133
134
|
|
|
@@ -135,7 +136,7 @@ describe('RFC 0148 §A/§C — certification-bundle-non-vacuous (consumer re-der
|
|
|
135
136
|
const rows = witnessedRows();
|
|
136
137
|
rows[0] = { requirementId: (rows[0] as BundleV2Requirement).requirementId, scenarioId: (rows[0] as BundleV2Requirement).scenarioId, disposition: 'blocked', detail: UNCLASSIFIED_RETURN_DETAIL, assertionCount: 0 };
|
|
137
138
|
const v = verifyBundleV2(bundle(rows));
|
|
138
|
-
expect(v.evidenceValid).toBe(false);
|
|
139
|
+
expect(v.evidenceValid, req('openwop.it.certification-bundle-non-vacuous.the-emitter-s-a-resolution-of-a-silent-zero-assertion-file-blocked-marker-detail', 'RFC 0148 §A/§C', 'the emitter\'s §A resolution of a silent zero-assertion file (blocked + marker detail) on a required row REJECTS as unwitnessed')).toBe(false);
|
|
139
140
|
expect(v.profiles[0]?.rejections.map((r) => r.kind)).toEqual(['unwitnessed-requirement']);
|
|
140
141
|
});
|
|
141
142
|
|
|
@@ -143,7 +144,7 @@ describe('RFC 0148 §A/§C — certification-bundle-non-vacuous (consumer re-der
|
|
|
143
144
|
const rows = witnessedRows().slice(1); // drop the first floor row
|
|
144
145
|
const dropped = requirementIdForScenario(floor().files[0] as string);
|
|
145
146
|
const v = verifyBundleV2(bundle(rows));
|
|
146
|
-
expect(v.evidenceValid).toBe(false);
|
|
147
|
+
expect(v.evidenceValid, req('openwop.it.certification-bundle-non-vacuous.missing-floor-a-required-requirement-with-no-row-at-all-rejects-unwitnessed-neve', 'RFC 0148 §A/§C', 'missing-floor: a required requirement with no row at all REJECTS (unwitnessed), never "not certified"')).toBe(false);
|
|
147
148
|
const rej = v.profiles[0]?.rejections ?? [];
|
|
148
149
|
expect(rej.map((r) => r.kind)).toEqual(['unwitnessed-requirement']);
|
|
149
150
|
expect(rej[0]?.requirementId).toBe(dropped);
|
|
@@ -153,10 +154,10 @@ describe('RFC 0148 §A/§C — certification-bundle-non-vacuous (consumer re-der
|
|
|
153
154
|
|
|
154
155
|
it('missing prefix witness: no `interrupt-*` scenario row and no summary row REJECTS the prefix requirement', () => {
|
|
155
156
|
const { prefixes } = floor();
|
|
156
|
-
if (prefixes.length === 0) return; // floor without a prefix requirement — nothing to assert here
|
|
157
|
+
if (prefixes.length === 0) return softSkip('blocked', 'precondition not met — `prefixes.length === 0` returned early (floor without a prefix requirement — nothing to assert here) (seam, prior step, or fixture unavailable)'); // floor without a prefix requirement — nothing to assert here
|
|
157
158
|
const rows = witnessedRows().filter((r) => !prefixes.some((p) => r.scenarioId.startsWith(p)));
|
|
158
159
|
const v = verifyBundleV2(bundle(rows));
|
|
159
|
-
expect(v.evidenceValid).toBe(false);
|
|
160
|
+
expect(v.evidenceValid, req('openwop.it.certification-bundle-non-vacuous.missing-prefix-witness-no-interrupt-scenario-row-and-no-summary-row-rejects-the', 'RFC 0148 §A/§C', 'missing prefix witness: no `interrupt-*` scenario row and no summary row REJECTS the prefix requirement')).toBe(false);
|
|
160
161
|
expect(v.profiles[0]?.rejections.some((r) => r.kind === 'unwitnessed-requirement' && r.requirementId === requirementIdForPrefix(prefixes[0] as string))).toBe(true);
|
|
161
162
|
});
|
|
162
163
|
|
|
@@ -164,7 +165,7 @@ describe('RFC 0148 §A/§C — certification-bundle-non-vacuous (consumer re-der
|
|
|
164
165
|
const rows = witnessedRows();
|
|
165
166
|
rows.push({ ...(rows[0] as BundleV2Requirement) });
|
|
166
167
|
const v = verifyBundleV2(bundle(rows));
|
|
167
|
-
expect(v.evidenceValid).toBe(false);
|
|
168
|
+
expect(v.evidenceValid, req('openwop.it.certification-bundle-non-vacuous.duplicate-requirement-two-rows-for-one-requirement-rejects-exactly-one-dispositi', 'RFC 0148 §A/§C', 'duplicate-requirement: two rows for one requirement REJECTS (exactly one disposition per requirement)')).toBe(false);
|
|
168
169
|
expect(v.rejections.map((r) => r.kind)).toContain('duplicate-requirement');
|
|
169
170
|
});
|
|
170
171
|
|
|
@@ -173,7 +174,7 @@ describe('RFC 0148 §A/§C — certification-bundle-non-vacuous (consumer re-der
|
|
|
173
174
|
const b = bundle(rows);
|
|
174
175
|
const tampered = { ...b, results: { ...b.results, totals: { ...b.results.totals, blocked: 0, executedPass: b.results.totals.executedPass + 1 } } };
|
|
175
176
|
const v = verifyBundleV2(tampered);
|
|
176
|
-
expect(v.evidenceValid).toBe(false);
|
|
177
|
+
expect(v.evidenceValid, req('openwop.it.certification-bundle-non-vacuous.tampered-witness-totals-that-disagree-with-the-rows-reject-a-hand-edited-count-i', 'RFC 0148 §A/§C', 'tampered-witness: totals that disagree with the rows REJECT — a hand-edited count is not a witness')).toBe(false);
|
|
177
178
|
expect(v.rejections.map((r) => r.kind)).toEqual(['totals-mismatch']);
|
|
178
179
|
});
|
|
179
180
|
|
|
@@ -181,7 +182,7 @@ describe('RFC 0148 §A/§C — certification-bundle-non-vacuous (consumer re-der
|
|
|
181
182
|
const rows = witnessedRows();
|
|
182
183
|
rows[0] = { ...(rows[0] as BundleV2Requirement), disposition: 'passed' };
|
|
183
184
|
const v = verifyBundleV2(bundle(rows));
|
|
184
|
-
expect(v.rejections.some((r) => r.kind === 'unknown-disposition')).toBe(true);
|
|
185
|
+
expect(v.rejections.some((r) => r.kind === 'unknown-disposition'), req('openwop.it.certification-bundle-non-vacuous.tampered-witness-an-invented-disposition-name-rejects', 'RFC 0148 §A/§C', 'tampered-witness: an invented disposition name REJECTS')).toBe(true);
|
|
185
186
|
expect(v.evidenceValid).toBe(false);
|
|
186
187
|
});
|
|
187
188
|
|
|
@@ -189,7 +190,7 @@ describe('RFC 0148 §A/§C — certification-bundle-non-vacuous (consumer re-der
|
|
|
189
190
|
const rows = witnessedRows();
|
|
190
191
|
rows[0] = { requirementId: (rows[0] as BundleV2Requirement).requirementId, scenarioId: (rows[0] as BundleV2Requirement).scenarioId, disposition: 'blocked' };
|
|
191
192
|
const v = verifyBundleV2(bundle(rows));
|
|
192
|
-
expect(v.rejections.map((r) => r.kind)).toContain('reason-missing');
|
|
193
|
+
expect(v.rejections.map((r) => r.kind), req('openwop.it.certification-bundle-non-vacuous.a-non-pass-without-a-reason-rejects-rfc-0148-a-anything-other-than-executed-pass', 'RFC 0148 §A', 'a non-pass without a reason REJECTS (RFC 0148 §A: anything other than executed-pass says why)')).toContain('reason-missing');
|
|
193
194
|
});
|
|
194
195
|
|
|
195
196
|
it('blocked-floor: an HONEST blocked row on a required requirement is VALID evidence that does NOT certify', () => {
|
|
@@ -197,7 +198,7 @@ describe('RFC 0148 §A/§C — certification-bundle-non-vacuous (consumer re-der
|
|
|
197
198
|
rows[0] = { ...(rows[0] as BundleV2Requirement), disposition: 'blocked', detail: 'seam unreachable from this runner (host-callback)' };
|
|
198
199
|
delete (rows[0] as { assertionCount?: number }).assertionCount;
|
|
199
200
|
const v = verifyBundleV2(bundle(rows));
|
|
200
|
-
expect(v.rejections).toEqual([]);
|
|
201
|
+
expect(v.rejections, req('openwop.it.certification-bundle-non-vacuous.blocked-floor-an-honest-blocked-row-on-a-required-requirement-is-valid-evidence', 'RFC 0148 §A/§C', 'blocked-floor: an HONEST blocked row on a required requirement is VALID evidence that does NOT certify')).toEqual([]);
|
|
201
202
|
expect(v.evidenceValid).toBe(true);
|
|
202
203
|
expect(v.certified).toBe(false);
|
|
203
204
|
expect(v.profiles[0]?.notCertifiable).toEqual([(rows[0] as BundleV2Requirement).requirementId]);
|
|
@@ -207,7 +208,7 @@ describe('RFC 0148 §A/§C — certification-bundle-non-vacuous (consumer re-der
|
|
|
207
208
|
const rows = witnessedRows();
|
|
208
209
|
rows[2] = { ...(rows[2] as BundleV2Requirement), disposition: 'executed-fail', detail: 'assertion failed against target', assertionCount: 4 };
|
|
209
210
|
const v = verifyBundleV2(bundle(rows));
|
|
210
|
-
expect(v.evidenceValid).toBe(true);
|
|
211
|
+
expect(v.evidenceValid, req('openwop.it.certification-bundle-non-vacuous.executed-fail-on-a-required-row-valid-evidence-not-certified', 'RFC 0148 §A/§C', 'executed-fail on a required row: valid evidence, not certified')).toBe(true);
|
|
211
212
|
expect(v.certified).toBe(false);
|
|
212
213
|
});
|
|
213
214
|
|
|
@@ -216,14 +217,14 @@ describe('RFC 0148 §A/§C — certification-bundle-non-vacuous (consumer re-der
|
|
|
216
217
|
rows[0] = { requirementId: (rows[0] as BundleV2Requirement).requirementId, scenarioId: (rows[0] as BundleV2Requirement).scenarioId, disposition: 'inapplicable', detail: 'profile not advertised in the captured discovery set', assertionCount: 0 };
|
|
217
218
|
rows[1] = { requirementId: (rows[1] as BundleV2Requirement).requirementId, scenarioId: (rows[1] as BundleV2Requirement).scenarioId, disposition: 'skipped', detail: 'operator opted the profile out (OPENWOP_OPTED_OUT_PROFILES)' };
|
|
218
219
|
const v = verifyBundleV2(bundle(rows));
|
|
219
|
-
expect(v.evidenceValid).toBe(true);
|
|
220
|
+
expect(v.evidenceValid, req('openwop.it.certification-bundle-non-vacuous.inapplicable-skipped-with-a-reason-on-a-required-row-certifies-rfc-0148-a-certif', 'RFC 0148 §A', 'inapplicable / skipped with a reason on a required row certifies (RFC 0148 §A CERTIFIABLE set)')).toBe(true);
|
|
220
221
|
expect(v.certified).toBe(true);
|
|
221
222
|
});
|
|
222
223
|
|
|
223
224
|
it('a claim the discovery document does not derive is not certified even with perfect rows (RFC 0089 §B(1))', () => {
|
|
224
225
|
const b = bundle(witnessedRows(), { discovery: { document: { protocolVersion: '1.0' } } });
|
|
225
226
|
const v = verifyBundleV2(b);
|
|
226
|
-
expect(v.evidenceValid).toBe(true);
|
|
227
|
+
expect(v.evidenceValid, req('openwop.it.certification-bundle-non-vacuous.a-claim-the-discovery-document-does-not-derive-is-not-certified-even-with-perfec', 'RFC 0089 §B(1', 'a claim the discovery document does not derive is not certified even with perfect rows (RFC 0089 §B(1))')).toBe(true);
|
|
227
228
|
expect(v.profiles[0]?.derivable).toBe(false);
|
|
228
229
|
expect(v.certified).toBe(false);
|
|
229
230
|
});
|
|
@@ -231,19 +232,19 @@ describe('RFC 0148 §A/§C — certification-bundle-non-vacuous (consumer re-der
|
|
|
231
232
|
it('a claimed profile with NO defined floor is unprovable — floorUnspecified, never certified (G6)', () => {
|
|
232
233
|
const b = bundle(witnessedRows(), { claimedProfiles: ['openwop-not-a-profile'] });
|
|
233
234
|
const v = verifyBundleV2(b);
|
|
234
|
-
expect(v.profiles[0]?.floorUnspecified).toBe(true);
|
|
235
|
+
expect(v.profiles[0]?.floorUnspecified, req('openwop.it.certification-bundle-non-vacuous.a-claimed-profile-with-no-defined-floor-is-unprovable-floorunspecified-never-cer', 'RFC 0148 §A/§C', 'a claimed profile with NO defined floor is unprovable — floorUnspecified, never certified (G6)')).toBe(true);
|
|
235
236
|
expect(v.certified).toBe(false);
|
|
236
237
|
});
|
|
237
238
|
|
|
238
239
|
it('a discovery-only profile certifies on derivation alone (RFC 0155 §A: openwop-discovery-core)', () => {
|
|
239
240
|
const b = bundle(witnessedRows(), { claimedProfiles: ['openwop-discovery-core'] });
|
|
240
241
|
const v = verifyBundleV2(b);
|
|
241
|
-
expect(v.certified).toBe(true);
|
|
242
|
+
expect(v.certified, req('openwop.it.certification-bundle-non-vacuous.a-discovery-only-profile-certifies-on-derivation-alone-rfc-0155-a-openwop-discov', 'RFC 0155 §A', 'a discovery-only profile certifies on derivation alone (RFC 0155 §A: openwop-discovery-core)')).toBe(true);
|
|
242
243
|
});
|
|
243
244
|
|
|
244
245
|
it('a v1 document handed to the v2 verifier is rejected as not-v2 rather than silently read', () => {
|
|
245
246
|
const b = bundle(witnessedRows(), { bundleVersion: '1' });
|
|
246
247
|
const v = verifyBundleV2(b);
|
|
247
|
-
expect(v.rejections.map((r) => r.kind)).toContain('not-v2');
|
|
248
|
+
expect(v.rejections.map((r) => r.kind), req('openwop.it.certification-bundle-non-vacuous.a-v1-document-handed-to-the-v2-verifier-is-rejected-as-not-v2-rather-than-silent', 'RFC 0148 §A/§C', 'a v1 document handed to the v2 verifier is rejected as not-v2 rather than silently read')).toContain('not-v2');
|
|
248
249
|
});
|
|
249
250
|
});
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
type BundleV2Like,
|
|
43
43
|
type BundleV2Requirement,
|
|
44
44
|
} from '../lib/certification-bundle-verify.js';
|
|
45
|
+
import { req } from '../lib/requirement-ids.js';
|
|
45
46
|
|
|
46
47
|
export const HOST_CALLBACK_NOT_REQUIRED = 'server-free: scrubs and verifies bundle documents built in-process; no host is contacted';
|
|
47
48
|
|
|
@@ -89,7 +90,7 @@ describe('RFC 0148 §C — certification-bundle-redaction: secret canaries never
|
|
|
89
90
|
rs[0] = { ...(rs[0] as BundleV2Requirement), disposition: 'executed-fail', assertionCount: 3, detail: `401 body: {"error":"bad key ${API_KEY}"} (canary ${CONFORMANCE_SECRET_CANARY} echoed)` };
|
|
90
91
|
const b = bundle(rs);
|
|
91
92
|
const { value, redactedAt } = scrubEvidence(b, [API_KEY, CONFORMANCE_SECRET_CANARY]);
|
|
92
|
-
expect(findLiteral(value, API_KEY)).toEqual([]);
|
|
93
|
+
expect(findLiteral(value, API_KEY), req('openwop.it.certification-bundle-redaction.scrubevidence-replaces-every-occurrence-of-every-secret-in-nested-values-and-rep', 'RFC 0148 §C', 'scrubEvidence replaces every occurrence of every secret in nested values and reports where')).toEqual([]);
|
|
93
94
|
expect(findLiteral(value, CONFORMANCE_SECRET_CANARY)).toEqual([]);
|
|
94
95
|
expect(redactedAt).toEqual(['$.results.requirements[0].detail']);
|
|
95
96
|
const detail = value.results.requirements[0]?.detail ?? '';
|
|
@@ -103,7 +104,7 @@ describe('RFC 0148 §C — certification-bundle-redaction: secret canaries never
|
|
|
103
104
|
it('scrubs the captured discovery document and host self-description too — evidence is the WHOLE document', () => {
|
|
104
105
|
const b = bundle(rows(), { name: 'h', version: '1', vendor: `acme (token ${API_KEY})` }, { ...DISCOVERY, ['x-host-debug']: { echo: CONFORMANCE_SECRET_CANARY } });
|
|
105
106
|
const { value, redactedAt } = scrubEvidence(b, [API_KEY, CONFORMANCE_SECRET_CANARY]);
|
|
106
|
-
expect(findLiteral(value, API_KEY)).toEqual([]);
|
|
107
|
+
expect(findLiteral(value, API_KEY), req('openwop.it.certification-bundle-redaction.scrubs-the-captured-discovery-document-and-host-self-description-too-evidence-is', 'RFC 0148 §C', 'scrubs the captured discovery document and host self-description too — evidence is the WHOLE document')).toEqual([]);
|
|
107
108
|
expect(findLiteral(value, CONFORMANCE_SECRET_CANARY)).toEqual([]);
|
|
108
109
|
expect([...redactedAt].sort()).toEqual(['$.discovery.document.x-host-debug.echo', '$.host.vendor']);
|
|
109
110
|
});
|
|
@@ -111,7 +112,7 @@ describe('RFC 0148 §C — certification-bundle-redaction: secret canaries never
|
|
|
111
112
|
it('scrubs object KEYS as well as values — a secret used as a map key is still a secret', () => {
|
|
112
113
|
const doc = { ...DISCOVERY, ['x-host-keys']: { [API_KEY]: 'tenant-a' } };
|
|
113
114
|
const { value } = scrubEvidence(bundle(rows(), { name: 'h', version: '1' }, doc), [API_KEY]);
|
|
114
|
-
expect(findLiteral(value, API_KEY)).toEqual([]);
|
|
115
|
+
expect(findLiteral(value, API_KEY), req('openwop.it.certification-bundle-redaction.scrubs-object-keys-as-well-as-values-a-secret-used-as-a-map-key-is-still-a-secre', 'RFC 0148 §C', 'scrubs object KEYS as well as values — a secret used as a map key is still a secret')).toEqual([]);
|
|
115
116
|
const keys = Object.keys((value.discovery.document as Record<string, Record<string, unknown>>)['x-host-keys'] ?? {});
|
|
116
117
|
expect(keys).toEqual([redactionMarker(API_KEY)]);
|
|
117
118
|
});
|
|
@@ -119,7 +120,7 @@ describe('RFC 0148 §C — certification-bundle-redaction: secret canaries never
|
|
|
119
120
|
it('empty and whitespace-only secrets are ignored (scrubbing "" would blank every string)', () => {
|
|
120
121
|
const b = bundle(rows());
|
|
121
122
|
const { value, redactedAt } = scrubEvidence(b, ['', ' ']);
|
|
122
|
-
expect(value).toEqual(b);
|
|
123
|
+
expect(value, req('openwop.it.certification-bundle-redaction.empty-and-whitespace-only-secrets-are-ignored-scrubbing-would-blank-every-string', 'RFC 0148 §C', 'empty and whitespace-only secrets are ignored (scrubbing "" would blank every string)')).toEqual(b);
|
|
123
124
|
expect(redactedAt).toEqual([]);
|
|
124
125
|
});
|
|
125
126
|
|
|
@@ -127,12 +128,12 @@ describe('RFC 0148 §C — certification-bundle-redaction: secret canaries never
|
|
|
127
128
|
const short = 'abc123';
|
|
128
129
|
const long = 'abc123-longer-secret';
|
|
129
130
|
const { value } = scrubEvidence({ detail: `x ${long} y ${short} z` }, [short, long]);
|
|
130
|
-
expect(value.detail).toBe(`x ${redactionMarker(long)} y ${redactionMarker(short)} z`);
|
|
131
|
+
expect(value.detail, req('openwop.it.certification-bundle-redaction.when-one-secret-is-a-prefix-of-another-the-longer-one-is-scrubbed-whole-no-parti', 'RFC 0148 §C', 'when one secret is a prefix of another the longer one is scrubbed whole (no partial marker inside a marker)')).toBe(`x ${redactionMarker(long)} y ${redactionMarker(short)} z`);
|
|
131
132
|
});
|
|
132
133
|
|
|
133
134
|
it('the redaction marker is stable, non-reversible, and does not contain the secret', () => {
|
|
134
135
|
const m = redactionMarker(API_KEY);
|
|
135
|
-
expect(m).toBe(redactionMarker(API_KEY));
|
|
136
|
+
expect(m, req('openwop.it.certification-bundle-redaction.the-redaction-marker-is-stable-non-reversible-and-does-not-contain-the-secret', 'RFC 0148 §C', 'the redaction marker is stable, non-reversible, and does not contain the secret')).toBe(redactionMarker(API_KEY));
|
|
136
137
|
expect(m).not.toContain(API_KEY);
|
|
137
138
|
expect(m).toMatch(/^«redacted:[0-9a-f]{12}»$/);
|
|
138
139
|
expect(redactionMarker('other')).not.toBe(m);
|
|
@@ -150,7 +151,7 @@ describe('RFC 0148 §C — certification-bundle-redaction: secret canaries never
|
|
|
150
151
|
SOME_OTHER_TOKEN: 'not-ours',
|
|
151
152
|
} as NodeJS.ProcessEnv;
|
|
152
153
|
const secrets = evidenceSecretsFromEnv(env, [API_KEY, undefined, '']);
|
|
153
|
-
expect(secrets.sort()).toEqual([API_KEY, 'k1', 'p1', 's1', 't1', CONFORMANCE_SECRET_CANARY].sort());
|
|
154
|
+
expect(secrets.sort(), req('openwop.it.certification-bundle-redaction.evidencesecretsfromenv-selects-openwop-key-token-secret-password-variables-the-h', 'RFC 0148 §C', 'evidenceSecretsFromEnv selects OPENWOP_* key/token/secret/password variables, the handed credential, and ALWAYS the canary')).toEqual([API_KEY, 'k1', 'p1', 's1', 't1', CONFORMANCE_SECRET_CANARY].sort());
|
|
154
155
|
});
|
|
155
156
|
|
|
156
157
|
it('verifyBundleV2 REJECTS a bundle carrying the conformance canary anywhere, and accepts the scrubbed twin', () => {
|
|
@@ -158,7 +159,7 @@ describe('RFC 0148 §C — certification-bundle-redaction: secret canaries never
|
|
|
158
159
|
rs[1] = { ...(rs[1] as BundleV2Requirement), detail: `resolved ${CONFORMANCE_SECRET_CANARY} in plain text` };
|
|
159
160
|
const leaked = bundle(rs);
|
|
160
161
|
const v = verifyBundleV2(leaked);
|
|
161
|
-
expect(v.evidenceValid).toBe(false);
|
|
162
|
+
expect(v.evidenceValid, req('openwop.it.certification-bundle-redaction.verifybundlev2-rejects-a-bundle-carrying-the-conformance-canary-anywhere-and-acc', 'RFC 0148 §C', 'verifyBundleV2 REJECTS a bundle carrying the conformance canary anywhere, and accepts the scrubbed twin')).toBe(false);
|
|
162
163
|
expect(v.rejections.map((r) => r.kind)).toEqual(['secret-canary']);
|
|
163
164
|
expect(v.rejections[0]?.detail).toContain('$.results.requirements[1].detail');
|
|
164
165
|
|
|
@@ -171,12 +172,12 @@ describe('RFC 0148 §C — certification-bundle-redaction: secret canaries never
|
|
|
171
172
|
it('the canary in a discovery-document KEY is still a rejection (keys are evidence)', () => {
|
|
172
173
|
const doc = { ...DISCOVERY, [`x-host-${CONFORMANCE_SECRET_CANARY}`]: true };
|
|
173
174
|
const v = verifyBundleV2(bundle(rows(), { name: 'h', version: '1' }, doc));
|
|
174
|
-
expect(v.rejections.some((r) => r.kind === 'secret-canary')).toBe(true);
|
|
175
|
+
expect(v.rejections.some((r) => r.kind === 'secret-canary'), req('openwop.it.certification-bundle-redaction.the-canary-in-a-discovery-document-key-is-still-a-rejection-keys-are-evidence', 'RFC 0148 §C', 'the canary in a discovery-document KEY is still a rejection (keys are evidence)')).toBe(true);
|
|
175
176
|
});
|
|
176
177
|
|
|
177
178
|
it('the target-configuration digest is a digest: the API key is not a substring of any string in the document', () => {
|
|
178
179
|
const b = bundle(rows());
|
|
179
|
-
expect((b as unknown as { targetConfigurationSha256: string }).targetConfigurationSha256).toMatch(/^[0-9a-f]{64}$/);
|
|
180
|
+
expect((b as unknown as { targetConfigurationSha256: string }).targetConfigurationSha256, req('openwop.it.certification-bundle-redaction.the-target-configuration-digest-is-a-digest-the-api-key-is-not-a-substring-of-an', 'RFC 0148 §C', 'the target-configuration digest is a digest: the API key is not a substring of any string in the document')).toMatch(/^[0-9a-f]{64}$/);
|
|
180
181
|
expect(findLiteral(b, API_KEY)).toEqual([]);
|
|
181
182
|
});
|
|
182
183
|
});
|
|
@@ -30,6 +30,7 @@ import { readFileSync } from 'node:fs';
|
|
|
30
30
|
import { join } from 'node:path';
|
|
31
31
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
32
32
|
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
33
|
+
import { req } from '../lib/requirement-ids.js';
|
|
33
34
|
|
|
34
35
|
const HEX = 'a'.repeat(64);
|
|
35
36
|
|
|
@@ -74,7 +75,7 @@ describe('RFC 0148 §C — certification bundle v2', () => {
|
|
|
74
75
|
const validate = validator();
|
|
75
76
|
|
|
76
77
|
it('a well-formed v2 bundle validates', () => {
|
|
77
|
-
expect(validate(bundle()), JSON.stringify(validate.errors)).toBe(true);
|
|
78
|
+
expect(validate(bundle()), req('openwop.it.certification-bundle-v2.a-well-formed-v2-bundle-validates', 'RFC 0148 §C', JSON.stringify(validate.errors))).toBe(true);
|
|
78
79
|
});
|
|
79
80
|
|
|
80
81
|
it('all five totals are required, including blocked', () => {
|
|
@@ -86,8 +87,8 @@ describe('RFC 0148 §C — certification bundle v2', () => {
|
|
|
86
87
|
delete totals[missing];
|
|
87
88
|
expect(
|
|
88
89
|
validate(bundle({ results: { totals, requirements: bundle().results.requirements } })),
|
|
89
|
-
`RFC 0148 §C: \`${missing}\` MUST be present — an omitted total reads as zero without ` +
|
|
90
|
-
'anyone having asserted it',
|
|
90
|
+
req('openwop.it.certification-bundle-v2.all-five-totals-are-required-including-blocked', 'RFC 0148 §C', `RFC 0148 §C: \`${missing}\` MUST be present — an omitted total reads as zero without ` +
|
|
91
|
+
'anyone having asserted it'),
|
|
91
92
|
).toBe(false);
|
|
92
93
|
}
|
|
93
94
|
});
|
|
@@ -95,8 +96,8 @@ describe('RFC 0148 §C — certification bundle v2', () => {
|
|
|
95
96
|
it('the requirement list cannot be empty', () => {
|
|
96
97
|
expect(
|
|
97
98
|
validate(bundle({ results: { totals: bundle().results.totals, requirements: [] } })),
|
|
98
|
-
'RFC 0148 §C: a bundle with no requirement rows records no execution. An empty evidence set ' +
|
|
99
|
-
'reading as proof is the defect this section exists to close.',
|
|
99
|
+
req('openwop.it.certification-bundle-v2.the-requirement-list-cannot-be-empty', 'RFC 0148 §C', 'RFC 0148 §C: a bundle with no requirement rows records no execution. An empty evidence set ' +
|
|
100
|
+
'reading as proof is the defect this section exists to close.'),
|
|
100
101
|
).toBe(false);
|
|
101
102
|
});
|
|
102
103
|
|
|
@@ -107,7 +108,7 @@ describe('RFC 0148 §C — certification bundle v2', () => {
|
|
|
107
108
|
totals: bundle().results.totals,
|
|
108
109
|
requirements: [{ requirementId: 'x', scenarioId: 'y', disposition: 'probably-fine' }],
|
|
109
110
|
},
|
|
110
|
-
})),
|
|
111
|
+
})), req('openwop.it.certification-bundle-v2.a-disposition-outside-the-a-vocabulary-is-rejected', 'RFC 0148 §C', 'a disposition outside the §A vocabulary is rejected'),
|
|
111
112
|
).toBe(false);
|
|
112
113
|
});
|
|
113
114
|
|
|
@@ -127,14 +128,14 @@ describe('RFC 0148 §C — certification bundle v2', () => {
|
|
|
127
128
|
const rows = (b.results as { requirements: { disposition: string; assertionCount?: number }[] }).requirements;
|
|
128
129
|
expect(
|
|
129
130
|
rows.some((r) => r.disposition === 'executed-pass' && r.assertionCount === 0),
|
|
130
|
-
'the vacuous shape MUST remain inspectable in the artifact — v1 could not express it at all',
|
|
131
|
+
req('openwop.it.certification-bundle-v2.a-vacuous-pass-is-representable-and-therefore-visible', 'RFC 0148 §C', 'the vacuous shape MUST remain inspectable in the artifact — v1 could not express it at all'),
|
|
131
132
|
).toBe(true);
|
|
132
133
|
});
|
|
133
134
|
|
|
134
135
|
it('claimed profiles use canonical IDs, not deprecated aliases', () => {
|
|
135
136
|
// RFC 0155 §E. A badge substantiated by a name that no longer means what it
|
|
136
137
|
// did is the `openwop-core` ambiguity in bundle form.
|
|
137
|
-
expect(validate(bundle({ claimedProfiles: ['openwop-core-standard'] }))).toBe(true);
|
|
138
|
+
expect(validate(bundle({ claimedProfiles: ['openwop-core-standard'] })), req('openwop.it.certification-bundle-v2.claimed-profiles-use-canonical-ids-not-deprecated-aliases', 'RFC 0148 §C', 'claimed profiles use canonical IDs, not deprecated aliases')).toBe(true);
|
|
138
139
|
expect(validate(bundle({ claimedProfiles: ['Legacy Core'] }))).toBe(false);
|
|
139
140
|
expect(validate(bundle({ claimedProfiles: [] }))).toBe(false);
|
|
140
141
|
});
|
|
@@ -146,12 +147,12 @@ describe('RFC 0148 §C — certification bundle v2', () => {
|
|
|
146
147
|
for (const field of ['scenarioManifestSha256', 'targetConfigurationSha256']) {
|
|
147
148
|
const b = bundle() as Record<string, unknown>;
|
|
148
149
|
delete b[field];
|
|
149
|
-
expect(validate(b), `RFC 0147 §A.4: ${field} MUST be present`).toBe(false);
|
|
150
|
+
expect(validate(b), req('openwop.it.certification-bundle-v2.provenance-digests-are-required-and-hex-shaped', 'RFC 0148 §C', `RFC 0147 §A.4: ${field} MUST be present`)).toBe(false);
|
|
150
151
|
}
|
|
151
152
|
expect(validate(bundle({ scenarioManifestSha256: 'not-a-digest' }))).toBe(false);
|
|
152
153
|
});
|
|
153
154
|
|
|
154
155
|
it('bundleVersion is pinned to 2', () => {
|
|
155
|
-
expect(validate(bundle({ bundleVersion: '1' })), 'a v1 bundle MUST NOT validate as v2').toBe(false);
|
|
156
|
+
expect(validate(bundle({ bundleVersion: '1' })), req('openwop.it.certification-bundle-v2.bundleversion-is-pinned-to-2', 'RFC 0148 §C', 'a v1 bundle MUST NOT validate as v2')).toBe(false);
|
|
156
157
|
});
|
|
157
158
|
});
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
type ChainCompensationPolicy,
|
|
55
55
|
type FragmentNodeCompensation,
|
|
56
56
|
} from '../lib/workflow-chain-expansion.js';
|
|
57
|
+
import { req } from '../lib/requirement-ids.js';
|
|
57
58
|
|
|
58
59
|
function schema(name: string): Record<string, unknown> {
|
|
59
60
|
return JSON.parse(readFileSync(join(SCHEMAS_DIR, name), 'utf8')) as Record<string, unknown>;
|
|
@@ -131,7 +132,7 @@ describe('RFC 0157 — schema: the chain manifest mirrors the compensation shape
|
|
|
131
132
|
$defs: { FragmentNode: { properties: Record<string, unknown> }; WorkflowChain: { properties: Record<string, unknown> } };
|
|
132
133
|
};
|
|
133
134
|
const wd = schema('workflow-definition.schema.json') as { $defs: { WorkflowNode: { properties: Record<string, unknown> } } };
|
|
134
|
-
expect(chainSchema.$defs.FragmentNode.properties['compensation'], 'FragmentNode MUST carry `compensation`').toBeDefined();
|
|
135
|
+
expect(chainSchema.$defs.FragmentNode.properties['compensation'], req('openwop.it.chain-compensation-expansion.fragmentnode-compensation-is-a-byte-mirror-of-workflownode-compensation', 'RFC 0157', 'FragmentNode MUST carry `compensation`')).toBeDefined();
|
|
135
136
|
expect(stripDescription(chainSchema.$defs.FragmentNode.properties['compensation'])).toEqual(
|
|
136
137
|
stripDescription(wd.$defs.WorkflowNode.properties['compensation']),
|
|
137
138
|
);
|
|
@@ -140,14 +141,14 @@ describe('RFC 0157 — schema: the chain manifest mirrors the compensation shape
|
|
|
140
141
|
it('WorkflowChain.compensation is a byte-mirror of compensation-policy.schema.json', () => {
|
|
141
142
|
const chainSchema = schema('workflow-chain-pack-manifest.schema.json') as { $defs: { WorkflowChain: { properties: Record<string, unknown> } } };
|
|
142
143
|
const policy = schema('compensation-policy.schema.json');
|
|
143
|
-
expect(chainSchema.$defs.WorkflowChain.properties['compensation'], 'WorkflowChain MUST carry the policy').toBeDefined();
|
|
144
|
+
expect(chainSchema.$defs.WorkflowChain.properties['compensation'], req('openwop.it.chain-compensation-expansion.workflowchain-compensation-is-a-byte-mirror-of-compensation-policy-schema-json', 'RFC 0157', 'WorkflowChain MUST carry the policy')).toBeDefined();
|
|
144
145
|
expect(stripDescription(chainSchema.$defs.WorkflowChain.properties['compensation'])).toEqual(stripDescription(policy));
|
|
145
146
|
});
|
|
146
147
|
|
|
147
148
|
it('the manifest stays self-contained — no cross-file $ref was introduced', () => {
|
|
148
149
|
const raw = readFileSync(join(SCHEMAS_DIR, 'workflow-chain-pack-manifest.schema.json'), 'utf8');
|
|
149
150
|
const refs = [...raw.matchAll(/"\$ref":\s*"([^"]+)"/g)].map((m) => m[1]);
|
|
150
|
-
for (const r of refs) expect(r.startsWith('#/'), `cross-file $ref ${r} would break fixed-list validators downstream`).toBe(true);
|
|
151
|
+
for (const r of refs) expect(r.startsWith('#/'), req('openwop.it.chain-compensation-expansion.the-manifest-stays-self-contained-no-cross-file-ref-was-introduced', 'RFC 0157', `cross-file $ref ${r} would break fixed-list validators downstream`)).toBe(true);
|
|
151
152
|
});
|
|
152
153
|
|
|
153
154
|
it('a manifest declaring both surfaces validates; a foreign key inside either is rejected', () => {
|
|
@@ -155,13 +156,13 @@ describe('RFC 0157 — schema: the chain manifest mirrors the compensation shape
|
|
|
155
156
|
expect(validate(manifestWith(CHAIN)), JSON.stringify(validate.errors)).toBe(true);
|
|
156
157
|
const badNode = structuredClone(CHAIN) as unknown as { dag: { nodes: Array<Record<string, unknown>> } };
|
|
157
158
|
(badNode.dag.nodes[0]!['compensation'] as Record<string, unknown>)['rollback'] = true;
|
|
158
|
-
expect(validate(manifestWith(badNode)), 'the mirrored node declaration MUST stay closed').toBe(false);
|
|
159
|
+
expect(validate(manifestWith(badNode)), req('openwop.it.chain-compensation-expansion.a-manifest-declaring-both-surfaces-validates-a-foreign-key-inside-either-is-reje', 'RFC 0157', 'the mirrored node declaration MUST stay closed')).toBe(false);
|
|
159
160
|
const badPolicy = structuredClone(CHAIN) as unknown as { compensation: Record<string, unknown> };
|
|
160
161
|
badPolicy.compensation['approvalScope'] = 'none';
|
|
161
|
-
expect(validate(manifestWith(badPolicy)), 'the mirrored policy keeps the escalate-only approval scope').toBe(false);
|
|
162
|
+
expect(validate(manifestWith(badPolicy)), req('openwop.it.chain-compensation-expansion.a-manifest-declaring-both-surfaces-validates-a-foreign-key-inside-either-is-reje', 'RFC 0157', 'the mirrored policy keeps the escalate-only approval scope')).toBe(false);
|
|
162
163
|
const noTriggers = structuredClone(CHAIN) as unknown as { compensation: Record<string, unknown> };
|
|
163
164
|
noTriggers.compensation = {};
|
|
164
|
-
expect(validate(manifestWith(noTriggers)), 'a policy without triggers is not a policy').toBe(false);
|
|
165
|
+
expect(validate(manifestWith(noTriggers)), req('openwop.it.chain-compensation-expansion.a-manifest-declaring-both-surfaces-validates-a-foreign-key-inside-either-is-reje', 'RFC 0157', 'a policy without triggers is not a policy')).toBe(false);
|
|
165
166
|
});
|
|
166
167
|
|
|
167
168
|
it('FragmentNode.irreversibleEffect (RFC 0151 UQ4) is a sibling boolean, mutually exclusive with compensation — in the manifest and in expansion', () => {
|
|
@@ -180,7 +181,7 @@ describe('RFC 0157 — schema: the chain manifest mirrors the compensation shape
|
|
|
180
181
|
// Both on one node: the schema rejects it AND expansion refuses fail-closed before emitting.
|
|
181
182
|
const both = structuredClone(CHAIN) as unknown as { dag: { nodes: Array<Record<string, unknown>> } };
|
|
182
183
|
both.dag.nodes[0]!['irreversibleEffect'] = true; // node 0 (`reserve`) declares a compensation
|
|
183
|
-
expect(validate(manifestWith(both)), 'compensation.md §B: irreversibleEffect: true + compensation is contradictory').toBe(false);
|
|
184
|
+
expect(validate(manifestWith(both)), req('openwop.it.chain-compensation-expansion.fragmentnode-irreversibleeffect-rfc-0151-uq4-is-a-sibling-boolean-mutually-exclu', 'RFC 0151', 'compensation.md §B: irreversibleEffect: true + compensation is contradictory')).toBe(false);
|
|
184
185
|
let thrown: unknown;
|
|
185
186
|
try {
|
|
186
187
|
expandChainWithCompensation(both as unknown as typeof CHAIN, CTX);
|
|
@@ -206,7 +207,7 @@ describe('RFC 0157 — expansion carries the declaration and the policy', () =>
|
|
|
206
207
|
expect(reserve?.compensation?.inputMapping?.['reservationId']).toBe(`\${nodes.${reserve?.id}.output.id}`);
|
|
207
208
|
expect(charge?.compensation?.inputMapping?.['chargeId']).toBe(`nodes.${charge?.id}.output.chargeId`);
|
|
208
209
|
expect(charge?.compensation?.requiresApproval).toBe(true);
|
|
209
|
-
expect(notify?.compensation, 'a node that declared none gains none').toBeUndefined();
|
|
210
|
+
expect(notify?.compensation, req('openwop.it.chain-compensation-expansion.the-declaration-survives-verbatim-onto-the-expanded-node-params-substituted-node', 'RFC 0157', 'a node that declared none gains none')).toBeUndefined();
|
|
210
211
|
// the mirrored core is untouched: plain expandChain still knows nothing of it
|
|
211
212
|
const plain = expandChain(CHAIN, CTX);
|
|
212
213
|
expect((plain.nodes[0] as { compensation?: unknown }).compensation).toBeUndefined();
|
|
@@ -220,7 +221,7 @@ describe('RFC 0157 — expansion carries the declaration and the policy', () =>
|
|
|
220
221
|
const out = expandChainWithCompensation(chain, CTX);
|
|
221
222
|
const nodes = out.nodes as ReadonlyArray<(typeof out.nodes)[number] & { compensation?: FragmentNodeCompensation }>;
|
|
222
223
|
const reserve = nodes.find((n) => n.id.endsWith('_reserve'));
|
|
223
|
-
expect(reserve?.compensation?.inputMapping?.['parentRef']).toBe('${nodes.upstream-in-parent.output.x}');
|
|
224
|
+
expect(reserve?.compensation?.inputMapping?.['parentRef'], req('openwop.it.chain-compensation-expansion.a-reference-to-a-non-fragment-node-id-inside-inputmapping-passes-through-unchang', 'RFC 0157', 'a reference to a NON-fragment node id inside inputMapping passes through unchanged')).toBe('${nodes.upstream-in-parent.output.x}');
|
|
224
225
|
});
|
|
225
226
|
|
|
226
227
|
it('an unresolvable compensation.nodeTypeId fails with chain_unresolvable_typeid before any node is emitted', () => {
|
|
@@ -231,7 +232,7 @@ describe('RFC 0157 — expansion carries the declaration and the policy', () =>
|
|
|
231
232
|
} catch (e) {
|
|
232
233
|
thrown = e;
|
|
233
234
|
}
|
|
234
|
-
expect(thrown).toBeInstanceOf(ChainUnresolvableTypeIdError);
|
|
235
|
+
expect(thrown, req('openwop.it.chain-compensation-expansion.an-unresolvable-compensation-nodetypeid-fails-with-chain-unresolvable-typeid-bef', 'RFC 0157', 'an unresolvable compensation.nodeTypeId fails with chain_unresolvable_typeid before any node is emitted')).toBeInstanceOf(ChainUnresolvableTypeIdError);
|
|
235
236
|
expect((thrown as ChainUnresolvableTypeIdError).typeId).toBe('vendor.pay.refund');
|
|
236
237
|
expect(String((thrown as Error).message)).toContain('chain_unresolvable_typeid');
|
|
237
238
|
});
|
|
@@ -239,12 +240,12 @@ describe('RFC 0157 — expansion carries the declaration and the policy', () =>
|
|
|
239
240
|
it('the chain policy becomes settings.compensation when the parent has none', () => {
|
|
240
241
|
const out = expandChainWithCompensation(CHAIN, CTX);
|
|
241
242
|
expect(out.settingsCompensation).toEqual(POLICY);
|
|
242
|
-
expect(out.settingsCompensation, 'a copy, not the same object').not.toBe(POLICY);
|
|
243
|
+
expect(out.settingsCompensation, req('openwop.it.chain-compensation-expansion.the-chain-policy-becomes-settings-compensation-when-the-parent-has-none', 'RFC 0157', 'a copy, not the same object')).not.toBe(POLICY);
|
|
243
244
|
});
|
|
244
245
|
|
|
245
246
|
it('a parent policy that is deep-equal (any key order) is accepted; a differing one is refused, never merged', () => {
|
|
246
247
|
const sameOtherOrder: ChainCompensationPolicy = { approvalScope: 'declared', triggers: ['node-failure', 'run-cancel'] };
|
|
247
|
-
expect(expandChainWithCompensation(CHAIN, CTX, sameOtherOrder).settingsCompensation).toEqual(sameOtherOrder);
|
|
248
|
+
expect(expandChainWithCompensation(CHAIN, CTX, sameOtherOrder).settingsCompensation, req('openwop.it.chain-compensation-expansion.a-parent-policy-that-is-deep-equal-any-key-order-is-accepted-a-differing-one-is', 'RFC 0157', 'a parent policy that is deep-equal (any key order) is accepted; a differing one is refused, never merged')).toEqual(sameOtherOrder);
|
|
248
249
|
const differs: ChainCompensationPolicy = { triggers: ['node-failure'] };
|
|
249
250
|
let thrown: unknown;
|
|
250
251
|
try {
|
|
@@ -262,7 +263,7 @@ describe('RFC 0157 — expansion carries the declaration and the policy', () =>
|
|
|
262
263
|
for (const n of chain.dag.nodes as unknown as Array<{ compensation?: unknown }>) delete n.compensation;
|
|
263
264
|
const parent: ChainCompensationPolicy = { triggers: ['operator-request'] };
|
|
264
265
|
const withParent = expandChainWithCompensation(chain, CTX, parent);
|
|
265
|
-
expect(withParent.settingsCompensation).toBe(parent);
|
|
266
|
+
expect(withParent.settingsCompensation, req('openwop.it.chain-compensation-expansion.a-chain-with-no-policy-inherits-the-parent-policy-or-none-and-carries-no-declara', 'RFC 0157', 'a chain with no policy inherits the parent policy (or none) and carries no declaration it did not make')).toBe(parent);
|
|
266
267
|
const nodes = withParent.nodes as ReadonlyArray<{ compensation?: unknown }>;
|
|
267
268
|
expect(nodes.every((n) => n.compensation === undefined)).toBe(true);
|
|
268
269
|
const withoutParent = carryCompensation(chain, expandChain(chain, CTX), CTX);
|