@openwop/openwop-conformance 1.6.1 → 1.11.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 +44 -0
- package/README.md +2 -2
- package/api/asyncapi.yaml +127 -0
- package/api/openapi.yaml +518 -1
- package/coverage.md +44 -2
- package/fixtures/conformance-run-duration-breach.json +33 -0
- package/fixtures/oauth-providers/synthetic.json +38 -0
- package/fixtures.md +29 -0
- package/package.json +1 -1
- package/schemas/README.md +22 -0
- package/schemas/agent-deployment-transition.schema.json +49 -0
- package/schemas/agent-deployment.schema.json +54 -0
- package/schemas/agent-eval-suite.schema.json +140 -0
- package/schemas/agent-inventory-response.schema.json +115 -0
- package/schemas/agent-manifest.schema.json +5 -0
- package/schemas/agent-org-chart.schema.json +82 -0
- package/schemas/agent-ref.schema.json +12 -2
- package/schemas/agent-roster-entry.schema.json +81 -0
- package/schemas/agent-roster-response.schema.json +21 -0
- package/schemas/ai-envelope.schema.json +28 -0
- package/schemas/artifact-type-pack-manifest.schema.json +160 -0
- package/schemas/budget-policy.schema.json +18 -0
- package/schemas/capabilities.schema.json +448 -4
- package/schemas/chat-card-pack-manifest.schema.json +158 -0
- package/schemas/credential-provenance.schema.json +18 -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/eval-summary.schema.json +92 -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 +33 -1
- package/schemas/org-chart-responsibility-view.schema.json +26 -0
- package/schemas/run-event-payloads.schema.json +380 -6
- package/schemas/run-event.schema.json +23 -0
- package/schemas/tool-descriptor.schema.json +63 -0
- package/schemas/trigger-subscription.schema.json +26 -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/agentRoster.ts +76 -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/liveRuntime.ts +59 -0
- package/src/lib/memoryAttribution.ts +48 -0
- package/src/lib/profiles.ts +157 -0
- package/src/lib/runtimeRequires.ts +38 -0
- package/src/lib/safeFetch.ts +87 -0
- package/src/lib/subRunAttestation.ts +35 -0
- package/src/lib/toolHooks.ts +33 -0
- package/src/scenarios/agent-deployment-shape.test.ts +139 -0
- package/src/scenarios/agent-eval-suite-shape.test.ts +167 -0
- package/src/scenarios/agent-live-allowlist-enforced.test.ts +53 -0
- package/src/scenarios/agent-live-invocation-bracket.test.ts +98 -0
- package/src/scenarios/agent-live-runtime-shape.test.ts +98 -0
- package/src/scenarios/agent-live-structured-output.test.ts +58 -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/agent-org-chart-shape.test.ts +127 -0
- package/src/scenarios/agent-platform-profile.test.ts +158 -0
- package/src/scenarios/agent-roster-attribution.test.ts +179 -0
- package/src/scenarios/agent-roster-shape.test.ts +146 -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/budget-policy-shape.test.ts +136 -0
- 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/egress-provenance-shape.test.ts +137 -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-capability-model-shape.test.ts +186 -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-authorization-code-roundtrip.test.ts +145 -0
- 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/runtime-requires-install-gate.test.ts +92 -0
- package/src/scenarios/runtime-requires-shape.test.ts +134 -0
- package/src/scenarios/safefetch-behavior.test.ts +99 -0
- package/src/scenarios/safefetch-live-audit.test.ts +175 -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 +20 -4
- 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-descriptor-shape.test.ts +133 -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/trigger-bridge-shape.test.ts +135 -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
- package/src/scenarios/x-openwop-form-pack-manifest.test.ts +155 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* oauth-authorization-code-roundtrip — RFC 0047 §C (the authorization-code grant
|
|
3
|
+
* end-to-end) + §C.2 / `credential-payload-redaction`.
|
|
4
|
+
*
|
|
5
|
+
* Closes the RFC 0047 Tier-2 gap: `oauth-capability-shape` proves the discovery
|
|
6
|
+
* block is well-formed and `oauth-connector-redaction` proves an already-acquired
|
|
7
|
+
* token doesn't leak — but nothing exercised the actual authorization-code DANCE
|
|
8
|
+
* (redirect → callback → token exchange) against a known provider. This scenario
|
|
9
|
+
* drives that roundtrip against ONE canonical synthetic provider whose endpoints a
|
|
10
|
+
* conformance test double serves, so a host can prove the grant without a live IdP.
|
|
11
|
+
*
|
|
12
|
+
* The synthetic provider + its canned exchange are defined in
|
|
13
|
+
* `fixtures/oauth-providers/synthetic.json`; the constants below mirror it (kept
|
|
14
|
+
* inline so the scenario runs from the published tarball without fixture-path
|
|
15
|
+
* resolution, exactly like `oauth-connector-redaction`'s TOKEN_CANARY).
|
|
16
|
+
*
|
|
17
|
+
* Capability-gated: skips unless the host advertises
|
|
18
|
+
* `capabilities.oauth.supported = true` AND lists `authorization_code` in
|
|
19
|
+
* `capabilities.oauth.grants`. Behavioral probe drives the optional host seam
|
|
20
|
+
* `POST /v1/host/sample/oauth/authorize-code-roundtrip`; a 404 (seam not wired)
|
|
21
|
+
* is a soft-skip — this is a Tier-2 host-pending scenario.
|
|
22
|
+
*
|
|
23
|
+
* Asserts, when the seam is present:
|
|
24
|
+
* 1. The roundtrip succeeds and returns a credential REFERENCE (the token was
|
|
25
|
+
* acquired + persisted as a host.credentials entry), never the token itself.
|
|
26
|
+
* 2. `connector.authorized` carries `{ provider, credentialRef, scopes }` and
|
|
27
|
+
* none of the token / refresh / code / state / redirectUri / codeVerifier.
|
|
28
|
+
* 3. RFC 0047 §C — the authorization code, redirect URI, state, and PKCE
|
|
29
|
+
* verifier MUST NOT appear on ANY run-visible surface; §C.2 — neither MUST
|
|
30
|
+
* the access/refresh token (the canaries are absent from the whole response).
|
|
31
|
+
*
|
|
32
|
+
* @see RFCS/0047-host-oauth-connector-flows.md §C
|
|
33
|
+
* @see conformance/fixtures/oauth-providers/synthetic.json
|
|
34
|
+
* @see SECURITY/invariants.yaml id: credential-payload-redaction
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { describe, it, expect } from 'vitest';
|
|
38
|
+
import { driver } from '../lib/driver.js';
|
|
39
|
+
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
40
|
+
|
|
41
|
+
interface DiscoveryOAuth {
|
|
42
|
+
supported?: boolean;
|
|
43
|
+
grants?: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Mirrors fixtures/oauth-providers/synthetic.json — keep in sync.
|
|
47
|
+
const SYNTHETIC = {
|
|
48
|
+
provider: 'synthetic',
|
|
49
|
+
authUrl: 'https://oauth.synthetic.openwop.test/authorize',
|
|
50
|
+
tokenUrl: 'https://oauth.synthetic.openwop.test/token',
|
|
51
|
+
scopes: ['openwop.read', 'openwop.write'],
|
|
52
|
+
authorizationCode: 'openwop-synthetic-auth-code-1f4b9e',
|
|
53
|
+
state: 'openwop-synthetic-state-7c2a8d',
|
|
54
|
+
redirectUri: 'https://host.example/openwop/oauth/callback',
|
|
55
|
+
codeVerifier: 'openwop-synthetic-pkce-verifier-3e9f1b2c5a7d4e8f0a1b2c3d4e5f6a7b',
|
|
56
|
+
accessTokenCanary: 'OPENWOP_OAUTH_TOKEN_CANARY_9d4c1f7a',
|
|
57
|
+
refreshTokenCanary: 'OPENWOP_OAUTH_REFRESH_CANARY_2b8e6a3f',
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
// Values that MUST NOT appear on any run-visible surface (RFC 0047 §C + §C.2).
|
|
61
|
+
const SECRET_VALUES: readonly string[] = [
|
|
62
|
+
SYNTHETIC.accessTokenCanary,
|
|
63
|
+
SYNTHETIC.refreshTokenCanary,
|
|
64
|
+
SYNTHETIC.authorizationCode,
|
|
65
|
+
SYNTHETIC.state,
|
|
66
|
+
SYNTHETIC.codeVerifier,
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
async function readOAuth(): Promise<DiscoveryOAuth | null> {
|
|
70
|
+
const res = await driver.get('/.well-known/openwop');
|
|
71
|
+
return capabilityFamily<DiscoveryOAuth>(res.json, 'oauth') ?? null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe('oauth-authorization-code-roundtrip: the grant dance (RFC 0047 §C)', () => {
|
|
75
|
+
it('acquires a token via authorization_code and returns a reference, never the token', async () => {
|
|
76
|
+
const oauth = await readOAuth();
|
|
77
|
+
if (!oauth?.supported) return; // capability-gated
|
|
78
|
+
if (!Array.isArray(oauth.grants) || !oauth.grants.includes('authorization_code')) return; // grant-gated
|
|
79
|
+
|
|
80
|
+
// Seam contract: the host performs the full authorization-code roundtrip
|
|
81
|
+
// against the synthetic provider's authUrl/tokenUrl, persists the acquired
|
|
82
|
+
// token as a host.credentials entry, and returns the run-observable surfaces
|
|
83
|
+
// (events incl. connector.authorized + snapshot + any debug bundle) plus the
|
|
84
|
+
// resulting credentialRef.
|
|
85
|
+
const res = await driver.post('/v1/host/sample/oauth/authorize-code-roundtrip', {
|
|
86
|
+
provider: SYNTHETIC.provider,
|
|
87
|
+
authUrl: SYNTHETIC.authUrl,
|
|
88
|
+
tokenUrl: SYNTHETIC.tokenUrl,
|
|
89
|
+
scopes: SYNTHETIC.scopes,
|
|
90
|
+
authorizationCode: SYNTHETIC.authorizationCode,
|
|
91
|
+
state: SYNTHETIC.state,
|
|
92
|
+
redirectUri: SYNTHETIC.redirectUri,
|
|
93
|
+
codeVerifier: SYNTHETIC.codeVerifier,
|
|
94
|
+
accessTokenCanary: SYNTHETIC.accessTokenCanary,
|
|
95
|
+
refreshTokenCanary: SYNTHETIC.refreshTokenCanary,
|
|
96
|
+
});
|
|
97
|
+
// A host that hasn't wired the seam soft-skips (Tier-2, host-pending).
|
|
98
|
+
if (res.status === 404) return;
|
|
99
|
+
|
|
100
|
+
expect(
|
|
101
|
+
res.status,
|
|
102
|
+
driver.describe(
|
|
103
|
+
'RFC 0047 §C',
|
|
104
|
+
'the authorize-code-roundtrip seam MUST perform the authorization_code grant against the synthetic provider and return the run observable surfaces',
|
|
105
|
+
),
|
|
106
|
+
).toBeLessThan(400);
|
|
107
|
+
|
|
108
|
+
const body = (res.json ?? {}) as { credentialRef?: unknown };
|
|
109
|
+
expect(
|
|
110
|
+
typeof body.credentialRef === 'string' && body.credentialRef.length > 0,
|
|
111
|
+
driver.describe(
|
|
112
|
+
'RFC 0047 §C',
|
|
113
|
+
'a successful roundtrip MUST resolve to a credential REFERENCE (token persisted as a host.credentials entry), not the raw token',
|
|
114
|
+
),
|
|
115
|
+
).toBe(true);
|
|
116
|
+
|
|
117
|
+
// §C + §C.2 — no secret material anywhere in the observable response.
|
|
118
|
+
const serialized = JSON.stringify(res.json ?? {});
|
|
119
|
+
for (const secret of SECRET_VALUES) {
|
|
120
|
+
expect(
|
|
121
|
+
serialized.includes(secret),
|
|
122
|
+
driver.describe(
|
|
123
|
+
'RFC 0047 §C / SECURITY/invariants.yaml credential-payload-redaction',
|
|
124
|
+
`the authorization code, state, PKCE verifier, and acquired token material MUST NOT appear on any run-visible surface — leaked: ${secret.slice(0, 16)}…`,
|
|
125
|
+
),
|
|
126
|
+
).toBe(false);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// §C — connector.authorized carries the reference + scopes, never the token.
|
|
130
|
+
const events = (res.json as { events?: Array<{ type?: string; payload?: Record<string, unknown> }> })?.events;
|
|
131
|
+
if (Array.isArray(events)) {
|
|
132
|
+
const authorized = events.find((e) => e?.type === 'connector.authorized');
|
|
133
|
+
if (authorized?.payload) {
|
|
134
|
+
const keys = Object.keys(authorized.payload);
|
|
135
|
+
expect(
|
|
136
|
+
keys.includes('credentialRef') && !keys.includes('access_token') && !keys.includes('refresh_token'),
|
|
137
|
+
driver.describe(
|
|
138
|
+
'RFC 0047 §C',
|
|
139
|
+
'connector.authorized MUST carry { provider, credentialRef, scopes } and MUST NOT carry token material',
|
|
140
|
+
),
|
|
141
|
+
).toBe(true);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -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
|
|