@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
|
@@ -19,6 +19,7 @@ import { driver } from '../lib/driver.js';
|
|
|
19
19
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
20
20
|
import { softSkip } from '../lib/soft-skip.js';
|
|
21
21
|
import { pollUntil } from '../lib/polling.js';
|
|
22
|
+
import { req } from '../lib/requirement-ids.js';
|
|
22
23
|
|
|
23
24
|
const NOOP_WORKFLOW_ID = 'conformance-noop';
|
|
24
25
|
const SKIP_NO_NOOP = !isFixtureAdvertised(NOOP_WORKFLOW_ID);
|
|
@@ -88,19 +89,19 @@ describe.skipIf(SKIP_NO_NOOP)('owner-subject-echo: consistency, echo, fork (RFC
|
|
|
88
89
|
if (subject === undefined) {
|
|
89
90
|
return softSkip('inapplicable', 'host does not emit owner.subject on new runs (RFC 0165 §B — optional in v1.x)');
|
|
90
91
|
}
|
|
91
|
-
expect(subject.tenant,
|
|
92
|
+
expect(subject.tenant, req('openwop.it.owner-subject-echo.subject-is-consistent-with-the-owner-triple-b-2-and-the-actor-chain-is-bounded-b', 'RFC 0165 §B.2', 'subject.tenant MUST equal owner.tenant')).toBe(snap.owner?.tenant);
|
|
92
93
|
if (snap.owner?.principal !== undefined) {
|
|
93
|
-
expect(subject.subjectId,
|
|
94
|
+
expect(subject.subjectId, req('openwop.it.owner-subject-echo.subject-is-consistent-with-the-owner-triple-b-2-and-the-actor-chain-is-bounded-b', 'RFC 0165 §B.2', 'subject.subjectId MUST equal owner.principal when both are present')).toBe(snap.owner.principal);
|
|
94
95
|
}
|
|
95
96
|
if (snap.owner?.principalKind !== undefined) {
|
|
96
|
-
expect(subject.kind,
|
|
97
|
+
expect(subject.kind, req('openwop.it.owner-subject-echo.subject-is-consistent-with-the-owner-triple-b-2-and-the-actor-chain-is-bounded-b', 'RFC 0165 §B.2', 'subject.kind MUST equal owner.principalKind when both are present')).toBe(snap.owner.principalKind);
|
|
97
98
|
}
|
|
98
99
|
if (subject.lane === 'saml' || subject.lane === 'scim') {
|
|
99
|
-
expect(subject.keyClass,
|
|
100
|
+
expect(subject.keyClass, req('openwop.it.owner-subject-echo.subject-is-consistent-with-the-owner-triple-b-2-and-the-actor-chain-is-bounded-b', 'RFC 0165 §B.2', 'keyClass MUST be present on a linkable lane')).toBeDefined();
|
|
100
101
|
} else {
|
|
101
|
-
expect(subject.keyClass,
|
|
102
|
+
expect(subject.keyClass, req('openwop.it.owner-subject-echo.subject-is-consistent-with-the-owner-triple-b-2-and-the-actor-chain-is-bounded-b', 'RFC 0165 §B.2', 'keyClass MUST be absent off the linkable lanes')).toBeUndefined();
|
|
102
103
|
}
|
|
103
|
-
expect(actorDepth(subject),
|
|
104
|
+
expect(actorDepth(subject), req('openwop.it.owner-subject-echo.subject-is-consistent-with-the-owner-triple-b-2-and-the-actor-chain-is-bounded-b', 'RFC 0165 §B.5', 'actor chain depth MUST NOT exceed 4')).toBeLessThanOrEqual(4);
|
|
104
105
|
});
|
|
105
106
|
|
|
106
107
|
it('run.started echoes the snapshot subject verbatim (§B.2, RFC 0048 §C)', async () => {
|
|
@@ -116,8 +117,8 @@ describe.skipIf(SKIP_NO_NOOP)('owner-subject-echo: consistency, echo, fork (RFC
|
|
|
116
117
|
// leave `pending` first, then the event is a fact, not a timing.
|
|
117
118
|
await pollUntil(runId, (s) => s.status !== 'pending', { label: 'run left pending (run.started appended)' });
|
|
118
119
|
const started = (await events(runId)).find((e) => e.type === 'run.started');
|
|
119
|
-
expect(started,
|
|
120
|
-
expect(started?.payload?.owner?.subject,
|
|
120
|
+
expect(started, req('openwop.it.owner-subject-echo.run-started-echoes-the-snapshot-subject-verbatim-b-2-rfc-0048-c', 'RFC 0048 §C', 'run.started MUST be present in the event log')).toBeDefined();
|
|
121
|
+
expect(started?.payload?.owner?.subject, req('openwop.it.owner-subject-echo.run-started-echoes-the-snapshot-subject-verbatim-b-2-rfc-0048-c', 'RFC 0165 §B.2', 'run.started owner.subject MUST equal RunSnapshot.owner.subject')).toEqual(snap.owner.subject);
|
|
121
122
|
});
|
|
122
123
|
|
|
123
124
|
it('a fork copies owner.tenant and owner.subject verbatim onto the child (§B.4)', async () => {
|
|
@@ -130,11 +131,11 @@ describe.skipIf(SKIP_NO_NOOP)('owner-subject-echo: consistency, echo, fork (RFC
|
|
|
130
131
|
if (fork.status === 404 || fork.status === 501 || fork.status === 403) {
|
|
131
132
|
return softSkip('inapplicable', `host does not offer :fork on this run (${fork.status}); the fork-copy rule has no fork to check`);
|
|
132
133
|
}
|
|
133
|
-
expect(fork.status,
|
|
134
|
+
expect(fork.status, req('openwop.it.owner-subject-echo.a-fork-copies-owner-tenant-and-owner-subject-verbatim-onto-the-child-b-4', 'replay.md §"Fork ownership"', ':fork MUST return 201 for a completed noop run')).toBe(201);
|
|
134
135
|
const childId = (fork.json as { runId?: string }).runId;
|
|
135
|
-
expect(typeof childId, 'fork response MUST include the child runId').toBe('string');
|
|
136
|
+
expect(typeof childId, req('openwop.it.owner-subject-echo.a-fork-copies-owner-tenant-and-owner-subject-verbatim-onto-the-child-b-4', 'replay.md §"Fork ownership"', 'fork response MUST include the child runId')).toBe('string');
|
|
136
137
|
const child = await snapshot(childId as string);
|
|
137
|
-
expect(child.owner?.tenant,
|
|
138
|
-
expect(child.owner?.subject,
|
|
138
|
+
expect(child.owner?.tenant, req('openwop.it.owner-subject-echo.a-fork-copies-owner-tenant-and-owner-subject-verbatim-onto-the-child-b-4', 'RFC 0165 §B.4', 'the child owner.tenant MUST equal the source')).toBe(source.owner.tenant);
|
|
139
|
+
expect(child.owner?.subject, req('openwop.it.owner-subject-echo.a-fork-copies-owner-tenant-and-owner-subject-verbatim-onto-the-child-b-4', 'RFC 0165 §B.4', 'the child owner.subject MUST be copied verbatim from the source')).toEqual(source.owner.subject);
|
|
139
140
|
});
|
|
140
141
|
});
|
|
@@ -23,6 +23,7 @@ import { readFileSync } from 'node:fs';
|
|
|
23
23
|
import { join } from 'node:path';
|
|
24
24
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
25
25
|
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
26
|
+
import { req } from '../lib/requirement-ids.js';
|
|
26
27
|
|
|
27
28
|
const read = (f: string): Record<string, unknown> => JSON.parse(readFileSync(join(SCHEMAS_DIR, f), 'utf8')) as Record<string, unknown>;
|
|
28
29
|
const subject = read('subject.schema.json');
|
|
@@ -41,28 +42,28 @@ describe('owner-subject-shape: subject.schema.json (RFC 0165 §B.1)', () => {
|
|
|
41
42
|
const validate = ajvWithSubject().compile(subject);
|
|
42
43
|
|
|
43
44
|
it('compiles and accepts a full SAML subject', () => {
|
|
44
|
-
expect(validate(VALID), JSON.stringify(validate.errors)).toBe(true);
|
|
45
|
+
expect(validate(VALID), req('openwop.it.owner-subject-shape.compiles-and-accepts-a-full-saml-subject', 'RFC 0165 §B.1–§B.2', JSON.stringify(validate.errors))).toBe(true);
|
|
45
46
|
});
|
|
46
47
|
|
|
47
48
|
it('accepts the legacy issuer form (RFC 0165 §B.3) and a workload subject', () => {
|
|
48
|
-
expect(validate({ issuer: 'urn:openwop:legacy', subjectId: 'user_42', tenant: 'acme', lane: 'api-key', kind: 'user' }), JSON.stringify(validate.errors)).toBe(true);
|
|
49
|
+
expect(validate({ issuer: 'urn:openwop:legacy', subjectId: 'user_42', tenant: 'acme', lane: 'api-key', kind: 'user' }), req('openwop.it.owner-subject-shape.accepts-the-legacy-issuer-form-rfc-0165-b-3-and-a-workload-subject', 'RFC 0165 §B.3', JSON.stringify(validate.errors))).toBe(true);
|
|
49
50
|
expect(validate({ issuer: 'spiffe://trust.example', subjectId: 'spiffe://trust.example/ns/a/sa/b', tenant: 'acme', lane: 'workload', kind: 'workload' }), JSON.stringify(validate.errors)).toBe(true);
|
|
50
51
|
});
|
|
51
52
|
|
|
52
53
|
it('rejects an email-shaped or whitespace subjectId (invariant subject-record-opaque)', () => {
|
|
53
|
-
expect(validate({ ...VALID, subjectId: 'alice@example.com' }), 'RFC 0165 §B.2: subjectId MUST NOT be an email address').toBe(false);
|
|
54
|
-
expect(validate({ ...VALID, subjectId: 'Alice Example' }), 'RFC 0165 §B.2: subjectId MUST NOT contain whitespace (a display name)').toBe(false);
|
|
54
|
+
expect(validate({ ...VALID, subjectId: 'alice@example.com' }), req('openwop.it.owner-subject-shape.rejects-an-email-shaped-or-whitespace-subjectid-invariant-subject-record-opaque', 'RFC 0165 §B.1–§B.2', 'RFC 0165 §B.2: subjectId MUST NOT be an email address')).toBe(false);
|
|
55
|
+
expect(validate({ ...VALID, subjectId: 'Alice Example' }), req('openwop.it.owner-subject-shape.rejects-an-email-shaped-or-whitespace-subjectid-invariant-subject-record-opaque', 'RFC 0165 §B.1–§B.2', 'RFC 0165 §B.2: subjectId MUST NOT contain whitespace (a display name)')).toBe(false);
|
|
55
56
|
});
|
|
56
57
|
|
|
57
58
|
it('requires keyClass on the saml/scim lanes and forbids it elsewhere', () => {
|
|
58
59
|
const { keyClass: _k, ...noKey } = VALID;
|
|
59
|
-
expect(validate(noKey), 'RFC 0165 §B.2: keyClass MUST be present when lane is saml or scim').toBe(false);
|
|
60
|
-
expect(validate({ ...VALID, lane: 'oidc' }), 'RFC 0165 §B.2: keyClass MUST be absent when the lane is not linkable').toBe(false);
|
|
60
|
+
expect(validate(noKey), req('openwop.it.owner-subject-shape.requires-keyclass-on-the-saml-scim-lanes-and-forbids-it-elsewhere', 'RFC 0165 §B.1–§B.2', 'RFC 0165 §B.2: keyClass MUST be present when lane is saml or scim')).toBe(false);
|
|
61
|
+
expect(validate({ ...VALID, lane: 'oidc' }), req('openwop.it.owner-subject-shape.requires-keyclass-on-the-saml-scim-lanes-and-forbids-it-elsewhere', 'RFC 0165 §B.1–§B.2', 'RFC 0165 §B.2: keyClass MUST be absent when the lane is not linkable')).toBe(false);
|
|
61
62
|
expect(validate({ ...noKey, lane: 'oidc' }), JSON.stringify(validate.errors)).toBe(true);
|
|
62
63
|
});
|
|
63
64
|
|
|
64
65
|
it('rejects an unknown member and a missing required member', () => {
|
|
65
|
-
expect(validate({ ...VALID, email: 'x' })).toBe(false);
|
|
66
|
+
expect(validate({ ...VALID, email: 'x' }), req('openwop.it.owner-subject-shape.rejects-an-unknown-member-and-a-missing-required-member', 'RFC 0165 §B.1–§B.2', 'rejects an unknown member and a missing required member')).toBe(false);
|
|
66
67
|
const { tenant: _t, ...noTenant } = VALID;
|
|
67
68
|
expect(validate(noTenant)).toBe(false);
|
|
68
69
|
});
|
|
@@ -70,7 +71,7 @@ describe('owner-subject-shape: subject.schema.json (RFC 0165 §B.1)', () => {
|
|
|
70
71
|
it('accepts a nested actor chain (depth is a host obligation, RFC 0165 §B.5)', () => {
|
|
71
72
|
const peer = { issuer: 'https://peer.example', subjectId: 'peer-1', tenant: 'acme', lane: 'workload', kind: 'workload' };
|
|
72
73
|
const { keyClass: _k, ...anon } = { ...VALID, kind: 'anonymous', lane: 'api-key', actor: peer };
|
|
73
|
-
expect(validate(anon), JSON.stringify(validate.errors)).toBe(true);
|
|
74
|
+
expect(validate(anon), req('openwop.it.owner-subject-shape.accepts-a-nested-actor-chain-depth-is-a-host-obligation-rfc-0165-b-5', 'RFC 0165 §B.5', JSON.stringify(validate.errors))).toBe(true);
|
|
74
75
|
});
|
|
75
76
|
});
|
|
76
77
|
|
|
@@ -82,14 +83,14 @@ describe('owner-subject-shape: both owner sub-objects declare `subject` (RFC 016
|
|
|
82
83
|
it('RunSnapshot.owner accepts a subject and still rejects an undeclared member', () => {
|
|
83
84
|
const v = ajvWithSubject().compile(ownerSnap);
|
|
84
85
|
expect(v({ tenant: 'acme', principal: 'idp-op-8f3a', principalKind: 'user', subject: VALID }), JSON.stringify(v.errors)).toBe(true);
|
|
85
|
-
expect(v({ tenant: 'acme', role: 'admin' })).toBe(false);
|
|
86
|
+
expect(v({ tenant: 'acme', role: 'admin' }), req('openwop.it.owner-subject-shape.runsnapshot-owner-accepts-a-subject-and-still-rejects-an-undeclared-member', 'RFC 0165 §B.1–§B.2', 'RunSnapshot.owner accepts a subject and still rejects an undeclared member')).toBe(false);
|
|
86
87
|
});
|
|
87
88
|
|
|
88
89
|
it('run.started owner echo accepts the same subject AND principalKind (the echo matches the snapshot)', () => {
|
|
89
90
|
const v = ajvWithSubject().compile(ownerEcho);
|
|
90
91
|
expect(
|
|
91
92
|
v({ tenant: 'acme', principal: 'idp-op-8f3a', principalKind: 'user', subject: VALID }),
|
|
92
|
-
'RFC 0165 §B.1: run.started owner MUST declare subject and principalKind so a host can echo its snapshot owner verbatim',
|
|
93
|
+
req('openwop.it.owner-subject-shape.run-started-owner-echo-accepts-the-same-subject-and-principalkind-the-echo-match', 'RFC 0165 §B.1–§B.2', 'RFC 0165 §B.1: run.started owner MUST declare subject and principalKind so a host can echo its snapshot owner verbatim'),
|
|
93
94
|
).toBe(true);
|
|
94
95
|
expect(v({ tenant: 'acme', subject: { ...VALID, subjectId: 'a@b' } })).toBe(false);
|
|
95
96
|
});
|
|
@@ -60,7 +60,6 @@
|
|
|
60
60
|
*/
|
|
61
61
|
|
|
62
62
|
import { describe, it, expect } from 'vitest';
|
|
63
|
-
import { driver } from '../lib/driver.js';
|
|
64
63
|
import { behaviorGate, behaviorGatePresent } from '../lib/behavior-gate.js';
|
|
65
64
|
import {
|
|
66
65
|
readArtifactTypesCap,
|
|
@@ -71,6 +70,7 @@ import {
|
|
|
71
70
|
withExtensions,
|
|
72
71
|
canonicalJson,
|
|
73
72
|
} from '../lib/artifactTypes.js';
|
|
73
|
+
import { req } from '../lib/requirement-ids.js';
|
|
74
74
|
|
|
75
75
|
const PROFILE = 'openwop-artifact-type-packs';
|
|
76
76
|
|
|
@@ -102,7 +102,7 @@ describe('pack-manifest-extension-opacity: a host MUST accept an extended manife
|
|
|
102
102
|
if (!behaviorGatePresent(PROFILE, res)) return; // seam absent: skip default, FAIL strict
|
|
103
103
|
expect(
|
|
104
104
|
ok(res.status),
|
|
105
|
-
|
|
105
|
+
req('openwop.it.pack-manifest-extension-opacity.leg-1-an-extension-bearing-manifest-installs', 'node-packs.md §"Vendor extensions on pack manifests"', 'a consumer MUST ignore an unrecognized extension and MUST NOT reject the pack for its presence'),
|
|
106
106
|
).toBe(true);
|
|
107
107
|
});
|
|
108
108
|
|
|
@@ -113,7 +113,7 @@ describe('pack-manifest-extension-opacity: a host MUST accept an extended manife
|
|
|
113
113
|
if (!behaviorGatePresent(PROFILE, res)) return;
|
|
114
114
|
expect(
|
|
115
115
|
ok(res.status),
|
|
116
|
-
|
|
116
|
+
req('openwop.it.pack-manifest-extension-opacity.leg-2-the-same-manifest-without-the-extension-installs-baseline', 'RFC 0139 §Conformance', 'baseline — guards against a differential that passes because EVERYTHING fails'),
|
|
117
117
|
).toBe(true);
|
|
118
118
|
});
|
|
119
119
|
});
|
|
@@ -130,12 +130,12 @@ describe('pack-manifest-extension-opacity: the differential — behavior MUST NO
|
|
|
130
130
|
|
|
131
131
|
expect(
|
|
132
132
|
ok(plain.status) === ok(extended.status),
|
|
133
|
-
|
|
133
|
+
req('openwop.it.pack-manifest-extension-opacity.leg-3-projections-for-m-and-m-are-equal-modulo-the-extensions-load-bearing', 'RFC 0139 §"The differential-install contract"', 'acceptance MUST NOT be a function of an unrecognized extension — a host MUST accept both or reject both for the same reason'),
|
|
134
134
|
).toBe(true);
|
|
135
135
|
|
|
136
136
|
expect(
|
|
137
137
|
canonicalJson(stripExtensions(extended.json)),
|
|
138
|
-
|
|
138
|
+
req('openwop.it.pack-manifest-extension-opacity.leg-3-projections-for-m-and-m-are-equal-modulo-the-extensions-load-bearing', 'RFC 0139 §"The differential-install contract"', 'the registration projection MUST be identical modulo the extension properties — a difference means the host acted on an extension it does not recognize (rendering catalog, derived facet, or code-path switch)'),
|
|
139
139
|
).toBe(canonicalJson(stripExtensions(plain.json)));
|
|
140
140
|
});
|
|
141
141
|
|
|
@@ -150,12 +150,12 @@ describe('pack-manifest-extension-opacity: the differential — behavior MUST NO
|
|
|
150
150
|
|
|
151
151
|
expect(
|
|
152
152
|
ok(hostile.status),
|
|
153
|
-
|
|
153
|
+
req('openwop.it.pack-manifest-extension-opacity.leg-4-an-extension-carrying-markup-a-templating-directive-is-not-interpreted-at', 'node-packs.md §"Vendor extensions on pack manifests"', 'an extension value is PACK-AUTHORED and therefore untrusted, but untrusted is not a rejection reason — the host MUST install and ignore'),
|
|
154
154
|
).toBe(true);
|
|
155
155
|
|
|
156
156
|
expect(
|
|
157
157
|
canonicalJson(stripExtensions(hostile.json)),
|
|
158
|
-
|
|
158
|
+
req('openwop.it.pack-manifest-extension-opacity.leg-4-an-extension-carrying-markup-a-templating-directive-is-not-interpreted-at', 'node-packs.md §"Vendor extensions on pack manifests"', 'MUST NOT render it, execute it, or interpret it as markup or a templating directive — a projection that differs from baseline means the value reached an interpreter'),
|
|
159
159
|
).toBe(canonicalJson(stripExtensions(plain.json)));
|
|
160
160
|
});
|
|
161
161
|
});
|
|
@@ -173,7 +173,7 @@ describe('pack-manifest-extension-opacity: the HOST reader stays narrow (RFC 013
|
|
|
173
173
|
if (!behaviorGatePresent(PROFILE, res)) return;
|
|
174
174
|
expect(
|
|
175
175
|
ok(res.status),
|
|
176
|
-
|
|
176
|
+
req('openwop.it.pack-manifest-extension-opacity.leg-5-a-misspelled-canonical-field-is-still-rejected-by-the-host-not-merely-by-t', 'node-packs.md §"Vendor extensions on pack manifests"', 'the hatch admits DECLARED extensions, not arbitrary keys — a host that widened its own reader to additionalProperties:true to "support extensions" accepts everything, which is not the same as accepting extensions'),
|
|
177
177
|
).toBe(false);
|
|
178
178
|
});
|
|
179
179
|
});
|
|
@@ -23,6 +23,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
23
23
|
import { softSkip } from '../lib/soft-skip.js';
|
|
24
24
|
import { driver } from '../lib/driver.js';
|
|
25
25
|
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
26
|
+
import { req } from '../lib/requirement-ids.js';
|
|
26
27
|
|
|
27
28
|
interface DiscoveryDoc {
|
|
28
29
|
capabilities?: Record<string, unknown>;
|
|
@@ -88,12 +89,12 @@ describe('pack-registry-isolation: test catalog MUST NOT bleed into production (
|
|
|
88
89
|
const stringified = body ? JSON.stringify(body) : '';
|
|
89
90
|
expect(
|
|
90
91
|
stringified.includes(name),
|
|
91
|
-
|
|
92
|
+
req('openwop.it.pack-registry-isolation.a-pack-put-to-v1-packs-test-name-must-not-appear-in-get-v1-packs-name',
|
|
92
93
|
'RFCS/0025-test-mode-registry-namespace.md §C point 1',
|
|
93
94
|
`pack name '${name}' was written via /v1/packs-test/${name}@${version} but appeared in /v1/packs/${name} response body — test-catalog isolation MUST hold`,
|
|
94
95
|
),
|
|
95
96
|
).toBe(false);
|
|
96
|
-
return;
|
|
97
|
+
return softSkip('blocked', 'precondition not met — `prodRes.status === 200` returned early (404 is the canonical "not found" — exactly what isolation requires. 200 with a payload that does NOT name our pack would mean the host returned a listing…');
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
// Acceptable: 4xx range (404 pack_not_found is the spec-canonical
|
|
@@ -101,7 +102,7 @@ describe('pack-registry-isolation: test catalog MUST NOT bleed into production (
|
|
|
101
102
|
// signal satisfies the invariant).
|
|
102
103
|
expect(
|
|
103
104
|
prodRes.status >= 400 && prodRes.status < 500,
|
|
104
|
-
|
|
105
|
+
req('openwop.it.pack-registry-isolation.a-pack-put-to-v1-packs-test-name-must-not-appear-in-get-v1-packs-name',
|
|
105
106
|
'RFCS/0025-test-mode-registry-namespace.md §C point 1',
|
|
106
107
|
`expected production-namespace GET to return 4xx for a test-namespace-only pack '${name}', got ${prodRes.status}`,
|
|
107
108
|
),
|
|
@@ -26,6 +26,8 @@ import { driver } from '../lib/driver.js';
|
|
|
26
26
|
import { discoveryFamilies } from '../lib/discovery-capabilities.js';
|
|
27
27
|
import { recordRequirement } from '../lib/requirement-ledger.js';
|
|
28
28
|
import { requirementIdForScenario } from '../lib/requirement-registry.js';
|
|
29
|
+
import { req } from '../lib/requirement-ids.js';
|
|
30
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
29
31
|
|
|
30
32
|
interface DiscoveryDoc {
|
|
31
33
|
capabilities?: Record<string, unknown>;
|
|
@@ -145,48 +147,48 @@ function errorCode(body: unknown): string | undefined {
|
|
|
145
147
|
|
|
146
148
|
describe('pack-registry-publish: URL / scope error catalog (RFC 0025)', () => {
|
|
147
149
|
it('PUT with non-spec scope MUST return 400 invalid_pack_scope', async () => {
|
|
148
|
-
if (!(await isTestModeAdvertised())) return;
|
|
150
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
149
151
|
const res = await putTest('bogus.unsupported-scope.pack', '1.0.0', Buffer.from([]));
|
|
150
|
-
if (res.status === 404) return; // seam not exposed
|
|
152
|
+
if (res.status === 404) return softSkip('blocked', 'precondition not met — `res.status === 404` returned early (seam not exposed) (seam, prior step, or fixture unavailable)'); // seam not exposed
|
|
151
153
|
expect(res.status).toBeGreaterThanOrEqual(400);
|
|
152
154
|
expect(res.status).toBeLessThan(500);
|
|
153
155
|
expect(
|
|
154
156
|
errorCode(res.json),
|
|
155
|
-
|
|
157
|
+
req('openwop.it.pack-registry-publish.put-with-non-spec-scope-must-return-400-invalid-pack-scope', 'node-packs.md §"PUT /v1/packs/{name}/-/{version}.tgz"', 'non-spec scope MUST return invalid_pack_scope'),
|
|
156
158
|
).toBe('invalid_pack_scope');
|
|
157
159
|
});
|
|
158
160
|
|
|
159
161
|
it('PUT with a single-segment URL pack name MUST return 400 invalid_pack_name', async () => {
|
|
160
|
-
if (!(await isTestModeAdvertised())) return;
|
|
162
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
161
163
|
const res = await putTest('singleseg', '1.0.0', Buffer.from([]));
|
|
162
|
-
if (res.status === 404) return;
|
|
163
|
-
expect(res.status).toBe(400);
|
|
164
|
+
if (res.status === 404) return softSkip('blocked', 'precondition not met — `res.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
165
|
+
expect(res.status, req('openwop.it.pack-registry-publish.put-with-a-single-segment-url-pack-name-must-return-400-invalid-pack-name', 'node-packs.md', 'PUT with a single-segment URL pack name MUST return 400 invalid_pack_name')).toBe(400);
|
|
164
166
|
expect(errorCode(res.json)).toBe('invalid_pack_name');
|
|
165
167
|
});
|
|
166
168
|
|
|
167
169
|
it('PUT with a non-semver URL version MUST return 400 invalid_version', async () => {
|
|
168
|
-
if (!(await isTestModeAdvertised())) return;
|
|
170
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
169
171
|
const res = await putTest(freshPackName(), 'not-a-semver', Buffer.from([]));
|
|
170
|
-
if (res.status === 404) return;
|
|
171
|
-
expect(res.status).toBe(400);
|
|
172
|
+
if (res.status === 404) return softSkip('blocked', 'precondition not met — `res.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
173
|
+
expect(res.status, req('openwop.it.pack-registry-publish.put-with-a-non-semver-url-version-must-return-400-invalid-version', 'node-packs.md', 'PUT with a non-semver URL version MUST return 400 invalid_version')).toBe(400);
|
|
172
174
|
expect(errorCode(res.json)).toBe('invalid_version');
|
|
173
175
|
});
|
|
174
176
|
});
|
|
175
177
|
|
|
176
178
|
describe('pack-registry-publish: body-shape error catalog (RFC 0025)', () => {
|
|
177
179
|
it('PUT with a JSON body (instead of tarball bytes) MUST return 400 invalid_body', async () => {
|
|
178
|
-
if (!(await isTestModeAdvertised())) return;
|
|
180
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
179
181
|
const res = await driver.put(`/v1/packs-test/${encodeURIComponent(freshPackName())}/-/1.0.0.tgz`, JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } });
|
|
180
|
-
if (res.status === 404) return;
|
|
181
|
-
expect(res.status).toBe(400);
|
|
182
|
+
if (res.status === 404) return softSkip('blocked', 'precondition not met — `res.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
183
|
+
expect(res.status, req('openwop.it.pack-registry-publish.put-with-a-json-body-instead-of-tarball-bytes-must-return-400-invalid-body', 'node-packs.md', 'PUT with a JSON body (instead of tarball bytes) MUST return 400 invalid_body')).toBe(400);
|
|
182
184
|
expect(errorCode(res.json)).toBe('invalid_body');
|
|
183
185
|
});
|
|
184
186
|
|
|
185
187
|
it('PUT with an empty body MUST return 400 invalid_body', async () => {
|
|
186
|
-
if (!(await isTestModeAdvertised())) return;
|
|
188
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
187
189
|
const res = await putTest(freshPackName(), '1.0.0', Buffer.from([]));
|
|
188
|
-
if (res.status === 404) return;
|
|
189
|
-
expect(res.status).toBe(400);
|
|
190
|
+
if (res.status === 404) return softSkip('blocked', 'precondition not met — `res.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
191
|
+
expect(res.status, req('openwop.it.pack-registry-publish.put-with-an-empty-body-must-return-400-invalid-body', 'node-packs.md', 'PUT with an empty body MUST return 400 invalid_body')).toBe(400);
|
|
190
192
|
expect(errorCode(res.json)).toBe('invalid_body');
|
|
191
193
|
});
|
|
192
194
|
});
|
|
@@ -196,15 +198,15 @@ describe('pack-registry-publish: tarball extraction error catalog (RFC 0025)', (
|
|
|
196
198
|
// For shape-only assertions, we don't need real gzip; the host's
|
|
197
199
|
// gunzip step fails first, surfacing tarball_gunzip_failed.
|
|
198
200
|
it('PUT with a body that isn\'t a valid gzip stream MUST return 400 tarball_gunzip_failed', async () => {
|
|
199
|
-
if (!(await isTestModeAdvertised())) return;
|
|
201
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
200
202
|
const res = await putTest(freshPackName(), '1.0.0', Buffer.from('not a gzip stream'));
|
|
201
|
-
if (res.status === 404) return;
|
|
202
|
-
expect(res.status).toBe(400);
|
|
203
|
+
if (res.status === 404) return softSkip('blocked', 'precondition not met — `res.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
204
|
+
expect(res.status, req('openwop.it.pack-registry-publish.put-with-a-body-that-isn-t-a-valid-gzip-stream-must-return-400-tarball-gunzip-fa', 'node-packs.md', 'PUT with a body that isn\'t a valid gzip stream MUST return 400 tarball_gunzip_failed')).toBe(400);
|
|
203
205
|
expect(errorCode(res.json)).toBe('tarball_gunzip_failed');
|
|
204
206
|
});
|
|
205
207
|
|
|
206
208
|
it('PUT with decompressed bytes exceeding the registry\'s cap MUST return 400 tarball_too_large', async () => {
|
|
207
|
-
if (!(await isTestModeAdvertised())) return;
|
|
209
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
208
210
|
// S45 (2026-08-18): the cap is on DECOMPRESSED bytes (node-packs.md
|
|
209
211
|
// §"Tarball extraction", recommended default 50 MB), so the probe is a
|
|
210
212
|
// small VALID gzip stream that inflates past it — 64 MiB of zeros gzips
|
|
@@ -219,142 +221,142 @@ describe('pack-registry-publish: tarball extraction error catalog (RFC 0025)', (
|
|
|
219
221
|
// tar layer means the cap is absent or above 64 MiB (> the recommended
|
|
220
222
|
// default) — say so in the run record if that is a deliberate host choice.
|
|
221
223
|
const res = await putTest(freshPackName(), '1.0.0', OVERSIZED_GZIP);
|
|
222
|
-
if (res.status === 404) return;
|
|
224
|
+
if (res.status === 404) return softSkip('blocked', 'precondition not met — `res.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
223
225
|
expect(
|
|
224
226
|
res.status,
|
|
225
|
-
|
|
227
|
+
req('openwop.it.pack-registry-publish.put-with-decompressed-bytes-exceeding-the-registry-s-cap-must-return-400-tarball', 'node-packs.md §"Tarball extraction"', 'a valid gzip inflating past the decompressed cap MUST be refused with 400 (a 413 here means a proxy refused the wire body — this probe is ~64 KB on the wire, so that is a host/proxy misconfiguration, not the cap)'),
|
|
226
228
|
).toBe(400);
|
|
227
229
|
expect(
|
|
228
230
|
errorCode(res.json),
|
|
229
|
-
|
|
231
|
+
req('openwop.it.pack-registry-publish.put-with-decompressed-bytes-exceeding-the-registry-s-cap-must-return-400-tarball', 'node-packs.md §"Tarball extraction"', 'decompressed bytes exceeding the registry cap MUST surface tarball_too_large (recommended default cap 50 MB; this probe inflates to 64 MiB)'),
|
|
230
232
|
).toBe('tarball_too_large');
|
|
231
233
|
});
|
|
232
234
|
|
|
233
235
|
it('PUT with no `pack.json` at the tarball root MUST return 400 tarball_manifest_missing', async () => {
|
|
234
|
-
if (!(await isTestModeAdvertised())) return;
|
|
236
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
235
237
|
// Stub: a real test would build a minimal gzip+tar with no pack.json.
|
|
236
238
|
// For now, soft-skip when the host needs a real tarball structure to reach this code path.
|
|
237
|
-
return;
|
|
239
|
+
return softSkip('skipped', 'suite-side limitation — Stub: a real test would build a minimal gzip+tar with no pack.json. For now, soft-skip when the host needs a real tarball structure to reach this code path.');
|
|
238
240
|
});
|
|
239
241
|
|
|
240
242
|
it('PUT with `pack.json` exceeding the registry\'s per-file cap MUST return 400 tarball_manifest_too_large', async () => {
|
|
241
|
-
if (!(await isTestModeAdvertised())) return;
|
|
242
|
-
return; // requires a real tarball builder — defer to host-side test
|
|
243
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
244
|
+
return softSkip('skipped', 'suite-side limitation — requires a real tarball builder — defer to host-side test'); // requires a real tarball builder — defer to host-side test
|
|
243
245
|
});
|
|
244
246
|
|
|
245
247
|
it('PUT with `pack.json` that isn\'t valid JSON MUST return 400 tarball_manifest_not_json', async () => {
|
|
246
|
-
if (!(await isTestModeAdvertised())) return;
|
|
247
|
-
return; // requires a real tarball builder
|
|
248
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
249
|
+
return softSkip('skipped', 'suite-side limitation — requires a real tarball builder'); // requires a real tarball builder
|
|
248
250
|
});
|
|
249
251
|
|
|
250
252
|
it('PUT with `manifest.runtime.entry` declaring a path that isn\'t in the tarball MUST return 400 tarball_entry_missing', async () => {
|
|
251
|
-
if (!(await isTestModeAdvertised())) return;
|
|
252
|
-
return; // requires a real tarball builder
|
|
253
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
254
|
+
return softSkip('skipped', 'suite-side limitation — requires a real tarball builder'); // requires a real tarball builder
|
|
253
255
|
});
|
|
254
256
|
|
|
255
257
|
it('PUT with an entry source exceeding the registry\'s per-file cap MUST return 400 tarball_entry_too_large', async () => {
|
|
256
|
-
if (!(await isTestModeAdvertised())) return;
|
|
257
|
-
return; // requires a real tarball builder
|
|
258
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
259
|
+
return softSkip('skipped', 'suite-side limitation — requires a real tarball builder'); // requires a real tarball builder
|
|
258
260
|
});
|
|
259
261
|
|
|
260
262
|
it('PUT with a tarball entry whose name contains `..` or otherwise escapes the pack root MUST return 400 tarball_path_traversal', async () => {
|
|
261
|
-
if (!(await isTestModeAdvertised())) return;
|
|
262
|
-
return; // requires a real tarball builder
|
|
263
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
264
|
+
return softSkip('skipped', 'suite-side limitation — requires a real tarball builder'); // requires a real tarball builder
|
|
263
265
|
});
|
|
264
266
|
|
|
265
267
|
it('PUT with a tar stream that the parser can\'t read past the gzip layer MUST return 400 tarball_tar_parse_failed', async () => {
|
|
266
|
-
if (!(await isTestModeAdvertised())) return;
|
|
268
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
267
269
|
// A gzip stream of garbage (header valid, payload not a tar)
|
|
268
270
|
const garbage = Buffer.from([0x1f, 0x8b, 0x08, 0x00, 0, 0, 0, 0, 0, 0xff, 0x01, 0x02]);
|
|
269
271
|
const res = await putTest(freshPackName(), '1.0.0', garbage);
|
|
270
|
-
if (res.status === 404) return;
|
|
271
|
-
if (res.status < 400 || res.status >= 500) return; // host may not reach this code path with garbage gzip
|
|
272
|
+
if (res.status === 404) return softSkip('blocked', 'precondition not met — `res.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
273
|
+
if (res.status < 400 || res.status >= 500) return softSkip('blocked', 'precondition not met — `res.status < 400 || res.status >= 500` returned early (host may not reach this code path with garbage gzip) (seam, prior step, or fixture unavailable)'); // host may not reach this code path with garbage gzip
|
|
272
274
|
const code = errorCode(res.json);
|
|
273
275
|
expect(
|
|
274
276
|
['tarball_tar_parse_failed', 'tarball_gunzip_failed'].includes(code ?? ''),
|
|
275
|
-
|
|
277
|
+
req('openwop.it.pack-registry-publish.put-with-a-tar-stream-that-the-parser-can-t-read-past-the-gzip-layer-must-return', 'node-packs.md', 'garbage gzip stream MUST surface tarball_tar_parse_failed or tarball_gunzip_failed'),
|
|
276
278
|
).toBe(true);
|
|
277
279
|
});
|
|
278
280
|
});
|
|
279
281
|
|
|
280
282
|
describe('pack-registry-publish: manifest contents error catalog (RFC 0025)', () => {
|
|
281
283
|
it('PUT with a `pack.json` that fails schema validation MUST return 400 invalid_manifest', async () => {
|
|
282
|
-
if (!(await isTestModeAdvertised())) return;
|
|
283
|
-
return; // requires a real tarball builder + intentionally-invalid manifest
|
|
284
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
285
|
+
return softSkip('skipped', 'suite-side limitation — requires a real tarball builder + intentionally-invalid manifest'); // requires a real tarball builder + intentionally-invalid manifest
|
|
284
286
|
});
|
|
285
287
|
|
|
286
288
|
it('PUT with `manifest.name`/`manifest.version` differing from URL MUST return 400 manifest_mismatch (or granular pair)', async () => {
|
|
287
|
-
if (!(await isTestModeAdvertised())) return;
|
|
288
|
-
return; // requires a real tarball builder
|
|
289
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
290
|
+
return softSkip('skipped', 'suite-side limitation — requires a real tarball builder'); // requires a real tarball builder
|
|
289
291
|
});
|
|
290
292
|
|
|
291
293
|
it('PUT with server-computed SHA-256 not matching `X-Pack-Sha256` MUST return 400 pack_integrity_failure', async () => {
|
|
292
|
-
if (!(await isTestModeAdvertised())) return;
|
|
294
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
293
295
|
const res = await putTest(freshPackName(), '1.0.0', Buffer.from([0x1f, 0x8b, 0]), { 'X-Pack-Sha256': '0'.repeat(64) });
|
|
294
|
-
if (res.status === 404) return;
|
|
295
|
-
if (res.status < 400) return; // host may not validate header on garbage gzip
|
|
296
|
+
if (res.status === 404) return softSkip('blocked', 'precondition not met — `res.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
297
|
+
if (res.status < 400) return softSkip('blocked', 'precondition not met — `res.status < 400` returned early (host may not validate header on garbage gzip) (seam, prior step, or fixture unavailable)'); // host may not validate header on garbage gzip
|
|
296
298
|
const code = errorCode(res.json);
|
|
297
299
|
expect(
|
|
298
300
|
['pack_integrity_failure', 'tarball_gunzip_failed', 'invalid_body'].includes(code ?? ''),
|
|
299
|
-
|
|
301
|
+
req('openwop.it.pack-registry-publish.put-with-server-computed-sha-256-not-matching-x-pack-sha256-must-return-400-pack', 'node-packs.md', 'SHA-256 mismatch MUST be detectable; absence of valid gzip masks this case for the test'),
|
|
300
302
|
).toBe(true);
|
|
301
303
|
});
|
|
302
304
|
|
|
303
305
|
it('PUT with `runtime.language` value not accepted by the registry MUST return 400 unsupported_runtime', async () => {
|
|
304
|
-
if (!(await isTestModeAdvertised())) return;
|
|
305
|
-
return; // requires a real tarball builder + manifest with unsupported runtime
|
|
306
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
307
|
+
return softSkip('skipped', 'suite-side limitation — requires a real tarball builder + manifest with unsupported runtime'); // requires a real tarball builder + manifest with unsupported runtime
|
|
306
308
|
});
|
|
307
309
|
});
|
|
308
310
|
|
|
309
311
|
describe('pack-registry-publish: authorization + conflict (RFC 0025)', () => {
|
|
310
312
|
it('PUT without `packs:publish` scope or namespace claim MUST return 403 forbidden', async () => {
|
|
311
|
-
if (!(await isTestModeAdvertised())) return;
|
|
313
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
312
314
|
// The test-mode catalog typically allows the conformance suite's API key
|
|
313
315
|
// by design; this assertion gates on the host returning 403 with the
|
|
314
316
|
// canonical code when scope IS missing (some hosts MAY accept the suite
|
|
315
317
|
// key universally — in that case the test soft-skips).
|
|
316
|
-
return;
|
|
318
|
+
return softSkip('skipped', 'suite-side limitation — The test-mode catalog typically allows the conformance suite\'s API key by design; this assertion gates on the host returning 403 with the canonical code when scope IS missing (some hosts MAY ac…');
|
|
317
319
|
});
|
|
318
320
|
|
|
319
321
|
it('PUT for an existing (name, version) with DIFFERENT content MUST return 409 conflict', async () => {
|
|
320
|
-
if (!(await isTestModeAdvertised())) return;
|
|
321
|
-
return; // requires successful first PUT then conflicting second PUT
|
|
322
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
323
|
+
return softSkip('skipped', 'suite-side limitation — requires successful first PUT then conflicting second PUT'); // requires successful first PUT then conflicting second PUT
|
|
322
324
|
});
|
|
323
325
|
|
|
324
326
|
it('PUT for an existing (name, version) with IDENTICAL sha256 content MUST return 200 OK (idempotent re-publish)', async () => {
|
|
325
|
-
if (!(await isTestModeAdvertised())) return;
|
|
326
|
-
return; // requires successful first PUT, then identical second PUT
|
|
327
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
328
|
+
return softSkip('skipped', 'suite-side limitation — requires successful first PUT, then identical second PUT'); // requires successful first PUT, then identical second PUT
|
|
327
329
|
});
|
|
328
330
|
});
|
|
329
331
|
|
|
330
332
|
describe('pack-registry-publish: unpublish window (RFC 0025)', () => {
|
|
331
333
|
it('DELETE for a version older than the unpublish window MUST return 400 unpublish_window_expired', async () => {
|
|
332
|
-
if (!(await isTestModeAdvertised())) return;
|
|
333
|
-
return; // requires time-travel or an explicit aged-version fixture
|
|
334
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
335
|
+
return softSkip('skipped', 'suite-side limitation — requires time-travel or an explicit aged-version fixture'); // requires time-travel or an explicit aged-version fixture
|
|
334
336
|
});
|
|
335
337
|
});
|
|
336
338
|
|
|
337
339
|
describe('pack-registry-publish: signature endpoint pairing (RFC 0025)', () => {
|
|
338
340
|
it('after PUT WITHOUT signature, GET /sig MUST return 404 signature_not_available', async () => {
|
|
339
|
-
if (!(await isTestModeAdvertised())) return;
|
|
341
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
340
342
|
const name = freshPackName();
|
|
341
343
|
const sigRes = await getTestSignature(name, '1.0.0');
|
|
342
344
|
if (sigRes.status === 404) {
|
|
343
345
|
// Could be either "seam returns 404 on missing pack" OR "signature_not_available 404"
|
|
344
346
|
const code = errorCode(sigRes.json);
|
|
345
|
-
if (code === 'signature_not_available' || code === undefined) return; // shape-conformant either way
|
|
347
|
+
if (code === 'signature_not_available' || code === undefined) return softSkip('blocked', 'precondition not met — `code === \'signature_not_available\' || code === undefined` returned early (shape-conformant either way) (seam, prior step, or fixture unavailable)'); // shape-conformant either way
|
|
346
348
|
}
|
|
347
349
|
// If a real test had PUT a pack without sig and gotten 200 back, the next GET .sig MUST be 404.
|
|
348
|
-
return; // soft-skip — requires successful prior PUT
|
|
350
|
+
return softSkip('skipped', 'suite-side limitation — soft-skip — requires successful prior PUT'); // soft-skip — requires successful prior PUT
|
|
349
351
|
});
|
|
350
352
|
|
|
351
353
|
it('after PUT WITH signature blob, GET /sig MUST return 200 (or 302 to signed URL)', async () => {
|
|
352
|
-
if (!(await isTestModeAdvertised())) return;
|
|
353
|
-
return; // requires real tarball with signature.sig at root
|
|
354
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
355
|
+
return softSkip('skipped', 'suite-side limitation — requires real tarball with signature.sig at root'); // requires real tarball with signature.sig at root
|
|
354
356
|
});
|
|
355
357
|
|
|
356
358
|
it('after YANK, GET /sig MUST return 404 signature_not_available', async () => {
|
|
357
|
-
if (!(await isTestModeAdvertised())) return;
|
|
358
|
-
return; // requires successful PUT then YANK
|
|
359
|
+
if (!(await isTestModeAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isTestModeAdvertised())` returned early');
|
|
360
|
+
return softSkip('skipped', 'suite-side limitation — requires successful PUT then YANK'); // requires successful PUT then YANK
|
|
359
361
|
});
|
|
360
362
|
});
|