@openwop/openwop-conformance 1.6.1 → 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 +10 -0
- package/README.md +2 -2
- package/api/asyncapi.yaml +57 -0
- package/api/openapi.yaml +250 -0
- package/coverage.md +14 -0
- package/fixtures/conformance-run-duration-breach.json +33 -0
- package/fixtures.md +19 -0
- package/package.json +1 -1
- package/schemas/README.md +10 -0
- package/schemas/agent-inventory-response.schema.json +90 -0
- package/schemas/ai-envelope.schema.json +28 -0
- package/schemas/artifact-type-pack-manifest.schema.json +160 -0
- package/schemas/capabilities.schema.json +171 -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 +3 -3
- 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/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/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 +1 -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
|
@@ -50,6 +50,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
50
50
|
import { driver } from '../lib/driver.js';
|
|
51
51
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
52
52
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
53
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
53
54
|
|
|
54
55
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
55
56
|
const FIXTURE = 'conformance-multi-agent-confidence-escalation';
|
|
@@ -84,7 +85,7 @@ describe.skipIf(HTTP_SKIP)('multi-agent-confidence-escalation: capability shape
|
|
|
84
85
|
it('confidenceEscalationFloor (when advertised) MUST be in [0.5, 1.0]', async () => {
|
|
85
86
|
const d = await readDiscovery();
|
|
86
87
|
if (d === null) return;
|
|
87
|
-
const em = d
|
|
88
|
+
const em = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel;
|
|
88
89
|
if (em === undefined) return;
|
|
89
90
|
const floor = em.confidenceEscalationFloor;
|
|
90
91
|
if (floor === undefined) return;
|
|
@@ -101,8 +102,8 @@ describe.skipIf(HTTP_SKIP)('multi-agent-confidence-escalation: capability shape
|
|
|
101
102
|
describe.skipIf(BEHAVIORAL_SKIP)('multi-agent-confidence-escalation: behavioral (RFC 0039 §A)', () => {
|
|
102
103
|
it('happy-path: low-confidence decision → confidence-escalated event + clarification interrupt + zero dispatch events', async () => {
|
|
103
104
|
const d = await readDiscovery();
|
|
104
|
-
const supported = d
|
|
105
|
-
const versionRaw = d
|
|
105
|
+
const supported = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.supported === true;
|
|
106
|
+
const versionRaw = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.version;
|
|
106
107
|
const version = typeof versionRaw === 'number' ? versionRaw : 0;
|
|
107
108
|
if (!supported || version < 2) return; // soft-skip — `version: 1` hosts pass via this absence
|
|
108
109
|
|
|
@@ -125,7 +126,7 @@ describe.skipIf(BEHAVIORAL_SKIP)('multi-agent-confidence-escalation: behavioral
|
|
|
125
126
|
// status — the host's own interrupt.md mapping determines the suffix).
|
|
126
127
|
// When the host does NOT advertise the field, fall back to the canonical
|
|
127
128
|
// either-status check.
|
|
128
|
-
const advertisedKind = d
|
|
129
|
+
const advertisedKind = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.confidenceEscalationInterruptKind;
|
|
129
130
|
const isVendorKind = typeof advertisedKind === 'string' && /^x-host-[a-z][a-z0-9-]*-[a-z][a-z0-9-]*$/.test(advertisedKind);
|
|
130
131
|
const isCanonicalKind = advertisedKind === 'clarification' || advertisedKind === 'approval';
|
|
131
132
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Asserts (Phase 1 — execution-loop + handoff state machine per spec/v1/multi-agent-execution.md):
|
|
11
11
|
*
|
|
12
12
|
* 1. Advertisement shape: when capabilities.multiAgent.executionModel.supported
|
|
13
|
-
* is present, version MUST be integer in [1,
|
|
13
|
+
* is present, version MUST be integer in [1, 5]; supported MUST be boolean.
|
|
14
14
|
*
|
|
15
15
|
* 2. Behavioral (gated on supported: true + fixture availability): a
|
|
16
16
|
* supervisor → next-worker → child-completed run emits the 4 expected
|
|
@@ -34,6 +34,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
34
34
|
import { driver } from '../lib/driver.js';
|
|
35
35
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
36
36
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
37
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
37
38
|
|
|
38
39
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
39
40
|
|
|
@@ -62,7 +63,7 @@ describe.skipIf(HTTP_SKIP)('multi-agent-handoff-state-machine: advertisement sha
|
|
|
62
63
|
it('capabilities.multiAgent.executionModel (when present) conforms to RFC 0037 §C', async () => {
|
|
63
64
|
const d = await readDiscovery();
|
|
64
65
|
if (d === null) return; // discovery unavailable — skip
|
|
65
|
-
const executionModel = d
|
|
66
|
+
const executionModel = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel;
|
|
66
67
|
if (executionModel === undefined) return; // host doesn't advertise — soft-skip
|
|
67
68
|
expect(
|
|
68
69
|
typeof executionModel.supported,
|
|
@@ -80,10 +81,10 @@ describe.skipIf(HTTP_SKIP)('multi-agent-handoff-state-machine: advertisement sha
|
|
|
80
81
|
).toBe('number');
|
|
81
82
|
const v = executionModel.version as number;
|
|
82
83
|
expect(
|
|
83
|
-
Number.isInteger(v) && v >= 1 && v <=
|
|
84
|
+
Number.isInteger(v) && v >= 1 && v <= 5,
|
|
84
85
|
driver.describe(
|
|
85
86
|
'RFCS/0037-multi-agent-execution-model.md §C',
|
|
86
|
-
'version MUST be an integer in [1,
|
|
87
|
+
'version MUST be an integer in [1, 5] (1 = Phase 1 only; Phases 2-5 lift the ceiling additively — Phase 5 = RFC 0061 stateful agent-loop lifecycle, matching `capabilities.schema.json` §multiAgent.executionModel.version maximum)',
|
|
87
88
|
),
|
|
88
89
|
).toBe(true);
|
|
89
90
|
});
|
|
@@ -104,7 +105,7 @@ const BEHAVIORAL_SKIP = HTTP_SKIP || !isFixtureAdvertised(PARENT_FIXTURE) || !is
|
|
|
104
105
|
describe.skipIf(BEHAVIORAL_SKIP)('multi-agent-handoff-state-machine: behavioral 4-event causation chain (RFC 0037 §"Handoff state machine")', () => {
|
|
105
106
|
it('happy-path: dispatch.began → dispatch.succeeded → child.completed → output.harvested fire in causation order', async () => {
|
|
106
107
|
const d = await readDiscovery();
|
|
107
|
-
const advertised = d
|
|
108
|
+
const advertised = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.supported === true;
|
|
108
109
|
if (!advertised) return; // soft-skip — host honest about not implementing
|
|
109
110
|
|
|
110
111
|
const create = await driver.post('/v1/runs', { workflowId: PARENT_FIXTURE });
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
import { describe, it, expect } from 'vitest';
|
|
50
50
|
import { driver } from '../lib/driver.js';
|
|
51
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
51
52
|
|
|
52
53
|
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
53
54
|
|
|
@@ -81,7 +82,7 @@ describe.skipIf(HTTP_SKIP)('multi-agent-memory-lifecycle: advertisement shape (R
|
|
|
81
82
|
ctx.skip();
|
|
82
83
|
return;
|
|
83
84
|
}
|
|
84
|
-
const ccmc = d
|
|
85
|
+
const ccmc = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.crossChildMemoryConcurrency;
|
|
85
86
|
if (ccmc === undefined) {
|
|
86
87
|
ctx.skip(); // optional advertisement — host hasn't opted in
|
|
87
88
|
return;
|
|
@@ -135,8 +136,8 @@ describe.skipIf(HTTP_SKIP)('multi-agent-memory-lifecycle: behavioral (RFC 0039
|
|
|
135
136
|
ctx.skip();
|
|
136
137
|
return;
|
|
137
138
|
}
|
|
138
|
-
const v = d
|
|
139
|
-
const memorySupported = d
|
|
139
|
+
const v = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.version;
|
|
140
|
+
const memorySupported = capabilityFamily<{ supported?: unknown }>(d, 'memory')?.supported;
|
|
140
141
|
const phase2OrLater = typeof v === 'number' && v >= 2;
|
|
141
142
|
const expiredRunId = process.env.OPENWOP_TEST_EXPIRED_REPLAY_RUN_ID;
|
|
142
143
|
if (!phase2OrLater || memorySupported !== true || !expiredRunId) {
|
|
@@ -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
|
const ALLOWED = new Set(['single-region', 'best-effort', 'strict']);
|
|
25
26
|
const REQUIRED_METRICS_WHEN_MULTI_REGION = [
|
|
@@ -40,9 +41,7 @@ interface ObservabilityCaps {
|
|
|
40
41
|
describe('multi-region-idempotency: capability shape', () => {
|
|
41
42
|
it('idempotency.crossRegion (when advertised) MUST be one of the closed enum', async () => {
|
|
42
43
|
const disco = await driver.get('/.well-known/openwop');
|
|
43
|
-
const idem =
|
|
44
|
-
(disco.json as { capabilities?: { idempotency?: IdempotencyCaps } }).capabilities
|
|
45
|
-
?.idempotency;
|
|
44
|
+
const idem = capabilityFamily<IdempotencyCaps>(disco.json, 'idempotency');
|
|
46
45
|
|
|
47
46
|
if (!idem || idem.crossRegion === undefined) {
|
|
48
47
|
// eslint-disable-next-line no-console
|
|
@@ -67,16 +66,16 @@ describe('multi-region-idempotency: capability shape', () => {
|
|
|
67
66
|
|
|
68
67
|
it('multi-region hosts SHOULD expose the cross-region conflict counter per §"Operator surface"', async () => {
|
|
69
68
|
const disco = await driver.get('/.well-known/openwop');
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
const crossRegion =
|
|
69
|
+
const idem = capabilityFamily<IdempotencyCaps>(disco.json, 'idempotency');
|
|
70
|
+
const observability = capabilityFamily<ObservabilityCaps>(disco.json, 'observability');
|
|
71
|
+
const crossRegion = idem?.crossRegion;
|
|
73
72
|
|
|
74
73
|
if (crossRegion !== 'best-effort' && crossRegion !== 'strict') {
|
|
75
74
|
// Single-region hosts have no conflicts to count — skip.
|
|
76
75
|
return;
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
const advertised = new Set(
|
|
78
|
+
const advertised = new Set(observability?.metrics?.names ?? []);
|
|
80
79
|
for (const name of REQUIRED_METRICS_WHEN_MULTI_REGION) {
|
|
81
80
|
expect(advertised.has(name), driver.describe(
|
|
82
81
|
'idempotency.md §"Operator surface"',
|
|
@@ -103,9 +102,10 @@ interface MultiRegionCaps {
|
|
|
103
102
|
describe('multi-region-idempotency: granular multiRegion advertisement shape (RFC 0036 §A)', () => {
|
|
104
103
|
it('capabilities.idempotency.multiRegion (when present) conforms to RFC 0036 §A', async () => {
|
|
105
104
|
const disco = await driver.get('/.well-known/openwop');
|
|
106
|
-
const idem =
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
const idem = capabilityFamily<IdempotencyCaps & { multiRegion?: MultiRegionCaps }>(
|
|
106
|
+
disco.json,
|
|
107
|
+
'idempotency',
|
|
108
|
+
);
|
|
109
109
|
const mr = idem?.multiRegion;
|
|
110
110
|
if (mr === undefined) return; // host doesn't advertise the granular block — soft-skip
|
|
111
111
|
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import { describe, it, expect } from 'vitest';
|
|
20
20
|
import { driver } from '../lib/driver.js';
|
|
21
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
21
22
|
|
|
22
23
|
interface DiscoveryOAuthProvider {
|
|
23
24
|
id?: string;
|
|
@@ -47,7 +48,7 @@ const VALID_GRANTS: ReadonlySet<string> = new Set([
|
|
|
47
48
|
async function readOAuth(): Promise<DiscoveryOAuth | null> {
|
|
48
49
|
const res = await driver.get('/.well-known/openwop');
|
|
49
50
|
const body = res.json as DiscoveryDoc | undefined;
|
|
50
|
-
return body
|
|
51
|
+
return capabilityFamily(body, 'oauth') ?? null;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
describe('oauth-capability-shape: advertisement shape (RFC 0047 §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 DiscoveryOAuth {
|
|
31
32
|
supported?: boolean;
|
|
@@ -42,7 +43,7 @@ const TOKEN_CANARY = 'OPENWOP_OAUTH_CANARY_b7d3e1a9c2';
|
|
|
42
43
|
async function readOAuth(): Promise<DiscoveryOAuth | null> {
|
|
43
44
|
const res = await driver.get('/.well-known/openwop');
|
|
44
45
|
const body = res.json as DiscoveryDoc | undefined;
|
|
45
|
-
return body
|
|
46
|
+
return capabilityFamily(body, 'oauth') ?? null;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
describe('oauth-connector-redaction: advertisement shape (RFC 0047 §A)', () => {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
import { describe, it, expect } from 'vitest';
|
|
21
21
|
import { driver } from '../lib/driver.js';
|
|
22
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
22
23
|
import { pollUntilStatus, pollUntilTerminal } from '../lib/polling.js';
|
|
23
24
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
24
25
|
|
|
@@ -235,9 +236,8 @@ describe.skipIf(SKIP)('pause/resume: drainPolicy discrimination per capabilities
|
|
|
235
236
|
it('every drainPolicy advertised by the host is accepted on :pause', async () => {
|
|
236
237
|
const disco = await driver.get('/.well-known/openwop');
|
|
237
238
|
const drainPolicies =
|
|
238
|
-
(disco.json
|
|
239
|
-
|
|
240
|
-
}).capabilities?.runs?.pauseResume?.drainPolicies ?? [];
|
|
239
|
+
capabilityFamily<{ pauseResume?: { drainPolicies?: string[] } }>(disco.json, 'runs')
|
|
240
|
+
?.pauseResume?.drainPolicies ?? [];
|
|
241
241
|
if (drainPolicies.length === 0) {
|
|
242
242
|
// eslint-disable-next-line no-console
|
|
243
243
|
console.warn('[pause-resume] host advertises no drainPolicies; skipping policy-discrimination subtest');
|
|
@@ -40,6 +40,7 @@ import { driver } from '../lib/driver.js';
|
|
|
40
40
|
import { loadEnv } from '../lib/env.js';
|
|
41
41
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
42
42
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
43
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
43
44
|
|
|
44
45
|
interface BackpressureCaps {
|
|
45
46
|
supported?: boolean;
|
|
@@ -54,8 +55,7 @@ interface ProductionCaps {
|
|
|
54
55
|
|
|
55
56
|
async function readProductionCaps(): Promise<ProductionCaps | undefined> {
|
|
56
57
|
const disco = await driver.get('/.well-known/openwop');
|
|
57
|
-
return (disco.json
|
|
58
|
-
.capabilities?.production;
|
|
58
|
+
return capabilityFamily<ProductionCaps>(disco.json, 'production');
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
function isProfileAdvertised(prod: ProductionCaps | undefined): boolean {
|
|
@@ -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 RetentionCaps {
|
|
36
37
|
supported?: boolean;
|
|
@@ -45,8 +46,7 @@ interface ProductionCaps {
|
|
|
45
46
|
|
|
46
47
|
async function readProductionCaps(): Promise<ProductionCaps | undefined> {
|
|
47
48
|
const disco = await driver.get('/.well-known/openwop');
|
|
48
|
-
return (disco.json
|
|
49
|
-
.capabilities?.production;
|
|
49
|
+
return capabilityFamily<ProductionCaps>(disco.json, 'production');
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
function isProfileAdvertised(prod: ProductionCaps | undefined): boolean {
|
|
@@ -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
|
|
|
@@ -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
|
|