@openwop/openwop-conformance 1.163.0 → 2.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/cli.js +262 -14
- package/dist/lib/certification-bundle-v3.js +142 -0
- package/dist/lib/paths.js +25 -3
- package/dist/lib/requirement-ids.js +57 -4
- package/dist/lib/requirement-ledger.js +1 -0
- package/dist/lib/scenario-disposition.js +6 -1
- package/dist/lib/spec-coherence.js +13 -32
- package/dist/spec-artifacts.lock.json +5 -0
- package/package.json +11 -33
- package/requirements.json +22892 -11701
- package/scenario-majors.json +1493 -0
- package/schemas/CORPUS-STAMP.json +137 -5
- package/src/cli.ts +236 -19
- package/src/global-setup.ts +3 -2
- package/src/lib/certification-bundle-v3.ts +175 -0
- package/src/lib/corpus-stamp.ts +29 -1
- package/src/lib/driver.ts +9 -1
- package/src/lib/era2-seed.ts +197 -0
- package/src/lib/paths.ts +25 -3
- package/src/lib/requirement-ids.ts +54 -4
- package/src/lib/requirement-ledger.ts +7 -1
- package/src/lib/scenario-disposition.ts +6 -1
- package/src/lib/seams.ts +45 -0
- package/src/lib/spec-coherence.ts +15 -32
- package/src/lib/v2.ts +62 -0
- package/src/scenarios/a2a-1-0-agent-card.test.ts +17 -16
- package/src/scenarios/a2a-1-0-task-roundtrip.test.ts +11 -10
- package/src/scenarios/a2a-card-runtime-consistency.test.ts +18 -17
- package/src/scenarios/a2a-peer-authority.test.ts +7 -6
- package/src/scenarios/a2a-task-roundtrip.test.ts +30 -29
- package/src/scenarios/a2a-version-negotiation.test.ts +16 -14
- package/src/scenarios/a2ui-surface-degrades.test.ts +5 -3
- package/src/scenarios/a2ui-surface-delta-transport.test.ts +35 -33
- package/src/scenarios/a2ui-surface-replay.test.ts +6 -4
- package/src/scenarios/a2ui-surface-shape.test.ts +11 -10
- package/src/scenarios/a2ui-surface-version-refusal.test.ts +6 -4
- package/src/scenarios/a2ui-untrusted-blocks-approval.test.ts +5 -3
- package/src/scenarios/agent-capability-degraded-projection.test.ts +12 -11
- package/src/scenarios/agent-channel-dispatch.test.ts +18 -16
- package/src/scenarios/agent-deployment-lifecycle.test.ts +20 -19
- package/src/scenarios/agent-deployment-shape.test.ts +22 -24
- package/src/scenarios/agent-eval-run.test.ts +19 -18
- package/src/scenarios/agent-eval-suite-shape.test.ts +20 -22
- package/src/scenarios/agent-live-allowlist-enforced.test.ts +6 -5
- package/src/scenarios/agent-live-invocation-bracket.test.ts +13 -12
- package/src/scenarios/agent-live-runtime-shape.test.ts +10 -12
- package/src/scenarios/agent-live-structured-output.test.ts +7 -6
- package/src/scenarios/agent-loop-iteration-monotonic.test.ts +3 -3
- package/src/scenarios/agent-loop-stateful-resume.test.ts +2 -2
- package/src/scenarios/agent-loop-version5-shape.test.ts +4 -4
- package/src/scenarios/agent-loop-workspace-snapshot.test.ts +3 -3
- package/src/scenarios/agent-manifest-role-profile.test.ts +9 -9
- package/src/scenarios/agent-manifest-runtime.test.ts +13 -12
- package/src/scenarios/agent-org-chart-scoping.test.ts +14 -13
- package/src/scenarios/agent-org-chart-shape.test.ts +11 -13
- package/src/scenarios/agent-platform-aggregate-evidence.test.ts +5 -4
- package/src/scenarios/agent-platform-profile.test.ts +18 -19
- package/src/scenarios/agent-requires-capabilities-shape.test.ts +4 -4
- package/src/scenarios/agent-roster-attribution.test.ts +18 -17
- package/src/scenarios/agent-roster-shape.test.ts +17 -19
- package/src/scenarios/agent-verifier-shape.test.ts +11 -11
- package/src/scenarios/agentConfidenceEscalation.test.ts +2 -1
- package/src/scenarios/agentMemoryCrossTenantIsolation.test.ts +6 -5
- package/src/scenarios/agentMemoryRedactionContract.test.ts +2 -1
- package/src/scenarios/agentMemoryRoundTrip.test.ts +2 -1
- package/src/scenarios/agentMemoryTtlExpiry.test.ts +4 -3
- package/src/scenarios/agentMessageReducer.test.ts +2 -1
- package/src/scenarios/agentMetadata.test.ts +2 -1
- package/src/scenarios/agentPackCatalog.test.ts +23 -21
- package/src/scenarios/agentPackExport.test.ts +4 -2
- package/src/scenarios/agentPackHandoffSchemaValidation.test.ts +5 -4
- package/src/scenarios/agentPackInstall.test.ts +4 -2
- package/src/scenarios/agentPackProvenance.test.ts +2 -1
- package/src/scenarios/agentReasoningEvents.test.ts +3 -2
- package/src/scenarios/agentReasoningStreaming.test.ts +11 -10
- package/src/scenarios/agents-run-tool-allowlist.test.ts +14 -12
- package/src/scenarios/ai-envelope-shape.test.ts +31 -29
- package/src/scenarios/aiEnvelope.capBreached.test.ts +26 -24
- package/src/scenarios/aiEnvelope.contractRefusal.test.ts +36 -34
- package/src/scenarios/aiEnvelope.correlationReplay.test.ts +17 -15
- package/src/scenarios/aiEnvelope.redaction.test.ts +22 -20
- package/src/scenarios/aiEnvelope.schemaDrift.test.ts +18 -16
- package/src/scenarios/aiEnvelope.trustBoundaryPropagation.test.ts +38 -36
- package/src/scenarios/aiEnvelope.universalKinds.test.ts +31 -29
- package/src/scenarios/aiproviders-input-shape.test.ts +5 -5
- package/src/scenarios/aiproviders-realtimevoice-shape.test.ts +14 -16
- package/src/scenarios/aiproviders-selfhosted-honesty.test.ts +6 -4
- package/src/scenarios/aiproviders-selfhosted-shape.test.ts +15 -17
- package/src/scenarios/aiproviders-speechsynth-shape.test.ts +6 -8
- package/src/scenarios/aiproviders-subscription-scope.test.ts +7 -8
- package/src/scenarios/anonymous-actor-audit-opaque.test.ts +9 -8
- package/src/scenarios/anonymous-actor-default-deny.test.ts +10 -9
- package/src/scenarios/anonymous-actor-egress-guarded.test.ts +6 -5
- package/src/scenarios/anonymous-actor-no-secret-reach.test.ts +7 -6
- package/src/scenarios/anonymous-actor-shape.test.ts +17 -19
- package/src/scenarios/anonymous-actor-write-gated.test.ts +9 -8
- package/src/scenarios/append-ordering.test.ts +7 -5
- package/src/scenarios/approval-gate-events.test.ts +4 -3
- package/src/scenarios/approval-gate-flow.test.ts +4 -3
- package/src/scenarios/approval-payload.test.ts +5 -4
- package/src/scenarios/artifact-auth.test.ts +7 -6
- package/src/scenarios/artifact-type-pack-install.test.ts +6 -6
- package/src/scenarios/artifact-type-pack-manifest-validation.test.ts +9 -8
- package/src/scenarios/artifact-type-registration-source.test.ts +20 -20
- package/src/scenarios/artifact-type-store-emission.test.ts +15 -18
- package/src/scenarios/artifact-type-store-without-render.test.ts +4 -4
- package/src/scenarios/audit-log-integrity.test.ts +8 -6
- package/src/scenarios/auth-api-key-rotation.test.ts +11 -9
- package/src/scenarios/auth-mtls.test.ts +22 -20
- package/src/scenarios/auth-oauth2-client-credentials.test.ts +16 -14
- package/src/scenarios/auth-oidc-user-bearer.test.ts +26 -24
- package/src/scenarios/auth-saml-profile.test.ts +14 -12
- package/src/scenarios/auth-scim-profile.test.ts +3 -2
- package/src/scenarios/auth-subject-link-key-class.test.ts +8 -7
- package/src/scenarios/auth-subject-link.test.ts +11 -10
- package/src/scenarios/auth.test.ts +5 -4
- package/src/scenarios/authorization-fail-closed.test.ts +3 -2
- package/src/scenarios/authorization-roles-shape.test.ts +9 -7
- package/src/scenarios/blob-cross-tenant-isolation.test.ts +8 -6
- package/src/scenarios/blob-presign-expiry.test.ts +12 -10
- package/src/scenarios/blob-roundtrip.test.ts +0 -0
- package/src/scenarios/budget-enforcement.test.ts +15 -14
- package/src/scenarios/budget-policy-shape.test.ts +18 -19
- package/src/scenarios/bulk-cancel.test.ts +9 -8
- package/src/scenarios/byok-auth-modes.test.ts +13 -14
- package/src/scenarios/byok-roundtrip.test.ts +12 -10
- package/src/scenarios/cache-cross-tenant-isolation.test.ts +8 -6
- package/src/scenarios/cache-ttl-expiry.test.ts +7 -5
- package/src/scenarios/callai-multimodal.test.ts +5 -3
- package/src/scenarios/cancellation.test.ts +6 -5
- package/src/scenarios/cap-breach.test.ts +15 -14
- package/src/scenarios/capability-arm-witness.test.ts +5 -3
- package/src/scenarios/certification-bundle-non-vacuous.test.ts +20 -19
- package/src/scenarios/certification-bundle-redaction.test.ts +11 -10
- package/src/scenarios/certification-bundle-v2.test.ts +11 -10
- package/src/scenarios/chain-compensation-expansion.test.ts +14 -13
- package/src/scenarios/chain-produced-var-roundtrip.test.ts +13 -14
- package/src/scenarios/chain-subchain-cycle-rejected.test.ts +9 -10
- package/src/scenarios/chain-subchain-fanout.test.ts +8 -9
- package/src/scenarios/chain-subchain-sibling.test.ts +14 -16
- package/src/scenarios/channel-presence-behavioral.test.ts +9 -7
- package/src/scenarios/channel-presence-shape.test.ts +11 -12
- package/src/scenarios/channel-ttl.test.ts +6 -5
- package/src/scenarios/chat-card-pack-execution.test.ts +4 -4
- package/src/scenarios/chat-card-pack-manifest-validation.test.ts +9 -8
- package/src/scenarios/commitment-fired.test.ts +6 -5
- package/src/scenarios/compensation-behavior.test.ts +24 -23
- package/src/scenarios/compensation-recovery.test.ts +28 -27
- package/src/scenarios/configurable-schema.test.ts +8 -6
- package/src/scenarios/conformance-advertised-seam-required.test.ts +6 -5
- package/src/scenarios/conformance-execution-witness.test.ts +9 -8
- package/src/scenarios/connection-pack-apihosts.test.ts +19 -17
- package/src/scenarios/connection-pack-manifest-valid.test.ts +12 -11
- package/src/scenarios/connection-pack-no-credential-material.test.ts +8 -6
- package/src/scenarios/connection-pack-reach-exclusive.test.ts +5 -4
- package/src/scenarios/connection-pack-write-reconsent.test.ts +6 -4
- package/src/scenarios/connection-provider-resolution.test.ts +18 -16
- package/src/scenarios/connector-manifest-validity.test.ts +8 -7
- package/src/scenarios/context-budget-transcript-bound.test.ts +17 -15
- package/src/scenarios/context-summarization-replay.test.ts +14 -12
- package/src/scenarios/contract-provenance.test.ts +18 -18
- package/src/scenarios/conversation-turn-model-provenance-shape.test.ts +14 -15
- package/src/scenarios/conversationCapabilityNegotiation.test.ts +2 -1
- package/src/scenarios/conversationLifecycle.test.ts +2 -1
- package/src/scenarios/conversationReplayDeterminism.test.ts +4 -2
- package/src/scenarios/conversationVsLegacySuspend.test.ts +2 -1
- package/src/scenarios/core-standard-profile.test.ts +8 -9
- package/src/scenarios/cost-attribution.test.ts +23 -21
- package/src/scenarios/credential-payload-redaction.test.ts +4 -3
- package/src/scenarios/credentials-capability-shape.test.ts +5 -4
- package/src/scenarios/cross-engine-append-behavior.test.ts +11 -10
- package/src/scenarios/cross-engine-append-ordering.test.ts +4 -3
- package/src/scenarios/cross-host-ancestry-endpoint.test.ts +11 -10
- package/src/scenarios/cross-host-causation-shape.test.ts +7 -6
- package/src/scenarios/cross-workspace-isolation.test.ts +4 -3
- package/src/scenarios/data-residency-admission.test.ts +15 -14
- package/src/scenarios/deadletter-capability-shape.test.ts +6 -4
- package/src/scenarios/deadletter-retry-exhaustion.test.ts +4 -3
- package/src/scenarios/debug-bundle-truncation.test.ts +9 -7
- package/src/scenarios/debugBundle.test.ts +25 -23
- package/src/scenarios/discovery-families-view.test.ts +5 -4
- package/src/scenarios/discovery.test.ts +30 -29
- package/src/scenarios/dispatch-cross-worker-handoff.test.ts +10 -8
- package/src/scenarios/dispatch-fanout-parallel.test.ts +24 -22
- package/src/scenarios/dispatch-input-mapping.test.ts +12 -10
- package/src/scenarios/dispatch-output-mapping.test.ts +9 -7
- package/src/scenarios/dispatch-per-item-input.test.ts +22 -20
- package/src/scenarios/dispatchLoop.test.ts +3 -2
- package/src/scenarios/distillation-index-roundtrip.test.ts +3 -3
- package/src/scenarios/distillation-secret-carryforward.test.ts +2 -2
- package/src/scenarios/distillation-shape.test.ts +4 -4
- package/src/scenarios/distillation-stable-archive.test.ts +6 -6
- package/src/scenarios/distillation-token-budget.test.ts +5 -5
- package/src/scenarios/durability-poison-exhaustion.test.ts +7 -6
- package/src/scenarios/egress-audience-binding.test.ts +11 -10
- package/src/scenarios/egress-decision-content-free.test.ts +6 -5
- package/src/scenarios/egress-provenance-shape.test.ts +17 -18
- package/src/scenarios/envelope-completion-distinguishes-truncation.test.ts +27 -25
- package/src/scenarios/envelope-nl-to-format-engaged.test.ts +19 -17
- package/src/scenarios/envelope-reasoning-secret-redaction.test.ts +28 -26
- package/src/scenarios/envelope-reasoning-shape.test.ts +19 -17
- package/src/scenarios/envelope-recovery-applied.test.ts +24 -22
- package/src/scenarios/envelope-refusal-shape.test.ts +31 -29
- package/src/scenarios/envelope-rendering-hint.test.ts +5 -4
- package/src/scenarios/envelope-retry-attempted.test.ts +33 -31
- package/src/scenarios/envelope-retry-exhausted.test.ts +22 -20
- package/src/scenarios/envelope-tier-one-subset-static.test.ts +9 -7
- package/src/scenarios/envelope-truncated.test.ts +19 -17
- package/src/scenarios/envelope-truncation-cap-exhaustion.test.ts +19 -17
- package/src/scenarios/envelope-variant-discriminator-static.test.ts +4 -3
- package/src/scenarios/errors.test.ts +20 -19
- package/src/scenarios/eventOrdering.test.ts +16 -14
- package/src/scenarios/exec-not-protocol-tier.test.ts +5 -7
- package/src/scenarios/experimental-tier-shape.test.ts +17 -15
- package/src/scenarios/export-bundle-portability.test.ts +17 -17
- package/src/scenarios/failure-path.test.ts +7 -6
- package/src/scenarios/feedback-capability-shape.test.ts +4 -3
- package/src/scenarios/feedback-correction-redaction.test.ts +2 -1
- package/src/scenarios/feedback-cross-tenant-isolation.test.ts +2 -1
- package/src/scenarios/feedback-fork-not-copied.test.ts +3 -2
- package/src/scenarios/feedback-on-terminal-run.test.ts +3 -2
- package/src/scenarios/feedback-record-and-list.test.ts +2 -1
- package/src/scenarios/feedback-unsupported-501.test.ts +2 -1
- package/src/scenarios/fixtures-gating.test.ts +29 -28
- package/src/scenarios/fixtures-valid.test.ts +16 -15
- package/src/scenarios/form-content-instantiation.test.ts +15 -14
- package/src/scenarios/frontend-plugin-packs.test.ts +29 -27
- package/src/scenarios/fs-path-traversal.test.ts +14 -12
- package/src/scenarios/goal-standing-continuation.test.ts +21 -21
- package/src/scenarios/grpc-transport.test.ts +9 -7
- package/src/scenarios/heartbeat-capability-shape.test.ts +3 -3
- package/src/scenarios/heartbeat-fires-once-per-tick.test.ts +2 -2
- package/src/scenarios/heartbeat-idempotent-no-spam.test.ts +3 -3
- package/src/scenarios/heartbeat-runtime-bound.test.ts +2 -2
- package/src/scenarios/highConcurrency.test.ts +14 -13
- package/src/scenarios/host-callback-declaration.test.ts +8 -7
- package/src/scenarios/http-client-ssrf.test.ts +7 -5
- package/src/scenarios/i18n-negotiation.test.ts +14 -12
- package/src/scenarios/idempotency-concurrent-claim.test.ts +14 -13
- package/src/scenarios/idempotency-key-determinism.test.ts +20 -18
- package/src/scenarios/idempotency.test.ts +8 -7
- package/src/scenarios/idempotencyRetry.test.ts +13 -11
- package/src/scenarios/identity-owner-shape.test.ts +6 -5
- package/src/scenarios/identity-passthrough.test.ts +4 -3
- package/src/scenarios/interrupt-approval.test.ts +6 -5
- package/src/scenarios/interrupt-approver-routing.test.ts +15 -15
- package/src/scenarios/interrupt-auth-required-resume.test.ts +5 -3
- package/src/scenarios/interrupt-clarification.test.ts +4 -3
- package/src/scenarios/interrupt-external-event-correlation.test.ts +6 -5
- package/src/scenarios/interrupt-parent-child-cascade.test.ts +4 -3
- package/src/scenarios/interrupt-quorum-resolution.test.ts +5 -4
- package/src/scenarios/interrupt-token-matrix.test.ts +8 -6
- package/src/scenarios/interruptRace.test.ts +12 -10
- package/src/scenarios/kv-atomic-increment.test.ts +11 -9
- package/src/scenarios/kv-cas.test.ts +14 -12
- package/src/scenarios/kv-cross-tenant-isolation.test.ts +12 -10
- package/src/scenarios/kv-ttl-expiry.test.ts +7 -5
- package/src/scenarios/localized-content-delivery.test.ts +24 -26
- package/src/scenarios/maliciousManifest.test.ts +10 -8
- package/src/scenarios/mcp-2026-07-28-discover.test.ts +15 -14
- package/src/scenarios/mcp-cache-tenant-scope.test.ts +6 -5
- package/src/scenarios/mcp-current-auth-boundary.test.ts +7 -6
- package/src/scenarios/mcp-discoverability.test.ts +9 -7
- package/src/scenarios/mcp-extension-opacity.test.ts +7 -6
- package/src/scenarios/mcp-mrtr-roundtrip.test.ts +18 -17
- package/src/scenarios/mcp-server-elicitation-bridge.test.ts +13 -12
- package/src/scenarios/mcp-server-prompt-roundtrip.test.ts +11 -10
- package/src/scenarios/mcp-server-resource-roundtrip.test.ts +11 -10
- package/src/scenarios/mcp-server-sampling-bridge.test.ts +12 -11
- package/src/scenarios/mcp-server-tool-roundtrip.test.ts +14 -13
- package/src/scenarios/mcp-server-untrusted-args.test.ts +14 -13
- package/src/scenarios/mcp-stateless-request.test.ts +11 -10
- package/src/scenarios/mcp-tool-roundtrip.test.ts +4 -3
- package/src/scenarios/mcp-toolcall-redaction.test.ts +4 -3
- package/src/scenarios/mcp-version-negotiation.test.ts +15 -14
- package/src/scenarios/media-url-inline-cap.test.ts +21 -19
- package/src/scenarios/memory-attribution-emits-on-write.test.ts +5 -5
- package/src/scenarios/memory-attribution-no-content.test.ts +4 -4
- package/src/scenarios/memory-attribution-replay-stable.test.ts +4 -3
- package/src/scenarios/memory-attribution-shape.test.ts +4 -3
- package/src/scenarios/memory-attribution-tenant-scoped.test.ts +3 -3
- package/src/scenarios/memory-capability-model-shape.test.ts +14 -15
- package/src/scenarios/memory-compaction-event-emitted.test.ts +7 -6
- package/src/scenarios/memory-compaction-provenance-tag.test.ts +3 -2
- package/src/scenarios/memory-compaction-sr1-carry-forward.test.ts +7 -6
- package/src/scenarios/memory-consolidation-idempotent.test.ts +5 -4
- package/src/scenarios/memory-consolidation-shape.test.ts +9 -11
- package/src/scenarios/memory-degraded-projection.test.ts +10 -9
- package/src/scenarios/memory-injection-budget.test.ts +15 -14
- package/src/scenarios/metric-emission.test.ts +4 -3
- package/src/scenarios/model-capability-insufficient.test.ts +19 -17
- package/src/scenarios/model-capability-substituted.test.ts +20 -18
- package/src/scenarios/multi-agent-confidence-escalation.test.ts +12 -11
- package/src/scenarios/multi-agent-handoff-state-machine.test.ts +10 -9
- package/src/scenarios/multi-agent-memory-lifecycle.test.ts +11 -10
- package/src/scenarios/multi-node-ordering.test.ts +4 -3
- package/src/scenarios/multi-party-conversation-behavioral.test.ts +6 -6
- package/src/scenarios/multi-party-conversation-shape.test.ts +21 -23
- package/src/scenarios/multi-region-idempotency-behavior.test.ts +16 -15
- package/src/scenarios/multi-region-idempotency.test.ts +6 -5
- package/src/scenarios/node-failed-payload-shape.test.ts +5 -4
- package/src/scenarios/node-module-required-capabilities-shape.test.ts +10 -8
- package/src/scenarios/oauth-authorization-code-roundtrip.test.ts +5 -4
- package/src/scenarios/oauth-capability-shape.test.ts +5 -4
- package/src/scenarios/oauth-connector-redaction.test.ts +4 -3
- package/src/scenarios/openapi-resolved-paths.test.ts +6 -5
- package/src/scenarios/orchestratorConservativePath.test.ts +2 -1
- package/src/scenarios/orchestratorDispatch.test.ts +2 -1
- package/src/scenarios/orchestratorTermination.test.ts +2 -1
- package/src/scenarios/org-position-no-authority-escalation.test.ts +2 -2
- package/src/scenarios/otel-collector-canary-inspection.test.ts +8 -7
- package/src/scenarios/otel-emission-grpc.test.ts +3 -2
- package/src/scenarios/otel-emission.test.ts +6 -5
- package/src/scenarios/otel-trace-propagation-subworkflow.test.ts +7 -6
- package/src/scenarios/otel-trace-propagation.test.ts +2 -1
- package/src/scenarios/owner-subject-echo.test.ts +13 -12
- package/src/scenarios/owner-subject-shape.test.ts +11 -10
- package/src/scenarios/pack-manifest-extension-opacity.test.ts +8 -8
- package/src/scenarios/pack-registry-isolation.test.ts +4 -3
- package/src/scenarios/pack-registry-publish.test.ts +65 -63
- package/src/scenarios/pack-registry.test.ts +38 -36
- package/src/scenarios/pause-resume.test.ts +18 -16
- package/src/scenarios/policies.test.ts +20 -18
- package/src/scenarios/production-backpressure.test.ts +18 -16
- package/src/scenarios/production-retention-expiry.test.ts +8 -6
- package/src/scenarios/profile-discovery-core-alias.test.ts +13 -11
- package/src/scenarios/profileDerivation.test.ts +34 -33
- package/src/scenarios/prompt-all-four-kinds-events.test.ts +11 -9
- package/src/scenarios/prompt-composed-secret-redaction.test.ts +11 -9
- package/src/scenarios/prompt-composed-trust-marker.test.ts +10 -8
- package/src/scenarios/prompt-end-to-end-events.test.ts +17 -15
- package/src/scenarios/prompt-list-and-fetch.test.ts +16 -14
- package/src/scenarios/prompt-mutable-lifecycle.test.ts +13 -11
- package/src/scenarios/prompt-mutation-workspace-membership-enforced.test.ts +6 -4
- package/src/scenarios/prompt-pack-install.test.ts +14 -12
- package/src/scenarios/prompt-prefix-cache.test.ts +19 -17
- package/src/scenarios/prompt-read-workspace-membership-enforced.test.ts +11 -9
- package/src/scenarios/prompt-render-deterministic.test.ts +19 -17
- package/src/scenarios/prompt-resolution-chain-agent-intrinsic.test.ts +7 -5
- package/src/scenarios/prompt-resolution-chain-event.test.ts +12 -10
- package/src/scenarios/prompt-resolution-chain-fallback-cascade.test.ts +10 -8
- package/src/scenarios/prompt-resolution-chain-node-wins.test.ts +8 -6
- package/src/scenarios/prompt-template-shape.test.ts +30 -28
- package/src/scenarios/proposal-reviewable-learning.test.ts +19 -19
- package/src/scenarios/protocol-versions-array.test.ts +10 -9
- package/src/scenarios/provider-usage.test.ts +17 -15
- package/src/scenarios/providerPolicyEnforcement.test.ts +16 -14
- package/src/scenarios/purpose-propagation.test.ts +23 -21
- package/src/scenarios/queue-ack-nack-dlq.test.ts +12 -10
- package/src/scenarios/queue-cross-tenant-isolation.test.ts +8 -6
- package/src/scenarios/queue-publish-consume-roundtrip.test.ts +10 -8
- package/src/scenarios/rate-limit-envelope.test.ts +6 -5
- package/src/scenarios/redaction.test.ts +13 -11
- package/src/scenarios/redactionAdversarial.test.ts +11 -9
- package/src/scenarios/registry-public.test.ts +12 -11
- package/src/scenarios/replay-divergence-at-refusal.test.ts +16 -15
- package/src/scenarios/replay-fanout-suppression.test.ts +16 -15
- package/src/scenarios/replay-fork-arbitrary.test.ts +24 -23
- package/src/scenarios/replay-fork.test.ts +20 -19
- package/src/scenarios/replay-llm-cache-key-portable.test.ts +11 -9
- package/src/scenarios/replay-llm-cache-key.test.ts +22 -21
- package/src/scenarios/replay-observable-sequence-determinism.test.ts +4 -3
- package/src/scenarios/replay-retention-expiry.test.ts +11 -9
- package/src/scenarios/replay-side-effect-suppression.test.ts +21 -19
- package/src/scenarios/replayDeterminism.test.ts +13 -12
- package/src/scenarios/requirement-ledger.test.ts +15 -14
- package/src/scenarios/restart-during-run.test.ts +3 -2
- package/src/scenarios/route-coverage.test.ts +20 -19
- package/src/scenarios/run-diff.test.ts +22 -20
- package/src/scenarios/run-execution-bounds-shape.test.ts +14 -12
- package/src/scenarios/run-transport-economy.test.ts +13 -11
- package/src/scenarios/runner-ledger.test.ts +17 -16
- package/src/scenarios/runs-lifecycle.test.ts +7 -6
- package/src/scenarios/runtime-capabilities.test.ts +12 -10
- package/src/scenarios/runtime-requires-install-gate.test.ts +8 -8
- package/src/scenarios/runtime-requires-shape.test.ts +7 -6
- package/src/scenarios/safefetch-behavior.test.ts +22 -21
- package/src/scenarios/safefetch-live-audit.test.ts +14 -13
- package/src/scenarios/sandbox-memory-cap.test.ts +2 -1
- package/src/scenarios/sandbox-mvp-behavior.test.ts +24 -23
- package/src/scenarios/sandbox-no-host-fs-escape.test.ts +3 -2
- package/src/scenarios/sandbox-timeout-cap.test.ts +2 -1
- package/src/scenarios/sandbox-wasm-isolation.test.ts +14 -15
- package/src/scenarios/sandbox-wasm-timeout.test.ts +5 -6
- package/src/scenarios/scheduling-capability-shape.test.ts +8 -6
- package/src/scenarios/scheduling-cron-fires-once.test.ts +8 -6
- package/src/scenarios/search-bm25-roundtrip.test.ts +8 -6
- package/src/scenarios/secret-leakage-otel-attribute.test.ts +22 -20
- package/src/scenarios/self-hosted-runner.test.ts +26 -24
- package/src/scenarios/semantic-digest-vectors.test.ts +8 -7
- package/src/scenarios/speech-synthesis-roundtrip.test.ts +9 -7
- package/src/scenarios/speech-synthesis-unadvertised.test.ts +5 -3
- package/src/scenarios/sql-injection-rejection.test.ts +8 -6
- package/src/scenarios/sql-transaction-atomicity.test.ts +10 -8
- package/src/scenarios/staleClaim.test.ts +5 -4
- package/src/scenarios/stream-modes-buffer.test.ts +8 -7
- package/src/scenarios/stream-modes-mixed.test.ts +10 -9
- package/src/scenarios/stream-modes.test.ts +12 -11
- package/src/scenarios/stream-subscribe-from-beginning.test.ts +11 -9
- package/src/scenarios/stream-text-fixture.test.ts +14 -13
- package/src/scenarios/streamReconnect.test.ts +13 -11
- package/src/scenarios/strict-behavior-gate.test.ts +9 -8
- package/src/scenarios/subrun-approval-fail-closed.test.ts +2 -2
- package/src/scenarios/subrun-approval-gate.test.ts +3 -3
- package/src/scenarios/subrun-attestation-shape.test.ts +2 -2
- package/src/scenarios/subrun-checksum-stable.test.ts +4 -4
- package/src/scenarios/subworkflow-input-mapping.test.ts +16 -14
- package/src/scenarios/subworkflow.test.ts +10 -9
- package/src/scenarios/table-cross-tenant-isolation.test.ts +8 -6
- package/src/scenarios/table-cursor-pagination.test.ts +8 -6
- package/src/scenarios/table-schema-enforcement.test.ts +7 -5
- package/src/scenarios/test-seam-unauthenticated.test.ts +2 -2
- package/src/scenarios/tool-catalog-compact-projection.test.ts +10 -8
- package/src/scenarios/tool-catalog-projection.test.ts +11 -9
- package/src/scenarios/tool-descriptor-shape.test.ts +14 -15
- package/src/scenarios/tool-hooks-authorization-fail-closed.test.ts +3 -3
- package/src/scenarios/tool-hooks-content-free.test.ts +4 -4
- package/src/scenarios/tool-hooks-failure-honesty.test.ts +4 -4
- package/src/scenarios/tool-hooks-rate-limit.test.ts +2 -2
- package/src/scenarios/tool-hooks-secret-redaction.test.ts +2 -2
- package/src/scenarios/tool-hooks-shape.test.ts +3 -3
- package/src/scenarios/tool-session-lifecycle.test.ts +14 -13
- package/src/scenarios/trigger-bridge-delivery.test.ts +14 -14
- package/src/scenarios/trigger-bridge-shape.test.ts +19 -20
- package/src/scenarios/trigger-ingestion.test.ts +19 -17
- package/src/scenarios/trigger-stream-cdc-sources.test.ts +16 -14
- package/src/scenarios/v2-approver-enforced.test.ts +97 -0
- package/src/scenarios/v2-assurance-downgrade-audited.test.ts +75 -0
- package/src/scenarios/v2-bundle-v3-signed.test.ts +96 -0
- package/src/scenarios/v2-capabilities-root-closed.test.ts +63 -0
- package/src/scenarios/v2-capability-record-shape.test.ts +104 -0
- package/src/scenarios/v2-chain-pin-exact.test.ts +118 -0
- package/src/scenarios/v2-coherence-not-in-bundle.test.ts +98 -0
- package/src/scenarios/v2-compensation-read-projection.test.ts +85 -0
- package/src/scenarios/v2-configurable-closed.test.ts +67 -0
- package/src/scenarios/v2-dual-stack-negotiation.test.ts +118 -0
- package/src/scenarios/v2-effect-identity-business-key.test.ts +132 -0
- package/src/scenarios/v2-effect-seam-manifest.test.ts +163 -0
- package/src/scenarios/v2-enum-growth-rule.test.ts +69 -0
- package/src/scenarios/v2-era-key.test.ts +86 -0
- package/src/scenarios/v2-error-registry.test.ts +89 -0
- package/src/scenarios/v2-event-type-closed.test.ts +97 -0
- package/src/scenarios/v2-fork-a-v1-run.test.ts +109 -0
- package/src/scenarios/v2-form-when-reuses-edge-conditions.test.ts +70 -0
- package/src/scenarios/v2-header-scheme.test.ts +67 -0
- package/src/scenarios/v2-id-grammar.test.ts +77 -0
- package/src/scenarios/v2-idempotency-key-grammar.test.ts +65 -0
- package/src/scenarios/v2-interrupt-token-scheme.test.ts +55 -0
- package/src/scenarios/v2-lane-issuer-advertised.test.ts +62 -0
- package/src/scenarios/v2-legacy-profiles-absent.test.ts +78 -0
- package/src/scenarios/v2-manifest-ceiling-refused.test.ts +130 -0
- package/src/scenarios/v2-manifest-hatch-carried.test.ts +120 -0
- package/src/scenarios/v2-min-client-version.test.ts +48 -0
- package/src/scenarios/v2-minimum-version-refused.test.ts +141 -0
- package/src/scenarios/v2-mrtr-rounds-ceiling.test.ts +95 -0
- package/src/scenarios/v2-negotiation-authenticated.test.ts +125 -0
- package/src/scenarios/v2-negotiation-decided-emitted.test.ts +149 -0
- package/src/scenarios/v2-no-transport-advertisement.test.ts +57 -0
- package/src/scenarios/v2-owner-subject-required.test.ts +72 -0
- package/src/scenarios/v2-pack-isolation.test.ts +147 -0
- package/src/scenarios/v2-payload-registry-closed.test.ts +110 -0
- package/src/scenarios/v2-peer-dependency-declared.test.ts +132 -0
- package/src/scenarios/v2-pinned-run-disposition.test.ts +124 -0
- package/src/scenarios/v2-poll-cursor-v2.test.ts +114 -0
- package/src/scenarios/v2-preferred-version-default.test.ts +83 -0
- package/src/scenarios/v2-profiles-derived-only.test.ts +70 -0
- package/src/scenarios/v2-provider-conflict.test.ts +115 -0
- package/src/scenarios/v2-refresh-sla.test.ts +78 -0
- package/src/scenarios/v2-relaxation-recorded.test.ts +134 -0
- package/src/scenarios/v2-revocation-honored.test.ts +66 -0
- package/src/scenarios/v2-subject-link-record.test.ts +102 -0
- package/src/scenarios/v2-unmapped-type-refused.test.ts +95 -0
- package/src/scenarios/v2-v1-events-translated.test.ts +105 -0
- package/src/scenarios/v2-v1-signed-webhook-accepted.test.ts +109 -0
- package/src/scenarios/v2-webhook-durable-delivery.test.ts +238 -0
- package/src/scenarios/v2-well-known-one-resource.test.ts +129 -0
- package/src/scenarios/vector-knn-roundtrip.test.ts +9 -7
- package/src/scenarios/verifier-gating.test.ts +8 -6
- package/src/scenarios/version-fold.test.ts +9 -8
- package/src/scenarios/version-negotiation.test.ts +12 -11
- package/src/scenarios/voice-bargein-no-partial-leak.test.ts +8 -6
- package/src/scenarios/voice-event-payloads-shape.test.ts +10 -11
- package/src/scenarios/voice-interim-not-durable.test.ts +7 -5
- package/src/scenarios/voice-streamref-tenant-bound.test.ts +5 -3
- package/src/scenarios/voice-synthesis-streaming.test.ts +11 -9
- package/src/scenarios/voice-transcription-streaming.test.ts +8 -6
- package/src/scenarios/voice-transcription-unadvertised.test.ts +5 -3
- package/src/scenarios/wasm-pack-abi-version-rejection.test.ts +3 -2
- package/src/scenarios/wasm-pack-invoke-completed.test.ts +3 -2
- package/src/scenarios/wasm-pack-invoke-suspended.test.ts +3 -2
- package/src/scenarios/wasm-pack-load.test.ts +4 -3
- package/src/scenarios/wasm-pack-memory-cap.test.ts +5 -4
- package/src/scenarios/wasm-pack-replay-determinism.test.ts +2 -1
- package/src/scenarios/webhook-negative.test.ts +11 -9
- package/src/scenarios/webhook-receiver-adversarial.test.ts +8 -7
- package/src/scenarios/webhook-sig-algorithm.test.ts +5 -3
- package/src/scenarios/webhook-signed-delivery.test.ts +16 -15
- package/src/scenarios/webhook-tenant-isolation.test.ts +16 -14
- package/src/scenarios/workflow-chain-deferred-parameters.test.ts +28 -30
- package/src/scenarios/workflow-chain-expansion.test.ts +21 -20
- package/src/scenarios/workflow-chain-host-expansion.test.ts +21 -20
- package/src/scenarios/workflow-chain-pack-manifest-validation.test.ts +14 -12
- package/src/scenarios/workflow-chain-pack-signature-verification.test.ts +5 -4
- package/src/scenarios/workflow-chain-unresolvable-typeid.test.ts +9 -8
- package/src/scenarios/workflow-primary-output-annotation.test.ts +7 -6
- package/src/scenarios/workflow-variable-format.test.ts +24 -24
- package/src/scenarios/workload-identity-behavior.test.ts +15 -13
- package/src/scenarios/workload-identity-chain-bounds.test.ts +18 -17
- package/src/scenarios/workspace-behavior.test.ts +19 -18
- package/src/scenarios/workspace-capability-shape.test.ts +3 -2
- package/src/scenarios/workspace-cross-tenant-isolation-blackbox.test.ts +6 -5
- package/src/scenarios/workspace-cross-tenant-isolation.test.ts +7 -6
- package/src/scenarios/x-openwop-form-pack-manifest.test.ts +8 -7
- package/src/setup.ts +44 -6
- package/vitest.config.ts +5 -1
- package/api/.redocly.lint-ignore.yaml +0 -22
- package/api/asyncapi.yaml +0 -834
- package/api/grpc/openwop.proto +0 -251
- package/api/openapi.yaml +0 -2693
- package/api/redocly.yaml +0 -23
- package/schemas/README.md +0 -144
- package/schemas/a2a-task-state.schema.json +0 -78
- package/schemas/a2ui-surface-delta-frame.schema.json +0 -48
- package/schemas/agent-deployment-transition.schema.json +0 -49
- package/schemas/agent-deployment.schema.json +0 -54
- package/schemas/agent-eval-suite.schema.json +0 -140
- package/schemas/agent-inventory-response.schema.json +0 -115
- package/schemas/agent-manifest.schema.json +0 -156
- package/schemas/agent-org-chart.schema.json +0 -82
- package/schemas/agent-ref.schema.json +0 -63
- package/schemas/agent-roster-entry.schema.json +0 -81
- package/schemas/agent-roster-response.schema.json +0 -21
- package/schemas/ai-envelope.schema.json +0 -134
- package/schemas/annotation-create.schema.json +0 -37
- package/schemas/annotation.schema.json +0 -56
- package/schemas/artifact-type-pack-manifest.schema.json +0 -244
- package/schemas/audit-verify-result.schema.json +0 -90
- package/schemas/budget-policy.schema.json +0 -18
- package/schemas/capabilities.schema.json +0 -4160
- package/schemas/certification-bundle-v2.schema.json +0 -118
- package/schemas/channel-presence-payload.schema.json +0 -41
- package/schemas/channel-written-payload.schema.json +0 -55
- package/schemas/chat-card-pack-manifest.schema.json +0 -253
- package/schemas/compact-tool-descriptor.schema.json +0 -51
- package/schemas/compensation-policy.schema.json +0 -64
- package/schemas/conformance-certification-bundle.schema.json +0 -86
- package/schemas/connection-pack-manifest.schema.json +0 -353
- package/schemas/conversation-event.schema.json +0 -168
- package/schemas/conversation-turn.schema.json +0 -117
- package/schemas/core-conformance-mock-agent-config.schema.json +0 -152
- package/schemas/credential-provenance.schema.json +0 -18
- package/schemas/credential-reference.schema.json +0 -21
- package/schemas/debug-bundle.schema.json +0 -196
- package/schemas/dispatch-config.schema.json +0 -112
- package/schemas/envelopes/clarification.request.schema.json +0 -52
- package/schemas/envelopes/error.schema.json +0 -30
- package/schemas/envelopes/media.audio.schema.json +0 -38
- package/schemas/envelopes/media.file.schema.json +0 -37
- package/schemas/envelopes/media.image.schema.json +0 -33
- package/schemas/envelopes/schema.request.schema.json +0 -26
- package/schemas/envelopes/schema.response.schema.json +0 -22
- package/schemas/envelopes/ui.a2ui-surface.schema.json +0 -154
- package/schemas/error-envelope.schema.json +0 -25
- package/schemas/eval-summary.schema.json +0 -92
- package/schemas/export-bundle.schema.json +0 -66
- package/schemas/form-content-pack-manifest.schema.json +0 -243
- package/schemas/frontend-plugin-manifest.schema.json +0 -109
- package/schemas/goal.schema.json +0 -104
- package/schemas/heartbeat-evaluated.schema.json +0 -14
- package/schemas/heartbeat-state-changed.schema.json +0 -14
- package/schemas/localized-content-language-settings.schema.json +0 -26
- package/schemas/localized-content-page-response.schema.json +0 -60
- package/schemas/localized-content-page.schema.json +0 -62
- package/schemas/localized-content-section.schema.json +0 -51
- package/schemas/memory-entry.schema.json +0 -36
- package/schemas/memory-list-options.schema.json +0 -37
- package/schemas/node-pack-manifest.schema.json +0 -654
- package/schemas/orchestrator-decision.schema.json +0 -90
- package/schemas/org-chart-responsibility-view.schema.json +0 -26
- package/schemas/pack-lockfile.schema.json +0 -108
- package/schemas/prompt-kind.schema.json +0 -8
- package/schemas/prompt-pack-manifest.schema.json +0 -117
- package/schemas/prompt-ref.schema.json +0 -40
- package/schemas/prompt-template.schema.json +0 -149
- package/schemas/proposal.schema.json +0 -84
- package/schemas/registry-version-manifest.schema.json +0 -208
- package/schemas/residency.schema.json +0 -16
- package/schemas/run-ancestry-response.schema.json +0 -54
- package/schemas/run-diff-response.schema.json +0 -64
- package/schemas/run-event-payloads.schema.json +0 -4452
- package/schemas/run-event.schema.json +0 -211
- package/schemas/run-options.schema.json +0 -80
- package/schemas/run-orchestrator-decided-event.schema.json +0 -20
- package/schemas/run-snapshot.schema.json +0 -148
- package/schemas/security-advisory.schema.json +0 -109
- package/schemas/self-hosted-runner-dispatch-frame.schema.json +0 -79
- package/schemas/self-hosted-runner-registration.schema.json +0 -53
- package/schemas/self-hosted-runner-result-frame.schema.json +0 -38
- package/schemas/subject.schema.json +0 -56
- package/schemas/suspend-request.schema.json +0 -207
- package/schemas/tool-descriptor.schema.json +0 -63
- package/schemas/trigger-event.schema.json +0 -222
- package/schemas/trigger-subscription-registration.schema.json +0 -67
- package/schemas/trigger-subscription.schema.json +0 -26
- package/schemas/ui-plugin-message.schema.json +0 -90
- package/schemas/workflow-chain-pack-manifest.schema.json +0 -615
- package/schemas/workflow-definition.schema.json +0 -850
- package/schemas/workload-identity.schema.json +0 -79
- package/schemas/workspace-file-create.schema.json +0 -20
- package/schemas/workspace-file.schema.json +0 -39
|
@@ -34,6 +34,8 @@ import Ajv2020 from 'ajv/dist/2020.js';
|
|
|
34
34
|
import addFormats from 'ajv-formats';
|
|
35
35
|
import type { ErrorObject } from 'ajv';
|
|
36
36
|
import { SCHEMAS_DIR, V1_DIR } from '../lib/paths.js';
|
|
37
|
+
import { req } from '../lib/requirement-ids.js';
|
|
38
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
37
39
|
|
|
38
40
|
const SCHEMA_PATH = join(SCHEMAS_DIR, 'workflow-chain-pack-manifest.schema.json');
|
|
39
41
|
// In-repo example pack — proves the schema validates a non-trivial
|
|
@@ -100,7 +102,7 @@ describe('category: workflow-chain-pack manifest validation', () => {
|
|
|
100
102
|
.join('\n');
|
|
101
103
|
expect(
|
|
102
104
|
ok,
|
|
103
|
-
`Positive sample MUST validate against workflow-chain-pack-manifest.schema.json — got:\n${errs}
|
|
105
|
+
req('openwop.it.workflow-chain-pack-manifest-validation.positive-a-valid-workflow-chain-pack-manifest-validates-cleanly', 'workflow-chain-packs.md', `Positive sample MUST validate against workflow-chain-pack-manifest.schema.json — got:\n${errs}`),
|
|
104
106
|
).toBe(true);
|
|
105
107
|
});
|
|
106
108
|
|
|
@@ -134,7 +136,7 @@ describe('category: workflow-chain-pack manifest validation', () => {
|
|
|
134
136
|
};
|
|
135
137
|
const ok = validate(conditionalChain);
|
|
136
138
|
const errs = (validate.errors ?? []).map((e: ErrorObject) => `${e.instancePath || '/'}: ${e.message}`).join('\n');
|
|
137
|
-
expect(ok, `Object-shaped edge condition MUST validate — got:\n${errs}`).toBe(true);
|
|
139
|
+
expect(ok, req('openwop.it.workflow-chain-pack-manifest-validation.positive-a-fragmentedge-condition-takes-the-top-level-edgecondition-shape-rfc-00', 'RFC 0013', `Object-shaped edge condition MUST validate — got:\n${errs}`)).toBe(true);
|
|
138
140
|
});
|
|
139
141
|
|
|
140
142
|
it('negative: a bare-string FragmentEdge condition is rejected (the pre-2026-07-03 shape)', () => {
|
|
@@ -160,7 +162,7 @@ describe('category: workflow-chain-pack manifest validation', () => {
|
|
|
160
162
|
},
|
|
161
163
|
],
|
|
162
164
|
};
|
|
163
|
-
expect(validate(stringCondition), 'A string edge condition MUST NOT validate under the corrected schema').toBe(false);
|
|
165
|
+
expect(validate(stringCondition), req('openwop.it.workflow-chain-pack-manifest-validation.negative-a-bare-string-fragmentedge-condition-is-rejected-the-pre-2026-07-03-sha', 'workflow-chain-packs.md', 'A string edge condition MUST NOT validate under the corrected schema')).toBe(false);
|
|
164
166
|
});
|
|
165
167
|
|
|
166
168
|
it('negative: manifest mixing chains[] AND nodes[] is rejected (pack_kind_invalid)', () => {
|
|
@@ -192,14 +194,14 @@ describe('category: workflow-chain-pack manifest validation', () => {
|
|
|
192
194
|
const ok = validate(manifest);
|
|
193
195
|
expect(
|
|
194
196
|
ok,
|
|
195
|
-
'Manifest with both nodes[] and chains[] MUST fail workflow-chain schema validation (pack_kind_invalid at the registry surface).',
|
|
197
|
+
req('openwop.it.workflow-chain-pack-manifest-validation.negative-manifest-mixing-chains-and-nodes-is-rejected-pack-kind-invalid', 'workflow-chain-packs.md', 'Manifest with both nodes[] and chains[] MUST fail workflow-chain schema validation (pack_kind_invalid at the registry surface).'),
|
|
196
198
|
).toBe(false);
|
|
197
199
|
const hasAdditionalPropertiesErr = (validate.errors ?? []).some(
|
|
198
200
|
(e: ErrorObject) => e.keyword === 'additionalProperties',
|
|
199
201
|
);
|
|
200
202
|
expect(
|
|
201
203
|
hasAdditionalPropertiesErr,
|
|
202
|
-
'Expected an `additionalProperties` violation flagging the unexpected `nodes` field.',
|
|
204
|
+
req('openwop.it.workflow-chain-pack-manifest-validation.negative-manifest-mixing-chains-and-nodes-is-rejected-pack-kind-invalid', 'workflow-chain-packs.md', 'Expected an `additionalProperties` violation flagging the unexpected `nodes` field.'),
|
|
203
205
|
).toBe(true);
|
|
204
206
|
});
|
|
205
207
|
|
|
@@ -228,7 +230,7 @@ describe('category: workflow-chain-pack manifest validation', () => {
|
|
|
228
230
|
const ok = validate(manifest);
|
|
229
231
|
expect(
|
|
230
232
|
ok,
|
|
231
|
-
'Manifest with invalid chainId MUST fail workflow-chain schema validation.',
|
|
233
|
+
req('openwop.it.workflow-chain-pack-manifest-validation.negative-chain-entry-with-invalid-chainid-is-rejected-pattern-violation', 'workflow-chain-packs.md', 'Manifest with invalid chainId MUST fail workflow-chain schema validation.'),
|
|
232
234
|
).toBe(false);
|
|
233
235
|
const hasPatternErr = (validate.errors ?? []).some(
|
|
234
236
|
(e: ErrorObject) =>
|
|
@@ -236,14 +238,14 @@ describe('category: workflow-chain-pack manifest validation', () => {
|
|
|
236
238
|
);
|
|
237
239
|
expect(
|
|
238
240
|
hasPatternErr,
|
|
239
|
-
'Expected a `pattern` violation on the chains[].chainId field.',
|
|
241
|
+
req('openwop.it.workflow-chain-pack-manifest-validation.negative-chain-entry-with-invalid-chainid-is-rejected-pattern-violation', 'workflow-chain-packs.md', 'Expected a `pattern` violation on the chains[].chainId field.'),
|
|
240
242
|
).toBe(true);
|
|
241
243
|
});
|
|
242
244
|
|
|
243
245
|
it('positive: the in-repo example pack at examples/packs/workflow-chain-sample/ validates against the schema', () => {
|
|
244
246
|
if (!EXAMPLE_PACK_PATH || !existsSync(EXAMPLE_PACK_PATH)) {
|
|
245
247
|
// Published-tarball layout doesn't ship examples/; skip cleanly.
|
|
246
|
-
return;
|
|
248
|
+
return softSkip('blocked', 'precondition not met — `!EXAMPLE_PACK_PATH || !existsSync(EXAMPLE_PACK_PATH)` returned early (Published-tarball layout doesn\'t ship examples/; skip cleanly.) (seam, prior step, or fixture unavailable)');
|
|
247
249
|
}
|
|
248
250
|
const manifest = JSON.parse(readFileSync(EXAMPLE_PACK_PATH, 'utf8'));
|
|
249
251
|
const ok = validate(manifest);
|
|
@@ -252,15 +254,15 @@ describe('category: workflow-chain-pack manifest validation', () => {
|
|
|
252
254
|
.join('\n');
|
|
253
255
|
expect(
|
|
254
256
|
ok,
|
|
255
|
-
`examples/packs/workflow-chain-sample/pack.json MUST validate against workflow-chain-pack-manifest.schema.json (closes RFC 0013 Phase 4 in-tree path). Errors:\n${errs}
|
|
257
|
+
req('openwop.it.workflow-chain-pack-manifest-validation.positive-the-in-repo-example-pack-at-examples-packs-workflow-chain-sample-valida', 'workflow-chain-packs.md', `examples/packs/workflow-chain-sample/pack.json MUST validate against workflow-chain-pack-manifest.schema.json (closes RFC 0013 Phase 4 in-tree path). Errors:\n${errs}`),
|
|
256
258
|
).toBe(true);
|
|
257
259
|
// Spot-check the structural claims the example README makes:
|
|
258
|
-
expect(manifest.kind, 'example pack MUST declare kind: "workflow-chain"').toBe(
|
|
260
|
+
expect(manifest.kind, req('openwop.it.workflow-chain-pack-manifest-validation.positive-the-in-repo-example-pack-at-examples-packs-workflow-chain-sample-valida', 'workflow-chain-packs.md', 'example pack MUST declare kind: "workflow-chain"')).toBe(
|
|
259
261
|
'workflow-chain',
|
|
260
262
|
);
|
|
261
263
|
expect(
|
|
262
264
|
Array.isArray(manifest.chains) && manifest.chains.length === 2,
|
|
263
|
-
'example pack MUST ship exactly 2 chains (1-node + 2-node shapes) per its README contract',
|
|
265
|
+
req('openwop.it.workflow-chain-pack-manifest-validation.positive-the-in-repo-example-pack-at-examples-packs-workflow-chain-sample-valida', 'workflow-chain-packs.md', 'example pack MUST ship exactly 2 chains (1-node + 2-node shapes) per its README contract'),
|
|
264
266
|
).toBe(true);
|
|
265
267
|
});
|
|
266
268
|
|
|
@@ -285,7 +287,7 @@ describe('category: workflow-chain-pack manifest validation', () => {
|
|
|
285
287
|
const ok = validate(manifest);
|
|
286
288
|
expect(
|
|
287
289
|
ok,
|
|
288
|
-
'Manifest without kind: "workflow-chain" MUST fail this schema (the other path is node-pack-manifest.schema.json).',
|
|
290
|
+
req('openwop.it.workflow-chain-pack-manifest-validation.negative-omitting-kind-field-rejects-kind-is-required', 'workflow-chain-packs.md', 'Manifest without kind: "workflow-chain" MUST fail this schema (the other path is node-pack-manifest.schema.json).'),
|
|
289
291
|
).toBe(false);
|
|
290
292
|
});
|
|
291
293
|
});
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
import { describe, it, expect } from 'vitest';
|
|
32
32
|
import { generateKeyPairSync, sign, verify } from 'node:crypto';
|
|
33
|
+
import { req } from '../lib/requirement-ids.js';
|
|
33
34
|
|
|
34
35
|
/** Canonical workflow-chain pack manifest used as the signing target.
|
|
35
36
|
* Mirrors the spec doc's Positive example, kept tight so the test
|
|
@@ -76,7 +77,7 @@ describe('category: workflow-chain pack signature — Ed25519 verification reuse
|
|
|
76
77
|
const ok = verify(null, manifest, publicKey, signature);
|
|
77
78
|
expect(
|
|
78
79
|
ok,
|
|
79
|
-
'Per workflow-chain-packs.md §"Expansion semantics" step 2: the signature recipe is IDENTICAL to node-packs (Ed25519 over pack.json bytes). A valid pair MUST verify with the canonical recipe.',
|
|
80
|
+
req('openwop.it.workflow-chain-pack-signature-verification.valid-manifest-valid-signature-must-verify-positive-path', 'workflow-chain-packs.md', 'Per workflow-chain-packs.md §"Expansion semantics" step 2: the signature recipe is IDENTICAL to node-packs (Ed25519 over pack.json bytes). A valid pair MUST verify with the canonical recipe.'),
|
|
80
81
|
).toBe(true);
|
|
81
82
|
});
|
|
82
83
|
|
|
@@ -92,7 +93,7 @@ describe('category: workflow-chain pack signature — Ed25519 verification reuse
|
|
|
92
93
|
const ok = verify(null, tampered, publicKey, signature);
|
|
93
94
|
expect(
|
|
94
95
|
ok,
|
|
95
|
-
'Per node-packs.md §Signing (reused unchanged for chain packs): Ed25519 verification MUST detect any byte-level tamper to the signed payload.',
|
|
96
|
+
req('openwop.it.workflow-chain-pack-signature-verification.tampered-manifest-must-fail-verification-sha-level-tamper-detection', 'workflow-chain-packs.md', 'Per node-packs.md §Signing (reused unchanged for chain packs): Ed25519 verification MUST detect any byte-level tamper to the signed payload.'),
|
|
96
97
|
).toBe(false);
|
|
97
98
|
});
|
|
98
99
|
|
|
@@ -108,7 +109,7 @@ describe('category: workflow-chain pack signature — Ed25519 verification reuse
|
|
|
108
109
|
const ok = verify(null, manifest, attackerPubKey, signature);
|
|
109
110
|
expect(
|
|
110
111
|
ok,
|
|
111
|
-
'Ed25519 verification MUST fail when the public key doesn\'t match the private key that produced the signature — the spec\'s trust model depends on this property.',
|
|
112
|
+
req('openwop.it.workflow-chain-pack-signature-verification.wrong-key-signature-must-fail-verification', 'workflow-chain-packs.md', 'Ed25519 verification MUST fail when the public key doesn\'t match the private key that produced the signature — the spec\'s trust model depends on this property.'),
|
|
112
113
|
).toBe(false);
|
|
113
114
|
});
|
|
114
115
|
|
|
@@ -130,7 +131,7 @@ describe('category: workflow-chain pack signature — Ed25519 verification reuse
|
|
|
130
131
|
};
|
|
131
132
|
expect(
|
|
132
133
|
signedManifest.signing.method,
|
|
133
|
-
'workflow-chain packs MUST accept the same signing block keys as node packs (publicKeyRef + signatureRef + method) per spec design.',
|
|
134
|
+
req('openwop.it.workflow-chain-pack-signature-verification.chain-pack-manifests-carry-the-same-signing-block-shape-as-node-packs', 'workflow-chain-packs.md', 'workflow-chain packs MUST accept the same signing block keys as node packs (publicKeyRef + signatureRef + method) per spec design.'),
|
|
134
135
|
).toBe('manual');
|
|
135
136
|
expect(signedManifest.signing.publicKeyRef).toBe('keys/2026-05.pem');
|
|
136
137
|
expect(signedManifest.signing.signatureRef).toBe('pack.json.sig');
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
ChainUnresolvableTypeIdError,
|
|
37
37
|
type WorkflowChain,
|
|
38
38
|
} from '../lib/workflow-chain-expansion.js';
|
|
39
|
+
import { req } from '../lib/requirement-ids.js';
|
|
39
40
|
|
|
40
41
|
const CHAIN_WITH_UNKNOWN_TYPEID: WorkflowChain = {
|
|
41
42
|
chainId: 'vendor.acme.someChain',
|
|
@@ -66,7 +67,7 @@ describe('category: workflow-chain expansion — unresolvable typeId rejection',
|
|
|
66
67
|
params: {},
|
|
67
68
|
isTypeIdResolvable: isKnown,
|
|
68
69
|
}),
|
|
69
|
-
'Per spec §"Expansion semantics" step 3: hosts MUST reject expansion with `chain_unresolvable_typeid` when any fragment node\'s typeId can\'t be resolved by the destination host.',
|
|
70
|
+
req('openwop.it.workflow-chain-unresolvable-typeid.throws-chainunresolvabletypeiderror-when-chain-references-an-unknown-typeid', 'workflow-chain-packs.md', 'Per spec §"Expansion semantics" step 3: hosts MUST reject expansion with `chain_unresolvable_typeid` when any fragment node\'s typeId can\'t be resolved by the destination host.'),
|
|
70
71
|
).toThrow(ChainUnresolvableTypeIdError);
|
|
71
72
|
});
|
|
72
73
|
|
|
@@ -79,21 +80,21 @@ describe('category: workflow-chain expansion — unresolvable typeId rejection',
|
|
|
79
80
|
});
|
|
80
81
|
expect.fail('expandChain MUST have thrown ChainUnresolvableTypeIdError');
|
|
81
82
|
} catch (err) {
|
|
82
|
-
expect(err, 'expected ChainUnresolvableTypeIdError').toBeInstanceOf(
|
|
83
|
+
expect(err, req('openwop.it.workflow-chain-unresolvable-typeid.error-carries-the-offending-typeid-and-the-chainid-in-details', 'workflow-chain-packs.md', 'expected ChainUnresolvableTypeIdError')).toBeInstanceOf(
|
|
83
84
|
ChainUnresolvableTypeIdError,
|
|
84
85
|
);
|
|
85
86
|
const e = err as ChainUnresolvableTypeIdError;
|
|
86
87
|
expect(
|
|
87
88
|
e.code,
|
|
88
|
-
'Per spec §"Error codes": the wire-level error code MUST be `chain_unresolvable_typeid`.',
|
|
89
|
+
req('openwop.it.workflow-chain-unresolvable-typeid.error-carries-the-offending-typeid-and-the-chainid-in-details', 'workflow-chain-packs.md', 'Per spec §"Error codes": the wire-level error code MUST be `chain_unresolvable_typeid`.'),
|
|
89
90
|
).toBe('chain_unresolvable_typeid');
|
|
90
91
|
expect(
|
|
91
92
|
e.typeId,
|
|
92
|
-
'The error MUST surface the offending typeId so the host editor can render an actionable diagnostic.',
|
|
93
|
+
req('openwop.it.workflow-chain-unresolvable-typeid.error-carries-the-offending-typeid-and-the-chainid-in-details', 'workflow-chain-packs.md', 'The error MUST surface the offending typeId so the host editor can render an actionable diagnostic.'),
|
|
93
94
|
).toBe('made.up.foo');
|
|
94
95
|
expect(
|
|
95
96
|
e.chainId,
|
|
96
|
-
'The error MUST surface the chainId so the host editor knows which chain-pack tile triggered the rejection.',
|
|
97
|
+
req('openwop.it.workflow-chain-unresolvable-typeid.error-carries-the-offending-typeid-and-the-chainid-in-details', 'workflow-chain-packs.md', 'The error MUST surface the chainId so the host editor knows which chain-pack tile triggered the rejection.'),
|
|
97
98
|
).toBe('vendor.acme.someChain');
|
|
98
99
|
}
|
|
99
100
|
});
|
|
@@ -115,7 +116,7 @@ describe('category: workflow-chain expansion — unresolvable typeId rejection',
|
|
|
115
116
|
}
|
|
116
117
|
expect(
|
|
117
118
|
threw,
|
|
118
|
-
'Expansion MUST throw before producing any output — host editors rely on the "no partial expansion" guarantee to keep parent workflows clean on rejection.',
|
|
119
|
+
req('openwop.it.workflow-chain-unresolvable-typeid.rejection-happens-before-any-id-rewriting-or-placeholder-substitution', 'workflow-chain-packs.md', 'Expansion MUST throw before producing any output — host editors rely on the "no partial expansion" guarantee to keep parent workflows clean on rejection.'),
|
|
119
120
|
).toBe(true);
|
|
120
121
|
});
|
|
121
122
|
|
|
@@ -134,7 +135,7 @@ describe('category: workflow-chain expansion — unresolvable typeId rejection',
|
|
|
134
135
|
params: {},
|
|
135
136
|
isTypeIdResolvable: isKnown,
|
|
136
137
|
}),
|
|
137
|
-
'Sanity: when every typeId resolves, expansion MUST proceed without throwing.',
|
|
138
|
+
req('openwop.it.workflow-chain-unresolvable-typeid.accepts-the-chain-when-every-typeid-is-resolvable', 'workflow-chain-packs.md', 'Sanity: when every typeId resolves, expansion MUST proceed without throwing.'),
|
|
138
139
|
).not.toThrow();
|
|
139
140
|
});
|
|
140
141
|
|
|
@@ -163,7 +164,7 @@ describe('category: workflow-chain expansion — unresolvable typeId rejection',
|
|
|
163
164
|
const e = err as ChainUnresolvableTypeIdError;
|
|
164
165
|
expect(
|
|
165
166
|
e.typeId,
|
|
166
|
-
'When multiple nodes have unknown typeIds, the throw MUST identify the first unknown one encountered in declaration order.',
|
|
167
|
+
req('openwop.it.workflow-chain-unresolvable-typeid.throws-on-the-first-unknown-typeid-encountered-fail-fast', 'workflow-chain-packs.md', 'When multiple nodes have unknown typeIds, the throw MUST identify the first unknown one encountered in declaration order.'),
|
|
167
168
|
).toBe('made.up.foo');
|
|
168
169
|
}
|
|
169
170
|
});
|
|
@@ -23,6 +23,7 @@ import addFormats from 'ajv-formats';
|
|
|
23
23
|
import { readFileSync, readdirSync } from 'node:fs';
|
|
24
24
|
import { join } from 'node:path';
|
|
25
25
|
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
26
|
+
import { req } from '../lib/requirement-ids.js';
|
|
26
27
|
|
|
27
28
|
function compileWorkflowDefinition(): ReturnType<Ajv2020['compile']> {
|
|
28
29
|
const ajv = new Ajv2020({ strict: false, allErrors: true });
|
|
@@ -78,7 +79,7 @@ describe('workflow-primary-output-annotation: outputRole shape (RFC 0065)', () =
|
|
|
78
79
|
baseNode('b'),
|
|
79
80
|
]);
|
|
80
81
|
const ok = validate(def);
|
|
81
|
-
expect(ok, JSON.stringify(validate.errors, null, 2)).toBe(true);
|
|
82
|
+
expect(ok, req('openwop.it.workflow-primary-output-annotation.accepts-a-workflow-with-one-node-declaring-outputrole-primary', 'RFC 0065', JSON.stringify(validate.errors, null, 2))).toBe(true);
|
|
82
83
|
});
|
|
83
84
|
|
|
84
85
|
it('accepts primary AND secondary annotations on different nodes', () => {
|
|
@@ -88,7 +89,7 @@ describe('workflow-primary-output-annotation: outputRole shape (RFC 0065)', () =
|
|
|
88
89
|
baseNode('c'),
|
|
89
90
|
]);
|
|
90
91
|
const ok = validate(def);
|
|
91
|
-
expect(ok, JSON.stringify(validate.errors, null, 2)).toBe(true);
|
|
92
|
+
expect(ok, req('openwop.it.workflow-primary-output-annotation.accepts-primary-and-secondary-annotations-on-different-nodes', 'RFC 0065', JSON.stringify(validate.errors, null, 2))).toBe(true);
|
|
92
93
|
});
|
|
93
94
|
|
|
94
95
|
it('accepts a workflow with the field absent (additive promise)', () => {
|
|
@@ -97,7 +98,7 @@ describe('workflow-primary-output-annotation: outputRole shape (RFC 0065)', () =
|
|
|
97
98
|
baseNode('b'),
|
|
98
99
|
]);
|
|
99
100
|
const ok = validate(def);
|
|
100
|
-
expect(ok, JSON.stringify(validate.errors, null, 2)).toBe(true);
|
|
101
|
+
expect(ok, req('openwop.it.workflow-primary-output-annotation.accepts-a-workflow-with-the-field-absent-additive-promise', 'RFC 0065', JSON.stringify(validate.errors, null, 2))).toBe(true);
|
|
101
102
|
});
|
|
102
103
|
|
|
103
104
|
it('rejects an unknown outputRole enum value', () => {
|
|
@@ -105,7 +106,7 @@ describe('workflow-primary-output-annotation: outputRole shape (RFC 0065)', () =
|
|
|
105
106
|
baseNode('a', { outputRole: 'tertiary' }),
|
|
106
107
|
]);
|
|
107
108
|
const ok = validate(def);
|
|
108
|
-
expect(ok).toBe(false);
|
|
109
|
+
expect(ok, req('openwop.it.workflow-primary-output-annotation.rejects-an-unknown-outputrole-enum-value', 'RFC 0065', 'rejects an unknown outputRole enum value')).toBe(false);
|
|
109
110
|
expect(validate.errors).toBeTruthy();
|
|
110
111
|
});
|
|
111
112
|
|
|
@@ -114,7 +115,7 @@ describe('workflow-primary-output-annotation: outputRole shape (RFC 0065)', () =
|
|
|
114
115
|
baseNode('a', { outputRole: 1 }),
|
|
115
116
|
]);
|
|
116
117
|
const ok = validate(def);
|
|
117
|
-
expect(ok).toBe(false);
|
|
118
|
+
expect(ok, req('openwop.it.workflow-primary-output-annotation.rejects-outputrole-set-to-a-non-string', 'RFC 0065', 'rejects outputRole set to a non-string')).toBe(false);
|
|
118
119
|
});
|
|
119
120
|
|
|
120
121
|
it('permits multiple nodes declaring outputRole="primary" (tooling decides)', () => {
|
|
@@ -127,6 +128,6 @@ describe('workflow-primary-output-annotation: outputRole shape (RFC 0065)', () =
|
|
|
127
128
|
baseNode('b', { outputRole: 'primary' }),
|
|
128
129
|
]);
|
|
129
130
|
const ok = validate(def);
|
|
130
|
-
expect(ok, JSON.stringify(validate.errors, null, 2)).toBe(true);
|
|
131
|
+
expect(ok, req('openwop.it.workflow-primary-output-annotation.permits-multiple-nodes-declaring-outputrole-primary-tooling-decides', 'RFC 0065', JSON.stringify(validate.errors, null, 2))).toBe(true);
|
|
131
132
|
});
|
|
132
133
|
});
|
|
@@ -48,8 +48,8 @@ import { behaviorGate } from '../lib/behavior-gate.js';
|
|
|
48
48
|
import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
|
|
49
49
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
50
50
|
import { driver } from '../lib/driver.js';
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
52
|
+
import { req } from '../lib/requirement-ids.js';
|
|
53
53
|
const WORKFLOW_DEF = join(SCHEMAS_DIR, 'workflow-definition.schema.json');
|
|
54
54
|
// S38 (2026-08-17): `spec/` is NOT in the published package (`files`), so a path built
|
|
55
55
|
// from SCHEMAS_DIR/../spec ENOENTs for every npm consumer — five always-on legs reddened
|
|
@@ -83,11 +83,11 @@ describe('workflow-variable-format §A: corpus (RFC 0136, always-on)', () => {
|
|
|
83
83
|
const wv = defs.WorkflowVariable;
|
|
84
84
|
const props = wv.properties as Record<string, Record<string, unknown>>;
|
|
85
85
|
|
|
86
|
-
expect(props.format,
|
|
87
|
-
expect(props.format.type,
|
|
86
|
+
expect(props.format, req('openwop.it.workflow-variable-format.a1-workflowvariable-declares-format-as-an-advisory-string', '§WorkflowVariable', '`format` is declared')).toBeDefined();
|
|
87
|
+
expect(props.format.type, req('openwop.it.workflow-variable-format.a1-workflowvariable-declares-format-as-an-advisory-string', '§WorkflowVariable', '`format` is a string')).toBe('string');
|
|
88
88
|
expect(
|
|
89
89
|
(wv.required as string[] | undefined)?.includes('format') ?? false,
|
|
90
|
-
|
|
90
|
+
req('openwop.it.workflow-variable-format.a1-workflowvariable-declares-format-as-an-advisory-string', '§WorkflowVariable', '`format` is OPTIONAL — additive per COMPATIBILITY.md §2.1'),
|
|
91
91
|
).toBe(false);
|
|
92
92
|
});
|
|
93
93
|
|
|
@@ -98,7 +98,7 @@ describe('workflow-variable-format §A: corpus (RFC 0136, always-on)', () => {
|
|
|
98
98
|
const recognised = { name: 'recipientEmail', type: 'string', format: 'email' };
|
|
99
99
|
expect(
|
|
100
100
|
validate(recognised),
|
|
101
|
-
|
|
101
|
+
req('openwop.it.workflow-variable-format.a2-an-unrecognised-format-validates-requirement-2-unknown-plain-text-never-an-er', '§WorkflowVariable', `recognised format validates: ${JSON.stringify(validate.errors)}`),
|
|
102
102
|
).toBe(true);
|
|
103
103
|
|
|
104
104
|
// OUTSIDE the table. This is the whole point: the property must NOT be an enum, or a
|
|
@@ -107,7 +107,7 @@ describe('workflow-variable-format §A: corpus (RFC 0136, always-on)', () => {
|
|
|
107
107
|
const unrecognised = { name: 'ipAddress', type: 'string', format: 'vendor.acme.ipv4-or-hostname' };
|
|
108
108
|
expect(
|
|
109
109
|
validate(unrecognised),
|
|
110
|
-
|
|
110
|
+
req('openwop.it.workflow-variable-format.a2-an-unrecognised-format-validates-requirement-2-unknown-plain-text-never-an-er', '§WorkflowVariable', `unrecognised format validates (RFC 0136 req 2): ${JSON.stringify(validate.errors)}`),
|
|
111
111
|
).toBe(true);
|
|
112
112
|
});
|
|
113
113
|
|
|
@@ -116,21 +116,21 @@ describe('workflow-variable-format §A: corpus (RFC 0136, always-on)', () => {
|
|
|
116
116
|
const both = { name: 'notifyAddress', type: 'string', format: 'email', sensitive: true };
|
|
117
117
|
expect(
|
|
118
118
|
validate(both),
|
|
119
|
-
|
|
119
|
+
req('openwop.it.workflow-variable-format.a3-format-and-sensitive-compose-on-one-variable-no-interaction', '§WorkflowVariable', `format + sensitive compose: ${JSON.stringify(validate.errors)}`),
|
|
120
120
|
).toBe(true);
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
it.skipIf(CHAIN_DOC === null)('A4 — chain-pack spec documents deferred-mode `format` propagation as mode-scoped', () => {
|
|
124
124
|
const doc = readFileSync(CHAIN_DOC as string, 'utf8');
|
|
125
125
|
const step1 = doc.slice(doc.indexOf('Materialize parameters as variables'));
|
|
126
|
-
expect(step1.length > 0,
|
|
126
|
+
expect(step1.length > 0, req('openwop.it.workflow-variable-format.a4-chain-pack-spec-documents-deferred-mode-format-propagation-as-mode-scoped', '§Deferred-parameter expansion', 'step 1 present')).toBe(true);
|
|
127
127
|
expect(
|
|
128
128
|
/`format`/.test(step1.slice(0, 1400)),
|
|
129
|
-
|
|
129
|
+
req('openwop.it.workflow-variable-format.a4-chain-pack-spec-documents-deferred-mode-format-propagation-as-mode-scoped', '§Deferred-parameter expansion', 'step 1 copy-list names `format`'),
|
|
130
130
|
).toBe(true);
|
|
131
131
|
expect(
|
|
132
132
|
/this mode only|mode only/i.test(step1.slice(0, 1400)),
|
|
133
|
-
|
|
133
|
+
req('openwop.it.workflow-variable-format.a4-chain-pack-spec-documents-deferred-mode-format-propagation-as-mode-scoped', '§Deferred-parameter expansion', 'the `format` MUST is scoped to deferred mode, not universal'),
|
|
134
134
|
).toBe(true);
|
|
135
135
|
});
|
|
136
136
|
|
|
@@ -138,7 +138,7 @@ describe('workflow-variable-format §A: corpus (RFC 0136, always-on)', () => {
|
|
|
138
138
|
const rfc = readFileSync(RFC_DOC as string, 'utf8');
|
|
139
139
|
expect(
|
|
140
140
|
/configurableSchema/.test(rfc),
|
|
141
|
-
|
|
141
|
+
req('openwop.it.workflow-variable-format.a5-the-rfc-forbids-format-participating-in-a-configurable-validation-decision', 'RFC 0136', 'names the configurableSchema propagation path'),
|
|
142
142
|
).toBe(true);
|
|
143
143
|
// The trap: run-options.md §1 makes validating `configurable` against
|
|
144
144
|
// `configurableSchema` a MUST with `validation_error` on failure. A format-asserting
|
|
@@ -147,7 +147,7 @@ describe('workflow-variable-format §A: corpus (RFC 0136, always-on)', () => {
|
|
|
147
147
|
// closes it: annotation permitted, assertion forbidden.
|
|
148
148
|
expect(
|
|
149
149
|
/requirement 3[\s\S]{0,600}(back door|surface-independent)/i.test(rfc),
|
|
150
|
-
|
|
150
|
+
req('openwop.it.workflow-variable-format.a5-the-rfc-forbids-format-participating-in-a-configurable-validation-decision', 'RFC 0136 req 8', 'states requirement 3 is surface-independent'),
|
|
151
151
|
).toBe(true);
|
|
152
152
|
});
|
|
153
153
|
});
|
|
@@ -171,7 +171,7 @@ describe('workflow-variable-format §B: host behaviour (RFC 0136, capability-gat
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
it('B1 — deferred expansion mints `format` onto the WorkflowVariable: string copied (req 7), unknown copied (req 2), non-string omits it (req 1)', async () => {
|
|
174
|
-
if (!(await deferredParamsGateOpen())) return;
|
|
174
|
+
if (!(await deferredParamsGateOpen())) return softSkip('blocked', 'precondition not met — `!(await deferredParamsGateOpen())` returned early (seam, prior step, or fixture unavailable)');
|
|
175
175
|
|
|
176
176
|
// Drive the RFC 0124 deferred-expand seam with a chain whose parameters exercise all
|
|
177
177
|
// three propagation rules in one expansion; assert the minted variables[] carry (or omit)
|
|
@@ -190,15 +190,15 @@ describe('workflow-variable-format §B: host behaviour (RFC 0136, capability-gat
|
|
|
190
190
|
});
|
|
191
191
|
// A host advertising deferredParameters but not yet serving the variables[]-returning
|
|
192
192
|
// seam extension soft-skips (404), as does a host with the seam disabled in this boot.
|
|
193
|
-
if (res.status === 404) return;
|
|
194
|
-
expect(res.status,
|
|
193
|
+
if (res.status === 404) return softSkip('blocked', 'precondition not met — `res.status === 404` returned early (A host advertising deferredParameters but not yet serving the variables[]-returning seam extension soft-skips (404), as does a host with the seam disabled in…');
|
|
194
|
+
expect(res.status, req('openwop.it.workflow-variable-format.b1-deferred-expansion-mints-format-onto-the-workflowvariable-string-copied-req-7', '§Deferred-parameter expansion', 'the deferred-expand seam returns 200')).toBe(200);
|
|
195
195
|
const variables = (res.json as { variables?: Array<{ name: string; type?: string; format?: string }> }).variables;
|
|
196
|
-
if (!Array.isArray(variables)) return; // seam present but not returning variables[] yet — soft-skip
|
|
196
|
+
if (!Array.isArray(variables)) return softSkip('blocked', 'precondition not met — `!Array.isArray(variables)` returned early (seam present but not returning variables[] yet — soft-skip) (seam, prior step, or fixture unavailable)'); // seam present but not returning variables[] yet — soft-skip
|
|
197
197
|
const fmt = (n: string): string | undefined => variables.find((v) => v.name === n)?.format;
|
|
198
198
|
|
|
199
|
-
expect(fmt('email'),
|
|
200
|
-
expect(fmt('note'),
|
|
201
|
-
expect(fmt('count'),
|
|
199
|
+
expect(fmt('email'), req('openwop.it.workflow-variable-format.b1-deferred-expansion-mints-format-onto-the-workflowvariable-string-copied-req-7', '§WorkflowVariable', 'req 7: a string param\'s recognised `format` is minted verbatim')).toBe('email');
|
|
200
|
+
expect(fmt('note'), req('openwop.it.workflow-variable-format.b1-deferred-expansion-mints-format-onto-the-workflowvariable-string-copied-req-7', '§WorkflowVariable', 'req 2: an UNRECOGNISED `format` is minted verbatim, never dropped')).toBe('vendor.acme.freeform');
|
|
201
|
+
expect(fmt('count'), req('openwop.it.workflow-variable-format.b1-deferred-expansion-mints-format-onto-the-workflowvariable-string-copied-req-7', '§WorkflowVariable', 'req 1: a NON-STRING param mints no `format`')).toBeUndefined();
|
|
202
202
|
});
|
|
203
203
|
|
|
204
204
|
const B2_FIXTURE = 'conformance-workflow-variable-format-advisory';
|
|
@@ -210,13 +210,13 @@ describe('workflow-variable-format §B: host behaviour (RFC 0136, capability-gat
|
|
|
210
210
|
// endpoint, since registration is "POST /v1/workflows or equivalent" (capabilities.md)
|
|
211
211
|
// and the create path is not a mandated portable surface. Gate on fixture advertisement:
|
|
212
212
|
// req 3 is universal (not deferred-mode-specific), so it rides the host loading the fixture.
|
|
213
|
-
if (!isFixtureAdvertised(B2_FIXTURE)) return;
|
|
213
|
+
if (!isFixtureAdvertised(B2_FIXTURE)) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!isFixtureAdvertised(B2_FIXTURE)` returned early (Portable pre-registered-workflow pattern (cf. agentPackHandoffSchemaValidation): the fixture `conformance-wor…');
|
|
214
214
|
|
|
215
215
|
const run = await driver.post('/v1/runs', { workflowId: B2_FIXTURE });
|
|
216
|
-
if (run.status === 404) return; // no run surface — soft-skip
|
|
216
|
+
if (run.status === 404) return softSkip('blocked', 'precondition not met — `run.status === 404` returned early (no run surface — soft-skip) (seam, prior step, or fixture unavailable)'); // no run surface — soft-skip
|
|
217
217
|
expect(
|
|
218
218
|
[200, 201],
|
|
219
|
-
|
|
219
|
+
req('openwop.it.workflow-variable-format.b2-a-run-whose-variable-value-does-not-match-its-declared-format-is-accepted-and', 'POST /v1/runs', 'req 3: a run whose variable value violates its advisory `format` is accepted, not rejected'),
|
|
220
220
|
).toContain(run.status);
|
|
221
221
|
const runId = (run.json as { runId: string }).runId;
|
|
222
222
|
|
|
@@ -232,7 +232,7 @@ describe('workflow-variable-format §B: host behaviour (RFC 0136, capability-gat
|
|
|
232
232
|
}
|
|
233
233
|
expect(
|
|
234
234
|
snap?.status,
|
|
235
|
-
|
|
235
|
+
req('openwop.it.workflow-variable-format.b2-a-run-whose-variable-value-does-not-match-its-declared-format-is-accepted-and', '§WorkflowVariable', 'req 3: the run COMPLETES — `format` is advisory, a value mismatch MUST NOT fail the run'),
|
|
236
236
|
).toBe('completed');
|
|
237
237
|
});
|
|
238
238
|
});
|
|
@@ -37,6 +37,8 @@ import { readErrorCode, readRetriable } from '../lib/error-envelope.js';
|
|
|
37
37
|
import { driver } from '../lib/driver.js';
|
|
38
38
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
39
39
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
40
|
+
import { req } from '../lib/requirement-ids.js';
|
|
41
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
40
42
|
|
|
41
43
|
const PROFILE = 'openwop-workload-identity';
|
|
42
44
|
/**
|
|
@@ -88,7 +90,7 @@ describe('RFC 0154 §A — workload identity resolution (capability-gated behavi
|
|
|
88
90
|
const r = await resolve({ identity: { ...IDENTITY, audience: 'openwop-host' } });
|
|
89
91
|
expect(
|
|
90
92
|
r,
|
|
91
|
-
|
|
93
|
+
req('openwop.it.workload-identity-behavior.the-seam-is-wired-or-the-requirement-is-blocked-rather-than-skipped',
|
|
92
94
|
'spec/v1/host-sample-test-seams.md §20',
|
|
93
95
|
'a host advertising `auth.workloadIdentity` MUST expose the resolution seam. §A\'s ' +
|
|
94
96
|
'requirements — verify, bind, resolve-before-authorize, fail closed — are invisible from ' +
|
|
@@ -102,21 +104,21 @@ describe('RFC 0154 §A — workload identity resolution (capability-gated behavi
|
|
|
102
104
|
it('a verified identity resolves to a principal', async () => {
|
|
103
105
|
if (!behaviorGate(PROFILE, (await caps())?.supported === true)) return;
|
|
104
106
|
const r = await resolve({ identity: { ...IDENTITY, audience: 'openwop-host' } });
|
|
105
|
-
if (r === null) return;
|
|
106
|
-
expect(r.status,
|
|
107
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
108
|
+
expect(r.status, req('openwop.it.workload-identity-behavior.a-verified-identity-resolves-to-a-principal', 'RFCS/0154 §A', 'a verified identity resolves')).toBe(200);
|
|
107
109
|
const principalId = (r.json as { principalId?: string }).principalId;
|
|
108
|
-
expect(principalId,
|
|
110
|
+
expect(principalId, req('openwop.it.workload-identity-behavior.a-verified-identity-resolves-to-a-principal', 'RFCS/0154 §A', 'resolution yields an OpenWOP principal')).toBeTruthy();
|
|
109
111
|
});
|
|
110
112
|
|
|
111
113
|
it('an identity for another audience is rejected', async () => {
|
|
112
114
|
if (!behaviorGate(PROFILE, (await caps())?.supported === true)) return;
|
|
113
115
|
const r = await resolve({ identity: { ...IDENTITY, audience: 'some-other-host' }, expectedAudience: 'openwop-host' });
|
|
114
|
-
if (r === null) return;
|
|
116
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
115
117
|
// The load-bearing negative. A host that echoes its input passes every
|
|
116
118
|
// positive assertion; only this distinguishes a verifier from a passthrough.
|
|
117
119
|
expect(
|
|
118
120
|
r.status >= 400,
|
|
119
|
-
|
|
121
|
+
req('openwop.it.workload-identity-behavior.an-identity-for-another-audience-is-rejected',
|
|
120
122
|
'RFCS/0154 §A + RFC 0147 R12',
|
|
121
123
|
'an identity minted for another host, accepted here, is how a credential valid elsewhere ' +
|
|
122
124
|
'becomes a credential valid here — the confused-deputy path this profile prevents',
|
|
@@ -140,10 +142,10 @@ describe('RFC 0154 §A — workload identity resolution (capability-gated behavi
|
|
|
140
142
|
},
|
|
141
143
|
},
|
|
142
144
|
});
|
|
143
|
-
if (r === null) return;
|
|
145
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
144
146
|
expect(
|
|
145
147
|
r.status >= 400,
|
|
146
|
-
|
|
148
|
+
req('openwop.it.workload-identity-behavior.an-expired-delegation-is-rejected',
|
|
147
149
|
'RFCS/0154 §B',
|
|
148
150
|
'a delegation without a live expiry is a standing grant, which is not what delegation means',
|
|
149
151
|
),
|
|
@@ -154,13 +156,13 @@ describe('RFC 0154 §A — workload identity resolution (capability-gated behavi
|
|
|
154
156
|
it('a failure is non-retriable and carries a closed reason code', async () => {
|
|
155
157
|
if (!behaviorGate(PROFILE, (await caps())?.supported === true)) return;
|
|
156
158
|
const r = await resolve({ identity: { scheme: 'spiffe', subject: 'spiffe://example/unknown' } });
|
|
157
|
-
if (r === null || r.status < 400) return;
|
|
159
|
+
if (r === null || r.status < 400) return softSkip('blocked', 'precondition not met — `r === null || r.status < 400` returned early (seam, prior step, or fixture unavailable)');
|
|
158
160
|
// canonical flat envelope: code = `error`, retriable = `details.retriable`
|
|
159
161
|
// (the legacy nested `error.{code,retriable}` the §20 catalog prescribed until
|
|
160
162
|
// 2026-08-16 is tolerated by the helpers for the deprecation window)
|
|
161
163
|
expect(
|
|
162
164
|
readRetriable(r.json),
|
|
163
|
-
|
|
165
|
+
req('openwop.it.workload-identity-behavior.a-failure-is-non-retriable-and-carries-a-closed-reason-code',
|
|
164
166
|
'spec/v1/host-sample-test-seams.md §20',
|
|
165
167
|
'an identity that does not resolve will not resolve on retry. Marking it retriable invites ' +
|
|
166
168
|
'a caller to hammer a failing authorization path.',
|
|
@@ -168,19 +170,19 @@ describe('RFC 0154 §A — workload identity resolution (capability-gated behavi
|
|
|
168
170
|
).toBe(false);
|
|
169
171
|
expect(
|
|
170
172
|
['identity_unverified', 'identity_unresolvable', 'audience_mismatch', 'delegation_expired', 'sender_constraint_missing', 'delegation_chain_too_long', 'delegation_chain_cyclic', 'delegation_scope_amplified'],
|
|
171
|
-
|
|
173
|
+
req('openwop.it.workload-identity-behavior.a-failure-is-non-retriable-and-carries-a-closed-reason-code', 'RFCS/0154 §A', 'failures use a closed reason vocabulary'),
|
|
172
174
|
).toContain(readErrorCode(r.json));
|
|
173
175
|
});
|
|
174
176
|
|
|
175
177
|
it('a resolution response carries no credential material', async () => {
|
|
176
178
|
if (!behaviorGate(PROFILE, (await caps())?.supported === true)) return;
|
|
177
179
|
const r = await resolve({ identity: { ...IDENTITY, audience: 'openwop-host' } });
|
|
178
|
-
if (r === null) return;
|
|
180
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
179
181
|
const serialized = JSON.stringify(r.json ?? {});
|
|
180
182
|
for (const forbidden of ['-----BEGIN', 'Bearer ', 'eyJ']) {
|
|
181
183
|
expect(
|
|
182
184
|
serialized.includes(forbidden),
|
|
183
|
-
|
|
185
|
+
req('openwop.it.workload-identity-behavior.a-resolution-response-carries-no-credential-material',
|
|
184
186
|
'RFCS/0154 §A',
|
|
185
187
|
'a verified identity is a fact about a completed verification, not a container for the ' +
|
|
186
188
|
`material that proved it. Found: ${forbidden}`,
|
|
@@ -36,7 +36,8 @@ import { readErrorCode, readRetriable } from '../lib/error-envelope.js';
|
|
|
36
36
|
import { driver } from '../lib/driver.js';
|
|
37
37
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
38
38
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
39
|
-
import { seamAbsent } from '../lib/soft-skip.js';
|
|
39
|
+
import { seamAbsent, softSkip } from '../lib/soft-skip.js';
|
|
40
|
+
import { req } from '../lib/requirement-ids.js';
|
|
40
41
|
|
|
41
42
|
const DELEGATION_PROFILE = 'openwop-workload-identity-delegation';
|
|
42
43
|
const SEAM = '/v1/host/sample/test/workload-identity/resolve';
|
|
@@ -76,10 +77,10 @@ function hop(n: number, scopes?: readonly string[]): Record<string, unknown> {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
/** A refusal: 4xx, non-retriable, the named closed reason. */
|
|
79
|
-
function expectRefusal(r: { status: number; json: unknown }, reason: string, why: string): void {
|
|
80
|
-
expect(r.status >= 400,
|
|
81
|
-
expect(readErrorCode(r.json),
|
|
82
|
-
expect(readRetriable(r.json),
|
|
80
|
+
function expectRefusal(requirementId: string, r: { status: number; json: unknown }, reason: string, why: string): void {
|
|
81
|
+
expect(r.status >= 400, req(requirementId, 'RFCS/0154 §B', why)).toBe(true);
|
|
82
|
+
expect(readErrorCode(r.json), req(requirementId, 'spec/v1/host-sample-test-seams.md §20', `closed reason \`${reason}\``)).toBe(reason);
|
|
83
|
+
expect(readRetriable(r.json), req(requirementId, 'spec/v1/host-sample-test-seams.md §20', 'a chain the host refuses will be refused on retry')).toBe(false);
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
describe('RFC 0154 §B — delegation chain bounds (capability-gated behavior)', () => {
|
|
@@ -88,13 +89,13 @@ describe('RFC 0154 §B — delegation chain bounds (capability-gated behavior)',
|
|
|
88
89
|
if (!behaviorGate(DELEGATION_PROFILE, caps.supported)) return;
|
|
89
90
|
expect(
|
|
90
91
|
caps.maxChainDepth,
|
|
91
|
-
|
|
92
|
+
req('openwop.it.workload-identity-chain-bounds.a-chain-longer-than-the-advertised-maxchaindepth-is-refused', 'spec/v1/auth.md §Bounds', 'a host advertising delegation MUST advertise a positive integer `maxChainDepth`'),
|
|
92
93
|
).not.toBeNull();
|
|
93
94
|
const depth = caps.maxChainDepth as number;
|
|
94
95
|
const chain = Array.from({ length: depth + 1 }, (_, i) => hop(i + 1));
|
|
95
96
|
const r = await resolve({ identity: { ...IDENTITY, delegation: { chain, audience: 'openwop-host', expiresAt: LIVE } }, expectedAudience: 'openwop-host' });
|
|
96
|
-
if (r === null) return;
|
|
97
|
-
expectRefusal(r, 'delegation_chain_too_long', `a chain of ${depth + 1} hops exceeds the advertised bound of ${depth} — each hop is another party the host trusts transitively`);
|
|
97
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
98
|
+
expectRefusal('openwop.it.workload-identity-chain-bounds.a-chain-longer-than-the-advertised-maxchaindepth-is-refused', r, 'delegation_chain_too_long', `a chain of ${depth + 1} hops exceeds the advertised bound of ${depth} — each hop is another party the host trusts transitively`);
|
|
98
99
|
});
|
|
99
100
|
|
|
100
101
|
it('a chain that revisits a subject is refused as cyclic', async () => {
|
|
@@ -106,15 +107,15 @@ describe('RFC 0154 §B — delegation chain bounds (capability-gated behavior)',
|
|
|
106
107
|
// and this leg accepts either reason, saying so.
|
|
107
108
|
const chain = [hop(1), hop(2), hop(1)];
|
|
108
109
|
const r = await resolve({ identity: { ...IDENTITY, delegation: { chain, audience: 'openwop-host', expiresAt: LIVE } }, expectedAudience: 'openwop-host' });
|
|
109
|
-
if (r === null) return;
|
|
110
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
110
111
|
const bound = caps.maxChainDepth ?? Number.POSITIVE_INFINITY;
|
|
111
112
|
if (bound < 3) {
|
|
112
|
-
expect(r.status >= 400,
|
|
113
|
-
expect(['delegation_chain_cyclic', 'delegation_chain_too_long'],
|
|
113
|
+
expect(r.status >= 400, req('openwop.it.workload-identity-chain-bounds.a-chain-that-revisits-a-subject-is-refused-as-cyclic', 'RFCS/0154 §B', 'a cyclic chain is refused')).toBe(true);
|
|
114
|
+
expect(['delegation_chain_cyclic', 'delegation_chain_too_long'], req('openwop.it.workload-identity-chain-bounds.a-chain-that-revisits-a-subject-is-refused-as-cyclic', 'spec/v1/host-sample-test-seams.md §20', 'cyclic or too-long — the bound is below the cycle length')).toContain(readErrorCode(r.json));
|
|
114
115
|
expect(readRetriable(r.json)).toBe(false);
|
|
115
|
-
return;
|
|
116
|
+
return softSkip('blocked', 'precondition not met — `bound < 3` returned early (seam, prior step, or fixture unavailable)');
|
|
116
117
|
}
|
|
117
|
-
expectRefusal(r, 'delegation_chain_cyclic', 'a subject appearing twice is a chain that loops back through authority it already spent — unbounded laundering with a bounded length');
|
|
118
|
+
expectRefusal('openwop.it.workload-identity-chain-bounds.a-chain-that-revisits-a-subject-is-refused-as-cyclic', r, 'delegation_chain_cyclic', 'a subject appearing twice is a chain that loops back through authority it already spent — unbounded laundering with a bounded length');
|
|
118
119
|
});
|
|
119
120
|
|
|
120
121
|
it('a later hop claiming a scope the previous hop did not hold is refused', async () => {
|
|
@@ -122,8 +123,8 @@ describe('RFC 0154 §B — delegation chain bounds (capability-gated behavior)',
|
|
|
122
123
|
if (!behaviorGate(DELEGATION_PROFILE, caps.supported)) return;
|
|
123
124
|
const chain = [hop(1, ['runs:read']), hop(2, ['runs:read', 'runs:write'])];
|
|
124
125
|
const r = await resolve({ identity: { ...IDENTITY, delegation: { chain, audience: 'openwop-host', expiresAt: LIVE } }, expectedAudience: 'openwop-host' });
|
|
125
|
-
if (r === null) return;
|
|
126
|
-
expectRefusal(r, 'delegation_scope_amplified', 'the effective scopes at any hop MUST NOT exceed the hop before it — a chain is provenance, and provenance cannot mint `runs:write` from `runs:read`');
|
|
126
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
127
|
+
expectRefusal('openwop.it.workload-identity-chain-bounds.a-later-hop-claiming-a-scope-the-previous-hop-did-not-hold-is-refused', r, 'delegation_scope_amplified', 'the effective scopes at any hop MUST NOT exceed the hop before it — a chain is provenance, and provenance cannot mint `runs:write` from `runs:read`');
|
|
127
128
|
});
|
|
128
129
|
|
|
129
130
|
it('a well-formed bounded, acyclic, non-amplifying chain still resolves (the negatives are not a blanket refusal)', async () => {
|
|
@@ -131,11 +132,11 @@ describe('RFC 0154 §B — delegation chain bounds (capability-gated behavior)',
|
|
|
131
132
|
if (!behaviorGate(DELEGATION_PROFILE, caps.supported)) return;
|
|
132
133
|
const chain = [hop(1, ['runs:read', 'manifest:read']), hop(2, ['runs:read'])];
|
|
133
134
|
const r = await resolve({ identity: { ...IDENTITY, delegation: { chain, audience: 'openwop-host', expiresAt: LIVE } }, expectedAudience: 'openwop-host' });
|
|
134
|
-
if (r === null) return;
|
|
135
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
135
136
|
// A host that refuses EVERY chain passes the three negatives vacuously; this
|
|
136
137
|
// is the positive that keeps them honest. Scopes narrow hop-to-hop, which
|
|
137
138
|
// is the one direction §B permits.
|
|
138
|
-
expect(r.status,
|
|
139
|
+
expect(r.status, req('openwop.it.workload-identity-chain-bounds.a-well-formed-bounded-acyclic-non-amplifying-chain-still-resolves-the-negatives', 'RFCS/0154 §B', 'a compliant chain resolves; the bounds refuse laundering, not delegation')).toBe(200);
|
|
139
140
|
expect((r.json as { resolved?: unknown }).resolved).toBe(true);
|
|
140
141
|
});
|
|
141
142
|
});
|