@openwop/openwop-conformance 2.45.16 → 2.45.18
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 +15 -0
- package/README.md +2 -2
- package/dist/spec-artifacts.lock.json +2 -2
- package/package.json +2 -2
- package/requirements.json +808 -673
- package/scenario-majors.json +5 -23
- package/schemas/CORPUS-STAMP.json +30 -30
- package/src/lib/envelope-schema-static.ts +3 -2
- package/src/lib/if-none-match-witness.ts +139 -0
- package/src/lib/prompt-library-witness.ts +11 -2
- package/src/scenarios/otel-emission-grpc.test.ts +1 -1
- package/src/scenarios/v2-artifact-types-advertisement.test.ts +1 -1
- package/src/scenarios/v2-discovery-etag.test.ts +122 -0
- package/src/scenarios/v2-envelope-tier-one-subset-static.test.ts +7 -20
- package/src/scenarios/v2-portability-advertisement.test.ts +1 -1
- package/src/scenarios/v2-prompt-list-and-fetch.test.ts +4 -2
- package/src/scenarios/v2-provider-usage-advertisement.test.ts +1 -1
- package/src/scenarios/v2-run-snapshot-etag.test.ts +116 -22
- package/src/scenarios/v2-self-hosted-runner-advertisement.test.ts +1 -1
- package/src/scenarios/v2-ai-envelope-shape-static.test.ts +0 -93
- package/src/scenarios/v2-artifact-created-static.test.ts +0 -92
- package/src/scenarios/v2-envelope-variant-discriminator-static.test.ts +0 -57
- package/src/scenarios/v2-export-bundle-static.test.ts +0 -108
- package/src/scenarios/v2-media-inline-cap-static.test.ts +0 -59
- package/src/scenarios/v2-provider-usage-payload-static.test.ts +0 -92
- package/src/scenarios/v2-self-hosted-runner-frames-static.test.ts +0 -121
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* v2 — the `media.*` payload shapes, statically (`spec/v2/core/host-services.md`
|
|
3
|
-
* §aiProviders: "A `media.*` envelope inlines base64 only up to
|
|
4
|
-
* `maxInlineMediaBytes` (default 256 KiB); above that the host MUST use a
|
|
5
|
-
* `url`"; RFC 0055 §C). The v1 twin is the static half of
|
|
6
|
-
* `media-url-inline-cap`. Its advertisement leg (`aiProviders.maxInlineMediaBytes`)
|
|
7
|
-
* is in `v2-ai-providers-advertisement`; its seam legs (the media store and the
|
|
8
|
-
* debug-bundle walk) are not ported.
|
|
9
|
-
*
|
|
10
|
-
* compile the three `media.{image,audio,file}` schemas compile;
|
|
11
|
-
* url a URL-reference payload validates;
|
|
12
|
-
* inline an inline-base64 payload validates;
|
|
13
|
-
* bytes a payload without `bytes` is rejected (`bytes` is what a cap is
|
|
14
|
-
* judged against);
|
|
15
|
-
* closed an unknown property is rejected.
|
|
16
|
-
*
|
|
17
|
-
* Host-free: every leg runs with no OPENWOP_BASE_URL.
|
|
18
|
-
*
|
|
19
|
-
* @see spec/v2/core/host-services.md §aiProviders
|
|
20
|
-
* @see schemas/v2/envelopes/media.image.schema.json
|
|
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
|
-
|
|
27
|
-
const DOC = 'host-services.md §aiProviders';
|
|
28
|
-
const MEDIA_KINDS = ['media.image', 'media.audio', 'media.file'] as const;
|
|
29
|
-
const ID_COMPILE = 'openwop.requirement.media.payload-schemas-compile';
|
|
30
|
-
const ID_URL = 'openwop.requirement.media.url-reference-accepted';
|
|
31
|
-
const ID_INLINE = 'openwop.requirement.media.inline-base64-accepted';
|
|
32
|
-
const ID_BYTES = 'openwop.requirement.media.bytes-required';
|
|
33
|
-
const ID_CLOSED = 'openwop.requirement.media.payload-closed';
|
|
34
|
-
|
|
35
|
-
describe('v2 media payload shapes (host-services.md §aiProviders)', () => {
|
|
36
|
-
for (const kind of MEDIA_KINDS) {
|
|
37
|
-
it(`envelopes/${kind}.schema.json compiles under Ajv 2020`, () => {
|
|
38
|
-
expect(() => v2Validator(`envelopes/${kind}`), req(ID_COMPILE, DOC, `schemas/v2/envelopes/${kind}.schema.json MUST compile`)).not.toThrow();
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
it('accepts a URL-reference image payload', () => {
|
|
43
|
-
const r = v2Validator('envelopes/media.image')({ url: 'https://host.example/runs/run_1/assets/img_9.png', bytes: 184320, mimeType: 'image/png' });
|
|
44
|
-
expect(r.ok, req(ID_URL, DOC, `a URL-reference media payload MUST validate (${r.errors})`)).toBe(true);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('accepts an inline-base64 audio payload', () => {
|
|
48
|
-
const r = v2Validator('envelopes/media.audio')({ base64: 'AAAA', bytes: 3, mimeType: 'audio/ogg', durationSeconds: 1.2 });
|
|
49
|
-
expect(r.ok, req(ID_INLINE, DOC, `an inline-base64 media payload MUST validate (${r.errors})`)).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('rejects a media payload missing bytes', () => {
|
|
53
|
-
expect(v2Validator('envelopes/media.file')({ url: 'https://host.example/runs/run_1/assets/report.pdf' }).ok, req(ID_BYTES, DOC, 'bytes is required on a media payload — it is what the inline cap is judged against')).toBe(false);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('rejects a media payload with an unknown property', () => {
|
|
57
|
-
expect(v2Validator('envelopes/media.image')({ bytes: 1, wat: true }).ok, req(ID_CLOSED, DOC, 'a media payload is closed: an unknown property MUST be rejected')).toBe(false);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* v2 — the `provider.usage` payload, statically (`spec/v2/core/events.md`
|
|
3
|
-
* §providerUsage; `schemas/v2/run-event-payloads.schema.json#/$defs/providerUsage`;
|
|
4
|
-
* RFC 0026; invariant `provider-usage-no-credential-leak`). The v1 twin is the
|
|
5
|
-
* schema half of `provider-usage`; its emit-seam legs (and the v1
|
|
6
|
-
* `provider_usage_credential_leak` code, which `spec/v2/errors.json` does not
|
|
7
|
-
* carry) are not ported.
|
|
8
|
-
*
|
|
9
|
-
* type `provider.usage` is a registered v2 event type;
|
|
10
|
-
* positive a payload with the four required fields, and one with every
|
|
11
|
-
* optional field, validates;
|
|
12
|
-
* required a payload missing provider, model, inputTokens or outputTokens
|
|
13
|
-
* is rejected;
|
|
14
|
-
* counts a fractional or negative token count is rejected;
|
|
15
|
-
* cost a negative `costEstimateUsd` is rejected; `currency` is an
|
|
16
|
-
* ISO 4217 code ("`providerUsage.currency` is the ISO 4217
|
|
17
|
-
* currency of `costEstimateUsd`");
|
|
18
|
-
* no-leak the payload is closed, so a credential ref, a hashed credential
|
|
19
|
-
* id or prompt/response text is rejected ("The payload MUST NOT
|
|
20
|
-
* carry credential refs, hashed credential identifiers, or prompt
|
|
21
|
-
* or response text").
|
|
22
|
-
*
|
|
23
|
-
* Host-free: every leg runs with no OPENWOP_BASE_URL.
|
|
24
|
-
*
|
|
25
|
-
* @see spec/v2/core/events.md §providerUsage
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import { describe, it, expect } from 'vitest';
|
|
29
|
-
import { v2RefValidator } from '../lib/v2.js';
|
|
30
|
-
import { req } from '../lib/requirement-ids.js';
|
|
31
|
-
|
|
32
|
-
const DOC = 'events.md §providerUsage';
|
|
33
|
-
const ID_TYPE = 'openwop.requirement.provider-usage.event-type-registered';
|
|
34
|
-
const ID_POSITIVE = 'openwop.requirement.provider-usage.payload-well-formed-accepted';
|
|
35
|
-
const ID_REQUIRED = 'openwop.requirement.provider-usage.payload-required-fields';
|
|
36
|
-
const ID_COUNTS = 'openwop.requirement.provider-usage.payload-token-counts';
|
|
37
|
-
const ID_COST = 'openwop.requirement.provider-usage.payload-cost-and-currency';
|
|
38
|
-
const ID_NO_LEAK = 'openwop.requirement.provider-usage.payload-no-credential-or-content';
|
|
39
|
-
|
|
40
|
-
const MINIMAL = { provider: 'anthropic', model: 'claude-x', inputTokens: 120, outputTokens: 30 } as const;
|
|
41
|
-
const FULL = { ...MINIMAL, totalTokens: 150, costEstimateUsd: 0.0021, currency: 'EUR', cacheHit: false, cacheReadTokens: 0, cacheWriteTokens: 64 } as const;
|
|
42
|
-
|
|
43
|
-
function without(field: string): Record<string, unknown> {
|
|
44
|
-
const c: Record<string, unknown> = { ...MINIMAL };
|
|
45
|
-
delete c[field];
|
|
46
|
-
return c;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
describe('v2 provider.usage payload (events.md §providerUsage)', () => {
|
|
50
|
-
const validate = v2RefValidator('run-event-payloads.schema.json#/$defs/providerUsage');
|
|
51
|
-
|
|
52
|
-
it('provider.usage is a registered v2 run-event type', () => {
|
|
53
|
-
const r = v2RefValidator('run-event.schema.json#/properties/type')('provider.usage');
|
|
54
|
-
expect(r.ok, req(ID_TYPE, DOC, `provider.usage MUST be accepted by the v2 run-event type union (${r.errors})`)).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('accepts a minimal and a full payload', () => {
|
|
58
|
-
for (const p of [MINIMAL, FULL]) {
|
|
59
|
-
const r = validate(p);
|
|
60
|
-
expect(r.ok, req(ID_POSITIVE, DOC, `a well-formed provider.usage payload MUST validate (${r.errors})`)).toBe(true);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it.each(['provider', 'model', 'inputTokens', 'outputTokens'])('rejects a payload without %s', (field) => {
|
|
65
|
-
expect(validate(without(field)).ok, req(ID_REQUIRED, DOC, `a provider.usage payload without ${field} MUST be rejected`)).toBe(false);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it.each<[string, Record<string, unknown>]>([
|
|
69
|
-
['a fractional inputTokens', { ...MINIMAL, inputTokens: 1.5 }],
|
|
70
|
-
['a negative outputTokens', { ...MINIMAL, outputTokens: -1 }],
|
|
71
|
-
['a string inputTokens', { ...MINIMAL, inputTokens: '120' }],
|
|
72
|
-
])('rejects %s', (what, p) => {
|
|
73
|
-
expect(validate(p).ok, req(ID_COUNTS, DOC, `a provider.usage payload with ${what} MUST be rejected`)).toBe(false);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it.each<[string, Record<string, unknown>]>([
|
|
77
|
-
['a negative costEstimateUsd', { ...MINIMAL, costEstimateUsd: -0.01 }],
|
|
78
|
-
['a lower-case currency', { ...MINIMAL, costEstimateUsd: 0.01, currency: 'usd' }],
|
|
79
|
-
['a currency symbol', { ...MINIMAL, costEstimateUsd: 0.01, currency: '$' }],
|
|
80
|
-
])('rejects %s', (what, p) => {
|
|
81
|
-
expect(validate(p).ok, req(ID_COST, DOC, `a provider.usage payload with ${what} MUST be rejected`)).toBe(false);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it.each<[string, Record<string, unknown>]>([
|
|
85
|
-
['a credential ref', { ...MINIMAL, credentialRef: 'secret:tenant:byok-anthropic:v1' }],
|
|
86
|
-
['a hashed credential id', { ...MINIMAL, credentialHash: 'sha256:9f2c' }],
|
|
87
|
-
['prompt text', { ...MINIMAL, prompt: 'summarize the attached notes' }],
|
|
88
|
-
['response text', { ...MINIMAL, response: 'Here is a summary' }],
|
|
89
|
-
])('rejects a payload carrying %s', (what, p) => {
|
|
90
|
-
expect(validate(p).ok, req(ID_NO_LEAK, DOC, `the provider.usage payload MUST NOT carry ${what}`)).toBe(false);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* v2 — the self-hosted-runner frame and registration shapes, statically
|
|
3
|
-
* (`spec/v2/core/execution.md` §selfHostedRunner; RFC 0122). The v1 twin is
|
|
4
|
-
* the schema half of `self-hosted-runner`; its live legs (subject isolation,
|
|
5
|
-
* `runner_unavailable`, at-most-once) need a runner seam and are not ported.
|
|
6
|
-
*
|
|
7
|
-
* compile the three schemas compile under Ajv 2020 with the v2 tree;
|
|
8
|
-
* dispatch a model and a tool dispatch frame validate; a frame with no
|
|
9
|
-
* `seq`, a non-integer or negative `seq`, an unknown `kind`, a
|
|
10
|
-
* model frame without `provider`/`model`, or a tool frame
|
|
11
|
-
* without `tool` is rejected;
|
|
12
|
-
* result a result frame validates; one without `output` is rejected;
|
|
13
|
-
* closed dispatch, result and registration (and its `capabilities`)
|
|
14
|
-
* are closed objects, so no frame or record can carry a
|
|
15
|
-
* credential field alongside the step ("A runner credential
|
|
16
|
-
* MUST NOT transit the host or appear in any frame");
|
|
17
|
-
* registration a registration validates; one without `subject` is rejected
|
|
18
|
-
* (subject-first routing keys on it).
|
|
19
|
-
*
|
|
20
|
-
* The shape rules these schemas state bind the host's dispatch frames and its
|
|
21
|
-
* registration record; the result frame is the runner's, so its legs witness
|
|
22
|
-
* the corpus shape the host's intake is written against, not the host.
|
|
23
|
-
*
|
|
24
|
-
* Host-free: every leg runs with no OPENWOP_BASE_URL.
|
|
25
|
-
*
|
|
26
|
-
* @see spec/v2/core/execution.md §selfHostedRunner
|
|
27
|
-
* @see schemas/v2/self-hosted-runner-dispatch-frame.schema.json
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
import { describe, it, expect } from 'vitest';
|
|
31
|
-
import { v2Validator } from '../lib/v2.js';
|
|
32
|
-
import { req } from '../lib/requirement-ids.js';
|
|
33
|
-
|
|
34
|
-
const DOC = 'execution.md §selfHostedRunner';
|
|
35
|
-
const ID_COMPILE = 'openwop.requirement.self-hosted-runner.frame-schemas-compile';
|
|
36
|
-
const ID_DISPATCH = 'openwop.requirement.self-hosted-runner.dispatch-frame-shape';
|
|
37
|
-
const ID_RESULT = 'openwop.requirement.self-hosted-runner.result-frame-shape';
|
|
38
|
-
const ID_CLOSED = 'openwop.requirement.self-hosted-runner.frames-closed';
|
|
39
|
-
const ID_REGISTRATION = 'openwop.requirement.self-hosted-runner.registration-shape';
|
|
40
|
-
|
|
41
|
-
const SCHEMAS = ['self-hosted-runner-dispatch-frame', 'self-hosted-runner-result-frame', 'self-hosted-runner-registration'] as const;
|
|
42
|
-
|
|
43
|
-
const MODEL_FRAME = { runId: 'acme/run_x-xxxxxxxxxx', stepId: 'step_3', seq: 12, kind: 'model', provider: 'anthropic', model: 'claude-x', inputs: { messages: [] } } as const;
|
|
44
|
-
const TOOL_FRAME = { runId: 'acme/run_y-xxxxxxxxxx', stepId: 'step_1', seq: 0, kind: 'tool', tool: 'local.shell.readFile', inputs: { path: '/tmp/x' } } as const;
|
|
45
|
-
const RESULT = { runId: 'acme/run_x-xxxxxxxxxx', stepId: 'step_3', seq: 12, output: { role: 'assistant', content: 'ok' } } as const;
|
|
46
|
-
const REGISTRATION = { runnerId: 'runner_laptop_01', subject: 'user_42', capabilities: { providers: ['anthropic'], models: ['claude-x'] } } as const;
|
|
47
|
-
|
|
48
|
-
function without(o: Readonly<Record<string, unknown>>, field: string): Record<string, unknown> {
|
|
49
|
-
const c: Record<string, unknown> = { ...o };
|
|
50
|
-
delete c[field];
|
|
51
|
-
return c;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
describe('v2 self-hosted-runner frames: schemas compile', () => {
|
|
55
|
-
for (const name of SCHEMAS) {
|
|
56
|
-
it(`${name}.schema.json compiles under Ajv 2020`, () => {
|
|
57
|
-
expect(() => v2Validator(name), req(ID_COMPILE, DOC, `schemas/v2/${name}.schema.json MUST compile`)).not.toThrow();
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe('v2 self-hosted-runner frames: dispatch frame', () => {
|
|
63
|
-
const validate = v2Validator('self-hosted-runner-dispatch-frame');
|
|
64
|
-
|
|
65
|
-
it('accepts a model frame and a tool frame', () => {
|
|
66
|
-
for (const f of [MODEL_FRAME, TOOL_FRAME]) {
|
|
67
|
-
const r = validate(f);
|
|
68
|
-
expect(r.ok, req(ID_DISPATCH, DOC, `a well-formed ${f.kind} dispatch frame MUST validate (${r.errors})`)).toBe(true);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it.each<[string, Record<string, unknown>]>([
|
|
73
|
-
['no seq cursor', without(MODEL_FRAME, 'seq')],
|
|
74
|
-
['a fractional seq', { ...MODEL_FRAME, seq: 1.5 }],
|
|
75
|
-
['a negative seq', { ...MODEL_FRAME, seq: -1 }],
|
|
76
|
-
['an unknown kind', { ...MODEL_FRAME, kind: 'agent' }],
|
|
77
|
-
['a model frame without provider', without(MODEL_FRAME, 'provider')],
|
|
78
|
-
['a model frame without model', without(MODEL_FRAME, 'model')],
|
|
79
|
-
['a tool frame without tool', without(TOOL_FRAME, 'tool')],
|
|
80
|
-
['no stepId (the at-most-once key)', without(MODEL_FRAME, 'stepId')],
|
|
81
|
-
])('rejects %s', (_what, frame) => {
|
|
82
|
-
expect(validate(frame).ok, req(ID_DISPATCH, DOC, `a dispatch frame with ${_what} MUST be rejected`)).toBe(false);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe('v2 self-hosted-runner frames: result frame', () => {
|
|
87
|
-
const validate = v2Validator('self-hosted-runner-result-frame');
|
|
88
|
-
|
|
89
|
-
it('accepts a result frame', () => {
|
|
90
|
-
const r = validate(RESULT);
|
|
91
|
-
expect(r.ok, req(ID_RESULT, DOC, `a well-formed result frame MUST validate (${r.errors})`)).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('rejects a result frame without output', () => {
|
|
95
|
-
expect(validate(without(RESULT, 'output')).ok, req(ID_RESULT, DOC, 'a result frame without output MUST be rejected')).toBe(false);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('v2 self-hosted-runner frames: closed shapes carry no credential field', () => {
|
|
100
|
-
it.each<[string, string, Record<string, unknown>]>([
|
|
101
|
-
['dispatch frame', 'self-hosted-runner-dispatch-frame', { ...MODEL_FRAME, apiKey: 'sk-canary' }],
|
|
102
|
-
['result frame', 'self-hosted-runner-result-frame', { ...RESULT, credential: 'sk-canary' }],
|
|
103
|
-
['registration', 'self-hosted-runner-registration', { ...REGISTRATION, bearer: 'sk-canary' }],
|
|
104
|
-
['registration capabilities', 'self-hosted-runner-registration', { ...REGISTRATION, capabilities: { providers: ['anthropic'], apiKeys: { anthropic: 'sk-canary' } } }],
|
|
105
|
-
])('a %s with an extra credential-named field is rejected', (what, schema, doc) => {
|
|
106
|
-
expect(v2Validator(schema)(doc).ok, req(ID_CLOSED, DOC, `the ${what} is closed: a runner credential MUST NOT appear in any frame`)).toBe(false);
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
describe('v2 self-hosted-runner frames: registration', () => {
|
|
111
|
-
const validate = v2Validator('self-hosted-runner-registration');
|
|
112
|
-
|
|
113
|
-
it('accepts a registration', () => {
|
|
114
|
-
const r = validate(REGISTRATION);
|
|
115
|
-
expect(r.ok, req(ID_REGISTRATION, DOC, `a well-formed registration MUST validate (${r.errors})`)).toBe(true);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('rejects a registration without subject', () => {
|
|
119
|
-
expect(validate(without(REGISTRATION, 'subject')).ok, req(ID_REGISTRATION, DOC, 'a registration without its owning subject MUST be rejected')).toBe(false);
|
|
120
|
-
});
|
|
121
|
-
});
|