@hypequery/protocol-conformance 0.0.0-canary-20260722115402

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 (80) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +40 -0
  3. package/dist/adapters/generators.d.ts +13 -0
  4. package/dist/adapters/generators.d.ts.map +1 -0
  5. package/dist/adapters/generators.js +366 -0
  6. package/dist/adapters/reference.d.ts +6 -0
  7. package/dist/adapters/reference.d.ts.map +1 -0
  8. package/dist/adapters/reference.js +172 -0
  9. package/dist/adapters/stdio.d.ts +17 -0
  10. package/dist/adapters/stdio.d.ts.map +1 -0
  11. package/dist/adapters/stdio.js +80 -0
  12. package/dist/bin/conformance.d.ts +3 -0
  13. package/dist/bin/conformance.d.ts.map +1 -0
  14. package/dist/bin/conformance.js +84 -0
  15. package/dist/bin/reference-adapter.d.ts +3 -0
  16. package/dist/bin/reference-adapter.d.ts.map +1 -0
  17. package/dist/bin/reference-adapter.js +16 -0
  18. package/dist/compare.d.ts +3 -0
  19. package/dist/compare.d.ts.map +1 -0
  20. package/dist/compare.js +125 -0
  21. package/dist/fs.d.ts +11 -0
  22. package/dist/fs.d.ts.map +1 -0
  23. package/dist/fs.js +36 -0
  24. package/dist/index.d.ts +11 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +9 -0
  27. package/dist/manifest.d.ts +13 -0
  28. package/dist/manifest.d.ts.map +1 -0
  29. package/dist/manifest.js +118 -0
  30. package/dist/report.d.ts +4 -0
  31. package/dist/report.d.ts.map +1 -0
  32. package/dist/report.js +23 -0
  33. package/dist/runner.d.ts +12 -0
  34. package/dist/runner.d.ts.map +1 -0
  35. package/dist/runner.js +197 -0
  36. package/dist/types.d.ts +84 -0
  37. package/dist/types.d.ts.map +1 -0
  38. package/dist/types.js +5 -0
  39. package/fixtures/deployment-bundles-v1/README.md +61 -0
  40. package/fixtures/deployment-bundles-v1/identity.json +7 -0
  41. package/fixtures/deployment-bundles-v1/rejections.json +11 -0
  42. package/fixtures/deployment-bundles-v1/success.json +23 -0
  43. package/fixtures/deployment-releases-v1/README.md +36 -0
  44. package/fixtures/deployment-releases-v1/identity.json +7 -0
  45. package/fixtures/deployment-releases-v1/rejections.json +8 -0
  46. package/fixtures/deployment-releases-v1/success.json +14 -0
  47. package/fixtures/deployments-v1/README.md +77 -0
  48. package/fixtures/deployments-v1/identity.json +7 -0
  49. package/fixtures/deployments-v1/rejections.json +12 -0
  50. package/fixtures/deployments-v1/success.json +81 -0
  51. package/fixtures/expressions-v1/README.md +25 -0
  52. package/fixtures/expressions-v1/rejections.json +17 -0
  53. package/fixtures/expressions-v1/success.json +47 -0
  54. package/fixtures/fuzz-seeds-v1/README.md +29 -0
  55. package/fixtures/fuzz-seeds-v1/sql-expressions.json +32 -0
  56. package/fixtures/fuzz-seeds-v1/structured-values.json +32 -0
  57. package/fixtures/fuzz-seeds-v1/value-sources.json +191 -0
  58. package/fixtures/identifiers-v1/README.md +15 -0
  59. package/fixtures/identifiers-v1/rejections.json +58 -0
  60. package/fixtures/identifiers-v1/success.json +26 -0
  61. package/fixtures/manifest.json +117 -0
  62. package/fixtures/query-diagnostics-v1/README.md +49 -0
  63. package/fixtures/query-diagnostics-v1/rejections.json +13 -0
  64. package/fixtures/query-diagnostics-v1/success.json +27 -0
  65. package/fixtures/query-events-v1/README.md +60 -0
  66. package/fixtures/query-events-v1/rejections.json +18 -0
  67. package/fixtures/query-events-v1/success.json +40 -0
  68. package/fixtures/query-implementations-v1/README.md +33 -0
  69. package/fixtures/query-implementations-v1/rejections.json +80 -0
  70. package/fixtures/query-implementations-v1/success.json +96 -0
  71. package/fixtures/query-schemas-v1/README.md +25 -0
  72. package/fixtures/query-schemas-v1/rejections.json +15 -0
  73. package/fixtures/query-schemas-v1/success.json +17 -0
  74. package/fixtures/sql-portability-v1/README.md +15 -0
  75. package/fixtures/sql-portability-v1/non-portable.json +146 -0
  76. package/fixtures/sql-portability-v1/portable.json +346 -0
  77. package/fixtures/tagged-values-v1/README.md +44 -0
  78. package/fixtures/tagged-values-v1/rejections.json +298 -0
  79. package/fixtures/tagged-values-v1/success.json +262 -0
  80. package/package.json +52 -0
