@openwop/openwop-conformance 1.3.0 → 1.5.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 +132 -1
- package/README.md +3 -2
- package/api/asyncapi.yaml +8 -0
- package/api/openapi.yaml +371 -1
- package/coverage.md +26 -6
- package/fixtures/conformance-envelope-nl-to-format-engaged.json +41 -0
- package/fixtures/conformance-envelope-recovery-applied.json +39 -0
- package/fixtures/conformance-envelope-refusal.json +38 -0
- package/fixtures/conformance-envelope-retry-attempted.json +39 -0
- package/fixtures/conformance-envelope-retry-exhausted.json +38 -0
- package/fixtures/conformance-envelope-truncated.json +39 -0
- package/fixtures/conformance-envelope-truncation-cap-exhaustion.json +39 -0
- package/fixtures/conformance-model-capability-insufficient.json +25 -0
- package/fixtures/conformance-multi-agent-confidence-escalation.json +49 -0
- package/fixtures/conformance-multi-agent-handoff-child.json +27 -0
- package/fixtures/conformance-multi-agent-handoff.json +49 -0
- package/fixtures/conformance-prompt-all-four-kinds.json +39 -0
- package/fixtures/conformance-prompt-end-to-end.json +33 -0
- package/fixtures/conformance-subworkflow-mid-run-mutation-child.json +31 -0
- package/fixtures/conformance-subworkflow-mid-run-mutation.json +33 -0
- package/fixtures/openwop-smoke-cost-emit.json +37 -0
- package/fixtures/prompt-templates/conformance-prompt-few-shot-2.json +14 -0
- package/fixtures/prompt-templates/conformance-prompt-few-shot.json +14 -0
- package/fixtures/prompt-templates/conformance-prompt-schema-hint.json +14 -0
- package/fixtures/prompt-templates/conformance-prompt-secret-redaction.json +23 -0
- package/fixtures/prompt-templates/conformance-prompt-trust-marker.json +23 -0
- package/fixtures/prompt-templates/conformance-prompt-writer-system.json +15 -0
- package/fixtures/prompt-templates/conformance-prompt-writer-user.json +15 -0
- package/fixtures.md +39 -0
- package/package.json +1 -1
- package/schemas/README.md +5 -0
- package/schemas/agent-manifest.schema.json +16 -0
- package/schemas/capabilities.schema.json +384 -1
- package/schemas/envelopes/clarification.request.schema.json +9 -0
- package/schemas/envelopes/error.schema.json +4 -0
- package/schemas/envelopes/schema.request.schema.json +4 -0
- package/schemas/envelopes/schema.response.schema.json +1 -1
- package/schemas/node-pack-manifest.schema.json +28 -0
- package/schemas/orchestrator-decision.schema.json +12 -0
- package/schemas/prompt-kind.schema.json +8 -0
- package/schemas/prompt-pack-manifest.schema.json +80 -0
- package/schemas/prompt-ref.schema.json +40 -0
- package/schemas/prompt-template.schema.json +149 -0
- package/schemas/registry-version-manifest.schema.json +5 -0
- package/schemas/run-ancestry-response.schema.json +54 -0
- package/schemas/run-event-payloads.schema.json +479 -11
- package/schemas/run-event.schema.json +15 -1
- package/schemas/run-snapshot.schema.json +3 -2
- package/schemas/workflow-definition.schema.json +19 -1
- package/src/lib/llm-cache-key-recipe.ts +68 -0
- package/src/scenarios/aiEnvelope.contractRefusal.test.ts +104 -13
- package/src/scenarios/aiEnvelope.correlationReplay.test.ts +32 -15
- package/src/scenarios/aiEnvelope.redaction.test.ts +6 -5
- package/src/scenarios/aiEnvelope.schemaDrift.test.ts +5 -5
- package/src/scenarios/aiEnvelope.trustBoundaryPropagation.test.ts +211 -12
- package/src/scenarios/aiEnvelope.universalKinds.test.ts +7 -7
- package/src/scenarios/blob-presign-expiry.test.ts +7 -7
- package/src/scenarios/cache-ttl-expiry.test.ts +6 -6
- package/src/scenarios/cost-attribution.test.ts +124 -11
- package/src/scenarios/cross-engine-append-ordering.test.ts +99 -0
- package/src/scenarios/cross-host-ancestry-endpoint.test.ts +136 -0
- package/src/scenarios/cross-host-causation-shape.test.ts +117 -0
- package/src/scenarios/cross-host-traceparent-propagation.test.ts +60 -0
- package/src/scenarios/envelope-completion-distinguishes-truncation.test.ts +223 -0
- package/src/scenarios/envelope-nl-to-format-engaged.test.ts +152 -0
- package/src/scenarios/envelope-reasoning-secret-redaction.test.ts +343 -0
- package/src/scenarios/envelope-reasoning-shape.test.ts +190 -0
- package/src/scenarios/envelope-recovery-applied.test.ts +229 -0
- package/src/scenarios/envelope-refusal-shape.test.ts +289 -0
- package/src/scenarios/envelope-retry-attempted.test.ts +258 -0
- package/src/scenarios/envelope-retry-exhausted.test.ts +168 -0
- package/src/scenarios/envelope-tier-one-subset-static.test.ts +229 -0
- package/src/scenarios/envelope-truncated.test.ts +136 -0
- package/src/scenarios/envelope-truncation-cap-exhaustion.test.ts +144 -0
- package/src/scenarios/envelope-variant-discriminator-static.test.ts +152 -0
- package/src/scenarios/fixtures-valid.test.ts +123 -15
- package/src/scenarios/kv-ttl-expiry.test.ts +7 -7
- package/src/scenarios/model-capability-insufficient.test.ts +221 -0
- package/src/scenarios/model-capability-substituted.test.ts +203 -0
- package/src/scenarios/multi-agent-confidence-escalation.test.ts +201 -0
- package/src/scenarios/multi-agent-handoff-state-machine.test.ts +167 -0
- package/src/scenarios/multi-agent-memory-lifecycle.test.ts +124 -0
- package/src/scenarios/multi-region-idempotency.test.ts +58 -0
- package/src/scenarios/node-module-required-capabilities-shape.test.ts +185 -0
- package/src/scenarios/prompt-all-four-kinds-events.test.ts +198 -0
- package/src/scenarios/prompt-composed-secret-redaction.test.ts +178 -0
- package/src/scenarios/prompt-composed-trust-marker.test.ts +165 -0
- package/src/scenarios/prompt-end-to-end-events.test.ts +202 -0
- package/src/scenarios/prompt-list-and-fetch.test.ts +207 -0
- package/src/scenarios/prompt-mutable-lifecycle.test.ts +216 -0
- package/src/scenarios/prompt-pack-install.test.ts +187 -0
- package/src/scenarios/prompt-render-deterministic.test.ts +240 -0
- package/src/scenarios/prompt-resolution-chain-agent-intrinsic.test.ts +140 -0
- package/src/scenarios/prompt-resolution-chain-fallback-cascade.test.ts +172 -0
- package/src/scenarios/prompt-resolution-chain-node-wins.test.ts +144 -0
- package/src/scenarios/prompt-template-shape.test.ts +359 -0
- package/src/scenarios/queue-ack-nack-dlq.test.ts +7 -7
- package/src/scenarios/queue-publish-consume-roundtrip.test.ts +7 -7
- package/src/scenarios/replay-divergence-at-refusal.test.ts +134 -0
- package/src/scenarios/replay-llm-cache-key-portable.test.ts +197 -0
- package/src/scenarios/replay-llm-cache-key.test.ts +1 -40
- package/src/scenarios/replay-observable-sequence-determinism.test.ts +80 -0
- package/src/scenarios/sandbox-capability-gate-respected.test.ts +27 -0
- package/src/scenarios/sandbox-memory-cap.test.ts +58 -0
- package/src/scenarios/sandbox-no-cross-pack-mutation.test.ts +30 -0
- package/src/scenarios/sandbox-no-host-env-leak.test.ts +27 -0
- package/src/scenarios/sandbox-no-host-fs-escape.test.ts +88 -0
- package/src/scenarios/sandbox-no-host-process-escape.test.ts +31 -0
- package/src/scenarios/sandbox-no-network-escape.test.ts +28 -0
- package/src/scenarios/sandbox-timeout-cap.test.ts +58 -0
- package/src/scenarios/search-bm25-roundtrip.test.ts +7 -7
- package/src/scenarios/spec-corpus-validity.test.ts +34 -6
- package/src/scenarios/sql-transaction-atomicity.test.ts +6 -6
- package/src/scenarios/stream-subscribe-from-beginning.test.ts +7 -7
- package/src/scenarios/subworkflow-input-mapping.test.ts +70 -4
- package/src/scenarios/table-cursor-pagination.test.ts +7 -7
- package/src/scenarios/table-schema-enforcement.test.ts +7 -7
- package/src/scenarios/vector-knn-roundtrip.test.ts +7 -7
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* aiEnvelope.universalKinds — FINAL v1.1 advertisement-shape
|
|
2
|
+
* aiEnvelope.universalKinds — FINAL v1.1 advertisement-shape + behavioral.
|
|
3
3
|
*
|
|
4
|
-
* Status:
|
|
5
|
-
*
|
|
6
|
-
* for hosts that opt into
|
|
7
|
-
* (`capabilities.envelopeContracts.advertised: true`)
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Status: ACTIVE (advertisement-shape + behavioral). `spec/v1/ai-envelope.md`
|
|
5
|
+
* promoted Draft → FINAL v1.1 2026-05-18. Asserts the advertisement shape
|
|
6
|
+
* for hosts that opt into envelope-contracts
|
|
7
|
+
* (`capabilities.envelopeContracts.advertised: true`), plus live behavioral
|
|
8
|
+
* universal-kind acceptance through the `POST /v1/host/sample/envelope/accept`
|
|
9
|
+
* seam (soft-skip on HTTP 404).
|
|
10
10
|
*
|
|
11
11
|
* Summary: hosts MUST advertise the four universal kinds (`clarification.request`,
|
|
12
12
|
* `schema.request`, `schema.response`, `error`) in `capabilities.supportedEnvelopes`
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* blob-presign-expiry — RFC 0019 advertisement-shape verification + behavioral
|
|
2
|
+
* blob-presign-expiry — RFC 0019 advertisement-shape verification + behavioral roundtrip.
|
|
3
3
|
*
|
|
4
|
-
* Status: ACTIVE (advertisement-shape). RFC 0019 promoted to
|
|
5
|
-
* 2026-05-17. The matching `capabilities.blobStorage` block has
|
|
6
|
-
* `schemas/capabilities.schema.json`. This scenario asserts the
|
|
7
|
-
* shape against any host that boots the conformance suite, and
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Status: ACTIVE (advertisement-shape + behavioral). RFC 0019 promoted to
|
|
5
|
+
* `Active` 2026-05-17. The matching `capabilities.blobStorage` block has
|
|
6
|
+
* landed in `schemas/capabilities.schema.json`. This scenario asserts the
|
|
7
|
+
* advertisement shape against any host that boots the conformance suite, and
|
|
8
|
+
* exercises the behavioral surface through the `/v1/host/sample/test/surface`
|
|
9
|
+
* seam (soft-skip with HTTP 404 on hosts that don't expose it).
|
|
10
10
|
*
|
|
11
11
|
* Summary: Presigned URLs MUST expire at the advertised TTL.
|
|
12
12
|
*
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* cache-ttl-expiry — RFC 0019 advertisement-shape verification + behavioral
|
|
2
|
+
* cache-ttl-expiry — RFC 0019 advertisement-shape verification + behavioral roundtrip.
|
|
3
3
|
*
|
|
4
|
-
* Status: ACTIVE (advertisement-shape). RFC 0019 promoted to
|
|
5
|
-
* 2026-05-17. The matching `capabilities.cache` block has landed in
|
|
4
|
+
* Status: ACTIVE (advertisement-shape + behavioral). RFC 0019 promoted to
|
|
5
|
+
* `Active` 2026-05-17. The matching `capabilities.cache` block has landed in
|
|
6
6
|
* `schemas/capabilities.schema.json`. This scenario asserts the advertisement
|
|
7
|
-
* shape against any host that boots the conformance suite, and
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* shape against any host that boots the conformance suite, and exercises the
|
|
8
|
+
* behavioral surface through the `/v1/host/sample/test/surface` seam
|
|
9
|
+
* (soft-skip with HTTP 404 on hosts that don't expose it).
|
|
10
10
|
*
|
|
11
11
|
* Summary: Cache TTL honored with at most 1-second drift.
|
|
12
12
|
*
|
|
@@ -17,10 +17,13 @@
|
|
|
17
17
|
* error envelope and skips trivially-pass when absent. When present,
|
|
18
18
|
* asserts the canary cost shape lands in `metrics.openwopCost` end-to-end.
|
|
19
19
|
*
|
|
20
|
-
* Two
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* enforcement in host-specific
|
|
20
|
+
* Two runtime-side enforcement claims (raw-OTel-span allowlist + per-attribute
|
|
21
|
+
* type validation at emission) are intentionally out of scope here — the
|
|
22
|
+
* conformance suite is black-box and can only see what the REST + event-log
|
|
23
|
+
* surfaces expose. Hosts cover those enforcement paths in host-specific
|
|
24
|
+
* unit tests against their own observability module (the reference
|
|
25
|
+
* workflow-engine ships them alongside the implementation, separately from
|
|
26
|
+
* this black-box suite).
|
|
24
27
|
*
|
|
25
28
|
* Spec references:
|
|
26
29
|
* - https://github.com/openwop/openwop/blob/main/spec/v1/observability.md §"AI cost"
|
|
@@ -32,12 +35,36 @@ import { describe, it, expect } from 'vitest';
|
|
|
32
35
|
import { driver } from '../lib/driver.js';
|
|
33
36
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
34
37
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
38
|
+
import { getCollector, waitForRunSpans } from '../lib/otel-collector.js';
|
|
35
39
|
|
|
36
40
|
const NOOP_WORKFLOW_ID = 'conformance-noop';
|
|
37
41
|
const COST_EMIT_WORKFLOW_ID = 'openwop-smoke-cost-emit';
|
|
38
42
|
const SKIP_NO_NOOP = !isFixtureAdvertised(NOOP_WORKFLOW_ID);
|
|
39
43
|
const SKIP_NO_COST_EMIT = !isFixtureAdvertised(COST_EMIT_WORKFLOW_ID);
|
|
40
44
|
|
|
45
|
+
/** Canonical attribute allowlist mirroring
|
|
46
|
+
* `spec/v1/observability.md §"Cost attribution attributes"`. Kept
|
|
47
|
+
* in-suite (not imported from the SDK) so the assertion is a
|
|
48
|
+
* cross-host wire contract rather than a sanity check on the host's
|
|
49
|
+
* own constant. Hosts SHOULD use `sanitizeCostAttributes` from
|
|
50
|
+
* `@openwop/openwop` at emit time; the suite asserts against the
|
|
51
|
+
* wire-side projection independently. */
|
|
52
|
+
const OPENWOP_COST_ATTRIBUTE_NAMES: readonly string[] = [
|
|
53
|
+
'openwop.cost.tokens.input',
|
|
54
|
+
'openwop.cost.tokens.output',
|
|
55
|
+
'openwop.cost.tokens.total',
|
|
56
|
+
'openwop.cost.usd',
|
|
57
|
+
'openwop.cost.currency',
|
|
58
|
+
'openwop.cost.estimated',
|
|
59
|
+
'openwop.cost.provider',
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/** BYOK / Bearer credential-shape detection — same families covered by
|
|
63
|
+
* `aiEnvelope.redaction.test.ts` and the host-side ephemeralRunSecrets
|
|
64
|
+
* scrubber. Lookarounds anchor to alphanumerics so credentials embedded
|
|
65
|
+
* in snake_case / kebab-case neighbors still match. */
|
|
66
|
+
const CREDENTIAL_SHAPE_RE = /(?<![A-Za-z0-9_])(?:sk-(?:ant-|proj-)?[A-Za-z0-9_-]{20,}|Bearer\s+[A-Za-z0-9._~+/=-]{20,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,})(?![A-Za-z0-9])|CANARY-openwop-CONFORMANCE-NEVER-SECRET[A-Za-z0-9_-]*/g;
|
|
67
|
+
|
|
41
68
|
describe.skipIf(SKIP_NO_NOOP)('cost-attribution: metrics.openwopCost forward-compat shape (G6)', () => {
|
|
42
69
|
it('on any run, IF metrics.openwopCost is present, its shape MUST match the spec', async () => {
|
|
43
70
|
// Use the noop fixture so we don't depend on AI nodes. The fixture
|
|
@@ -196,12 +223,98 @@ describe.skipIf(SKIP_NO_COST_EMIT)('cost-attribution: end-to-end roundtrip via c
|
|
|
196
223
|
});
|
|
197
224
|
});
|
|
198
225
|
|
|
199
|
-
describe('cost-attribution: G6 / O4
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
)
|
|
226
|
+
describe.skipIf(SKIP_NO_COST_EMIT)('cost-attribution: G6 / O4 allowlist + redaction (live OTel spans)', () => {
|
|
227
|
+
// Drives the `openwop-smoke-cost-emit` fixture, which posts arbitrary
|
|
228
|
+
// `attrs` into `conformance.cost.emit` — a mix of (a) all 7
|
|
229
|
+
// allowlisted attribute names, (b) one non-allowlisted key
|
|
230
|
+
// (`openwop.cost.evil`), and (c) a credential-shaped canary under a
|
|
231
|
+
// non-allowlisted name. The host's `sanitizeCostForOtel` MUST drop
|
|
232
|
+
// (b) and (c) before they reach the active OTel span.
|
|
233
|
+
//
|
|
234
|
+
// Reads the live span via the in-suite OTel collector (setup boots it
|
|
235
|
+
// when `OPENWOP_OTEL_COLLECTOR=true`; the test soft-skips when the
|
|
236
|
+
// collector isn't available, matching `otel-emission.test.ts`).
|
|
237
|
+
|
|
238
|
+
it('only allowlisted openwop.cost.* attributes reach the OTel span (G6 close criteria — allowlist enforcement)', async () => {
|
|
239
|
+
if (!getCollector()) {
|
|
240
|
+
// eslint-disable-next-line no-console
|
|
241
|
+
console.warn('[cost-attribution] OTel collector not started; set OPENWOP_OTEL_COLLECTOR=true to run');
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const collector = getCollector()!;
|
|
245
|
+
collector.reset();
|
|
246
|
+
|
|
247
|
+
const create = await driver.post('/v1/runs', { workflowId: COST_EMIT_WORKFLOW_ID });
|
|
248
|
+
expect(create.status).toBe(201);
|
|
249
|
+
const runId = (create.json as { runId: string }).runId;
|
|
250
|
+
await pollUntilTerminal(runId, { timeoutMs: 15_000 });
|
|
251
|
+
|
|
252
|
+
const runSpans = await waitForRunSpans(runId, { timeoutMs: 5_000, minCount: 1 });
|
|
253
|
+
expect(runSpans.length, driver.describe(
|
|
254
|
+
'observability.md §"Span attributes"',
|
|
255
|
+
'host MUST emit at least one span for the cost-emit run',
|
|
256
|
+
)).toBeGreaterThan(0);
|
|
203
257
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
258
|
+
// Inspect every span across the run for stray cost-namespace attrs.
|
|
259
|
+
// The fixture only emits on the `emit-cost` node's span, but the
|
|
260
|
+
// assertion is global: NO openwop.cost.* key may appear outside the
|
|
261
|
+
// allowlist on ANY span attributable to this run.
|
|
262
|
+
const ALLOWLIST = new Set(OPENWOP_COST_ATTRIBUTE_NAMES);
|
|
263
|
+
const stray: Array<{ span: string; key: string }> = [];
|
|
264
|
+
for (const span of runSpans) {
|
|
265
|
+
for (const key of span.attributes.keys()) {
|
|
266
|
+
if (key.startsWith('openwop.cost.') && !ALLOWLIST.has(key)) {
|
|
267
|
+
stray.push({ span: span.name, key });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
expect(stray, driver.describe(
|
|
272
|
+
'observability.md §"Cost attribution attributes" (allowlist enforcement)',
|
|
273
|
+
'host MUST NOT emit any openwop.cost.* attribute outside OPENWOP_COST_ATTRIBUTE_NAMES; defense-in-depth against accidental leakage of upstream provider fields under unfamiliar key names',
|
|
274
|
+
)).toEqual([]);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('credential-shaped canaries do NOT leak to any OTel attribute (G6 close criteria — redaction)', async () => {
|
|
278
|
+
if (!getCollector()) {
|
|
279
|
+
// eslint-disable-next-line no-console
|
|
280
|
+
console.warn('[cost-attribution] OTel collector not started; set OPENWOP_OTEL_COLLECTOR=true to run');
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const collector = getCollector()!;
|
|
284
|
+
collector.reset();
|
|
285
|
+
|
|
286
|
+
const create = await driver.post('/v1/runs', { workflowId: COST_EMIT_WORKFLOW_ID });
|
|
287
|
+
expect(create.status).toBe(201);
|
|
288
|
+
const runId = (create.json as { runId: string }).runId;
|
|
289
|
+
await pollUntilTerminal(runId, { timeoutMs: 15_000 });
|
|
290
|
+
|
|
291
|
+
const runSpans = await waitForRunSpans(runId, { timeoutMs: 5_000, minCount: 1 });
|
|
292
|
+
|
|
293
|
+
// Serialize EVERY span attribute value across the run and assert
|
|
294
|
+
// the canary marker is absent. The fixture deliberately ships the
|
|
295
|
+
// canary under a non-allowlisted key (`openwop.cost.leaked_token`)
|
|
296
|
+
// so the only way it appears in spans is if the sanitizer leaked.
|
|
297
|
+
const corpus = runSpans
|
|
298
|
+
.flatMap((span) => Array.from(span.attributes.values()))
|
|
299
|
+
.map((v) => (typeof v === 'string' ? v : JSON.stringify(v)))
|
|
300
|
+
.join('\n');
|
|
301
|
+
|
|
302
|
+
expect(
|
|
303
|
+
corpus.includes('CANARY-openwop-CONFORMANCE-NEVER-SECRET'),
|
|
304
|
+
driver.describe(
|
|
305
|
+
'SECURITY/invariants.yaml cost-attribution-allowlist-redaction',
|
|
306
|
+
'no canary plaintext substring may survive the allowlist sanitizer on its way to OTel spans',
|
|
307
|
+
),
|
|
308
|
+
).toBe(false);
|
|
309
|
+
|
|
310
|
+
// Belt-and-suspenders: also assert no BYOK-shape match anywhere in
|
|
311
|
+
// span attributes — catches credential-shaped values smuggled
|
|
312
|
+
// through non-canary keys that the allowlist still happens to let
|
|
313
|
+
// through (none today, but the regression test is cheap).
|
|
314
|
+
const byokMatches = corpus.match(CREDENTIAL_SHAPE_RE) ?? [];
|
|
315
|
+
expect(byokMatches, driver.describe(
|
|
316
|
+
'SECURITY/invariants.yaml cost-attribution-allowlist-redaction',
|
|
317
|
+
'no credential-shape substring may appear in cost-attribute span values',
|
|
318
|
+
)).toEqual([]);
|
|
319
|
+
});
|
|
207
320
|
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cross-engine-append-ordering — RFC 0036 §B advertisement-shape + behavioral.
|
|
3
|
+
*
|
|
4
|
+
* Status: ACTIVE (advertisement-shape). RFC 0036 promoted Draft → Active
|
|
5
|
+
* 2026-05-21. Capability-gated on `capabilities.eventLog.crossEngineOrdering.supported: true`.
|
|
6
|
+
* Hosts that don't advertise the capability soft-skip cleanly.
|
|
7
|
+
*
|
|
8
|
+
* Asserts (advertisement-shape — always-on when discovery is reachable):
|
|
9
|
+
* 1. capabilities.eventLog.crossEngineOrdering.supported MUST be boolean when present.
|
|
10
|
+
* 2. capabilities.eventLog.crossEngineOrdering.orderingModel MUST be one of
|
|
11
|
+
* {lamport, vector-clock, global-sequencer} when present.
|
|
12
|
+
* 3. When supported: true, orderingModel MUST be present (otherwise the
|
|
13
|
+
* claim has no operational meaning).
|
|
14
|
+
*
|
|
15
|
+
* Behavioral assertion (drives a two-engine fixture against the host's
|
|
16
|
+
* multi-engine simulator at apps/workflow-engine/.../multi-region-simulator.ts):
|
|
17
|
+
* concurrent appends from two engines to the same runId converge on a total
|
|
18
|
+
* order that both engines observe consistently on read. This assertion lands
|
|
19
|
+
* when the simulator harness is wired in a follow-up commit (per RFC 0036 §C);
|
|
20
|
+
* today's scenario soft-skips behavioral when the simulator env-gate
|
|
21
|
+
* (`OPENWOP_TEST_MULTI_ENGINE=true`) is unset.
|
|
22
|
+
*
|
|
23
|
+
* @see RFCS/0036-multi-region-and-cross-engine-guarantees.md §B
|
|
24
|
+
* @see schemas/capabilities.schema.json §capabilities.eventLog.crossEngineOrdering
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { describe, it, expect } from 'vitest';
|
|
28
|
+
import { driver } from '../lib/driver.js';
|
|
29
|
+
|
|
30
|
+
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
31
|
+
const ORDERING_MODELS = new Set(['lamport', 'vector-clock', 'global-sequencer']);
|
|
32
|
+
|
|
33
|
+
interface DiscoveryDoc {
|
|
34
|
+
capabilities?: {
|
|
35
|
+
eventLog?: {
|
|
36
|
+
crossEngineOrdering?: {
|
|
37
|
+
supported?: unknown;
|
|
38
|
+
orderingModel?: unknown;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
45
|
+
try {
|
|
46
|
+
const res = await driver.get('/.well-known/openwop');
|
|
47
|
+
if (res.status !== 200) return null;
|
|
48
|
+
return res.json as DiscoveryDoc;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
describe.skipIf(HTTP_SKIP)('cross-engine-append-ordering: advertisement shape (RFC 0036 §B)', () => {
|
|
55
|
+
it('capabilities.eventLog.crossEngineOrdering (when present) conforms to RFC 0036 §B', async () => {
|
|
56
|
+
const d = await readDiscovery();
|
|
57
|
+
if (d === null) return;
|
|
58
|
+
const ceo = d.capabilities?.eventLog?.crossEngineOrdering;
|
|
59
|
+
if (ceo === undefined) return; // host doesn't advertise — soft-skip
|
|
60
|
+
|
|
61
|
+
expect(
|
|
62
|
+
typeof ceo.supported,
|
|
63
|
+
driver.describe(
|
|
64
|
+
'RFCS/0036-multi-region-and-cross-engine-guarantees.md §B',
|
|
65
|
+
'capabilities.eventLog.crossEngineOrdering.supported MUST be boolean when present',
|
|
66
|
+
),
|
|
67
|
+
).toBe('boolean');
|
|
68
|
+
|
|
69
|
+
if (ceo.orderingModel !== undefined) {
|
|
70
|
+
expect(
|
|
71
|
+
ORDERING_MODELS.has(ceo.orderingModel as string),
|
|
72
|
+
driver.describe(
|
|
73
|
+
'RFCS/0036-multi-region-and-cross-engine-guarantees.md §B',
|
|
74
|
+
'orderingModel MUST be one of {lamport, vector-clock, global-sequencer}',
|
|
75
|
+
),
|
|
76
|
+
).toBe(true);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (ceo.supported === true) {
|
|
80
|
+
expect(
|
|
81
|
+
ceo.orderingModel,
|
|
82
|
+
driver.describe(
|
|
83
|
+
'RFCS/0036-multi-region-and-cross-engine-guarantees.md §B',
|
|
84
|
+
'when supported: true, orderingModel MUST be present (the categorical claim has no operational meaning without an advertised mechanism)',
|
|
85
|
+
),
|
|
86
|
+
).toBeDefined();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Behavioral assertion — drives a two-engine append + cross-engine read against
|
|
92
|
+
// the host's multi-engine simulator. Lands when the simulator harness is wired
|
|
93
|
+
// in a follow-up commit per RFC 0036 §C. Today the scenario soft-skips behavioral
|
|
94
|
+
// when the simulator env-gate is unset; capability-gated advertisement-shape
|
|
95
|
+
// probe above is the today-landable contract surface.
|
|
96
|
+
//
|
|
97
|
+
// Cross-host promotion path per RFCs/0001 §"Promotion to Accepted": once the
|
|
98
|
+
// simulator lands + a host advertises + the behavioral assertion passes against
|
|
99
|
+
// it, RFC 0036's cross-engine half graduates Active → Accepted.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cross-host-ancestry-endpoint — RFC 0040 §C `GET /v1/runs/{runId}/ancestry` behavioral.
|
|
3
|
+
*
|
|
4
|
+
* Status: ACTIVE (capability-gated behavioral). Gated on
|
|
5
|
+
* `capabilities.multiAgent.executionModel.crossHostCausation.ancestryEndpointSupported: true`.
|
|
6
|
+
* Hosts that don't advertise the endpoint soft-skip; hosts that DO
|
|
7
|
+
* advertise MUST serve the endpoint with the documented response shape.
|
|
8
|
+
*
|
|
9
|
+
* Asserts:
|
|
10
|
+
*
|
|
11
|
+
* 1. Top-level run (no parent dispatch): `GET /v1/runs/{runId}/ancestry`
|
|
12
|
+
* returns 200 with body `{runId, hostId, parent: null}`.
|
|
13
|
+
*
|
|
14
|
+
* 2. Response shape conforms to `schemas/run-ancestry-response.schema.json`:
|
|
15
|
+
* `runId` matches the request path; `hostId` matches the host's
|
|
16
|
+
* advertised `crossHostCausation.hostId`; `parent` is either null OR
|
|
17
|
+
* an object with `{runId, hostId, cause}` required + optional
|
|
18
|
+
* `wellKnownUrl` (present for cross-host parents).
|
|
19
|
+
*
|
|
20
|
+
* 3. (Behavioral, soft-skip if no cross-host fixture) Cross-host
|
|
21
|
+
* parent: a run dispatched from a different host's MCP tool call OR
|
|
22
|
+
* A2A message returns `parent.wellKnownUrl` set + `parent.cause` ∈
|
|
23
|
+
* {mcp-tool-call, a2a-message}. Lands when a cross-host test
|
|
24
|
+
* fixture ships.
|
|
25
|
+
*
|
|
26
|
+
* 4. Hosts that advertise `crossHostCausation.supported: true` but NOT
|
|
27
|
+
* `ancestryEndpointSupported: true` MUST return 404 from the
|
|
28
|
+
* endpoint (per spec/v1/multi-agent-execution.md §"GET /v1/runs/{runId}/ancestry").
|
|
29
|
+
*
|
|
30
|
+
* @see RFCS/0040-multi-agent-cross-host-causation.md §C
|
|
31
|
+
* @see spec/v1/multi-agent-execution.md §"GET /v1/runs/{runId}/ancestry endpoint"
|
|
32
|
+
* @see schemas/run-ancestry-response.schema.json
|
|
33
|
+
* @see api/openapi.yaml §getRunAncestry
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { describe, it, expect } from 'vitest';
|
|
37
|
+
import { driver } from '../lib/driver.js';
|
|
38
|
+
|
|
39
|
+
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
40
|
+
|
|
41
|
+
interface DiscoveryDoc {
|
|
42
|
+
capabilities?: {
|
|
43
|
+
multiAgent?: {
|
|
44
|
+
executionModel?: {
|
|
45
|
+
crossHostCausation?: {
|
|
46
|
+
supported?: unknown;
|
|
47
|
+
hostId?: unknown;
|
|
48
|
+
ancestryEndpointSupported?: unknown;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
56
|
+
try {
|
|
57
|
+
const res = await driver.get('/.well-known/openwop');
|
|
58
|
+
if (res.status !== 200) return null;
|
|
59
|
+
return res.json as DiscoveryDoc;
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe.skipIf(HTTP_SKIP)('cross-host-ancestry-endpoint: behavioral (RFC 0040 §C)', () => {
|
|
66
|
+
it('hosts advertising ancestryEndpointSupported MUST serve GET /v1/runs/{runId}/ancestry with the documented shape on a top-level run', async (ctx) => {
|
|
67
|
+
const d = await readDiscovery();
|
|
68
|
+
const chc = d?.capabilities?.multiAgent?.executionModel?.crossHostCausation;
|
|
69
|
+
if (chc?.ancestryEndpointSupported !== true) {
|
|
70
|
+
ctx.skip();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Create a fresh top-level run via the host's conformance-dispatch-loop
|
|
75
|
+
// fixture (any always-on fixture works; the ancestry semantics don't
|
|
76
|
+
// depend on the specific workflow).
|
|
77
|
+
const create = await driver.post('/v1/runs', { workflowId: 'conformance-dispatch-loop' });
|
|
78
|
+
if (create.status !== 201) {
|
|
79
|
+
ctx.skip();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const runId = (create.json as { runId: string }).runId;
|
|
83
|
+
|
|
84
|
+
const ancestryRes = await driver.get(`/v1/runs/${encodeURIComponent(runId)}/ancestry`);
|
|
85
|
+
expect(ancestryRes.status, driver.describe(
|
|
86
|
+
'RFCS/0040-multi-agent-cross-host-causation.md §C',
|
|
87
|
+
'host advertising ancestryEndpointSupported MUST serve the endpoint (200) — 404 is non-conformant',
|
|
88
|
+
)).toBe(200);
|
|
89
|
+
|
|
90
|
+
const body = ancestryRes.json as { runId?: string; hostId?: string; parent?: unknown };
|
|
91
|
+
expect(body.runId, 'runId in response MUST match the request path').toBe(runId);
|
|
92
|
+
expect(
|
|
93
|
+
typeof body.hostId === 'string' && (body.hostId as string).length >= 1,
|
|
94
|
+
'hostId MUST be present + non-empty',
|
|
95
|
+
).toBe(true);
|
|
96
|
+
if (chc.hostId !== undefined) {
|
|
97
|
+
expect(
|
|
98
|
+
body.hostId,
|
|
99
|
+
'hostId in response MUST equal the host\'s advertised crossHostCausation.hostId',
|
|
100
|
+
).toBe(chc.hostId);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Top-level run: parent is null.
|
|
104
|
+
expect(
|
|
105
|
+
body.parent,
|
|
106
|
+
driver.describe(
|
|
107
|
+
'RFCS/0040-multi-agent-cross-host-causation.md §C + schemas/run-ancestry-response.schema.json',
|
|
108
|
+
'a top-level run (not dispatched from any other run) MUST return parent: null',
|
|
109
|
+
),
|
|
110
|
+
).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('hosts advertising crossHostCausation.supported but NOT ancestryEndpointSupported MUST return 404 from the ancestry endpoint', async (ctx) => {
|
|
114
|
+
const d = await readDiscovery();
|
|
115
|
+
const chc = d?.capabilities?.multiAgent?.executionModel?.crossHostCausation;
|
|
116
|
+
if (chc?.supported !== true) {
|
|
117
|
+
ctx.skip();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (chc.ancestryEndpointSupported === true) {
|
|
121
|
+
ctx.skip(); // covered by the test above
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Use any runId — even a synthetic non-existent one. The endpoint should
|
|
126
|
+
// 404 regardless of run existence when the capability is not advertised.
|
|
127
|
+
const ancestryRes = await driver.get('/v1/runs/synthetic-test-run-id/ancestry');
|
|
128
|
+
expect(
|
|
129
|
+
ancestryRes.status,
|
|
130
|
+
driver.describe(
|
|
131
|
+
'spec/v1/multi-agent-execution.md §"GET /v1/runs/{runId}/ancestry endpoint"',
|
|
132
|
+
'hosts that advertise crossHostCausation.supported: true but NOT ancestryEndpointSupported MUST return 404 — the endpoint is opt-in even within Phase 3',
|
|
133
|
+
),
|
|
134
|
+
).toBe(404);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cross-host-causation-shape — RFC 0040 advertisement-shape + payload-field shape.
|
|
3
|
+
*
|
|
4
|
+
* Status: ACTIVE (advertisement-shape). RFC 0040 Phase 3 filed Draft
|
|
5
|
+
* 2026-05-22. Capability-gated on
|
|
6
|
+
* `capabilities.multiAgent.executionModel.crossHostCausation.supported: true`.
|
|
7
|
+
* Hosts that don't advertise soft-skip cleanly.
|
|
8
|
+
*
|
|
9
|
+
* Asserts (advertisement-shape — always-on when discovery is reachable):
|
|
10
|
+
*
|
|
11
|
+
* 1. capabilities.multiAgent.executionModel.crossHostCausation.supported
|
|
12
|
+
* MUST be boolean when present.
|
|
13
|
+
* 2. When crossHostCausation.supported: true, hostId MUST be present + non-empty.
|
|
14
|
+
* 3. ancestryEndpointSupported (when present) MUST be boolean.
|
|
15
|
+
* 4. When crossHostCausation.supported: true, the host's executionModel.version
|
|
16
|
+
* MUST be >= 3 (Phase 3 requires the multi-agent execution model framework).
|
|
17
|
+
*
|
|
18
|
+
* Behavioral assertion (payload-field shape, soft-skipped when no host
|
|
19
|
+
* emits cross-host events): cross-host event payloads carry
|
|
20
|
+
* `causationHostId` matching the originating host's hostId. Lands when
|
|
21
|
+
* a cross-host composition test fixture ships.
|
|
22
|
+
*
|
|
23
|
+
* @see RFCS/0040-multi-agent-cross-host-causation.md
|
|
24
|
+
* @see spec/v1/multi-agent-execution.md §"Cross-host causation (RFC 0040 Phase 3, normative)"
|
|
25
|
+
* @see schemas/capabilities.schema.json §multiAgent.executionModel.crossHostCausation
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { describe, it, expect } from 'vitest';
|
|
29
|
+
import { driver } from '../lib/driver.js';
|
|
30
|
+
|
|
31
|
+
const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
|
|
32
|
+
|
|
33
|
+
interface DiscoveryDoc {
|
|
34
|
+
capabilities?: {
|
|
35
|
+
multiAgent?: {
|
|
36
|
+
executionModel?: {
|
|
37
|
+
supported?: unknown;
|
|
38
|
+
version?: unknown;
|
|
39
|
+
crossHostCausation?: {
|
|
40
|
+
supported?: unknown;
|
|
41
|
+
hostId?: unknown;
|
|
42
|
+
ancestryEndpointSupported?: unknown;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
50
|
+
try {
|
|
51
|
+
const res = await driver.get('/.well-known/openwop');
|
|
52
|
+
if (res.status !== 200) return null;
|
|
53
|
+
return res.json as DiscoveryDoc;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe.skipIf(HTTP_SKIP)('cross-host-causation-shape: advertisement shape (RFC 0040 §D)', () => {
|
|
60
|
+
it('crossHostCausation (when present) conforms to RFC 0040 §D', async (ctx) => {
|
|
61
|
+
const d = await readDiscovery();
|
|
62
|
+
if (d === null) {
|
|
63
|
+
ctx.skip();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const chc = d.capabilities?.multiAgent?.executionModel?.crossHostCausation;
|
|
67
|
+
if (chc === undefined) {
|
|
68
|
+
ctx.skip(); // host doesn't advertise — soft-skip
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
expect(
|
|
73
|
+
typeof chc.supported,
|
|
74
|
+
driver.describe(
|
|
75
|
+
'RFCS/0040-multi-agent-cross-host-causation.md §D',
|
|
76
|
+
'crossHostCausation.supported MUST be boolean when present',
|
|
77
|
+
),
|
|
78
|
+
).toBe('boolean');
|
|
79
|
+
|
|
80
|
+
if (chc.supported === true) {
|
|
81
|
+
const version = d.capabilities?.multiAgent?.executionModel?.version as number | undefined;
|
|
82
|
+
expect(
|
|
83
|
+
typeof version === 'number' && version >= 3,
|
|
84
|
+
driver.describe(
|
|
85
|
+
'RFCS/0040-multi-agent-cross-host-causation.md §D',
|
|
86
|
+
'when crossHostCausation.supported: true, multiAgent.executionModel.version MUST be >= 3',
|
|
87
|
+
),
|
|
88
|
+
).toBe(true);
|
|
89
|
+
|
|
90
|
+
expect(
|
|
91
|
+
typeof chc.hostId === 'string' && (chc.hostId as string).length >= 1,
|
|
92
|
+
driver.describe(
|
|
93
|
+
'RFCS/0040-multi-agent-cross-host-causation.md §D',
|
|
94
|
+
'when crossHostCausation.supported: true, hostId MUST be present + non-empty',
|
|
95
|
+
),
|
|
96
|
+
).toBe(true);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (chc.ancestryEndpointSupported !== undefined) {
|
|
100
|
+
expect(
|
|
101
|
+
typeof chc.ancestryEndpointSupported,
|
|
102
|
+
driver.describe(
|
|
103
|
+
'RFCS/0040-multi-agent-cross-host-causation.md §D',
|
|
104
|
+
'ancestryEndpointSupported MUST be boolean when present',
|
|
105
|
+
),
|
|
106
|
+
).toBe('boolean');
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Behavioral payload-field assertion lands when a cross-host composition test
|
|
112
|
+
// fixture ships. Expected: a `core.workflowChain.event` (or any payload type
|
|
113
|
+
// listed in spec/v1/multi-agent-execution.md §"causationHostId payload field")
|
|
114
|
+
// emitted in response to a cross-host invocation carries `causationHostId`
|
|
115
|
+
// equal to the originating host's advertised hostId. Today's reference
|
|
116
|
+
// workflow-engine sample doesn't have a cross-host fixture; the assertion
|
|
117
|
+
// soft-skips on hosts that don't emit cross-host events.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cross-host-traceparent-propagation — RFC 0040 §B behavioral (capability-gated).
|
|
3
|
+
*
|
|
4
|
+
* Status: ACTIVE (capability-gated; behavioral assertion soft-skipped
|
|
5
|
+
* until a cross-host MCP/A2A composition test fixture ships). Gated on
|
|
6
|
+
* `capabilities.multiAgent.executionModel.version >= 3` AND
|
|
7
|
+
* `capabilities.multiAgent.executionModel.crossHostCausation.supported: true`.
|
|
8
|
+
*
|
|
9
|
+
* Asserts (when host advertises Phase 3 + a real MCP/A2A composition
|
|
10
|
+
* endpoint is reachable):
|
|
11
|
+
*
|
|
12
|
+
* 1. An outbound MCP tool call dispatched from a Phase 3 host MUST
|
|
13
|
+
* carry the parent run's W3C `traceparent` header. The MCP server
|
|
14
|
+
* receives the header AND uses it as the parent trace for any
|
|
15
|
+
* spans it emits (closing the cross-host span linkage that
|
|
16
|
+
* RFC 0023's same-host coverage left open).
|
|
17
|
+
*
|
|
18
|
+
* 2. An inbound MCP tool reply OR A2A message handler MUST adopt the
|
|
19
|
+
* `traceparent` header from the inbound envelope as the trace
|
|
20
|
+
* parent for any subsequent events the receiving agent emits.
|
|
21
|
+
*
|
|
22
|
+
* 3. (Symmetric) Outbound A2A messages MUST carry the parent run's
|
|
23
|
+
* `traceparent`; inbound A2A handlers MUST adopt it.
|
|
24
|
+
*
|
|
25
|
+
* Behavioral wiring requires a cross-host test harness: either a real
|
|
26
|
+
* MCP server peer (`OPENWOP_MCP_REAL_SERVER_URL`) or an A2A peer
|
|
27
|
+
* (`OPENWOP_A2A_REAL_PEER_URL`) the host can call into. Without those,
|
|
28
|
+
* the assertion soft-skips and only the shape probe in
|
|
29
|
+
* cross-host-causation-shape.test.ts applies.
|
|
30
|
+
*
|
|
31
|
+
* @see RFCS/0040-multi-agent-cross-host-causation.md §B
|
|
32
|
+
* @see spec/v1/multi-agent-execution.md §"W3C tracecontext across MCP + A2A composition"
|
|
33
|
+
* @see RFCS/0023-conformance-agent-event-emitters.md (the same-host predecessor)
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { describe, it } from 'vitest';
|
|
37
|
+
|
|
38
|
+
// Behavioral assertions in this file are currently `it.todo` placeholders;
|
|
39
|
+
// the cross-host MCP / A2A peer harness (gated on OPENWOP_MCP_REAL_SERVER_URL
|
|
40
|
+
// / OPENWOP_A2A_REAL_PEER_URL) hasn't landed yet. When it does, the
|
|
41
|
+
// `it.todo` calls flip back to runnable `it(...)` bodies that read discovery
|
|
42
|
+
// (via `driver.get('/.well-known/openwop')`), gate on `Phase 3` advertisement,
|
|
43
|
+
// and drive the workflow through the configured real peer.
|
|
44
|
+
|
|
45
|
+
describe('cross-host-traceparent-propagation: behavioral (RFC 0040 §B)', () => {
|
|
46
|
+
// Behavioral assertion drives a workflow that calls an MCP tool via the
|
|
47
|
+
// host's `core.mcp.toolCall` node. The MCP peer (configured via
|
|
48
|
+
// OPENWOP_MCP_REAL_SERVER_URL) records inbound headers; the test reads
|
|
49
|
+
// the recorded headers and asserts `traceparent` is present + matches
|
|
50
|
+
// the format `00-{traceId}-{spanId}-{flags}` per W3C tracecontext.
|
|
51
|
+
// Until the peer harness lands, the assertion is surfaced as `todo` so
|
|
52
|
+
// test reporters track the gap rather than reporting a vacuous PASS.
|
|
53
|
+
it.todo('Phase 3 host MUST inject parent run\'s traceparent into outbound MCP requests');
|
|
54
|
+
|
|
55
|
+
// Behavioral assertion drives a workflow that dispatches an A2A message
|
|
56
|
+
// via the host's `core.a2a.send` (or equivalent) node. The A2A peer
|
|
57
|
+
// (configured via OPENWOP_A2A_REAL_PEER_URL) records inbound headers;
|
|
58
|
+
// the test asserts `traceparent` is present + well-formed.
|
|
59
|
+
it.todo('Phase 3 host MUST inject parent run\'s traceparent into outbound A2A messages');
|
|
60
|
+
});
|