@openwop/openwop-conformance 1.152.0 → 1.154.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +9 -0
  3. package/dist/cli.js +7 -2
  4. package/dist/lib/requirement-ids.js +100 -0
  5. package/dist/lib/scenario-disposition.js +22 -0
  6. package/package.json +33 -2
  7. package/requirement-aliases.json +4 -0
  8. package/requirements.json +23554 -0
  9. package/schemas/CORPUS-STAMP.json +99 -3
  10. package/src/cli.ts +7 -5
  11. package/src/global-setup.ts +13 -0
  12. package/src/lib/corpus-stamp.ts +125 -0
  13. package/src/lib/requirement-ids.ts +111 -0
  14. package/src/lib/scenario-disposition.ts +22 -0
  15. package/src/setup.ts +84 -5
  16. package/src/lib/capabilities-auth-subject-link.test.ts +0 -103
  17. package/src/lib/fork-availability.test.ts +0 -69
  18. package/src/lib/global-setup.test.ts +0 -76
  19. package/src/lib/grpc-framing.test.ts +0 -96
  20. package/src/lib/oidc-issuer.test.ts +0 -328
  21. package/src/lib/otel-collector-grpc.test.ts +0 -191
  22. package/src/lib/otel-collector.test.ts +0 -303
  23. package/src/lib/otlp-protobuf.test.ts +0 -461
  24. package/src/lib/polling.test.ts +0 -80
  25. package/src/lib/requirement-ledger.test.ts +0 -75
  26. package/src/lib/risk-disposition.test.ts +0 -91
  27. package/src/lib/saml-idp.test.ts +0 -127
  28. package/src/lib/spec-coherence-registry.test.ts +0 -155
  29. package/src/lib/webhook-receiver.test.ts +0 -144
  30. package/src/scenarios/artifact-schema-compile-bounded.test.ts +0 -126
  31. package/src/scenarios/artifact-type-legacy-ids.test.ts +0 -124
  32. package/src/scenarios/capability-example-root-layout.test.ts +0 -272
  33. package/src/scenarios/certification-floor-enforcement.test.ts +0 -204
  34. package/src/scenarios/chain-subchain-unsupported-refused.test.ts +0 -70
  35. package/src/scenarios/compensation-profile.test.ts +0 -340
  36. package/src/scenarios/core-manifest-and-extension-registry.test.ts +0 -250
  37. package/src/scenarios/discovery-canonical-family-no-shadow.test.ts +0 -219
  38. package/src/scenarios/edge-condition-truthy-falsy.test.ts +0 -108
  39. package/src/scenarios/effect-identity-composition.test.ts +0 -129
  40. package/src/scenarios/effect-identity-cross-scope.test.ts +0 -82
  41. package/src/scenarios/error-envelope-canonical-shape.test.ts +0 -64
  42. package/src/scenarios/form-content-packs.test.ts +0 -415
  43. package/src/scenarios/multi-region-effect-vocabulary.test.ts +0 -175
  44. package/src/scenarios/normative-example-extraction.test.ts +0 -242
  45. package/src/scenarios/openapi-asyncapi-sdk-parity.test.ts +0 -309
  46. package/src/scenarios/pack-manifest-extensions.test.ts +0 -203
  47. package/src/scenarios/protocol-version-grammar.test.ts +0 -119
  48. package/src/scenarios/registry-declarative-kinds.test.ts +0 -121
  49. package/src/scenarios/rfc-0147-self-audit.test.ts +0 -104
  50. package/src/scenarios/rfc-lifecycle-coherence.test.ts +0 -215
  51. package/src/scenarios/semantic-digest-v2.test.ts +0 -128
  52. package/src/scenarios/spec-corpus-validity.test.ts +0 -1727
  53. package/src/scenarios/spec-section-citations.test.ts +0 -132
  54. package/src/scenarios/tool-result-trust-monotone.test.ts +0 -168
  55. package/src/scenarios/versioned-composition-profiles.test.ts +0 -201
  56. package/src/scenarios/workflow-chain-internal-flag.test.ts +0 -84
  57. package/src/scenarios/workload-identity-profile.test.ts +0 -184
