@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
|
@@ -108,6 +108,7 @@ import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
|
108
108
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
109
109
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
110
110
|
import { discoverOwnedTenant, resolveRegistrationUrl } from '../lib/webhook-receiver.js';
|
|
111
|
+
import { req } from '../lib/requirement-ids.js';
|
|
111
112
|
|
|
112
113
|
interface DeliveredRequest {
|
|
113
114
|
readonly headers: Record<string, string>;
|
|
@@ -116,13 +117,13 @@ interface DeliveredRequest {
|
|
|
116
117
|
|
|
117
118
|
async function startReceiver(): Promise<{ server: Server; url: string; received: DeliveredRequest[] }> {
|
|
118
119
|
const received: DeliveredRequest[] = [];
|
|
119
|
-
const server = createServer((
|
|
120
|
+
const server = createServer((reqBody: IncomingMessage, res: ServerResponse) => {
|
|
120
121
|
const chunks: Buffer[] = [];
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
reqBody.on('data', (c: Buffer) => chunks.push(c));
|
|
123
|
+
reqBody.on('end', () => {
|
|
123
124
|
const body = Buffer.concat(chunks).toString('utf8');
|
|
124
125
|
const headers: Record<string, string> = {};
|
|
125
|
-
for (const [k, v] of Object.entries(
|
|
126
|
+
for (const [k, v] of Object.entries(reqBody.headers)) {
|
|
126
127
|
if (typeof v === 'string') headers[k.toLowerCase()] = v;
|
|
127
128
|
else if (Array.isArray(v)) headers[k.toLowerCase()] = v.join(',');
|
|
128
129
|
}
|
|
@@ -225,7 +226,7 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
228
|
|
|
228
|
-
expect(reg.status,
|
|
229
|
+
expect(reg.status, req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature',
|
|
229
230
|
'webhooks.md §"Register"',
|
|
230
231
|
'POST /v1/webhooks MUST return 201 with webhookId + secret on success',
|
|
231
232
|
)).toBe(201);
|
|
@@ -240,7 +241,7 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
240
241
|
// what the spec shows. The postgres reference host returns both names, which
|
|
241
242
|
// is why nothing went red here.
|
|
242
243
|
const sub = reg.json as { webhookId: string; secret: string };
|
|
243
|
-
expect(typeof sub.webhookId,
|
|
244
|
+
expect(typeof sub.webhookId, req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature',
|
|
244
245
|
'api/openapi.yaml registerWebhook 201',
|
|
245
246
|
'the 201 body MUST carry `webhookId` (required) — `subscriptionId` is not in the contract',
|
|
246
247
|
)).toBe('string');
|
|
@@ -276,7 +277,7 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
276
277
|
// has a subscriber; if nothing arrived HERE, the delivery went somewhere
|
|
277
278
|
// this process cannot see and every assertion below it would be vacuous.
|
|
278
279
|
// It fails — it must never soft-skip.
|
|
279
|
-
expect(ourDeliveries.length,
|
|
280
|
+
expect(ourDeliveries.length, req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature',
|
|
280
281
|
'webhooks.md §"Delivery"',
|
|
281
282
|
registration.tunnelled
|
|
282
283
|
? 'host MUST POST at least one event for THIS run to the registered subscriber. ' +
|
|
@@ -289,7 +290,7 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
289
290
|
// Validate the FIRST delivery's signature contract. Other deliveries
|
|
290
291
|
// share the same signing rules; checking one is sufficient.
|
|
291
292
|
const first = ourDeliveries[0]!;
|
|
292
|
-
expect(first.headers['x-openwop-signature-algorithm'],
|
|
293
|
+
expect(first.headers['x-openwop-signature-algorithm'], req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature',
|
|
293
294
|
'webhooks.md §"Signature algorithm versioning"',
|
|
294
295
|
'every delivery MUST set X-openwop-Signature-Algorithm: v1',
|
|
295
296
|
)).toBe('v1');
|
|
@@ -301,26 +302,26 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
301
302
|
// `X-openwop-Signature: sha256={hex}`. (Suite↔spec divergence fixed 2026-08-09.)
|
|
302
303
|
expect(
|
|
303
304
|
first.headers['x-openwop-webhook-id'],
|
|
304
|
-
|
|
305
|
+
req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature', 'webhooks.md §"Delivery headers"', 'X-openwop-Webhook-Id MUST carry the subscription id'),
|
|
305
306
|
).toBe(sub.webhookId);
|
|
306
307
|
|
|
307
308
|
const timestamp = first.headers['x-openwop-timestamp'];
|
|
308
309
|
expect(
|
|
309
310
|
typeof timestamp === 'string' && timestamp.length > 0,
|
|
310
|
-
|
|
311
|
+
req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature', 'webhooks.md §"Delivery headers"', 'X-openwop-Timestamp MUST be a Unix-seconds integer string'),
|
|
311
312
|
).toBe(true);
|
|
312
313
|
|
|
313
314
|
const signature = first.headers['x-openwop-signature'] ?? '';
|
|
314
315
|
expect(
|
|
315
316
|
signature.startsWith('sha256='),
|
|
316
|
-
|
|
317
|
+
req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature', 'webhooks.md §"Delivery headers"', 'X-openwop-Signature MUST carry the `sha256=` prefix'),
|
|
317
318
|
).toBe(true);
|
|
318
319
|
const expected = createHmac('sha256', sub.secret)
|
|
319
320
|
.update(`${timestamp}.${first.body}`, 'utf8')
|
|
320
321
|
.digest('hex');
|
|
321
322
|
expect(
|
|
322
323
|
signature.replace('sha256=', ''),
|
|
323
|
-
|
|
324
|
+
req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature', 'webhooks.md §"Delivery headers"', 'X-openwop-Signature MUST be sha256=HMAC-SHA256(secret, `${X-openwop-Timestamp}.${rawBody}`)'),
|
|
324
325
|
).toBe(expected);
|
|
325
326
|
|
|
326
327
|
// RFC 0165 §C.1 — dual emission of the `OpenWOP-*` family. SHOULD-level and
|
|
@@ -337,11 +338,11 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
337
338
|
['openwop-signature', 'x-openwop-signature'],
|
|
338
339
|
['openwop-signature-algorithm', 'x-openwop-signature-algorithm'],
|
|
339
340
|
] as const) {
|
|
340
|
-
expect(first.headers[neu],
|
|
341
|
+
expect(first.headers[neu], req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature', 'RFC 0165 §C.1', `${neu} MUST equal ${old} when the OpenWOP-* family is emitted`)).toBe(first.headers[old]);
|
|
341
342
|
}
|
|
342
343
|
expect(
|
|
343
344
|
String(dual).replace('sha256=', ''),
|
|
344
|
-
|
|
345
|
+
req('openwop.it.webhook-signed-delivery.host-posts-run-events-to-subscriber-with-valid-x-openwop-signature', 'RFC 0165 §C.1', 'OpenWOP-Signature MUST verify over the same bytes as X-openwop-Signature'),
|
|
345
346
|
).toBe(expected);
|
|
346
347
|
}
|
|
347
348
|
|
|
@@ -404,7 +405,7 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
404
405
|
}
|
|
405
406
|
}
|
|
406
407
|
|
|
407
|
-
expect(reg.status,
|
|
408
|
+
expect(reg.status, req('openwop.it.webhook-signed-delivery.control-with-a-tunnel-wired-the-host-still-refuses-the-loopback-receiver',
|
|
408
409
|
'webhooks.md §"SSRF protection"',
|
|
409
410
|
'the server MUST reject loopback subscription URLs at registration time. ' +
|
|
410
411
|
'It was accepted here, so this run cannot attribute the tunnelled test\'s pass to ' +
|
|
@@ -38,6 +38,8 @@ import { describe, it, expect } from 'vitest';
|
|
|
38
38
|
import { driver, type OpenWOPResponse } from '../lib/driver.js';
|
|
39
39
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
40
40
|
import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
|
|
41
|
+
import { req } from '../lib/requirement-ids.js';
|
|
42
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
41
43
|
|
|
42
44
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
43
45
|
|
|
@@ -66,26 +68,26 @@ async function gateOnWebhooks(): Promise<boolean> {
|
|
|
66
68
|
describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: advertisement shape (webhooks.md)', () => {
|
|
67
69
|
it('the webhooks block is either absent or carries a boolean supported flag', async () => {
|
|
68
70
|
const cap = await readCapabilityFamily<WebhooksCap>('webhooks');
|
|
69
|
-
if (cap === undefined) return; // host doesn't advertise webhooks — conformant
|
|
71
|
+
if (cap === undefined) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap === undefined` returned early (host doesn\'t advertise webhooks — conformant)'); // host doesn't advertise webhooks — conformant
|
|
70
72
|
expect(
|
|
71
73
|
typeof cap.supported,
|
|
72
|
-
|
|
74
|
+
req('openwop.it.webhook-tenant-isolation.the-webhooks-block-is-either-absent-or-carries-a-boolean-supported-flag', 'capabilities.schema.json §webhooks', 'webhooks.supported MUST be a boolean when present'),
|
|
73
75
|
).toBe('boolean');
|
|
74
76
|
});
|
|
75
77
|
});
|
|
76
78
|
|
|
77
79
|
describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: two-tenant proof via the test seam (RFC 0093 §A.3)', () => {
|
|
78
80
|
it('a subscription registered under tenant A is invisible to tenant B', async () => {
|
|
79
|
-
if (!(await gateOnWebhooks())) return;
|
|
81
|
+
if (!(await gateOnWebhooks())) return softSkip('blocked', 'precondition not met — `!(await gateOnWebhooks())` returned early (seam, prior step, or fixture unavailable)');
|
|
80
82
|
|
|
81
83
|
const reg = await seam('tenant-a', 'register', {
|
|
82
84
|
url: SAFE_URL,
|
|
83
85
|
events: ['run.completed'],
|
|
84
86
|
});
|
|
85
|
-
if (reg.status === 404) return; // host doesn't expose the test seam — soft-skip
|
|
86
|
-
expect(reg.status,
|
|
87
|
+
if (reg.status === 404) return softSkip('blocked', 'precondition not met — `reg.status === 404` returned early (host doesn\'t expose the test seam — soft-skip) (seam, prior step, or fixture unavailable)'); // host doesn't expose the test seam — soft-skip
|
|
88
|
+
expect(reg.status, req('openwop.it.webhook-tenant-isolation.a-subscription-registered-under-tenant-a-is-invisible-to-tenant-b', 'RFC 0093 §A.3', 'seam register under tenant A MUST succeed')).toBe(200);
|
|
87
89
|
const webhookId = (reg.json as { webhookId?: string }).webhookId;
|
|
88
|
-
expect(typeof webhookId, 'seam register MUST return the new webhookId').toBe('string');
|
|
90
|
+
expect(typeof webhookId, req('openwop.it.webhook-tenant-isolation.a-subscription-registered-under-tenant-a-is-invisible-to-tenant-b', 'RFC 0093 §A.3', 'seam register MUST return the new webhookId')).toBe('string');
|
|
89
91
|
|
|
90
92
|
try {
|
|
91
93
|
const listB = await seam('tenant-b', 'list', {});
|
|
@@ -94,7 +96,7 @@ describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: two-tenant proof via the t
|
|
|
94
96
|
.map((w) => w.webhookId);
|
|
95
97
|
expect(
|
|
96
98
|
idsB,
|
|
97
|
-
|
|
99
|
+
req('openwop.it.webhook-tenant-isolation.a-subscription-registered-under-tenant-a-is-invisible-to-tenant-b',
|
|
98
100
|
'SECURITY/invariants.yaml webhook-cross-tenant-isolation',
|
|
99
101
|
'tenant B MUST NOT see tenant A subscriptions in its list',
|
|
100
102
|
),
|
|
@@ -105,7 +107,7 @@ describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: two-tenant proof via the t
|
|
|
105
107
|
.map((w) => w.webhookId);
|
|
106
108
|
expect(
|
|
107
109
|
idsA,
|
|
108
|
-
|
|
110
|
+
req('openwop.it.webhook-tenant-isolation.a-subscription-registered-under-tenant-a-is-invisible-to-tenant-b', 'RFC 0093 §A.3', 'tenant A MUST see its own subscription (same-tenant control)'),
|
|
109
111
|
).toContain(webhookId);
|
|
110
112
|
} finally {
|
|
111
113
|
await seam('tenant-a', 'unregister', { webhookId });
|
|
@@ -115,7 +117,7 @@ describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: two-tenant proof via the t
|
|
|
115
117
|
|
|
116
118
|
describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: black-box registration-surface scoping (webhooks.md)', () => {
|
|
117
119
|
it('registering under a tenant the caller is not a member of is refused', async () => {
|
|
118
|
-
if (!(await gateOnWebhooks())) return;
|
|
120
|
+
if (!(await gateOnWebhooks())) return softSkip('blocked', 'precondition not met — `!(await gateOnWebhooks())` returned early (seam, prior step, or fixture unavailable)');
|
|
119
121
|
|
|
120
122
|
const foreignTenant = `openwop-conformance-foreign-${uniqueSuffix()}`;
|
|
121
123
|
const reg = await driver.post('/v1/webhooks', {
|
|
@@ -135,7 +137,7 @@ describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: black-box registration-sur
|
|
|
135
137
|
}
|
|
136
138
|
expect(
|
|
137
139
|
reg.status,
|
|
138
|
-
|
|
140
|
+
req('openwop.it.webhook-tenant-isolation.registering-under-a-tenant-the-caller-is-not-a-member-of-is-refused',
|
|
139
141
|
'webhooks.md §Endpoints ("the caller MUST be a member of the tenant") + RFC 0093 §A.3',
|
|
140
142
|
'registration under a tenant the caller is not a member of MUST be refused (403/404/400), never 201',
|
|
141
143
|
),
|
|
@@ -143,7 +145,7 @@ describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: black-box registration-sur
|
|
|
143
145
|
});
|
|
144
146
|
|
|
145
147
|
it('a held subscription cannot be unregistered through a foreign tenant scope', async () => {
|
|
146
|
-
if (!(await gateOnWebhooks())) return;
|
|
148
|
+
if (!(await gateOnWebhooks())) return softSkip('blocked', 'precondition not met — `!(await gateOnWebhooks())` returned early (seam, prior step, or fixture unavailable)');
|
|
147
149
|
|
|
148
150
|
// Register under the caller's own tenant (host defaults the scope from
|
|
149
151
|
// the API key when tenantId is omitted — the same registration shape the
|
|
@@ -159,10 +161,10 @@ describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: black-box registration-sur
|
|
|
159
161
|
console.warn(
|
|
160
162
|
`[webhook-tenant-isolation] could not register a probe subscription (${reg.status}); skipping foreign-unregister leg`,
|
|
161
163
|
);
|
|
162
|
-
return;
|
|
164
|
+
return softSkip('blocked', 'precondition not met — `reg.status !== 201` returned early ([webhook-tenant-isolation] could not register a probe subscription (…); skipping foreign-unregister leg) (seam, prior step, or fixture unavailable)');
|
|
163
165
|
}
|
|
164
166
|
const webhookId = (reg.json as { webhookId?: string }).webhookId;
|
|
165
|
-
expect(typeof webhookId, 'registration MUST return webhookId').toBe('string');
|
|
167
|
+
expect(typeof webhookId, req('openwop.it.webhook-tenant-isolation.a-held-subscription-cannot-be-unregistered-through-a-foreign-tenant-scope', 'webhooks.md §Unregister (403 non-member / 404 not-in-scope) + RFC 0093 §A.3', 'registration MUST return webhookId')).toBe('string');
|
|
166
168
|
|
|
167
169
|
try {
|
|
168
170
|
const foreignTenant = `openwop-conformance-foreign-${uniqueSuffix()}`;
|
|
@@ -171,7 +173,7 @@ describe.skipIf(HTTP_SKIP)('webhook-tenant-isolation: black-box registration-sur
|
|
|
171
173
|
);
|
|
172
174
|
expect(
|
|
173
175
|
del.status !== 204 && del.status < 500,
|
|
174
|
-
|
|
176
|
+
req('openwop.it.webhook-tenant-isolation.a-held-subscription-cannot-be-unregistered-through-a-foreign-tenant-scope',
|
|
175
177
|
'webhooks.md §Unregister (403 non-member / 404 not-in-scope) + RFC 0093 §A.3',
|
|
176
178
|
`unregister through a foreign tenant scope MUST NOT succeed (got ${del.status})`,
|
|
177
179
|
),
|
|
@@ -50,6 +50,8 @@ import {
|
|
|
50
50
|
type WorkflowChain,
|
|
51
51
|
type DeferredExpansionContext,
|
|
52
52
|
} from '../lib/workflow-chain-expansion.js';
|
|
53
|
+
import { req } from '../lib/requirement-ids.js';
|
|
54
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
53
55
|
|
|
54
56
|
const CAPS = join(SCHEMAS_DIR, 'capabilities.schema.json');
|
|
55
57
|
const MANIFEST = join(SCHEMAS_DIR, 'workflow-chain-pack-manifest.schema.json');
|
|
@@ -59,10 +61,6 @@ const MANIFEST = join(SCHEMAS_DIR, 'workflow-chain-pack-manifest.schema.json');
|
|
|
59
61
|
// repo-layout only: `null` in the published layout and skipped, never thrown.
|
|
60
62
|
const CHAIN_DOC: string | null = V1_DIR === null ? null : join(V1_DIR, 'workflow-chain-packs.md');
|
|
61
63
|
|
|
62
|
-
/** Spec-cited assertion message. Unlike `driver.describe`, this does NOT load
|
|
63
|
-
* env, so it is safe in always-on server-free legs (no OPENWOP_BASE_URL). */
|
|
64
|
-
const cite = (section: string, requirement: string): string => `${section} — ${requirement}`;
|
|
65
|
-
|
|
66
64
|
const resolvable = () => true;
|
|
67
65
|
const HOST_FULL = { promptVariableSource: true, secretsSupported: true };
|
|
68
66
|
|
|
@@ -112,13 +110,13 @@ describe('workflow-chain-deferred: non-sensitive deferral (server-free, RFC 0124
|
|
|
112
110
|
|
|
113
111
|
expect(
|
|
114
112
|
hasResidualToken({ nodes: out.nodes, edges: out.edges }),
|
|
115
|
-
|
|
113
|
+
req('openwop.it.workflow-chain-deferred-parameters.materializes-variables-with-defaultvalue-type-and-leaves-zero-params-tokens-r3', 'workflow-chain-packs.md §Deferred-parameter expansion', 'the persisted fragment MUST contain zero {{params.*}} tokens (R3 portability)'),
|
|
116
114
|
).toBe(false);
|
|
117
115
|
|
|
118
116
|
const topicVar = out.variables.find((v) => v.name === 'topic');
|
|
119
|
-
expect(topicVar, 'topic MUST be materialized as a top-level variable').toBeDefined();
|
|
120
|
-
expect(topicVar!.defaultValue, 'author input becomes defaultValue').toBe('sales');
|
|
121
|
-
expect(topicVar!.type, 'type copied from the parameter schema').toBe('string');
|
|
117
|
+
expect(topicVar, req('openwop.it.workflow-chain-deferred-parameters.materializes-variables-with-defaultvalue-type-and-leaves-zero-params-tokens-r3', 'workflow-chain-packs.md', 'topic MUST be materialized as a top-level variable')).toBeDefined();
|
|
118
|
+
expect(topicVar!.defaultValue, req('openwop.it.workflow-chain-deferred-parameters.materializes-variables-with-defaultvalue-type-and-leaves-zero-params-tokens-r3', 'workflow-chain-packs.md', 'author input becomes defaultValue')).toBe('sales');
|
|
119
|
+
expect(topicVar!.type, req('openwop.it.workflow-chain-deferred-parameters.materializes-variables-with-defaultvalue-type-and-leaves-zero-params-tokens-r3', 'workflow-chain-packs.md', 'type copied from the parameter schema')).toBe('string');
|
|
122
120
|
|
|
123
121
|
// prompt token → {{varName}} + source:"variable"
|
|
124
122
|
expect((out.nodes[0].config as { systemPrompt: string }).systemPrompt).toBe('Hello {{topic}}');
|
|
@@ -133,7 +131,7 @@ describe('workflow-chain-deferred: non-sensitive deferral (server-free, RFC 0124
|
|
|
133
131
|
const out = expandChainDeferred(c, deferCtx({ count: 5 }, c.parameters as never));
|
|
134
132
|
expect(
|
|
135
133
|
out.nodes[0].inputs!.limit,
|
|
136
|
-
|
|
134
|
+
req('openwop.it.workflow-chain-deferred-parameters.a-whole-value-input-token-becomes-a-variable-sourced-portvalue-wcp2-raw-typed', 'workflow-chain-packs.md §Deferred-parameter expansion', 'a whole-value {{params.x}} input rewrites to a variable-sourced PortValue, not a stringified token'),
|
|
137
135
|
).toEqual({ source: 'variable', variable: 'count' });
|
|
138
136
|
});
|
|
139
137
|
|
|
@@ -145,7 +143,7 @@ describe('workflow-chain-deferred: non-sensitive deferral (server-free, RFC 0124
|
|
|
145
143
|
const out = expandChainDeferred(c, deferCtx({ topic: 't' }, c.parameters as never));
|
|
146
144
|
expect(
|
|
147
145
|
out.configurableSchema.properties.topic,
|
|
148
|
-
|
|
146
|
+
req('openwop.it.workflow-chain-deferred-parameters.the-bare-param-name-is-the-override-key-in-the-auto-generated-configurableschema', 'workflow-chain-packs.md §Override key + variable naming', 'the bare parameter name is the normative override key'),
|
|
149
147
|
).toEqual({ type: 'string' });
|
|
150
148
|
});
|
|
151
149
|
});
|
|
@@ -162,14 +160,14 @@ describe('workflow-chain-deferred: §Security sensitive-parameter MUSTs (server-
|
|
|
162
160
|
|
|
163
161
|
expect(
|
|
164
162
|
out.promptVariables.find((p) => p.name === 'apiKey')?.source,
|
|
165
|
-
|
|
163
|
+
req('openwop.it.workflow-chain-deferred-parameters.a-sensitive-param-in-a-prompt-body-materializes-as-source-secret-no-plaintext-de', 'workflow-chain-packs.md §Security', 'a sensitive prompt-body param MUST bind source:"secret" (not source:"variable")'),
|
|
166
164
|
).toBe('secret');
|
|
167
165
|
// The plaintext secret MUST appear NOWHERE — not as a variable defaultValue, not in the fragment.
|
|
168
166
|
expect(
|
|
169
167
|
JSON.stringify(out).includes('sk-PLAINTEXT-SECRET'),
|
|
170
|
-
|
|
168
|
+
req('openwop.it.workflow-chain-deferred-parameters.a-sensitive-param-in-a-prompt-body-materializes-as-source-secret-no-plaintext-de', 'workflow-chain-packs.md §Security', 'the sensitive value MUST NOT be materialized into variables[] or the persisted fragment (never bagged, SR-1)'),
|
|
171
169
|
).toBe(false);
|
|
172
|
-
expect(out.variables.find((v) => v.name === 'apiKey'), 'sensitive param is NOT a plaintext top-level variable').toBeUndefined();
|
|
170
|
+
expect(out.variables.find((v) => v.name === 'apiKey'), req('openwop.it.workflow-chain-deferred-parameters.a-sensitive-param-in-a-prompt-body-materializes-as-source-secret-no-plaintext-de', 'workflow-chain-packs.md', 'sensitive param is NOT a plaintext top-level variable')).toBeUndefined();
|
|
173
171
|
});
|
|
174
172
|
|
|
175
173
|
it('a sensitive param in a whole-value node.input FAILS CLOSED (sensitive_param_not_deferrable, 422)', () => {
|
|
@@ -183,10 +181,10 @@ describe('workflow-chain-deferred: §Security sensitive-parameter MUSTs (server-
|
|
|
183
181
|
} catch (e) {
|
|
184
182
|
err = e;
|
|
185
183
|
}
|
|
186
|
-
expect(err, 'a sensitive whole-value input MUST throw, not defer').toBeInstanceOf(SensitiveParamNotDeferrableError);
|
|
184
|
+
expect(err, req('openwop.it.workflow-chain-deferred-parameters.a-sensitive-param-in-a-whole-value-node-input-fails-closed-sensitive-param-not-d', 'workflow-chain-packs.md', 'a sensitive whole-value input MUST throw, not defer')).toBeInstanceOf(SensitiveParamNotDeferrableError);
|
|
187
185
|
expect(
|
|
188
186
|
(err as SensitiveParamNotDeferrableError).code,
|
|
189
|
-
|
|
187
|
+
req('openwop.it.workflow-chain-deferred-parameters.a-sensitive-param-in-a-whole-value-node-input-fails-closed-sensitive-param-not-d', 'workflow-chain-packs.md §Security', 'a sensitive param outside a prompt body MUST fail closed with sensitive_param_not_deferrable'),
|
|
190
188
|
).toBe('sensitive_param_not_deferrable');
|
|
191
189
|
expect((err as SensitiveParamNotDeferrableError).httpStatus).toBe(422);
|
|
192
190
|
});
|
|
@@ -204,7 +202,7 @@ describe('workflow-chain-deferred: §Security sensitive-parameter MUSTs (server-
|
|
|
204
202
|
}
|
|
205
203
|
expect(
|
|
206
204
|
err,
|
|
207
|
-
|
|
205
|
+
req('openwop.it.workflow-chain-deferred-parameters.a-sensitive-prompt-body-param-on-a-host-without-secrets-support-fails-closed-422', 'workflow-chain-packs.md §Security', 'a host lacking capabilities.secrets MUST NOT plaintext-defer a sensitive param — fail closed'),
|
|
208
206
|
).toBeInstanceOf(SensitiveParamNotDeferrableError);
|
|
209
207
|
});
|
|
210
208
|
|
|
@@ -219,7 +217,7 @@ describe('workflow-chain-deferred: §Security sensitive-parameter MUSTs (server-
|
|
|
219
217
|
);
|
|
220
218
|
// Fallback resolves the token at expansion time — no {{topic}} slot, value inlined, still zero {{params.*}}.
|
|
221
219
|
expect((out.nodes[0].config as { systemPrompt: string }).systemPrompt).toBe('Hi sales');
|
|
222
|
-
expect(hasResidualToken({ nodes: out.nodes }), 'no residual {{params.*}} even on the fallback path').toBe(false);
|
|
220
|
+
expect(hasResidualToken({ nodes: out.nodes }), req('openwop.it.workflow-chain-deferred-parameters.negative-capability-no-prompts-variable-source-prompt-token-falls-back-to-expans', 'workflow-chain-packs.md', 'no residual {{params.*}} even on the fallback path')).toBe(false);
|
|
223
221
|
});
|
|
224
222
|
});
|
|
225
223
|
|
|
@@ -227,29 +225,29 @@ describe('workflow-chain-deferred: schema + spec surface (always-on, server-free
|
|
|
227
225
|
it('capabilities.schema.json §workflowChainPacks.deferredParameters requires supported:boolean', () => {
|
|
228
226
|
const raw = readFileSync(CAPS, 'utf8');
|
|
229
227
|
// The deferredParameters block MUST parse as part of the capabilities schema.
|
|
230
|
-
expect(() => JSON.parse(raw), 'capabilities.schema.json MUST be valid JSON').not.toThrow();
|
|
231
|
-
expect(raw.includes('"deferredParameters"'), 'the deferredParameters capability block MUST exist').toBe(true);
|
|
228
|
+
expect(() => JSON.parse(raw), req('openwop.it.workflow-chain-deferred-parameters.capabilities-schema-json-workflowchainpacks-deferredparameters-requires-supporte', 'workflow-chain-packs.md', 'capabilities.schema.json MUST be valid JSON')).not.toThrow();
|
|
229
|
+
expect(raw.includes('"deferredParameters"'), req('openwop.it.workflow-chain-deferred-parameters.capabilities-schema-json-workflowchainpacks-deferredparameters-requires-supporte', 'workflow-chain-packs.md', 'the deferredParameters capability block MUST exist')).toBe(true);
|
|
232
230
|
expect(
|
|
233
231
|
raw.includes('sensitive_param_not_deferrable'),
|
|
234
|
-
|
|
232
|
+
req('openwop.it.workflow-chain-deferred-parameters.capabilities-schema-json-workflowchainpacks-deferredparameters-requires-supporte', 'capabilities.schema.json §deferredParameters', 'the capability description MUST reference the fail-closed sensitive rule'),
|
|
235
233
|
).toBe(true);
|
|
236
234
|
});
|
|
237
235
|
|
|
238
236
|
it('workflow-chain-pack-manifest.schema.json documents x-openwop-sensitive with the source:"secret" MUST', () => {
|
|
239
237
|
const raw = readFileSync(MANIFEST, 'utf8');
|
|
240
|
-
expect(raw.includes('x-openwop-sensitive'), 'the manifest schema MUST recognize x-openwop-sensitive').toBe(true);
|
|
238
|
+
expect(raw.includes('x-openwop-sensitive'), req('openwop.it.workflow-chain-deferred-parameters.workflow-chain-pack-manifest-schema-json-documents-x-openwop-sensitive-with-the', 'workflow-chain-packs.md', 'the manifest schema MUST recognize x-openwop-sensitive')).toBe(true);
|
|
241
239
|
expect(
|
|
242
240
|
raw.includes('source:"secret"') || raw.includes('source:\\"secret\\"'),
|
|
243
|
-
|
|
241
|
+
req('openwop.it.workflow-chain-deferred-parameters.workflow-chain-pack-manifest-schema-json-documents-x-openwop-sensitive-with-the', 'workflow-chain-pack-manifest.schema.json', 'the x-openwop-sensitive description MUST reflect the amended source:"secret" MUST, not the stale plaintext-variable wording'),
|
|
244
242
|
).toBe(true);
|
|
245
243
|
});
|
|
246
244
|
|
|
247
245
|
it.skipIf(CHAIN_DOC === null)('the spec pins the error code + the per-run credentialRef (not plaintext) supply shape', () => {
|
|
248
246
|
const spec = readFileSync(CHAIN_DOC as string, 'utf8');
|
|
249
|
-
expect(spec.includes('sensitive_param_not_deferrable'), 'error code MUST be documented').toBe(true);
|
|
247
|
+
expect(spec.includes('sensitive_param_not_deferrable'), req('openwop.it.workflow-chain-deferred-parameters.the-spec-pins-the-error-code-the-per-run-credentialref-not-plaintext-supply-shap', 'workflow-chain-packs.md', 'error code MUST be documented')).toBe(true);
|
|
250
248
|
expect(
|
|
251
249
|
spec.includes('credentialRef'),
|
|
252
|
-
|
|
250
|
+
req('openwop.it.workflow-chain-deferred-parameters.the-spec-pins-the-error-code-the-per-run-credentialref-not-plaintext-supply-shap', 'workflow-chain-packs.md §Security', 'per-run supply of a sensitive param MUST be a credentialRef, not plaintext'),
|
|
253
251
|
).toBe(true);
|
|
254
252
|
});
|
|
255
253
|
});
|
|
@@ -265,20 +263,20 @@ describe('workflow-chain-deferred: host behavior (capability-gated, RFC 0124)',
|
|
|
265
263
|
override: { topic: 'run-topic' },
|
|
266
264
|
fork: true,
|
|
267
265
|
});
|
|
268
|
-
if (res.status === 404 || res.status === 403) return; // seam unwired — soft-skip
|
|
266
|
+
if (res.status === 404 || res.status === 403) return softSkip('blocked', 'precondition not met — `res.status === 404 || res.status === 403` returned early (seam unwired — soft-skip) (seam, prior step, or fixture unavailable)'); // seam unwired — soft-skip
|
|
269
267
|
|
|
270
268
|
const body = res.json as { resolved?: string; forkResolved?: string; contentTrust?: string } | undefined;
|
|
271
269
|
expect(
|
|
272
270
|
body?.resolved,
|
|
273
|
-
|
|
271
|
+
req('openwop.it.workflow-chain-deferred-parameters.a-deferred-round-trip-bare-param-configurable-override-changes-the-value-fork-re', 'workflow-chain-packs.md §Deferred-parameter expansion', 'a bare-param configurable override rebinds the resolved value'),
|
|
274
272
|
).toBe('run-topic');
|
|
275
273
|
expect(
|
|
276
274
|
body?.forkResolved,
|
|
277
|
-
|
|
275
|
+
req('openwop.it.workflow-chain-deferred-parameters.a-deferred-round-trip-bare-param-configurable-override-changes-the-value-fork-re', 'replay.md §Determinism', ':fork replays the same bound value (RunSnapshot.variables byte-equivalence, R4)'),
|
|
278
276
|
).toBe('run-topic');
|
|
279
277
|
expect(
|
|
280
278
|
body?.contentTrust,
|
|
281
|
-
|
|
279
|
+
req('openwop.it.workflow-chain-deferred-parameters.a-deferred-round-trip-bare-param-configurable-override-changes-the-value-fork-re', 'workflow-chain-packs.md §Deferred-parameter expansion step 4', 'a deferred-variable prompt binding composes contentTrust:"untrusted" (R1)'),
|
|
282
280
|
).toBe('untrusted');
|
|
283
281
|
});
|
|
284
282
|
|
|
@@ -291,12 +289,12 @@ describe('workflow-chain-deferred: host behavior (capability-gated, RFC 0124)',
|
|
|
291
289
|
sensitiveParam: 'apiKey',
|
|
292
290
|
credentialRef: 'cred-123',
|
|
293
291
|
});
|
|
294
|
-
if (res.status === 404 || res.status === 403) return; // seam unwired — soft-skip
|
|
292
|
+
if (res.status === 404 || res.status === 403) return softSkip('blocked', 'precondition not met — `res.status === 404 || res.status === 403` returned early (seam unwired — soft-skip) (seam, prior step, or fixture unavailable)'); // seam unwired — soft-skip
|
|
295
293
|
|
|
296
294
|
const body = res.json as { composed?: string } | undefined;
|
|
297
295
|
expect(
|
|
298
296
|
body?.composed?.includes('[REDACTED'),
|
|
299
|
-
|
|
297
|
+
req('openwop.it.workflow-chain-deferred-parameters.a-sensitive-param-composes-as-redacted-credentialref-and-the-plaintext-appears-n', 'workflow-chain-packs.md §Security', 'a source:"secret" sensitive var MUST redact to [REDACTED:<credentialRef>] in prompt.composed'),
|
|
300
298
|
).toBe(true);
|
|
301
299
|
});
|
|
302
300
|
});
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
import { describe, it, expect } from 'vitest';
|
|
36
36
|
import { expandChain, type WorkflowChain } from '../lib/workflow-chain-expansion.js';
|
|
37
|
+
import { req } from '../lib/requirement-ids.js';
|
|
37
38
|
|
|
38
39
|
/** Index helper that narrows away the `T | undefined` from `noUncheckedIndexedAccess`
|
|
39
40
|
* with an actionable error message — replaces `arr[i]!` non-null assertions so a
|
|
@@ -95,7 +96,7 @@ describe('category: workflow-chain expansion — placeholder substitution', () =
|
|
|
95
96
|
const config = at(fragment.nodes, 0, 'fragment.nodes').config as { systemPrompt: string };
|
|
96
97
|
expect(
|
|
97
98
|
config.systemPrompt,
|
|
98
|
-
'Per workflow-chain-packs.md §"Parameter substitution": placeholders MUST be substituted literally at expansion time.',
|
|
99
|
+
req('openwop.it.workflow-chain-expansion.substitutes-params-x-placeholders-literally-in-config-strings', 'workflow-chain-packs.md', 'Per workflow-chain-packs.md §"Parameter substitution": placeholders MUST be substituted literally at expansion time.'),
|
|
99
100
|
).toBe('Write a PRD for: AI-powered toaster\nAudience: restaurant chains');
|
|
100
101
|
});
|
|
101
102
|
|
|
@@ -130,11 +131,11 @@ describe('category: workflow-chain expansion — placeholder substitution', () =
|
|
|
130
131
|
};
|
|
131
132
|
expect(
|
|
132
133
|
config.outer.middle.message,
|
|
133
|
-
'Recursive substitution MUST walk into nested objects (per spec §Parameter substitution: "Substitution MUST recurse into nested string values within `config` and `inputs`").',
|
|
134
|
+
req('openwop.it.workflow-chain-expansion.recurses-into-nested-object-structures-in-config', 'workflow-chain-packs.md', 'Recursive substitution MUST walk into nested objects (per spec §Parameter substitution: "Substitution MUST recurse into nested string values within `config` and `inputs`").'),
|
|
134
135
|
).toBe('Hello world');
|
|
135
136
|
expect(
|
|
136
137
|
config.outer.middle.tags,
|
|
137
|
-
'Recursive substitution MUST also walk into array elements when those elements are strings.',
|
|
138
|
+
req('openwop.it.workflow-chain-expansion.recurses-into-nested-object-structures-in-config', 'workflow-chain-packs.md', 'Recursive substitution MUST also walk into array elements when those elements are strings.'),
|
|
138
139
|
).toEqual(['static', 'world-tag']);
|
|
139
140
|
});
|
|
140
141
|
|
|
@@ -156,7 +157,7 @@ describe('category: workflow-chain expansion — placeholder substitution', () =
|
|
|
156
157
|
{ expansionId: 'x', params: {}, isTypeIdResolvable: RESOLVE_ALL },
|
|
157
158
|
);
|
|
158
159
|
const config = at(fragment.nodes, 0, 'fragment.nodes').config as { literal: string; empty: string };
|
|
159
|
-
expect(config.literal).toBe('no placeholders here');
|
|
160
|
+
expect(config.literal, req('openwop.it.workflow-chain-expansion.leaves-non-placeholder-strings-untouched', 'workflow-chain-packs.md', 'leaves non-placeholder strings untouched')).toBe('no placeholders here');
|
|
160
161
|
expect(config.empty).toBe('');
|
|
161
162
|
});
|
|
162
163
|
|
|
@@ -178,7 +179,7 @@ describe('category: workflow-chain expansion — placeholder substitution', () =
|
|
|
178
179
|
{ expansionId: 'x', params: { who: 'Ada' }, isTypeIdResolvable: RESOLVE_ALL },
|
|
179
180
|
);
|
|
180
181
|
const config = at(fragment.nodes, 0, 'fragment.nodes').config as { a: string; b: string; joined: string };
|
|
181
|
-
expect(config.a).toBe('Ada');
|
|
182
|
+
expect(config.a, req('openwop.it.workflow-chain-expansion.substitutes-the-same-placeholder-name-in-multiple-positions', 'workflow-chain-packs.md', 'substitutes the same placeholder name in multiple positions')).toBe('Ada');
|
|
182
183
|
expect(config.b).toBe('Ada');
|
|
183
184
|
expect(config.joined).toBe('hi Ada, hi again Ada');
|
|
184
185
|
});
|
|
@@ -231,7 +232,7 @@ describe('category: workflow-chain expansion — whole-value typed resolution',
|
|
|
231
232
|
};
|
|
232
233
|
expect(
|
|
233
234
|
config.retryPolicy,
|
|
234
|
-
'Per workflow-chain-packs.md §"Parameter substitution": a value that is EXACTLY one `{{params.x}}` token MUST resolve to the raw typed value — an object param MUST NOT be stringified to "[object Object]".',
|
|
235
|
+
req('openwop.it.workflow-chain-expansion.resolves-a-whole-value-params-x-token-to-the-raw-typed-value-object-array-number', 'workflow-chain-packs.md', 'Per workflow-chain-packs.md §"Parameter substitution": a value that is EXACTLY one `{{params.x}}` token MUST resolve to the raw typed value — an object param MUST NOT be stringified to "[object Object]".'),
|
|
235
236
|
).toEqual({ attempts: 3, backoff: 'exponential' });
|
|
236
237
|
expect(config.allowlist).toEqual(['a', 'b']);
|
|
237
238
|
expect(config.maxTokens).toBe(4096);
|
|
@@ -255,7 +256,7 @@ describe('category: workflow-chain expansion — whole-value typed resolution',
|
|
|
255
256
|
const config = at(fragment.nodes, 0, 'fragment.nodes').config as { label: string };
|
|
256
257
|
expect(
|
|
257
258
|
config.label,
|
|
258
|
-
'A token embedded in surrounding text MUST do literal string substitution (the numeric param coerces to its string form).',
|
|
259
|
+
req('openwop.it.workflow-chain-expansion.does-literal-string-coercion-for-an-embedded-token-in-surrounding-text', 'workflow-chain-packs.md', 'A token embedded in surrounding text MUST do literal string substitution (the numeric param coerces to its string form).'),
|
|
259
260
|
).toBe('items: 42');
|
|
260
261
|
});
|
|
261
262
|
});
|
|
@@ -290,7 +291,7 @@ describe('category: workflow-chain expansion — inputs preservation', () => {
|
|
|
290
291
|
};
|
|
291
292
|
expect(
|
|
292
293
|
inputs.prompt,
|
|
293
|
-
'Per workflow-chain-packs.md §"Parameter substitution": expansion MUST preserve a present `node.inputs` (PortValue references) verbatim — only `{{params.*}}` tokens inside its string leaves are substituted.',
|
|
294
|
+
req('openwop.it.workflow-chain-expansion.preserves-a-present-node-inputs-portvalue-references-through-expansion', 'workflow-chain-packs.md', 'Per workflow-chain-packs.md §"Parameter substitution": expansion MUST preserve a present `node.inputs` (PortValue references) verbatim — only `{{params.*}}` tokens inside its string leaves are substituted.'),
|
|
294
295
|
).toEqual({ sourceNodeId: 'upstream', sourcePort: 'text' });
|
|
295
296
|
expect(inputs.seed).toBe('xyz');
|
|
296
297
|
});
|
|
@@ -310,7 +311,7 @@ describe('category: workflow-chain expansion — node id collision avoidance', (
|
|
|
310
311
|
});
|
|
311
312
|
expect(
|
|
312
313
|
at(first.nodes, 0, 'first.nodes').id,
|
|
313
|
-
'Per spec §"Expansion semantics" step 6: each expansion MUST use a unique `expansionId` prefix so multi-expansion DAGs do not collide.',
|
|
314
|
+
req('openwop.it.workflow-chain-expansion.same-chain-expanded-twice-in-one-parent-workflow-produces-non-colliding-node-ids', 'workflow-chain-packs.md', 'Per spec §"Expansion semantics" step 6: each expansion MUST use a unique `expansionId` prefix so multi-expansion DAGs do not collide.'),
|
|
314
315
|
).not.toBe(at(second.nodes, 0, 'second.nodes').id);
|
|
315
316
|
expect(at(first.nodes, 0, 'first.nodes').id).toBe('vendor_acme_generatePRD_a8f3_prd-call');
|
|
316
317
|
expect(at(second.nodes, 0, 'second.nodes').id).toBe('vendor_acme_generatePRD_b9e4_prd-call');
|
|
@@ -324,7 +325,7 @@ describe('category: workflow-chain expansion — node id collision avoidance', (
|
|
|
324
325
|
});
|
|
325
326
|
expect(
|
|
326
327
|
at(fragment.nodes, 0, 'fragment.nodes').id,
|
|
327
|
-
'chainId `vendor.acme.generatePRD` MUST be slugged (dots → underscores) so the resulting id is safe to use in storage backends that reserve `.` for hierarchical keys.',
|
|
328
|
+
req('openwop.it.workflow-chain-expansion.rewritten-ids-replace-dots-in-chainid-with-underscores-for-storage-key-safety', 'workflow-chain-packs.md', 'chainId `vendor.acme.generatePRD` MUST be slugged (dots → underscores) so the resulting id is safe to use in storage backends that reserve `.` for hierarchical keys.'),
|
|
328
329
|
).toMatch(/^vendor_acme_generatePRD_/);
|
|
329
330
|
expect(at(fragment.nodes, 0, 'fragment.nodes').id).not.toMatch(/\./);
|
|
330
331
|
});
|
|
@@ -337,7 +338,7 @@ describe('category: workflow-chain expansion — node id collision avoidance', (
|
|
|
337
338
|
});
|
|
338
339
|
expect(
|
|
339
340
|
fragment.idMap.get('prd-call'),
|
|
340
|
-
'The expansion contract MUST surface the original-id → rewritten-id map so the caller can route adjacent parent-workflow edges to the right expanded node.',
|
|
341
|
+
req('openwop.it.workflow-chain-expansion.exposes-idmap-so-the-caller-can-wire-parent-workflow-edges-into-the-expansion', 'workflow-chain-packs.md', 'The expansion contract MUST surface the original-id → rewritten-id map so the caller can route adjacent parent-workflow edges to the right expanded node.'),
|
|
341
342
|
).toBe('vendor_acme_generatePRD_a8f3_prd-call');
|
|
342
343
|
});
|
|
343
344
|
});
|
|
@@ -367,7 +368,7 @@ describe('category: workflow-chain expansion — edge rewriting', () => {
|
|
|
367
368
|
expect(fragment.edges).toHaveLength(1);
|
|
368
369
|
expect(
|
|
369
370
|
at(fragment.edges, 0, 'fragment.edges').from,
|
|
370
|
-
'Edge `from` ids that match fragment node ids MUST be rewritten with the same prefix as the nodes themselves.',
|
|
371
|
+
req('openwop.it.workflow-chain-expansion.rewrites-from-and-to-ids-that-reference-fragment-nodes', 'workflow-chain-packs.md', 'Edge `from` ids that match fragment node ids MUST be rewritten with the same prefix as the nodes themselves.'),
|
|
371
372
|
).toBe('vendor_acme_twoStep_e1_first');
|
|
372
373
|
expect(at(fragment.edges, 0, 'fragment.edges').to).toBe('vendor_acme_twoStep_e1_second');
|
|
373
374
|
});
|
|
@@ -387,7 +388,7 @@ describe('category: workflow-chain expansion — edge rewriting', () => {
|
|
|
387
388
|
});
|
|
388
389
|
expect(
|
|
389
390
|
at(fragment.edges, 0, 'fragment.edges').from,
|
|
390
|
-
'Port-name suffix (after the `.`) MUST be preserved verbatim during id rewriting.',
|
|
391
|
+
req('openwop.it.workflow-chain-expansion.preserves-port-name-suffix-nodeid-portname-when-rewriting-edge-refs', 'workflow-chain-packs.md', 'Port-name suffix (after the `.`) MUST be preserved verbatim during id rewriting.'),
|
|
391
392
|
).toBe('vendor_acme_twoStep_e2_first.out');
|
|
392
393
|
expect(at(fragment.edges, 0, 'fragment.edges').to).toBe('vendor_acme_twoStep_e2_second.in');
|
|
393
394
|
});
|
|
@@ -413,7 +414,7 @@ describe('category: workflow-chain expansion — edge rewriting', () => {
|
|
|
413
414
|
});
|
|
414
415
|
expect(
|
|
415
416
|
at(fragment.edges, 0, 'fragment.edges').from,
|
|
416
|
-
'Edge refs to nodes OUTSIDE the fragment MUST pass through unchanged so the parent host can wire adjacent edges post-splice.',
|
|
417
|
+
req('openwop.it.workflow-chain-expansion.leaves-edge-refs-alone-when-they-don-t-match-a-fragment-node-id', 'workflow-chain-packs.md', 'Edge refs to nodes OUTSIDE the fragment MUST pass through unchanged so the parent host can wire adjacent edges post-splice.'),
|
|
417
418
|
).toBe('parent-upstream');
|
|
418
419
|
expect(at(fragment.edges, 0, 'fragment.edges').to).toBe('vendor_acme_twoStep_e3_first');
|
|
419
420
|
expect(at(fragment.edges, 1, 'fragment.edges').from).toBe('vendor_acme_twoStep_e3_second');
|
|
@@ -449,11 +450,11 @@ describe('category: workflow-chain expansion — edge rewriting', () => {
|
|
|
449
450
|
expect(edge.to).toBe('vendor_acme_twoStep_e4_second');
|
|
450
451
|
expect(
|
|
451
452
|
edge.condition,
|
|
452
|
-
'Per RFC 0013 amendment (2026-07-03): a present `FragmentEdge.condition` MUST be carried onto the expanded WorkflowEdge (not dropped at expansion).',
|
|
453
|
+
req('openwop.it.workflow-chain-expansion.preserves-edge-condition-and-triggerrule-onto-the-expanded-workflowedge-rfc-0013', 'RFC 0013', 'Per RFC 0013 amendment (2026-07-03): a present `FragmentEdge.condition` MUST be carried onto the expanded WorkflowEdge (not dropped at expansion).'),
|
|
453
454
|
).toEqual({ type: 'equals', left: 'status', right: 'ok' });
|
|
454
455
|
expect(
|
|
455
456
|
edge.triggerRule,
|
|
456
|
-
'Per RFC 0125 / workflow-chain-packs.md §"Expansion semantics": expansion MUST preserve `FragmentEdge.triggerRule` onto the resulting WorkflowEdge so the scheduler honors the fan-in / error-routing rule.',
|
|
457
|
+
req('openwop.it.workflow-chain-expansion.preserves-edge-condition-and-triggerrule-onto-the-expanded-workflowedge-rfc-0013', 'RFC 0013', 'Per RFC 0125 / workflow-chain-packs.md §"Expansion semantics": expansion MUST preserve `FragmentEdge.triggerRule` onto the resulting WorkflowEdge so the scheduler honors the fan-in / error-routing rule.'),
|
|
457
458
|
).toBe('all_complete');
|
|
458
459
|
});
|
|
459
460
|
|
|
@@ -465,7 +466,7 @@ describe('category: workflow-chain expansion — edge rewriting', () => {
|
|
|
465
466
|
});
|
|
466
467
|
expect(
|
|
467
468
|
at(fragment.edges, 0, 'fragment.edges').triggerRule,
|
|
468
|
-
'When a fragment edge declares no `triggerRule`, the expanded edge MUST NOT carry one — omission is identical to the `all_success` default (preserves wire-shape minimality + prior behavior).',
|
|
469
|
+
req('openwop.it.workflow-chain-expansion.omits-triggerrule-on-the-expanded-edge-when-the-fragment-edge-declares-none-defa', 'workflow-chain-packs.md', 'When a fragment edge declares no `triggerRule`, the expanded edge MUST NOT carry one — omission is identical to the `all_success` default (preserves wire-shape minimality + prior behavior).'),
|
|
469
470
|
).toBeUndefined();
|
|
470
471
|
});
|
|
471
472
|
});
|
|
@@ -491,7 +492,7 @@ describe('category: workflow-chain expansion — capability propagation', () =>
|
|
|
491
492
|
for (const node of fragment.nodes) {
|
|
492
493
|
expect(
|
|
493
494
|
node.capabilities,
|
|
494
|
-
'Per spec §"Expansion semantics" step 8: chain-level `capabilities[]` MUST be copied to every expanded node so existing capability gates (side-effect, streamable) apply uniformly.',
|
|
495
|
+
req('openwop.it.workflow-chain-expansion.copies-chain-capabilities-into-every-expanded-node-s-capabilities', 'workflow-chain-packs.md', 'Per spec §"Expansion semantics" step 8: chain-level `capabilities[]` MUST be copied to every expanded node so existing capability gates (side-effect, streamable) apply uniformly.'),
|
|
495
496
|
).toEqual(['side-effectful', 'cacheable']);
|
|
496
497
|
}
|
|
497
498
|
});
|
|
@@ -504,7 +505,7 @@ describe('category: workflow-chain expansion — capability propagation', () =>
|
|
|
504
505
|
});
|
|
505
506
|
expect(
|
|
506
507
|
at(fragment.nodes, 0, 'fragment.nodes').capabilities,
|
|
507
|
-
'When the chain declares no `capabilities[]`, expanded nodes MUST NOT carry an empty array (preserves wire-shape minimality).',
|
|
508
|
+
req('openwop.it.workflow-chain-expansion.omits-capabilities-on-expanded-nodes-when-the-chain-declares-none', 'workflow-chain-packs.md', 'When the chain declares no `capabilities[]`, expanded nodes MUST NOT carry an empty array (preserves wire-shape minimality).'),
|
|
508
509
|
).toBeUndefined();
|
|
509
510
|
});
|
|
510
511
|
});
|
|
@@ -519,7 +520,7 @@ describe('category: workflow-chain expansion — runtime-invariance contract', (
|
|
|
519
520
|
for (const node of fragment.nodes) {
|
|
520
521
|
expect(
|
|
521
522
|
node.typeId,
|
|
522
|
-
'Per workflow-chain-packs.md §"What hosts dispatch": dispatching runtimes see only concrete `core.*`/published-vendor typeIds — the chain reference MUST NOT be preserved at runtime.',
|
|
523
|
+
req('openwop.it.workflow-chain-expansion.expanded-fragment-carries-only-concrete-typeids-no-chain-reference-survives', 'workflow-chain-packs.md', 'Per workflow-chain-packs.md §"What hosts dispatch": dispatching runtimes see only concrete `core.*`/published-vendor typeIds — the chain reference MUST NOT be preserved at runtime.'),
|
|
523
524
|
).not.toMatch(/^vendor\.acme\.generatePRD$/);
|
|
524
525
|
expect(node.typeId).toBe('core.ai.callPrompt');
|
|
525
526
|
}
|