@openwop/openwop-conformance 1.72.2 → 1.98.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +2 -2
- package/api/asyncapi.yaml +58 -0
- package/api/openapi.yaml +4 -1
- package/dist/cli.js +107 -1
- package/dist/lib/profiles.js +70 -4
- package/package.json +2 -1
- package/schemas/CORPUS-STAMP.json +2 -2
- package/schemas/README.md +2 -0
- package/schemas/capabilities.schema.json +2054 -572
- package/schemas/certification-bundle-v2.schema.json +108 -0
- package/schemas/run-event-payloads.schema.json +3411 -857
- package/schemas/run-event.schema.json +31 -9
- package/schemas/workflow-definition.schema.json +492 -130
- package/schemas/workload-identity.schema.json +73 -0
- package/src/cli.ts +119 -1
- package/src/lib/a2a-fake-peer.ts +20 -0
- package/src/lib/behavior-gate.ts +42 -7
- package/src/lib/llm-cache-key-recipe.ts +51 -0
- package/src/lib/mcp-fake-server.ts +20 -0
- package/src/lib/profiles.ts +95 -4
- package/src/lib/requirement-ledger.ts +138 -0
- package/src/lib/requirement-registry.ts +62 -0
- package/src/scenarios/a2a-version-negotiation.test.ts +159 -0
- package/src/scenarios/capability-example-root-layout.test.ts +113 -0
- package/src/scenarios/certification-bundle-v2.test.ts +157 -0
- package/src/scenarios/certification-floor-enforcement.test.ts +115 -0
- package/src/scenarios/compensation-behavior.test.ts +164 -0
- package/src/scenarios/compensation-profile.test.ts +175 -0
- package/src/scenarios/contract-provenance.test.ts +209 -0
- package/src/scenarios/core-manifest-and-extension-registry.test.ts +198 -0
- package/src/scenarios/discovery-canonical-family-no-shadow.test.ts +219 -0
- package/src/scenarios/effect-identity-composition.test.ts +129 -0
- package/src/scenarios/effect-identity-cross-scope.test.ts +82 -0
- package/src/scenarios/mcp-version-negotiation.test.ts +159 -0
- package/src/scenarios/multi-region-effect-vocabulary.test.ts +175 -0
- package/src/scenarios/multi-region-idempotency.test.ts +17 -7
- package/src/scenarios/openapi-resolved-paths.test.ts +127 -0
- package/src/scenarios/protocol-version-grammar.test.ts +119 -0
- package/src/scenarios/requirement-ledger.test.ts +162 -0
- package/src/scenarios/rfc-0147-self-audit.test.ts +104 -0
- package/src/scenarios/rfc-lifecycle-coherence.test.ts +137 -0
- package/src/scenarios/semantic-digest-v2.test.ts +128 -0
- package/src/scenarios/semantic-digest-vectors.test.ts +140 -0
- package/src/scenarios/spec-corpus-validity.test.ts +22 -8
- package/src/scenarios/strict-behavior-gate.test.ts +120 -0
- package/src/scenarios/versioned-composition-profiles.test.ts +183 -0
- package/src/scenarios/workload-identity-behavior.test.ts +188 -0
- package/src/scenarios/workload-identity-profile.test.ts +175 -0
- package/vectors/semantic-request-digest-v2.json +236 -0
package/api/asyncapi.yaml
CHANGED
|
@@ -456,6 +456,64 @@ components:
|
|
|
456
456
|
payload:
|
|
457
457
|
$ref: '#/components/schemas/RunEventDoc'
|
|
458
458
|
|
|
459
|
+
CompensationRequested:
|
|
460
|
+
name: compensation.requested
|
|
461
|
+
title: Compensation plan persisted (RFC 0151 §C/§D)
|
|
462
|
+
summary: >-
|
|
463
|
+
The unwind plan is durable. §C requires this BEFORE the first inverse
|
|
464
|
+
action executes — a host that starts unwinding first cannot resume after
|
|
465
|
+
a crash, and the crash is when resumption matters.
|
|
466
|
+
contentType: application/json
|
|
467
|
+
payload:
|
|
468
|
+
$ref: '#/components/schemas/RunEventDoc'
|
|
469
|
+
|
|
470
|
+
CompensationStarted:
|
|
471
|
+
name: compensation.started
|
|
472
|
+
title: First inverse action started (RFC 0151 §D)
|
|
473
|
+
summary: Compensation began executing. MUST follow compensation.requested.
|
|
474
|
+
contentType: application/json
|
|
475
|
+
payload:
|
|
476
|
+
$ref: '#/components/schemas/RunEventDoc'
|
|
477
|
+
|
|
478
|
+
CompensationCompleted:
|
|
479
|
+
name: compensation.completed
|
|
480
|
+
title: Compensation completed (RFC 0151 §D)
|
|
481
|
+
summary: Every inverse action in the plan succeeded.
|
|
482
|
+
contentType: application/json
|
|
483
|
+
payload:
|
|
484
|
+
$ref: '#/components/schemas/RunEventDoc'
|
|
485
|
+
|
|
486
|
+
CompensationFailed:
|
|
487
|
+
name: compensation.failed
|
|
488
|
+
title: Compensation failed (RFC 0151 §D)
|
|
489
|
+
summary: >-
|
|
490
|
+
An inverse action exhausted its retries. Compensation is a second effect,
|
|
491
|
+
not an undo — it can fail, and the run's compensationStatus records that
|
|
492
|
+
rather than the run silently reporting success.
|
|
493
|
+
contentType: application/json
|
|
494
|
+
payload:
|
|
495
|
+
$ref: '#/components/schemas/RunEventDoc'
|
|
496
|
+
|
|
497
|
+
CompensationPaused:
|
|
498
|
+
name: compensation.paused
|
|
499
|
+
title: Compensation paused for authorized intervention (RFC 0151 §C)
|
|
500
|
+
summary: >-
|
|
501
|
+
Cancelling the parent MUST NOT silently abandon an active compensation.
|
|
502
|
+
It continues, pauses here, or records manual intervention required.
|
|
503
|
+
contentType: application/json
|
|
504
|
+
payload:
|
|
505
|
+
$ref: '#/components/schemas/RunEventDoc'
|
|
506
|
+
|
|
507
|
+
CompensationManualInterventionRequired:
|
|
508
|
+
name: compensation.manual_intervention_required
|
|
509
|
+
title: Compensation needs an operator (RFC 0151 §C/§E)
|
|
510
|
+
summary: >-
|
|
511
|
+
The host cannot proceed unaided. Recorded explicitly because an unwind
|
|
512
|
+
that stops without saying so is indistinguishable from one that finished.
|
|
513
|
+
contentType: application/json
|
|
514
|
+
payload:
|
|
515
|
+
$ref: '#/components/schemas/RunEventDoc'
|
|
516
|
+
|
|
459
517
|
RunPaused:
|
|
460
518
|
name: run.paused
|
|
461
519
|
title: Run paused
|
package/api/openapi.yaml
CHANGED
|
@@ -38,7 +38,10 @@ externalDocs:
|
|
|
38
38
|
url: https://openwop.dev/spec/v1/
|
|
39
39
|
|
|
40
40
|
servers:
|
|
41
|
-
|
|
41
|
+
# No `/v1` here: the version segment lives in the path keys below, so a
|
|
42
|
+
# server base carrying it too resolves every operation to `/v1/v1/*` and
|
|
43
|
+
# the unversioned discovery route to `/v1/.well-known/openwop` (RFC 0149 §A).
|
|
44
|
+
- url: https://{host}
|
|
42
45
|
description: openwop-compliant server
|
|
43
46
|
variables:
|
|
44
47
|
host:
|
package/dist/cli.js
CHANGED
|
@@ -42,6 +42,7 @@ function parseArgs(argv) {
|
|
|
42
42
|
let impl;
|
|
43
43
|
let implVersion;
|
|
44
44
|
let certify;
|
|
45
|
+
let bundleVersion = '1';
|
|
45
46
|
for (let i = 0; i < argv.length; i++) {
|
|
46
47
|
const arg = argv[i] ?? '';
|
|
47
48
|
if (arg === '-h' || arg === '--help') {
|
|
@@ -83,6 +84,15 @@ function parseArgs(argv) {
|
|
|
83
84
|
case '--implementation-version':
|
|
84
85
|
implVersion = nextValue();
|
|
85
86
|
break;
|
|
87
|
+
case '--bundle-version': {
|
|
88
|
+
const v = nextValue();
|
|
89
|
+
if (v !== '1' && v !== '2') {
|
|
90
|
+
process.stderr.write(`--bundle-version must be 1 or 2 (got '${v}')\n`);
|
|
91
|
+
process.exit(2);
|
|
92
|
+
}
|
|
93
|
+
bundleVersion = v;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
86
96
|
case '--certify':
|
|
87
97
|
certify = nextValue();
|
|
88
98
|
break;
|
|
@@ -92,7 +102,7 @@ function parseArgs(argv) {
|
|
|
92
102
|
}
|
|
93
103
|
}
|
|
94
104
|
}
|
|
95
|
-
return { baseUrl, apiKey, offline, filter, help, impl, implVersion, certify };
|
|
105
|
+
return { baseUrl, apiKey, offline, filter, help, impl, implVersion, certify, bundleVersion };
|
|
96
106
|
}
|
|
97
107
|
const HELP_TEXT = `openwop-conformance — run the openwop conformance suite against a server
|
|
98
108
|
|
|
@@ -112,6 +122,10 @@ Implementation labels (cosmetic — surface in failure messages):
|
|
|
112
122
|
--impl-version <version> Implementation version (env: OPENWOP_IMPLEMENTATION_VERSION)
|
|
113
123
|
|
|
114
124
|
Certification (RFC 0089):
|
|
125
|
+
--bundle-version <1|2> Certification bundle format. Default 1. Version 2 (RFC 0148
|
|
126
|
+
§C) records per-requirement DISPOSITIONS instead of pass/fail/skip
|
|
127
|
+
file lists, so "we could not check" stops being indistinguishable
|
|
128
|
+
from "checked and it holds". See the note it prints.
|
|
115
129
|
--certify <out.json> Generate a machine-readable conformance certification
|
|
116
130
|
bundle: fetch /.well-known/openwop (captured verbatim +
|
|
117
131
|
SHA-256), derive claimedProfiles from it, run the suite
|
|
@@ -310,6 +324,98 @@ async function runCertify(args, baseUrl, apiKey) {
|
|
|
310
324
|
skipped,
|
|
311
325
|
},
|
|
312
326
|
};
|
|
327
|
+
// (d2) RFC 0148 §C — bundle v2.
|
|
328
|
+
//
|
|
329
|
+
// v1's `{passed, failed, skipped}` file lists cannot express the distinction
|
|
330
|
+
// this program exists for. A file counted as `passed` whether its assertions
|
|
331
|
+
// ran or its runner returned early, and `skipped` flattened three different
|
|
332
|
+
// claims — operator-excluded, not-applicable, and could-not-check — of which
|
|
333
|
+
// only the first two are certifiable.
|
|
334
|
+
//
|
|
335
|
+
// THE HONEST LIMIT, stated because it changes what a v2 bundle from this
|
|
336
|
+
// runner means: the runner reads vitest's per-FILE outcome. It does not read
|
|
337
|
+
// the RFC 0148 §A requirement ledger, because scenarios do not yet record
|
|
338
|
+
// into it. So a skipped file cannot be split into `skipped` /`inapplicable` /
|
|
339
|
+
// `blocked` here, and §A's rule is that an unclassifiable requirement resolves
|
|
340
|
+
// to `blocked` — never to a pass. That is what this emits.
|
|
341
|
+
//
|
|
342
|
+
// The consequence is deliberate and correct: a v2 bundle produced today shows
|
|
343
|
+
// a large `blocked` count and therefore does NOT certify. That is not a defect
|
|
344
|
+
// in the emitter. It is the true state of the evidence, which v1 was unable to
|
|
345
|
+
// represent and therefore reported as a clean skip.
|
|
346
|
+
if (args.bundleVersion === '2') {
|
|
347
|
+
const scenarioIds = [...passed, ...failed, ...skipped].sort();
|
|
348
|
+
const manifestSha = createHash('sha256').update(scenarioIds.join('\n'), 'utf8').digest('hex');
|
|
349
|
+
// Configuration identity per RFC 0147 §A.4: what the run was pointed at, and
|
|
350
|
+
// the discovery it saw. Two runs against differently-configured hosts are
|
|
351
|
+
// different evidence, and this is what says so.
|
|
352
|
+
const configSha = createHash('sha256')
|
|
353
|
+
.update(`${args.baseUrl}\n${sha256}\n${process.env['OPENWOP_REQUIRE_BEHAVIOR'] ?? ''}`, 'utf8')
|
|
354
|
+
.digest('hex');
|
|
355
|
+
const requirements = [
|
|
356
|
+
...passed.map((f) => ({
|
|
357
|
+
requirementId: `openwop.scenario.${f.replace(/\.test\.ts$/, '')}`,
|
|
358
|
+
scenarioId: f,
|
|
359
|
+
disposition: 'executed-pass',
|
|
360
|
+
})),
|
|
361
|
+
...failed.map((f) => ({
|
|
362
|
+
requirementId: `openwop.scenario.${f.replace(/\.test\.ts$/, '')}`,
|
|
363
|
+
scenarioId: f,
|
|
364
|
+
disposition: 'executed-fail',
|
|
365
|
+
detail: 'the scenario executed and failed',
|
|
366
|
+
})),
|
|
367
|
+
...skipped.map((f) => ({
|
|
368
|
+
requirementId: `openwop.scenario.${f.replace(/\.test\.ts$/, '')}`,
|
|
369
|
+
scenarioId: f,
|
|
370
|
+
disposition: 'blocked',
|
|
371
|
+
detail: 'runner cannot classify a skipped file: the RFC 0148 §A requirement ledger is not ' +
|
|
372
|
+
'populated during a run, so skipped/inapplicable/blocked are indistinguishable here. ' +
|
|
373
|
+
'§A resolves an unclassifiable requirement to `blocked`, never to a pass.',
|
|
374
|
+
})),
|
|
375
|
+
];
|
|
376
|
+
const v2 = {
|
|
377
|
+
bundleVersion: '2',
|
|
378
|
+
generatedAt: new Date().toISOString(),
|
|
379
|
+
generator: { name: '@openwop/openwop-conformance --certify', version },
|
|
380
|
+
suite: { package: '@openwop/openwop-conformance', version },
|
|
381
|
+
host,
|
|
382
|
+
discovery: { url: discoveryUrl, sha256, document },
|
|
383
|
+
claimedProfiles,
|
|
384
|
+
results: {
|
|
385
|
+
totals: {
|
|
386
|
+
executedPass: passed.length,
|
|
387
|
+
executedFail: failed.length,
|
|
388
|
+
skipped: 0,
|
|
389
|
+
inapplicable: 0,
|
|
390
|
+
blocked: skipped.length,
|
|
391
|
+
},
|
|
392
|
+
requirements,
|
|
393
|
+
},
|
|
394
|
+
scenarioManifestSha256: manifestSha,
|
|
395
|
+
targetConfigurationSha256: configSha,
|
|
396
|
+
};
|
|
397
|
+
const v2Schema = JSON.parse(readFileSync(join(SCHEMAS_DIR, 'certification-bundle-v2.schema.json'), 'utf8'));
|
|
398
|
+
const v2Ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
399
|
+
addFormats(v2Ajv);
|
|
400
|
+
const v2Validate = v2Ajv.compile(v2Schema);
|
|
401
|
+
if (!v2Validate(v2)) {
|
|
402
|
+
process.stderr.write('openwop-conformance --certify: assembled v2 bundle FAILED schema validation:\n' +
|
|
403
|
+
`${JSON.stringify(v2Validate.errors, null, 2)}\n`);
|
|
404
|
+
process.exit(2);
|
|
405
|
+
}
|
|
406
|
+
writeFileSync(outPath, `${JSON.stringify(v2, null, 2)}\n`);
|
|
407
|
+
process.stdout.write(`openwop-conformance --certify: wrote bundle v2 to ${outPath}\n` +
|
|
408
|
+
` host: ${host.name}@${host.version}\n` +
|
|
409
|
+
` executed-pass ${passed.length} / executed-fail ${failed.length} / blocked ${skipped.length}\n` +
|
|
410
|
+
(skipped.length > 0
|
|
411
|
+
? ` NOTE: ${skipped.length} requirement(s) are 'blocked', not 'skipped'. The runner cannot\n` +
|
|
412
|
+
` classify a skipped file until scenarios record into the RFC 0148 §A ledger, and §A\n` +
|
|
413
|
+
` resolves an unclassifiable requirement to 'blocked' rather than to a pass. A bundle\n` +
|
|
414
|
+
` with blocked > 0 does NOT certify — that is the true state of the evidence, not a\n` +
|
|
415
|
+
` defect in this emitter.\n`
|
|
416
|
+
: ''));
|
|
417
|
+
process.exit(failed.length > 0 ? 1 : 0);
|
|
418
|
+
}
|
|
313
419
|
// (e) Validate against the bundle schema BEFORE writing.
|
|
314
420
|
const schema = JSON.parse(readFileSync(join(SCHEMAS_DIR, 'conformance-certification-bundle.schema.json'), 'utf8'));
|
|
315
421
|
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
package/dist/lib/profiles.js
CHANGED
|
@@ -38,6 +38,13 @@ function isStringArray(value) {
|
|
|
38
38
|
function isNonNegativeInteger(value) {
|
|
39
39
|
return typeof value === 'number' && Number.isInteger(value) && value >= 0;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* RFC 0149 §C — `protocolVersion` is ASCII `<major>.<minor>`, no leading zero
|
|
43
|
+
* except zero itself. Kept identical to the `pattern` in
|
|
44
|
+
* `schemas/capabilities.schema.json`; `protocol-version-grammar.test.ts` asserts
|
|
45
|
+
* the two agree, so this cannot drift into being the looser of the pair again.
|
|
46
|
+
*/
|
|
47
|
+
const PROTOCOL_VERSION_GRAMMAR = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/;
|
|
41
48
|
/**
|
|
42
49
|
* `openwop-core` predicate. Every other profile implies `openwop-core`. A host
|
|
43
50
|
* that fails this predicate is not openwop-compatible.
|
|
@@ -47,7 +54,14 @@ function isNonNegativeInteger(value) {
|
|
|
47
54
|
export function isCore(c) {
|
|
48
55
|
if (typeof c.protocolVersion !== 'string')
|
|
49
56
|
return false;
|
|
50
|
-
|
|
57
|
+
// RFC 0149 §C. `startsWith('1.')` admitted `1.0.0` and `1.banana` while
|
|
58
|
+
// rejecting `1`, so the predicate deciding whether a host is openwop-compatible
|
|
59
|
+
// at all was looser than the schema every host validates against. The major is
|
|
60
|
+
// the hard boundary; a higher minor stays core under v1 additive rules.
|
|
61
|
+
const version = PROTOCOL_VERSION_GRAMMAR.exec(c.protocolVersion);
|
|
62
|
+
if (version === null)
|
|
63
|
+
return false;
|
|
64
|
+
if (version[1] !== '1')
|
|
51
65
|
return false;
|
|
52
66
|
if (!Array.isArray(c.supportedEnvelopes))
|
|
53
67
|
return false;
|
|
@@ -437,6 +451,42 @@ export const PROFILE_FLOOR_SCENARIOS = {
|
|
|
437
451
|
],
|
|
438
452
|
requiredAnyPrefix: ['interrupt-'],
|
|
439
453
|
},
|
|
454
|
+
// ── Floors TRANSCRIBED from `profiles.md`, not invented here ────────────────
|
|
455
|
+
// `profiles.md` §"Claiming vs passing" already states the rule normatively:
|
|
456
|
+
// "A host CLAIMS a profile by satisfying its predicate AND passing the
|
|
457
|
+
// conformance scenarios labelled with the profile tag." Each per-profile
|
|
458
|
+
// section then names its scenarios. This map was an incomplete transcription
|
|
459
|
+
// of that prose, and every profile it omitted verified as floor-proven
|
|
460
|
+
// against nothing (RFC 0148 §C, gap G6).
|
|
461
|
+
// `profiles.md` §`openwop-core` is a pure discovery-payload predicate — it
|
|
462
|
+
// names no runtime scenario, so the predicate IS the whole claim.
|
|
463
|
+
'openwop-core': { required: [], discoveryOnly: true },
|
|
464
|
+
// `profiles.md` §`openwop-fixtures`: "The profile is discovery-payload-only —
|
|
465
|
+
// it confirms the host claims SOME fixture, not that any specific fixture is
|
|
466
|
+
// wired."
|
|
467
|
+
'openwop-fixtures': { required: [], discoveryOnly: true },
|
|
468
|
+
// `profiles.md` §`openwop-stream-sse`: "A host passes `openwop-stream-sse`
|
|
469
|
+
// when discovery passes the predicate AND those scenarios pass."
|
|
470
|
+
'openwop-stream-sse': {
|
|
471
|
+
required: ['stream-modes.test.ts', 'stream-modes-buffer.test.ts', 'stream-modes-mixed.test.ts'],
|
|
472
|
+
},
|
|
473
|
+
// `profiles.md` §`openwop-stream-poll`: "Conformance scenarios in
|
|
474
|
+
// `stream-modes.test.ts` exercise polling."
|
|
475
|
+
'openwop-stream-poll': { required: ['stream-modes.test.ts'] },
|
|
476
|
+
// `profiles.md` §`openwop-node-packs`: "a host passes `openwop-node-packs`
|
|
477
|
+
// when it passes those scenarios."
|
|
478
|
+
'openwop-node-packs': { required: ['pack-registry.test.ts', 'pack-registry-publish.test.ts'] },
|
|
479
|
+
// ── Deliberately NOT transcribed ───────────────────────────────────────────
|
|
480
|
+
// `openwop-replay-fork` cannot be expressed by this model. `profiles.md`
|
|
481
|
+
// §`openwop-replay-fork` says "Hosts MAY support either or both modes; the
|
|
482
|
+
// conformance scenarios pass on whichever mode the host advertises" — a floor
|
|
483
|
+
// conditional on the advertised mode, which a flat `required[]` cannot state.
|
|
484
|
+
// Forcing it would either over-require (failing an honest single-mode host) or
|
|
485
|
+
// under-require (the vacuity this fix removes). It stays unspecified, and is
|
|
486
|
+
// therefore unprovable, until the model can express a discovery-conditional
|
|
487
|
+
// floor. Same for `openwop-interrupts`, `openwop-secrets`,
|
|
488
|
+
// `openwop-provider-policy`, `openwop-memory`, and `openwop-trigger-bridge`,
|
|
489
|
+
// whose prose sections do not yet name a settled floor set.
|
|
440
490
|
};
|
|
441
491
|
/** Is `profile` derivable from a discovery document? Maps a profile name to its predicate (RFC 0089 §B(1)). */
|
|
442
492
|
export function profileDerivable(c, profile) {
|
|
@@ -457,11 +507,27 @@ const scenarioBasename = (id) => id.split('/').pop() ?? id;
|
|
|
457
507
|
export function verifyBundleProfile(bundle, profile) {
|
|
458
508
|
const derivable = profileDerivable(bundle.discovery.document, profile);
|
|
459
509
|
const floor = PROFILE_FLOOR_SCENARIOS[profile];
|
|
510
|
+
// An UNDEFINED floor set makes §B(2) unevaluable, so the claim is unprovable —
|
|
511
|
+
// never proven. Reading the old code, `missingFloor` fell to `[]` and
|
|
512
|
+
// `prefixOk` to `[].every(...)` === true, so `floorProven` came out TRUE for
|
|
513
|
+
// every profile this map omitted: the verifier reported a claim as floor-proven
|
|
514
|
+
// having checked nothing. An empty floor that is a real decision says so with
|
|
515
|
+
// `discoveryOnly` (RFC 0148 §C, gap G6).
|
|
516
|
+
if (floor === undefined) {
|
|
517
|
+
return { profile, derivable, floorProven: false, valid: false, missingFloor: [], floorUnspecified: true };
|
|
518
|
+
}
|
|
460
519
|
const passed = new Set(bundle.results.passed.map(scenarioBasename));
|
|
461
|
-
const missingFloor = floor
|
|
462
|
-
const prefixOk = (floor
|
|
520
|
+
const missingFloor = floor.required.filter((r) => !passed.has(scenarioBasename(r)));
|
|
521
|
+
const prefixOk = (floor.requiredAnyPrefix ?? []).every((p) => [...passed].some((s) => s.startsWith(p)));
|
|
463
522
|
const floorProven = missingFloor.length === 0 && prefixOk;
|
|
464
|
-
return {
|
|
523
|
+
return {
|
|
524
|
+
profile,
|
|
525
|
+
derivable,
|
|
526
|
+
floorProven,
|
|
527
|
+
valid: derivable && floorProven,
|
|
528
|
+
missingFloor,
|
|
529
|
+
floorUnspecified: false,
|
|
530
|
+
};
|
|
465
531
|
}
|
|
466
532
|
/** Verify every profile in `bundle.claimedProfiles`; the bundle is valid iff all claims are valid. */
|
|
467
533
|
export function verifyBundle(bundle) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openwop/openwop-conformance",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.98.0",
|
|
4
4
|
"description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"coverage.md",
|
|
21
21
|
"api",
|
|
22
22
|
"schemas",
|
|
23
|
+
"vectors",
|
|
23
24
|
"vitest.config.ts",
|
|
24
25
|
"README.md",
|
|
25
26
|
"CHANGELOG.md",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "Provenance of this vendored schemas/ copy. See conformance/README.md \u00a7\"Resolving the contract\". Compare against the stamp in your installed @openwop/openwop-conformance to detect a stale hand-copied contract.",
|
|
3
|
-
"suiteVersion": "1.
|
|
4
|
-
"corpusCommit": "
|
|
3
|
+
"suiteVersion": "1.98.0",
|
|
4
|
+
"corpusCommit": "74e2402caae98a97d16a2d8d56f1b46aae759741"
|
|
5
5
|
}
|
package/schemas/README.md
CHANGED
|
@@ -87,6 +87,8 @@
|
|
|
87
87
|
| `suspend-request.schema.json` | `interrupt.md` | `InterruptPayload` with 8 `kind` discriminators (approval, clarification, external-event, custom, conversation.start, conversation.exchange, conversation.close, low-confidence) |
|
|
88
88
|
| `workflow-chain-pack-manifest.schema.json` | `workflow-chain-packs.md` + RFC 0013 | Manifest for workflow-chain packs (`kind: "workflow-chain"`) — pre-configured DAG fragments expanded inline at workflow-author time. Peer to `node-pack-manifest.schema.json`; disjoint via the `kind` discriminator. |
|
|
89
89
|
| `workflow-definition.schema.json` | `channels-and-reducers.md` + `node-packs.md` | DAG of nodes + edges + triggers + variables + channels |
|
|
90
|
+
| `certification-bundle-v2.schema.json` | `profiles.md` + RFC 0148 §C | Certification bundle v2 — per-requirement dispositions instead of pass/fail/skip file lists. `blocked` is a REQUIRED total because `blocked: 0` asserted differs from `blocked` unstated, and `assertionCount` makes an `executed-pass` that ran nothing visible in the artifact |
|
|
91
|
+
| `workload-identity.schema.json` | `auth.md` + RFC 0154 §A/§B | Verified workload identity + delegated actor chain. Closed by design: raw certificates, tokens, proofs, and credentials MUST NOT enter these objects — `subject` is opaque and `proofRef`/`thumbprintRef` are digest references, because these objects reach events, spans, and audit records |
|
|
90
92
|
| `workspace-file.schema.json` | `agent-workspace.md` + `RFCS/0059` | RFC 0059 — a versioned workspace file (`{path, content, version, etag, updatedAt}`); response of `GET/PUT /v1/host/workspace/files/{path}`. |
|
|
91
93
|
| `workspace-file-create.schema.json` | `agent-workspace.md` + `RFCS/0059` | RFC 0059 — `PUT /v1/host/workspace/files/{path}` request body (content + optional contentType; path from the URL, version/etag host-assigned). |
|
|
92
94
|
|