@openwop/openwop-conformance 1.6.0 → 1.10.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/CHANGELOG.md +18 -0
- package/README.md +2 -2
- package/api/asyncapi.yaml +74 -1
- package/api/openapi.yaml +316 -0
- package/coverage.md +16 -0
- package/fixtures/conformance-run-duration-breach.json +33 -0
- package/fixtures.md +19 -0
- package/package.json +1 -1
- package/schemas/README.md +12 -0
- package/schemas/agent-inventory-response.schema.json +90 -0
- package/schemas/ai-envelope.schema.json +28 -0
- package/schemas/annotation-create.schema.json +37 -0
- package/schemas/annotation.schema.json +56 -0
- package/schemas/artifact-type-pack-manifest.schema.json +160 -0
- package/schemas/capabilities.schema.json +195 -4
- package/schemas/chat-card-pack-manifest.schema.json +158 -0
- package/schemas/envelopes/media.audio.schema.json +38 -0
- package/schemas/envelopes/media.file.schema.json +37 -0
- package/schemas/envelopes/media.image.schema.json +33 -0
- package/schemas/heartbeat-evaluated.schema.json +14 -0
- package/schemas/heartbeat-state-changed.schema.json +14 -0
- package/schemas/node-pack-manifest.schema.json +16 -1
- package/schemas/run-event-payloads.schema.json +96 -5
- package/schemas/run-event.schema.json +4 -0
- package/schemas/workflow-definition.schema.json +5 -0
- package/schemas/workspace-file-create.schema.json +20 -0
- package/schemas/workspace-file.schema.json +39 -0
- package/src/lib/agentLoop.ts +44 -0
- package/src/lib/agentRuntime.ts +45 -0
- package/src/lib/artifactTypes.ts +96 -0
- package/src/lib/cardPacks.ts +52 -0
- package/src/lib/discovery-capabilities.ts +50 -0
- package/src/lib/distillation.ts +38 -0
- package/src/lib/feedback.ts +31 -0
- package/src/lib/heartbeat.ts +31 -0
- package/src/lib/memoryAttribution.ts +48 -0
- package/src/lib/subRunAttestation.ts +35 -0
- package/src/lib/toolHooks.ts +33 -0
- package/src/scenarios/agent-loop-iteration-monotonic.test.ts +33 -0
- package/src/scenarios/agent-loop-stateful-resume.test.ts +28 -0
- package/src/scenarios/agent-loop-version5-shape.test.ts +41 -0
- package/src/scenarios/agent-loop-workspace-snapshot.test.ts +33 -0
- package/src/scenarios/agent-manifest-runtime.test.ts +85 -0
- package/src/scenarios/ai-envelope-shape.test.ts +14 -18
- package/src/scenarios/aiEnvelope.capBreached.test.ts +2 -1
- package/src/scenarios/aiEnvelope.schemaDrift.test.ts +2 -1
- package/src/scenarios/aiEnvelope.universalKinds.test.ts +2 -1
- package/src/scenarios/approval-gate-flow.test.ts +4 -6
- package/src/scenarios/artifact-schema-compile-bounded.test.ts +126 -0
- package/src/scenarios/artifact-type-pack-install.test.ts +78 -0
- package/src/scenarios/artifact-type-pack-manifest-validation.test.ts +140 -0
- package/src/scenarios/artifact-type-store-without-render.test.ts +54 -0
- package/src/scenarios/audit-log-integrity.test.ts +3 -2
- package/src/scenarios/auth-api-key-rotation.test.ts +2 -1
- package/src/scenarios/auth-mtls.test.ts +2 -1
- package/src/scenarios/auth-oauth2-client-credentials.test.ts +2 -1
- package/src/scenarios/auth-oidc-user-bearer.test.ts +2 -1
- package/src/scenarios/auth-saml-profile.test.ts +2 -1
- package/src/scenarios/auth-scim-profile.test.ts +2 -1
- package/src/scenarios/authorization-fail-closed.test.ts +2 -1
- package/src/scenarios/authorization-roles-shape.test.ts +2 -1
- package/src/scenarios/byok-auth-modes.test.ts +141 -0
- package/src/scenarios/chat-card-pack-execution.test.ts +56 -0
- package/src/scenarios/chat-card-pack-manifest-validation.test.ts +128 -0
- package/src/scenarios/commitment-fired.test.ts +83 -0
- package/src/scenarios/credential-payload-redaction.test.ts +2 -1
- package/src/scenarios/credentials-capability-shape.test.ts +2 -1
- package/src/scenarios/cross-engine-append-ordering.test.ts +2 -1
- package/src/scenarios/cross-host-ancestry-endpoint.test.ts +3 -2
- package/src/scenarios/cross-host-causation-shape.test.ts +3 -2
- package/src/scenarios/deadletter-capability-shape.test.ts +2 -1
- package/src/scenarios/deadletter-retry-exhaustion.test.ts +2 -1
- package/src/scenarios/distillation-index-roundtrip.test.ts +35 -0
- package/src/scenarios/distillation-secret-carryforward.test.ts +35 -0
- package/src/scenarios/distillation-shape.test.ts +41 -0
- package/src/scenarios/distillation-stable-archive.test.ts +37 -0
- package/src/scenarios/distillation-token-budget.test.ts +45 -0
- package/src/scenarios/envelope-completion-distinguishes-truncation.test.ts +4 -3
- package/src/scenarios/envelope-reasoning-secret-redaction.test.ts +5 -4
- package/src/scenarios/envelope-reasoning-shape.test.ts +3 -2
- package/src/scenarios/envelope-refusal-shape.test.ts +3 -2
- package/src/scenarios/envelope-rendering-hint.test.ts +95 -0
- package/src/scenarios/envelope-retry-attempted.test.ts +2 -1
- package/src/scenarios/envelope-tier-one-subset-static.test.ts +3 -2
- package/src/scenarios/exec-not-protocol-tier.test.ts +137 -0
- package/src/scenarios/experimental-tier-shape.test.ts +5 -4
- package/src/scenarios/feedback-capability-shape.test.ts +35 -0
- package/src/scenarios/feedback-correction-redaction.test.ts +35 -0
- package/src/scenarios/feedback-cross-tenant-isolation.test.ts +37 -0
- package/src/scenarios/feedback-fork-not-copied.test.ts +40 -0
- package/src/scenarios/feedback-on-terminal-run.test.ts +32 -0
- package/src/scenarios/feedback-record-and-list.test.ts +32 -0
- package/src/scenarios/feedback-unsupported-501.test.ts +32 -0
- package/src/scenarios/fs-path-traversal.test.ts +2 -1
- package/src/scenarios/heartbeat-capability-shape.test.ts +35 -0
- package/src/scenarios/heartbeat-fires-once-per-tick.test.ts +28 -0
- package/src/scenarios/heartbeat-idempotent-no-spam.test.ts +43 -0
- package/src/scenarios/heartbeat-runtime-bound.test.ts +30 -0
- package/src/scenarios/http-client-ssrf.test.ts +10 -13
- package/src/scenarios/mcp-toolcall-redaction.test.ts +3 -2
- package/src/scenarios/media-url-inline-cap.test.ts +167 -0
- package/src/scenarios/memory-attribution-emits-on-write.test.ts +54 -0
- package/src/scenarios/memory-attribution-no-content.test.ts +45 -0
- package/src/scenarios/memory-attribution-replay-stable.test.ts +60 -0
- package/src/scenarios/memory-attribution-shape.test.ts +28 -0
- package/src/scenarios/memory-attribution-tenant-scoped.test.ts +44 -0
- package/src/scenarios/memory-compaction-event-emitted.test.ts +2 -1
- package/src/scenarios/memory-compaction-provenance-tag.test.ts +2 -1
- package/src/scenarios/memory-compaction-sr1-carry-forward.test.ts +2 -1
- package/src/scenarios/memory-consolidation-idempotent.test.ts +77 -0
- package/src/scenarios/memory-consolidation-shape.test.ts +90 -0
- package/src/scenarios/model-capability-substituted.test.ts +2 -1
- package/src/scenarios/multi-agent-confidence-escalation.test.ts +5 -4
- package/src/scenarios/multi-agent-handoff-state-machine.test.ts +6 -5
- package/src/scenarios/multi-agent-memory-lifecycle.test.ts +4 -3
- package/src/scenarios/multi-region-idempotency.test.ts +10 -10
- package/src/scenarios/oauth-capability-shape.test.ts +2 -1
- package/src/scenarios/oauth-connector-redaction.test.ts +2 -1
- package/src/scenarios/pause-resume.test.ts +3 -3
- package/src/scenarios/production-backpressure.test.ts +2 -2
- package/src/scenarios/production-retention-expiry.test.ts +2 -2
- package/src/scenarios/prompt-all-four-kinds-events.test.ts +2 -1
- package/src/scenarios/prompt-composed-secret-redaction.test.ts +2 -1
- package/src/scenarios/prompt-composed-trust-marker.test.ts +2 -1
- package/src/scenarios/prompt-end-to-end-events.test.ts +2 -1
- package/src/scenarios/prompt-list-and-fetch.test.ts +2 -1
- package/src/scenarios/prompt-mutable-lifecycle.test.ts +2 -1
- package/src/scenarios/prompt-mutation-workspace-membership-enforced.test.ts +2 -1
- package/src/scenarios/prompt-pack-install.test.ts +2 -1
- package/src/scenarios/prompt-read-workspace-membership-enforced.test.ts +2 -1
- package/src/scenarios/prompt-render-deterministic.test.ts +2 -1
- package/src/scenarios/prompt-resolution-chain-agent-intrinsic.test.ts +2 -1
- package/src/scenarios/prompt-resolution-chain-fallback-cascade.test.ts +2 -1
- package/src/scenarios/prompt-resolution-chain-node-wins.test.ts +2 -1
- package/src/scenarios/prompt-template-shape.test.ts +2 -1
- package/src/scenarios/provider-usage.test.ts +2 -1
- package/src/scenarios/redaction.test.ts +4 -1
- package/src/scenarios/replay-divergence-at-refusal.test.ts +4 -3
- package/src/scenarios/replay-fork-arbitrary.test.ts +3 -1
- package/src/scenarios/replay-llm-cache-key-portable.test.ts +2 -1
- package/src/scenarios/replayDeterminism.test.ts +3 -1
- package/src/scenarios/run-execution-bounds-shape.test.ts +133 -0
- package/src/scenarios/sandbox-memory-cap.test.ts +2 -1
- package/src/scenarios/sandbox-mvp-behavior.test.ts +2 -1
- package/src/scenarios/sandbox-no-host-fs-escape.test.ts +2 -1
- package/src/scenarios/sandbox-timeout-cap.test.ts +2 -1
- package/src/scenarios/scheduling-capability-shape.test.ts +2 -1
- package/src/scenarios/scheduling-cron-fires-once.test.ts +2 -1
- package/src/scenarios/secret-leakage-otel-attribute.test.ts +7 -6
- package/src/scenarios/spec-corpus-validity.test.ts +4 -1
- package/src/scenarios/subrun-approval-fail-closed.test.ts +33 -0
- package/src/scenarios/subrun-approval-gate.test.ts +35 -0
- package/src/scenarios/subrun-attestation-shape.test.ts +30 -0
- package/src/scenarios/subrun-checksum-stable.test.ts +43 -0
- package/src/scenarios/tool-hooks-authorization-fail-closed.test.ts +39 -0
- package/src/scenarios/tool-hooks-content-free.test.ts +40 -0
- package/src/scenarios/tool-hooks-rate-limit.test.ts +32 -0
- package/src/scenarios/tool-hooks-secret-redaction.test.ts +34 -0
- package/src/scenarios/tool-hooks-shape.test.ts +34 -0
- package/src/scenarios/wasm-pack-abi-version-rejection.test.ts +3 -10
- package/src/scenarios/wasm-pack-invoke-completed.test.ts +2 -2
- package/src/scenarios/wasm-pack-invoke-suspended.test.ts +2 -2
- package/src/scenarios/wasm-pack-load.test.ts +2 -2
- package/src/scenarios/wasm-pack-memory-cap.test.ts +3 -6
- package/src/scenarios/wasm-pack-replay-determinism.test.ts +2 -2
- package/src/scenarios/workflow-primary-output-annotation.test.ts +142 -0
- package/src/scenarios/workspace-behavior.test.ts +134 -0
- package/src/scenarios/workspace-capability-shape.test.ts +73 -0
- package/src/scenarios/workspace-cross-tenant-isolation.test.ts +84 -0
|
@@ -34,6 +34,7 @@ import { driver } from '../lib/driver.js';
|
|
|
34
34
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
35
35
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
36
36
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
37
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
37
38
|
|
|
38
39
|
const WORKFLOW_ID = 'conformance-prompt-all-four-kinds';
|
|
39
40
|
const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
|
|
@@ -64,7 +65,7 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
function promptsSupported(d: DiscoveryDoc | null): boolean {
|
|
67
|
-
return d
|
|
68
|
+
return capabilityFamily(d, 'prompts')?.supported === true;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
async function readAllEvents(runId: string): Promise<RunEventDoc[]> {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
import { describe, it, expect } from 'vitest';
|
|
34
34
|
import { driver } from '../lib/driver.js';
|
|
35
35
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
36
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
36
37
|
|
|
37
38
|
interface DiscoveryDoc {
|
|
38
39
|
capabilities?: {
|
|
@@ -63,7 +64,7 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
function promptsSupportFull(d: DiscoveryDoc | null): boolean {
|
|
66
|
-
const p = d
|
|
67
|
+
const p = capabilityFamily(d, 'prompts');
|
|
67
68
|
if (!p) return false;
|
|
68
69
|
return p.supported === true && p.observability === 'full';
|
|
69
70
|
}
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
import { describe, it, expect } from 'vitest';
|
|
33
33
|
import { driver } from '../lib/driver.js';
|
|
34
34
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
35
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
35
36
|
|
|
36
37
|
interface DiscoveryDoc {
|
|
37
38
|
capabilities?: {
|
|
@@ -59,7 +60,7 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
function promptsSupportFull(d: DiscoveryDoc | null): boolean {
|
|
62
|
-
const p = d
|
|
63
|
+
const p = capabilityFamily(d, 'prompts');
|
|
63
64
|
if (!p) return false;
|
|
64
65
|
return p.supported === true && p.observability === 'full';
|
|
65
66
|
}
|
|
@@ -34,6 +34,7 @@ import { driver } from '../lib/driver.js';
|
|
|
34
34
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
35
35
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
36
36
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
37
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
37
38
|
|
|
38
39
|
const WORKFLOW_ID = 'conformance-prompt-end-to-end';
|
|
39
40
|
const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
|
|
@@ -64,7 +65,7 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
function promptsSupported(d: DiscoveryDoc | null): boolean {
|
|
67
|
-
return d
|
|
68
|
+
return capabilityFamily(d, 'prompts')?.supported === true;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/** Drain the run's event log via polling. The fixture is tiny so all
|
|
@@ -34,6 +34,7 @@ import { join } from 'node:path';
|
|
|
34
34
|
import { driver } from '../lib/driver.js';
|
|
35
35
|
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
36
36
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
37
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
37
38
|
|
|
38
39
|
interface DiscoveryDoc {
|
|
39
40
|
capabilities?: {
|
|
@@ -65,7 +66,7 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
function endpointsSupported(d: DiscoveryDoc | null): boolean {
|
|
68
|
-
return d
|
|
69
|
+
return capabilityFamily(d, 'prompts')?.endpointsSupported === true;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
import { describe, it, expect } from 'vitest';
|
|
36
36
|
import { driver } from '../lib/driver.js';
|
|
37
37
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
38
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
38
39
|
|
|
39
40
|
interface DiscoveryDoc {
|
|
40
41
|
capabilities?: {
|
|
@@ -60,7 +61,7 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
function mutableSupport(d: DiscoveryDoc | null): boolean {
|
|
63
|
-
const p = d
|
|
64
|
+
const p = capabilityFamily(d, 'prompts');
|
|
64
65
|
return p?.endpointsSupported === true && p?.mutableLibrary === true;
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
import { describe, it, expect } from 'vitest';
|
|
42
42
|
import { randomUUID } from 'node:crypto';
|
|
43
43
|
import { driver } from '../lib/driver.js';
|
|
44
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
44
45
|
|
|
45
46
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
46
47
|
|
|
@@ -71,7 +72,7 @@ describe.skipIf(HTTP_SKIP)(
|
|
|
71
72
|
ctx.skip();
|
|
72
73
|
return;
|
|
73
74
|
}
|
|
74
|
-
const mutableLibrary = d
|
|
75
|
+
const mutableLibrary = capabilityFamily(d, 'prompts')?.mutableLibrary;
|
|
75
76
|
if (mutableLibrary !== true) {
|
|
76
77
|
ctx.skip();
|
|
77
78
|
return;
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
import { describe, it, expect } from 'vitest';
|
|
47
47
|
import { driver } from '../lib/driver.js';
|
|
48
48
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
49
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
49
50
|
|
|
50
51
|
interface DiscoveryDoc {
|
|
51
52
|
capabilities?: {
|
|
@@ -79,7 +80,7 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
function endpointsSupported(d: DiscoveryDoc | null): boolean {
|
|
82
|
-
return d
|
|
83
|
+
return capabilityFamily(d, 'prompts')?.endpointsSupported === true;
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
import { describe, it, expect } from 'vitest';
|
|
68
68
|
import { randomUUID } from 'node:crypto';
|
|
69
69
|
import { driver } from '../lib/driver.js';
|
|
70
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
70
71
|
|
|
71
72
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
72
73
|
|
|
@@ -101,7 +102,7 @@ describe.skipIf(HTTP_SKIP)(
|
|
|
101
102
|
ctx.skip();
|
|
102
103
|
return;
|
|
103
104
|
}
|
|
104
|
-
const promptsSupported = d
|
|
105
|
+
const promptsSupported = capabilityFamily(d, 'prompts')?.supported;
|
|
105
106
|
if (promptsSupported !== true) {
|
|
106
107
|
ctx.skip();
|
|
107
108
|
return;
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import { describe, it, expect } from 'vitest';
|
|
24
24
|
import { driver } from '../lib/driver.js';
|
|
25
25
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
26
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
26
27
|
|
|
27
28
|
interface DiscoveryDoc {
|
|
28
29
|
capabilities?: {
|
|
@@ -60,7 +61,7 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
function endpointsSupported(d: DiscoveryDoc | null): boolean {
|
|
63
|
-
return d
|
|
64
|
+
return capabilityFamily(d, 'prompts')?.endpointsSupported === true;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
/** Pick a template that has at least one input-source variable
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
import { describe, it, expect } from 'vitest';
|
|
31
31
|
import { driver } from '../lib/driver.js';
|
|
32
32
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
33
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
33
34
|
|
|
34
35
|
interface DiscoveryDoc {
|
|
35
36
|
capabilities?: {
|
|
@@ -60,7 +61,7 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
function promptsAgentBindings(d: DiscoveryDoc | null): boolean {
|
|
63
|
-
const p = d
|
|
64
|
+
const p = capabilityFamily(d, 'prompts');
|
|
64
65
|
if (!p) return false;
|
|
65
66
|
return p.supported === true && p.agentBindings === true;
|
|
66
67
|
}
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
import { describe, it, expect } from 'vitest';
|
|
32
32
|
import { driver } from '../lib/driver.js';
|
|
33
33
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
34
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
34
35
|
|
|
35
36
|
interface DiscoveryDoc {
|
|
36
37
|
capabilities?: {
|
|
@@ -60,7 +61,7 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
function promptsSupported(d: DiscoveryDoc | null): boolean {
|
|
63
|
-
return d
|
|
64
|
+
return capabilityFamily(d, 'prompts')?.supported === true;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
import { describe, it, expect } from 'vitest';
|
|
30
30
|
import { driver } from '../lib/driver.js';
|
|
31
31
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
32
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
32
33
|
|
|
33
34
|
interface DiscoveryDoc {
|
|
34
35
|
capabilities?: {
|
|
@@ -58,7 +59,7 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
function promptsSupported(d: DiscoveryDoc | null): boolean {
|
|
61
|
-
return d
|
|
62
|
+
return capabilityFamily(d, 'prompts')?.supported === true;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
@@ -33,6 +33,7 @@ import { readFileSync } from 'node:fs';
|
|
|
33
33
|
import { join } from 'node:path';
|
|
34
34
|
import { driver } from '../lib/driver.js';
|
|
35
35
|
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
36
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
36
37
|
|
|
37
38
|
const PROMPT_KIND_VALUES = ['system', 'user', 'few-shot', 'schema-hint'] as const;
|
|
38
39
|
|
|
@@ -326,7 +327,7 @@ describe.skipIf(HTTP_SKIP)('prompt-template-shape: capabilities.prompts advertis
|
|
|
326
327
|
it('capabilities.prompts (when present) carries the optional shape per RFC 0027 §D', async () => {
|
|
327
328
|
const d = await readDiscovery();
|
|
328
329
|
if (d === null) return;
|
|
329
|
-
const prompts = d
|
|
330
|
+
const prompts = capabilityFamily(d, 'prompts');
|
|
330
331
|
if (prompts === undefined) return; // optional block; host MAY omit
|
|
331
332
|
expect(
|
|
332
333
|
typeof prompts.supported,
|
|
@@ -26,6 +26,7 @@ import { join } from 'node:path';
|
|
|
26
26
|
import { driver } from '../lib/driver.js';
|
|
27
27
|
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
28
28
|
import { queryTestEvents, isEventLogSeamAvailable, resetTestSeam } from '../lib/event-log-query.js';
|
|
29
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
29
30
|
|
|
30
31
|
interface DiscoveryDoc {
|
|
31
32
|
capabilities?: {
|
|
@@ -36,7 +37,7 @@ interface DiscoveryDoc {
|
|
|
36
37
|
async function readProviderUsageCap(): Promise<{ supported?: boolean; costEstimates?: boolean; currency?: string } | null> {
|
|
37
38
|
const res = await driver.get('/.well-known/openwop');
|
|
38
39
|
const body = res.json as DiscoveryDoc | undefined;
|
|
39
|
-
const cap = body
|
|
40
|
+
const cap = capabilityFamily(body, 'providerUsage');
|
|
40
41
|
return cap && typeof cap === 'object' ? cap : null;
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -100,7 +100,10 @@ describe('redaction: /.well-known/openwop secrets+aiProviders shape contract', (
|
|
|
100
100
|
'when secrets.supported is true, scopes MUST be non-empty',
|
|
101
101
|
)).toBeGreaterThanOrEqual(1);
|
|
102
102
|
for (const scope of scopes) {
|
|
103
|
-
|
|
103
|
+
// Allowlist MUST track the `secrets.scopes` enum in capabilities.schema.json
|
|
104
|
+
// (`["tenant", "user", "run", "workspace"]`). `workspace` is the RFC 0046/0048
|
|
105
|
+
// sub-tenant scope — additive; hosts that advertise it (e.g. MyndHyve) are conformant.
|
|
106
|
+
expect(['tenant', 'user', 'run', 'workspace']).toContain(scope);
|
|
104
107
|
}
|
|
105
108
|
expect(s.resolution, driver.describe(
|
|
106
109
|
'capabilities.md §"Secrets"',
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
|
|
47
47
|
import { describe, it, expect } from 'vitest';
|
|
48
48
|
import { driver } from '../lib/driver.js';
|
|
49
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
49
50
|
|
|
50
51
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
51
52
|
|
|
@@ -79,7 +80,7 @@ describe.skipIf(HTTP_SKIP)('replay-divergence-at-refusal: advertisement shape (R
|
|
|
79
80
|
ctx.skip();
|
|
80
81
|
return;
|
|
81
82
|
}
|
|
82
|
-
const rd = d
|
|
83
|
+
const rd = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.replayDeterminism;
|
|
83
84
|
if (rd === undefined) {
|
|
84
85
|
ctx.skip(); // optional advertisement — host hasn't opted in
|
|
85
86
|
return;
|
|
@@ -94,7 +95,7 @@ describe.skipIf(HTTP_SKIP)('replay-divergence-at-refusal: advertisement shape (R
|
|
|
94
95
|
).toBe('boolean');
|
|
95
96
|
|
|
96
97
|
if (rd.supported === true) {
|
|
97
|
-
const version = d
|
|
98
|
+
const version = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.version as number | undefined;
|
|
98
99
|
expect(
|
|
99
100
|
typeof version === 'number' && version >= 4,
|
|
100
101
|
driver.describe(
|
|
@@ -170,7 +171,7 @@ describe.skipIf(HTTP_SKIP)('replay-divergence-at-refusal: behavioral (RFC 0041
|
|
|
170
171
|
|
|
171
172
|
async function gateOnPhase4(ctx: { skip: () => void }): Promise<boolean> {
|
|
172
173
|
const d = await readDiscovery();
|
|
173
|
-
const rd = d
|
|
174
|
+
const rd = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.replayDeterminism;
|
|
174
175
|
if (rd?.supported !== true || rd?.refusalDivergenceEmission !== true) {
|
|
175
176
|
ctx.skip();
|
|
176
177
|
return false;
|
|
@@ -110,7 +110,9 @@ function structuralShape(
|
|
|
110
110
|
return events.map((e) => ({
|
|
111
111
|
type: e.type,
|
|
112
112
|
nodeId: e.nodeId ?? null,
|
|
113
|
-
|
|
113
|
+
// Canonical `payload` (run-event.schema.json) with the legacy `data`
|
|
114
|
+
// field as a fallback for hosts that haven't migrated their envelope.
|
|
115
|
+
data: e.payload ?? e.data ?? null,
|
|
114
116
|
}));
|
|
115
117
|
}
|
|
116
118
|
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
import { describe, it, expect } from 'vitest';
|
|
54
54
|
import { driver } from '../lib/driver.js';
|
|
55
55
|
import { expectedCacheKey, callCacheKeySeam as callSeam } from '../lib/llm-cache-key-recipe.js';
|
|
56
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
56
57
|
|
|
57
58
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
58
59
|
|
|
@@ -167,7 +168,7 @@ describe.skipIf(HTTP_SKIP)('replay-llm-cache-key-portable: non-recipe-field inva
|
|
|
167
168
|
describe.skipIf(HTTP_SKIP)('replay-llm-cache-key-portable: Phase 4 advertisement alignment (RFC 0041 §D)', () => {
|
|
168
169
|
it('hosts advertising version: 4 MUST advertise replayDeterminism.llmCacheKeyRecipe', async (ctx) => {
|
|
169
170
|
const d = await readDiscovery();
|
|
170
|
-
const em = d
|
|
171
|
+
const em = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel;
|
|
171
172
|
const version = em?.version;
|
|
172
173
|
if (typeof version !== 'number' || version < 4) {
|
|
173
174
|
ctx.skip(); // pre-Phase-4 or no multiAgent advertisement
|
|
@@ -60,7 +60,9 @@ function structuralShape(events: readonly RawEvent[]): Array<{ type: unknown; no
|
|
|
60
60
|
return events.map((e) => ({
|
|
61
61
|
type: e.type,
|
|
62
62
|
nodeId: e.nodeId ?? null,
|
|
63
|
-
|
|
63
|
+
// Canonical `payload` (run-event.schema.json) with the legacy `data`
|
|
64
|
+
// field as a fallback for hosts that haven't migrated their envelope.
|
|
65
|
+
data: e.payload ?? e.data ?? null,
|
|
64
66
|
}));
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run-execution-bounds-shape — RFC 0058 advertisement-shape + breach-contract
|
|
3
|
+
* verification for the two run-scoped execution bounds.
|
|
4
|
+
*
|
|
5
|
+
* Status: ACTIVE. RFC 0058 (run execution bounds) is `Active`. The
|
|
6
|
+
* `capabilities.limits.{maxRunDurationMs,maxLoopIterations}` fields and the
|
|
7
|
+
* `run-duration` / `loop-iterations` kinds on `cap.breached` have landed in
|
|
8
|
+
* `schemas/capabilities.schema.json` + `schemas/run-event-payloads.schema.json`.
|
|
9
|
+
*
|
|
10
|
+
* Always runs (shape-only): when the host advertises either limit, its value
|
|
11
|
+
* MUST be well-formed. Behavior is capability- AND fixture-gated. The
|
|
12
|
+
* `run-duration` (wall-clock timeout) block is now enforced + green against the
|
|
13
|
+
* in-memory reference host. The `loop-iterations` block stays soft-skipped until
|
|
14
|
+
* an execution-loop host advertises `multiAgent.executionModel` (RFC 0061),
|
|
15
|
+
* mirroring the RFC 0052 scheduling pattern.
|
|
16
|
+
*
|
|
17
|
+
* What this scenario asserts:
|
|
18
|
+
* 1. `capabilities.limits.maxRunDurationMs`, when present, is an integer ≥ 1000.
|
|
19
|
+
* 2. `capabilities.limits.maxLoopIterations`, when present, is an integer ≥ 1.
|
|
20
|
+
* 3. (gated) A run with `configurable.runTimeoutMs` below its real duration
|
|
21
|
+
* reaches terminal `failed` with `error.code = "run_timeout"` and emits
|
|
22
|
+
* `cap.breached { kind: "run-duration" }` whose `observed > limit`.
|
|
23
|
+
*
|
|
24
|
+
* @see RFCS/0058-run-execution-bounds.md
|
|
25
|
+
* @see spec/v1/run-options.md §Reserved keys (runTimeoutMs / maxLoopIterations)
|
|
26
|
+
* @see spec/v1/capabilities.md §"Engine-enforced limits and the cap.breached event"
|
|
27
|
+
* @see schemas/run-event-payloads.schema.json §capBreached
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { describe, it, expect } from 'vitest';
|
|
31
|
+
import { driver } from '../lib/driver.js';
|
|
32
|
+
import { pollUntilTerminal } from '../lib/polling.js';
|
|
33
|
+
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
34
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
35
|
+
|
|
36
|
+
interface DiscoveryLimits {
|
|
37
|
+
maxRunDurationMs?: number;
|
|
38
|
+
maxLoopIterations?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface DiscoveryDoc {
|
|
42
|
+
capabilities?: { limits?: DiscoveryLimits };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface RunEvent {
|
|
46
|
+
readonly type: string;
|
|
47
|
+
readonly sequence: number;
|
|
48
|
+
readonly payload?: unknown;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const TIMEOUT_FIXTURE = 'conformance-run-duration-breach';
|
|
52
|
+
|
|
53
|
+
async function readLimits(): Promise<DiscoveryLimits | null> {
|
|
54
|
+
const res = await driver.get('/.well-known/openwop');
|
|
55
|
+
const body = res.json as DiscoveryDoc | undefined;
|
|
56
|
+
return capabilityFamily(body, 'limits') ?? null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('run-execution-bounds-shape: advertisement shape (RFC 0058)', () => {
|
|
60
|
+
it('maxRunDurationMs is an integer >= 1000 when present', async () => {
|
|
61
|
+
const limits = await readLimits();
|
|
62
|
+
if (limits?.maxRunDurationMs === undefined) return; // not advertised
|
|
63
|
+
expect(
|
|
64
|
+
Number.isInteger(limits.maxRunDurationMs) && limits.maxRunDurationMs >= 1000,
|
|
65
|
+
driver.describe(
|
|
66
|
+
'capabilities.schema.json §limits.maxRunDurationMs',
|
|
67
|
+
`capabilities.limits.maxRunDurationMs MUST be an integer >= 1000, got: ${limits.maxRunDurationMs}`,
|
|
68
|
+
),
|
|
69
|
+
).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('maxLoopIterations is an integer >= 1 when present', async () => {
|
|
73
|
+
const limits = await readLimits();
|
|
74
|
+
if (limits?.maxLoopIterations === undefined) return; // not advertised
|
|
75
|
+
expect(
|
|
76
|
+
Number.isInteger(limits.maxLoopIterations) && limits.maxLoopIterations >= 1,
|
|
77
|
+
driver.describe(
|
|
78
|
+
'capabilities.schema.json §limits.maxLoopIterations',
|
|
79
|
+
`capabilities.limits.maxLoopIterations MUST be an integer >= 1, got: ${limits.maxLoopIterations}`,
|
|
80
|
+
),
|
|
81
|
+
).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Behavior: capability- AND fixture-gated. Skips on hosts that do not enforce
|
|
86
|
+
// run-duration timeouts (incl. the reference hosts) until one wires the seam.
|
|
87
|
+
const SKIP_TIMEOUT = !isFixtureAdvertised(TIMEOUT_FIXTURE);
|
|
88
|
+
|
|
89
|
+
describe.skipIf(SKIP_TIMEOUT)('run-execution-bounds: run-duration breach (RFC 0058)', () => {
|
|
90
|
+
it('a run with runTimeoutMs below its real duration fails with run_timeout + cap.breached{run-duration}', async () => {
|
|
91
|
+
const create = await driver.post('/v1/runs', {
|
|
92
|
+
workflowId: TIMEOUT_FIXTURE,
|
|
93
|
+
configurable: { runTimeoutMs: 1000 },
|
|
94
|
+
});
|
|
95
|
+
expect(create.status, driver.describe(
|
|
96
|
+
'rest-endpoints.md POST /v1/runs',
|
|
97
|
+
'run creation MUST accept a runTimeoutMs override',
|
|
98
|
+
)).toBe(201);
|
|
99
|
+
const runId = (create.json as { runId: string }).runId;
|
|
100
|
+
|
|
101
|
+
const terminal = await pollUntilTerminal(runId);
|
|
102
|
+
expect(terminal.status, driver.describe(
|
|
103
|
+
'run-options.md §runTimeoutMs',
|
|
104
|
+
'a run exceeding its runTimeoutMs MUST reach terminal `failed`',
|
|
105
|
+
)).toBe('failed');
|
|
106
|
+
expect(terminal.error?.code, driver.describe(
|
|
107
|
+
'rest-endpoints.md §run_timeout',
|
|
108
|
+
'RunSnapshot.error.code MUST equal "run_timeout" on wall-clock timeout',
|
|
109
|
+
)).toBe('run_timeout');
|
|
110
|
+
|
|
111
|
+
const eventsRes = await driver.get(
|
|
112
|
+
`/v1/runs/${encodeURIComponent(runId)}/events/poll?lastSequence=0&timeout=1`,
|
|
113
|
+
);
|
|
114
|
+
const events = (eventsRes.json as { events?: RunEvent[] } | undefined)?.events ?? [];
|
|
115
|
+
const breach = events.find((e) => e.type === 'cap.breached');
|
|
116
|
+
expect(breach, driver.describe(
|
|
117
|
+
'capabilities.md §Engine-enforced limits',
|
|
118
|
+
'a cap.breached event MUST be emitted on run-duration breach',
|
|
119
|
+
)).toBeDefined();
|
|
120
|
+
const payload = breach!.payload as { kind?: string; limit?: number; observed?: number } | undefined;
|
|
121
|
+
expect(payload?.kind, driver.describe(
|
|
122
|
+
'run-event-payloads.schema.json §capBreached.kind',
|
|
123
|
+
'cap.breached payload MUST carry kind="run-duration"',
|
|
124
|
+
)).toBe('run-duration');
|
|
125
|
+
expect(
|
|
126
|
+
typeof payload?.observed === 'number' && typeof payload?.limit === 'number' && payload!.observed > payload!.limit,
|
|
127
|
+
driver.describe(
|
|
128
|
+
'run-event-payloads.schema.json §capBreached.observed',
|
|
129
|
+
'observed (elapsedMs) MUST be strictly greater than limit (resolved timeout)',
|
|
130
|
+
),
|
|
131
|
+
).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { describe, it, expect } from 'vitest';
|
|
17
17
|
import { driver } from '../lib/driver.js';
|
|
18
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
18
19
|
|
|
19
20
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
20
21
|
|
|
@@ -26,7 +27,7 @@ async function readSandbox(): Promise<{ supported: boolean; memoryLimitBytes?: n
|
|
|
26
27
|
try {
|
|
27
28
|
const r = await driver.get('/.well-known/openwop');
|
|
28
29
|
if (r.status !== 200) return null;
|
|
29
|
-
const sb = (r.json as D)
|
|
30
|
+
const sb = capabilityFamily((r.json as D), 'sandbox');
|
|
30
31
|
if (!sb || sb.supported !== true) return null;
|
|
31
32
|
return {
|
|
32
33
|
supported: true,
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
import { describe, it, expect } from 'vitest';
|
|
39
39
|
import { driver } from '../lib/driver.js';
|
|
40
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
40
41
|
|
|
41
42
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
42
43
|
|
|
@@ -66,7 +67,7 @@ async function isSandboxAdvertised(): Promise<boolean> {
|
|
|
66
67
|
try {
|
|
67
68
|
const res = await driver.get('/.well-known/openwop');
|
|
68
69
|
if (res.status !== 200) return false;
|
|
69
|
-
return (res.json as DiscoveryDoc)
|
|
70
|
+
return capabilityFamily((res.json as DiscoveryDoc), 'sandbox')?.supported === true;
|
|
70
71
|
} catch {
|
|
71
72
|
return false;
|
|
72
73
|
}
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
import { describe, it, expect } from 'vitest';
|
|
28
28
|
import { driver } from '../lib/driver.js';
|
|
29
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
29
30
|
|
|
30
31
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
31
32
|
|
|
@@ -45,7 +46,7 @@ async function readSandboxCaps(): Promise<SandboxCaps | null> {
|
|
|
45
46
|
try {
|
|
46
47
|
const res = await driver.get('/.well-known/openwop');
|
|
47
48
|
if (res.status !== 200) return null;
|
|
48
|
-
return (res.json as DiscoveryDoc)
|
|
49
|
+
return capabilityFamily((res.json as DiscoveryDoc), 'sandbox') ?? null;
|
|
49
50
|
} catch {
|
|
50
51
|
return null;
|
|
51
52
|
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { describe, it, expect } from 'vitest';
|
|
17
17
|
import { driver } from '../lib/driver.js';
|
|
18
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
18
19
|
|
|
19
20
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
20
21
|
|
|
@@ -26,7 +27,7 @@ async function readSandbox(): Promise<{ supported: boolean; wallClockLimitMs?: n
|
|
|
26
27
|
try {
|
|
27
28
|
const r = await driver.get('/.well-known/openwop');
|
|
28
29
|
if (r.status !== 200) return null;
|
|
29
|
-
const sb = (r.json as D)
|
|
30
|
+
const sb = capabilityFamily((r.json as D), 'sandbox');
|
|
30
31
|
if (!sb || sb.supported !== true) return null;
|
|
31
32
|
return {
|
|
32
33
|
supported: true,
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import { describe, it, expect } from 'vitest';
|
|
22
22
|
import { driver } from '../lib/driver.js';
|
|
23
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
23
24
|
|
|
24
25
|
interface DiscoveryScheduling {
|
|
25
26
|
supported?: boolean;
|
|
@@ -39,7 +40,7 @@ const ISO_DURATION = /^P(?:\d+Y)?(?:\d+M)?(?:\d+W)?(?:\d+D)?(?:T(?:\d+H)?(?:\d+M
|
|
|
39
40
|
async function readScheduling(): Promise<DiscoveryScheduling | null> {
|
|
40
41
|
const res = await driver.get('/.well-known/openwop');
|
|
41
42
|
const body = res.json as DiscoveryDoc | undefined;
|
|
42
|
-
return body
|
|
43
|
+
return capabilityFamily(body, 'scheduling') ?? null;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
describe('scheduling-capability-shape: advertisement shape (RFC 0052 §A)', () => {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
import { describe, it, expect } from 'vitest';
|
|
28
28
|
import { driver } from '../lib/driver.js';
|
|
29
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
29
30
|
|
|
30
31
|
interface DiscoveryDoc {
|
|
31
32
|
capabilities?: { scheduling?: { supported?: boolean; cron?: boolean } };
|
|
@@ -33,7 +34,7 @@ interface DiscoveryDoc {
|
|
|
33
34
|
|
|
34
35
|
async function readScheduling(): Promise<{ supported?: boolean; cron?: boolean } | null> {
|
|
35
36
|
const res = await driver.get('/.well-known/openwop');
|
|
36
|
-
return (res.json as DiscoveryDoc | undefined)
|
|
37
|
+
return capabilityFamily((res.json as DiscoveryDoc | undefined), 'scheduling') ?? null;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
describe('scheduling-cron-fires-once: once-per-tick + missed-tick (RFC 0052 §B)', () => {
|
|
@@ -55,6 +55,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
55
55
|
import { driver } from '../lib/driver.js';
|
|
56
56
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
57
57
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
58
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
58
59
|
|
|
59
60
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
60
61
|
const BYOK_WORKFLOW_ID = 'openwop-smoke-byok-roundtrip';
|
|
@@ -99,8 +100,8 @@ describe.skipIf(HTTP_SKIP || FIXTURE_SKIP)(
|
|
|
99
100
|
return;
|
|
100
101
|
}
|
|
101
102
|
const d = await readDiscovery();
|
|
102
|
-
const secretsOk = d
|
|
103
|
-
const seamOk = d
|
|
103
|
+
const secretsOk = capabilityFamily<{ supported?: unknown }>(d, 'secrets')?.supported === true;
|
|
104
|
+
const seamOk = capabilityFamily<{ testSeams?: Record<string, unknown> }>(d, 'observability')?.testSeams?.otelScrape === true;
|
|
104
105
|
if (!secretsOk || !seamOk) {
|
|
105
106
|
ctx.skip();
|
|
106
107
|
return;
|
|
@@ -168,8 +169,8 @@ describe.skipIf(HTTP_SKIP || FIXTURE_SKIP)(
|
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
170
171
|
const d = await readDiscovery();
|
|
171
|
-
const secretsOk = d
|
|
172
|
-
const seamOk = d
|
|
172
|
+
const secretsOk = capabilityFamily<{ supported?: unknown }>(d, 'secrets')?.supported === true;
|
|
173
|
+
const seamOk = capabilityFamily<{ testSeams?: Record<string, unknown> }>(d, 'observability')?.testSeams?.debugBundleExport === true;
|
|
173
174
|
if (!secretsOk || !seamOk) {
|
|
174
175
|
ctx.skip();
|
|
175
176
|
return;
|
|
@@ -209,11 +210,11 @@ describe.skipIf(HTTP_SKIP || FIXTURE_SKIP)(
|
|
|
209
210
|
() => {
|
|
210
211
|
it('when secrets.supported is true, observability.testSeams advertisements MUST be boolean if present', async (ctx) => {
|
|
211
212
|
const d = await readDiscovery();
|
|
212
|
-
if (d
|
|
213
|
+
if (capabilityFamily<{ supported?: unknown }>(d, 'secrets')?.supported !== true) {
|
|
213
214
|
ctx.skip();
|
|
214
215
|
return;
|
|
215
216
|
}
|
|
216
|
-
const seams = d
|
|
217
|
+
const seams = capabilityFamily<{ testSeams?: Record<string, unknown> }>(d, 'observability')?.testSeams;
|
|
217
218
|
if (seams === undefined) {
|
|
218
219
|
ctx.skip(); // host honest about not exposing the seams — Drift #17 path
|
|
219
220
|
return;
|