@openwop/openwop-conformance 2.45.13 → 2.45.15

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +2 -2
  3. package/dist/spec-artifacts.lock.json +2 -2
  4. package/package.json +2 -2
  5. package/requirements.json +1446 -4
  6. package/scenario-majors.json +65 -2
  7. package/schemas/CORPUS-STAMP.json +14 -12
  8. package/src/lib/backpressure-witness.ts +2 -2
  9. package/src/lib/budget-witness.ts +3 -3
  10. package/src/lib/envelope-schema-static.ts +166 -0
  11. package/src/lib/exhaustion-facet-witness.ts +1 -1
  12. package/src/lib/family-advert-witness.ts +212 -0
  13. package/src/lib/i18n-double.ts +99 -0
  14. package/src/lib/i18n-negotiation-witness.ts +173 -0
  15. package/src/lib/localized-content-witness.ts +49 -0
  16. package/src/lib/model-capability-insufficient-witness.ts +121 -0
  17. package/src/lib/prompt-double.ts +217 -0
  18. package/src/lib/prompt-events-witness.ts +211 -0
  19. package/src/lib/prompt-library-witness.ts +406 -0
  20. package/src/lib/run-bounds-witness.ts +111 -0
  21. package/src/lib/scratch-host.ts +4 -1
  22. package/src/lib/table-schema-witness.ts +3 -1
  23. package/src/scenarios/v2-ai-envelope-shape-static.test.ts +93 -0
  24. package/src/scenarios/v2-ai-providers-advertisement.test.ts +94 -0
  25. package/src/scenarios/v2-envelope-reasoning-shape.test.ts +81 -0
  26. package/src/scenarios/v2-envelope-tier-one-subset-static.test.ts +77 -0
  27. package/src/scenarios/v2-envelope-variant-discriminator-static.test.ts +57 -0
  28. package/src/scenarios/v2-envelopes-advertisement.test.ts +85 -0
  29. package/src/scenarios/v2-i18n-negotiation.test.ts +91 -0
  30. package/src/scenarios/v2-limits-envelope-caps-advertisement.test.ts +61 -0
  31. package/src/scenarios/v2-localized-content-delivery.test.ts +136 -0
  32. package/src/scenarios/v2-media-inline-cap-static.test.ts +59 -0
  33. package/src/scenarios/v2-model-capabilities-advertisement.test.ts +61 -0
  34. package/src/scenarios/v2-model-capability-insufficient.test.ts +63 -0
  35. package/src/scenarios/v2-prompt-all-four-kinds-events.test.ts +69 -0
  36. package/src/scenarios/v2-prompt-end-to-end-events.test.ts +71 -0
  37. package/src/scenarios/v2-prompt-list-and-fetch.test.ts +103 -0
  38. package/src/scenarios/v2-prompt-mutable-lifecycle.test.ts +130 -0
  39. package/src/scenarios/v2-prompt-pack-install.test.ts +78 -0
  40. package/src/scenarios/v2-prompt-render-deterministic.test.ts +87 -0
  41. package/src/scenarios/v2-prompt-resolution-chain-event.test.ts +46 -0
  42. package/src/scenarios/v2-prompt-template-shape.test.ts +112 -0
  43. package/src/scenarios/v2-run-execution-bounds.test.ts +67 -0
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "$comment": "GENERATED by conformance/scripts/generate-scenario-majors.mjs (RFC 0168 §D.3). Do not edit; add a file to BOTH_MAJORS in the generator to target both majors.",
3
3
  "counts": {
4
- "files": 594,
4
+ "files": 615,
5
5
  "v1": 455,
6
- "v2": 154
6
+ "v2": 175
7
7
  },
