@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.
- package/CHANGELOG.md +45 -0
- package/README.md +2 -2
- package/dist/spec-artifacts.lock.json +2 -2
- package/package.json +2 -2
- package/requirements.json +2668 -4
- package/scenario-majors.json +164 -2
- package/schemas/CORPUS-STAMP.json +13 -13
- package/src/lib/artifact-schema-url-witness.ts +83 -0
- package/src/lib/backpressure-witness.ts +2 -2
- package/src/lib/budget-witness.ts +3 -3
- package/src/lib/dispatch-witness.ts +212 -0
- package/src/lib/envelope-schema-static.ts +166 -0
- package/src/lib/exhaustion-facet-witness.ts +1 -1
- package/src/lib/family-advert-witness.ts +411 -0
- package/src/lib/fixture-run-observer.ts +97 -0
- package/src/lib/i18n-double.ts +99 -0
- package/src/lib/i18n-negotiation-witness.ts +173 -0
- package/src/lib/localized-content-witness.ts +49 -0
- package/src/lib/model-capability-insufficient-witness.ts +121 -0
- package/src/lib/prompt-double.ts +217 -0
- package/src/lib/prompt-events-witness.ts +211 -0
- package/src/lib/prompt-library-witness.ts +406 -0
- package/src/lib/provider-usage-witness.ts +63 -0
- package/src/lib/run-bounds-witness.ts +111 -0
- package/src/lib/run-graph-double.ts +144 -0
- package/src/lib/scratch-host.ts +4 -1
- package/src/lib/subworkflow-witness.ts +192 -0
- package/src/lib/table-schema-witness.ts +3 -1
- package/src/scenarios/v2-agent-runtime-advertisement.test.ts +51 -0
- package/src/scenarios/v2-ai-envelope-advertisement.test.ts +43 -0
- package/src/scenarios/v2-ai-envelope-shape-static.test.ts +93 -0
- package/src/scenarios/v2-ai-providers-advertisement.test.ts +94 -0
- package/src/scenarios/v2-artifact-created-static.test.ts +92 -0
- package/src/scenarios/v2-artifact-type-schema-url.test.ts +44 -0
- package/src/scenarios/v2-artifact-types-advertisement.test.ts +64 -0
- package/src/scenarios/v2-authorization-advertisement.test.ts +66 -0
- package/src/scenarios/v2-channel-presence-advertisement.test.ts +41 -0
- package/src/scenarios/v2-conversation-turn-model-provenance-advertisement.test.ts +41 -0
- package/src/scenarios/v2-conversation-turn-model-provenance.test.ts +110 -0
- package/src/scenarios/v2-credentials-advertisement.test.ts +66 -0
- package/src/scenarios/v2-data-residency-admission.test.ts +98 -0
- package/src/scenarios/v2-data-residency-advertisement.test.ts +52 -0
- package/src/scenarios/v2-dead-letter-advertisement.test.ts +54 -0
- package/src/scenarios/v2-dispatch-input-mapping.test.ts +70 -0
- package/src/scenarios/v2-dispatch-loop.test.ts +80 -0
- package/src/scenarios/v2-envelope-contracts-advertisement.test.ts +40 -0
- package/src/scenarios/v2-envelope-reasoning-shape.test.ts +81 -0
- package/src/scenarios/v2-envelope-tier-one-subset-static.test.ts +77 -0
- package/src/scenarios/v2-envelope-variant-discriminator-static.test.ts +57 -0
- package/src/scenarios/v2-envelopes-advertisement.test.ts +85 -0
- package/src/scenarios/v2-export-bundle-static.test.ts +108 -0
- package/src/scenarios/v2-i18n-negotiation.test.ts +91 -0
- package/src/scenarios/v2-limits-envelope-caps-advertisement.test.ts +61 -0
- package/src/scenarios/v2-localized-content-delivery.test.ts +136 -0
- package/src/scenarios/v2-media-inline-cap-static.test.ts +59 -0
- package/src/scenarios/v2-model-capabilities-advertisement.test.ts +61 -0
- package/src/scenarios/v2-model-capability-insufficient.test.ts +63 -0
- package/src/scenarios/v2-multi-party-conversation-advertisement.test.ts +52 -0
- package/src/scenarios/v2-node-pack-runtimes-advertisement.test.ts +52 -0
- package/src/scenarios/v2-nondeterminism-policy-advertisement.test.ts +41 -0
- package/src/scenarios/v2-portability-advertisement.test.ts +63 -0
- package/src/scenarios/v2-prompt-all-four-kinds-events.test.ts +69 -0
- package/src/scenarios/v2-prompt-end-to-end-events.test.ts +71 -0
- package/src/scenarios/v2-prompt-library-advertisement.test.ts +40 -0
- package/src/scenarios/v2-prompt-list-and-fetch.test.ts +103 -0
- package/src/scenarios/v2-prompt-mutable-lifecycle.test.ts +130 -0
- package/src/scenarios/v2-prompt-pack-install.test.ts +78 -0
- package/src/scenarios/v2-prompt-render-deterministic.test.ts +87 -0
- package/src/scenarios/v2-prompt-resolution-chain-event.test.ts +46 -0
- package/src/scenarios/v2-prompt-template-shape.test.ts +112 -0
- package/src/scenarios/v2-provider-usage-advertisement.test.ts +61 -0
- package/src/scenarios/v2-provider-usage-emission.test.ts +50 -0
- package/src/scenarios/v2-provider-usage-payload-static.test.ts +92 -0
- package/src/scenarios/v2-purpose-propagation-advertisement.test.ts +52 -0
- package/src/scenarios/v2-run-execution-bounds.test.ts +67 -0
- package/src/scenarios/v2-scheduling-advertisement.test.ts +55 -0
- package/src/scenarios/v2-self-hosted-runner-advertisement.test.ts +52 -0
- package/src/scenarios/v2-self-hosted-runner-frames-static.test.ts +121 -0
- package/src/scenarios/v2-sub-workflow-advertisement.test.ts +53 -0
- package/src/scenarios/v2-subworkflow-input-mapping.test.ts +74 -0
- package/src/scenarios/v2-subworkflow-linkage.test.ts +76 -0
- package/src/scenarios/v2-ui-plugins-advertisement.test.ts +85 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2 — the `modelCapabilities` advertisement (`spec/v2/core/host-services.md`
|
|
3
|
+
* §modelCapabilities; RFC 0031 §C/§E, RFC 0055). The v1 twin is the
|
|
4
|
+
* advertisement half of `model-capability-substituted`; the legs live in
|
|
5
|
+
* `lib/family-advert-witness.ts`.
|
|
6
|
+
*
|
|
7
|
+
* record the record validates against its seat in
|
|
8
|
+
* `schemas/v2/capabilities.schema.json` — at v2 there is no
|
|
9
|
+
* `supported` seat, so a v1-shaped record fails here;
|
|
10
|
+
* advertised every identifier is spec-reserved or host-private, prefixed
|
|
11
|
+
* `x-host-<host>-` ("a host-private identifier MUST be prefixed");
|
|
12
|
+
* substitution `substitutionSupported` is a boolean.
|
|
13
|
+
*
|
|
14
|
+
* Dispositions: discovery unreadable ⇒ `blocked`; `modelCapabilities` absent,
|
|
15
|
+
* or the facet a leg reads absent ⇒ `inapplicable`.
|
|
16
|
+
*
|
|
17
|
+
* @see spec/v2/core/host-services.md §modelCapabilities
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { describe, it, expect } from 'vitest';
|
|
21
|
+
import { v2Discovery } from '../lib/v2.js';
|
|
22
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
23
|
+
import { req } from '../lib/requirement-ids.js';
|
|
24
|
+
import { majorProfile } from '../lib/major-profile.js';
|
|
25
|
+
import { modelCapabilitiesAdvertisedLeg, modelCapabilitiesSubstitutionLeg, recordSchemaLeg } from '../lib/family-advert-witness.js';
|
|
26
|
+
|
|
27
|
+
const PROFILE = majorProfile(2);
|
|
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.model-capabilities.advert-record-schema';
|
|
30
|
+
const ID_ADVERTISED = 'openwop.requirement.model-capabilities.advertised-identifiers';
|
|
31
|
+
const ID_SUBSTITUTION = 'openwop.requirement.model-capabilities.substitution-supported-boolean';
|
|
32
|
+
|
|
33
|
+
async function discovery(): Promise<Record<string, unknown> | null> {
|
|
34
|
+
try { return await v2Discovery(); } catch { return null; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('v2 modelCapabilities advertisement (host-services.md §modelCapabilities)', () => {
|
|
38
|
+
it('the modelCapabilities record validates against its capabilities-schema seat', async () => {
|
|
39
|
+
const doc = await discovery();
|
|
40
|
+
if (!doc) return softSkip('blocked', UNREADABLE);
|
|
41
|
+
const out = recordSchemaLeg(PROFILE, doc, 'modelCapabilities', 'host-services.md §modelCapabilities');
|
|
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('modelCapabilities.advertised names only spec-reserved or x-host- prefixed identifiers', async () => {
|
|
47
|
+
const doc = await discovery();
|
|
48
|
+
if (!doc) return softSkip('blocked', UNREADABLE);
|
|
49
|
+
const out = modelCapabilitiesAdvertisedLeg(PROFILE, doc);
|
|
50
|
+
if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
|
|
51
|
+
for (const x of out.findings) expect(x.ok, req(ID_ADVERTISED, x.doc, x.message)).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('modelCapabilities.substitutionSupported is a boolean', async () => {
|
|
55
|
+
const doc = await discovery();
|
|
56
|
+
if (!doc) return softSkip('blocked', UNREADABLE);
|
|
57
|
+
const out = modelCapabilitiesSubstitutionLeg(PROFILE, doc);
|
|
58
|
+
if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
|
|
59
|
+
for (const x of out.findings) expect(x.ok, req(ID_SUBSTITUTION, x.doc, x.message)).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2 — the model-capability refusal, end to end (`spec/v2/core/host-services.md`
|
|
3
|
+
* §modelCapabilities; RFC 0031 §B step 4 + §D). The v1 twin is the two fixture
|
|
4
|
+
* legs of `model-capability-insufficient`; its four `evaluate-model-capability-gate`
|
|
5
|
+
* seam legs are not ported. The legs live in
|
|
6
|
+
* `lib/model-capability-insufficient-witness.ts`; what differs between majors
|
|
7
|
+
* (the run path, the event name `model.capability-insufficient`) is the profile row.
|
|
8
|
+
*
|
|
9
|
+
* One run of the `conformance-model-capability-insufficient` fixture, whose one
|
|
10
|
+
* node requires a capability no model offers and declares no fallback:
|
|
11
|
+
* refusal the run ends `failed` / `capability_not_provided`; the log
|
|
12
|
+
* carries `model.capability-insufficient` before `node.failed`,
|
|
13
|
+
* its payload valid against
|
|
14
|
+
* `run-event-payloads#/$defs/modelCapabilityInsufficient`, with
|
|
15
|
+
* `fallbackAttempted` not true;
|
|
16
|
+
* no-dispatch the log carries no `node.completed`, `provider.usage` or
|
|
17
|
+
* envelope-reliability event.
|
|
18
|
+
*
|
|
19
|
+
* Dispositions: `modelCapabilities` not advertised, or the fixture not
|
|
20
|
+
* advertised ⇒ `inapplicable` (the fixture is the opt-in).
|
|
21
|
+
*
|
|
22
|
+
* Proven against a scratch double in `lib/model-capability-insufficient-witness.test.ts`.
|
|
23
|
+
*
|
|
24
|
+
* @see spec/v2/core/host-services.md §modelCapabilities
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { describe, it, expect } from 'vitest';
|
|
28
|
+
import { v2Discovery } from '../lib/v2.js';
|
|
29
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
30
|
+
import { req } from '../lib/requirement-ids.js';
|
|
31
|
+
import { majorProfile } from '../lib/major-profile.js';
|
|
32
|
+
import { drive, judgeNoDispatch, judgeRefusal, type InsufficientObservation, type InsufficientSkip } from '../lib/model-capability-insufficient-witness.js';
|
|
33
|
+
|
|
34
|
+
const PROFILE = majorProfile(2);
|
|
35
|
+
const ID_REFUSAL = 'openwop.requirement.model-capabilities.insufficient-refusal';
|
|
36
|
+
const ID_NO_DISPATCH = 'openwop.requirement.model-capabilities.insufficient-no-dispatch';
|
|
37
|
+
|
|
38
|
+
let run: Promise<InsufficientSkip | InsufficientObservation | null> | undefined;
|
|
39
|
+
/** One fixture run serves both legs. `null`: discovery unreadable. */
|
|
40
|
+
function observe(): Promise<InsufficientSkip | InsufficientObservation | null> {
|
|
41
|
+
run ??= (async () => {
|
|
42
|
+
let doc: Record<string, unknown> | null;
|
|
43
|
+
try { doc = await v2Discovery(); } catch { doc = null; }
|
|
44
|
+
return doc === null ? null : drive(PROFILE, doc);
|
|
45
|
+
})();
|
|
46
|
+
return run;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('v2 model-capability-insufficient (host-services.md §modelCapabilities)', () => {
|
|
50
|
+
it('a node requiring a capability no model offers fails capability_not_provided after model.capability-insufficient', async () => {
|
|
51
|
+
const o = await observe();
|
|
52
|
+
if (o === null) return softSkip('blocked', 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0');
|
|
53
|
+
if ('kind' in o) return softSkip(o.disposition, o.reason);
|
|
54
|
+
for (const x of judgeRefusal(PROFILE, o)) expect(x.ok, req(ID_REFUSAL, x.doc, x.message)).toBe(true);
|
|
55
|
+
}, 60_000);
|
|
56
|
+
|
|
57
|
+
it('a refused dispatch emits no node completion, provider usage or envelope-reliability event', async () => {
|
|
58
|
+
const o = await observe();
|
|
59
|
+
if (o === null) return softSkip('blocked', 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0');
|
|
60
|
+
if ('kind' in o) return softSkip(o.disposition, o.reason);
|
|
61
|
+
for (const x of judgeNoDispatch(PROFILE, o)) expect(x.ok, req(ID_NO_DISPATCH, x.doc, x.message)).toBe(true);
|
|
62
|
+
}, 60_000);
|
|
63
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2 — the `multiPartyConversation` advertisement (`spec/v2/core/conversation.md` §`multiPartyConversation`): the multi-party conversation (council) claim. The v1 twin is
|
|
3
|
+
* `multi-party-conversation-shape`;
|
|
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
|
+
* max-participants multiPartyConversation.maxParticipants is an integer of at least 2;
|
|
8
|
+
*
|
|
9
|
+
* Not here: roster enforcement and the over-cap refusal: v2 has no client route that opens a conversation with a roster, and the refusals name no error code (TODO §8).
|
|
10
|
+
*
|
|
11
|
+
* Dispositions: discovery unreadable ⇒ `blocked`; `multiPartyConversation` absent, or the
|
|
12
|
+
* facet a leg reads absent ⇒ `inapplicable`.
|
|
13
|
+
*
|
|
14
|
+
* @see spec/v2/core/conversation.md §`multiPartyConversation`
|
|
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 { multiPartyMaxParticipantsLeg, recordSchemaLeg } from '../lib/family-advert-witness.js';
|
|
23
|
+
|
|
24
|
+
const PROFILE = majorProfile(2);
|
|
25
|
+
const DOC = 'conversation.md §multiPartyConversation';
|
|
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.multi-party-conversation.advert-record-schema';
|
|
28
|
+
const ID_MAX_PARTICIPANTS = 'openwop.requirement.multi-party-conversation.max-participants';
|
|
29
|
+
|
|
30
|
+
async function discovery(): Promise<Record<string, unknown> | null> {
|
|
31
|
+
try { return await v2Discovery(); } catch { return null; }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('v2 multiPartyConversation advertisement (conversation.md §multiPartyConversation)', () => {
|
|
35
|
+
it('the multiPartyConversation record validates against its capabilities-schema seat', async () => {
|
|
36
|
+
const doc = await discovery();
|
|
37
|
+
if (!doc) return softSkip('blocked', UNREADABLE);
|
|
38
|
+
if (!(await familyAdvertised('multiPartyConversation'))) return softSkip('inapplicable', 'the host does not advertise multiPartyConversation');
|
|
39
|
+
const out = recordSchemaLeg(PROFILE, doc, 'multiPartyConversation', 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('multiPartyConversation.maxParticipants is an integer of at least 2', async () => {
|
|
45
|
+
const doc = await discovery();
|
|
46
|
+
if (!doc) return softSkip('blocked', UNREADABLE);
|
|
47
|
+
if (!(await familyAdvertised('multiPartyConversation'))) return softSkip('inapplicable', 'the host does not advertise multiPartyConversation');
|
|
48
|
+
const out = multiPartyMaxParticipantsLeg(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_MAX_PARTICIPANTS, x.doc, x.message)).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2 — the `nodePackRuntimes` advertisement (`spec/v2/core/node-pack-runtimes.md` §WASM): the node-pack runtimes a host loads. The v1 twin is
|
|
3
|
+
* `wasm-pack-load`;
|
|
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
|
+
* wasm-facet-shape nodePackRuntimes.wasm lists ABI versions and a memory cap in range;
|
|
8
|
+
*
|
|
9
|
+
* Not here: ABI rejection at load and the memory-cap breach need an operator-installed fixture pack.
|
|
10
|
+
*
|
|
11
|
+
* Dispositions: discovery unreadable ⇒ `blocked`; `nodePackRuntimes` absent, or the
|
|
12
|
+
* facet a leg reads absent ⇒ `inapplicable`.
|
|
13
|
+
*
|
|
14
|
+
* @see spec/v2/core/node-pack-runtimes.md §WASM
|
|
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 { nodePackRuntimesWasmLeg, recordSchemaLeg } from '../lib/family-advert-witness.js';
|
|
23
|
+
|
|
24
|
+
const PROFILE = majorProfile(2);
|
|
25
|
+
const DOC = 'node-pack-runtimes.md §WASM';
|
|
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.node-pack-runtimes.advert-record-schema';
|
|
28
|
+
const ID_WASM_FACET_SHAPE = 'openwop.requirement.node-pack-runtimes.wasm-facet-shape';
|
|
29
|
+
|
|
30
|
+
async function discovery(): Promise<Record<string, unknown> | null> {
|
|
31
|
+
try { return await v2Discovery(); } catch { return null; }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('v2 nodePackRuntimes advertisement (node-pack-runtimes.md §WASM)', () => {
|
|
35
|
+
it('the nodePackRuntimes record validates against its capabilities-schema seat', async () => {
|
|
36
|
+
const doc = await discovery();
|
|
37
|
+
if (!doc) return softSkip('blocked', UNREADABLE);
|
|
38
|
+
if (!(await familyAdvertised('nodePackRuntimes'))) return softSkip('inapplicable', 'the host does not advertise nodePackRuntimes');
|
|
39
|
+
const out = recordSchemaLeg(PROFILE, doc, 'nodePackRuntimes', 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('nodePackRuntimes.wasm lists ABI versions and a memory cap in range', async () => {
|
|
45
|
+
const doc = await discovery();
|
|
46
|
+
if (!doc) return softSkip('blocked', UNREADABLE);
|
|
47
|
+
if (!(await familyAdvertised('nodePackRuntimes'))) return softSkip('inapplicable', 'the host does not advertise nodePackRuntimes');
|
|
48
|
+
const out = nodePackRuntimesWasmLeg(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_WASM_FACET_SHAPE, x.doc, x.message)).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2 — the `nondeterminismPolicy` advertisement (`spec/v2/core/replay.md` §Declared nondeterminism): the declared-nondeterminism claim. The v1 twin is
|
|
3
|
+
* `agent-platform-profile`;
|
|
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
|
+
*
|
|
8
|
+
* Not here: recording every declared source needs a `sources[]` seat and a named event (TODO §8).
|
|
9
|
+
*
|
|
10
|
+
* Dispositions: discovery unreadable ⇒ `blocked`; `nondeterminismPolicy` absent, or the
|
|
11
|
+
* facet a leg reads absent ⇒ `inapplicable`.
|
|
12
|
+
*
|
|
13
|
+
* @see spec/v2/core/replay.md §Declared nondeterminism
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, it, expect } from 'vitest';
|
|
17
|
+
import { v2Discovery, familyAdvertised } from '../lib/v2.js';
|
|
18
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
19
|
+
import { req } from '../lib/requirement-ids.js';
|
|
20
|
+
import { majorProfile } from '../lib/major-profile.js';
|
|
21
|
+
import { recordSchemaLeg } from '../lib/family-advert-witness.js';
|
|
22
|
+
|
|
23
|
+
const PROFILE = majorProfile(2);
|
|
24
|
+
const DOC = 'replay.md §Declared nondeterminism';
|
|
25
|
+
const UNREADABLE = 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0';
|
|
26
|
+
const ID_RECORD = 'openwop.requirement.nondeterminism-policy.advert-record-schema';
|
|
27
|
+
|
|
28
|
+
async function discovery(): Promise<Record<string, unknown> | null> {
|
|
29
|
+
try { return await v2Discovery(); } catch { return null; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('v2 nondeterminismPolicy advertisement (replay.md §Declared nondeterminism)', () => {
|
|
33
|
+
it('the nondeterminismPolicy record validates against its capabilities-schema seat', async () => {
|
|
34
|
+
const doc = await discovery();
|
|
35
|
+
if (!doc) return softSkip('blocked', UNREADABLE);
|
|
36
|
+
if (!(await familyAdvertised('nondeterminismPolicy'))) return softSkip('inapplicable', 'the host does not advertise nondeterminismPolicy');
|
|
37
|
+
const out = recordSchemaLeg(PROFILE, doc, 'nondeterminismPolicy', DOC);
|
|
38
|
+
if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
|
|
39
|
+
for (const x of out.findings) expect(x.ok, req(ID_RECORD, x.doc, x.message)).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2 — the `portability` advertisement (`spec/v2/core/portability.md`; RFC
|
|
3
|
+
* 0098). The v1 twin is the advertisement leg of `export-bundle-portability`;
|
|
4
|
+
* the legs live in `lib/family-advert-witness.ts`.
|
|
5
|
+
*
|
|
6
|
+
* record the record validates against its capabilities-schema seat;
|
|
7
|
+
* import⇒dryRun "A host advertising `import` MUST advertise `dryRun: true`",
|
|
8
|
+
* read as the schema reads it: `import: true` ⇒ `dryRun: true`;
|
|
9
|
+
* kinds unique export-bundle item kinds.
|
|
10
|
+
*
|
|
11
|
+
* Not here: import refusal of credential material, dry-run no-write and
|
|
12
|
+
* idempotence: v2 defines no import route ("a host serves them on routes of its
|
|
13
|
+
* own"). The bundle shape is `v2-export-bundle-static`.
|
|
14
|
+
*
|
|
15
|
+
* Dispositions: discovery unreadable ⇒ `blocked`; `portability` absent, or the
|
|
16
|
+
* facet a leg reads absent ⇒ `inapplicable`.
|
|
17
|
+
*
|
|
18
|
+
* @see spec/v2/core/portability.md §The portability record
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { describe, it, expect } from 'vitest';
|
|
22
|
+
import { v2Discovery } 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 { portabilityImportDryRunLeg, portabilityKindsLeg, recordSchemaLeg } from '../lib/family-advert-witness.js';
|
|
27
|
+
|
|
28
|
+
const PROFILE = majorProfile(2);
|
|
29
|
+
const DOC = 'portability.md §The portability record';
|
|
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.portability.advert-record-schema';
|
|
32
|
+
const ID_IMPORT_REQUIRES_DRY_RUN = 'openwop.requirement.portability.import-requires-dry-run';
|
|
33
|
+
const ID_KINDS_SHAPE = 'openwop.requirement.portability.kinds-shape';
|
|
34
|
+
|
|
35
|
+
async function discovery(): Promise<Record<string, unknown> | null> {
|
|
36
|
+
try { return await v2Discovery(); } catch { return null; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('v2 portability advertisement (portability.md §The portability record)', () => {
|
|
40
|
+
it('the portability record validates against its capabilities-schema seat', async () => {
|
|
41
|
+
const doc = await discovery();
|
|
42
|
+
if (!doc) return softSkip('blocked', UNREADABLE);
|
|
43
|
+
const out = recordSchemaLeg(PROFILE, doc, 'portability', DOC);
|
|
44
|
+
if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
|
|
45
|
+
for (const x of out.findings) expect(x.ok, req(ID_RECORD, x.doc, x.message)).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('a host advertising portability.import advertises dryRun: true', async () => {
|
|
49
|
+
const doc = await discovery();
|
|
50
|
+
if (!doc) return softSkip('blocked', UNREADABLE);
|
|
51
|
+
const out = portabilityImportDryRunLeg(PROFILE, doc);
|
|
52
|
+
if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
|
|
53
|
+
for (const x of out.findings) expect(x.ok, req(ID_IMPORT_REQUIRES_DRY_RUN, x.doc, x.message)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('portability.kinds lists unique export-bundle item kinds', async () => {
|
|
57
|
+
const doc = await discovery();
|
|
58
|
+
if (!doc) return softSkip('blocked', UNREADABLE);
|
|
59
|
+
const out = portabilityKindsLeg(PROFILE, doc);
|
|
60
|
+
if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
|
|
61
|
+
for (const x of out.findings) expect(x.ok, req(ID_KINDS_SHAPE, x.doc, x.message)).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2 — every PromptKind resolves and composes on real dispatch
|
|
3
|
+
* (`spec/v2/core/host-services.md` §`prompts` → §Resolution, §Composition;
|
|
4
|
+
* RFC 0027 §A). The v1 twin is `prompt-all-four-kinds-events`; the judges live
|
|
5
|
+
* in `lib/prompt-events-witness.ts`.
|
|
6
|
+
*
|
|
7
|
+
* A run of `conformance-prompt-all-four-kinds` (one node: system, user,
|
|
8
|
+
* schema-hint, and two few-shot refs) is created with `POST /runs`, and its
|
|
9
|
+
* log read with `GET /runs/{runId}/events/poll`:
|
|
10
|
+
* kinds the run completes; `agent.prompt-resolved` (v1
|
|
11
|
+
* `agent.promptResolved`) is emitted for each kind, and surfaces
|
|
12
|
+
* each of the five refs (`fewShotPromptRefs[1]` included); unless
|
|
13
|
+
* observability is `off`, every ref appears in some
|
|
14
|
+
* `prompt.composed.refs` — and under `off`, no `prompt.composed`
|
|
15
|
+
* at all; every payload validates against the v2 payload defs;
|
|
16
|
+
* ordering every `prompt.composed` follows an `agent.prompt-resolved` for
|
|
17
|
+
* its node.
|
|
18
|
+
*
|
|
19
|
+
* Dispositions: `prompts` not advertised or the fixture not in `fixtures[]` ⇒
|
|
20
|
+
* `inapplicable` (the fixture is the opt-in); discovery unreadable ⇒
|
|
21
|
+
* `blocked`; the event codemap unreadable ⇒ `blocked`.
|
|
22
|
+
*
|
|
23
|
+
* Proven against a scratch double in `lib/prompt-events-witness.test.ts`.
|
|
24
|
+
*
|
|
25
|
+
* @see spec/v2/core/host-services.md §Resolution
|
|
26
|
+
* @see conformance/fixtures/conformance-prompt-all-four-kinds.json
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { describe, it, expect } from 'vitest';
|
|
30
|
+
import { v2Discovery, v2RefValidator } 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 { ALL_FOUR_FIXTURE, driveFixture, judgeAllFourKinds, judgeOrdering, type FixtureObservation, type Skip } from '../lib/prompt-events-witness.js';
|
|
35
|
+
|
|
36
|
+
const PROFILE = majorProfile(2);
|
|
37
|
+
const ID_KINDS = 'openwop.requirement.prompts.all-four-kinds-events';
|
|
38
|
+
const ID_ORDER = 'openwop.requirement.prompts.resolution-precedes-composition';
|
|
39
|
+
const validators = {
|
|
40
|
+
resolved: v2RefValidator('run-event-payloads.schema.json#/$defs/agentPromptResolved'),
|
|
41
|
+
composed: v2RefValidator('run-event-payloads.schema.json#/$defs/promptComposed'),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
let run: Promise<Skip | FixtureObservation> | undefined;
|
|
45
|
+
type Observed = { ok: true; o: FixtureObservation } | { ok: false; skip: () => undefined };
|
|
46
|
+
async function observed(): Promise<Observed> {
|
|
47
|
+
if (!process.env['OPENWOP_BASE_URL']) return { ok: false, skip: () => 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 { ok: false, skip: () => softSkip('blocked', 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0') };
|
|
51
|
+
run ??= driveFixture(PROFILE, doc, ALL_FOUR_FIXTURE);
|
|
52
|
+
const o = await run;
|
|
53
|
+
if ('kind' in o) return { ok: false, skip: () => softSkip(o.disposition, o.reason) };
|
|
54
|
+
return { ok: true, o };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe('v2 prompt events: all four kinds (host-services.md §Resolution, §Composition)', () => {
|
|
58
|
+
it('each kind and each configured ref is resolved and composed', async () => {
|
|
59
|
+
const r = await observed();
|
|
60
|
+
if (!r.ok) return r.skip();
|
|
61
|
+
for (const x of judgeAllFourKinds(r.o, validators)) expect(x.ok, req(ID_KINDS, x.doc, x.message)).toBe(true);
|
|
62
|
+
}, 60_000);
|
|
63
|
+
|
|
64
|
+
it('every prompt.composed follows an agent.prompt-resolved for its node', async () => {
|
|
65
|
+
const r = await observed();
|
|
66
|
+
if (!r.ok) return r.skip();
|
|
67
|
+
for (const x of judgeOrdering(r.o)) expect(x.ok, req(ID_ORDER, x.doc, x.message)).toBe(true);
|
|
68
|
+
}, 60_000);
|
|
69
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2 — a node's systemPromptRef resolves and composes on real dispatch
|
|
3
|
+
* (`spec/v2/core/host-services.md` §`prompts` → §Resolution, §Composition;
|
|
4
|
+
* RFCs 0027 §E, 0029 §A). The v1 twin is `prompt-end-to-end-events`; the
|
|
5
|
+
* judges live in `lib/prompt-events-witness.ts`.
|
|
6
|
+
*
|
|
7
|
+
* A run of `conformance-prompt-end-to-end` (node `writer`, `systemPromptRef:
|
|
8
|
+
* prompt:conformance.prompt.writer-system@1.0.0`) is created with
|
|
9
|
+
* `POST /runs`, and its log read with `GET /runs/{runId}/events/poll`:
|
|
10
|
+
* resolution the run completes; `agent.prompt-resolved` (kind `system`,
|
|
11
|
+
* node `writer`) names the node layer as the applied entry and
|
|
12
|
+
* resolves the configured ref;
|
|
13
|
+
* composition unless observability is `off`, `prompt.composed` carries a
|
|
14
|
+
* `sha256:<hex64>` hash and kind `system-only`; bodies
|
|
15
|
+
* (`composed`, `systemPrompt`) only under `full` — NEW at v2:
|
|
16
|
+
* under `hashed` (the default) no body field appears (v1
|
|
17
|
+
* required a body unconditionally); under `off`, no
|
|
18
|
+
* `prompt.composed` at all;
|
|
19
|
+
* ordering every `prompt.composed` follows an `agent.prompt-resolved`
|
|
20
|
+
* for its node.
|
|
21
|
+
*
|
|
22
|
+
* Dispositions: `prompts` not advertised or the fixture not in `fixtures[]` ⇒
|
|
23
|
+
* `inapplicable`; discovery unreadable ⇒ `blocked`.
|
|
24
|
+
*
|
|
25
|
+
* Proven against a scratch double in `lib/prompt-events-witness.test.ts`.
|
|
26
|
+
*
|
|
27
|
+
* @see spec/v2/core/host-services.md §Resolution, §Composition
|
|
28
|
+
* @see conformance/fixtures/conformance-prompt-end-to-end.json
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { describe, it, expect } from 'vitest';
|
|
32
|
+
import { 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 { END_TO_END_FIXTURE, driveFixture, judgeEndToEnd, judgeOrdering, type FixtureObservation, type Skip } from '../lib/prompt-events-witness.js';
|
|
37
|
+
|
|
38
|
+
const PROFILE = majorProfile(2);
|
|
39
|
+
const ID_E2E = 'openwop.requirement.prompts.end-to-end-resolution-and-composition';
|
|
40
|
+
const ID_ORDER = 'openwop.requirement.prompts.resolution-precedes-composition';
|
|
41
|
+
const validators = {
|
|
42
|
+
resolved: v2RefValidator('run-event-payloads.schema.json#/$defs/agentPromptResolved'),
|
|
43
|
+
composed: v2RefValidator('run-event-payloads.schema.json#/$defs/promptComposed'),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
let run: Promise<Skip | FixtureObservation> | undefined;
|
|
47
|
+
type Observed = { ok: true; o: FixtureObservation } | { ok: false; skip: () => undefined };
|
|
48
|
+
async function observed(): Promise<Observed> {
|
|
49
|
+
if (!process.env['OPENWOP_BASE_URL']) return { ok: false, skip: () => softSkip('inapplicable', 'no target: OPENWOP_BASE_URL is unset') };
|
|
50
|
+
let doc: Record<string, unknown> | null;
|
|
51
|
+
try { doc = await v2Discovery(); } catch { doc = null; }
|
|
52
|
+
if (!doc) return { ok: false, skip: () => softSkip('blocked', 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0') };
|
|
53
|
+
run ??= driveFixture(PROFILE, doc, END_TO_END_FIXTURE);
|
|
54
|
+
const o = await run;
|
|
55
|
+
if ('kind' in o) return { ok: false, skip: () => softSkip(o.disposition, o.reason) };
|
|
56
|
+
return { ok: true, o };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('v2 prompt events: end to end (host-services.md §Resolution, §Composition)', () => {
|
|
60
|
+
it('the node layer wins, and prompt.composed carries the hash with bodies only under full', async () => {
|
|
61
|
+
const r = await observed();
|
|
62
|
+
if (!r.ok) return r.skip();
|
|
63
|
+
for (const x of judgeEndToEnd(r.o, validators)) expect(x.ok, req(ID_E2E, x.doc, x.message)).toBe(true);
|
|
64
|
+
}, 60_000);
|
|
65
|
+
|
|
66
|
+
it('agent.prompt-resolved precedes prompt.composed for the node', async () => {
|
|
67
|
+
const r = await observed();
|
|
68
|
+
if (!r.ok) return r.skip();
|
|
69
|
+
for (const x of judgeOrdering(r.o)) expect(x.ok, req(ID_ORDER, x.doc, x.message)).toBe(true);
|
|
70
|
+
}, 60_000);
|
|
71
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2 — the `promptLibrary` advertisement (`spec/v2/core/host-services.md` §`promptLibrary`): the prompt library 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: returning a pinned version verbatim needs a fixture pack.
|
|
8
|
+
*
|
|
9
|
+
* Dispositions: discovery unreadable ⇒ `blocked`; `promptLibrary` absent, or the
|
|
10
|
+
* facet a leg reads absent ⇒ `inapplicable`.
|
|
11
|
+
*
|
|
12
|
+
* @see spec/v2/core/host-services.md §`promptLibrary`
|
|
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 = 'host-services.md §promptLibrary';
|
|
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.prompt-library.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 promptLibrary advertisement (host-services.md §promptLibrary)', () => {
|
|
32
|
+
it('the promptLibrary record validates against its capabilities-schema seat', async () => {
|
|
33
|
+
const doc = await discovery();
|
|
34
|
+
if (!doc) return softSkip('blocked', UNREADABLE);
|
|
35
|
+
if (!(await familyAdvertised('promptLibrary'))) return softSkip('inapplicable', 'the host does not advertise promptLibrary');
|
|
36
|
+
const out = recordSchemaLeg(PROFILE, doc, 'promptLibrary', 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,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2 — the prompt library read surface (`spec/v2/core/host-services.md`
|
|
3
|
+
* §`prompts` + §Library; `api/v2/openapi.yaml` `listPromptTemplates`,
|
|
4
|
+
* `getPromptTemplate`; RFC 0028 §A). The v1 twin is `prompt-list-and-fetch`;
|
|
5
|
+
* the legs live in `lib/prompt-library-witness.ts`.
|
|
6
|
+
*
|
|
7
|
+
* list `GET /prompts` answers `{ items, nextCursor? }`; every item
|
|
8
|
+
* validates against the closed v2 PromptTemplate;
|
|
9
|
+
* filters `?kind=system` and `?source=host` narrow;
|
|
10
|
+
* fetch a listed template is fetched by id;
|
|
11
|
+
* etag an ETag, when sent (a SHOULD), revalidates to `304`;
|
|
12
|
+
* unknown an unknown id is `404` with the v2 error envelope;
|
|
13
|
+
* gate off NEW at v2, unaided: with `prompts.endpointsSupported` not
|
|
14
|
+
* advertised (the record absent, or the facet not `true`),
|
|
15
|
+
* `GET /prompts` answers `404 not_found` (errors.md
|
|
16
|
+
* §Unadvertised operations).
|
|
17
|
+
*
|
|
18
|
+
* Dispositions: no target ⇒ `inapplicable`; discovery unreadable ⇒ `blocked`;
|
|
19
|
+
* `prompts` absent or `endpointsSupported` not `true` ⇒ the read legs are
|
|
20
|
+
* `inapplicable` (gated by presence, never a strict-mode failure) and the
|
|
21
|
+
* gate-off leg binds; an empty library ⇒ fetch/etag `inapplicable`.
|
|
22
|
+
*
|
|
23
|
+
* Proven against a scratch double in `lib/prompt-library-witness.test.ts`.
|
|
24
|
+
*
|
|
25
|
+
* @see spec/v2/core/host-services.md §prompts
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { describe, it, expect } from 'vitest';
|
|
29
|
+
import { v2Discovery, v2Validator } from '../lib/v2.js';
|
|
30
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
31
|
+
import { req } from '../lib/requirement-ids.js';
|
|
32
|
+
import { majorProfile } from '../lib/major-profile.js';
|
|
33
|
+
import { etagLeg, fetchLeg, filterLeg, gateOffReadLeg, listLeg, promptAdverts, unknownLeg, type PromptAdverts } from '../lib/prompt-library-witness.js';
|
|
34
|
+
|
|
35
|
+
const PROFILE = majorProfile(2);
|
|
36
|
+
const ID_LIST = 'openwop.requirement.prompts.list-shape';
|
|
37
|
+
const ID_FILTER = 'openwop.requirement.prompts.list-filters';
|
|
38
|
+
const ID_FETCH = 'openwop.requirement.prompts.fetch-by-id';
|
|
39
|
+
const ID_ETAG = 'openwop.requirement.prompts.etag-revalidation';
|
|
40
|
+
const ID_UNKNOWN = 'openwop.requirement.prompts.unknown-template-404';
|
|
41
|
+
const ID_GATE = 'openwop.requirement.prompts.endpoints-gate-off-404';
|
|
42
|
+
const HTTP_SKIP = !process.env['OPENWOP_BASE_URL'];
|
|
43
|
+
const template = v2Validator('prompt-template');
|
|
44
|
+
const envelope = v2Validator('error-envelope');
|
|
45
|
+
|
|
46
|
+
type Ready = { ok: true; a: PromptAdverts } | { ok: false; skip: () => undefined };
|
|
47
|
+
async function ready(): Promise<Ready> {
|
|
48
|
+
if (HTTP_SKIP) return { ok: false, skip: () => softSkip('inapplicable', 'no target: OPENWOP_BASE_URL is unset') };
|
|
49
|
+
let doc: Record<string, unknown> | null;
|
|
50
|
+
try { doc = await v2Discovery(); } catch { doc = null; }
|
|
51
|
+
if (!doc) return { ok: false, skip: () => softSkip('blocked', 'v2 discovery unreachable — /.well-known/openwop did not answer 200 JSON under OpenWOP-Version: 2.0') };
|
|
52
|
+
return { ok: true, a: promptAdverts(PROFILE, doc) };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe('v2 prompt library: read surface (host-services.md §prompts)', () => {
|
|
56
|
+
it('GET /prompts answers items whose every entry validates', async () => {
|
|
57
|
+
const r = await ready();
|
|
58
|
+
if (!r.ok) return r.skip();
|
|
59
|
+
const out = await listLeg(r.a, template);
|
|
60
|
+
if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
|
|
61
|
+
for (const x of out.findings) expect(x.ok, req(ID_LIST, x.doc, x.message)).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('the kind and source filters narrow the list', async () => {
|
|
65
|
+
const r = await ready();
|
|
66
|
+
if (!r.ok) return r.skip();
|
|
67
|
+
const out = await filterLeg(r.a);
|
|
68
|
+
if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
|
|
69
|
+
for (const x of out.findings) expect(x.ok, req(ID_FILTER, x.doc, x.message)).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('a listed template is fetched by id', async () => {
|
|
73
|
+
const r = await ready();
|
|
74
|
+
if (!r.ok) return r.skip();
|
|
75
|
+
const out = await fetchLeg(PROFILE, r.a, template);
|
|
76
|
+
if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
|
|
77
|
+
for (const x of out.findings) expect(x.ok, req(ID_FETCH, x.doc, x.message)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('an ETag, when sent, revalidates to 304', async () => {
|
|
81
|
+
const r = await ready();
|
|
82
|
+
if (!r.ok) return r.skip();
|
|
83
|
+
const out = await etagLeg(PROFILE, r.a);
|
|
84
|
+
if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
|
|
85
|
+
for (const x of out.findings) expect(x.ok, req(ID_ETAG, x.doc, x.message)).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('an unknown templateId answers 404 with the error envelope', async () => {
|
|
89
|
+
const r = await ready();
|
|
90
|
+
if (!r.ok) return r.skip();
|
|
91
|
+
const out = await unknownLeg(PROFILE, r.a, envelope);
|
|
92
|
+
if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
|
|
93
|
+
for (const x of out.findings) expect(x.ok, req(ID_UNKNOWN, x.doc, x.message)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('with endpointsSupported unadvertised, GET /prompts answers 404 not_found', async () => {
|
|
97
|
+
const r = await ready();
|
|
98
|
+
if (!r.ok) return r.skip();
|
|
99
|
+
const out = await gateOffReadLeg(r.a);
|
|
100
|
+
if (out.kind === 'skip') return softSkip(out.disposition, out.reason);
|
|
101
|
+
for (const x of out.findings) expect(x.ok, req(ID_GATE, x.doc, x.message)).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
});
|