@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.
- package/LICENSE +201 -0
- package/README.md +40 -0
- package/dist/adapters/generators.d.ts +13 -0
- package/dist/adapters/generators.d.ts.map +1 -0
- package/dist/adapters/generators.js +366 -0
- package/dist/adapters/reference.d.ts +6 -0
- package/dist/adapters/reference.d.ts.map +1 -0
- package/dist/adapters/reference.js +172 -0
- package/dist/adapters/stdio.d.ts +17 -0
- package/dist/adapters/stdio.d.ts.map +1 -0
- package/dist/adapters/stdio.js +80 -0
- package/dist/bin/conformance.d.ts +3 -0
- package/dist/bin/conformance.d.ts.map +1 -0
- package/dist/bin/conformance.js +84 -0
- package/dist/bin/reference-adapter.d.ts +3 -0
- package/dist/bin/reference-adapter.d.ts.map +1 -0
- package/dist/bin/reference-adapter.js +16 -0
- package/dist/compare.d.ts +3 -0
- package/dist/compare.d.ts.map +1 -0
- package/dist/compare.js +125 -0
- package/dist/fs.d.ts +11 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +36 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/manifest.d.ts +13 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +118 -0
- package/dist/report.d.ts +4 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +23 -0
- package/dist/runner.d.ts +12 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +197 -0
- package/dist/types.d.ts +84 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/fixtures/deployment-bundles-v1/README.md +61 -0
- package/fixtures/deployment-bundles-v1/identity.json +7 -0
- package/fixtures/deployment-bundles-v1/rejections.json +11 -0
- package/fixtures/deployment-bundles-v1/success.json +23 -0
- package/fixtures/deployment-releases-v1/README.md +36 -0
- package/fixtures/deployment-releases-v1/identity.json +7 -0
- package/fixtures/deployment-releases-v1/rejections.json +8 -0
- package/fixtures/deployment-releases-v1/success.json +14 -0
- package/fixtures/deployments-v1/README.md +77 -0
- package/fixtures/deployments-v1/identity.json +7 -0
- package/fixtures/deployments-v1/rejections.json +12 -0
- package/fixtures/deployments-v1/success.json +81 -0
- package/fixtures/expressions-v1/README.md +25 -0
- package/fixtures/expressions-v1/rejections.json +17 -0
- package/fixtures/expressions-v1/success.json +47 -0
- package/fixtures/fuzz-seeds-v1/README.md +29 -0
- package/fixtures/fuzz-seeds-v1/sql-expressions.json +32 -0
- package/fixtures/fuzz-seeds-v1/structured-values.json +32 -0
- package/fixtures/fuzz-seeds-v1/value-sources.json +191 -0
- package/fixtures/identifiers-v1/README.md +15 -0
- package/fixtures/identifiers-v1/rejections.json +58 -0
- package/fixtures/identifiers-v1/success.json +26 -0
- package/fixtures/manifest.json +117 -0
- package/fixtures/query-diagnostics-v1/README.md +49 -0
- package/fixtures/query-diagnostics-v1/rejections.json +13 -0
- package/fixtures/query-diagnostics-v1/success.json +27 -0
- package/fixtures/query-events-v1/README.md +60 -0
- package/fixtures/query-events-v1/rejections.json +18 -0
- package/fixtures/query-events-v1/success.json +40 -0
- package/fixtures/query-implementations-v1/README.md +33 -0
- package/fixtures/query-implementations-v1/rejections.json +80 -0
- package/fixtures/query-implementations-v1/success.json +96 -0
- package/fixtures/query-schemas-v1/README.md +25 -0
- package/fixtures/query-schemas-v1/rejections.json +15 -0
- package/fixtures/query-schemas-v1/success.json +17 -0
- package/fixtures/sql-portability-v1/README.md +15 -0
- package/fixtures/sql-portability-v1/non-portable.json +146 -0
- package/fixtures/sql-portability-v1/portable.json +346 -0
- package/fixtures/tagged-values-v1/README.md +44 -0
- package/fixtures/tagged-values-v1/rejections.json +298 -0
- package/fixtures/tagged-values-v1/success.json +262 -0
- package/package.json +52 -0
package/dist/manifest.js
ADDED
|
@@ -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
|
+
}
|
package/dist/report.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/runner.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -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
|
+
]
|