8
8
  "majors": {
9
9
  "a2a-1-0-agent-card.test.ts": [
@@ -1299,6 +1299,12 @@
1299
1299
  "v2-agent-org-chart-served-shape.test.ts": [
1300
1300
  2
1301
1301
  ],
1302
+ "v2-ai-envelope-shape-static.test.ts": [
1303
+ 2
1304
+ ],
1305
+ "v2-ai-providers-advertisement.test.ts": [
1306
+ 2
1307
+ ],
1302
1308
  "v2-approval-reject-disposition.test.ts": [
1303
1309
  2
1304
1310
  ],
@@ -1389,6 +1395,18 @@
1389
1395
  "v2-enum-growth-rule.test.ts": [
1390
1396
  2
1391
1397
  ],
1398
+ "v2-envelope-reasoning-shape.test.ts": [
1399
+ 2
1400
+ ],
1401
+ "v2-envelope-tier-one-subset-static.test.ts": [
1402
+ 2
1403
+ ],
1404
+ "v2-envelope-variant-discriminator-static.test.ts": [
1405
+ 2
1406
+ ],
1407
+ "v2-envelopes-advertisement.test.ts": [
1408
+ 2
1409
+ ],
1392
1410
  "v2-era-2-append-vocabulary.test.ts": [
1393
1411
  2
1394
1412
  ],
@@ -1425,6 +1443,9 @@
1425
1443
  "v2-header-scheme.test.ts": [
1426
1444
  2
1427
1445
  ],
1446
+ "v2-i18n-negotiation.test.ts": [
1447
+ 2
1448
+ ],
1428
1449
  "v2-id-grammar.test.ts": [
1429
1450
  2
1430
1451
  ],
@@ -1455,6 +1476,12 @@
1455
1476
  "v2-legacy-profiles-absent.test.ts": [
1456
1477
  2
1457
1478
  ],
1479
+ "v2-limits-envelope-caps-advertisement.test.ts": [
1480
+ 2
1481
+ ],
1482
+ "v2-localized-content-delivery.test.ts": [
1483
+ 2
1484
+ ],
1458
1485
  "v2-malformed-body-envelope.test.ts": [
1459
1486
  2
1460
1487
  ],
@@ -1473,6 +1500,9 @@
1473
1500
  "v2-mcp-tasks.test.ts": [
1474
1501
  2
1475
1502
  ],
1503
+ "v2-media-inline-cap-static.test.ts": [
1504
+ 2
1505
+ ],
1476
1506
  "v2-memory-cross-tenant-isolation.test.ts": [
1477
1507
  2
1478
1508
  ],
@@ -1482,6 +1512,12 @@
1482
1512
  "v2-minimum-version-refused.test.ts": [
1483
1513
  2
1484
1514
  ],
1515
+ "v2-model-capabilities-advertisement.test.ts": [
1516
+ 2
1517
+ ],
1518
+ "v2-model-capability-insufficient.test.ts": [
1519
+ 2
1520
+ ],
1485
1521
  "v2-mrtr-rounds-ceiling.test.ts": [
1486
1522
  2
1487
1523
  ],
@@ -1536,6 +1572,30 @@
1536
1572
  "v2-profiles-derived-only.test.ts": [
1537
1573
  2
1538
1574
  ],
1575
+ "v2-prompt-all-four-kinds-events.test.ts": [
1576
+ 2
1577
+ ],
1578
+ "v2-prompt-end-to-end-events.test.ts": [
1579
+ 2
1580
+ ],
1581
+ "v2-prompt-list-and-fetch.test.ts": [
1582
+ 2
1583
+ ],
1584
+ "v2-prompt-mutable-lifecycle.test.ts": [
1585
+ 2
1586
+ ],
1587
+ "v2-prompt-pack-install.test.ts": [
1588
+ 2
1589
+ ],
1590
+ "v2-prompt-render-deterministic.test.ts": [
1591
+ 2
1592
+ ],
1593
+ "v2-prompt-resolution-chain-event.test.ts": [
1594
+ 2
1595
+ ],
1596
+ "v2-prompt-template-shape.test.ts": [
1597
+ 2
1598
+ ],
1539
1599
  "v2-protected-resource-metadata.test.ts": [
1540
1600
  2
1541
1601
  ],
@@ -1575,6 +1635,9 @@
1575
1635
  "v2-run-diff-identical.test.ts": [
1576
1636
  2
1577
1637
  ],
1638
+ "v2-run-execution-bounds.test.ts": [
1639
+ 2
1640
+ ],
1578
1641
  "v2-run-fork-ancestry.test.ts": [
1579
1642
  2
1580
1643
  ],
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "_comment": "Provenance of @openwop/spec-artifacts (RFC 0168 §D.2). files: SHA-256 per file; the conformance suite compares the installed peer against dist/spec-artifacts.lock.json at start.",
3
3
  "package": "@openwop/spec-artifacts",
4
- "version": "2.45.13",
5
- "corpusTag": "v2.45.13",
4
+ "version": "2.45.15",
5
+ "corpusTag": "v2.45.15",
6
6
  "files": {
7
7
  "api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
8
8
  "api/asyncapi.yaml": "d5ecb9ee6114582be3b1f662c84bfac9ae96dae7bacb853e461168f70a8e1c7d",
9
9
  "api/grpc/openwop.proto": "c3e72bb17cba514ee98feb6434e6c9b6ea6795bfd086489ec69fd882dd1ad977",
10
10
  "api/openapi.yaml": "ac6de5303872664b78ff419c035915593e01599e02835e777a0123de1baeb398",
11
11
  "api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
12
- "api/seams-v2.yaml": "e04f33a55093271aa1d8c1de7e9415c234e0422e1d51c78eaf89e4f45fface82",
13
- "api/v2/asyncapi.yaml": "ae7e64de2b5bca421d98e244ddf4f25a20e22555945c39b2095661d074c994cb",
14
- "api/v2/openapi.yaml": "eb07406e2e79a0f6096fbc50a509028568bff40edf11f6d736d97711b20a3db7",
12
+ "api/seams-v2.yaml": "b8a98faeb78af296f41b6c4bb926d8c01d45b5b81974d785924d6d4efa939831",
13
+ "api/v2/asyncapi.yaml": "667763e99e9bbaf49ed0468e2f10fca37a94327fb1d6a6168d011ab4e892066e",
14
+ "api/v2/openapi.yaml": "be44d7aa5c911d59c2a993206bb9b491070ad8551d03d49be04371cac7e6b6af",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
16
  "schemas/README.md": "bd1c00fae7b6fda506a75aff841bbdc438d4ce092642a72041203bee999b88de",
17
17
  "schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
@@ -207,13 +207,14 @@
207
207
  "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
208
208
  "spec/v1/alias-detectors.json": "0b2f28808ffe0b9d98a187775038c3406bb70919747a788a6e4f866e293ae6d3",
209
209
  "spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
210
- "spec/v1/core-standard-manifest.json": "0e4fef9b14346bff71b5141679bc5a8b5325a0025c49155cdf17fa985c6d9ecf",
210
+ "spec/v1/core-standard-manifest.json": "430abcd05b35ca4fbb14f21f1d7a76a7408d80a795986b6c6a4aa525870e7624",
211
211
  "spec/v1/deprecations.json": "a3293072ea951857395d05f0437264e88965f1e9019ff61c55bacfa9c1aaa17c",
212
212
  "spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
213
+ "spec/v1/end-of-support.json": "35f0bf3b70fe78b16e2a2fd81a153fc27e703f6c16376c6fe7cdf1f4f2c4e6d4",
213
214
  "spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
214
215
  "spec/v1/event-codemap.schema.json": "d05933b2e88103aff51a2f774df97b9bda065e0fc88dbd9b114cc47a32189174",
215
216
  "spec/v1/extensions.json": "79a60754aa16cbdbdb604f8e5c00038af535a1690a4d07bd5ff7c38f0256a7bc",
216
- "spec/v1/gaps.json": "54fd7e55136294a2f852f95c87209bb991947e5075e56ffa23679dcf2f5c50ff",
217
+ "spec/v1/gaps.json": "68e23cccf787f77e31710671baa3618ca24ee6eada68df84a7b55dc746762a2a",
217
218
  "spec/v1/gaps.schema.json": "8fd83259f556553c9df0f53e7a82ca8c2a4771d471197f69b4e2ae0ceeacfd66",
218
219
  "spec/v1/migrations.json": "4c50760be70cb644d02427bcaf5749606700edf4513f423903cf3b1d12f5c300",
219
220
  "spec/v1/migrations.schema.json": "886779aa6c22e646db097f5df210adb018a4dd14a7b815465a18c8a7056c8f72",
@@ -238,7 +239,7 @@
238
239
  "spec/v2/core/interrupt.md": "10f15dfe30baa0fc748aea0a067f9b271bea316fde6b598d78f77edc296f698a",
239
240
  "spec/v2/core/node-pack-runtimes.md": "e1088cb69b8c3c67256c494063328916c2980ab1aa97a7063de25bab82fdb1e5",
240
241
  "spec/v2/core/oauth.md": "c069ae4f9aae2eafe14c07c0bf709aa7ade0dcc821973d740400d49d901f7061",
241
- "spec/v2/core/overview.md": "d7000fbcd2f59280870fc592f5d5ea28a5195b9eb209029f09c5de59e0f2bf9c",
242
+ "spec/v2/core/overview.md": "999cb657711d6c28e62032fc31a013cc175f7a981a3b012e4bf4a90a2b84344c",
242
243
  "spec/v2/core/packs.md": "0d1516c4b08fe67b85759feb9ccbafdd766216435a4a0a7e58e27e6f9fe6b6cc",
243
244
  "spec/v2/core/persistence.md": "249a1de353f421cdf92b9006a13a10afdb002f4029eb6a13520cc95252edb2ec",
244
245
  "spec/v2/core/portability.md": "ab13a6ca43544b9a6a91edfb377ec680c768d14be841c84fb79125b231426891",
@@ -247,13 +248,14 @@
247
248
  "spec/v2/core/security-defaults.md": "887d8c4bc592094e3b931c23f1c2175c18ebbd47528b074016f9d2091bc513b2",
248
249
  "spec/v2/core/storage.md": "dc94b985847c1341c7a064f04a6010f0fdbe6c3ab3e555d4362878c81d764c18",
249
250
  "spec/v2/core/tool-catalog.md": "d16a9004418719c48301804aefa4d7ab6f41112a4ed83727f552aa1b92b418b0",
250
- "spec/v2/core/versioning.md": "fe48f4514b9c9f998d5da1930b8faa02ca4de89dcd49b07d95414fb04c67d438",
251
+ "spec/v2/core/versioning.md": "348a6dc5bd3425efc0016bb6772d5d04ebf578b73eebcdfec6c2d82346a8577e",
251
252
  "spec/v2/core/webhooks.md": "daf6ec03092f4b3fde1eb74a5e2e801ec48db5139a13fcc0fd7fb4a004635ae3",
252
253
  "spec/v2/core/workflow-chain-packs.md": "fdce7a8477e269950a2bac05ee6eddbd66eb0b670b464d00054f7196ec05d29c",
253
254
  "spec/v2/corrections.json": "3a8d93e33865b692d26a3b6bfd3819766e206b2e0c2a69669baa4bf833d05a49",
254
255
  "spec/v2/corrections.schema.json": "46bbffb954d13bd9995aebc912ceeb2544f7064331abf25fd6ab85c81a5cec94",
255
256
  "spec/v2/declaration.json": "e963d3aa73892ee08281227c8a01aa6c06f1b825cdcb26aab029b1ab3f4155bf",
256
257
  "spec/v2/declaration.schema.json": "ef951633e4070899f64828f5cf1b5f4870270a0360f81dbb49b5b4d6cd407550",
258
+ "spec/v2/eos-override.json": "4c72d2f6a658943b01c1de1643cf294642091933af7832f38ee5495df684cb2d",
257
259
  "spec/v2/errors.json": "83f048b0a06952b4af6f125fb66c4212acab51056b411390f1010acd6ab9cdc0",
258
260
  "spec/v2/errors.schema.json": "47404ad53e6147cf9017b813483712466573e419fb33e6c4b6c12d3e7deba1bc",
259
261
  "spec/v2/event-codemap.json": "37869a03cdfd8758b57e6f350641a6ebbd383231cebda51f5473ad85f94ab74c",
@@ -307,9 +309,9 @@
307
309
  "spec/v2/path-manifest.json": "b84c7561f50c7df0cb61b202bd6595acad3ee7edca7a45f711949bea3c69bd27",
308
310
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
309
311
  "spec/v2/profiles.json": "180beb9ef2d161766fa23b2987e7e829851e3d316273147cf6a4a3f324befd6b",
310
- "spec/v2/release.json": "38fa895f3288a59e95ec9d37b7d493c897bc7a174e9cde0f82d65d0d235ddff5",
312
+ "spec/v2/release.json": "75a6a33e253e1015ff582bcd3dbc2ed23cd8bea95e12d34006ee3239c91b71bb",
311
313
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
312
- "spec/v2/surface-baseline.json": "c53a82ff3aa935978d29ed7832ce4df54792c12a81c987cd524a9f8e3f85b099"
314
+ "spec/v2/surface-baseline.json": "49311a9dd16aaa597d1c6724aa24456edd5f372d9b7d0bd6f5f1647f68db6db8"
313
315
  },
314
- "corpusCommit": "9419dea220a38f0c498b48e894d43992f34ce5d5"
316
+ "corpusCommit": "eef96d5601f777c4bf94d9c2a69b06df86948172"
315
317
  }
@@ -89,7 +89,7 @@ export async function saturate(profile: MajorProfile, discovery: unknown): Promi
89
89
  streams.push(ctl);
90
90
  // `Accept: text/event-stream` keeps the request in flight; without it a
91
91
  // negotiating host answers a one-shot snapshot and the slot drops.
92
- const stream = fetch(`${env.baseUrl}${profile.runsPath}/${encodeURIComponent(runId)}/events`, {
92
+ const stream = fetch(`${env.baseUrl}${profile.runsPath}/${profile.idSegment(runId)}/events`, {
93
93
  headers: { Authorization: `Bearer ${env.apiKey}`, Accept: 'text/event-stream', ...profile.versionHeaders },
94
94
  signal: ctl.signal,
95
95
  }).then((r) => r.status, () => null);
@@ -127,7 +127,7 @@ export async function saturate(profile: MajorProfile, discovery: unknown): Promi
127
127
  if (runIds.length > 0) {
128
128
  const bulk = await http(() => driver.post(`${profile.runsPath}:bulk-cancel`, { runIds }));
129
129
  if (bulk === null || bulk.status >= 400) {
130
- for (const id of runIds) await http(() => driver.post(`${profile.runsPath}/${encodeURIComponent(id)}/cancel`, {}));
130
+ for (const id of runIds) await http(() => driver.post(`${profile.runsPath}/${profile.idSegment(id)}/cancel`, {}));
131
131
  }
132
132
  }
133
133
  await Promise.allSettled(pending);
@@ -59,7 +59,7 @@ async function readEvents(profile: MajorProfile, runId: string): Promise<RunEven
59
59
  const out: RunEvent[] = [];
60
60
  let after = 0;
61
61
  for (let page = 0; page < 50; page++) {
62
- const res = await http(() => driver.get(`${profile.runsPath}/${encodeURIComponent(runId)}/events/poll?timeout=1&afterSequence=${after}`));
62
+ const res = await http(() => driver.get(`${profile.runsPath}/${profile.idSegment(runId)}/events/poll?timeout=1&afterSequence=${after}`));
63
63
  if (res === null || res.status !== 200) return page === 0 ? null : out;
64
64
  const batch = (res.json as { events?: unknown } | undefined)?.events;
65
65
  if (!Array.isArray(batch) || batch.length === 0) return out;
@@ -101,11 +101,11 @@ export async function drive(profile: MajorProfile, discovery: unknown, timeoutMs
101
101
  const deadline = Date.now() + timeoutMs;
102
102
  let snap: Record<string, unknown> | null = null;
103
103
  for (;;) {
104
- const res = await http(() => driver.get(`${profile.runsPath}/${encodeURIComponent(runId)}`));
104
+ const res = await http(() => driver.get(`${profile.runsPath}/${profile.idSegment(runId)}`));
105
105
  snap = res?.status === 200 && isRecord(res.json) ? res.json : null;
106
106
  if (snap !== null && TERMINAL.has(String(snap['status']))) break;
107
107
  if (Date.now() > deadline) {
108
- await http(() => driver.post(`${profile.runsPath}/${encodeURIComponent(runId)}/cancel`, {}));
108
+ await http(() => driver.post(`${profile.runsPath}/${profile.idSegment(runId)}/cancel`, {}));
109
109
  return { kind: 'skip', disposition: 'blocked', reason: `the budgeted run did not reach a terminal status within ${timeoutMs} ms (last: ${snap === null ? 'unreadable' : String(snap['status'])})` };
110
110
  }
111
111
  await new Promise((r) => setTimeout(r, 250));
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Static walkers over envelope payload schemas, shared by the v2 static twins
3
+ * `v2-envelope-tier-one-subset-static` and `v2-envelope-variant-discriminator-static`.
4
+ *
5
+ * The walk rules are the v1 rules unchanged (RFC 0030 §B, RFC 0031 §A); what
6
+ * moved at major 2 is where the schemas live (`schemas/v2/envelopes/`) and
7
+ * where a host's kind catalog is advertised (`supportedEnvelopes.kinds`, a
8
+ * family record, instead of a root `string[]`).
9
+ *
10
+ * Every function here is pure and is proven in both directions in
11
+ * `envelope-schema-static.test.ts`: a conforming schema yields no violation,
12
+ * and each defect yields the one it names.
13
+ */
14
+
15
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { SCHEMAS_DIR } from './paths.js';
18
+
19
+ export interface SchemaViolation { readonly path: string; readonly rule: string; readonly detail?: string }
20
+
21
+ const isRecord = (v: unknown): v is Record<string, unknown> => v !== null && typeof v === 'object' && !Array.isArray(v);
22
+
23
+ /** The v2 envelope payload schema directory. */
24
+ export const V2_ENVELOPES_DIR = join(SCHEMAS_DIR, 'v2', 'envelopes');
25
+
26
+ /** The four kinds every engine recognizes (`events.md` §"AI envelopes"). */
27
+ export const UNIVERSAL_KINDS = ['clarification.request', 'schema.request', 'schema.response', 'error'] as const;
28
+
29
+ /** A v2 envelope payload schema by kind, or `null` when the corpus has none. */
30
+ export function loadV2EnvelopeSchema(kind: string): Record<string, unknown> | null {
31
+ const p = join(V2_ENVELOPES_DIR, `${kind}.schema.json`);
32
+ if (!existsSync(p)) return null;
33
+ const v: unknown = JSON.parse(readFileSync(p, 'utf8'));
34
+ return isRecord(v) ? v : null;
35
+ }
36
+
37
+ /** Every kind with a payload schema under `schemas/v2/envelopes/`. */
38
+ export function listV2EnvelopeKinds(): string[] {
39
+ if (!existsSync(V2_ENVELOPES_DIR)) return [];
40
+ return readdirSync(V2_ENVELOPES_DIR).filter((f) => f.endsWith('.schema.json')).map((f) => f.replace(/\.schema\.json$/, '')).sort();
41
+ }
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // RFC 0030 §B — the Tier-1 structured-output subset.
45
+ // ---------------------------------------------------------------------------
46
+
47
+ /** Keywords that fail across more than one Tier-1 vendor (Gemini drops `oneOf` silently). Checked in both modes. */
48
+ const LOAD_BEARING_KEYWORDS = ['oneOf', 'allOf', 'not', 'if', 'then', 'else', 'dependencies', 'prefixItems', 'propertyNames'] as const;
49
+ export const MAX_DEPTH = 5;
50
+ export const MAX_PROPERTIES = 100;
51
+
52
+ const hasType = (schema: Record<string, unknown>, t: string): boolean => schema['type'] === t || (Array.isArray(schema['type']) && (schema['type'] as unknown[]).includes(t));
53
+
54
+ /**
55
+ * Walk a payload schema for Tier-1 subset violations.
56
+ *
57
+ * `load-bearing`: only the rules that fail across several vendors — the floor
58
+ * every corpus schema is held to. `strict`: every rule of the OpenAI-strict ∩
59
+ * Anthropic-strict ∩ Gemini intersection, applied only when a host advertises
60
+ * `envelopes.tierOneSubsetCompliance: "strict"`.
61
+ */
62
+ export function tierOneViolations(schema: Record<string, unknown>, mode: 'load-bearing' | 'strict'): SchemaViolation[] {
63
+ const out: SchemaViolation[] = [];
64
+ const count = { n: 0 };
65
+ walkTierOne(schema, '#', 0, count, out, mode);
66
+ if (count.n > MAX_PROPERTIES) out.push({ path: '#', rule: 'max-property-count-100-exceeded', detail: `count=${count.n}` });
67
+ return out;
68
+ }
69
+
70
+ function walkTierOne(schema: Record<string, unknown>, path: string, depth: number, count: { n: number }, out: SchemaViolation[], mode: 'load-bearing' | 'strict'): void {
71
+ if (depth > MAX_DEPTH) { out.push({ path, rule: 'max-nesting-depth-5', detail: `depth=${depth}` }); return; }
72
+ for (const kw of LOAD_BEARING_KEYWORDS) if (kw in schema) out.push({ path, rule: 'forbidden-keyword', detail: kw });
73
+ if (Array.isArray(schema['anyOf'])) {
74
+ (schema['anyOf'] as unknown[]).forEach((b, i) => { if (isRecord(b)) walkTierOne(b, `${path}/anyOf/${i}`, depth + 1, count, out, mode); });
75
+ }
76
+ if (hasType(schema, 'object')) {
77
+ if (mode === 'strict' && schema['additionalProperties'] !== false) out.push({ path, rule: 'additionalProperties-must-be-false-on-object-strict-only' });
78
+ const props = isRecord(schema['properties']) ? schema['properties'] : {};
79
+ const required = Array.isArray(schema['required']) ? (schema['required'] as unknown[]) : [];
80
+ for (const [name, sub] of Object.entries(props)) {
81
+ count.n++;
82
+ if (mode === 'strict' && !required.includes(name)) out.push({ path: `${path}/properties/${name}`, rule: 'property-not-in-required-strict-mode-only' });
83
+ if (isRecord(sub)) walkTierOne(sub, `${path}/properties/${name}`, depth + 1, count, out, mode);
84
+ }
85
+ }
86
+ if (mode === 'strict') {
87
+ const bounded: Array<[boolean, readonly string[], string]> = [
88
+ [hasType(schema, 'string'), ['minLength', 'maxLength', 'pattern', 'format'], 'forbidden-string-constraint-strict-only'],
89
+ [hasType(schema, 'number') || hasType(schema, 'integer'), ['minimum', 'maximum', 'multipleOf'], 'forbidden-number-constraint-strict-only'],
90
+ [hasType(schema, 'array'), ['minItems', 'maxItems', 'uniqueItems'], 'forbidden-array-constraint-strict-only'],
91
+ ];
92
+ for (const [applies, kws, rule] of bounded) if (applies) for (const kw of kws) if (kw in schema) out.push({ path, rule, detail: kw });
93
+ }
94
+ if (hasType(schema, 'array') && isRecord(schema['items'])) walkTierOne(schema['items'], `${path}/items`, depth + 1, count, out, mode);
95
+ const defs = isRecord(schema['$defs']) ? schema['$defs'] : {};
96
+ for (const [name, sub] of Object.entries(defs)) if (isRecord(sub)) walkTierOne(sub, `${path}/$defs/${name}`, depth + 1, count, out, mode);
97
+ }
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // RFC 0031 §A — variant payloads: no `oneOf`; every `anyOf` branch discriminated.
101
+ // ---------------------------------------------------------------------------
102
+
103
+ /** `oneOf` anywhere in a payload schema. */
104
+ export function oneOfViolations(schema: unknown, path = '#'): SchemaViolation[] {
105
+ const out: SchemaViolation[] = [];
106
+ const walk = (s: unknown, p: string): void => {
107
+ if (Array.isArray(s)) { s.forEach((x, i) => walk(x, `${p}/${i}`)); return; }
108
+ if (!isRecord(s)) return;
109
+ if ('oneOf' in s) out.push({ path: p, rule: 'oneOf-forbidden', detail: 'use anyOf with a single-string-enum discriminator (RFC 0031 §A)' });
110
+ for (const [k, v] of Object.entries(s)) walk(v, `${p}/${k}`);
111
+ };
112
+ walk(schema, path);
113
+ return out;
114
+ }
115
+
116
+ /**
117
+ * A branch is discriminated when one of its `required` properties is
118
+ * `type: string` with an `enum` of exactly one value. A `$ref` branch is
119
+ * accepted unresolved, as at v1: the referenced shape is walked where it is
120
+ * defined, and a version-selected union (one branch per `schemaVersion`, as
121
+ * `ui.a2ui-surface` declares) is not a payload-field discrimination at all.
122
+ */
123
+ export function branchDiscriminated(branch: unknown): boolean {
124
+ if (!isRecord(branch)) return false;
125
+ if ('$ref' in branch) return true;
126
+ const required = Array.isArray(branch['required']) ? (branch['required'] as unknown[]) : [];
127
+ const props = isRecord(branch['properties']) ? branch['properties'] : {};
128
+ return required.some((name) => {
129
+ const p = typeof name === 'string' ? props[name] : undefined;
130
+ return isRecord(p) && p['type'] === 'string' && Array.isArray(p['enum']) && (p['enum'] as unknown[]).length === 1;
131
+ });
132
+ }
133
+
134
+ /** Whether a branch can match a JSON object: an explicit object type, a `$ref`, or object keywords with no type. */
135
+ function admitsObject(branch: unknown): boolean {
136
+ if (!isRecord(branch)) return false;
137
+ if ('$ref' in branch) return true;
138
+ const t = branch['type'];
139
+ if (t === undefined) return 'properties' in branch || 'required' in branch;
140
+ return t === 'object' || (Array.isArray(t) && t.includes('object'));
141
+ }
142
+
143
+ /**
144
+ * Every `anyOf` branch, at any depth, that carries no single-string-enum
145
+ * discriminator — in a VARIANT union, one where two or more branches can be
146
+ * objects. A union with at most one object branch (`string | binding`,
147
+ * `string | number | boolean | array | binding`, as `ui.a2ui-surface`
148
+ * payload v2 declares) is a type union: JSON type alone selects the branch, no
149
+ * field is inspected, and a scalar branch has no property to discriminate on.
150
+ * RFC 0031 §A governs variant payloads; it is not read as forbidding these.
151
+ */
152
+ export function anyOfDiscriminatorViolations(schema: unknown, path = '#'): SchemaViolation[] {
153
+ const out: SchemaViolation[] = [];
154
+ const walk = (s: unknown, p: string): void => {
155
+ if (Array.isArray(s)) { s.forEach((x, i) => walk(x, `${p}/${i}`)); return; }
156
+ if (!isRecord(s)) return;
157
+ if (Array.isArray(s['anyOf']) && (s['anyOf'] as unknown[]).filter(admitsObject).length >= 2) {
158
+ (s['anyOf'] as unknown[]).forEach((b, i) => {
159
+ if (admitsObject(b) && !branchDiscriminated(b)) out.push({ path: `${p}/anyOf/${i}`, rule: 'anyOf-branch-missing-discriminator', detail: 'a branch MUST declare a single-string-enum discriminator property in required (RFC 0031 §A)' });
160
+ });
161
+ }
162
+ for (const [k, v] of Object.entries(s)) walk(v, `${p}/${k}`);
163
+ };
164
+ walk(schema, path);
165
+ return out;
166
+ }
@@ -75,7 +75,7 @@ export async function drive(profile: MajorProfile, doc: unknown): Promise<FacetR
75
75
 
76
76
  // A host that accepted the create started a run. Stop it, so a failing row leaves none behind.
77
77
  const runId = (res.json as { runId?: unknown } | undefined)?.runId;
78
- if (typeof runId === 'string') await http(() => driver.post(`${profile.runsPath}/${encodeURIComponent(runId)}/cancel`, {}));
78
+ if (typeof runId === 'string') await http(() => driver.post(`${profile.runsPath}/${profile.idSegment(runId)}/cancel`, {}));
79
79
 
80
80
  return { kind: 'observed', observation: { facet, create: { status: res.status, code: readErrorCode(res.json) ?? undefined } } };
81
81
  }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * The advertisement-shape witness for the claims-check AI families at major 2:
3
+ * `envelopes`, `limits` (the three envelope caps), `modelCapabilities` and
4
+ * `aiProviders`.
5
+ *
6
+ * A claims-check family is witnessed by what the host says about itself, so
7
+ * every leg here reads the discovery record alone. Each leg checks one facet
8
+ * twice: against `schemas/v2/capabilities.schema.json` (the record seat or the
9
+ * facet's subschema, through `v2RefValidator`) and against the v2 prose rule
10
+ * that gives the facet its meaning. The schema and the prose do not always
11
+ * agree; where they do not, the leg says which one it is enforcing.
12
+ *
13
+ * Not here, on purpose:
14
+ * - `limits.maxRunDurationMs` / `maxLoopIterations` — `run-bounds-witness.ts`;
15
+ * - every runtime rule of these families (retry routing, refusal, substitution,
16
+ * cap enforcement) — they need a mock-provider or envelope-accept seam.
17
+ *
18
+ * Every leg is pure: `(profile, discovery) → outcome`. A family the host does
19
+ * not advertise, or a facet it does not carry, is `inapplicable` — never a
20
+ * pass. Proven in both directions in `family-advert-witness.test.ts`.
21
+ */
22
+
23
+ import type { MajorProfile } from './major-profile.js';
24
+ import { v2RefValidator } from './v2.js';
25
+
26
+ export interface AdvertFinding { readonly ok: boolean; readonly doc: string; readonly message: string }
27
+ export type AdvertOutcome =
28
+ | { readonly kind: 'skip'; readonly disposition: 'inapplicable' | 'blocked'; readonly reason: string }
29
+ | { readonly kind: 'observed'; readonly findings: readonly AdvertFinding[] };
30
+
31
+ const isRecord = (v: unknown): v is Record<string, unknown> => v !== null && typeof v === 'object' && !Array.isArray(v);
32
+ const inapplicable = (reason: string): AdvertOutcome => ({ kind: 'skip', disposition: 'inapplicable', reason });
33
+ const observed = (findings: AdvertFinding[]): AdvertOutcome => ({ kind: 'observed', findings });
34
+ const SCHEMA = 'schemas/v2/capabilities.schema.json';
35
+
36
+ /** Validate a value against a pointer into the v2 capabilities schema. */
37
+ function bySchema(pointer: string, value: unknown, doc: string, what: string): AdvertFinding {
38
+ const r = v2RefValidator(`capabilities.schema.json#${pointer}`)(value);
39
+ return { ok: r.ok, doc, message: `${what} MUST validate against ${SCHEMA}#${pointer}${r.ok ? '' : ` (${r.errors})`}` };
40
+ }
41
+
42
+ /** One leg: given the family record, judge one facet. */
43
+ type Leg = (rec: Record<string, unknown>) => AdvertOutcome;
44
+
45
+ /** Run a leg against a discovery document, gating on the family record. */
46
+ function onFamily(profile: MajorProfile, discovery: unknown, family: string, leg: Leg): AdvertOutcome {
47
+ const rec = profile.family(discovery, family);
48
+ if (rec === null) return inapplicable(`the host does not advertise ${family}`);
49
+ return leg(rec);
50
+ }
51
+
52
+ /** The record as a whole against its seat in the capabilities schema. Catches every v1 shape v2 retired (`supported`, `selfHosted[]`, the per-provider `authModes` map, …). */
53
+ export function recordSchemaLeg(profile: MajorProfile, discovery: unknown, family: 'envelopes' | 'modelCapabilities' | 'aiProviders', doc: string): AdvertOutcome {
54
+ return onFamily(profile, discovery, family, (rec) => observed([bySchema(`/properties/${family}`, rec, doc, `the ${family} record`)]));
55
+ }
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // envelopes — spec/v2/core/events.md §`envelopes`
59
+ // ---------------------------------------------------------------------------
60
+
61
+ const ENV_DOC = 'events.md §envelopes';
62
+ const ENV = '/properties/envelopes/properties';
63
+
64
+ /** `envelopes.reasoning`: an object whose `promptDirective` is one of three values; no `supported` seat at v2. */
65
+ export const envelopesReasoningLeg = (p: MajorProfile, d: unknown): AdvertOutcome => onFamily(p, d, 'envelopes', (rec) => {
66
+ const r = rec['reasoning'];
67
+ if (r === undefined) return inapplicable('the host does not advertise envelopes.reasoning (optional)');
68
+ const out = [bySchema(`${ENV}/reasoning`, r, ENV_DOC, 'envelopes.reasoning')];
69
+ if (isRecord(r) && r['promptDirective'] !== undefined) {
70
+ out.push({ ok: ['mandatory', 'advisory', 'off'].includes(String(r['promptDirective'])), doc: ENV_DOC, message: `envelopes.reasoning.promptDirective MUST be mandatory, advisory or off (got ${JSON.stringify(r['promptDirective'])})` });
71
+ }
72
+ return observed(out);
73
+ });
74
+
75
+ /** `envelopes.tierOneSubsetCompliance`: one of strict, warn, off. */
76
+ export const envelopesTierOneLeg = (p: MajorProfile, d: unknown): AdvertOutcome => onFamily(p, d, 'envelopes', (rec) => {
77
+ const v = rec['tierOneSubsetCompliance'];
78
+ if (v === undefined) return inapplicable('the host does not advertise envelopes.tierOneSubsetCompliance (optional)');
79
+ return observed([bySchema(`${ENV}/tierOneSubsetCompliance`, v, ENV_DOC, 'envelopes.tierOneSubsetCompliance')]);
80
+ });
81
+
82
+ /** The six reliability events by their v2 names, and the four v1 dotted names v2 accepts as aliases until 3.0 (RFC 0228 §G). */
83
+ export const RELIABILITY_EVENTS = ['envelope.retry-attempted', 'envelope.retry-exhausted', 'envelope.refusal', 'envelope.truncated', 'envelope.nl-to-format-engaged', 'envelope.recovery-applied'] as const;
84
+ export const RELIABILITY_ALIASES: Readonly<Record<string, string>> = {
85
+ 'envelope.retry.attempted': 'envelope.retry-attempted',
86
+ 'envelope.retry.exhausted': 'envelope.retry-exhausted',
87
+ 'envelope.nlToFormat.engaged': 'envelope.nl-to-format-engaged',
88
+ 'envelope.recovery.applied': 'envelope.recovery-applied',
89
+ };
90
+ /** A listed reliability event by its v2 name: an alias folds to its hyphenated equivalent; anything else is returned unchanged. */
91
+ export const canonicalReliabilityEvent = (e: string): string => RELIABILITY_ALIASES[e] ?? e;
92
+
93
+ /**
94
+ * `envelopes.reliability`: `events[]` names only reliability events (v2 name
95
+ * or v1 alias), and — after folding aliases — includes the two MUST-tier ones;
96
+ * `maxRetryAttempts` is an integer in [1, 16].
97
+ */
98
+ export const envelopesReliabilityLeg = (p: MajorProfile, d: unknown): AdvertOutcome => onFamily(p, d, 'envelopes', (rec) => {
99
+ const r = rec['reliability'];
100
+ if (r === undefined) return inapplicable('the host does not advertise envelopes.reliability (optional)');
101
+ const out = [bySchema(`${ENV}/reliability`, r, ENV_DOC, 'envelopes.reliability')];
102
+ const events = isRecord(r) ? r['events'] : undefined;
103
+ if (!Array.isArray(events)) {
104
+ out.push({ ok: false, doc: ENV_DOC, message: `a host advertising envelopes.reliability MUST list the events it emits in reliability.events[] (got ${JSON.stringify(events)})` });
105
+ return observed(out);
106
+ }
107
+ const canonical = new Set(events.filter((e): e is string => typeof e === 'string').map(canonicalReliabilityEvent));
108
+ for (const e of events) {
109
+ const name = typeof e === 'string' ? canonicalReliabilityEvent(e) : undefined;
110
+ out.push({ ok: name !== undefined && (RELIABILITY_EVENTS as readonly string[]).includes(name), doc: ENV_DOC, message: `reliability.events[] MUST name only reliability events, by v2 name or v1 alias (got ${JSON.stringify(e)})` });
111
+ }
112
+ for (const must of ['envelope.retry-exhausted', 'envelope.refusal']) {
113
+ out.push({ ok: canonical.has(must), doc: ENV_DOC, message: `a host advertising envelopes.reliability MUST emit and list ${must} (aliases folded; listed: ${JSON.stringify(events)})` });
114
+ }
115
+ return observed(out);
116
+ });
117
+
118
+ /** `envelopes.reliability.completion`: `distinguishesTruncation` is a boolean; `truncationBudgetMultiplier` a number in [1, 8]. */
119
+ export const envelopesCompletionLeg = (p: MajorProfile, d: unknown): AdvertOutcome => onFamily(p, d, 'envelopes', (rec) => {
120
+ const r = rec['reliability'];
121
+ const c = isRecord(r) ? r['completion'] : undefined;
122
+ if (c === undefined) return inapplicable('the host does not advertise envelopes.reliability.completion (optional)');
123
+ const out = [bySchema(`${ENV}/reliability/properties/completion`, c, ENV_DOC, 'envelopes.reliability.completion')];
124
+ const dt = isRecord(c) ? c['distinguishesTruncation'] : undefined;
125
+ out.push({ ok: typeof dt === 'boolean', doc: ENV_DOC, message: `reliability.completion.distinguishesTruncation MUST be a boolean (got ${JSON.stringify(dt)})` });
126
+ return observed(out);
127
+ });
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // limits — spec/v2/core/runs.md §Limits: the three envelope caps
131
+ // ---------------------------------------------------------------------------
132
+
133
+ export const ENVELOPE_CAPS = ['clarificationRounds', 'schemaRounds', 'envelopesPerTurn'] as const;
134
+ export type EnvelopeCap = (typeof ENVELOPE_CAPS)[number];
135
+
136
+ /** `limits.<cap>`: present (runs.md: "`limits` always carries" it) and a non-negative integer. */
137
+ export const limitsCapLeg = (p: MajorProfile, d: unknown, cap: EnvelopeCap): AdvertOutcome => onFamily(p, d, 'limits', (rec) => {
138
+ const v = rec[cap];
139
+ const doc = 'runs.md §Limits';
140
+ if (v === undefined) return observed([{ ok: false, doc, message: `limits MUST always carry ${cap} (absent)` }]);
141
+ return observed([
142
+ bySchema(`/properties/limits/properties/${cap}`, v, doc, `limits.${cap}`),
143
+ { ok: Number.isInteger(v) && (v as number) >= 0, doc, message: `limits.${cap} MUST be a non-negative integer (got ${JSON.stringify(v)})` },
144
+ ]);
145
+ });
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // modelCapabilities — spec/v2/core/host-services.md §modelCapabilities
149
+ // ---------------------------------------------------------------------------
150
+
151
+ /** The spec-reserved identifiers (RFC 0031 §C, RFC 0055), as listed by the schema seat's description. */
152
+ export const RESERVED_MODEL_CAPABILITIES = ['structured-output', 'discriminator-enum', 'long-context', 'reasoning', 'function-calling', 'vision-input', 'audio-input', 'audio-output', 'image-output'] as const;
153
+ const HOST_PRIVATE = /^x-host-[a-z][a-z0-9-]*-[a-z][a-z0-9-]*$/;
154
+ const MC_DOC = 'host-services.md §modelCapabilities';
155
+
156
+ /** `modelCapabilities.advertised`: unique strings, each spec-reserved or prefixed `x-host-<host>-`. */
157
+ export const modelCapabilitiesAdvertisedLeg = (p: MajorProfile, d: unknown): AdvertOutcome => onFamily(p, d, 'modelCapabilities', (rec) => {
158
+ const a = rec['advertised'];
159
+ if (a === undefined) return inapplicable('the host does not advertise modelCapabilities.advertised');
160
+ const out = [bySchema('/properties/modelCapabilities/properties/advertised', a, MC_DOC, 'modelCapabilities.advertised')];
161
+ if (Array.isArray(a)) {
162
+ for (const id of a) {
163
+ const s = typeof id === 'string' ? id : '';
164
+ out.push({ ok: (RESERVED_MODEL_CAPABILITIES as readonly string[]).includes(s) || HOST_PRIVATE.test(s), doc: MC_DOC, message: `a modelCapabilities.advertised identifier MUST be spec-reserved (${RESERVED_MODEL_CAPABILITIES.join(', ')}) or host-private, prefixed x-host-<host>- (got ${JSON.stringify(id)})` });
165
+ }
166
+ }
167
+ return observed(out);
168
+ });
169
+
170
+ /** `modelCapabilities.substitutionSupported`: a boolean. */
171
+ export const modelCapabilitiesSubstitutionLeg = (p: MajorProfile, d: unknown): AdvertOutcome => onFamily(p, d, 'modelCapabilities', (rec) => {
172
+ const v = rec['substitutionSupported'];
173
+ if (v === undefined) return inapplicable('the host does not advertise modelCapabilities.substitutionSupported (absent means no substitution)');
174
+ return observed([bySchema('/properties/modelCapabilities/properties/substitutionSupported', v, MC_DOC, 'modelCapabilities.substitutionSupported')]);
175
+ });
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // aiProviders — spec/v2/core/host-services.md §aiProviders (v1 facets narrowed by C2.11)
179
+ // ---------------------------------------------------------------------------
180
+
181
+ const AP_DOC = 'host-services.md §aiProviders';
182
+ const AP = '/properties/aiProviders/properties';
183
+
184
+ /** The modes `host-services.md` §aiProviders names. The v2 schema seat is `string[]` with no enum; the vocabulary is the prose's. */
185
+
186
+ /** `aiProviders.selfHosted`: one boolean at v2 (the v1 `selfHosted[]` id list retired, C2.11). */
187
+ export const aiProvidersSelfHostedLeg = (p: MajorProfile, d: unknown): AdvertOutcome => onFamily(p, d, 'aiProviders', (rec) => {
188
+ const v = rec['selfHosted'];
189
+ if (v === undefined) return inapplicable('the host does not advertise aiProviders.selfHosted');
190
+ return observed([bySchema(`${AP}/selfHosted`, v, AP_DOC, 'aiProviders.selfHosted (a boolean at v2, not the v1 id list)')]);
191
+ });
192
+
193
+ /** `aiProviders.authModes`: one flat list of modes at v2 (the v1 per-provider map retired, C2.11). */
194
+ export const aiProvidersAuthModesShapeLeg = (p: MajorProfile, d: unknown): AdvertOutcome => onFamily(p, d, 'aiProviders', (rec) => {
195
+ const v = rec['authModes'];
196
+ if (v === undefined) return inapplicable('the host does not advertise aiProviders.authModes');
197
+ return observed([bySchema(`${AP}/authModes`, v, AP_DOC, 'aiProviders.authModes (one flat list at v2, not the v1 per-provider map)')]);
198
+ });
199
+
200
+ /** `aiProviders.maxInlineMediaBytes`: an integer ≥ 1. */
201
+ export const aiProvidersInlineMediaLeg = (p: MajorProfile, d: unknown): AdvertOutcome => onFamily(p, d, 'aiProviders', (rec) => {
202
+ const v = rec['maxInlineMediaBytes'];
203
+ if (v === undefined) return inapplicable('the host does not advertise aiProviders.maxInlineMediaBytes (default 256 KiB)');
204
+ return observed([bySchema(`${AP}/maxInlineMediaBytes`, v, AP_DOC, 'aiProviders.maxInlineMediaBytes')]);
205
+ });
206
+
207
+ /** `aiProviders.promptPrefixCache`: one boolean at v2 (the v1 `{ supported, providers[] }` object retired, C2.11). */
208
+ export const aiProvidersPromptPrefixCacheLeg = (p: MajorProfile, d: unknown): AdvertOutcome => onFamily(p, d, 'aiProviders', (rec) => {
209
+ const v = rec['promptPrefixCache'];
210
+ if (v === undefined) return inapplicable('the host does not advertise aiProviders.promptPrefixCache');
211
+ return observed([bySchema(`${AP}/promptPrefixCache`, v, AP_DOC, 'aiProviders.promptPrefixCache (a boolean at v2, not the v1 object)')]);
212
+ });