@hypequery/protocol-conformance 0.9.1 → 0.10.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 (41) hide show
  1. package/README.md +43 -27
  2. package/dist/adapters/generators.d.ts.map +1 -1
  3. package/dist/adapters/generators.js +14 -0
  4. package/dist/adapters/reference.d.ts +10 -1
  5. package/dist/adapters/reference.d.ts.map +1 -1
  6. package/dist/adapters/reference.js +52 -1
  7. package/dist/adapters/stdio.d.ts +3 -1
  8. package/dist/adapters/stdio.d.ts.map +1 -1
  9. package/dist/adapters/stdio.js +3 -0
  10. package/dist/bin/reference-adapter.js +2 -1
  11. package/dist/compare.js +10 -0
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/report.d.ts.map +1 -1
  16. package/dist/report.js +4 -0
  17. package/dist/runner.d.ts.map +1 -1
  18. package/dist/runner.js +74 -8
  19. package/dist/types.d.ts +14 -0
  20. package/dist/types.d.ts.map +1 -1
  21. package/fixtures/cache-keys-v1/README.md +59 -0
  22. package/fixtures/cache-keys-v1/rejections.json +172 -0
  23. package/fixtures/cache-keys-v1/success.json +134 -0
  24. package/fixtures/deployment-bundles-v1/README.md +5 -57
  25. package/fixtures/deployment-releases-v1/README.md +6 -33
  26. package/fixtures/deployments-v1/README.md +5 -73
  27. package/fixtures/expressions-v1/README.md +4 -22
  28. package/fixtures/fuzz-seeds-v1/README.md +6 -26
  29. package/fixtures/identifiers-v1/README.md +4 -12
  30. package/fixtures/identifiers-v1/rejections.json +38 -0
  31. package/fixtures/identifiers-v1/success.json +18 -0
  32. package/fixtures/manifest.json +161 -39
  33. package/fixtures/query-diagnostics-v1/README.md +4 -45
  34. package/fixtures/query-events-v1/README.md +4 -56
  35. package/fixtures/query-implementations-v1/README.md +4 -30
  36. package/fixtures/query-schemas-v1/README.md +4 -22
  37. package/fixtures/sql-portability-v1/README.md +3 -12
  38. package/fixtures/tagged-values-v1/README.md +12 -1
  39. package/fixtures/tagged-values-v1/rejections.json +89 -0
  40. package/fixtures/tagged-values-v1/success.json +127 -0
  41. package/package.json +13 -5
package/README.md CHANGED
@@ -1,40 +1,56 @@
1
1
  # @hypequery/protocol-conformance
2
2
 
3
- Cross-language conformance runner and TypeScript reference adapter for the
4
- Hypequery security protocol. It drives any implementation of the protocol
5
- against the language-neutral fixtures in `specs/security-protocol/fixtures/`
6
- and checks the results against the pinned expectations.
3
+ `@hypequery/protocol-conformance` checks TypeScript, Python, or any other implementation of the Hypequery security protocol against the same language-neutral fixtures. It includes a command-line runner and the TypeScript reference adapter.
7
4
 
8
- See [RFC 0012](../../specs/security-protocol/rfc/0012-cross-language-conformance.md)
9
- for the manifest schema, the adapter wire protocol, and the pass criteria.
5
+ The manifest, adapter wire format, and pass rules are defined in [RFC 0012](../../specs/security-protocol/rfc/0012-cross-language-conformance.md).
10
6
 
11
- ## Usage
7
+ ## Run the reference adapter
12
8
 
