@openwop/openwop-conformance 2.45.14 → 2.45.16

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 (82) hide show
  1. package/CHANGELOG.md +45 -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 +2668 -4
  6. package/scenario-majors.json +164 -2
  7. package/schemas/CORPUS-STAMP.json +13 -13
  8. package/src/lib/artifact-schema-url-witness.ts +83 -0
  9. package/src/lib/backpressure-witness.ts +2 -2
  10. package/src/lib/budget-witness.ts +3 -3
  11. package/src/lib/dispatch-witness.ts +212 -0
  12. package/src/lib/envelope-schema-static.ts +166 -0
  13. package/src/lib/exhaustion-facet-witness.ts +1 -1
  14. package/src/lib/family-advert-witness.ts +411 -0
  15. package/src/lib/fixture-run-observer.ts +97 -0
  16. package/src/lib/i18n-double.ts +99 -0
  17. package/src/lib/i18n-negotiation-witness.ts +173 -0
  18. package/src/lib/localized-content-witness.ts +49 -0
  19. package/src/lib/model-capability-insufficient-witness.ts +121 -0
  20. package/src/lib/prompt-double.ts +217 -0
  21. package/src/lib/prompt-events-witness.ts +211 -0
  22. package/src/lib/prompt-library-witness.ts +406 -0
  23. package/src/lib/provider-usage-witness.ts +63 -0
  24. package/src/lib/run-bounds-witness.ts +111 -0
  25. package/src/lib/run-graph-double.ts +144 -0
  26. package/src/lib/scratch-host.ts +4 -1
  27. package/src/lib/subworkflow-witness.ts +192 -0
  28. package/src/lib/table-schema-witness.ts +3 -1
  29. package/src/scenarios/v2-agent-runtime-advertisement.test.ts +51 -0
  30. package/src/scenarios/v2-ai-envelope-advertisement.test.ts +43 -0
  31. package/src/scenarios/v2-ai-envelope-shape-static.test.ts +93 -0
  32. package/src/scenarios/v2-ai-providers-advertisement.test.ts +94 -0
  33. package/src/scenarios/v2-artifact-created-static.test.ts +92 -0
  34. package/src/scenarios/v2-artifact-type-schema-url.test.ts +44 -0
  35. package/src/scenarios/v2-artifact-types-advertisement.test.ts +64 -0
  36. package/src/scenarios/v2-authorization-advertisement.test.ts +66 -0
  37. package/src/scenarios/v2-channel-presence-advertisement.test.ts +41 -0
  38. package/src/scenarios/v2-conversation-turn-model-provenance-advertisement.test.ts +41 -0
  39. package/src/scenarios/v2-conversation-turn-model-provenance.test.ts +110 -0
  40. package/src/scenarios/v2-credentials-advertisement.test.ts +66 -0
  41. package/src/scenarios/v2-data-residency-admission.test.ts +98 -0
  42. package/src/scenarios/v2-data-residency-advertisement.test.ts +52 -0
  43. package/src/scenarios/v2-dead-letter-advertisement.test.ts +54 -0
  44. package/src/scenarios/v2-dispatch-input-mapping.test.ts +70 -0
  45. package/src/scenarios/v2-dispatch-loop.test.ts +80 -0
  46. package/src/scenarios/v2-envelope-contracts-advertisement.test.ts +40 -0
  47. package/src/scenarios/v2-envelope-reasoning-shape.test.ts +81 -0
  48. package/src/scenarios/v2-envelope-tier-one-subset-static.test.ts +77 -0
  49. package/src/scenarios/v2-envelope-variant-discriminator-static.test.ts +57 -0
  50. package/src/scenarios/v2-envelopes-advertisement.test.ts +85 -0
  51. package/src/scenarios/v2-export-bundle-static.test.ts +108 -0
  52. package/src/scenarios/v2-i18n-negotiation.test.ts +91 -0
  53. package/src/scenarios/v2-limits-envelope-caps-advertisement.test.ts +61 -0
  54. package/src/scenarios/v2-localized-content-delivery.test.ts +136 -0
  55. package/src/scenarios/v2-media-inline-cap-static.test.ts +59 -0
  56. package/src/scenarios/v2-model-capabilities-advertisement.test.ts +61 -0
  57. package/src/scenarios/v2-model-capability-insufficient.test.ts +63 -0
  58. package/src/scenarios/v2-multi-party-conversation-advertisement.test.ts +52 -0
  59. package/src/scenarios/v2-node-pack-runtimes-advertisement.test.ts +52 -0
  60. package/src/scenarios/v2-nondeterminism-policy-advertisement.test.ts +41 -0
  61. package/src/scenarios/v2-portability-advertisement.test.ts +63 -0
  62. package/src/scenarios/v2-prompt-all-four-kinds-events.test.ts +69 -0
  63. package/src/scenarios/v2-prompt-end-to-end-events.test.ts +71 -0
  64. package/src/scenarios/v2-prompt-library-advertisement.test.ts +40 -0
  65. package/src/scenarios/v2-prompt-list-and-fetch.test.ts +103 -0
  66. package/src/scenarios/v2-prompt-mutable-lifecycle.test.ts +130 -0
  67. package/src/scenarios/v2-prompt-pack-install.test.ts +78 -0
  68. package/src/scenarios/v2-prompt-render-deterministic.test.ts +87 -0
  69. package/src/scenarios/v2-prompt-resolution-chain-event.test.ts +46 -0
  70. package/src/scenarios/v2-prompt-template-shape.test.ts +112 -0
  71. package/src/scenarios/v2-provider-usage-advertisement.test.ts +61 -0
  72. package/src/scenarios/v2-provider-usage-emission.test.ts +50 -0
  73. package/src/scenarios/v2-provider-usage-payload-static.test.ts +92 -0
  74. package/src/scenarios/v2-purpose-propagation-advertisement.test.ts +52 -0
  75. package/src/scenarios/v2-run-execution-bounds.test.ts +67 -0
  76. package/src/scenarios/v2-scheduling-advertisement.test.ts +55 -0
  77. package/src/scenarios/v2-self-hosted-runner-advertisement.test.ts +52 -0
  78. package/src/scenarios/v2-self-hosted-runner-frames-static.test.ts +121 -0
  79. package/src/scenarios/v2-sub-workflow-advertisement.test.ts +53 -0
  80. package/src/scenarios/v2-subworkflow-input-mapping.test.ts +74 -0
  81. package/src/scenarios/v2-subworkflow-linkage.test.ts +76 -0
  82. package/src/scenarios/v2-ui-plugins-advertisement.test.ts +85 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * v2 — the `credentials` advertisement (`spec/v2/core/oauth.md` §Credentials;
3
+ * RFC 0046). The v1 twin is the advertisement legs of
4
+ * `credentials-capability-shape`; the legs live in
5
+ * `lib/family-advert-witness.ts`.
6
+ *
7
+ * record the record validates against its capabilities-schema seat;
8
+ * scopes `scopes` is a subset of user, workspace, tenant (a scope
9
+ * outside it is refused `credential_scope_unsupported`);
10
+ * rotation `rotation` is `none` or `two-key-overlap`.
11
+ *
12
+ * Not here: resolution, scope refusal, rotation overlap and redaction need a
13
+ * credential seam (`credential-payload-redaction` has no v2 seam).
14
+ *
15
+ * Dispositions: discovery unreadable ⇒ `blocked`; `credentials` absent, or the
16
+ * facet a leg reads absent ⇒ `inapplicable`.
17
+ *
18
+ * @see spec/v2/core/oauth.md §Credentials
19
+ */
20
+
21
+ import { describe, it, expect } from 'vitest';
22
+ import { v2Discovery, familyAdvertised } from '../lib/v2.js';
23
+ import { softSkip } from '../lib/soft-skip.js';
24
+ import { req } from '../lib/requirement-ids.js';
25
+ import { majorProfile } from '../lib/major-profile.js';
26
+ import { credentialsRotationLeg, credentialsScopesLeg, recordSchemaLeg } from '../lib/family-advert-witness.js';
27
+
28
+ const PROFILE = majorProfile(2);
29
+ const DOC = 'oauth.md §Credentials';
30
+ const UNREADABLE = 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0';
31
+ const ID_RECORD = 'openwop.requirement.credentials.advert-record-schema';
32
+ const ID_SCOPES_SUBSET = 'openwop.requirement.credentials.scopes-subset';
33
+ const ID_ROTATION_VALUE = 'openwop.requirement.credentials.rotation-value';
34
+
35
+ async function discovery(): Promise<Record<string, unknown> | null> {
36
+ try { return await v2Discovery(); } catch { return null; }
37
+ }
38
+
39
+ describe('v2 credentials advertisement (oauth.md §Credentials)', () => {
40
+ it('the credentials record validates against its capabilities-schema seat', async () => {
41
+ const doc = await discovery();
42
+ if (!doc) return softSkip('blocked', UNREADABLE);
43
+ if (!(await familyAdvertised('credentials'))) return softSkip('inapplicable', 'the host does not advertise credentials');
44
+ const out = recordSchemaLeg(PROFILE, doc, 'credentials', DOC);
45
+ if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
46
+ for (const x of out.findings) expect(x.ok, req(ID_RECORD, x.doc, x.message)).toBe(true);
47
+ });
48
+
49
+ it('credentials.scopes is a subset of user, workspace, tenant', async () => {
50
+ const doc = await discovery();
51
+ if (!doc) return softSkip('blocked', UNREADABLE);
52
+ if (!(await familyAdvertised('credentials'))) return softSkip('inapplicable', 'the host does not advertise credentials');
53
+ const out = credentialsScopesLeg(PROFILE, doc);
54
+ if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
55
+ for (const x of out.findings) expect(x.ok, req(ID_SCOPES_SUBSET, x.doc, x.message)).toBe(true);
56
+ });
57
+
58
+ it('credentials.rotation is none or two-key-overlap', async () => {
59
+ const doc = await discovery();
60
+ if (!doc) return softSkip('blocked', UNREADABLE);
61
+ if (!(await familyAdvertised('credentials'))) return softSkip('inapplicable', 'the host does not advertise credentials');
62
+ const out = credentialsRotationLeg(PROFILE, doc);
63
+ if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
64
+ for (const x of out.findings) expect(x.ok, req(ID_ROTATION_VALUE, x.doc, x.message)).toBe(true);
65
+ });
66
+ });
@@ -0,0 +1,98 @@
1
+ /**
2
+ * v2 — `dataResidency` admission control (`spec/v2/core/runs.md` §Refusals and
3
+ * §`dataResidency`). The v1 twin is `data-residency-admission`.
4
+ *
5
+ * A host advertising `dataResidency` MUST honour or reject a `residency`
6
+ * constraint on `POST /runs`, and MUST NOT accept and ignore one:
7
+ *
8
+ * reject a region outside `dataResidency.regions` answers
9
+ * `422 residency_unavailable` and creates no run;
10
+ * accept a region inside `dataResidency.regions` is not refused for its
11
+ * residency: the create answers `201`.
12
+ *
13
+ * The legs drive the normative create route only; no seam. The refused region
14
+ * is made up by the suite and checked against the advertised list, so it can
15
+ * never collide with a real one. The accept leg posts the `conformance-noop`
16
+ * fixture workflow; a host that does not install it answers 404 and the leg is
17
+ * `inapplicable` rather than a pass. Physical confinement is an operator SHOULD
18
+ * and cannot be observed on the wire.
19
+ *
20
+ * Dispositions: discovery unreadable ⇒ `blocked`; `dataResidency` absent or no
21
+ * advertised region ⇒ `inapplicable`; a 429 ⇒ `blocked` (the run budget, not
22
+ * the wire).
23
+ *
24
+ * @see spec/v2/core/runs.md §Refusals
25
+ * @see spec/v2/core/runs.md §`dataResidency`
26
+ */
27
+
28
+ import { describe, it, expect } from 'vitest';
29
+ import { driver, type OpenWOPResponse } from '../lib/driver.js';
30
+ import { v2Discovery, familyAdvertised } from '../lib/v2.js';
31
+ import { softSkip } from '../lib/soft-skip.js';
32
+ import { req } from '../lib/requirement-ids.js';
33
+ import { readErrorCode } from '../lib/error-envelope.js';
34
+
35
+ const DOC = 'spec/v2/core/runs.md §dataResidency';
36
+ const ID_REJECT = 'openwop.requirement.data-residency.reject-unadvertised-region';
37
+ const ID_ACCEPT = 'openwop.requirement.data-residency.accept-advertised-region';
38
+ const NOOP = 'conformance-noop';
39
+ const UNREADABLE = 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0';
40
+
41
+ async function http(fn: () => Promise<OpenWOPResponse>): Promise<OpenWOPResponse | null> {
42
+ try { return await fn(); } catch { return null; }
43
+ }
44
+
45
+ /** The advertised regions, or a skip reason. */
46
+ async function advertisedRegions(): Promise<string[] | { disposition: 'blocked' | 'inapplicable'; reason: string }> {
47
+ let doc: Record<string, unknown> | null;
48
+ try { doc = await v2Discovery(); } catch { doc = null; }
49
+ if (!doc) return { disposition: 'blocked', reason: UNREADABLE };
50
+ const rec = await familyAdvertised('dataResidency');
51
+ if (!rec) return { disposition: 'inapplicable', reason: 'the host does not advertise dataResidency' };
52
+ const regions = Array.isArray(rec['regions']) ? rec['regions'].filter((r): r is string => typeof r === 'string' && r.length > 0) : [];
53
+ if (regions.length === 0) return { disposition: 'inapplicable', reason: 'dataResidency advertises no region (regions is empty or malformed; the record leg owns that)' };
54
+ return regions;
55
+ }
56
+
57
+ /** Cancel a run the host created, so a failing row leaves nothing behind. */
58
+ async function cancelIfCreated(res: OpenWOPResponse): Promise<string | undefined> {
59
+ const runId = (res.json as { runId?: unknown } | undefined)?.runId;
60
+ if (typeof runId !== 'string') return undefined;
61
+ await http(() => driver.post(`/runs/${encodeURIComponent(runId)}/cancel`, {}));
62
+ return runId;
63
+ }
64
+
65
+ describe('v2 dataResidency admission (runs.md §dataResidency)', () => {
66
+ it('a region outside dataResidency.regions answers 422 residency_unavailable and creates no run', async () => {
67
+ const regions = await advertisedRegions();
68
+ if (!Array.isArray(regions)) return softSkip(regions.disposition, regions.reason);
69
+ let bogus = 'zz-conformance-nowhere';
70
+ while (regions.includes(bogus)) bogus += '-x';
71
+
72
+ const res = await http(() => driver.post('/runs', { workflowId: NOOP, residency: { region: bogus } }));
73
+ if (res === null) return softSkip('blocked', 'POST /runs unreachable (fetch failed)');
74
+ if (res.status === 429) return softSkip('blocked', 'POST /runs answered 429 — the run budget, not the wire');
75
+ const runId = await cancelIfCreated(res);
76
+
77
+ expect(res.status, req(ID_REJECT, DOC, `an unadvertised region ("${bogus}") MUST be refused with 422 (got ${res.status} ${readErrorCode(res.json) ?? ''})`.trim())).toBe(422);
78
+ expect(readErrorCode(res.json), req(ID_REJECT, DOC, 'the refusal MUST carry the registered code residency_unavailable')).toBe('residency_unavailable');
79
+ expect(runId, req(ID_REJECT, DOC, `a refused residency request MUST create no run (the response carried runId ${String(runId)})`)).toBeUndefined();
80
+ });
81
+
82
+ it('a region inside dataResidency.regions is accepted', async () => {
83
+ const regions = await advertisedRegions();
84
+ if (!Array.isArray(regions)) return softSkip(regions.disposition, regions.reason);
85
+ const region = regions[0] as string;
86
+
87
+ const res = await http(() => driver.post('/runs', { workflowId: NOOP, residency: { region } }));
88
+ if (res === null) return softSkip('blocked', 'POST /runs unreachable (fetch failed)');
89
+ if (res.status === 429) return softSkip('blocked', 'POST /runs answered 429 — the run budget, not the wire');
90
+ await cancelIfCreated(res);
91
+ if (res.status === 404 && readErrorCode(res.json) !== 'residency_unavailable') {
92
+ return softSkip('inapplicable', `the host does not install the ${NOOP} fixture workflow (404 ${readErrorCode(res.json) ?? ''}); the accept leg needs a workflow to create`.trim());
93
+ }
94
+
95
+ expect(readErrorCode(res.json), req(ID_ACCEPT, DOC, `an advertised region ("${region}") MUST NOT be refused residency_unavailable`)).not.toBe('residency_unavailable');
96
+ expect(res.status, req(ID_ACCEPT, DOC, `a create with an advertised region MUST answer 201 (got ${res.status} ${readErrorCode(res.json) ?? ''})`.trim())).toBe(201);
97
+ });
98
+ });
@@ -0,0 +1,52 @@
1
+ /**
2
+ * v2 — the `dataResidency` advertisement (`spec/v2/core/runs.md` §`dataResidency`): the residency regions a host will honour. The v1 twin is
3
+ * `data-residency-admission`;
4
+ * the legs live in `lib/family-advert-witness.ts` (witness wave 3).
5
+ *
6
+ * record the record validates against its capabilities-schema seat;
7
+ * regions-shape dataResidency.regions holds unique non-empty region codes;
8
+ *
9
+ * Not here: the admission rule (accept an advertised region, refuse any other with `422 residency_unavailable`, create no run) is in `v2-data-residency-admission.test.ts`.
10
+ *
11
+ * Dispositions: discovery unreadable ⇒ `blocked`; `dataResidency` absent, or the
12
+ * facet a leg reads absent ⇒ `inapplicable`.
13
+ *
14
+ * @see spec/v2/core/runs.md §`dataResidency`
15
+ */
16
+
17
+ import { describe, it, expect } from 'vitest';
18
+ import { v2Discovery, familyAdvertised } from '../lib/v2.js';
19
+ import { softSkip } from '../lib/soft-skip.js';
20
+ import { req } from '../lib/requirement-ids.js';
21
+ import { majorProfile } from '../lib/major-profile.js';
22
+ import { dataResidencyRegionsLeg, recordSchemaLeg } from '../lib/family-advert-witness.js';
23
+
24
+ const PROFILE = majorProfile(2);
25
+ const DOC = 'runs.md §dataResidency';
26
+ const UNREADABLE = 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0';
27
+ const ID_RECORD = 'openwop.requirement.data-residency.advert-record-schema';
28
+ const ID_REGIONS_SHAPE = 'openwop.requirement.data-residency.regions-shape';
29
+
30
+ async function discovery(): Promise<Record<string, unknown> | null> {
31
+ try { return await v2Discovery(); } catch { return null; }
32
+ }
33
+
34
+ describe('v2 dataResidency advertisement (runs.md §dataResidency)', () => {
35
+ it('the dataResidency record validates against its capabilities-schema seat', async () => {
36
+ const doc = await discovery();
37
+ if (!doc) return softSkip('blocked', UNREADABLE);
38
+ if (!(await familyAdvertised('dataResidency'))) return softSkip('inapplicable', 'the host does not advertise dataResidency');
39
+ const out = recordSchemaLeg(PROFILE, doc, 'dataResidency', DOC);
40
+ if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
41
+ for (const x of out.findings) expect(x.ok, req(ID_RECORD, x.doc, x.message)).toBe(true);
42
+ });
43
+
44
+ it('dataResidency.regions holds unique non-empty region codes', async () => {
45
+ const doc = await discovery();
46
+ if (!doc) return softSkip('blocked', UNREADABLE);
47
+ if (!(await familyAdvertised('dataResidency'))) return softSkip('inapplicable', 'the host does not advertise dataResidency');
48
+ const out = dataResidencyRegionsLeg(PROFILE, doc);
49
+ if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
50
+ for (const x of out.findings) expect(x.ok, req(ID_REGIONS_SHAPE, x.doc, x.message)).toBe(true);
51
+ });
52
+ });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * v2 — the `deadLetter` advertisement (`spec/v2/core/runs.md` §Dead letters;
3
+ * RFC 0053). The v1 twin is the advertisement legs of
4
+ * `deadletter-capability-shape`; the legs live in
5
+ * `lib/family-advert-witness.ts`.
6
+ *
7
+ * record the record validates against its capabilities-schema seat;
8
+ * retentionDays an integer of at least 1.
9
+ *
10
+ * Not here: routing to the sink, `run.dead-lettered` and fork-eligibility for
11
+ * `retentionDays` need a retry-exhaustion fixture run.
12
+ *
13
+ * Dispositions: discovery unreadable ⇒ `blocked`; `deadLetter` absent, or the
14
+ * facet a leg reads absent ⇒ `inapplicable`.
15
+ *
16
+ * @see spec/v2/core/runs.md §Dead letters
17
+ */
18
+
19
+ import { describe, it, expect } from 'vitest';
20
+ import { v2Discovery, familyAdvertised } from '../lib/v2.js';
21
+ import { softSkip } from '../lib/soft-skip.js';
22
+ import { req } from '../lib/requirement-ids.js';
23
+ import { majorProfile } from '../lib/major-profile.js';
24
+ import { deadLetterRetentionDaysLeg, recordSchemaLeg } from '../lib/family-advert-witness.js';
25
+
26
+ const PROFILE = majorProfile(2);
27
+ const DOC = 'runs.md §Dead letters';
28
+ const UNREADABLE = 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0';
29
+ const ID_RECORD = 'openwop.requirement.dead-letter.advert-record-schema';
30
+ const ID_RETENTION_DAYS = 'openwop.requirement.dead-letter.retention-days';
31
+
32
+ async function discovery(): Promise<Record<string, unknown> | null> {
33
+ try { return await v2Discovery(); } catch { return null; }
34
+ }
35
+
36
+ describe('v2 deadLetter advertisement (runs.md §Dead letters)', () => {
37
+ it('the deadLetter record validates against its capabilities-schema seat', async () => {
38
+ const doc = await discovery();
39
+ if (!doc) return softSkip('blocked', UNREADABLE);
40
+ if (!(await familyAdvertised('deadLetter'))) return softSkip('inapplicable', 'the host does not advertise deadLetter');
41
+ const out = recordSchemaLeg(PROFILE, doc, 'deadLetter', DOC);
42
+ if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
43
+ for (const x of out.findings) expect(x.ok, req(ID_RECORD, x.doc, x.message)).toBe(true);
44
+ });
45
+
46
+ it('deadLetter.retentionDays is an integer of at least 1', async () => {
47
+ const doc = await discovery();
48
+ if (!doc) return softSkip('blocked', UNREADABLE);
49
+ if (!(await familyAdvertised('deadLetter'))) return softSkip('inapplicable', 'the host does not advertise deadLetter');
50
+ const out = deadLetterRetentionDaysLeg(PROFILE, doc);
51
+ if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
52
+ for (const x of out.findings) expect(x.ok, req(ID_RETENTION_DAYS, x.doc, x.message)).toBe(true);
53
+ });
54
+ });
@@ -0,0 +1,70 @@
1
+ /**
2
+ * v2 — `core.dispatch` `inputMapping` (`spec/v2/facets/agents.schema.json`
3
+ * `dispatchMapping`: a host advertising it honors `inputMapping`, "building
4
+ * child inputs from parent variables before dispatch";
5
+ * `schemas/v2/dispatch-config.schema.json` `inputMapping`: "Child receives
6
+ * `inputs[childKey] = parentVariables[parentKey]`", mirroring
7
+ * `core.subWorkflow`'s `inputMapping`). The v1 twin is `dispatch-input-mapping`
8
+ * (HVMAP-1a, HVMAP-1a-null); the legs live in `lib/dispatch-witness.ts`.
9
+ *
10
+ * projection `conformance-dispatch-input-mapping`: the dispatched child's
11
+ * `inputs.childGreeting` is the parent's `parentName` ("Alice");
12
+ * unset `conformance-dispatch-input-mapping-no-default`: `parentName`
13
+ * is unset, so the child's `inputs.childGreeting` is absent —
14
+ * never `null` or a placeholder.
15
+ * The child is found by the parent's `node.dispatched` (`childWorkflowId`), or
16
+ * else the dispatch node's `node.completed` `outputs.childRunId`.
17
+ * NOT ported: the registration-refusal leg (it drives a capability-toggle seam).
18
+ *
19
+ * Dispositions: `agents.dispatchMapping` not `true`, or a fixture not in
20
+ * `fixtures[]` ⇒ `inapplicable`; the child unlocatable on the parent's log ⇒
21
+ * `blocked`; discovery unreadable ⇒ `blocked`.
22
+ *
23
+ * Proven against a double in `lib/dispatch-witness.test.ts`.
24
+ *
25
+ * @see spec/v2/facets/agents.schema.json §dispatchMapping
26
+ * @see schemas/v2/dispatch-config.schema.json §inputMapping
27
+ */
28
+
29
+ import { describe, it, expect } from 'vitest';
30
+ import { familyAdvertised, v2Discovery } from '../lib/v2.js';
31
+ import { softSkip } from '../lib/soft-skip.js';
32
+ import { req } from '../lib/requirement-ids.js';
33
+ import { majorProfile } from '../lib/major-profile.js';
34
+ import type { Outcome, Skip } from '../lib/fixture-run-observer.js';
35
+ import { DISPATCH_NO_DEFAULT_FIXTURE, DISPATCH_PARENT_FIXTURE, driveDispatchMapping, judgeProjection, judgeUnsetProjection, type DispatchObservation } from '../lib/dispatch-witness.js';
36
+
37
+ const PROFILE = majorProfile(2);
38
+ const ID_PROJECTION = 'openwop.requirement.agents.dispatch-input-mapping-projection';
39
+ const ID_UNSET = 'openwop.requirement.agents.dispatch-input-mapping-unset-undefined';
40
+
41
+ const runs = new Map<string, Promise<Skip | DispatchObservation>>();
42
+ async function observation(fixture: string): Promise<DispatchObservation | (() => undefined)> {
43
+ if (!process.env['OPENWOP_BASE_URL']) return () => softSkip('inapplicable', 'no target: OPENWOP_BASE_URL is unset');
44
+ let doc: Record<string, unknown> | null;
45
+ try { doc = await v2Discovery(); } catch { doc = null; }
46
+ if (!doc) return () => softSkip('blocked', 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0');
47
+ if (!(await familyAdvertised('agents'))) return () => softSkip('inapplicable', 'the host does not advertise agents');
48
+ if (!runs.has(fixture)) runs.set(fixture, driveDispatchMapping(PROFILE, doc, fixture));
49
+ const o = await runs.get(fixture)!;
50
+ return 'kind' in o ? () => softSkip(o.disposition, o.reason) : o;
51
+ }
52
+ function assert(id: string, out: Outcome): undefined {
53
+ if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
54
+ for (const x of out.findings) expect(x.ok, req(id, x.doc, x.message)).toBe(true);
55
+ return undefined;
56
+ }
57
+
58
+ describe('v2 dispatch: inputMapping (agents.dispatchMapping)', () => {
59
+ it('inputMapping projects the parent variable into the dispatched child inputs', async () => {
60
+ const o = await observation(DISPATCH_PARENT_FIXTURE);
61
+ if (typeof o === 'function') return o();
62
+ return assert(ID_PROJECTION, judgeProjection(o));
63
+ }, 90_000);
64
+
65
+ it('an unset parent variable projects to undefined: the child input is absent, never null', async () => {
66
+ const o = await observation(DISPATCH_NO_DEFAULT_FIXTURE);
67
+ if (typeof o === 'function') return o();
68
+ return assert(ID_UNSET, judgeUnsetProjection(o));
69
+ }, 90_000);
70
+ });
@@ -0,0 +1,80 @@
1
+ /**
2
+ * v2 — the supervisor loop (`spec/v2/core/execution.md` §`multiAgent`, levels
3
+ * 1 and 5). The v1 twin is `dispatchLoop`; the legs live in
4
+ * `lib/dispatch-witness.ts`. Event names come from the codemap through the
5
+ * profile: `runOrchestrator.decided` is `orchestrator.decided` at major 2.
6
+ *
7
+ * A run of `conformance-dispatch-loop` (`core.orchestrator.supervisor` ⇄
8
+ * `core.dispatch`) is created with `POST /runs` and its log read with
9
+ * `events/poll`:
10
+ * terminates the run completes; each turn records its decision, the last
11
+ * is `terminate`, and none follows it ("`terminate` completes
12
+ * the run");
13
+ * decisions every decision payload is well-formed (the decision checked
14
+ * against the `OrchestratorDecision` branches) and its `agentId`
15
+ * never changes;
16
+ * iteration at `executionModel.version >= 5`, `iteration` is 1-based and
17
+ * +1 per turn.
18
+ * NOT ported: v1's exact `['next-worker', 'terminate']` sequence. That is the
19
+ * fixture catalog's prerequisite on the host's supervisor, not a rule the v2
20
+ * text states.
21
+ *
22
+ * Dispositions: `multiAgent.executionModel` not advertised, or the fixture not
23
+ * in `fixtures[]` ⇒ `inapplicable`; version below 5 ⇒ the iteration leg
24
+ * `inapplicable`; discovery unreadable ⇒ `blocked`.
25
+ *
26
+ * Proven against a double in `lib/dispatch-witness.test.ts`.
27
+ *
28
+ * @see spec/v2/core/execution.md §multiAgent
29
+ */
30
+
31
+ import { describe, it, expect } from 'vitest';
32
+ import { familyAdvertised, v2Discovery, v2RefValidator } from '../lib/v2.js';
33
+ import { softSkip } from '../lib/soft-skip.js';
34
+ import { req } from '../lib/requirement-ids.js';
35
+ import { majorProfile } from '../lib/major-profile.js';
36
+ import type { Outcome, Skip } from '../lib/fixture-run-observer.js';
37
+ import { decisionBranchValidator, driveLoop, judgeDecisions, judgeIteration, judgeLoopTerminates, type LoopObservation } from '../lib/dispatch-witness.js';
38
+
39
+ const PROFILE = majorProfile(2);
40
+ const ID_TERMINATES = 'openwop.requirement.multi-agent.supervisor-loop-terminates';
41
+ const ID_DECISIONS = 'openwop.requirement.multi-agent.decision-recorded-well-formed';
42
+ const ID_ITERATION = 'openwop.requirement.multi-agent.decision-iteration-monotonic';
43
+ const validateDecision = decisionBranchValidator((i) => v2RefValidator(`orchestrator-decision.schema.json#/oneOf/${i}`));
44
+
45
+ let run: Promise<Skip | LoopObservation> | undefined;
46
+ async function observation(): Promise<LoopObservation | (() => undefined)> {
47
+ if (!process.env['OPENWOP_BASE_URL']) return () => softSkip('inapplicable', 'no target: OPENWOP_BASE_URL is unset');
48
+ let doc: Record<string, unknown> | null;
49
+ try { doc = await v2Discovery(); } catch { doc = null; }
50
+ if (!doc) return () => softSkip('blocked', 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0');
51
+ if (!(await familyAdvertised('multiAgent'))) return () => softSkip('inapplicable', 'the host does not advertise multiAgent');
52
+ run ??= driveLoop(PROFILE, doc);
53
+ const o = await run;
54
+ return 'kind' in o ? () => softSkip(o.disposition, o.reason) : o;
55
+ }
56
+ function assert(id: string, out: Outcome): undefined {
57
+ if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
58
+ for (const x of out.findings) expect(x.ok, req(id, x.doc, x.message)).toBe(true);
59
+ return undefined;
60
+ }
61
+
62
+ describe('v2 multiAgent: the supervisor loop (execution.md §multiAgent)', () => {
63
+ it('each turn records its decision and terminate completes the run', async () => {
64
+ const o = await observation();
65
+ if (typeof o === 'function') return o();
66
+ return assert(ID_TERMINATES, judgeLoopTerminates(o));
67
+ }, 90_000);
68
+
69
+ it('every recorded decision is well-formed and the deciding agent never changes', async () => {
70
+ const o = await observation();
71
+ if (typeof o === 'function') return o();
72
+ return assert(ID_DECISIONS, judgeDecisions(o, validateDecision));
73
+ }, 90_000);
74
+
75
+ it('at executionModel.version >= 5, iteration is 1-based and increments by exactly 1 per turn', async () => {
76
+ const o = await observation();
77
+ if (typeof o === 'function') return o();
78
+ return assert(ID_ITERATION, judgeIteration(o));
79
+ }, 90_000);
80
+ });
@@ -0,0 +1,40 @@
1
+ /**
2
+ * v2 — the `envelopeContracts` advertisement (`spec/v2/core/events.md` §Envelope contracts): the envelope contract claim. It had no v1 witness;
3
+ * the legs live in `lib/family-advert-witness.ts` (witness wave 3).
4
+ *
5
+ * record the record validates against its capabilities-schema seat;
6
+ *
7
+ * Not here: the refusal leg needs an error code in the prose and a fixture pack.
8
+ *
9
+ * Dispositions: discovery unreadable ⇒ `blocked`; `envelopeContracts` absent, or the
10
+ * facet a leg reads absent ⇒ `inapplicable`.
11
+ *
12
+ * @see spec/v2/core/events.md §Envelope contracts
13
+ */
14
+
15
+ import { describe, it, expect } from 'vitest';
16
+ import { v2Discovery, familyAdvertised } from '../lib/v2.js';
17
+ import { softSkip } from '../lib/soft-skip.js';
18
+ import { req } from '../lib/requirement-ids.js';
19
+ import { majorProfile } from '../lib/major-profile.js';
20
+ import { recordSchemaLeg } from '../lib/family-advert-witness.js';
21
+
22
+ const PROFILE = majorProfile(2);
23
+ const DOC = 'events.md §Envelope contracts';
24
+ const UNREADABLE = 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0';
25
+ const ID_RECORD = 'openwop.requirement.envelope-contracts.advert-record-schema';
26
+
27
+ async function discovery(): Promise<Record<string, unknown> | null> {
28
+ try { return await v2Discovery(); } catch { return null; }
29
+ }
30
+
31
+ describe('v2 envelopeContracts advertisement (events.md §Envelope contracts)', () => {
32
+ it('the envelopeContracts record validates against its capabilities-schema seat', async () => {
33
+ const doc = await discovery();
34
+ if (!doc) return softSkip('blocked', UNREADABLE);
35
+ if (!(await familyAdvertised('envelopeContracts'))) return softSkip('inapplicable', 'the host does not advertise envelopeContracts');
36
+ const out = recordSchemaLeg(PROFILE, doc, 'envelopeContracts', DOC);
37
+ if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
38
+ for (const x of out.findings) expect(x.ok, req(ID_RECORD, x.doc, x.message)).toBe(true);
39
+ });
40
+ });
@@ -0,0 +1,81 @@
1
+ /**
2
+ * v2 — the optional `reasoning` payload field (`spec/v2/core/events.md`
3
+ * §`envelopes`; RFC 0030 §A). Static: reads `schemas/v2/envelopes/` only. The
4
+ * v1 twin is `envelope-reasoning-shape`; its two advertisement legs
5
+ * (`envelopes.reasoning`, `envelopes.tierOneSubsetCompliance`) are ported to
6
+ * `v2-envelopes-advertisement`, not repeated here.
7
+ *
8
+ * declared `clarification.request`, `schema.request` and `error` declare an
9
+ * OPTIONAL `reasoning: string` ("A vendor kind whose payload needs
10
+ * multi-step reasoning SHOULD declare it as optional");
11
+ * omitted `schema.response` (a side-channel ack) does not declare it;
12
+ * absent ok each of the three validates WITHOUT `reasoning` ("A host MUST NOT
13
+ * reject an envelope without it, whatever `promptDirective` says");
14
+ * present ok each validates WITH a string `reasoning`;
15
+ * typed a non-string `reasoning` is rejected.
16
+ *
17
+ * Host-free: every leg runs with no OPENWOP_BASE_URL.
18
+ *
19
+ * @see spec/v2/core/events.md §envelopes
20
+ * @see schemas/v2/envelopes/
21
+ */
22
+
23
+ import { describe, it, expect } from 'vitest';
24
+ import { v2Validator } from '../lib/v2.js';
25
+ import { req } from '../lib/requirement-ids.js';
26
+ import { loadV2EnvelopeSchema } from '../lib/envelope-schema-static.js';
27
+
28
+ const DOC = 'events.md §envelopes';
29
+ const ID_DECLARED = 'openwop.requirement.envelopes.reasoning-declared-optional-string';
30
+ const ID_OMITTED = 'openwop.requirement.envelopes.reasoning-omitted-from-schema-response';
31
+ const ID_ABSENT_OK = 'openwop.requirement.envelopes.reasoning-absent-accepted';
32
+ const ID_PRESENT_OK = 'openwop.requirement.envelopes.reasoning-present-accepted';
33
+ const ID_TYPED = 'openwop.requirement.envelopes.reasoning-non-string-rejected';
34
+
35
+ /** A valid payload per kind, without `reasoning`. */
36
+ const BASE: Readonly<Record<string, Record<string, unknown>>> = {
37
+ 'clarification.request': { questions: [{ id: 'q1', question: 'Which provider?' }] },
38
+ 'schema.request': { envelopeType: 'acme.prd.create' },
39
+ error: { code: 'validation_failed', message: 'Could not match the schema.' },
40
+ };
41
+ const KINDS = Object.keys(BASE);
42
+
43
+ describe('v2 envelope reasoning: payload schemas (events.md §envelopes)', () => {
44
+ for (const kind of KINDS) {
45
+ it(`${kind} declares an optional reasoning of type string`, () => {
46
+ const schema = loadV2EnvelopeSchema(kind);
47
+ const props = (schema?.['properties'] ?? {}) as Record<string, Record<string, unknown>>;
48
+ const required = (schema?.['required'] ?? []) as unknown[];
49
+ expect(schema !== null, req(ID_DECLARED, DOC, `schemas/v2/envelopes/${kind}.schema.json MUST exist`)).toBe(true);
50
+ expect(props['reasoning']?.['type'], req(ID_DECLARED, DOC, `${kind} MUST declare reasoning as type string`)).toBe('string');
51
+ expect(required.includes('reasoning'), req(ID_DECLARED, DOC, `${kind} MUST NOT require reasoning — it is optional`)).toBe(false);
52
+ });
53
+ }
54
+
55
+ it('schema.response does not declare reasoning (a side-channel ack)', () => {
56
+ const schema = loadV2EnvelopeSchema('schema.response');
57
+ const props = (schema?.['properties'] ?? {}) as Record<string, unknown>;
58
+ expect(schema !== null, req(ID_OMITTED, 'RFC 0030 §A', 'schemas/v2/envelopes/schema.response.schema.json MUST exist')).toBe(true);
59
+ expect('reasoning' in props, req(ID_OMITTED, 'RFC 0030 §A', 'schema.response MUST NOT declare reasoning')).toBe(false);
60
+ });
61
+ });
62
+
63
+ describe('v2 envelope reasoning: round-trip (events.md §envelopes)', () => {
64
+ for (const kind of KINDS) {
65
+ const validate = v2Validator(`envelopes/${kind}`);
66
+
67
+ it(`${kind} validates without reasoning`, () => {
68
+ const r = validate(BASE[kind]);
69
+ expect(r.ok, req(ID_ABSENT_OK, DOC, `a ${kind} payload without reasoning MUST validate — a host MUST NOT reject it (${r.errors})`)).toBe(true);
70
+ });
71
+
72
+ it(`${kind} validates with a string reasoning`, () => {
73
+ const r = validate({ reasoning: 'The input named X twice; I asked which one.', ...BASE[kind] });
74
+ expect(r.ok, req(ID_PRESENT_OK, DOC, `a ${kind} payload with a string reasoning MUST validate (${r.errors})`)).toBe(true);
75
+ });
76
+
77
+ it(`${kind} rejects a non-string reasoning`, () => {
78
+ expect(validate({ reasoning: 42, ...BASE[kind] }).ok, req(ID_TYPED, DOC, `${kind}: a numeric reasoning MUST be rejected — reasoning is a plain string`)).toBe(false);
79
+ });
80
+ }
81
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * v2 — the Tier-1 structured-output subset, statically (RFC 0030 §B;
3
+ * `envelopes.tierOneSubsetCompliance` in `schemas/v2/capabilities.schema.json`).
4
+ * The v1 twin is `envelope-tier-one-subset-static`; the walker lives in
5
+ * `lib/envelope-schema-static.ts`.
6
+ *
7
+ * load-bearing ALWAYS ON, host-free: each universal-kind payload schema in
8
+ * `schemas/v2/envelopes/` uses none of the keywords that fail
9
+ * across several Tier-1 vendors (`oneOf`, `allOf`, `not`,
10
+ * `if`/`then`/`else`, `dependencies`, `prefixItems`,
11
+ * `propertyNames`), nests at most 5 deep and has at most 100
12
+ * properties;
13
+ * strict LIVE: a host advertising `tierOneSubsetCompliance: "strict"`
14
+ * has, for every kind it lists in `supportedEnvelopes.kinds`
15
+ * that resolves to a corpus schema, a schema meeting the whole
16
+ * intersection (closed objects, every property required, no
17
+ * string/number/array bounds). Host-served vendor schemas are
18
+ * not fetched, as at v1.
19
+ *
20
+ * The strict rule is stated by the schema seat's description ("`strict`: every
21
+ * host-served envelope schema passes the static subset-compliance check"). The
22
+ * v2 prose (`events.md` §envelopes) states a different, behavioural rule — the
23
+ * host MUST accept an envelope restricted to the subset from any advertised
24
+ * provider — and does not distinguish strict, warn and off.
25
+ *
26
+ * Dispositions (strict leg): discovery unreadable ⇒ `blocked`; `envelopes` not
27
+ * advertised, or `tierOneSubsetCompliance` other than "strict" ⇒
28
+ * `inapplicable`; no advertised kind resolving to a corpus schema ⇒
29
+ * `inapplicable`.
30
+ *
31
+ * @see RFCS/0030-envelope-reasoning-and-tier-one-subset.md §B
32
+ * @see spec/v2/core/events.md §envelopes
33
+ */
34
+
35
+ import { describe, it, expect } from 'vitest';
36
+ import { familyAdvertised, v2Discovery } from '../lib/v2.js';
37
+ import { softSkip } from '../lib/soft-skip.js';
38
+ import { req } from '../lib/requirement-ids.js';
39
+ import { loadV2EnvelopeSchema, tierOneViolations, UNIVERSAL_KINDS, type SchemaViolation } from '../lib/envelope-schema-static.js';
40
+
41
+ const ID_LOAD_BEARING = 'openwop.requirement.envelopes.tier-one-load-bearing-universal-kinds';
42
+ const ID_STRICT = 'openwop.requirement.envelopes.tier-one-strict-advertised-kinds';
43
+
44
+ describe('v2 Tier-1 subset: universal kinds meet the load-bearing rules (always on)', () => {
45
+ for (const kind of UNIVERSAL_KINDS) {
46
+ it(`${kind} uses no keyword that fails across Tier-1 vendors`, () => {
47
+ const schema = loadV2EnvelopeSchema(kind);
48
+ expect(schema !== null, req(ID_LOAD_BEARING, 'RFC 0030 §B', `schemas/v2/envelopes/${kind}.schema.json MUST exist`)).toBe(true);
49
+ const violations = schema === null ? [] : tierOneViolations(schema, 'load-bearing');
50
+ expect(violations, req(ID_LOAD_BEARING, 'RFC 0030 §B', `${kind} load-bearing Tier-1 violations: ${JSON.stringify(violations)}`)).toEqual([]);
51
+ });
52
+ }
53
+ });
54
+
55
+ describe('v2 Tier-1 subset: a strict host\'s advertised kinds meet the whole intersection', () => {
56
+ it('a host advertising tierOneSubsetCompliance strict has advertised kinds whose corpus schemas satisfy the Tier-1 intersection', async () => {
57
+ let doc: Record<string, unknown> | null;
58
+ try { doc = await v2Discovery(); } catch { doc = null; }
59
+ if (!doc) return softSkip('blocked', 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0');
60
+ const envelopes = await familyAdvertised('envelopes');
61
+ if (envelopes === null) return softSkip('inapplicable', 'the host does not advertise envelopes');
62
+ if (envelopes['tierOneSubsetCompliance'] !== 'strict') return softSkip('inapplicable', `envelopes.tierOneSubsetCompliance is ${JSON.stringify(envelopes['tierOneSubsetCompliance'])}, not "strict" — the strict walk binds only a strict host`);
63
+ const catalog = await familyAdvertised('supportedEnvelopes');
64
+ const kinds = Array.isArray(catalog?.['kinds']) ? (catalog['kinds'] as unknown[]).filter((k): k is string => typeof k === 'string') : [];
65
+ const walked: Record<string, SchemaViolation[]> = {};
66
+ let resolved = 0;
67
+ for (const kind of kinds) {
68
+ const schema = loadV2EnvelopeSchema(kind);
69
+ if (schema === null) continue;
70
+ resolved++;
71
+ const v = tierOneViolations(schema, 'strict');
72
+ if (v.length > 0) walked[kind] = v;
73
+ }
74
+ if (resolved === 0) return softSkip('inapplicable', `none of the ${kinds.length} kinds in supportedEnvelopes.kinds resolves to a corpus schema; host-served vendor schemas are not fetched`);
75
+ expect(walked, req(ID_STRICT, 'capabilities.schema.json §envelopes.tierOneSubsetCompliance', `a strict host's advertised kinds MUST satisfy the Tier-1 intersection: ${JSON.stringify(walked)}`)).toEqual({});
76
+ });
77
+ });