@@ -1,203 +0,0 @@
1
- /**
2
- * Pack-manifest vendor extensions — RFC 0138.
3
- *
4
- * `host-extensions.md` §"Vendor-prefixed namespaces" carries a normative MUST:
5
- * *"A client receiving an unknown vendor-prefixed field MUST treat it as
6
- * opaque."* Every pack manifest, however, set `additionalProperties: false`
7
- * with no pattern escape — so a vendor-prefixed field could not legally exist
8
- * on a pack manifest at all. **The corpus mandated a behavior for a case it
9
- * structurally forbade**, and a host with a working extension had to choose
10
- * between shipping it and publishing a conformant pack. RFC 0138 adds an
11
- * `^(x-|vendor\.)` escape hatch on each manifest root and each kind's per-item
12
- * entry object.
13
- *
14
- * Always-on + server-free. Three parts:
15
- *
16
- * PART 1 — every pack manifest admits the hatch, INCLUDING the registry
17
- * publication contract. Without the hatch on `registry-version-manifest`, a
18
- * pack carrying a root-level extension validates against its source manifest
19
- * and is then rejected at registry `PUT` — the same split-brain, one layer
20
- * down. Enumerated by an explicit in-scope count, not a naming glob, so a new
21
- * pack kind that forgets the hatch fails rather than being skipped.
22
- *
23
- * PART 2 — the hatch is NARROW. A misspelled canonical field
24
- * (`dispalyName`) is still rejected. This is the regression guard that keeps
25
- * `additionalProperties: false` doing its real job: the hatch admits
26
- * DECLARED extensions, not arbitrary keys. A change that widened the pattern
27
- * to `^.*` would pass PART 1 and fail here.
28
- *
29
- * PART 3 — the corpus states the opacity + trust rules normatively. The
30
- * schema alone cannot express "MUST ignore"; without the prose the hatch is
31
- * just a hole. Guards the `pack-manifest-extension-opaque` invariant text.
32
- *
33
- * @see spec/v1/node-packs.md §"Vendor extensions on pack manifests"
34
- * @see spec/v1/host-extensions.md §"Vendor-prefixed namespaces"
35
- * @see RFCS/0138-pack-manifest-vendor-extensions.md
36
- * @see SECURITY/invariants.yaml `pack-manifest-extension-opaque`
37
- */
38
-
39
- import { describe, it, expect } from 'vitest';
40
- import { readdirSync, readFileSync } from 'node:fs';
41
- import { join } from 'node:path';
42
- import Ajv2020 from 'ajv/dist/2020.js';
43
- import addFormats from 'ajv-formats';
44
- import { SCHEMAS_DIR, V1_DIR } from '../lib/paths.js';
45
-
46
- const why = (specRef: string, requirement: string): string => `${specRef} — ${requirement}`;
47
-
48
- /** The canonical hatch pattern. Kept as a literal so a drift in any schema is caught. */
49
- const HATCH = '^(x-|vendor\\.)';
50
-
51
- /**
52
- * Manifest schemas RFC 0138 deliberately does NOT cover. Anything else matching
53
- * `*manifest*.schema.json` MUST carry the hatch — so a new pack kind is caught
54
- * rather than silently omitted.
55
- *
56
- * The first draft of this file globbed `*-pack-manifest.schema.json`, which
57
- * silently skipped `frontend-plugin-manifest.schema.json` (RFC 0117,
58
- * `kind: "frontend-plugin"`) purely because it does not carry `-pack-` in its
59
- * name. Enumerating by naming convention is how a coverage hole hides; the
60
- * exclusions below are a stated list, not an accident of a glob.
61
- */
62
- const OUT_OF_SCOPE: Record<string, string> = {
63
- // Not pack-manifest structure — their own contracts with their own
64
- // compatibility surface. Stated in node-packs.md §Vendor extensions.
65
- 'agent-manifest.schema.json': "a node pack's agents[] entries — separate contract",
66
- };
67
-
68
- const manifestFiles = readdirSync(SCHEMAS_DIR)
69
- .filter((f) => f.includes('manifest') && f.endsWith('.schema.json'))
70
- .filter((f) => !(f in OUT_OF_SCOPE))
71
- .sort();
72
-
73
- const load = (f: string): Record<string, unknown> =>
74
- JSON.parse(readFileSync(join(SCHEMAS_DIR, f), 'utf8'));
75
-
76
- /** Walks a schema and returns every object node that declares `additionalProperties: false`. */
77
- function closedObjects(root: unknown): Array<Record<string, unknown>> {
78
- const out: Array<Record<string, unknown>> = [];
79
- (function walk(node: unknown): void {
80
- if (node === null || typeof node !== 'object') return;
81
- const o = node as Record<string, unknown>;
82
- if (o['additionalProperties'] === false) out.push(o);
83
- for (const k of Object.keys(o)) walk(o[k]);
84
- })(root);
85
- return out;
86
- }
87
-
88
- describe('pack-manifest-extensions: every manifest admits the hatch (RFC 0138, server-free)', () => {
89
- it('every pack kind is covered — the enumeration is not a naming-convention glob', () => {
90
- // 8 source manifests (node, workflow-chain, prompt, artifact-type, card,
91
- // connection, form-content, frontend-plugin) + the registry publication contract.
92
- expect(
93
- manifestFiles.length,
94
- why('RFC 0138', `expected 9 in-scope manifests, found ${manifestFiles.length}: ${manifestFiles.join(', ')}. A new pack kind MUST be added here, or listed in OUT_OF_SCOPE with a reason.`),
95
- ).toBe(9);
96
- });
97
-
98
- for (const f of manifestFiles) {
99
- it(`${f.replace(/-?(pack-)?manifest\.schema\.json$/, '')} — manifest ROOT carries the extension hatch`, () => {
100
- const s = load(f);
101
- const pp = s['patternProperties'] as Record<string, unknown> | undefined;
102
- expect(
103
- pp !== undefined && Object.keys(pp).includes(HATCH),
104
- why('node-packs.md §Vendor extensions on pack manifests', `${f} root MUST admit ^(x-|vendor\\.) so the host-extensions.md opacity MUST is satisfiable`),
105
- ).toBe(true);
106
- });
107
- }
108
- });
109
-
110
- describe('pack-manifest-extensions: the hatch is NARROW — typos still rejected (RFC 0138)', () => {
111
- const ajv = new Ajv2020({ allErrors: true, strict: false });
112
- addFormats(ajv);
113
-
114
- // artifact-type is the motivating kind (a real host carried `x-openwop-app.canvas`
115
- // on an artifactTypes[] entry and could not publish the pack).
116
- const validate = ajv.compile(load('artifact-type-pack-manifest.schema.json'));
117
- const pack = (extra: Record<string, unknown>): Record<string, unknown> => ({
118
- name: 'community.openwop.canvas-checklist',
119
- version: '1.0.0',
120
- kind: 'artifact-type',
121
- engines: { openwop: '>=1.1.0 <2.0.0' },
122
- artifactTypes: [
123
- { artifactTypeId: 'community.openwop.doc.checklist', schemaRef: 'schemas/checklist.json', ...extra },
124
- ],
125
- });
126
-
127
- it('an `x-` extension on an entry VALIDATES (the motivating case)', () => {
128
- expect(
129
- validate(pack({ 'x-openwop-app.canvas': { components: ['checklist'] } })),
130
- why('node-packs.md §Vendor extensions on pack manifests', 'a host extension MUST NOT make the pack unpublishable'),
131
- ).toBe(true);
132
- });
133
-
134
- it('a `vendor.` extension on an entry VALIDATES', () => {
135
- expect(
136
- validate(pack({ 'vendor.acme.rating': 5 })),
137
- why('host-extensions.md §Vendor-prefixed namespaces', 'vendor-prefixed fields are legitimate'),
138
- ).toBe(true);
139
- });
140
-
141
- it('a MISSPELLED canonical field is STILL REJECTED', () => {
142
- expect(
143
- validate(pack({ dispalyName: 'Checklist' })),
144
- why(
145
- 'node-packs.md §Vendor extensions on pack manifests',
146
- 'the hatch admits DECLARED extensions, not arbitrary keys — additionalProperties:false still catches typos. A pattern widened to ^.* would pass the other legs and fail here.',
147
- ),
148
- ).toBe(false);
149
- });
150
-
151
- it('an unextended pack still validates (RFC 0138 is additive)', () => {
152
- expect(validate(pack({})), why('COMPATIBILITY.md §2.1', 'existing manifests validate unchanged')).toBe(true);
153
- });
154
-
155
- it('the hatch pattern is not accidentally permissive', () => {
156
- for (const f of manifestFiles) {
157
- for (const node of closedObjects(load(f))) {
158
- for (const pat of Object.keys((node['patternProperties'] as Record<string, unknown>) ?? {})) {
159
- const re = new RegExp(pat);
160
- expect(re.test('dispalyName'), why('RFC 0138', `${f}: pattern ${pat} MUST NOT match a bare canonical-looking key`)).toBe(false);
161
- expect(re.test('x-anything'), why('RFC 0138', `${f}: pattern ${pat} admits x- extensions`)).toBe(true);
162
- }
163
- }
164
- }
165
- });
166
- });
167
-
168
- describe('pack-manifest-extensions: the corpus states opacity + trust normatively (RFC 0138)', () => {
169
- const packsDoc = V1_DIR ? readFileSync(join(V1_DIR, 'node-packs.md'), 'utf8') : '';
170
- const extDoc = V1_DIR ? readFileSync(join(V1_DIR, 'host-extensions.md'), 'utf8') : '';
171
-
172
- it.skipIf(V1_DIR === null)('an unrecognized extension MUST be ignored, not rejected', () => {
173
- expect(
174
- /MUST ignore it\W{0,4}\s*and\W{0,4}\s*MUST NOT\W{0,4}\s*reject the pack/i.test(packsDoc),
175
- why('node-packs.md §Vendor extensions on pack manifests', 'unrecognized extensions MUST be ignored, never a rejection reason'),
176
- ).toBe(true);
177
- });
178
-
179
- it.skipIf(V1_DIR === null)('"ignore" is defined — not render, execute, interpret, or persist-for-later', () => {
180
- expect(
181
- /MUST NOT render it, execute it, interpret it as markup/i.test(packsDoc),
182
- why('node-packs.md §Vendor extensions on pack manifests', '"ignore" means ignore — the hatch MUST NOT become a rendering or execution channel'),
183
- ).toBe(true);
184
- expect(
185
- /pack-authored content/i.test(packsDoc) && /untrusted/i.test(packsDoc),
186
- why('node-packs.md §Vendor extensions on pack manifests', 'an extension value is pack-authored, therefore untrusted'),
187
- ).toBe(true);
188
- });
189
-
190
- it.skipIf(V1_DIR === null)('extensions are NOT a capability-negotiation channel', () => {
191
- expect(
192
- /NOT\W{0,4}\s*a versioning or capability-negotiation channel/i.test(packsDoc),
193
- why('node-packs.md §Vendor extensions on pack manifests', 'a host MUST NOT infer support from an extension property'),
194
- ).toBe(true);
195
- });
196
-
197
- it.skipIf(V1_DIR === null)('host-extensions.md still carries the opacity MUST the hatch exists to satisfy', () => {
198
- expect(
199
- /unknown vendor-prefixed field MUST treat it as opaque/i.test(extDoc),
200
- why('host-extensions.md §Vendor-prefixed namespaces', 'the MUST that motivated RFC 0138 is still present'),
201
- ).toBe(true);
202
- });
203
- });
@@ -1,119 +0,0 @@
1
- /**
2
- * RFC 0149 §C — `protocolVersion` is `<major>.<minor>`, and the corpus enforces it.
3
- *
4
- * The field was specified three incompatible ways at once. `capabilities.schema.json`
5
- * constrained it to `minLength: 1` — so `"v1.0"`, `"1.0.0"`, `"01.0"`, and `"banana"` all
6
- * validated. `profiles.ts` derived core-ness from `startsWith('1.')`, which admits
7
- * `"1.0.0"` and `"1.banana"` while rejecting a legitimate future `"2.0"` for the right
8
- * reason and `"1"` for the wrong one. Prose described it as semver while every example
9
- * showed two components.
10
- *
11
- * The consequence is a negotiation the wire cannot decide. Version comparison needs an
12
- * integer major as the hard compatibility boundary and an integer minor as the additive
13
- * contract level. Neither can be extracted from a string the schema never constrained, so
14
- * two hosts could advertise `"1.0"` and `"1.0.0"` and no consumer could tell whether it
15
- * was looking at a patch convention, a typo, or a different protocol.
16
- *
17
- * RFC 0149 §C: ASCII `<major>.<minor>`, no leading zero except zero itself. Patch belongs
18
- * to suite and SDK versions, not the spec version. This also closes gap V2 in
19
- * `version-negotiation.md` §"Open spec gaps" — "concrete `protocolVersion` semver
20
- * semantics" — which had sat open with owner `future`.
21
- *
22
- * Server-free. The schema ships in both the repository and the published tarball; the
23
- * prose leg self-skips under the published layout.
24
- */
25
-
26
- import { describe, it, expect } from 'vitest';
27
- import { readFileSync } from 'node:fs';
28
- import { join, resolve as pathResolve } from 'node:path';
29
- import { V1_DIR } from '../lib/paths.js';
30
- import { isCore } from '../lib/profiles.js';
31
-
32
- /** RFC 0149 §C. */
33
- const GRAMMAR = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/;
34
-
35
- const SCHEMA_PATH =
36
- V1_DIR === null ? null : pathResolve(V1_DIR, '..', '..', 'schemas', 'capabilities.schema.json');
37
-
38
- function protocolVersionSchema(): { pattern?: string } | null {
39
- if (SCHEMA_PATH === null) return null;
40
- const schema = JSON.parse(readFileSync(SCHEMA_PATH, 'utf8')) as {
41
- properties?: Record<string, { pattern?: string }>;
42
- };
43
- return schema.properties?.['protocolVersion'] ?? null;
44
- }
45
-
46
- /** A discovery payload that is core-valid except for the version under test. */
47
- function payload(protocolVersion: unknown) {
48
- return {
49
- protocolVersion,
50
- supportedEnvelopes: ['plan'],
51
- schemaVersions: {},
52
- limits: { clarificationRounds: 1, schemaRounds: 1, envelopesPerTurn: 1 },
53
- };
54
- }
55
-
56
- const VALID = ['1.0', '1.12', '0.0', '2.0', '10.3'];
57
- const INVALID = ['1', '1.0.0', 'v1.0', '01.0', '1.', '.0', '1.0-rc1', '', ' 1.0', '1.0 '];
58
-
59
- describe.skipIf(V1_DIR === null)('RFC 0149 §C — protocolVersion grammar', () => {
60
- it('the schema declares protocolVersion at all', () => {
61
- // Guard: a lookup returning null would make the pattern leg vacuous.
62
- expect(protocolVersionSchema(), 'capabilities.schema.json MUST declare `protocolVersion`').not.toBeNull();
63
- });
64
-
65
- it('the schema constrains protocolVersion by pattern, not merely by length', () => {
66
- const node = protocolVersionSchema();
67
- expect(
68
- node?.pattern,
69
- 'RFC 0149 §C: `minLength: 1` admits `"v1.0"`, `"1.0.0"`, and `"banana"`. Compatibility ' +
70
- 'comparison needs an integer major and an integer minor, which cannot be extracted from ' +
71
- 'an unconstrained string.',
72
- ).toBe(GRAMMAR.source);
73
- });
74
-
75
- it.each(VALID)('accepts %s', (v) => {
76
- expect(GRAMMAR.test(v), `RFC 0149 §C: ${v} is a legal major.minor`).toBe(true);
77
- });
78
-
79
- it.each(INVALID)('rejects %s', (v) => {
80
- expect(
81
- GRAMMAR.test(v),
82
- `RFC 0149 §C: ${JSON.stringify(v)} is not major.minor — patch belongs to suite and SDK ` +
83
- 'versions, and a leading zero is forbidden except for zero itself',
84
- ).toBe(false);
85
- });
86
-
87
- it('core derivation applies the grammar rather than a prefix test', () => {
88
- // `startsWith('1.')` admitted `1.0.0` and `1.banana` and rejected `1`. The
89
- // predicate that decides whether a host is openwop-compatible at all must
90
- // not be looser than the schema every host validates against.
91
- expect(isCore(payload('1.0')), '`1.0` is core-valid').toBe(true);
92
- for (const bad of ['1.0.0', '1.banana', '1', 'v1.0', '01.0']) {
93
- expect(
94
- isCore(payload(bad)),
95
- `RFC 0149 §C: \`${bad}\` MUST NOT derive \`openwop-core\` — the predicate cannot be ` +
96
- 'more permissive than the grammar',
97
- ).toBe(false);
98
- }
99
- });
100
-
101
- it('a different major is not core; a higher minor is', () => {
102
- // §C: consumers MUST reject a different unsupported major and MUST tolerate a
103
- // higher minor under v1 additive rules. The predicate is the v1 suite's, so a
104
- // v2 host is correctly not-core here — that is a major boundary, not a defect.
105
- expect(isCore(payload('2.0')), 'a different major is outside this suite').toBe(false);
106
- expect(isCore(payload('1.99')), 'a higher minor stays core under v1 additive rules').toBe(true);
107
- });
108
-
109
- it('the spec states the grammar normatively', () => {
110
- // Searched raw: the grammar contains `*` and `_`-adjacent metacharacters, so
111
- // the usual markdown-emphasis strip would eat its own quantifiers.
112
- const doc = readFileSync(join(V1_DIR as string, 'version-negotiation.md'), 'utf8');
113
- expect(
114
- doc.includes(GRAMMAR.source),
115
- 'RFC 0149 §C: `version-negotiation.md` MUST carry the grammar, so the schema pattern has ' +
116
- 'a normative source rather than being the only place the rule exists.',
117
- ).toBe(true);
118
- });
119
- });
@@ -1,121 +0,0 @@
1
- /**
2
- * Publishable declarative pack kinds (RFC 0107, `Active`).
3
- *
4
- * OpenWOP defines declarative pack kinds at the SOURCE level — artifact-type
5
- * (RFC 0075, `kind: "artifact-type"`) and connection (RFC 0095,
6
- * `kind: "connection"`). RFC 0107 extends the PUBLISHED contract,
7
- * `registry-version-manifest.schema.json`, so those kinds can be published to a
8
- * registry: it adds a `kind` discriminator (absent ≡ `node`), the per-kind
9
- * declarative payload (`artifactTypes[]` / `provider` / `chains[]` / …), and
10
- * makes `runtime` required only for executable kinds.
11
- *
12
- * Always-on + server-free. Two parts (mirrors the project's other
13
- * schema-contract scenarios):
14
- *
15
- * PART 1 — contract present. `registry-operations.md` §"Validation flow"
16
- * carries the kind-aware manifest-schema selection (#3) and the
17
- * skip-runtime-check-for-declarative-kinds rule (#7); the version-manifest
18
- * schema carries the `kind` enum + declarative payload + conditional runtime.
19
- * Guards against the requirement being silently dropped.
20
- *
21
- * PART 2 — schema admits declarative kinds and still rejects malformed ones.
22
- * A published artifact-type and a published connection version manifest
23
- * validate; a node manifest is unchanged; a declarative manifest carrying
24
- * `runtime` (forbidden) and a node manifest missing `runtime` are rejected.
25
- *
26
- * @see spec/v1/registry-operations.md §"Validation flow"
27
- * @see schemas/registry-version-manifest.schema.json
28
- * @see RFCS/0107-publishable-declarative-pack-kinds.md
29
- * @see RFCS/0075-artifact-type-packs-realworld-amendment.md, RFCS/0095-connection-packs-portable-provider-definitions.md
30
- */
31
-
32
- import { describe, it, expect } from 'vitest';
33
- import { readFileSync } from 'node:fs';
34
- import { join } from 'node:path';
35
- import Ajv2020 from 'ajv/dist/2020.js';
36
- import addFormats from 'ajv-formats';
37
- import { SCHEMAS_DIR, V1_DIR } from '../lib/paths.js';
38
-
39
- const why = (specRef: string, requirement: string): string => `${specRef} — ${requirement}`;
40
-
41
- describe('registry-declarative-kinds: contract present in the corpus (RFC 0107, server-free)', () => {
42
- const registryDoc = V1_DIR ? readFileSync(join(V1_DIR, 'registry-operations.md'), 'utf8') : '';
43
- const versionManifestSchema = JSON.parse(
44
- readFileSync(join(SCHEMAS_DIR, 'registry-version-manifest.schema.json'), 'utf8'),
45
- );
46
-
47
- it.skipIf(V1_DIR === null)('registry-operations.md §Validation flow selects the manifest schema by `kind`', () => {
48
- expect(
49
- /kind[\s\S]{0,120}artifact-type-pack-manifest\.schema\.json/.test(registryDoc),
50
- why('registry-operations.md §Validation flow #3', 'pack.json validates against the per-`kind` source schema (RFC 0107)'),
51
- ).toBe(true);
52
- });
53
-
54
- it.skipIf(V1_DIR === null)('registry-operations.md §Validation flow skips the runtime check for declarative kinds', () => {
55
- expect(
56
- /declarative[\s\S]{0,160}(skipped|MUST NOT)/i.test(registryDoc) && /runtime/i.test(registryDoc),
57
- why('registry-operations.md §Validation flow #7', 'runtime-support check is skipped for declarative kinds (RFC 0107)'),
58
- ).toBe(true);
59
- });
60
-
61
- it('registry-version-manifest.schema.json carries the `kind` discriminator + declarative payload + conditional runtime', () => {
62
- const props = versionManifestSchema.properties ?? {};
63
- expect(props.kind?.enum, why('registry-version-manifest.schema.json', '`kind` enum present')).toEqual(
64
- expect.arrayContaining(['node', 'artifact-type', 'connection']),
65
- );
66
- expect(!!props.artifactTypes && !!props.provider, why('registry-version-manifest.schema.json', 'declarative payload props present')).toBe(true);
67
- expect(
68
- (versionManifestSchema.required ?? []).includes('runtime'),
69
- why('registry-version-manifest.schema.json', '`runtime` is NOT top-level required (conditional on kind)'),
70
- ).toBe(false);
71
- expect(
72
- JSON.stringify(versionManifestSchema.allOf ?? []).includes('runtime'),
73
- why('registry-version-manifest.schema.json', '`allOf` if/then/else makes runtime conditional on kind'),
74
- ).toBe(true);
75
- });
76
- });
77
-
78
- describe('registry-declarative-kinds: version-manifest schema admits declarative kinds (RFC 0107, server-free)', () => {
79
- const ajv = new Ajv2020({ allErrors: true, strict: false });
80
- addFormats(ajv);
81
- const validate = ajv.compile(
82
- JSON.parse(readFileSync(join(SCHEMAS_DIR, 'registry-version-manifest.schema.json'), 'utf8')),
83
- );
84
-
85
- const base = { name: 'core.openwop.x', version: '1.0.0', engines: { openwop: '>=1.0.0' }, integrity: 'sha256-abc=' };
86
-
87
- it('a published artifact-type version manifest validates (kind + artifactTypes, no runtime)', () => {
88
- const ok = validate({ ...base, kind: 'artifact-type', artifactTypes: [{ artifactTypeId: 'doc.one-pager', title: 'One-Pager', schema: { type: 'object' } }] });
89
- expect(ok, why('registry-operations.md §Validation flow', 'artifact-type manifest publishes (RFC 0107)')).toBe(true);
90
- });
91
-
92
- it('a published connection version manifest validates (kind + provider, no runtime)', () => {
93
- const ok = validate({ ...base, kind: 'connection', provider: { id: 'github', category: 'dev' } });
94
- expect(ok, why('registry-operations.md §Validation flow', 'connection manifest publishes (RFC 0107)')).toBe(true);
95
- });
96
-
97
- it('a published chat-card version manifest validates (kind: "card" per RFC 0071, + cards, no runtime)', () => {
98
- const ok = validate({ ...base, kind: 'card', cards: [{ cardTypeId: 'core.openwop.card.summarize', prompt: { template: 'Summarize: {{input}}' } }] });
99
- expect(ok, why('registry-operations.md §Validation flow', 'chat-card pack publishes with kind "card" (RFC 0071 / RFC 0107) — NOT "chat-card"')).toBe(true);
100
- });
101
-
102
- it('the legacy "chat-card" kind string is REJECTED (RFC 0071 kind is "card")', () => {
103
- const ok = validate({ ...base, kind: 'chat-card', cards: [{ cardTypeId: 'core.openwop.card.x', prompt: { template: 'x' } }] });
104
- expect(ok, why('registry-version-manifest.schema.json', 'kind enum is "card", not "chat-card" (corrected 2026-06-23)')).toBe(false);
105
- });
106
-
107
- it('an unchanged node version manifest still validates (no kind, runtime + nodes)', () => {
108
- const ok = validate({ ...base, runtime: { language: 'javascript' }, nodes: [{ typeId: 'core.openwop.x.n', version: '1.0.0', category: 'data', role: 'pure' }] });
109
- expect(ok, why('COMPATIBILITY.md §2.1', 'RFC 0107 is additive — node manifests validate unchanged')).toBe(true);
110
- });
111
-
112
- it('a declarative manifest carrying `runtime` is REJECTED (declarative kinds carry no runtime)', () => {
113
- const ok = validate({ ...base, kind: 'artifact-type', artifactTypes: [{ artifactTypeId: 'doc.x' }], runtime: { language: 'javascript' } });
114
- expect(ok, why('registry-version-manifest.schema.json', 'declarative kind MUST NOT carry runtime')).toBe(false);
115
- });
116
-
117
- it('a node manifest MISSING `runtime` is still REJECTED (executable kinds require runtime)', () => {
118
- const ok = validate({ ...base, nodes: [{ typeId: 'core.openwop.x.n', version: '1.0.0', category: 'data', role: 'pure' }] });
119
- expect(ok, why('registry-version-manifest.schema.json', 'executable kind (node/absent) still requires runtime')).toBe(false);
120
- });
121
- });
@@ -1,104 +0,0 @@
1
- /**
2
- * RFC 0147 §A.10 — the program audits itself, and silence is not compliance.
3
- *
4
- * §A states ten invariants that "apply to every workstream". §A.10 forbids using
5
- * the RFC's existence or partial implementation as evidence its gaps are closed.
6
- * A program that audits everything except itself has the same defect it was
7
- * written to fix, one level up.
8
- *
9
- * This gate does NOT assert the program is compliant. It asserts that every §A
10
- * invariant carries an **explicit disposition** — including the ones recorded as
11
- * VIOLATED. That is the RFC 0148 §A design applied to governance: an invariant
12
- * with no row is uncovered, not satisfied, and a self-audit that quietly omits
13
- * its uncomfortable rows is worth less than none because it looks like coverage.
14
- *
15
- * Two rows are currently VIOLATED and the gate is green, which is the intended
16
- * behavior. Making the gate fail on a violation would create pressure to delete
17
- * the row rather than fix the program — the failure mode RFC 0149 §D measured
18
- * when it declined to ship a gate that fires 69 times on its first run.
19
- *
20
- * Server-free.
21
- */
22
-
23
- import { describe, it, expect } from 'vitest';
24
- import { readFileSync, existsSync } from 'node:fs';
25
- import { resolve, dirname, join } from 'node:path';
26
- import { fileURLToPath } from 'node:url';
27
- import { V1_DIR } from '../lib/paths.js';
28
-
29
- const AUDIT = V1_DIR === null
30
- ? null
31
- : join(resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..'), 'docs', 'RFC-0147-SELF-AUDIT.md');
32
-
33
- /** The ten §A invariants, by their heading anchor. */
34
- const INVARIANTS = [
35
- 'A.1',
36
- 'A.2',
37
- 'A.3',
38
- 'A.4',
39
- 'A.5',
40
- 'A.6',
41
- 'A.7',
42
- 'A.8',
43
- 'A.9',
44
- 'A.10',
45
- ] as const;
46
-
47
- const DISPOSITIONS = /satisfied|VIOLATED|not satisfied|partially satisfied/;
48
-
49
- const AUDIT_PRESENT = AUDIT !== null && existsSync(AUDIT);
50
-
51
- describe.skipIf(!AUDIT_PRESENT)('RFC 0147 §A.10 — program self-audit', () => {
52
- // `describe.skipIf` still EXECUTES this factory — it decides afterwards not to
53
- // run the tests it collected. So a read here throws at collection time even
54
- // when the suite is destined to skip, and one file's collection error takes
55
- // the whole file down rather than skipping it. The audit lives under `docs/`,
56
- // which is not bundled, so this is exactly the published-layout path.
57
- const doc = AUDIT_PRESENT ? readFileSync(AUDIT as string, 'utf8') : '';
58
-
59
- it('the audit exists and is substantive', () => {
60
- // Guard: a stub file would make every leg below vacuous, and this gate's
61
- // whole purpose is that an omitted row is visible.
62
- expect(doc.length, 'the self-audit MUST be substantive').toBeGreaterThan(2000);
63
- expect(doc).toMatch(/RFC 0147/);
64
- });
65
-
66
- it('every §A invariant carries an explicit disposition', () => {
67
- const missing = INVARIANTS.filter((id) => {
68
- const heading = new RegExp(`^## ${id.replace('.', '\\.')} — `, 'm');
69
- const at = heading.exec(doc);
70
- if (at === null) return true;
71
- // The disposition must appear in that section, not merely somewhere.
72
- const section = doc.slice(at.index, doc.indexOf('\n## ', at.index + 1) + 1 || undefined);
73
- return !DISPOSITIONS.test(section);
74
- });
75
- expect(
76
- missing,
77
- 'RFC 0147 §A.10: an invariant with no recorded disposition is UNCOVERED, not satisfied. ' +
78
- 'A self-audit that omits its uncomfortable rows is worth less than none, because it ' +
79
- 'looks like coverage.',
80
- ).toEqual([]);
81
- });
82
-
83
- it('the audit records the violations it found rather than only the wins', () => {
84
- // The load-bearing leg. An audit that reported nothing adverse would be
85
- // indistinguishable from one nobody ran — and this program has two known
86
- // violations, so a clean sheet here means the file stopped being honest.
87
- expect(
88
- /VIOLATED/.test(doc),
89
- 'RFC 0147 §A.5 and §A.6 are currently violated: four RFCs reached `Accepted` with no ' +
90
- 'evidence, and five high-risk RFCs had their comment windows waived by the exact ' +
91
- 'bootstrap mechanism §A.6 says must not shorten them. If those rows have been removed ' +
92
- 'rather than resolved, this leg is the thing that notices.',
93
- ).toBe(true);
94
- });
95
-
96
- it('the audit does not claim the exit criteria are met', () => {
97
- // §A.10, applied to the audit itself.
98
- expect(
99
- /exit criteria in RFC 0147 are not met|exit criteria .{0,40}not met/i.test(doc),
100
- 'RFC 0147 §A.10: the program MUST NOT be read as closed. Three remaining gates — external ' +
101
- 'audit, second maintainer, Tier-3 host — cannot be closed by work in this repository.',
102
- ).toBe(true);
103
- });
104
- });