@@ -0,0 +1,118 @@
1
+ function decodePointerToken(token) {
2
+ return token.replace(/~1/g, '/').replace(/~0/g, '~');
3
+ }
4
+ /** Minimal RFC 6901 JSON pointer resolution for the section selectors. */
5
+ export function resolveJsonPointer(root, pointer) {
6
+ if (pointer === '')
7
+ return root;
8
+ if (!pointer.startsWith('/')) {
9
+ throw new Error(`Invalid JSON pointer: ${pointer}`);
10
+ }
11
+ let current = root;
12
+ for (const rawToken of pointer.slice(1).split('/')) {
13
+ const token = decodePointerToken(rawToken);
14
+ if (Array.isArray(current)) {
15
+ current = current[Number(token)];
16
+ }
17
+ else if (current !== null && typeof current === 'object') {
18
+ current = current[token];
19
+ }
20
+ else {
21
+ throw new Error(`JSON pointer ${pointer} does not resolve`);
22
+ }
23
+ }
24
+ return current;
25
+ }
26
+ function asCaseArray(value, context) {
27
+ if (!Array.isArray(value)) {
28
+ throw new Error(`Expected an array of cases at ${context}`);
29
+ }
30
+ return value;
31
+ }
32
+ function requireId(entry, context) {
33
+ const id = entry.id;
34
+ if (typeof id !== 'string' || id.length === 0) {
35
+ throw new Error(`Fixture case at ${context} is missing a string id`);
36
+ }
37
+ return id;
38
+ }
39
+ function enumerateFile(family, file, loadJson) {
40
+ const data = loadJson(file.path);
41
+ const cases = [];
42
+ const pushArray = (array, section) => {
43
+ for (const entry of array) {
44
+ cases.push({
45
+ family: family.name,
46
+ role: file.role,
47
+ id: requireId(entry, `${file.path}${section ?? ''}`),
48
+ section,
49
+ case: entry,
50
+ });
51
+ }
52
+ };
53
+ if (file.sections && file.sections.length > 0) {
54
+ for (const section of file.sections) {
55
+ pushArray(asCaseArray(resolveJsonPointer(data, section), `${file.path}${section}`), section);
56
+ }
57
+ }
58
+ else {
59
+ pushArray(asCaseArray(data, file.path));
60
+ }
61
+ return cases;
62
+ }
63
+ /** Cases derived from the family fixture files, in manifest order. */
64
+ export function enumerateFamilyCases(manifest, loadJson) {
65
+ return manifest.families.flatMap((family) => {
66
+ const cases = family.files.flatMap((file) => enumerateFile(family, file, loadJson));
67
+ // An identity case pins the canonical bytes and hash of the success case
68
+ // that shares its id. Attach that success value so an adapter can derive
69
+ // the canonical form without loading the success file itself.
70
+ const successValues = new Map();
71
+ for (const ec of cases) {
72
+ if (ec.role === 'success')
73
+ successValues.set(ec.id, ec.case.value);
74
+ }
75
+ return cases.map((ec) => {
76
+ if (ec.role !== 'identity')
77
+ return ec;
78
+ if (!successValues.has(ec.id)) {
79
+ throw new Error(`Identity case ${family.name}/${ec.id} has no matching success value`);
80
+ }
81
+ return { ...ec, case: { ...ec.case, value: successValues.get(ec.id) } };
82
+ });
83
+ });
84
+ }
85
+ /**
86
+ * Cases derived from the fuzz corpus. A seed with its own `targets` fans out
87
+ * to one case per target family; otherwise it uses the manifest entry family.
88
+ */
89
+ export function enumerateFuzzCases(manifest, loadJson) {
90
+ const cases = [];
91
+ for (const entry of manifest.fuzz) {
92
+ const seeds = asCaseArray(loadJson(entry.path), entry.path);
93
+ for (const seed of seeds) {
94
+ const seedId = requireId(seed, entry.path);
95
+ const targets = Array.isArray(seed.targets)
96
+ ? seed.targets
97
+ : entry.family !== undefined
98
+ ? [entry.family]
99
+ : [];
100
+ if (targets.length === 0) {
101
+ throw new Error(`Fuzz seed ${seedId} in ${entry.path} has no target family`);
102
+ }
103
+ const fanOut = targets.length > 1;
104
+ for (const family of targets) {
105
+ cases.push({
106
+ family,
107
+ role: 'fuzz',
108
+ id: fanOut ? `${seedId}@${family}` : seedId,
109
+ case: seed,
110
+ });
111
+ }
112
+ }
113
+ }
114
+ return cases;
115
+ }
116
+ export function enumerateAllCases(manifest, loadJson) {
117
+ return [...enumerateFamilyCases(manifest, loadJson), ...enumerateFuzzCases(manifest, loadJson)];
118
+ }
@@ -0,0 +1,4 @@
1
+ import type { RunSummary } from './types.js';
2
+ export declare function formatJsonReport(summary: RunSummary): string;
3
+ export declare function formatPrettyReport(summary: RunSummary): string;
4
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAE5D;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAsB9D"}
package/dist/report.js ADDED
@@ -0,0 +1,23 @@
1
+ export function formatJsonReport(summary) {
2
+ return JSON.stringify(summary, null, 2);
3
+ }
4
+ export function formatPrettyReport(summary) {
5
+ const lines = [];
6
+ const adapter = summary.adapter;
7
+ const label = adapter?.implementation
8
+ ? `${adapter.implementation}${adapter.version ? `@${adapter.version}` : ''}`
9
+ : 'adapter';
10
+ lines.push(`Conformance run against ${label}${adapter?.language ? ` (${adapter.language})` : ''}`);
11
+ for (const outcome of summary.outcomes) {
12
+ if (outcome.status === 'pass')
13
+ continue;
14
+ const mark = outcome.status === 'skip' ? 'SKIP' : 'FAIL';
15
+ const detail = [outcome.expected ? `expected ${outcome.expected}` : '', outcome.actual ? `got ${outcome.actual}` : '', outcome.message ?? '']
16
+ .filter(Boolean)
17
+ .join(', ');
18
+ lines.push(` ${mark} ${outcome.family}/${outcome.role}/${outcome.id}${detail ? ` — ${detail}` : ''}`);
19
+ }
20
+ lines.push(`${summary.passed} passed, ${summary.failed} failed, ${summary.skipped} skipped`
21
+ + (summary.notRun > 0 ? `, ${summary.notRun} not run (family not announced)` : ''));
22
+ return lines.join('\n');
23
+ }
@@ -0,0 +1,12 @@
1
+ import { type RunSummary } from './types.js';
2
+ export interface RunConformanceOptions {
3
+ readonly adapterCommand: readonly string[];
4
+ readonly fixturesDir?: string;
5
+ /** Restrict to these families (intersected with what the adapter announces). */
6
+ readonly families?: readonly string[];
7
+ readonly timeoutMs?: number;
8
+ readonly skipFuzz?: boolean;
9
+ readonly onlyFuzz?: boolean;
10
+ }
11
+ export declare function runConformance(options: RunConformanceOptions): Promise<RunSummary>;
12
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAQA,OAAO,EAML,KAAK,UAAU,EAChB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AA6FD,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CA4FxF"}
package/dist/runner.js ADDED
@@ -0,0 +1,197 @@
1
+ // Drives an adapter over NDJSON: handshake, then one case at a time with a
2
+ // per-case timeout. On a premature adapter exit the runner respawns once and
3
+ // continues; a second premature exit fails the run.
4
+ import { spawn } from 'node:child_process';
5
+ import { createInterface } from 'node:readline';
6
+ import { compareCase } from './compare.js';
7
+ import { createJsonLoader, loadManifest, resolveFixturesDir } from './fs.js';
8
+ import { enumerateAllCases } from './manifest.js';
9
+ import { CONFORMANCE_MANIFEST_VERSION, CONFORMANCE_PROTOCOL_VERSION, } from './types.js';
10
+ const DEFAULT_TIMEOUT_MS = 5_000;
11
+ class AdapterExitError extends Error {
12
+ }
13
+ class AdapterTimeoutError extends Error {
14
+ }
15
+ /** A single adapter child process with a line-oriented message queue. */
16
+ class AdapterConnection {
17
+ child;
18
+ rl;
19
+ queue = [];
20
+ waiter;
21
+ exited = false;
22
+ constructor(command) {
23
+ this.child = spawn(command[0], command.slice(1), { shell: false });
24
+ this.child.stderr.pipe(process.stderr);
25
+ this.rl = createInterface({ input: this.child.stdout, crlfDelay: Infinity });
26
+ this.rl.on('line', (line) => {
27
+ const trimmed = line.trim();
28
+ if (trimmed === '')
29
+ return;
30
+ let message;
31
+ try {
32
+ message = JSON.parse(trimmed);
33
+ }
34
+ catch {
35
+ return;
36
+ }
37
+ if (this.waiter) {
38
+ const resolve = this.waiter;
39
+ this.waiter = undefined;
40
+ resolve(message);
41
+ }
42
+ else {
43
+ this.queue.push(message);
44
+ }
45
+ });
46
+ this.child.on('exit', () => {
47
+ this.exited = true;
48
+ });
49
+ }
50
+ write(message) {
51
+ if (!this.exited)
52
+ this.child.stdin.write(`${JSON.stringify(message)}\n`);
53
+ }
54
+ nextMessage(timeoutMs) {
55
+ const queued = this.queue.shift();
56
+ if (queued)
57
+ return Promise.resolve(queued);
58
+ if (this.exited)
59
+ return Promise.reject(new AdapterExitError('adapter exited'));
60
+ return new Promise((resolve, reject) => {
61
+ // A single settle path clears the timer, the exit listener, and the
62
+ // waiter so no timer can reject a promise that already resolved.
63
+ const settle = (fn) => {
64
+ clearTimeout(timer);
65
+ this.child.removeListener('exit', onExit);
66
+ this.waiter = undefined;
67
+ fn();
68
+ };
69
+ const onExit = () => settle(() => reject(new AdapterExitError('adapter exited')));
70
+ const timer = setTimeout(() => settle(() => reject(new AdapterTimeoutError('timed out waiting for result'))), timeoutMs);
71
+ this.child.once('exit', onExit);
72
+ this.waiter = (message) => settle(() => resolve(message));
73
+ });
74
+ }
75
+ async handshake(timeoutMs) {
76
+ this.write({
77
+ type: 'hello',
78
+ protocol: CONFORMANCE_PROTOCOL_VERSION,
79
+ manifestVersion: CONFORMANCE_MANIFEST_VERSION,
80
+ });
81
+ const message = await this.nextMessage(timeoutMs);
82
+ if (message.type !== 'hello' || !Array.isArray(message.families)) {
83
+ throw new Error('adapter did not answer the handshake');
84
+ }
85
+ return message;
86
+ }
87
+ end() {
88
+ this.write({ type: 'end' });
89
+ this.child.stdin.end();
90
+ }
91
+ kill() {
92
+ if (!this.exited)
93
+ this.child.kill('SIGKILL');
94
+ }
95
+ }
96
+ export async function runConformance(options) {
97
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
98
+ const fixturesDir = resolveFixturesDir(options.fixturesDir);
99
+ const manifest = loadManifest(fixturesDir);
100
+ const loadJson = createJsonLoader(fixturesDir);
101
+ let cases = enumerateAllCases(manifest, loadJson);
102
+ if (options.onlyFuzz)
103
+ cases = cases.filter((c) => c.role === 'fuzz');
104
+ if (options.skipFuzz)
105
+ cases = cases.filter((c) => c.role !== 'fuzz');
106
+ if (options.families && options.families.length > 0) {
107
+ const requested = new Set(options.families);
108
+ cases = cases.filter((c) => requested.has(c.family));
109
+ }
110
+ // The handshake covers process spawn plus the adapter's first write, which
111
+ // can be slow under load; it gets a generous timeout independent of the
112
+ // per-case timeout so a busy machine never mistakes startup for a hang.
113
+ const handshakeTimeoutMs = Math.max(timeoutMs, DEFAULT_TIMEOUT_MS);
114
+ let connection = new AdapterConnection(options.adapterCommand);
115
+ let hello = await connection.handshake(handshakeTimeoutMs);
116
+ let announced = new Set(hello.families);
117
+ let respawnBudget = 1;
118
+ const outcomes = [];
119
+ let seq = 0;
120
+ let notRun = 0;
121
+ for (const ec of cases) {
122
+ if (!announced.has(ec.family)) {
123
+ notRun += 1;
124
+ continue;
125
+ }
126
+ seq += 1;
127
+ connection.write({
128
+ type: 'case',
129
+ seq,
130
+ family: ec.family,
131
+ role: ec.role,
132
+ id: ec.id,
133
+ ...(ec.section ? { section: ec.section } : {}),
134
+ case: ec.case,
135
+ });
136
+ try {
137
+ const message = await connection.nextMessage(timeoutMs);
138
+ if (message.type !== 'result' || message.seq !== seq) {
139
+ outcomes.push(failure(ec, 'protocol error', 'out-of-order result'));
140
+ continue;
141
+ }
142
+ outcomes.push(compareCase(ec, message));
143
+ }
144
+ catch (error) {
145
+ outcomes.push(failure(ec, 'a result', error instanceof AdapterTimeoutError ? 'timeout' : 'adapter exited'));
146
+ // Both failures leave the connection unusable: a crashed child is gone,
147
+ // and a timed-out child may still deliver a late reply that would be
148
+ // consumed as the next case's result — or block every later case and
149
+ // outlive the run. Kill it and start fresh before continuing. Crashes are
150
+ // rate-limited because they can recur instantly; timeouts are naturally
151
+ // bounded by the per-case timeout, so they always refresh.
152
+ if (error instanceof AdapterExitError) {
153
+ if (respawnBudget <= 0)
154
+ break;
155
+ respawnBudget -= 1;
156
+ }
157
+ if (!(await refreshConnection()))
158
+ break;
159
+ }
160
+ }
161
+ connection.end();
162
+ return summarize(outcomes, notRun, hello);
163
+ async function refreshConnection() {
164
+ connection.kill();
165
+ connection = new AdapterConnection(options.adapterCommand);
166
+ try {
167
+ hello = await connection.handshake(handshakeTimeoutMs);
168
+ }
169
+ catch {
170
+ // A replacement that cannot even complete a handshake ends the run
171
+ // cleanly rather than throwing out of the loop.
172
+ return false;
173
+ }
174
+ announced = new Set(hello.families);
175
+ seq = 0;
176
+ return true;
177
+ }
178
+ }
179
+ function failure(ec, expected, actual) {
180
+ return { family: ec.family, role: ec.role, id: ec.id, status: 'fail', expected, actual };
181
+ }
182
+ function summarize(outcomes, notRun, hello) {
183
+ return {
184
+ total: outcomes.length,
185
+ passed: outcomes.filter((o) => o.status === 'pass').length,
186
+ failed: outcomes.filter((o) => o.status === 'fail').length,
187
+ skipped: outcomes.filter((o) => o.status === 'skip').length,
188
+ notRun,
189
+ adapter: {
190
+ implementation: hello.implementation,
191
+ version: hello.version,
192
+ language: hello.language,
193
+ families: hello.families,
194
+ },
195
+ outcomes,
196
+ };
197
+ }
@@ -0,0 +1,84 @@
1
+ export declare const CONFORMANCE_PROTOCOL_VERSION = 1;
2
+ export declare const CONFORMANCE_MANIFEST_VERSION = 1;
3
+ export type FixtureRole = 'success' | 'rejection' | 'identity' | 'portable' | 'non-portable' | 'fuzz';
4
+ export interface ManifestFileEntry {
5
+ readonly path: string;
6
+ readonly role: Exclude<FixtureRole, 'fuzz'>;
7
+ /** JSON pointers to the case arrays when the file root is not an array. */
8
+ readonly sections?: readonly string[];
9
+ }
10
+ export interface ManifestFamily {
11
+ readonly name: string;
12
+ readonly rfc?: string;
13
+ readonly codePrefixes: readonly string[];
14
+ readonly files: readonly ManifestFileEntry[];
15
+ }
16
+ export interface ManifestFuzzEntry {
17
+ readonly path: string;
18
+ /** Default target family when a seed does not carry its own `targets`. */
19
+ readonly family?: string;
20
+ }
21
+ export interface ConformanceManifest {
22
+ readonly kind: 'hypequery-conformance-manifest';
23
+ readonly version: number;
24
+ readonly families: readonly ManifestFamily[];
25
+ readonly fuzz: readonly ManifestFuzzEntry[];
26
+ }
27
+ /** A single case the runner sends to an adapter. */
28
+ export interface EnumeratedCase {
29
+ readonly family: string;
30
+ readonly role: FixtureRole;
31
+ readonly id: string;
32
+ /** JSON pointer the case was read from, when the file declared sections. */
33
+ readonly section?: string;
34
+ readonly case: Record<string, unknown>;
35
+ }
36
+ export interface AdapterHello {
37
+ readonly type: 'hello';
38
+ readonly protocol: number;
39
+ readonly implementation?: string;
40
+ readonly version?: string;
41
+ readonly language?: string;
42
+ readonly families: readonly string[];
43
+ }
44
+ export type HandlerResult = {
45
+ readonly ok: true;
46
+ readonly output?: Record<string, unknown>;
47
+ } | {
48
+ readonly ok: false;
49
+ readonly code: string;
50
+ readonly output?: Record<string, unknown>;
51
+ } | {
52
+ readonly skipped: true;
53
+ readonly reason?: string;
54
+ };
55
+ export type CaseResult = HandlerResult & {
56
+ readonly type: 'result';
57
+ readonly seq: number;
58
+ };
59
+ export type CaseStatus = 'pass' | 'fail' | 'skip';
60
+ export interface CaseOutcome {
61
+ readonly family: string;
62
+ readonly role: FixtureRole;
63
+ readonly id: string;
64
+ readonly status: CaseStatus;
65
+ readonly expected?: string;
66
+ readonly actual?: string;
67
+ readonly message?: string;
68
+ }
69
+ export interface RunSummary {
70
+ readonly total: number;
71
+ readonly passed: number;
72
+ readonly failed: number;
73
+ readonly skipped: number;
74
+ /** Cases whose family the adapter did not announce. */
75
+ readonly notRun: number;
76
+ readonly adapter?: {
77
+ readonly implementation?: string;
78
+ readonly version?: string;
79
+ readonly language?: string;
80
+ readonly families: readonly string[];
81
+ };
82
+ readonly outcomes: readonly CaseOutcome[];
83
+ }
84
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ // Shared types for the conformance manifest, the wire protocol between the
2
+ // runner and an adapter, and the enumerated cases the runner drives. See
3
+ // specs/security-protocol/rfc/0012-cross-language-conformance.md.
4
+ export const CONFORMANCE_PROTOCOL_VERSION = 1;
5
+ export const CONFORMANCE_MANIFEST_VERSION = 1;
@@ -0,0 +1,61 @@
1
+ # Deployment bundle manifest v1 fixtures
2
+
3
+ - `success.json` contains complete manifests accepted by the v1 validator.
4
+ - `rejections.json` maps generated invalid inputs to stable failure codes.
5
+ - `identity.json` fixes the RFC 8785 canonical bytes and domain-separated
6
+ SHA-256 identity for matching success fixture ids.
7
+
8
+ The fixtures exercise RFC 0007. Artifact byte verification is covered by the
9
+ CLI reference verifier because these language-neutral fixtures describe the
10
+ manifest rather than a filesystem.
11
+
12
+ ## Generated rejection semantics
13
+
14
+ Every rejection is a deterministic transform of this pinned base manifest
15
+ (RFC 0012), where `artifact(index)` denotes (with `<sha256>` the 64-digit
16
+ lowercase hexadecimal form of `index`, zero-padded):
17
+
18
+ ```json
19
+ {
20
+ "runtime": "node",
21
+ "path": "artifacts/<sha256>.mjs",
22
+ "sha256": "<sha256>",
23
+ "byteLength": 1
24
+ }
25
+ ```
26
+
27
+ and the base is:
28
+
29
+ ```json
30
+ {
31
+ "kind": "hypequery-deployment-bundle",
32
+ "version": 1,
33
+ "deployment": {
34
+ "path": "deployment.json",
35
+ "identity": "1111111111111111111111111111111111111111111111111111111111111111",
36
+ "sha256": "2222222222222222222222222222222222222222222222222222222222222222",
37
+ "byteLength": 1
38
+ },
39
+ "artifacts": [artifact(0)]
40
+ }
41
+ ```
42
+
43
+ Generator types expand as follows:
44
+
45
+ - `wrong-root-type`: an empty array instead of an object;
46
+ - `unknown-root-field`: the base plus `"extra": true`;
47
+ - `unsupported-version`: the base with `"version": 2`;
48
+ - `malformed-digest`: the base with `deployment.identity` replaced by
49
+ `"bad"`;
50
+ - `traversal-path`: the base with `deployment.path` replaced by
51
+ `"../deployment.json"`;
52
+ - `duplicate-path`: the base with its only artifact's `path` replaced by
53
+ `"deployment.json"`, colliding with the deployment path;
54
+ - `too-many-artifacts`: the base with 101 artifacts `artifact(0)` through
55
+ `artifact(100)`;
56
+ - `deployment-too-large`: the base with `deployment.byteLength` replaced by
57
+ 16777217 (16 MiB plus one byte);
58
+ - `unsafe-accessor`: the base with `kind` served by an enumerable computed
59
+ accessor returning `"hypequery-deployment-bundle"` instead of a plain
60
+ data property. Host-model conditional (RFC 0012): implementations whose
61
+ input model cannot express computed accessors skip this case.
@@ -0,0 +1,7 @@
1
+ [
2
+ {
3
+ "id": "node-runtime-bundle",
4
+ "canonical": "{\"artifacts\":[{\"byteLength\":2048,\"path\":\"artifacts/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef.mjs\",\"runtime\":\"node\",\"sha256\":\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"}],\"deployment\":{\"byteLength\":1024,\"identity\":\"1111111111111111111111111111111111111111111111111111111111111111\",\"path\":\"deployment.json\",\"sha256\":\"2222222222222222222222222222222222222222222222222222222222222222\"},\"kind\":\"hypequery-deployment-bundle\",\"version\":1}",
5
+ "sha256": "fca7dcefbc455553890c2408d6a693d4583ec19cf0c055202930a03b41560ace"
6
+ }
7
+ ]
@@ -0,0 +1,11 @@
1
+ [
2
+ { "id": "wrong-root-type", "generator": { "type": "wrong-root-type" }, "error": "HQ_BUNDLE_TYPE" },
3
+ { "id": "unknown-root-field", "generator": { "type": "unknown-root-field" }, "error": "HQ_BUNDLE_UNKNOWN_FIELD" },
4
+ { "id": "unsupported-version", "generator": { "type": "unsupported-version" }, "error": "HQ_BUNDLE_INVALID_VERSION" },
5
+ { "id": "malformed-digest", "generator": { "type": "malformed-digest" }, "error": "HQ_BUNDLE_INVALID_VALUE" },
6
+ { "id": "traversal-path", "generator": { "type": "traversal-path" }, "error": "HQ_BUNDLE_INVALID_PATH" },
7
+ { "id": "duplicate-path", "generator": { "type": "duplicate-path" }, "error": "HQ_BUNDLE_INVALID_REFERENCE" },
8
+ { "id": "too-many-artifacts", "generator": { "type": "too-many-artifacts" }, "error": "HQ_BUNDLE_TOO_MANY_ITEMS" },
9
+ { "id": "deployment-too-large", "generator": { "type": "deployment-too-large" }, "error": "HQ_BUNDLE_TOO_LARGE" },
10
+ { "id": "unsafe-accessor", "generator": { "type": "unsafe-accessor" }, "error": "HQ_BUNDLE_UNSAFE_OBJECT" }
11
+ ]
@@ -0,0 +1,23 @@
1
+ [
2
+ {
3
+ "id": "node-runtime-bundle",
4
+ "value": {
5
+ "kind": "hypequery-deployment-bundle",
6
+ "version": 1,
7
+ "deployment": {
8
+ "path": "deployment.json",
9
+ "identity": "1111111111111111111111111111111111111111111111111111111111111111",
10
+ "sha256": "2222222222222222222222222222222222222222222222222222222222222222",
11
+ "byteLength": 1024
12
+ },
13
+ "artifacts": [
14
+ {
15
+ "runtime": "node",
16
+ "path": "artifacts/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef.mjs",
17
+ "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
18
+ "byteLength": 2048
19
+ }
20
+ ]
21
+ }
22
+ }
23
+ ]
@@ -0,0 +1,36 @@
1
+ # Deployment release envelope v1 fixtures
2
+
3
+ - `success.json` contains complete envelopes accepted by the v1 validator.
4
+ - `rejections.json` maps generated invalid inputs to stable failure codes.
5
+ - `identity.json` fixes the RFC 8785 canonical bytes and domain-separated
6
+ SHA-256 identity for matching success fixture ids.
7
+
8
+ These fixtures exercise RFC 0008. Bundle filesystem verification remains part
9
+ of RFC 0007 and is intentionally not duplicated here.
10
+
11
+ ## Generated rejection semantics
12
+
13
+ Every rejection is a deterministic transform of this pinned base envelope
14
+ (RFC 0012):
15
+
16
+ ```json
17
+ {
18
+ "kind": "hypequery-deployment-release",
19
+ "version": 1,
20
+ "bundleIdentity": "0000000000000000000000000000000000000000000000000000000000000000",
21
+ "target": { "project": "project_1", "environment": "production" }
22
+ }
23
+ ```
24
+
25
+ Generator types expand as follows:
26
+
27
+ - `wrong-root-type`: an empty array instead of an object;
28
+ - `unknown-root-field`: the base plus `"extra": true`;
29
+ - `unsupported-version`: the base with `"version": 2`;
30
+ - `malformed-bundle-identity`: the base with `"bundleIdentity": "bad"`;
31
+ - `target-too-large`: the base with `target.project` replaced by `p`
32
+ followed by 128 repetitions of `a` (129 bytes);
33
+ - `unsafe-accessor`: the base with `kind` served by an enumerable computed
34
+ accessor returning `"hypequery-deployment-release"` instead of a plain
35
+ data property. Host-model conditional (RFC 0012): implementations whose
36
+ input model cannot express computed accessors skip this case.
@@ -0,0 +1,7 @@
1
+ [
2
+ {
3
+ "id": "production-release",
4
+ "canonical": "{\"bundleIdentity\":\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"kind\":\"hypequery-deployment-release\",\"target\":{\"environment\":\"production\",\"project\":\"prj_acme-analytics\"},\"version\":1}",
5
+ "sha256": "3ffb5ef6824431b6859c5ae609903e41abf3515c8080dff951dc4babfa2b6cda"
6
+ }
7
+ ]
@@ -0,0 +1,8 @@
1
+ [
2
+ { "id": "wrong-root-type", "generator": { "type": "wrong-root-type" }, "error": "HQ_RELEASE_TYPE" },
3
+ { "id": "unknown-root-field", "generator": { "type": "unknown-root-field" }, "error": "HQ_RELEASE_UNKNOWN_FIELD" },
4
+ { "id": "unsupported-version", "generator": { "type": "unsupported-version" }, "error": "HQ_RELEASE_INVALID_VERSION" },
5
+ { "id": "malformed-bundle-identity", "generator": { "type": "malformed-bundle-identity" }, "error": "HQ_RELEASE_INVALID_VALUE" },
6
+ { "id": "target-too-large", "generator": { "type": "target-too-large" }, "error": "HQ_RELEASE_TOO_LARGE" },
7
+ { "id": "unsafe-accessor", "generator": { "type": "unsafe-accessor" }, "error": "HQ_RELEASE_UNSAFE_OBJECT" }
8
+ ]
@@ -0,0 +1,14 @@
1
+ [
2
+ {
3
+ "id": "production-release",
4
+ "value": {
5
+ "kind": "hypequery-deployment-release",
6
+ "version": 1,
7
+ "bundleIdentity": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
8
+ "target": {
9
+ "project": "prj_acme-analytics",
10
+ "environment": "production"
11
+ }
12
+ }
13
+ }
14
+ ]