13
- ```sh
14
- # Run the whole suite against the bundled TypeScript reference adapter.
15
- hypequery-protocol-conformance run -- hypequery-protocol-reference-adapter
9
+ ```bash
10
+ hypequery-protocol-conformance run -- \
11
+ hypequery-protocol-reference-adapter
12
+ ```
13
+
14
+ ## Test another language
15
+
16
+ Everything after `--` is the adapter command. The runner starts it directly, without a shell:
16
17
 
17
- # Run against another implementation (any language). Everything after `--`
18
- # is the adapter command, spawned without a shell.
19
- hypequery-protocol-conformance run --fixtures ./fixtures -- python -m my_impl.adapter
18
+ ```bash
19
+ hypequery-protocol-conformance run \
20
+ --fixtures ./specs/security-protocol/fixtures \
21
+ -- python -m my_implementation.adapter
22
+ ```
23
+
24
+ Useful options include:
20
25
 
21
- # Restrict to families, skip or isolate the fuzz corpus, emit JSON.
22
- hypequery-protocol-conformance run --families sql-portability-v1 --skip-fuzz \
23
- --report json -- node ./adapter.mjs
26
+ ```bash
27
+ # Run selected fixture families and emit JSON.
28
+ hypequery-protocol-conformance run \
29
+ --families sql-portability-v1 \
30
+ --skip-fuzz \
31
+ --report json \
32
+ -- node ./adapter.mjs
24
33
 
25
- # List the enumerated case counts per family.
34
+ # Show case counts by family.
26
35
  hypequery-protocol-conformance list
