@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
|
@@ -35,8 +35,7 @@ import { join } from 'node:path';
|
|
|
35
35
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
36
36
|
import addFormats from 'ajv-formats';
|
|
37
37
|
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
38
|
-
|
|
39
|
-
const why = (specRef: string, requirement: string): string => `${specRef} — ${requirement}`;
|
|
38
|
+
import { req } from '../lib/requirement-ids.js';
|
|
40
39
|
|
|
41
40
|
function loadSchema(name: string): Record<string, unknown> {
|
|
42
41
|
return JSON.parse(readFileSync(join(SCHEMAS_DIR, name), 'utf8')) as Record<string, unknown>;
|
|
@@ -54,32 +53,32 @@ describe('tool-descriptor-shape: ToolDescriptor (RFC 0078 §C, server-free)', ()
|
|
|
54
53
|
egress: 'none', approval: 'never', replayPolicy: 'idempotent',
|
|
55
54
|
safetyTier: 'read', costHint: 'low', latencyHint: 'low',
|
|
56
55
|
}),
|
|
57
|
-
|
|
56
|
+
req('openwop.it.tool-descriptor-shape.a-conforming-descriptor-validates', 'tool-catalog.md §C', 'a conforming ToolDescriptor MUST validate'),
|
|
58
57
|
).toBe(true);
|
|
59
58
|
});
|
|
60
59
|
|
|
61
60
|
it('a descriptor missing the REQUIRED safetyTier is rejected', () => {
|
|
62
61
|
expect(
|
|
63
62
|
validate({ toolId: 'x', source: 'mcp' }),
|
|
64
|
-
|
|
63
|
+
req('openwop.it.tool-descriptor-shape.a-descriptor-missing-the-required-safetytier-is-rejected', 'tool-catalog.md §C', 'safetyTier is REQUIRED'),
|
|
65
64
|
).toBe(false);
|
|
66
65
|
});
|
|
67
66
|
|
|
68
67
|
it('enforces exec ⇒ host-extension (RFC 0069; §C-1/§F-4)', () => {
|
|
69
68
|
expect(
|
|
70
69
|
validate({ toolId: 'x-host-acme-shell', source: 'host-extension', safetyTier: 'exec', approval: 'always', egress: 'host-owned' }),
|
|
71
|
-
|
|
70
|
+
req('openwop.it.tool-descriptor-shape.enforces-exec-host-extension-rfc-0069-c-1-f-4', 'tool-catalog.md §C-1', 'an exec tool sourced from host-extension MUST validate'),
|
|
72
71
|
).toBe(true);
|
|
73
72
|
expect(
|
|
74
73
|
validate({ toolId: 'openwop:run-shell', source: 'node-pack', safetyTier: 'exec' }),
|
|
75
|
-
|
|
74
|
+
req('openwop.it.tool-descriptor-shape.enforces-exec-host-extension-rfc-0069-c-1-f-4', 'tool-catalog.md §C-1 / RFC 0069', 'an exec tool MUST NOT be protocol-tier (node-pack)'),
|
|
76
75
|
).toBe(false);
|
|
77
76
|
});
|
|
78
77
|
|
|
79
78
|
it('rejects an unknown property (additionalProperties:false)', () => {
|
|
80
79
|
expect(
|
|
81
80
|
validate({ toolId: 'x', source: 'mcp', safetyTier: 'read', danger: true }),
|
|
82
|
-
|
|
81
|
+
req('openwop.it.tool-descriptor-shape.rejects-an-unknown-property-additionalproperties-false', 'tool-catalog.md §C', 'ToolDescriptor MUST be additionalProperties:false'),
|
|
83
82
|
).toBe(false);
|
|
84
83
|
});
|
|
85
84
|
});
|
|
@@ -90,12 +89,12 @@ describe('tool-descriptor-shape: capability advertisement (RFC 0078 §A, server-
|
|
|
90
89
|
const toolCatalog = (caps.properties as Record<string, { properties?: Record<string, unknown> }>).toolCatalog;
|
|
91
90
|
expect(
|
|
92
91
|
toolCatalog,
|
|
93
|
-
|
|
92
|
+
req('openwop.it.tool-descriptor-shape.capabilities-toolcatalog-is-declared-with-its-sub-flags', 'capabilities.md §toolCatalog', 'capabilities.toolCatalog MUST be declared'),
|
|
94
93
|
).toBeDefined();
|
|
95
94
|
for (const flag of ['supported', 'sources', 'sessionLifecycle']) {
|
|
96
95
|
expect(
|
|
97
96
|
toolCatalog?.properties?.[flag],
|
|
98
|
-
|
|
97
|
+
req('openwop.it.tool-descriptor-shape.capabilities-toolcatalog-is-declared-with-its-sub-flags', 'tool-catalog.md §A', `capabilities.toolCatalog.${flag} MUST be declared`),
|
|
99
98
|
).toBeDefined();
|
|
100
99
|
}
|
|
101
100
|
});
|
|
@@ -112,22 +111,22 @@ describe('tool-descriptor-shape: session lifecycle events (RFC 0078 §D, server-
|
|
|
112
111
|
|
|
113
112
|
it('tool.session.opened validates a content-free record', () => {
|
|
114
113
|
const v = compile('toolSessionOpened');
|
|
115
|
-
expect(v({ sessionId: 's1', toolId: 'mcp:fs.read' }),
|
|
116
|
-
expect(v({ toolId: 'mcp:fs.read' }),
|
|
114
|
+
expect(v({ sessionId: 's1', toolId: 'mcp:fs.read' }), req('openwop.it.tool-descriptor-shape.tool-session-opened-validates-a-content-free-record', 'tool-catalog.md §D', 'opened MUST validate')).toBe(true);
|
|
115
|
+
expect(v({ toolId: 'mcp:fs.read' }), req('openwop.it.tool-descriptor-shape.tool-session-opened-validates-a-content-free-record', 'tool-catalog.md §D', 'opened requires sessionId')).toBe(false);
|
|
117
116
|
});
|
|
118
117
|
|
|
119
118
|
it('tool.session.closed validates + enforces the closed outcome enum', () => {
|
|
120
119
|
const v = compile('toolSessionClosed');
|
|
121
|
-
expect(v({ sessionId: 's1', toolId: 'mcp:fs.read', outcome: 'completed' }),
|
|
122
|
-
expect(v({ sessionId: 's1', toolId: 'mcp:fs.read' }),
|
|
123
|
-
expect(v({ sessionId: 's1', toolId: 'mcp:fs.read', outcome: 'exploded' }),
|
|
120
|
+
expect(v({ sessionId: 's1', toolId: 'mcp:fs.read', outcome: 'completed' }), req('openwop.it.tool-descriptor-shape.tool-session-closed-validates-enforces-the-closed-outcome-enum', 'tool-catalog.md §D', 'closed MUST validate')).toBe(true);
|
|
121
|
+
expect(v({ sessionId: 's1', toolId: 'mcp:fs.read' }), req('openwop.it.tool-descriptor-shape.tool-session-closed-validates-enforces-the-closed-outcome-enum', 'tool-catalog.md §D', 'closed requires outcome')).toBe(false);
|
|
122
|
+
expect(v({ sessionId: 's1', toolId: 'mcp:fs.read', outcome: 'exploded' }), req('openwop.it.tool-descriptor-shape.tool-session-closed-validates-enforces-the-closed-outcome-enum', 'tool-catalog.md §D', 'outcome is a closed enum')).toBe(false);
|
|
124
123
|
});
|
|
125
124
|
|
|
126
125
|
it('both session event names appear in the RunEventType enum', () => {
|
|
127
126
|
const runEvent = loadSchema('run-event.schema.json');
|
|
128
127
|
const enumVals = ((runEvent.$defs as Record<string, { enum?: string[] }>).RunEventType?.enum) ?? [];
|
|
129
128
|
for (const name of ['tool.session.opened', 'tool.session.closed']) {
|
|
130
|
-
expect(enumVals.includes(name),
|
|
129
|
+
expect(enumVals.includes(name), req('openwop.it.tool-descriptor-shape.both-session-event-names-appear-in-the-runeventtype-enum', 'run-event.schema.json', `${name} MUST be in the RunEventType enum`)).toBe(true);
|
|
131
130
|
}
|
|
132
131
|
});
|
|
133
132
|
});
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import { describe, it, expect } from 'vitest';
|
|
15
15
|
import { softSkip } from '../lib/soft-skip.js';
|
|
16
|
-
import { driver } from '../lib/driver.js';
|
|
17
16
|
import { readToolHooksCap, invokeToolHook } from '../lib/toolHooks.js';
|
|
17
|
+
import { req } from '../lib/requirement-ids.js';
|
|
18
18
|
|
|
19
19
|
describe('tool-hooks-authorization-fail-closed (RFC 0064 §C)', () => {
|
|
20
20
|
it('a principal lacking a tool scope is denied and the tool is not invoked', async () => {
|
|
@@ -30,11 +30,11 @@ describe('tool-hooks-authorization-fail-closed (RFC 0064 §C)', () => {
|
|
|
30
30
|
if (res === null) return softSkip('blocked', 'seam absent — soft-skip');
|
|
31
31
|
expect(
|
|
32
32
|
(res.toolReturned ?? {}).status,
|
|
33
|
-
|
|
33
|
+
req('openwop.it.tool-hooks-authorization-fail-closed.a-principal-lacking-a-tool-scope-is-denied-and-the-tool-is-not-invoked', 'RFC 0064 §C', 'a missing/unevaluable tool scope MUST fail closed → status:"forbidden"'),
|
|
34
34
|
).toBe('forbidden');
|
|
35
35
|
expect(
|
|
36
36
|
(res.toolReturned ?? {}).durationMs,
|
|
37
|
-
|
|
37
|
+
req('openwop.it.tool-hooks-authorization-fail-closed.a-principal-lacking-a-tool-scope-is-denied-and-the-tool-is-not-invoked', 'RFC 0064 §C', 'a forbidden call never starts, so durationMs MUST be absent'),
|
|
38
38
|
).toBeUndefined();
|
|
39
39
|
});
|
|
40
40
|
});
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
import { describe, it, expect } from 'vitest';
|
|
14
14
|
import { softSkip } from '../lib/soft-skip.js';
|
|
15
|
-
import { driver } from '../lib/driver.js';
|
|
16
15
|
import { readToolHooksCap, invokeToolHook } from '../lib/toolHooks.js';
|
|
16
|
+
import { req } from '../lib/requirement-ids.js';
|
|
17
17
|
|
|
18
18
|
describe('tool-hooks-content-free (RFC 0064 §B)', () => {
|
|
19
19
|
it('toolCalled carries argsHash; toolReturned carries status + durationMs', async () => {
|
|
@@ -25,16 +25,16 @@ describe('tool-hooks-content-free (RFC 0064 §B)', () => {
|
|
|
25
25
|
const returned = res.toolReturned ?? {};
|
|
26
26
|
expect(
|
|
27
27
|
typeof called.argsHash === 'string' && (called.argsHash as string).length > 0,
|
|
28
|
-
|
|
28
|
+
req('openwop.it.tool-hooks-content-free.toolcalled-carries-argshash-toolreturned-carries-status-durationms', 'RFC 0064 §B', 'agent.toolCalled MUST carry a non-empty argsHash when prePostEvents'),
|
|
29
29
|
).toBe(true);
|
|
30
30
|
expect(
|
|
31
31
|
['ok', 'error', 'forbidden', 'rate_limited'].includes(returned.status as string),
|
|
32
|
-
|
|
32
|
+
req('openwop.it.tool-hooks-content-free.toolcalled-carries-argshash-toolreturned-carries-status-durationms', 'RFC 0064 §B', 'agent.toolReturned MUST carry a tool-hooks status'),
|
|
33
33
|
).toBe(true);
|
|
34
34
|
if (returned.status === 'ok') {
|
|
35
35
|
expect(
|
|
36
36
|
typeof returned.durationMs === 'number' && (returned.durationMs as number) >= 0,
|
|
37
|
-
|
|
37
|
+
req('openwop.it.tool-hooks-content-free.toolcalled-carries-argshash-toolreturned-carries-status-durationms', 'RFC 0064 §B', 'a completed tool call MUST record a non-negative durationMs'),
|
|
38
38
|
).toBe(true);
|
|
39
39
|
}
|
|
40
40
|
});
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
import { describe, it, expect } from 'vitest';
|
|
19
19
|
import { softSkip } from '../lib/soft-skip.js';
|
|
20
|
-
import { driver } from '../lib/driver.js';
|
|
21
20
|
import { readToolHooksCap, invokeToolHook } from '../lib/toolHooks.js';
|
|
21
|
+
import { req } from '../lib/requirement-ids.js';
|
|
22
22
|
|
|
23
23
|
describe('tool-hooks-failure-honesty (RFC 0064 §F)', () => {
|
|
24
24
|
it('a failing tool yields a populated error, never a bare success', async () => {
|
|
@@ -42,20 +42,20 @@ describe('tool-hooks-failure-honesty (RFC 0064 §F)', () => {
|
|
|
42
42
|
// §F: the failure carries a populated `_errorObject { code, message }`.
|
|
43
43
|
expect(
|
|
44
44
|
err !== undefined && typeof err['code'] === 'string' && (err['code'] as string).length > 0 && typeof err['message'] === 'string',
|
|
45
|
-
|
|
45
|
+
req('openwop.it.tool-hooks-failure-honesty.a-failing-tool-yields-a-populated-error-never-a-bare-success', 'RFC 0064 §F', 'a failed tool MUST populate error {code, message} on agent.toolReturned'),
|
|
46
46
|
).toBe(true);
|
|
47
47
|
|
|
48
48
|
// §F: `error` and `outcome` are mutually exclusive.
|
|
49
49
|
expect(
|
|
50
50
|
returned['outcome'] === undefined,
|
|
51
|
-
|
|
51
|
+
req('openwop.it.tool-hooks-failure-honesty.a-failing-tool-yields-a-populated-error-never-a-bare-success', 'RFC 0064 §F', 'error and outcome are mutually exclusive on a failed tool-return'),
|
|
52
52
|
).toBe(true);
|
|
53
53
|
|
|
54
54
|
// §F: a host advertising `prePostEvents` MUST also set `status: 'error'`.
|
|
55
55
|
if (cap['prePostEvents'] === true) {
|
|
56
56
|
expect(
|
|
57
57
|
returned['status'] === 'error',
|
|
58
|
-
|
|
58
|
+
req('openwop.it.tool-hooks-failure-honesty.a-failing-tool-yields-a-populated-error-never-a-bare-success', 'RFC 0064 §F', 'a host advertising prePostEvents MUST set status:"error" on a tool failure'),
|
|
59
59
|
).toBe(true);
|
|
60
60
|
}
|
|
61
61
|
});
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
import { describe, it, expect } from 'vitest';
|
|
13
13
|
import { softSkip } from '../lib/soft-skip.js';
|
|
14
|
-
import { driver } from '../lib/driver.js';
|
|
15
14
|
import { readToolHooksCap, invokeToolHook } from '../lib/toolHooks.js';
|
|
15
|
+
import { req } from '../lib/requirement-ids.js';
|
|
16
16
|
|
|
17
17
|
describe('tool-hooks-rate-limit (RFC 0064 §D)', () => {
|
|
18
18
|
it('an exhausted (principal, tool) bucket yields status:"rate_limited"', async () => {
|
|
@@ -27,7 +27,7 @@ describe('tool-hooks-rate-limit (RFC 0064 §D)', () => {
|
|
|
27
27
|
if (res === null) return softSkip('blocked', 'seam absent — soft-skip');
|
|
28
28
|
expect(
|
|
29
29
|
(res.toolReturned ?? {}).status,
|
|
30
|
-
|
|
30
|
+
req('openwop.it.tool-hooks-rate-limit.an-exhausted-principal-tool-bucket-yields-status-rate-limited', 'RFC 0064 §D', 'an exhausted token bucket MUST yield status:"rate_limited" without invoking the tool'),
|
|
31
31
|
).toBe('rate_limited');
|
|
32
32
|
});
|
|
33
33
|
});
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
import { describe, it, expect } from 'vitest';
|
|
14
14
|
import { softSkip } from '../lib/soft-skip.js';
|
|
15
|
-
import { driver } from '../lib/driver.js';
|
|
16
15
|
import { readToolHooksCap, invokeToolHook } from '../lib/toolHooks.js';
|
|
16
|
+
import { req } from '../lib/requirement-ids.js';
|
|
17
17
|
|
|
18
18
|
const CANARY = 'sk-canary-rfc0064-do-not-leak-xyz789';
|
|
19
19
|
|
|
@@ -29,7 +29,7 @@ describe('tool-hooks-secret-redaction (RFC 0064 §E)', () => {
|
|
|
29
29
|
if (res === null) return softSkip('blocked', 'seam absent — soft-skip');
|
|
30
30
|
expect(
|
|
31
31
|
JSON.stringify(res).includes(CANARY),
|
|
32
|
-
|
|
32
|
+
req('openwop.it.tool-hooks-secret-redaction.a-secret-shaped-tool-arg-never-appears-in-the-emitted-events', 'RFC 0064 §B', 'a resolved secret MUST be redacted before hashing; the raw value MUST NOT appear in argsHash or any emitted field (SR-1)'),
|
|
33
33
|
).toBe(false);
|
|
34
34
|
});
|
|
35
35
|
});
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
import { describe, it, expect } from 'vitest';
|
|
14
14
|
import { softSkip } from '../lib/soft-skip.js';
|
|
15
|
-
import { driver } from '../lib/driver.js';
|
|
16
15
|
import { readToolHooksCap } from '../lib/toolHooks.js';
|
|
16
|
+
import { req } from '../lib/requirement-ids.js';
|
|
17
17
|
|
|
18
18
|
describe('tool-hooks-shape: advertisement (RFC 0064 §A)', () => {
|
|
19
19
|
it('capabilities.toolHooks is absent or a well-formed object', async () => {
|
|
@@ -21,13 +21,13 @@ describe('tool-hooks-shape: advertisement (RFC 0064 §A)', () => {
|
|
|
21
21
|
if (cap === null) return softSkip('inapplicable', 'not advertised — valid');
|
|
22
22
|
expect(
|
|
23
23
|
typeof cap.supported,
|
|
24
|
-
|
|
24
|
+
req('openwop.it.tool-hooks-shape.capabilities-toolhooks-is-absent-or-a-well-formed-object', 'capabilities.schema.json §toolHooks', 'toolHooks.supported MUST be a boolean when the block is present'),
|
|
25
25
|
).toBe('boolean');
|
|
26
26
|
for (const k of ['prePostEvents', 'perToolAuthorization', 'perToolRateLimit'] as const) {
|
|
27
27
|
if (cap[k] !== undefined) {
|
|
28
28
|
expect(
|
|
29
29
|
typeof cap[k],
|
|
30
|
-
|
|
30
|
+
req('openwop.it.tool-hooks-shape.capabilities-toolhooks-is-absent-or-a-well-formed-object', 'capabilities.schema.json §toolHooks', `toolHooks.${k} MUST be a boolean when present`),
|
|
31
31
|
).toBe('boolean');
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { describe, it, expect } from 'vitest';
|
|
22
|
-
import { driver } from '../lib/driver.js';
|
|
23
22
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
24
23
|
import { readToolCatalogCap, driveToolSession, TOOL_CONTENT_FORBIDDEN } from '../lib/toolCatalog.js';
|
|
25
24
|
import { queryTestEvents, isEventLogSeamAvailable, resetTestSeam } from '../lib/event-log-query.js';
|
|
25
|
+
import { req } from '../lib/requirement-ids.js';
|
|
26
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
26
27
|
|
|
27
28
|
const SESSION_OUTCOMES = ['completed', 'failed', 'aborted', 'expired'];
|
|
28
29
|
/** RFC 0064 tool-call event family bracketed by a tool session. */
|
|
@@ -35,21 +36,21 @@ describe('tool-session-lifecycle (RFC 0078 §D)', () => {
|
|
|
35
36
|
const lifecycle = cap?.sessionLifecycle === true || (typeof cap?.sessionLifecycle === 'object' && cap?.sessionLifecycle !== null);
|
|
36
37
|
if (!behaviorGate('openwop-tool-session-lifecycle', lifecycle)) return;
|
|
37
38
|
|
|
38
|
-
if (!(await isEventLogSeamAvailable())) return; // event-log seam absent — soft-skip
|
|
39
|
+
if (!(await isEventLogSeamAvailable())) return softSkip('blocked', 'precondition not met — `!(await isEventLogSeamAvailable())` returned early (event-log seam absent — soft-skip) (seam, prior step, or fixture unavailable)'); // event-log seam absent — soft-skip
|
|
39
40
|
const res = await driveToolSession({});
|
|
40
|
-
if (res === null || !res.runId) return; // session seam absent — soft-skip
|
|
41
|
+
if (res === null || !res.runId) return softSkip('blocked', 'precondition not met — `res === null || !res.runId` returned early (session seam absent — soft-skip) (seam, prior step, or fixture unavailable)'); // session seam absent — soft-skip
|
|
41
42
|
|
|
42
43
|
const q = await queryTestEvents(res.runId);
|
|
43
|
-
if (!q.ok) return;
|
|
44
|
+
if (!q.ok) return softSkip('blocked', 'precondition not met — `!q.ok` returned early (seam, prior step, or fixture unavailable)');
|
|
44
45
|
const events = q.events.slice().sort((a, b) => a.sequence - b.sequence);
|
|
45
46
|
|
|
46
47
|
const opened = events.filter((e) => e.type === 'tool.session.opened');
|
|
47
48
|
const closed = events.filter((e) => e.type === 'tool.session.closed');
|
|
48
49
|
expect(
|
|
49
50
|
opened.length >= 1 && closed.length >= 1,
|
|
50
|
-
|
|
51
|
+
req('openwop.it.tool-session-lifecycle.brackets-the-call-events-with-tool-session-opened-first-closed-last-one-sessioni', 'tool-catalog.md §D', 'a tool session MUST emit tool.session.opened + tool.session.closed'),
|
|
51
52
|
).toBe(true);
|
|
52
|
-
if (opened.length === 0 || closed.length === 0) return;
|
|
53
|
+
if (opened.length === 0 || closed.length === 0) return softSkip('blocked', 'precondition not met — `opened.length === 0 || closed.length === 0` returned early (seam, prior step, or fixture unavailable)');
|
|
53
54
|
|
|
54
55
|
const open = opened[0]!;
|
|
55
56
|
const close = closed[closed.length - 1]!;
|
|
@@ -59,16 +60,16 @@ describe('tool-session-lifecycle (RFC 0078 §D)', () => {
|
|
|
59
60
|
if (calls.length > 0) {
|
|
60
61
|
expect(
|
|
61
62
|
open.sequence < calls[0]!.sequence,
|
|
62
|
-
|
|
63
|
+
req('openwop.it.tool-session-lifecycle.brackets-the-call-events-with-tool-session-opened-first-closed-last-one-sessioni', 'RFC 0078 §D', 'tool.session.opened MUST precede the first call event'),
|
|
63
64
|
).toBe(true);
|
|
64
65
|
expect(
|
|
65
66
|
close.sequence > calls[calls.length - 1]!.sequence,
|
|
66
|
-
|
|
67
|
+
req('openwop.it.tool-session-lifecycle.brackets-the-call-events-with-tool-session-opened-first-closed-last-one-sessioni', 'RFC 0078 §D', 'tool.session.closed MUST follow the last call event'),
|
|
67
68
|
).toBe(true);
|
|
68
69
|
} else {
|
|
69
70
|
expect(
|
|
70
71
|
open.sequence < close.sequence,
|
|
71
|
-
|
|
72
|
+
req('openwop.it.tool-session-lifecycle.brackets-the-call-events-with-tool-session-opened-first-closed-last-one-sessioni', 'RFC 0078 §D', 'tool.session.opened MUST precede tool.session.closed'),
|
|
72
73
|
).toBe(true);
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -77,17 +78,17 @@ describe('tool-session-lifecycle (RFC 0078 §D)', () => {
|
|
|
77
78
|
const closeSid = close.payload.sessionId;
|
|
78
79
|
expect(
|
|
79
80
|
typeof openSid === 'string' && openSid === closeSid,
|
|
80
|
-
|
|
81
|
+
req('openwop.it.tool-session-lifecycle.brackets-the-call-events-with-tool-session-opened-first-closed-last-one-sessioni', 'run-event-payloads.schema.json#toolSession*', 'the bracket MUST share one sessionId'),
|
|
81
82
|
).toBe(true);
|
|
82
83
|
expect(
|
|
83
84
|
typeof open.payload.toolId === 'string' && typeof close.payload.toolId === 'string',
|
|
84
|
-
|
|
85
|
+
req('openwop.it.tool-session-lifecycle.brackets-the-call-events-with-tool-session-opened-first-closed-last-one-sessioni', 'run-event-payloads.schema.json#toolSession*', 'tool.session.* MUST carry a toolId'),
|
|
85
86
|
).toBe(true);
|
|
86
87
|
|
|
87
88
|
// Closed outcome enum discipline.
|
|
88
89
|
expect(
|
|
89
90
|
typeof close.payload.outcome === 'string' && SESSION_OUTCOMES.includes(close.payload.outcome as string),
|
|
90
|
-
|
|
91
|
+
req('openwop.it.tool-session-lifecycle.brackets-the-call-events-with-tool-session-opened-first-closed-last-one-sessioni', 'run-event-payloads.schema.json#toolSessionClosed', 'outcome MUST be in the closed enum'),
|
|
91
92
|
).toBe(true);
|
|
92
93
|
|
|
93
94
|
// Content-free: identifiers + metadata only.
|
|
@@ -95,7 +96,7 @@ describe('tool-session-lifecycle (RFC 0078 §D)', () => {
|
|
|
95
96
|
for (const forbidden of TOOL_CONTENT_FORBIDDEN) {
|
|
96
97
|
expect(
|
|
97
98
|
!(forbidden in evt.payload),
|
|
98
|
-
|
|
99
|
+
req('openwop.it.tool-session-lifecycle.brackets-the-call-events-with-tool-session-opened-first-closed-last-one-sessioni', 'RFC 0078 §F (SR-1)', `tool.session.* MUST be content-free (no ${forbidden})`),
|
|
99
100
|
).toBe(true);
|
|
100
101
|
}
|
|
101
102
|
}
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
|
|
30
30
|
import { describe, it, expect } from 'vitest';
|
|
31
31
|
import { seamAbsent } from '../lib/soft-skip.js';
|
|
32
|
-
import { driver } from '../lib/driver.js';
|
|
33
32
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
34
33
|
import {
|
|
35
34
|
isTriggerBridgeProfileAdvertised,
|
|
@@ -38,6 +37,7 @@ import {
|
|
|
38
37
|
SUBSCRIPTION_STATES,
|
|
39
38
|
} from '../lib/triggerBridge.js';
|
|
40
39
|
import { queryTestEvents, requireEvents, isEventLogSeamAvailable, resetTestSeam } from '../lib/event-log-query.js';
|
|
40
|
+
import { req } from '../lib/requirement-ids.js';
|
|
41
41
|
|
|
42
42
|
const CONTENT_FREE_FORBIDDEN = ['body', 'headers', 'payload', 'secret', 'credentials', 'token', 'apiKey'];
|
|
43
43
|
|
|
@@ -45,7 +45,7 @@ function expectContentFree(payload: Record<string, unknown>, where: string): voi
|
|
|
45
45
|
for (const f of CONTENT_FREE_FORBIDDEN) {
|
|
46
46
|
expect(
|
|
47
47
|
!(f in payload),
|
|
48
|
-
|
|
48
|
+
req('openwop.it.trigger-bridge-delivery.de-dups-by-dedupkey-retries-to-dead-letter-and-links-delivery-run-causation', 'RFC 0083 §C (SR-1)', `${where} MUST be content-free (no ${f})`),
|
|
49
49
|
).toBe(true);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -72,12 +72,12 @@ describe('trigger-bridge-delivery (RFC 0083 §C)', () => {
|
|
|
72
72
|
);
|
|
73
73
|
expect(
|
|
74
74
|
deliveredForKey.length === 1,
|
|
75
|
-
|
|
75
|
+
req('openwop.it.trigger-bridge-delivery.de-dups-by-dedupkey-retries-to-dead-letter-and-links-delivery-run-causation', 'trigger-bridge.md §C-1', 'a repeated dedupKey MUST be effectively-once — EXACTLY one delivered attempt (not zero, not two)'),
|
|
76
76
|
).toBe(true);
|
|
77
77
|
for (const e of dedupEvents) {
|
|
78
78
|
expect(
|
|
79
79
|
typeof e.payload.outcome === 'string' && DELIVERY_OUTCOMES.includes(e.payload.outcome as string),
|
|
80
|
-
|
|
80
|
+
req('openwop.it.trigger-bridge-delivery.de-dups-by-dedupkey-retries-to-dead-letter-and-links-delivery-run-causation', 'run-event-payloads.schema.json#triggerDeliveryAttempted', 'outcome MUST be delivered|retrying|dead-lettered'),
|
|
81
81
|
).toBe(true);
|
|
82
82
|
expectContentFree(e.payload, 'trigger.delivery.attempted');
|
|
83
83
|
}
|
|
@@ -86,7 +86,7 @@ describe('trigger-bridge-delivery (RFC 0083 §C)', () => {
|
|
|
86
86
|
const exhaust = await driveDelivery({ scenario: 'exhaust', source: 'webhook' });
|
|
87
87
|
expect(
|
|
88
88
|
exhaust !== null,
|
|
89
|
-
|
|
89
|
+
req('openwop.it.trigger-bridge-delivery.de-dups-by-dedupkey-retries-to-dead-letter-and-links-delivery-run-causation', 'trigger-bridge.md §C-2', 'the exhaust scenario MUST be wired when the delivery seam is'),
|
|
90
90
|
).toBe(true);
|
|
91
91
|
const exKey = exhaust!.runId ?? '__exhaust__';
|
|
92
92
|
const exhaustEvents = requireEvents(
|
|
@@ -95,12 +95,12 @@ describe('trigger-bridge-delivery (RFC 0083 §C)', () => {
|
|
|
95
95
|
);
|
|
96
96
|
expect(
|
|
97
97
|
exhaustEvents.length >= 1,
|
|
98
|
-
|
|
98
|
+
req('openwop.it.trigger-bridge-delivery.de-dups-by-dedupkey-retries-to-dead-letter-and-links-delivery-run-causation', 'trigger-bridge.md §C-2', 'an exhausted delivery MUST emit ≥1 trigger.delivery.attempted'),
|
|
99
99
|
).toBe(true);
|
|
100
100
|
const terminal = exhaustEvents.sort((a, b) => a.sequence - b.sequence)[exhaustEvents.length - 1]!;
|
|
101
101
|
expect(
|
|
102
102
|
terminal.payload.outcome === 'dead-lettered',
|
|
103
|
-
|
|
103
|
+
req('openwop.it.trigger-bridge-delivery.de-dups-by-dedupkey-retries-to-dead-letter-and-links-delivery-run-causation', 'trigger-bridge.md §C-2', 'an exhausted retry policy MUST terminate in a dead-lettered delivery'),
|
|
104
104
|
).toBe(true);
|
|
105
105
|
const stateEvents = requireEvents(
|
|
106
106
|
await queryTestEvents(exKey, { type: 'trigger.subscription.state.changed' }),
|
|
@@ -108,16 +108,16 @@ describe('trigger-bridge-delivery (RFC 0083 §C)', () => {
|
|
|
108
108
|
);
|
|
109
109
|
expect(
|
|
110
110
|
stateEvents.length >= 1,
|
|
111
|
-
|
|
111
|
+
req('openwop.it.trigger-bridge-delivery.de-dups-by-dedupkey-retries-to-dead-letter-and-links-delivery-run-causation', 'trigger-bridge.md §B', 'exhaustion MUST emit ≥1 trigger.subscription.state.changed'),
|
|
112
112
|
).toBe(true);
|
|
113
113
|
expect(
|
|
114
114
|
stateEvents.some((e) => e.payload.toState === 'dead-lettered'),
|
|
115
|
-
|
|
115
|
+
req('openwop.it.trigger-bridge-delivery.de-dups-by-dedupkey-retries-to-dead-letter-and-links-delivery-run-causation', 'trigger-bridge.md §B', 'the subscription MUST transition to dead-lettered on exhaustion'),
|
|
116
116
|
).toBe(true);
|
|
117
117
|
for (const e of stateEvents) {
|
|
118
118
|
expect(
|
|
119
119
|
typeof e.payload.toState === 'string' && SUBSCRIPTION_STATES.includes(e.payload.toState as string),
|
|
120
|
-
|
|
120
|
+
req('openwop.it.trigger-bridge-delivery.de-dups-by-dedupkey-retries-to-dead-letter-and-links-delivery-run-causation', 'trigger-bridge.md §B', 'toState MUST be in the four-state vocabulary'),
|
|
121
121
|
).toBe(true);
|
|
122
122
|
expectContentFree(e.payload, 'trigger.subscription.state.changed');
|
|
123
123
|
}
|
|
@@ -130,7 +130,7 @@ describe('trigger-bridge-delivery (RFC 0083 §C)', () => {
|
|
|
130
130
|
const delivered = await driveDelivery({ scenario: 'deliver', source: 'schedule' });
|
|
131
131
|
expect(
|
|
132
132
|
delivered !== null && typeof delivered.runId === 'string' && (delivered.runId as string).length > 0,
|
|
133
|
-
|
|
133
|
+
req('openwop.it.trigger-bridge-delivery.de-dups-by-dedupkey-retries-to-dead-letter-and-links-delivery-run-causation', 'trigger-bridge.md §C', 'a successful delivery MUST create a run'),
|
|
134
134
|
).toBe(true);
|
|
135
135
|
const deliveredRunId = delivered!.runId as string;
|
|
136
136
|
const attemptEvents = requireEvents(
|
|
@@ -140,7 +140,7 @@ describe('trigger-bridge-delivery (RFC 0083 §C)', () => {
|
|
|
140
140
|
const deliveredEvent = attemptEvents.find((e) => e.payload.outcome === 'delivered');
|
|
141
141
|
expect(
|
|
142
142
|
deliveredEvent !== undefined,
|
|
143
|
-
|
|
143
|
+
req('openwop.it.trigger-bridge-delivery.de-dups-by-dedupkey-retries-to-dead-letter-and-links-delivery-run-causation', 'trigger-bridge.md §C-1', 'a successful delivery MUST emit a trigger.delivery.attempted{outcome:delivered}'),
|
|
144
144
|
).toBe(true);
|
|
145
145
|
const runStartedEvents = requireEvents(
|
|
146
146
|
await queryTestEvents(deliveredRunId, { type: 'run.started' }),
|
|
@@ -148,14 +148,14 @@ describe('trigger-bridge-delivery (RFC 0083 §C)', () => {
|
|
|
148
148
|
);
|
|
149
149
|
expect(
|
|
150
150
|
runStartedEvents.length >= 1,
|
|
151
|
-
|
|
151
|
+
req('openwop.it.trigger-bridge-delivery.de-dups-by-dedupkey-retries-to-dead-letter-and-links-delivery-run-causation', 'trigger-bridge.md §C', 'a delivered run MUST emit run.started'),
|
|
152
152
|
).toBe(true);
|
|
153
153
|
const runStarted = runStartedEvents.sort((a, b) => a.sequence - b.sequence)[0]!;
|
|
154
154
|
expect(
|
|
155
155
|
typeof runStarted.causationId === 'string' &&
|
|
156
156
|
(runStarted.causationId as string).length > 0 &&
|
|
157
157
|
runStarted.causationId === deliveredEvent!.eventId,
|
|
158
|
-
|
|
158
|
+
req('openwop.it.trigger-bridge-delivery.de-dups-by-dedupkey-retries-to-dead-letter-and-links-delivery-run-causation', 'trigger-bridge.md §C / RFC 0040', 'run.started.causationId MUST EQUAL the delivery id (the trigger.delivery.attempted{delivered} eventId) — resolvable via /ancestry'),
|
|
159
159
|
).toBe(true);
|
|
160
160
|
|
|
161
161
|
await resetTestSeam();
|
|
@@ -36,8 +36,7 @@ import Ajv2020 from 'ajv/dist/2020.js';
|
|
|
36
36
|
import addFormats from 'ajv-formats';
|
|
37
37
|
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
38
38
|
import { deriveProfiles } from '../lib/profiles.js';
|
|
39
|
-
|
|
40
|
-
const why = (specRef: string, requirement: string): string => `${specRef} — ${requirement}`;
|
|
39
|
+
import { req } from '../lib/requirement-ids.js';
|
|
41
40
|
|
|
42
41
|
function loadSchema(name: string): Record<string, unknown> {
|
|
43
42
|
return JSON.parse(readFileSync(join(SCHEMAS_DIR, name), 'utf8')) as Record<string, unknown>;
|
|
@@ -53,26 +52,26 @@ describe('trigger-bridge-shape: TriggerSubscription (RFC 0083 §B, server-free)'
|
|
|
53
52
|
it('a conforming subscription validates', () => {
|
|
54
53
|
expect(
|
|
55
54
|
validate({ subscriptionId: 'sub-1', source: 'webhook', state: 'active', dedupEnabled: true, retryPolicy: { maxAttempts: 8, backoff: 'exponential' }, webhookId: 'wh-1', secretFingerprint: 'fp-abc' }),
|
|
56
|
-
|
|
55
|
+
req('openwop.it.trigger-bridge-shape.a-conforming-subscription-validates', 'trigger-bridge.md §B', 'a conforming TriggerSubscription MUST validate'),
|
|
57
56
|
).toBe(true);
|
|
58
57
|
});
|
|
59
58
|
|
|
60
59
|
it('rejects a missing REQUIRED state, an out-of-enum source, and an unknown property', () => {
|
|
61
|
-
expect(validate({ subscriptionId: 's', source: 'webhook' }),
|
|
62
|
-
expect(validate({ subscriptionId: 's', source: 'carrier-pigeon', state: 'active' }),
|
|
63
|
-
expect(validate({ subscriptionId: 's', source: 'webhook', state: 'active', body: 'inbound' }),
|
|
60
|
+
expect(validate({ subscriptionId: 's', source: 'webhook' }), req('openwop.it.trigger-bridge-shape.rejects-a-missing-required-state-an-out-of-enum-source-and-an-unknown-property', 'trigger-bridge.md §B', 'state is REQUIRED')).toBe(false);
|
|
61
|
+
expect(validate({ subscriptionId: 's', source: 'carrier-pigeon', state: 'active' }), req('openwop.it.trigger-bridge-shape.rejects-a-missing-required-state-an-out-of-enum-source-and-an-unknown-property', 'trigger-bridge.md §B', 'source is a closed enum')).toBe(false);
|
|
62
|
+
expect(validate({ subscriptionId: 's', source: 'webhook', state: 'active', body: 'inbound' }), req('openwop.it.trigger-bridge-shape.rejects-a-missing-required-state-an-out-of-enum-source-and-an-unknown-property', 'trigger-bridge.md §B', 'TriggerSubscription MUST be additionalProperties:false')).toBe(false);
|
|
64
63
|
});
|
|
65
64
|
|
|
66
65
|
it('secretFingerprint MUST be bounded — a full 64-hex (unsalted-hash-smelling) digest is rejected', () => {
|
|
67
66
|
const truncated = 'a1b2c3d4e5f6a7b8'; // 16 hex — a truncated host-keyed fingerprint
|
|
68
67
|
const fullDigest = 'a'.repeat(64); // 64 hex — smells like an unsalted SHA256(secret)
|
|
69
|
-
expect(validate({ subscriptionId: 's', source: 'webhook', state: 'active', secretFingerprint: truncated }),
|
|
70
|
-
expect(validate({ subscriptionId: 's', source: 'webhook', state: 'active', secretFingerprint: fullDigest }),
|
|
68
|
+
expect(validate({ subscriptionId: 's', source: 'webhook', state: 'active', secretFingerprint: truncated }), req('openwop.it.trigger-bridge-shape.secretfingerprint-must-be-bounded-a-full-64-hex-unsalted-hash-smelling-digest-is', 'trigger-bridge.md §B', 'a truncated fingerprint MUST validate')).toBe(true);
|
|
69
|
+
expect(validate({ subscriptionId: 's', source: 'webhook', state: 'active', secretFingerprint: fullDigest }), req('openwop.it.trigger-bridge-shape.secretfingerprint-must-be-bounded-a-full-64-hex-unsalted-hash-smelling-digest-is', 'SR-1', 'a full 64-hex digest MUST be rejected (brute-force oracle)')).toBe(false);
|
|
71
70
|
});
|
|
72
71
|
|
|
73
72
|
it('the state enum is exactly the four §B states', () => {
|
|
74
73
|
const stateEnum = ((sub.properties as Record<string, { enum?: string[] }>).state?.enum) ?? [];
|
|
75
|
-
expect([...stateEnum].sort(),
|
|
74
|
+
expect([...stateEnum].sort(), req('openwop.it.trigger-bridge-shape.the-state-enum-is-exactly-the-four-b-states', 'trigger-bridge.md §B', 'the four-state vocabulary MUST be stable')).toEqual([...STATES].sort());
|
|
76
75
|
});
|
|
77
76
|
});
|
|
78
77
|
|
|
@@ -87,23 +86,23 @@ describe('trigger-bridge-shape: trigger.* events (RFC 0083 §C, server-free)', (
|
|
|
87
86
|
|
|
88
87
|
it('trigger.subscription.state.changed validates + reason is a CLOSED enum (no URL-bearing free text)', () => {
|
|
89
88
|
const v = compile('triggerSubscriptionStateChanged');
|
|
90
|
-
expect(v({ subscriptionId: 's', source: 'webhook', fromState: 'active', toState: 'dead-lettered', reason: 'retry-exhausted' }),
|
|
91
|
-
expect(v({ subscriptionId: 's', source: 'webhook', fromState: 'active' }),
|
|
92
|
-
expect(v({ subscriptionId: 's', source: 'webhook', fromState: 'active', toState: 'failed', reason: 'https://attacker.example/leak?token=sk' }),
|
|
89
|
+
expect(v({ subscriptionId: 's', source: 'webhook', fromState: 'active', toState: 'dead-lettered', reason: 'retry-exhausted' }), req('openwop.it.trigger-bridge-shape.trigger-subscription-state-changed-validates-reason-is-a-closed-enum-no-url-bear', 'trigger-bridge.md §C', 'state-changed MUST validate')).toBe(true);
|
|
90
|
+
expect(v({ subscriptionId: 's', source: 'webhook', fromState: 'active' }), req('openwop.it.trigger-bridge-shape.trigger-subscription-state-changed-validates-reason-is-a-closed-enum-no-url-bear', 'trigger-bridge.md §C', 'toState is REQUIRED')).toBe(false);
|
|
91
|
+
expect(v({ subscriptionId: 's', source: 'webhook', fromState: 'active', toState: 'failed', reason: 'https://attacker.example/leak?token=sk' }), req('openwop.it.trigger-bridge-shape.trigger-subscription-state-changed-validates-reason-is-a-closed-enum-no-url-bear', 'SR-1', 'a free-form / URL-bearing reason MUST be rejected')).toBe(false);
|
|
93
92
|
});
|
|
94
93
|
|
|
95
94
|
it('trigger.delivery.attempted validates + enforces the outcome enum', () => {
|
|
96
95
|
const v = compile('triggerDeliveryAttempted');
|
|
97
|
-
expect(v({ subscriptionId: 's', dedupKey: 'evt-9f3', attempt: 1, outcome: 'delivered', runId: 'run_x' }),
|
|
98
|
-
expect(v({ subscriptionId: 's', dedupKey: 'evt-9f3', attempt: 1 }),
|
|
99
|
-
expect(v({ subscriptionId: 's', dedupKey: 'evt-9f3', attempt: 1, outcome: 'exploded' }),
|
|
96
|
+
expect(v({ subscriptionId: 's', dedupKey: 'evt-9f3', attempt: 1, outcome: 'delivered', runId: 'run_x' }), req('openwop.it.trigger-bridge-shape.trigger-delivery-attempted-validates-enforces-the-outcome-enum', 'trigger-bridge.md §C', 'delivery-attempted MUST validate')).toBe(true);
|
|
97
|
+
expect(v({ subscriptionId: 's', dedupKey: 'evt-9f3', attempt: 1 }), req('openwop.it.trigger-bridge-shape.trigger-delivery-attempted-validates-enforces-the-outcome-enum', 'trigger-bridge.md §C', 'outcome is REQUIRED')).toBe(false);
|
|
98
|
+
expect(v({ subscriptionId: 's', dedupKey: 'evt-9f3', attempt: 1, outcome: 'exploded' }), req('openwop.it.trigger-bridge-shape.trigger-delivery-attempted-validates-enforces-the-outcome-enum', 'trigger-bridge.md §C', 'outcome is a closed enum')).toBe(false);
|
|
100
99
|
});
|
|
101
100
|
|
|
102
101
|
it('both trigger event names appear in the RunEventType enum', () => {
|
|
103
102
|
const runEvent = loadSchema('run-event.schema.json');
|
|
104
103
|
const enumVals = ((runEvent.$defs as Record<string, { enum?: string[] }>).RunEventType?.enum) ?? [];
|
|
105
104
|
for (const name of ['trigger.subscription.state.changed', 'trigger.delivery.attempted']) {
|
|
106
|
-
expect(enumVals.includes(name),
|
|
105
|
+
expect(enumVals.includes(name), req('openwop.it.trigger-bridge-shape.both-trigger-event-names-appear-in-the-runeventtype-enum', 'run-event.schema.json', `${name} MUST be in the RunEventType enum`)).toBe(true);
|
|
107
106
|
}
|
|
108
107
|
});
|
|
109
108
|
});
|
|
@@ -112,8 +111,8 @@ describe('trigger-bridge-shape: capability + profile derivation (RFC 0083 §A/§
|
|
|
112
111
|
it('capabilities.triggerBridge + webhooks.durable are declared', () => {
|
|
113
112
|
const caps = loadSchema('capabilities.schema.json');
|
|
114
113
|
const props = caps.properties as Record<string, { properties?: Record<string, unknown> }>;
|
|
115
|
-
expect(props.triggerBridge?.properties?.supported,
|
|
116
|
-
expect(props.webhooks?.properties?.durable,
|
|
114
|
+
expect(props.triggerBridge?.properties?.supported, req('openwop.it.trigger-bridge-shape.capabilities-triggerbridge-webhooks-durable-are-declared', 'trigger-bridge.md §A', 'triggerBridge.supported MUST be declared')).toBeDefined();
|
|
115
|
+
expect(props.webhooks?.properties?.durable, req('openwop.it.trigger-bridge-shape.capabilities-triggerbridge-webhooks-durable-are-declared', 'trigger-bridge.md §A', 'webhooks.durable MUST be declared')).toBeDefined();
|
|
117
116
|
});
|
|
118
117
|
|
|
119
118
|
const coreBase = {
|
|
@@ -125,11 +124,11 @@ describe('trigger-bridge-shape: capability + profile derivation (RFC 0083 §A/§
|
|
|
125
124
|
|
|
126
125
|
it('deriveProfiles surfaces openwop-trigger-bridge for bridge + deadLetter + a durable source', () => {
|
|
127
126
|
const c = { ...coreBase, triggerBridge: { supported: true }, deadLetter: { supported: true }, queueBus: { supported: true } } as Record<string, unknown>;
|
|
128
|
-
expect(deriveProfiles(c).includes('openwop-trigger-bridge'),
|
|
127
|
+
expect(deriveProfiles(c).includes('openwop-trigger-bridge'), req('openwop.it.trigger-bridge-shape.deriveprofiles-surfaces-openwop-trigger-bridge-for-bridge-deadletter-a-durable-s', 'profiles.md §openwop-trigger-bridge', 'bridge + sink + durable source MUST derive the profile')).toBe(true);
|
|
129
128
|
});
|
|
130
129
|
|
|
131
130
|
it('deriveProfiles withholds openwop-trigger-bridge when the dead-letter sink is absent', () => {
|
|
132
131
|
const c = { ...coreBase, triggerBridge: { supported: true }, webhooks: { durable: true } } as Record<string, unknown>;
|
|
133
|
-
expect(deriveProfiles(c).includes('openwop-trigger-bridge'),
|
|
132
|
+
expect(deriveProfiles(c).includes('openwop-trigger-bridge'), req('openwop.it.trigger-bridge-shape.deriveprofiles-withholds-openwop-trigger-bridge-when-the-dead-letter-sink-is-abs', 'profiles.md §openwop-trigger-bridge', 'no deadLetter sink ⇒ MUST NOT derive the profile')).toBe(false);
|
|
134
133
|
});
|
|
135
134
|
});
|