@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
|
@@ -37,6 +37,8 @@ import { readErrorCode, readRetriable } from '../lib/error-envelope.js';
|
|
|
37
37
|
import { driver } from '../lib/driver.js';
|
|
38
38
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
39
39
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
40
|
+
import { req } from '../lib/requirement-ids.js';
|
|
41
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
40
42
|
|
|
41
43
|
const PROFILE = 'openwop-workload-identity';
|
|
42
44
|
/**
|
|
@@ -88,7 +90,7 @@ describe('RFC 0154 §A — workload identity resolution (capability-gated behavi
|
|
|
88
90
|
const r = await resolve({ identity: { ...IDENTITY, audience: 'openwop-host' } });
|
|
89
91
|
expect(
|
|
90
92
|
r,
|
|
91
|
-
|
|
93
|
+
req('openwop.it.workload-identity-behavior.the-seam-is-wired-or-the-requirement-is-blocked-rather-than-skipped',
|
|
92
94
|
'spec/v1/host-sample-test-seams.md §20',
|
|
93
95
|
'a host advertising `auth.workloadIdentity` MUST expose the resolution seam. §A\'s ' +
|
|
94
96
|
'requirements — verify, bind, resolve-before-authorize, fail closed — are invisible from ' +
|
|
@@ -102,21 +104,21 @@ describe('RFC 0154 §A — workload identity resolution (capability-gated behavi
|
|
|
102
104
|
it('a verified identity resolves to a principal', async () => {
|
|
103
105
|
if (!behaviorGate(PROFILE, (await caps())?.supported === true)) return;
|
|
104
106
|
const r = await resolve({ identity: { ...IDENTITY, audience: 'openwop-host' } });
|
|
105
|
-
if (r === null) return;
|
|
106
|
-
expect(r.status,
|
|
107
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
108
|
+
expect(r.status, req('openwop.it.workload-identity-behavior.a-verified-identity-resolves-to-a-principal', 'RFCS/0154 §A', 'a verified identity resolves')).toBe(200);
|
|
107
109
|
const principalId = (r.json as { principalId?: string }).principalId;
|
|
108
|
-
expect(principalId,
|
|
110
|
+
expect(principalId, req('openwop.it.workload-identity-behavior.a-verified-identity-resolves-to-a-principal', 'RFCS/0154 §A', 'resolution yields an OpenWOP principal')).toBeTruthy();
|
|
109
111
|
});
|
|
110
112
|
|
|
111
113
|
it('an identity for another audience is rejected', async () => {
|
|
112
114
|
if (!behaviorGate(PROFILE, (await caps())?.supported === true)) return;
|
|
113
115
|
const r = await resolve({ identity: { ...IDENTITY, audience: 'some-other-host' }, expectedAudience: 'openwop-host' });
|
|
114
|
-
if (r === null) return;
|
|
116
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
115
117
|
// The load-bearing negative. A host that echoes its input passes every
|
|
116
118
|
// positive assertion; only this distinguishes a verifier from a passthrough.
|
|
117
119
|
expect(
|
|
118
120
|
r.status >= 400,
|
|
119
|
-
|
|
121
|
+
req('openwop.it.workload-identity-behavior.an-identity-for-another-audience-is-rejected',
|
|
120
122
|
'RFCS/0154 §A + RFC 0147 R12',
|
|
121
123
|
'an identity minted for another host, accepted here, is how a credential valid elsewhere ' +
|
|
122
124
|
'becomes a credential valid here — the confused-deputy path this profile prevents',
|
|
@@ -140,10 +142,10 @@ describe('RFC 0154 §A — workload identity resolution (capability-gated behavi
|
|
|
140
142
|
},
|
|
141
143
|
},
|
|
142
144
|
});
|
|
143
|
-
if (r === null) return;
|
|
145
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
144
146
|
expect(
|
|
145
147
|
r.status >= 400,
|
|
146
|
-
|
|
148
|
+
req('openwop.it.workload-identity-behavior.an-expired-delegation-is-rejected',
|
|
147
149
|
'RFCS/0154 §B',
|
|
148
150
|
'a delegation without a live expiry is a standing grant, which is not what delegation means',
|
|
149
151
|
),
|
|
@@ -154,13 +156,13 @@ describe('RFC 0154 §A — workload identity resolution (capability-gated behavi
|
|
|
154
156
|
it('a failure is non-retriable and carries a closed reason code', async () => {
|
|
155
157
|
if (!behaviorGate(PROFILE, (await caps())?.supported === true)) return;
|
|
156
158
|
const r = await resolve({ identity: { scheme: 'spiffe', subject: 'spiffe://example/unknown' } });
|
|
157
|
-
if (r === null || r.status < 400) return;
|
|
159
|
+
if (r === null || r.status < 400) return softSkip('blocked', 'precondition not met — `r === null || r.status < 400` returned early (seam, prior step, or fixture unavailable)');
|
|
158
160
|
// canonical flat envelope: code = `error`, retriable = `details.retriable`
|
|
159
161
|
// (the legacy nested `error.{code,retriable}` the §20 catalog prescribed until
|
|
160
162
|
// 2026-08-16 is tolerated by the helpers for the deprecation window)
|
|
161
163
|
expect(
|
|
162
164
|
readRetriable(r.json),
|
|
163
|
-
|
|
165
|
+
req('openwop.it.workload-identity-behavior.a-failure-is-non-retriable-and-carries-a-closed-reason-code',
|
|
164
166
|
'spec/v1/host-sample-test-seams.md §20',
|
|
165
167
|
'an identity that does not resolve will not resolve on retry. Marking it retriable invites ' +
|
|
166
168
|
'a caller to hammer a failing authorization path.',
|
|
@@ -168,19 +170,19 @@ describe('RFC 0154 §A — workload identity resolution (capability-gated behavi
|
|
|
168
170
|
).toBe(false);
|
|
169
171
|
expect(
|
|
170
172
|
['identity_unverified', 'identity_unresolvable', 'audience_mismatch', 'delegation_expired', 'sender_constraint_missing', 'delegation_chain_too_long', 'delegation_chain_cyclic', 'delegation_scope_amplified'],
|
|
171
|
-
|
|
173
|
+
req('openwop.it.workload-identity-behavior.a-failure-is-non-retriable-and-carries-a-closed-reason-code', 'RFCS/0154 §A', 'failures use a closed reason vocabulary'),
|
|
172
174
|
).toContain(readErrorCode(r.json));
|
|
173
175
|
});
|
|
174
176
|
|
|
175
177
|
it('a resolution response carries no credential material', async () => {
|
|
176
178
|
if (!behaviorGate(PROFILE, (await caps())?.supported === true)) return;
|
|
177
179
|
const r = await resolve({ identity: { ...IDENTITY, audience: 'openwop-host' } });
|
|
178
|
-
if (r === null) return;
|
|
180
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
179
181
|
const serialized = JSON.stringify(r.json ?? {});
|
|
180
182
|
for (const forbidden of ['-----BEGIN', 'Bearer ', 'eyJ']) {
|
|
181
183
|
expect(
|
|
182
184
|
serialized.includes(forbidden),
|
|
183
|
-
|
|
185
|
+
req('openwop.it.workload-identity-behavior.a-resolution-response-carries-no-credential-material',
|
|
184
186
|
'RFCS/0154 §A',
|
|
185
187
|
'a verified identity is a fact about a completed verification, not a container for the ' +
|
|
186
188
|
`material that proved it. Found: ${forbidden}`,
|
|
@@ -36,7 +36,8 @@ import { readErrorCode, readRetriable } from '../lib/error-envelope.js';
|
|
|
36
36
|
import { driver } from '../lib/driver.js';
|
|
37
37
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
38
38
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
39
|
-
import { seamAbsent } from '../lib/soft-skip.js';
|
|
39
|
+
import { seamAbsent, softSkip } from '../lib/soft-skip.js';
|
|
40
|
+
import { req } from '../lib/requirement-ids.js';
|
|
40
41
|
|
|
41
42
|
const DELEGATION_PROFILE = 'openwop-workload-identity-delegation';
|
|
42
43
|
const SEAM = '/v1/host/sample/test/workload-identity/resolve';
|
|
@@ -76,10 +77,10 @@ function hop(n: number, scopes?: readonly string[]): Record<string, unknown> {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
/** A refusal: 4xx, non-retriable, the named closed reason. */
|
|
79
|
-
function expectRefusal(r: { status: number; json: unknown }, reason: string, why: string): void {
|
|
80
|
-
expect(r.status >= 400,
|
|
81
|
-
expect(readErrorCode(r.json),
|
|
82
|
-
expect(readRetriable(r.json),
|
|
80
|
+
function expectRefusal(requirementId: string, r: { status: number; json: unknown }, reason: string, why: string): void {
|
|
81
|
+
expect(r.status >= 400, req(requirementId, 'RFCS/0154 §B', why)).toBe(true);
|
|
82
|
+
expect(readErrorCode(r.json), req(requirementId, 'spec/v1/host-sample-test-seams.md §20', `closed reason \`${reason}\``)).toBe(reason);
|
|
83
|
+
expect(readRetriable(r.json), req(requirementId, 'spec/v1/host-sample-test-seams.md §20', 'a chain the host refuses will be refused on retry')).toBe(false);
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
describe('RFC 0154 §B — delegation chain bounds (capability-gated behavior)', () => {
|
|
@@ -88,13 +89,13 @@ describe('RFC 0154 §B — delegation chain bounds (capability-gated behavior)',
|
|
|
88
89
|
if (!behaviorGate(DELEGATION_PROFILE, caps.supported)) return;
|
|
89
90
|
expect(
|
|
90
91
|
caps.maxChainDepth,
|
|
91
|
-
|
|
92
|
+
req('openwop.it.workload-identity-chain-bounds.a-chain-longer-than-the-advertised-maxchaindepth-is-refused', 'spec/v1/auth.md §Bounds', 'a host advertising delegation MUST advertise a positive integer `maxChainDepth`'),
|
|
92
93
|
).not.toBeNull();
|
|
93
94
|
const depth = caps.maxChainDepth as number;
|
|
94
95
|
const chain = Array.from({ length: depth + 1 }, (_, i) => hop(i + 1));
|
|
95
96
|
const r = await resolve({ identity: { ...IDENTITY, delegation: { chain, audience: 'openwop-host', expiresAt: LIVE } }, expectedAudience: 'openwop-host' });
|
|
96
|
-
if (r === null) return;
|
|
97
|
-
expectRefusal(r, 'delegation_chain_too_long', `a chain of ${depth + 1} hops exceeds the advertised bound of ${depth} — each hop is another party the host trusts transitively`);
|
|
97
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
98
|
+
expectRefusal('openwop.it.workload-identity-chain-bounds.a-chain-longer-than-the-advertised-maxchaindepth-is-refused', r, 'delegation_chain_too_long', `a chain of ${depth + 1} hops exceeds the advertised bound of ${depth} — each hop is another party the host trusts transitively`);
|
|
98
99
|
});
|
|
99
100
|
|
|
100
101
|
it('a chain that revisits a subject is refused as cyclic', async () => {
|
|
@@ -106,15 +107,15 @@ describe('RFC 0154 §B — delegation chain bounds (capability-gated behavior)',
|
|
|
106
107
|
// and this leg accepts either reason, saying so.
|
|
107
108
|
const chain = [hop(1), hop(2), hop(1)];
|
|
108
109
|
const r = await resolve({ identity: { ...IDENTITY, delegation: { chain, audience: 'openwop-host', expiresAt: LIVE } }, expectedAudience: 'openwop-host' });
|
|
109
|
-
if (r === null) return;
|
|
110
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
110
111
|
const bound = caps.maxChainDepth ?? Number.POSITIVE_INFINITY;
|
|
111
112
|
if (bound < 3) {
|
|
112
|
-
expect(r.status >= 400,
|
|
113
|
-
expect(['delegation_chain_cyclic', 'delegation_chain_too_long'],
|
|
113
|
+
expect(r.status >= 400, req('openwop.it.workload-identity-chain-bounds.a-chain-that-revisits-a-subject-is-refused-as-cyclic', 'RFCS/0154 §B', 'a cyclic chain is refused')).toBe(true);
|
|
114
|
+
expect(['delegation_chain_cyclic', 'delegation_chain_too_long'], req('openwop.it.workload-identity-chain-bounds.a-chain-that-revisits-a-subject-is-refused-as-cyclic', 'spec/v1/host-sample-test-seams.md §20', 'cyclic or too-long — the bound is below the cycle length')).toContain(readErrorCode(r.json));
|
|
114
115
|
expect(readRetriable(r.json)).toBe(false);
|
|
115
|
-
return;
|
|
116
|
+
return softSkip('blocked', 'precondition not met — `bound < 3` returned early (seam, prior step, or fixture unavailable)');
|
|
116
117
|
}
|
|
117
|
-
expectRefusal(r, 'delegation_chain_cyclic', 'a subject appearing twice is a chain that loops back through authority it already spent — unbounded laundering with a bounded length');
|
|
118
|
+
expectRefusal('openwop.it.workload-identity-chain-bounds.a-chain-that-revisits-a-subject-is-refused-as-cyclic', r, 'delegation_chain_cyclic', 'a subject appearing twice is a chain that loops back through authority it already spent — unbounded laundering with a bounded length');
|
|
118
119
|
});
|
|
119
120
|
|
|
120
121
|
it('a later hop claiming a scope the previous hop did not hold is refused', async () => {
|
|
@@ -122,8 +123,8 @@ describe('RFC 0154 §B — delegation chain bounds (capability-gated behavior)',
|
|
|
122
123
|
if (!behaviorGate(DELEGATION_PROFILE, caps.supported)) return;
|
|
123
124
|
const chain = [hop(1, ['runs:read']), hop(2, ['runs:read', 'runs:write'])];
|
|
124
125
|
const r = await resolve({ identity: { ...IDENTITY, delegation: { chain, audience: 'openwop-host', expiresAt: LIVE } }, expectedAudience: 'openwop-host' });
|
|
125
|
-
if (r === null) return;
|
|
126
|
-
expectRefusal(r, 'delegation_scope_amplified', 'the effective scopes at any hop MUST NOT exceed the hop before it — a chain is provenance, and provenance cannot mint `runs:write` from `runs:read`');
|
|
126
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
127
|
+
expectRefusal('openwop.it.workload-identity-chain-bounds.a-later-hop-claiming-a-scope-the-previous-hop-did-not-hold-is-refused', r, 'delegation_scope_amplified', 'the effective scopes at any hop MUST NOT exceed the hop before it — a chain is provenance, and provenance cannot mint `runs:write` from `runs:read`');
|
|
127
128
|
});
|
|
128
129
|
|
|
129
130
|
it('a well-formed bounded, acyclic, non-amplifying chain still resolves (the negatives are not a blanket refusal)', async () => {
|
|
@@ -131,11 +132,11 @@ describe('RFC 0154 §B — delegation chain bounds (capability-gated behavior)',
|
|
|
131
132
|
if (!behaviorGate(DELEGATION_PROFILE, caps.supported)) return;
|
|
132
133
|
const chain = [hop(1, ['runs:read', 'manifest:read']), hop(2, ['runs:read'])];
|
|
133
134
|
const r = await resolve({ identity: { ...IDENTITY, delegation: { chain, audience: 'openwop-host', expiresAt: LIVE } }, expectedAudience: 'openwop-host' });
|
|
134
|
-
if (r === null) return;
|
|
135
|
+
if (r === null) return softSkip('blocked', 'precondition not met — `r === null` returned early (seam, prior step, or fixture unavailable)');
|
|
135
136
|
// A host that refuses EVERY chain passes the three negatives vacuously; this
|
|
136
137
|
// is the positive that keeps them honest. Scopes narrow hop-to-hop, which
|
|
137
138
|
// is the one direction §B permits.
|
|
138
|
-
expect(r.status,
|
|
139
|
+
expect(r.status, req('openwop.it.workload-identity-chain-bounds.a-well-formed-bounded-acyclic-non-amplifying-chain-still-resolves-the-negatives', 'RFCS/0154 §B', 'a compliant chain resolves; the bounds refuse laundering, not delegation')).toBe(200);
|
|
139
140
|
expect((r.json as { resolved?: unknown }).resolved).toBe(true);
|
|
140
141
|
});
|
|
141
142
|
});
|
|
@@ -24,6 +24,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
24
24
|
import { driver } from '../lib/driver.js';
|
|
25
25
|
import { softSkip, seamAbsent } from '../lib/soft-skip.js';
|
|
26
26
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
27
|
+
import { req } from '../lib/requirement-ids.js';
|
|
27
28
|
|
|
28
29
|
interface DiscoveryWorkspace {
|
|
29
30
|
supported?: boolean;
|
|
@@ -54,26 +55,26 @@ describe('workspace-behavior: §C CRUD round-trip (RFC 0059)', () => {
|
|
|
54
55
|
const path = 'behavior/CRUD.md';
|
|
55
56
|
|
|
56
57
|
const created = await driver.put(`${FILES}/${encodeURIComponent(path)}`, { content: 'first body' });
|
|
57
|
-
expect(created.status,
|
|
58
|
+
expect(created.status, req('openwop.it.workspace-behavior.put-creates-v1-get-returns-it-list-omits-content-delete-removes-it', 'agent-workspace.md §C PUT', 'create MUST return 200')).toBe(200);
|
|
58
59
|
const file = created.json as WorkspaceFile;
|
|
59
|
-
expect(file.version,
|
|
60
|
-
expect(typeof file.etag,
|
|
60
|
+
expect(file.version, req('openwop.it.workspace-behavior.put-creates-v1-get-returns-it-list-omits-content-delete-removes-it', 'agent-workspace.md §File model', 'version MUST start at 1')).toBe(1);
|
|
61
|
+
expect(typeof file.etag, req('openwop.it.workspace-behavior.put-creates-v1-get-returns-it-list-omits-content-delete-removes-it', 'agent-workspace.md §File model', 'PUT MUST return an etag')).toBe('string');
|
|
61
62
|
|
|
62
63
|
const got = await driver.get(`${FILES}/${encodeURIComponent(path)}`);
|
|
63
|
-
expect((got.json as WorkspaceFile).content,
|
|
64
|
+
expect((got.json as WorkspaceFile).content, req('openwop.it.workspace-behavior.put-creates-v1-get-returns-it-list-omits-content-delete-removes-it', 'agent-workspace.md §C GET', 'GET MUST return the content')).toBe('first body');
|
|
64
65
|
|
|
65
66
|
const list = await driver.get(FILES);
|
|
66
67
|
const rows = (list.json as { files?: WorkspaceFile[] }).files ?? [];
|
|
67
68
|
const row = rows.find((r) => r.path === path);
|
|
68
|
-
expect(row,
|
|
69
|
-
expect('content' in (row as object),
|
|
69
|
+
expect(row, req('openwop.it.workspace-behavior.put-creates-v1-get-returns-it-list-omits-content-delete-removes-it', 'agent-workspace.md §C list', 'list MUST include the created file')).toBeDefined();
|
|
70
|
+
expect('content' in (row as object), req('openwop.it.workspace-behavior.put-creates-v1-get-returns-it-list-omits-content-delete-removes-it', 'agent-workspace.md §C list', 'list MUST NOT include file bodies (metadata only)')).toBe(false);
|
|
70
71
|
|
|
71
72
|
const del = await driver.del(`${FILES}/${encodeURIComponent(path)}`);
|
|
72
|
-
expect(del.status,
|
|
73
|
-
expect(del.status, 'DELETE MUST be < 300').toBeLessThan(300);
|
|
73
|
+
expect(del.status, req('openwop.it.workspace-behavior.put-creates-v1-get-returns-it-list-omits-content-delete-removes-it', 'agent-workspace.md §C DELETE', 'DELETE MUST return 2xx')).toBeGreaterThanOrEqual(200);
|
|
74
|
+
expect(del.status, req('openwop.it.workspace-behavior.put-creates-v1-get-returns-it-list-omits-content-delete-removes-it', 'agent-workspace.md §C DELETE', 'DELETE MUST be < 300')).toBeLessThan(300);
|
|
74
75
|
|
|
75
76
|
const gone = await driver.get(`${FILES}/${encodeURIComponent(path)}`);
|
|
76
|
-
expect(gone.status,
|
|
77
|
+
expect(gone.status, req('openwop.it.workspace-behavior.put-creates-v1-get-returns-it-list-omits-content-delete-removes-it', 'agent-workspace.md §C GET', 'GET after DELETE MUST be 404')).toBe(404);
|
|
77
78
|
});
|
|
78
79
|
});
|
|
79
80
|
|
|
@@ -86,14 +87,14 @@ describe('workspace-behavior: §C optimistic concurrency (RFC 0059)', () => {
|
|
|
86
87
|
const etag = (v1.json as WorkspaceFile).etag!;
|
|
87
88
|
|
|
88
89
|
const stale = await driver.put(`${FILES}/${encodeURIComponent(path)}`, { content: 'nope' }, { headers: { 'If-Match': '"definitely-stale"' } });
|
|
89
|
-
expect(stale.status,
|
|
90
|
-
expect((stale.json as { error?: string }).error,
|
|
90
|
+
expect(stale.status, req('openwop.it.workspace-behavior.stale-if-match-409-workspace-conflict-matching-if-match-bumps-version', 'agent-workspace.md §C PUT', 'a stale If-Match MUST return 409 workspace_conflict')).toBe(409);
|
|
91
|
+
expect((stale.json as { error?: string }).error, req('openwop.it.workspace-behavior.stale-if-match-409-workspace-conflict-matching-if-match-bumps-version', 'rest-endpoints.md workspace_conflict', 'error code MUST be workspace_conflict')).toBe('workspace_conflict');
|
|
91
92
|
const details = (stale.json as { details?: { currentVersion?: number } }).details;
|
|
92
|
-
expect(typeof details?.currentVersion,
|
|
93
|
+
expect(typeof details?.currentVersion, req('openwop.it.workspace-behavior.stale-if-match-409-workspace-conflict-matching-if-match-bumps-version', 'agent-workspace.md §C PUT', '409 MUST carry details.currentVersion')).toBe('number');
|
|
93
94
|
|
|
94
95
|
const v2 = await driver.put(`${FILES}/${encodeURIComponent(path)}`, { content: 'v2' }, { headers: { 'If-Match': etag } });
|
|
95
|
-
expect(v2.status, 'a matching If-Match MUST succeed').toBe(200);
|
|
96
|
-
expect((v2.json as WorkspaceFile).version,
|
|
96
|
+
expect(v2.status, req('openwop.it.workspace-behavior.stale-if-match-409-workspace-conflict-matching-if-match-bumps-version', 'agent-workspace.md §C PUT', 'a matching If-Match MUST succeed')).toBe(200);
|
|
97
|
+
expect((v2.json as WorkspaceFile).version, req('openwop.it.workspace-behavior.stale-if-match-409-workspace-conflict-matching-if-match-bumps-version', 'agent-workspace.md §File model', 'a successful PUT MUST bump version')).toBe(2);
|
|
97
98
|
|
|
98
99
|
await driver.del(`${FILES}/${encodeURIComponent(path)}`);
|
|
99
100
|
});
|
|
@@ -106,8 +107,8 @@ describe('workspace-behavior: §C size ceiling (RFC 0059)', () => {
|
|
|
106
107
|
const max = typeof cap.maxFileBytes === 'number' ? cap.maxFileBytes : 1_048_576;
|
|
107
108
|
const tooBig = 'x'.repeat(max + 1);
|
|
108
109
|
const res = await driver.put(`${FILES}/${encodeURIComponent('behavior/TOOBIG.md')}`, { content: tooBig });
|
|
109
|
-
expect(res.status,
|
|
110
|
-
expect((res.json as { error?: string }).error,
|
|
110
|
+
expect(res.status, req('openwop.it.workspace-behavior.content-beyond-maxfilebytes-returns-workspace-too-large', 'agent-workspace.md §C PUT', 'oversize content MUST be rejected (4xx)')).toBeGreaterThanOrEqual(400);
|
|
111
|
+
expect((res.json as { error?: string }).error, req('openwop.it.workspace-behavior.content-beyond-maxfilebytes-returns-workspace-too-large', 'rest-endpoints.md workspace_too_large', 'error code MUST be workspace_too_large')).toBe('workspace_too_large');
|
|
111
112
|
});
|
|
112
113
|
});
|
|
113
114
|
|
|
@@ -124,10 +125,10 @@ describe('workspace-behavior: §D run-start snapshot (RFC 0059)', () => {
|
|
|
124
125
|
|
|
125
126
|
const snap = await driver.get(`/v1/runs/${encodeURIComponent(runId)}`);
|
|
126
127
|
const ws = (snap.json as { workspace?: Array<{ path: string }> }).workspace;
|
|
127
|
-
if (ws === undefined) return; // host doesn't expose the snapshot field — skip
|
|
128
|
+
if (ws === undefined) return softSkip('blocked', 'precondition not met — `ws === undefined` returned early (host doesn\'t expose the snapshot field — skip) (seam, prior step, or fixture unavailable)'); // host doesn't expose the snapshot field — skip
|
|
128
129
|
expect(
|
|
129
130
|
ws.some((f) => f.path === path),
|
|
130
|
-
|
|
131
|
+
req('openwop.it.workspace-behavior.a-run-started-after-a-write-exposes-the-workspace-snapshot', 'agent-workspace.md §D', 'the run snapshot MUST reflect a workspace file present at run start'),
|
|
131
132
|
).toBe(true);
|
|
132
133
|
|
|
133
134
|
await driver.del(`${FILES}/${encodeURIComponent(path)}`);
|
|
@@ -25,6 +25,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
25
25
|
import { driver } from '../lib/driver.js';
|
|
26
26
|
import { softSkip } from '../lib/soft-skip.js';
|
|
27
27
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
28
|
+
import { req } from '../lib/requirement-ids.js';
|
|
28
29
|
|
|
29
30
|
interface DiscoveryWorkspace {
|
|
30
31
|
supported?: boolean;
|
|
@@ -52,7 +53,7 @@ describe('workspace-capability-shape: advertisement shape (RFC 0059 §A)', () =>
|
|
|
52
53
|
if (ws === null) return softSkip('inapplicable', 'optional advertisement — `capabilities.workspace` not advertised by this host (RFC 0059 §A)');
|
|
53
54
|
expect(
|
|
54
55
|
typeof ws.supported,
|
|
55
|
-
|
|
56
|
+
req('openwop.it.workspace-capability-shape.capabilities-workspace-is-either-absent-or-well-formed',
|
|
56
57
|
'capabilities.schema.json §workspace',
|
|
57
58
|
'capabilities.workspace.supported MUST be a boolean when workspace is advertised',
|
|
58
59
|
),
|
|
@@ -67,7 +68,7 @@ describe('workspace-capability-shape: advertisement shape (RFC 0059 §A)', () =>
|
|
|
67
68
|
if (v === undefined) continue;
|
|
68
69
|
expect(
|
|
69
70
|
Number.isInteger(v) && v >= 1,
|
|
70
|
-
|
|
71
|
+
req('openwop.it.workspace-capability-shape.maxfilebytes-maxfiles-maxversions-are-positive-integers-when-present', 'RFC 0059 §A', `capabilities.workspace.${field} MUST be an integer >= 1, got: ${v}`),
|
|
71
72
|
).toBe(true);
|
|
72
73
|
}
|
|
73
74
|
});
|
|
@@ -28,6 +28,7 @@ import { softSkip } from '../lib/soft-skip.js';
|
|
|
28
28
|
import { randomUUID } from 'node:crypto';
|
|
29
29
|
import { driver } from '../lib/driver.js';
|
|
30
30
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
31
|
+
import { req } from '../lib/requirement-ids.js';
|
|
31
32
|
|
|
32
33
|
interface DiscoveryDoc {
|
|
33
34
|
workspace?: { supported?: boolean };
|
|
@@ -55,18 +56,18 @@ describe('workspace-cross-tenant-isolation (black-box): a §C file MUST NOT leak
|
|
|
55
56
|
|
|
56
57
|
// Owner A (default credential) writes the file via the NORMATIVE PUT.
|
|
57
58
|
const put = await driver.put(`/v1/host/workspace/files/${path}`, { content: secret });
|
|
58
|
-
expect(put.status,
|
|
59
|
+
expect(put.status, req('openwop.it.workspace-cross-tenant-isolation-blackbox.a-file-written-by-owner-a-is-unreadable-un-enumerable-by-a-second-tenant-credent', 'agent-workspace.md §C PUT', 'the owning workspace MUST create its file (200)')).toBe(200);
|
|
59
60
|
|
|
60
61
|
try {
|
|
61
62
|
// Owner B (second-tenant credential) MUST NOT read it — fail closed, no existence leak.
|
|
62
63
|
const crossRead = await driver.get(`/v1/host/workspace/files/${path}`, asTenantB);
|
|
63
64
|
expect(
|
|
64
65
|
crossRead.status === 404 || crossRead.status === 403,
|
|
65
|
-
|
|
66
|
+
req('openwop.it.workspace-cross-tenant-isolation-blackbox.a-file-written-by-owner-a-is-unreadable-un-enumerable-by-a-second-tenant-credent', 'agent-workspace.md §E WCT-1', `a second-tenant read MUST fail closed (404/403), got ${crossRead.status}`),
|
|
66
67
|
).toBe(true);
|
|
67
68
|
expect(
|
|
68
69
|
!JSON.stringify(crossRead.json ?? '').includes(secret),
|
|
69
|
-
|
|
70
|
+
req('openwop.it.workspace-cross-tenant-isolation-blackbox.a-file-written-by-owner-a-is-unreadable-un-enumerable-by-a-second-tenant-credent', 'agent-workspace.md §E WCT-1', 'a second-tenant read MUST NOT surface the owner\'s content'),
|
|
70
71
|
).toBe(true);
|
|
71
72
|
|
|
72
73
|
// Owner B MUST NOT enumerate it in the list projection.
|
|
@@ -74,13 +75,13 @@ describe('workspace-cross-tenant-isolation (black-box): a §C file MUST NOT leak
|
|
|
74
75
|
if (crossList.status === 200) {
|
|
75
76
|
expect(
|
|
76
77
|
!JSON.stringify((crossList.json as { files?: unknown })?.files ?? []).includes(path),
|
|
77
|
-
|
|
78
|
+
req('openwop.it.workspace-cross-tenant-isolation-blackbox.a-file-written-by-owner-a-is-unreadable-un-enumerable-by-a-second-tenant-credent', 'agent-workspace.md §E WCT-1', 'a second-tenant list MUST NOT enumerate the owner\'s path'),
|
|
78
79
|
).toBe(true);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
// Isolation, not loss: owner A still reads its own file.
|
|
82
83
|
const ownerRead = await driver.get(`/v1/host/workspace/files/${path}`);
|
|
83
|
-
expect(ownerRead.status,
|
|
84
|
+
expect(ownerRead.status, req('openwop.it.workspace-cross-tenant-isolation-blackbox.a-file-written-by-owner-a-is-unreadable-un-enumerable-by-a-second-tenant-credent', 'agent-workspace.md §C GET', 'the owning workspace MUST still read its own file')).toBe(200);
|
|
84
85
|
expect((ownerRead.json as { content?: string } | undefined)?.content).toBe(secret);
|
|
85
86
|
} finally {
|
|
86
87
|
// Best-effort cleanup as owner A.
|
|
@@ -22,6 +22,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
22
22
|
import { softSkip } from '../lib/soft-skip.js';
|
|
23
23
|
import { driver } from '../lib/driver.js';
|
|
24
24
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
25
|
+
import { req } from '../lib/requirement-ids.js';
|
|
25
26
|
|
|
26
27
|
interface DiscoveryDoc {
|
|
27
28
|
capabilities?: { workspace?: { supported?: boolean } };
|
|
@@ -46,25 +47,25 @@ describe('workspace-cross-tenant-isolation: a workspace file MUST NOT leak acros
|
|
|
46
47
|
// Owner A writes a file.
|
|
47
48
|
const put = await seam({ tenant: 'wct1-tenant-a', workspace: 'ws-a', op: 'put', path: SEAM_PATH, content: 'A-only secret body' });
|
|
48
49
|
if (put.status === 404) return softSkip('blocked', 'seam unwired — soft-skip the behavioral probe');
|
|
49
|
-
expect(put.status,
|
|
50
|
+
expect(put.status, req('openwop.it.workspace-cross-tenant-isolation.a-file-written-under-tenant-a-workspace-a-is-not-readable-under-a-different-owne', 'agent-workspace.md §C PUT', 'seam put MUST succeed for the owning workspace')).toBe(200);
|
|
50
51
|
|
|
51
52
|
// A DIFFERENT workspace (same tenant) MUST NOT read it.
|
|
52
53
|
const crossWs = await seam({ tenant: 'wct1-tenant-a', workspace: 'ws-b', op: 'get', path: SEAM_PATH });
|
|
53
54
|
expect(
|
|
54
55
|
crossWs.status === 404 || crossWs.status === 403,
|
|
55
|
-
|
|
56
|
+
req('openwop.it.workspace-cross-tenant-isolation.a-file-written-under-tenant-a-workspace-a-is-not-readable-under-a-different-owne', 'agent-workspace.md §E WCT-1', `a cross-workspace get MUST fail closed (404/403, no existence leak), got ${crossWs.status}`),
|
|
56
57
|
).toBe(true);
|
|
57
58
|
const crossWsBody = JSON.stringify(crossWs.json ?? '');
|
|
58
59
|
expect(
|
|
59
60
|
!crossWsBody.includes('A-only secret body'),
|
|
60
|
-
|
|
61
|
+
req('openwop.it.workspace-cross-tenant-isolation.a-file-written-under-tenant-a-workspace-a-is-not-readable-under-a-different-owne', 'agent-workspace.md §E WCT-1', 'a cross-workspace read MUST NOT surface the other owner\'s content'),
|
|
61
62
|
).toBe(true);
|
|
62
63
|
|
|
63
64
|
// A DIFFERENT tenant MUST NOT read it either.
|
|
64
65
|
const crossTenant = await seam({ tenant: 'wct1-tenant-b', workspace: 'ws-a', op: 'get', path: SEAM_PATH });
|
|
65
66
|
expect(
|
|
66
67
|
crossTenant.status === 404 || crossTenant.status === 403,
|
|
67
|
-
|
|
68
|
+
req('openwop.it.workspace-cross-tenant-isolation.a-file-written-under-tenant-a-workspace-a-is-not-readable-under-a-different-owne', 'agent-workspace.md §E WCT-1', `a cross-tenant get MUST fail closed, got ${crossTenant.status}`),
|
|
68
69
|
).toBe(true);
|
|
69
70
|
|
|
70
71
|
// And list MUST NOT enumerate the other owner's path.
|
|
@@ -72,14 +73,14 @@ describe('workspace-cross-tenant-isolation: a workspace file MUST NOT leak acros
|
|
|
72
73
|
const listed = JSON.stringify((crossList.json as { files?: unknown })?.files ?? []);
|
|
73
74
|
expect(
|
|
74
75
|
!listed.includes(SEAM_PATH),
|
|
75
|
-
|
|
76
|
+
req('openwop.it.workspace-cross-tenant-isolation.a-file-written-under-tenant-a-workspace-a-is-not-readable-under-a-different-owne', 'agent-workspace.md §E WCT-1', 'a cross-workspace list MUST NOT enumerate another owner\'s file'),
|
|
76
77
|
).toBe(true);
|
|
77
78
|
|
|
78
79
|
// Sanity: the owner itself still reads its file (isolation, not loss).
|
|
79
80
|
const ownerRead = await seam({ tenant: 'wct1-tenant-a', workspace: 'ws-a', op: 'get', path: SEAM_PATH });
|
|
80
81
|
expect(
|
|
81
82
|
(ownerRead.json as { content?: string } | undefined)?.content,
|
|
82
|
-
|
|
83
|
+
req('openwop.it.workspace-cross-tenant-isolation.a-file-written-under-tenant-a-workspace-a-is-not-readable-under-a-different-owne', 'agent-workspace.md §C GET', 'the owning workspace MUST still read its own file'),
|
|
83
84
|
).toBe('A-only secret body');
|
|
84
85
|
});
|
|
85
86
|
});
|
|
@@ -38,6 +38,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
38
38
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
39
39
|
import addFormats from 'ajv-formats';
|
|
40
40
|
import type { ErrorObject } from 'ajv';
|
|
41
|
+
import { req } from '../lib/requirement-ids.js';
|
|
41
42
|
|
|
42
43
|
/** The §A `x-openwop-form` annotation shape. `kind` is the only required
|
|
43
44
|
* field; it is an OPEN string (unknown kinds are valid per the forward-compat
|
|
@@ -103,11 +104,11 @@ describe('category: x-openwop-form pack-manifest shape (RFC 0066)', () => {
|
|
|
103
104
|
let validateConfig: ReturnType<typeof ajv.compile> | undefined;
|
|
104
105
|
expect(() => {
|
|
105
106
|
validateConfig = ajv.compile(annotatedConfigSchema());
|
|
106
|
-
}, 'node-packs.md §x-openwop-form: an annotated configSchema MUST remain a valid 2020-12 schema').not.toThrow();
|
|
107
|
+
}, req('openwop.it.x-openwop-form-pack-manifest.positive-a-configschema-carrying-x-openwop-form-remains-a-valid-json-schema-2020', 'node-packs.md', 'node-packs.md §x-openwop-form: an annotated configSchema MUST remain a valid 2020-12 schema')).not.toThrow();
|
|
107
108
|
// The annotations do not alter schema semantics: valid config passes…
|
|
108
109
|
expect(
|
|
109
110
|
validateConfig!({ provider: 'anthropic', model: 'claude', temperature: 1 }),
|
|
110
|
-
'x-openwop-form is advisory — it MUST NOT change what the schema accepts',
|
|
111
|
+
req('openwop.it.x-openwop-form-pack-manifest.positive-a-configschema-carrying-x-openwop-form-remains-a-valid-json-schema-2020', 'node-packs.md', 'x-openwop-form is advisory — it MUST NOT change what the schema accepts'),
|
|
111
112
|
).toBe(true);
|
|
112
113
|
// …and a type violation on an annotated field still rejects.
|
|
113
114
|
expect(validateConfig!({ provider: 123 })).toBe(false);
|
|
@@ -120,7 +121,7 @@ describe('category: x-openwop-form pack-manifest shape (RFC 0066)', () => {
|
|
|
120
121
|
if (ann === undefined) continue;
|
|
121
122
|
expect(
|
|
122
123
|
validateShape(ann),
|
|
123
|
-
`node-packs.md §x-openwop-form: the annotation on "${name}" MUST match the §A shape. Errors: ${JSON.stringify(validateShape.errors)}
|
|
124
|
+
req('openwop.it.x-openwop-form-pack-manifest.positive-each-documented-x-openwop-form-annotation-matches-the-a-shape', 'node-packs.md', `node-packs.md §x-openwop-form: the annotation on "${name}" MUST match the §A shape. Errors: ${JSON.stringify(validateShape.errors)}`),
|
|
124
125
|
).toBe(true);
|
|
125
126
|
}
|
|
126
127
|
});
|
|
@@ -128,28 +129,28 @@ describe('category: x-openwop-form pack-manifest shape (RFC 0066)', () => {
|
|
|
128
129
|
it('forward-compat: an unknown kind is a structurally valid annotation (renderer falls back per the §A MUST)', () => {
|
|
129
130
|
expect(
|
|
130
131
|
validateShape({ kind: 'unknown-future-picker' }),
|
|
131
|
-
'node-packs.md §x-openwop-form: an unknown kind MUST validate (kind is an open string, not a closed enum) so future vocabulary is forward-compatible',
|
|
132
|
+
req('openwop.it.x-openwop-form-pack-manifest.forward-compat-an-unknown-kind-is-a-structurally-valid-annotation-renderer-falls', 'node-packs.md', 'node-packs.md §x-openwop-form: an unknown kind MUST validate (kind is an open string, not a closed enum) so future vocabulary is forward-compatible'),
|
|
132
133
|
).toBe(true);
|
|
133
134
|
});
|
|
134
135
|
|
|
135
136
|
it('negative: an annotation missing kind is rejected', () => {
|
|
136
137
|
expect(
|
|
137
138
|
shapeFailsWith({ dependsOn: 'provider' }, 'required').length,
|
|
138
|
-
'node-packs.md §x-openwop-form: kind is the one REQUIRED sub-field',
|
|
139
|
+
req('openwop.it.x-openwop-form-pack-manifest.negative-an-annotation-missing-kind-is-rejected', 'node-packs.md', 'node-packs.md §x-openwop-form: kind is the one REQUIRED sub-field'),
|
|
139
140
|
).toBeGreaterThan(0);
|
|
140
141
|
});
|
|
141
142
|
|
|
142
143
|
it('negative: a non-string kind is rejected', () => {
|
|
143
144
|
expect(
|
|
144
145
|
shapeFailsWith({ kind: 42 }, 'type').length,
|
|
145
|
-
'node-packs.md §x-openwop-form: kind MUST be a string',
|
|
146
|
+
req('openwop.it.x-openwop-form-pack-manifest.negative-a-non-string-kind-is-rejected', 'node-packs.md', 'node-packs.md §x-openwop-form: kind MUST be a string'),
|
|
146
147
|
).toBeGreaterThan(0);
|
|
147
148
|
});
|
|
148
149
|
|
|
149
150
|
it('negative: a non-string dependsOn is rejected', () => {
|
|
150
151
|
expect(
|
|
151
152
|
shapeFailsWith({ kind: 'model-picker', dependsOn: ['provider'] }, 'type').length,
|
|
152
|
-
'node-packs.md §x-openwop-form: dependsOn names a sibling property — it MUST be a string',
|
|
153
|
+
req('openwop.it.x-openwop-form-pack-manifest.negative-a-non-string-dependson-is-rejected', 'node-packs.md', 'node-packs.md §x-openwop-form: dependsOn names a sibling property — it MUST be a string'),
|
|
153
154
|
).toBeGreaterThan(0);
|
|
154
155
|
});
|
|
155
156
|
});
|
package/src/setup.ts
CHANGED
|
@@ -292,7 +292,8 @@ afterEach(({ task }) => {
|
|
|
292
292
|
// which prove fixtures and helpers, not a host, and must never become bundle
|
|
293
293
|
// evidence (that is why RFC 0163 G5 / RFC 0050 G1 moved them out of scenarios).
|
|
294
294
|
const filepath = (task as { file?: { filepath?: string } }).file?.filepath ?? '';
|
|
295
|
-
|
|
295
|
+
// Suite 2.0.0: src/coherence/ rows feed the corpus ledger (scripts/check-spec-coherence.mjs), not a host bundle.
|
|
296
|
+
if (!/[\\/]src[\\/](scenarios|coherence)[\\/]/.test(filepath)) {
|
|
296
297
|
takeExplicitRequirementId();
|
|
297
298
|
return;
|
|
298
299
|
}
|
|
@@ -304,7 +305,8 @@ afterEach(({ task }) => {
|
|
|
304
305
|
const gate = since.find((e) => e.disposition === 'inapplicable') ?? since.find((e) => e.disposition === 'skipped');
|
|
305
306
|
let disposition: 'executed-pass' | 'executed-fail' | 'skipped' | 'inapplicable' | 'blocked';
|
|
306
307
|
let detail: string | undefined;
|
|
307
|
-
|
|
308
|
+
// Suite 2.0.0: under the corpus gate (scripts/check-spec-coherence.mjs sets OPENWOP_CORPUS_GATE) a coherence scenario IS the subject; its rows are real dispositions for evidence/corpus-ledger.json.
|
|
309
|
+
if (process.env.OPENWOP_CORPUS_GATE !== '1' && (SPEC_COHERENCE_SCENARIOS.has(file) || SPEC_COHERENCE_SCENARIOS.has(file.replace(/\.test\.ts$/, '')))) {
|
|
308
310
|
// Corpus-coherence scenario: it reads spec/v1 and asserts nothing about a
|
|
309
311
|
// host, in any layout. Its rows are `inapplicable` to every host — the same
|
|
310
312
|
// rule `resolveFileRecord` applies to the file in the published layout.
|
package/vitest.config.ts
CHANGED
|
@@ -6,7 +6,11 @@ export default defineConfig({
|
|
|
6
6
|
// scenarios; `src/lib/**/*.test.ts` are server-free unit tests for
|
|
7
7
|
// suite-internal helpers (e.g., the synthetic OIDC issuer harness)
|
|
8
8
|
// that don't depend on OPENWOP_BASE_URL.
|
|
9
|
-
include: ['src/scenarios/**/*.test.ts'
|
|
9
|
+
include: ['src/scenarios/**/*.test.ts'],
|
|
10
|
+
// Suite 2.0.0: the corpus-coherence scenarios (src/coherence/) and the suite
|
|
11
|
+
// self-tests (src/lib/*.test.ts) are NOT host scenarios; they run under
|
|
12
|
+
// vitest.coherence.config.ts (scripts/check-spec-coherence.mjs) and
|
|
13
|
+
// vitest.selftest.config.ts. A host run never executes them.
|
|
10
14
|
testTimeout: 30_000,
|
|
11
15
|
hookTimeout: 30_000,
|
|
12
16
|
globals: true,
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# Scoped, documented exceptions to redocly's `no-ambiguous-paths` rule.
|
|
2
|
-
# The rule stays ENABLED globally (it catches genuinely ambiguous new paths);
|
|
3
|
-
# only the two known-safe occurrences below are exempted.
|
|
4
|
-
#
|
|
5
|
-
# Why these are safe (NOT merely tolerated):
|
|
6
|
-
# The rule flags the structural template/literal swap between
|
|
7
|
-
# `/v1/agents/{agentId}/deployments` and the sibling collection routes
|
|
8
|
-
# `/v1/agents/roster/{rosterId}` + `/v1/agents/org-chart/{departmentId}`
|
|
9
|
-
# (e.g. `/v1/agents/roster/deployments` could in principle parse two ways).
|
|
10
|
-
# It is resolved deterministically:
|
|
11
|
-
# 1. `agentId` carries `pattern: '^(?!roster$|org-chart$).+$'` (see the two
|
|
12
|
-
# `/v1/agents/{agentId}*` paths), so `{agentId}` can NEVER be `roster` or
|
|
13
|
-
# `org-chart` — a pattern-aware validator/router cannot misroute.
|
|
14
|
-
# 2. OpenAPI 3.x matching is concrete-before-templated, so `/v1/agents/roster/*`
|
|
15
|
-
# and `/v1/agents/org-chart/*` bind to the literal collection routes first.
|
|
16
|
-
# The paths are all normative (RFC 0072/0082/0086/0087), so they can't be
|
|
17
|
-
# renamed without a breaking change; the pattern is the non-breaking fix and
|
|
18
|
-
# redocly's purely-structural rule can't see it.
|
|
19
|
-
openapi.yaml:
|
|
20
|
-
no-ambiguous-paths:
|
|
21
|
-
- '#/paths/~1v1~1agents~1roster~1{rosterId}'
|
|
22
|
-
- '#/paths/~1v1~1agents~1org-chart~1{departmentId}'
|