27
36
  ```
28
37
 
29
- Exit codes: `0` all cases passed, `1` conformance failures, `2` setup or
30
- protocol error.
38
+ Exit code `0` means every case passed, `1` means conformance failures, and `2` means the runner or adapter protocol could not be set up correctly.
39
+
40
+ ## Adapter shape
41
+
42
+ An adapter reads newline-delimited JSON from stdin and writes newline-delimited JSON to stdout. It answers the initial `hello` with supported fixture families, then returns one `result` for every `case`.
43
+
44
+ The exported `createStdioAdapter` helper handles the loop. Your handler maps `(family, role, case)` to one of:
45
+
46
+ ```ts
47
+ { ok: true, output? }
48
+ { ok: false, code }
49
+ { skipped: true, reason }
50
+ ```
51
+
52
+ The package includes a pinned fixture snapshot, so a pinned package version is also a pinned conformance target. Pass `--fixtures` to test local or newer specifications.
31
53
 
32
- ## Writing an adapter
54
+ ## License
33
55
 
34
- An adapter reads newline-delimited JSON on stdin and writes it on stdout. It
35
- answers a `hello` with the families it supports, then one `result` per `case`.
36
- The `createStdioAdapter` helper implements the loop; a handler maps
37
- `(family, role, case)` to `{ ok: true, output? }`, `{ ok: false, code }`, or
38
- `{ skipped: true, reason }`. The bundled fixture snapshot means a pinned
39
- package version is a pinned conformance target; pass `--fixtures` to test
40
- against newer or local specs.
56
+ Apache-2.0.
@@ -1 +1 @@
1
- {"version":3,"file":"generators.d.ts","sourceRoot":"","sources":["../../src/adapters/generators.ts"],"names":[],"mappings":"AAIA,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAkBpC,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAyB1D;AAID,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAMxD;AAMD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAiCzD;AAID,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CA2BrD;AAgBD,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAuB7D;AAkBD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAyBpD;AAeD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAoB1D;AA2BD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAwDzD;AAkBD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAmBrD;AAaD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAgBtD"}
1
+ {"version":3,"file":"generators.d.ts","sourceRoot":"","sources":["../../src/adapters/generators.ts"],"names":[],"mappings":"AAIA,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAkBpC,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAuC1D;AAID,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAMxD;AAMD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAiCzD;AAID,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CA2BrD;AAgBD,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAuB7D;AAkBD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAyBpD;AAeD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAoB1D;AA2BD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAwDzD;AAkBD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAmBrD;AAaD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAgBtD"}
@@ -35,6 +35,20 @@ export function materializeTaggedValue(spec) {
35
35
  throw new Error(`Unknown non-finite float: ${String(spec.value)}`);
36
36
  case 'repeat-string':
37
37
  return repeat(spec.utf8, spec.count);
38
+ case 'unsafe-accessor': {
39
+ // `$hypequery` served by a computed accessor instead of a data
40
+ // property. A validator must snapshot without invoking it and reject.
41
+ const value = {};
42
+ Object.defineProperty(value, '$hypequery', {
43
+ enumerable: true,
44
+ get: () => ({
45
+ type: 'uuid',
46
+ version: 1,
47
+ value: '01890f3e-7b7b-7cc2-98c4-dc0c0c07398f',
48
+ }),
49
+ });
50
+ return value;
51
+ }
38
52
  default:
39
53
  throw new Error(`Unknown tagged-value generator: ${String(spec.type)}`);
40
54
  }
@@ -1,6 +1,15 @@
1
1
  import type { FixtureRole, HandlerResult } from '../types.js';
2
2
  type Case = Record<string, unknown>;
3
- export declare const REFERENCE_FAMILIES: readonly ["tagged-values-v1", "identifiers-v1", "expressions-v1", "query-schemas-v1", "query-implementations-v1", "query-events-v1", "query-diagnostics-v1", "deployments-v1", "deployment-bundles-v1", "deployment-releases-v1"];
3
+ /**
4
+ * RFC 0012 hostile-object suite declaration for the TypeScript reference
5
+ * implementation. The cases live in @hypequery/protocol's
6
+ * `src/values/codec.test.ts`; each mechanism listed here is exercised there.
7
+ */
8
+ export declare const REFERENCE_HOSTILE_OBJECT_SUITE: {
9
+ readonly count: 7;
10
+ readonly mechanisms: readonly ["getter", "toJSON", "proxy", "custom-prototype", "symbol-key", "sparse-array", "cycle"];
11
+ };
12
+ export declare const REFERENCE_FAMILIES: readonly ["cache-keys-v1", "tagged-values-v1", "identifiers-v1", "expressions-v1", "query-schemas-v1", "query-implementations-v1", "query-events-v1", "query-diagnostics-v1", "deployments-v1", "deployment-bundles-v1", "deployment-releases-v1"];
4
13
  export declare function referenceHandle(family: string, role: FixtureRole, c: Case, section?: string): HandlerResult;
5
14
  export {};
6
15
  //# sourceMappingURL=reference.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reference.d.ts","sourceRoot":"","sources":["../../src/adapters/reference.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAc9D,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA0BpC,eAAO,MAAM,kBAAkB,mOAWrB,CAAC;AAEX,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,CAAC,EAAE,IAAI,EACP,OAAO,CAAC,EAAE,MAAM,GACf,aAAa,CAkCf"}
1
+ {"version":3,"file":"reference.d.ts","sourceRoot":"","sources":["../../src/adapters/reference.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAc9D,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA0BpC;;;;GAIG;AACH,eAAO,MAAM,8BAA8B;;;CAWjC,CAAC;AAEX,eAAO,MAAM,kBAAkB,oPAYrB,CAAC;AAEX,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,CAAC,EAAE,IAAI,EACP,OAAO,CAAC,EAAE,MAAM,GACf,aAAa,CAoCf"}
@@ -2,7 +2,7 @@
2
2
  // root export of @hypequery/protocol and returns a wire result. Every operation
3
3
  // goes through the package's supported surface — this is exactly what a second
4
4
  // implementation must reproduce.
5
- import { decodeCanonicalValue, encodeCanonicalValue, hashCanonicalValue, parseProtocolIdentifier, parseProtocolQualifiedIdentifier, prepareProtocolDeploymentBundleManifest, prepareProtocolDeploymentContract, prepareProtocolDeploymentReleaseEnvelope, splitProtocolQualifiedIdentifier, validateCanonicalValue, validateProtocolDeploymentBundleManifest, validateProtocolDeploymentContract, validateProtocolDeploymentReleaseEnvelope, validateProtocolExpression, validateProtocolQueryDiagnostics, validateProtocolQueryEvent, validateProtocolQueryImplementation, validateProtocolSchema, validateProtocolSemanticQuery, validateProtocolSqlExpression, } from '@hypequery/protocol';
5
+ import { decodeCanonicalValue, deriveProtocolCacheKey, deriveProtocolCacheNamespaceToken, encodeCanonicalValue, hashCanonicalValue, parseProtocolIdentifier, parseProtocolQualifiedIdentifier, prepareProtocolDeploymentBundleManifest, prepareProtocolDeploymentContract, prepareProtocolDeploymentReleaseEnvelope, splitProtocolQualifiedIdentifier, validateCanonicalValue, validateProtocolDeploymentBundleManifest, validateProtocolDeploymentContract, validateProtocolDeploymentReleaseEnvelope, validateProtocolExpression, validateProtocolQueryDiagnostics, validateProtocolQueryEvent, validateProtocolQueryImplementation, validateProtocolSchema, validateProtocolSemanticQuery, validateProtocolSqlExpression, } from '@hypequery/protocol';
6
6
  import { materializeBundle, materializeDeployment, materializeDiagnostics, materializeEvent, materializeExpression, materializeIdentifier, materializeImplementation, materializeRelease, materializeSchema, materializeTaggedValue, } from './generators.js';
7
7
  const STABLE_CODE = /^HQ_[A-Z0-9_]+$/;
8
8
  /** Turns a thrown protocol error into a wire rejection; rethrows anything else. */
@@ -25,7 +25,25 @@ const ACCEPT = { ok: true };
25
25
  function validationInput(c, materialize) {
26
26
  return c.generator ? materialize(c.generator) : c.value;
27
27
  }
28
+ /**
29
+ * RFC 0012 hostile-object suite declaration for the TypeScript reference
30
+ * implementation. The cases live in @hypequery/protocol's
31
+ * `src/values/codec.test.ts`; each mechanism listed here is exercised there.
32
+ */
33
+ export const REFERENCE_HOSTILE_OBJECT_SUITE = {
34
+ count: 7,
35
+ mechanisms: [
36
+ 'getter',
37
+ 'toJSON',
38
+ 'proxy',
39
+ 'custom-prototype',
40
+ 'symbol-key',
41
+ 'sparse-array',
42
+ 'cycle',
43
+ ],
44
+ };
28
45
  export const REFERENCE_FAMILIES = [
46
+ 'cache-keys-v1',
29
47
  'tagged-values-v1',
30
48
  'identifiers-v1',
31
49
  'expressions-v1',
@@ -68,10 +86,43 @@ export function referenceHandle(family, role, c, section) {
68
86
  return handleBundle(role, c);
69
87
  case 'deployment-releases-v1':
70
88
  return handleRelease(role, c);
89
+ case 'cache-keys-v1':
90
+ return handleCacheKey(role, c);
71
91
  default:
72
92
  throw new Error(`reference adapter does not support family ${family}`);
73
93
  }
74
94
  }
95
+ function hexToBytes(hex) {
96
+ const out = new Uint8Array(hex.length / 2);
97
+ for (let index = 0; index < out.length; index += 1) {
98
+ out[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
99
+ }
100
+ return out;
101
+ }
102
+ function handleCacheKey(role, c) {
103
+ const generator = c.generator;
104
+ const preimage = generator?.type === 'repeat-string'
105
+ ? (generator.utf8 ?? '').repeat(generator.count ?? 0)
106
+ : (c.preimageUtf8 ?? '');
107
+ const namespace = c.namespace;
108
+ return attempt(() => {
109
+ const key = deriveProtocolCacheKey({
110
+ secret: hexToBytes(c.secretHex),
111
+ namespace,
112
+ keyVersion: c.keyVersion,
113
+ preimage,
114
+ });
115
+ if (role !== 'success')
116
+ return { ok: true };
117
+ return {
118
+ ok: true,
119
+ output: {
120
+ key,
121
+ namespaceToken: deriveProtocolCacheNamespaceToken(hexToBytes(c.secretHex), namespace.project, namespace.environment),
122
+ },
123
+ };
124
+ });
125
+ }
75
126
  function handleTaggedValue(role, c) {
76
127
  if (role === 'success') {
77
128
  return attempt(() => ({
@@ -1,9 +1,11 @@
1
- import type { FixtureRole, HandlerResult } from '../types.js';
1
+ import type { FixtureRole, HandlerResult, HostileObjectSuiteDeclaration } from '../types.js';
2
2
  export interface StdioAdapterOptions {
3
3
  readonly implementation?: string;
4
4
  readonly version?: string;
5
5
  readonly language?: string;
6
6
  readonly families: readonly string[];
7
+ /** RFC 0012 language-specific hostile-object suite declaration. */
8
+ readonly hostileObjectSuite?: HostileObjectSuiteDeclaration;
7
9
  readonly handle: (family: string, role: FixtureRole, fixtureCase: Record<string, unknown>, section: string | undefined) => HandlerResult | Promise<HandlerResult>;
8
10
  /** Streams default to the process stdio; overridable for tests. */
9
11
  readonly input?: NodeJS.ReadableStream;
@@ -1 +1 @@
1
- {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/adapters/stdio.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE9D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,CACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,OAAO,EAAE,MAAM,GAAG,SAAS,KACxB,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5C,mEAAmE;IACnE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6EhF"}
1
+ {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/adapters/stdio.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAE7F,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,mEAAmE;IACnE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,6BAA6B,CAAC;IAC5D,QAAQ,CAAC,MAAM,EAAE,CACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,OAAO,EAAE,MAAM,GAAG,SAAS,KACxB,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5C,mEAAmE;IACnE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgFhF"}
@@ -51,6 +51,9 @@ export function createStdioAdapter(options) {
51
51
  version: options.version,
52
52
  language: options.language,
53
53
  families,
54
+ ...(options.hostileObjectSuite
55
+ ? { hostileObjectSuite: options.hostileObjectSuite }
56
+ : {}),
54
57
  });
55
58
  return;
56
59
  }
@@ -2,11 +2,12 @@
2
2
  // Reference adapter executable. Wraps @hypequery/protocol behind the RFC 0012
3
3
  // stdio protocol so the runner can drive it like any other implementation.
4
4
  import { createStdioAdapter } from '../adapters/stdio.js';
5
- import { REFERENCE_FAMILIES, referenceHandle } from '../adapters/reference.js';
5
+ import { REFERENCE_FAMILIES, REFERENCE_HOSTILE_OBJECT_SUITE, referenceHandle, } from '../adapters/reference.js';
6
6
  createStdioAdapter({
7
7
  implementation: '@hypequery/protocol',
8
8
  language: 'typescript',
9
9
  families: [...REFERENCE_FAMILIES],
10
+ hostileObjectSuite: REFERENCE_HOSTILE_OBJECT_SUITE,
10
11
  handle: referenceHandle,
11
12
  })
12
13
  .then((code) => process.exit(code))
package/dist/compare.js CHANGED
@@ -121,5 +121,15 @@ function compareSuccessOutput(ec, o) {
121
121
  return outcome(ec, 'fail', { expected: 'segments', actual: 'mismatch' });
122
122
  }
123
123
  }
124
+ if (ec.family === 'cache-keys-v1') {
125
+ // Reported as 'mismatch' rather than by value: a run against non-fixture
126
+ // inputs would otherwise print keys derived from a real secret.
127
+ if (o.key !== ec.case.key) {
128
+ return outcome(ec, 'fail', { expected: 'key', actual: 'mismatch' });
129
+ }
130
+ if (o.namespaceToken !== ec.case.namespaceToken) {
131
+ return outcome(ec, 'fail', { expected: 'namespaceToken', actual: 'mismatch' });
132
+ }
133
+ }
124
134
  return outcome(ec, 'pass');
125
135
  }
package/dist/index.d.ts CHANGED
@@ -6,6 +6,6 @@ export type { JsonLoader } from './manifest.js';
6
6
  export { compareCase } from './compare.js';
7
7
  export { formatPrettyReport, formatJsonReport } from './report.js';
8
8
  export { createStdioAdapter } from './adapters/stdio.js';
9
- export { referenceHandle, REFERENCE_FAMILIES } from './adapters/reference.js';
9
+ export { referenceHandle, REFERENCE_FAMILIES, REFERENCE_HOSTILE_OBJECT_SUITE, } from './adapters/reference.js';
10
10
  export * from './types.js';
11
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,8BAA8B,GAC/B,MAAM,yBAAyB,CAAC;AACjC,cAAc,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -5,5 +5,5 @@ export { enumerateAllCases, enumerateFamilyCases, enumerateFuzzCases, resolveJso
5
5
  export { compareCase } from './compare.js';
6
6
  export { formatPrettyReport, formatJsonReport } from './report.js';
7
7
  export { createStdioAdapter } from './adapters/stdio.js';
8
- export { referenceHandle, REFERENCE_FAMILIES } from './adapters/reference.js';
8
+ export { referenceHandle, REFERENCE_FAMILIES, REFERENCE_HOSTILE_OBJECT_SUITE, } from './adapters/reference.js';
9
9
  export * from './types.js';
@@ -1 +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"}
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,CA0B9D"}
package/dist/report.js CHANGED
@@ -19,5 +19,9 @@ export function formatPrettyReport(summary) {
19
19
  }
20
20
  lines.push(`${summary.passed} passed, ${summary.failed} failed, ${summary.skipped} skipped`
21
21
  + (summary.notRun > 0 ? `, ${summary.notRun} not run (family not announced)` : ''));
22
+ const suite = adapter?.hostileObjectSuite;
23
+ if (suite) {
24
+ lines.push(`hostile-object suite: ${suite.count} cases (${suite.mechanisms.join(', ')})`);
25
+ }
22
26
  return lines.join('\n');
23
27
  }
@@ -1 +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"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAQA,OAAO,EAOL,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;AAyJD,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CA6GxF"}
package/dist/runner.js CHANGED
@@ -12,6 +12,56 @@ class AdapterExitError extends Error {
12
12
  }
13
13
  class AdapterTimeoutError extends Error {
14
14
  }
15
+ function parseHostileObjectSuite(value) {
16
+ if (value === undefined)
17
+ return undefined;
18
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
19
+ throw new Error('adapter hostileObjectSuite must be an object');
20
+ }
21
+ const suite = value;
22
+ if (!Number.isSafeInteger(suite.count) || suite.count < 1) {
23
+ throw new Error('adapter hostileObjectSuite count must be a positive safe integer');
24
+ }
25
+ if (!Array.isArray(suite.mechanisms)
26
+ || suite.mechanisms.length === 0
27
+ || !suite.mechanisms.every((mechanism) => typeof mechanism === 'string' && mechanism.length > 0)
28
+ || new Set(suite.mechanisms).size !== suite.mechanisms.length) {
29
+ throw new Error('adapter hostileObjectSuite mechanisms must be unique non-empty strings');
30
+ }
31
+ return {
32
+ count: suite.count,
33
+ mechanisms: [...suite.mechanisms],
34
+ };
35
+ }
36
+ function parseAdapterHello(message, hostModelFamilies) {
37
+ if (message.type !== 'hello'
38
+ || message.protocol !== CONFORMANCE_PROTOCOL_VERSION
39
+ || !Array.isArray(message.families)
40
+ || !message.families.every((family) => typeof family === 'string' && family.length > 0)) {
41
+ throw new Error('adapter did not answer the handshake');
42
+ }
43
+ const families = message.families;
44
+ const hostileObjectSuite = parseHostileObjectSuite(message.hostileObjectSuite);
45
+ if (families.some((family) => hostModelFamilies.has(family)) && !hostileObjectSuite) {
46
+ throw new Error('adapter must declare hostileObjectSuite for host-model families');
47
+ }
48
+ for (const field of ['implementation', 'version', 'language']) {
49
+ if (message[field] !== undefined && typeof message[field] !== 'string') {
50
+ throw new Error(`adapter hello ${field} must be a string`);
51
+ }
52
+ }
53
+ return {
54
+ type: 'hello',
55
+ protocol: CONFORMANCE_PROTOCOL_VERSION,
56
+ families: [...families],
57
+ ...(typeof message.implementation === 'string'
58
+ ? { implementation: message.implementation }
59
+ : {}),
60
+ ...(typeof message.version === 'string' ? { version: message.version } : {}),
61
+ ...(typeof message.language === 'string' ? { language: message.language } : {}),
62
+ ...(hostileObjectSuite ? { hostileObjectSuite } : {}),
63
+ };
64
+ }
15
65
  /** A single adapter child process with a line-oriented message queue. */
16
66
  class AdapterConnection {
17
67
  child;
@@ -72,17 +122,14 @@ class AdapterConnection {
72
122
  this.waiter = (message) => settle(() => resolve(message));
73
123
  });
74
124
  }
75
- async handshake(timeoutMs) {
125
+ async handshake(timeoutMs, hostModelFamilies) {
76
126
  this.write({
77
127
  type: 'hello',
78
128
  protocol: CONFORMANCE_PROTOCOL_VERSION,
79
129
  manifestVersion: CONFORMANCE_MANIFEST_VERSION,
80
130
  });
81
131
  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;
132
+ return parseAdapterHello(message, hostModelFamilies);
86
133
  }
87
134
  end() {
88
135
  this.write({ type: 'end' });
@@ -98,7 +145,16 @@ export async function runConformance(options) {
98
145
  const fixturesDir = resolveFixturesDir(options.fixturesDir);
99
146
  const manifest = loadManifest(fixturesDir);
100
147
  const loadJson = createJsonLoader(fixturesDir);
101
- let cases = enumerateAllCases(manifest, loadJson);
148
+ const allCases = enumerateAllCases(manifest, loadJson);
149
+ const hostModelFamilies = new Set(allCases
150
+ .filter((c) => {
151
+ const generator = c.case.generator;
152
+ return typeof generator === 'object'
153
+ && generator !== null
154
+ && generator.type === 'unsafe-accessor';
155
+ })
156
+ .map((c) => c.family));
157
+ let cases = allCases;
102
158
  if (options.onlyFuzz)
103
159
  cases = cases.filter((c) => c.role === 'fuzz');
104
160
  if (options.skipFuzz)
@@ -112,7 +168,14 @@ export async function runConformance(options) {
112
168
  // per-case timeout so a busy machine never mistakes startup for a hang.
113
169
  const handshakeTimeoutMs = Math.max(timeoutMs, DEFAULT_TIMEOUT_MS);
114
170
  let connection = new AdapterConnection(options.adapterCommand);
115
- let hello = await connection.handshake(handshakeTimeoutMs);
171
+ let hello;
172
+ try {
173
+ hello = await connection.handshake(handshakeTimeoutMs, hostModelFamilies);
174
+ }
175
+ catch (error) {
176
+ connection.kill();
177
+ throw error;
178
+ }
116
179
  let announced = new Set(hello.families);
117
180
  let respawnBudget = 1;
118
181
  const outcomes = [];
@@ -164,7 +227,7 @@ export async function runConformance(options) {
164
227
  connection.kill();
165
228
  connection = new AdapterConnection(options.adapterCommand);
166
229
  try {
167
- hello = await connection.handshake(handshakeTimeoutMs);
230
+ hello = await connection.handshake(handshakeTimeoutMs, hostModelFamilies);
168
231
  }
169
232
  catch {
170
233
  // A replacement that cannot even complete a handshake ends the run
@@ -191,6 +254,9 @@ function summarize(outcomes, notRun, hello) {
191
254
  version: hello.version,
192
255
  language: hello.language,
193
256
  families: hello.families,
257
+ ...(hello.hostileObjectSuite
258
+ ? { hostileObjectSuite: hello.hostileObjectSuite }
259
+ : {}),
194
260
  },
195
261
  outcomes,
196
262
  };
package/dist/types.d.ts CHANGED
@@ -33,6 +33,18 @@ export interface EnumeratedCase {
33
33
  readonly section?: string;
34
34
  readonly case: Record<string, unknown>;
35
35
  }
36
+ /**
37
+ * RFC 0012 requires every implementation to declare a language-specific
38
+ * hostile-object suite covering the conversion mechanisms the shared
39
+ * `unsafe-accessor` generator cannot describe. The declaration travels in the
40
+ * adapter's `hello` message so it lands in the published run summary.
41
+ */
42
+ export interface HostileObjectSuiteDeclaration {
43
+ /** Number of cases in the implementation's own suite. */
44
+ readonly count: number;
45
+ /** Conversion mechanisms covered, e.g. `getter`, `toJSON`, `__str__`. */
46
+ readonly mechanisms: readonly string[];
47
+ }
36
48
  export interface AdapterHello {
37
49
  readonly type: 'hello';
38
50
  readonly protocol: number;
@@ -40,6 +52,7 @@ export interface AdapterHello {
40
52
  readonly version?: string;
41
53
  readonly language?: string;
42
54
  readonly families: readonly string[];
55
+ readonly hostileObjectSuite?: HostileObjectSuiteDeclaration;
43
56
  }
44
57
  export type HandlerResult = {
45
58
  readonly ok: true;
@@ -78,6 +91,7 @@ export interface RunSummary {
78
91
  readonly version?: string;
79
92
  readonly language?: string;
80
93
  readonly families: readonly string[];
94
+ readonly hostileObjectSuite?: HostileObjectSuiteDeclaration;
81
95
  };
82
96
  readonly outcomes: readonly CaseOutcome[];
83
97
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ # Cache key version 1 fixtures
2
+
3
+ These language-neutral fixtures accompany accepted RFC 0013 and are normative
4
+ for cache key version 1.
5
+
6
+ ## Success manifest
7
+
8
+ Each entry in `success.json` contains:
9
+
10
+ - `id`: stable fixture identifier;
11
+ - `secretHex`: the cache-key secret as lowercase hex;
12
+ - `namespace`: the `{project, environment}` pair;
13
+ - `keyVersion`: the integer key version;
14
+ - `preimageUtf8`: the exact canonical preimage as UTF-8 text;
15
+ - `namespaceToken`: the expected opaque namespace prefix;
16
+ - `key`: the expected complete store key.
17
+
18
+ Both `namespaceToken` and `key` are exact. An implementation that produces a
19
+ different string for the same inputs is non-conformant, not merely different.
20
+
21
+ ## Rejection manifest
22
+
23
+ Entries in `rejections.json` carry the same input fields plus the required
24
+ stable `error` code. An entry may replace `preimageUtf8` with a `generator`:
25
+
26
+ - `repeat-string`: concatenates `count` copies of the UTF-8 string `utf8`.
27
+
28
+ `secretHex` is an empty string for the missing-secret case.
29
+ The `precedence-*` cases deliberately violate several constraints at once and
30
+ pin the RFC's required first failure.
31
+
32
+ ## How these expectations were produced
33
+
34
+ The expected keys were generated with an implementation independent of the
35
+ TypeScript reference: Node's built-in `crypto.createHmac` rather than the
36
+ `@noble/hashes` primitives `@hypequery/protocol` uses. Agreement between the
37
+ two is therefore cross-validation rather than a restatement of one
38
+ implementation's behaviour.
39
+
40
+ ## What the corpus is designed to prove
41
+
42
+ Beyond exact derivation, the success cases demonstrate the separation
43
+ properties the scheme exists for:
44
+
45
+ - `namespace-separates-environment` and `namespace-separates-project` — an
46
+ identical preimage under a different namespace yields a different key, so a
47
+ shared store cannot serve one environment's entry to another;
48
+ - `rotation-changes-key` — incrementing `keyVersion` changes the key, making
49
+ rotation a flush rather than a silent reuse;
50
+ - `secret-separates-key` — a different secret yields a different key even for
51
+ an identical namespace and preimage;
52
+ - `deployment-target-punctuation` and `deployment-target-leading-digit` — the
53
+ namespace accepts the RFC 0008 target grammar used by deployment releases;
54
+ - `sensitive-preimage-not-recoverable` — a preimage containing an email
55
+ address and a tenant identifier produces a key containing neither.
56
+
57
+ The last of these is the whole point of the RFC: the previous scheme used the
58
+ readable preimage as the store key, so those values appeared in `SCAN` output,
59
+ eviction logs, and metric labels.