@noy-db/test-format-conformance 0.7.0-pre.15 → 0.7.0-pre.17
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/dist/index.d.ts +52 -2
- package/dist/index.js +39 -6
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Vault, ExportFormat } from '@noy-db/hub';
|
|
1
|
+
import { Vault, ExportFormat, NoydbStore } from '@noy-db/hub';
|
|
2
2
|
|
|
3
3
|
/** One plaintext-producing entry point, named as a consumer would call it. */
|
|
4
4
|
interface FormatEntryPoint {
|
|
@@ -34,6 +34,24 @@ interface FormatFixture {
|
|
|
34
34
|
* anything about the fixture's grants.
|
|
35
35
|
*/
|
|
36
36
|
vault(): Promise<Vault>;
|
|
37
|
+
/**
|
|
38
|
+
* The same vault, plus the {@link ObservedStore} it was built on (#1211).
|
|
39
|
+
*
|
|
40
|
+
* REQUIRED. It is declared optional only so the type does not break a
|
|
41
|
+
* consumer mid-upgrade; a fixture without it FAILS the before-reading case
|
|
42
|
+
* with a migration message rather than silently falling back to the weaker
|
|
43
|
+
* lexical observation. A silent fallback would let a package look conformant
|
|
44
|
+
* while observed by the mechanism this replaces, with nothing in the output
|
|
45
|
+
* saying which one ran.
|
|
46
|
+
*
|
|
47
|
+
* Wrap with `observeStore(...)` where the store is CREATED and pass the
|
|
48
|
+
* result to `createNoydb` — a wrapper applied after the vault exists
|
|
49
|
+
* intercepts nothing, because the vault captured its store at construction.
|
|
50
|
+
*/
|
|
51
|
+
observableVault?(): Promise<{
|
|
52
|
+
vault: Vault;
|
|
53
|
+
store: ObservedStore;
|
|
54
|
+
}>;
|
|
37
55
|
/**
|
|
38
56
|
* EVERY plaintext-producing export entry point — not a representative one.
|
|
39
57
|
* A format with four exports and one listed here reports a green suite for
|
|
@@ -77,6 +95,38 @@ interface FormatFixture {
|
|
|
77
95
|
declare class ExportDeniedByConformanceKit extends Error {
|
|
78
96
|
constructor(gate: 'export' | 'import', tier: string, format?: string);
|
|
79
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* A `NoydbStore` that counts the reads passing through it (#1211).
|
|
100
|
+
*
|
|
101
|
+
* ## Why the kit owns this and the FIXTURE applies it
|
|
102
|
+
*
|
|
103
|
+
* §7 of the design left open whether the kit should wrap the fixture's store
|
|
104
|
+
* or the fixture should hand back a pre-wrapped one. Building it settled the
|
|
105
|
+
* question: **a wrapper applied after `vault()` returns intercepts nothing**,
|
|
106
|
+
* because the vault captured its store at construction. So the wrapping has to
|
|
107
|
+
* happen where the store is created — inside the fixture.
|
|
108
|
+
*
|
|
109
|
+
* The counting logic still lives HERE rather than in nine fixtures: a fixture
|
|
110
|
+
* that miscounts would make its own package look conformant, which is the one
|
|
111
|
+
* thing a shared kit exists to prevent. The fixture threads it; the kit owns
|
|
112
|
+
* what it means.
|
|
113
|
+
*
|
|
114
|
+
* ## What is counted
|
|
115
|
+
*
|
|
116
|
+
* `get` and `list` only — the read surface an export must traverse to produce
|
|
117
|
+
* plaintext. `put`/`delete`/`loadAll`/`saveAll` are untouched and unwrapped.
|
|
118
|
+
*/
|
|
119
|
+
interface ObservedStore extends NoydbStore {
|
|
120
|
+
/** Reads recorded since the last {@link ObservedStore.__resetReads}. */
|
|
121
|
+
__reads(): number;
|
|
122
|
+
/** Zero the counter. The kit calls this to open its measurement window. */
|
|
123
|
+
__resetReads(): void;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Wrap a store so the conformance kit can count reads through it.
|
|
127
|
+
* Apply this where the store is CREATED, and pass the result to `createNoydb`.
|
|
128
|
+
*/
|
|
129
|
+
declare function observeStore(inner: NoydbStore): ObservedStore;
|
|
80
130
|
/**
|
|
81
131
|
* Run the shared `as-*` gate contract against one format.
|
|
82
132
|
*
|
|
@@ -84,4 +134,4 @@ declare class ExportDeniedByConformanceKit extends Error {
|
|
|
84
134
|
*/
|
|
85
135
|
declare function runFormatConformanceTests(name: string, fixture: FormatFixture): void;
|
|
86
136
|
|
|
87
|
-
export { ExportDeniedByConformanceKit, type FormatEntryPoint, type FormatFixture, runFormatConformanceTests };
|
|
137
|
+
export { ExportDeniedByConformanceKit, type FormatEntryPoint, type FormatFixture, type ObservedStore, observeStore, runFormatConformanceTests };
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,24 @@ var ExportDeniedByConformanceKit = class extends Error {
|
|
|
6
6
|
this.name = "ExportDeniedByConformanceKit";
|
|
7
7
|
}
|
|
8
8
|
};
|
|
9
|
+
function observeStore(inner) {
|
|
10
|
+
let reads = 0;
|
|
11
|
+
return {
|
|
12
|
+
...inner,
|
|
13
|
+
get: (...args) => {
|
|
14
|
+
reads += 1;
|
|
15
|
+
return inner.get(...args);
|
|
16
|
+
},
|
|
17
|
+
list: (...args) => {
|
|
18
|
+
reads += 1;
|
|
19
|
+
return inner.list(...args);
|
|
20
|
+
},
|
|
21
|
+
__reads: () => reads,
|
|
22
|
+
__resetReads: () => {
|
|
23
|
+
reads = 0;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
9
27
|
function denyGates(vault, tier, format, seen) {
|
|
10
28
|
const v = vault;
|
|
11
29
|
v["assertCanExport"] = () => {
|
|
@@ -46,14 +64,28 @@ function runFormatConformanceTests(name, fixture) {
|
|
|
46
64
|
const vault = denyGates(await fixture.vault(), fixture.tier, fixture.format, seen);
|
|
47
65
|
await expect(entry.run(vault)).rejects.toThrow(ExportDeniedByConformanceKit);
|
|
48
66
|
});
|
|
49
|
-
it(`${entry.name}: refuses BEFORE reading any record`, async () => {
|
|
50
|
-
|
|
51
|
-
|
|
67
|
+
it(`${entry.name}: refuses BEFORE reading any record \u2014 observed at the STORE`, async () => {
|
|
68
|
+
expect(
|
|
69
|
+
fixture.observableVault,
|
|
70
|
+
`${entry.name}: fixture must supply observableVault() \u2014 wrap the store with observeStore() where it is CREATED and return it alongside the vault (#1211). A wrapper applied after the vault exists intercepts nothing.`
|
|
71
|
+
).toBeTypeOf("function");
|
|
72
|
+
const built = await fixture.observableVault();
|
|
73
|
+
const vault = denyGates(built.vault, fixture.tier, fixture.format, { decryptCalls: [] });
|
|
74
|
+
built.store.__resetReads();
|
|
52
75
|
await expect(entry.run(vault)).rejects.toThrow(ExportDeniedByConformanceKit);
|
|
53
76
|
expect(
|
|
54
|
-
|
|
55
|
-
`${entry.name} read
|
|
56
|
-
).
|
|
77
|
+
built.store.__reads(),
|
|
78
|
+
`${entry.name} read from the store before the export gate refused`
|
|
79
|
+
).toBe(0);
|
|
80
|
+
});
|
|
81
|
+
it(`${entry.name}: the ungated call DOES read the store \u2014 the control for the case above`, async () => {
|
|
82
|
+
const built = await fixture.observableVault();
|
|
83
|
+
built.store.__resetReads();
|
|
84
|
+
await entry.run(built.vault);
|
|
85
|
+
expect(
|
|
86
|
+
built.store.__reads(),
|
|
87
|
+
`${entry.name} produced output without reading the store \u2014 the refusal case above cannot distinguish a working gate from an entry point that reads nothing`
|
|
88
|
+
).toBeGreaterThan(0);
|
|
57
89
|
});
|
|
58
90
|
}
|
|
59
91
|
const importEntries = fixture.imports ?? [];
|
|
@@ -102,6 +134,7 @@ function runFormatConformanceTests(name, fixture) {
|
|
|
102
134
|
}
|
|
103
135
|
export {
|
|
104
136
|
ExportDeniedByConformanceKit,
|
|
137
|
+
observeStore,
|
|
105
138
|
runFormatConformanceTests
|
|
106
139
|
};
|
|
107
140
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/test-format-conformance** — the `as-*` export/import gate,\n * published as an executable suite.\n *\n * The `as-*` family is the one place plaintext leaves the vault. Every export\n * is gated by `vault.assertCanExport(tier, format)` before producing anything,\n * and that call is the whole security boundary: a projection that skips it\n * hands out decrypted records to a caller the vault would have refused. The\n * import side is `vault.assertCanImport`, gating what may be planned INTO a\n * vault.\n *\n * ## Two entry-point shapes, one contract\n *\n * The 0.7 line inverted four formats: the entry point moved from a function\n * taking the vault as an ARGUMENT (`toString(vault, opts)`) to a METHOD ON the\n * vault (`vault.export(asCsv(), {})`). Both shapes carry the same obligation,\n * and this kit checks both with one mechanism — see the next section, because\n * getting that mechanism wrong is precisely how this kit went blind once.\n *\n * ## Gated is not the property. Gated BEFORE decrypting is.\n *\n * A gate called after `exportStream` has already run is a gate that refuses\n * the caller and decrypts anyway. So the suite asserts BOTH:\n *\n * - every entry point REJECTS when the gate denies — and rejects with THIS\n * KIT'S OWN ERROR, so the refusal is attributable to the gate rather than\n * to a miswired fixture throwing something else; and\n * - it rejects having read NOTHING — `exportStream` is never called.\n *\n * The second is the one a delegation refactor breaks silently: move the gate\n * downstream and every existing test still passes.\n *\n * ## Why the kit PATCHES THE INSTANCE, and no longer proxies it (#1209)\n *\n * The first version wrapped the vault in a `Proxy` whose `get` trap replaced\n * `assertCanExport`, forwarding calls with `value.apply(target, args)`. That\n * works when the entry point takes the vault as an argument — the package\n * calls `proxy.assertCanExport(...)` and the trap fires. It CANNOT work for a\n * method on the vault: `vault.export` runs with `this` bound to the real\n * object (`apply(receiver, …)` is not an option — `Vault` has private fields,\n * and a Proxy receiver breaks private-field access), so the gate it consults\n * is the unproxied one and the denial is silently bypassed. Both assertions\n * passed vacuously; nothing turned red.\n *\n * Patching own properties onto the REAL instance intercepts both shapes,\n * because property lookup happens at call time and an own property shadows the\n * prototype method — including for hub's own INTERNAL delegation\n * (`exportJSON()` calls `this.exportStream(...)`, which the Proxy never saw\n * and the patch does). Private fields keep working because it IS the real\n * object. The patch mutates the fixture's vault, which is why `vault()` must\n * build a fresh one per case — a requirement the fixture already carries.\n *\n * If hub ever routes its gate around `vault.assertCanExport` (say, by inlining\n * the capability check), this mechanism fails LOUD — the ungated call\n * succeeds, the denial test goes red — not silent. That is the acceptable\n * failure direction.\n *\n * ## What is observed, and what deliberately is not\n *\n * `exportStream` is the decrypting PRIMITIVE, and the only method recorded.\n * `vault.export` / `vault.import` are NOT recorded: under the inverted shape\n * they are the entry points themselves (and `download`/`write` call\n * `vault.export` internally), so recording them would fail every correct\n * inverted format spuriously. The first version also recorded a `snapshot`\n * method that `Vault` does not have — a guessed identifier, which is a query\n * that cannot falsify. The list is now exactly the primitives that exist.\n *\n * @packageDocumentation\n */\nimport { describe, it, expect } from 'vitest'\nimport type { Vault, ExportFormat } from '@noy-db/hub'\n\n/** One plaintext-producing entry point, named as a consumer would call it. */\nexport interface FormatEntryPoint {\n /** Shown in the test title, e.g. `'toString'` or `'vault.export'`. */\n readonly name: string\n /** Call it against the supplied vault. Arguments are the fixture's business. */\n run(vault: Vault): Promise<unknown>\n}\n\n/** Everything an `as-*` package must supply to be checked against the gate. */\nexport interface FormatFixture {\n /**\n * The TIER the package passes to `assertCanExport`. The `as-*` family is\n * two capability classes, not one — discovered by wiring `as-noydb`, which\n * calls `assertCanExport('bundle')` and never mentions plaintext because it\n * emits an encrypted pod. A kit that assumed one tier would have made that\n * fixture describe itself wrongly while still passing.\n */\n readonly tier: 'plaintext' | 'bundle'\n /**\n * The format tag, e.g. `'csv'`. REQUIRED for the plaintext tier and\n * meaningless for `bundle` — hub itself throws when a plaintext check\n * arrives without one, so the pairing is asserted rather than assumed.\n */\n readonly format?: ExportFormat\n /**\n * A REAL vault with at least one record. Built fresh per case, so an entry\n * point that mutates it cannot leak into the next assertion — and because\n * the kit PATCHES the instance it is handed, reuse would leak the patch.\n *\n * For a format using the inverted shape (`vault.export(...)`), the vault\n * must be created with `formatsStrategy: withFormats()` — without it the\n * CAN-export guard fails with `FormatsNotEnabledError` before proving\n * anything about the fixture's grants.\n */\n vault(): Promise<Vault>\n /**\n * EVERY plaintext-producing export entry point — not a representative one.\n * A format with four exports and one listed here reports a green suite for\n * the three nobody checked.\n */\n readonly exports: ReadonlyArray<FormatEntryPoint>\n /**\n * Import entry points (`vault.import(...)`, legacy `fromString`), gated by\n * `assertCanImport`. Optional because not every format decodes — but a\n * format that ships a `decode` and declares no imports here is reporting a\n * green suite for a gate nobody checked, and the suite says so out loud.\n *\n * The fixture's vault must hold an `importCapability` grant for the format,\n * or the denial case is unfalsifiable: the refusal arrives from the missing\n * grant rather than from the kit's denial, and nothing distinguishes that\n * from a working gate.\n */\n readonly imports?: ReadonlyArray<FormatEntryPoint>\n /**\n * The on-disk write path, if the package has one. It must refuse without\n * `acknowledgeRisks: true`; pass a call that OMITS the flag.\n *\n * The vault this receives is the fixture's own — NOT a denying one — and it\n * must be export-CAPABLE. A vault that would refuse the export anyway makes\n * the case unfalsifiable: the refusal arrives from the gate upstream and the\n * acknowledgement is never reached. That is not hypothetical; it is what the\n * first version of this kit did, and deleting the acknowledgement guard from\n * as-csv left the suite green.\n */\n writeWithoutAcknowledgement?: (vault: Vault, path: string) => Promise<unknown>\n}\n\n/**\n * Thrown by the kit's denial patch so a refusal is ATTRIBUTABLE to the gate.\n *\n * The denial tests match on this class, not on \"it threw\". A bare\n * `rejects.toThrow()` passes on any error — a miswired fixture raising\n * `TypeError`, a vault missing `withFormats()` — which is exactly the state a\n * brand-new fixture is most likely to be in. The first version of this kit\n * defined this class for that purpose and then never matched on it.\n */\nexport class ExportDeniedByConformanceKit extends Error {\n constructor(gate: 'export' | 'import', tier: string, format?: string) {\n super(`conformance: assertCan${gate === 'export' ? 'Export' : 'Import'} denied '${tier}'${format ? ` / '${format}'` : ''}`)\n this.name = 'ExportDeniedByConformanceKit'\n }\n}\n\ninterface Observation {\n decryptCalls: string[]\n}\n\n/**\n * Patch a REAL vault in place: both gates deny with the kit's own error, and\n * the decrypting primitive is recorded. Returns the same instance.\n *\n * Own-property assignment shadows the prototype methods, so the patch fires\n * for the argument shape (`toString(vault)` → `vault.assertCanExport(...)`),\n * the inverted shape (`vault.export(...)` → `contextFor(this)` → property\n * lookup at call time), and hub's internal delegation (`exportJSON()` →\n * `this.exportStream(...)`).\n */\nfunction denyGates(vault: Vault, tier: string, format: string | undefined, seen: Observation): Vault {\n const v = vault as unknown as Record<string, unknown>\n v['assertCanExport'] = () => {\n throw new ExportDeniedByConformanceKit('export', tier, format)\n }\n v['assertCanImport'] = () => {\n throw new ExportDeniedByConformanceKit('import', tier, format)\n }\n const realStream = (vault.exportStream as (...a: unknown[]) => unknown).bind(vault)\n v['exportStream'] = (...args: unknown[]) => {\n seen.decryptCalls.push('exportStream')\n return realStream(...args)\n }\n return vault\n}\n\n/**\n * Run the shared `as-*` gate contract against one format.\n *\n * @param name - shown in the suite title, e.g. `'as-csv'`.\n */\nexport function runFormatConformanceTests(name: string, fixture: FormatFixture): void {\n describe(`${name} — as-* export gate conformance`, () => {\n it('declares a tier, and a format iff the tier needs one', () => {\n // Hub throws on `assertCanExport('plaintext')` with no format, so a\n // fixture in that state describes a call the package cannot be making.\n if (fixture.tier === 'plaintext') {\n expect(fixture.format, 'the plaintext tier requires a format').toBeTruthy()\n } else {\n expect(fixture.format, `the '${fixture.tier}' tier takes no format`).toBeUndefined()\n }\n })\n\n it('declares at least one export entry point', () => {\n // A fixture with an empty list would pass every case below without\n // running anything — a live suite iterating an empty array.\n expect(fixture.exports.length).toBeGreaterThan(0)\n })\n\n for (const entry of fixture.exports) {\n it(`${entry.name}: SUCCEEDS on an ungated vault — otherwise its refusal below is free`, async () => {\n // Per ENTRY, not only exports[0]: a refusal is only evidence when the\n // same call would otherwise succeed, and each entry point can be\n // miswired independently. A fixture whose `format` tag does not match\n // what the package passes — or which forgets the exportCapability\n // grant, or omits `formatsStrategy: withFormats()` on an inverted\n // vault — makes the denial pass by refusing for the wrong reason.\n const vault = await fixture.vault()\n // `toSatisfy(() => true)`, not `toBeDefined()`: `download`/`write`\n // return Promise<void>, and their resolved value is legitimately\n // undefined. The assertion is \"it RESOLVES\" — the guard is about the\n // call not refusing, not about what it returns. (Found the moment this\n // guard went per-entry; the old exports[0]-only guard happened to\n // always land on a value-returning entry.)\n await expect(\n entry.run(vault),\n `${entry.name} failed on an ungated vault — check the \\`format\\` tag, the exportCapability grant, and (for vault.export entries) formatsStrategy: withFormats()`,\n ).resolves.toSatisfy(() => true)\n })\n\n it(`${entry.name}: REFUSES when assertCanExport denies — with the KIT'S error`, async () => {\n const seen: Observation = { decryptCalls: [] }\n const vault = denyGates(await fixture.vault(), fixture.tier, fixture.format, seen)\n // Matched on the class: a bare toThrow() passes on ANY error, which\n // makes a miswired fixture indistinguishable from a working gate.\n await expect(entry.run(vault)).rejects.toThrow(ExportDeniedByConformanceKit)\n })\n\n it(`${entry.name}: refuses BEFORE reading any record`, async () => {\n const seen: Observation = { decryptCalls: [] }\n const vault = denyGates(await fixture.vault(), fixture.tier, fixture.format, seen)\n await expect(entry.run(vault)).rejects.toThrow(ExportDeniedByConformanceKit)\n // The property that a delegation refactor breaks silently: a gate\n // moved downstream still refuses the caller, having already decrypted.\n expect(\n seen.decryptCalls,\n `${entry.name} read records before the export gate refused`,\n ).toEqual([])\n })\n }\n\n const importEntries = fixture.imports ?? []\n const importTitle = importEntries.length\n ? null\n : 'imports: SKIPPED — fixture declares none, so the assertCanImport gate is UNVERIFIED here'\n if (importTitle) {\n it(importTitle, () => {\n // Passes loudly. A format that ships a `decode` and declares no import\n // entries is leaving a gate unchecked, and the output should say so\n // rather than staying quiet — a documented absence, not a hole.\n expect(importEntries).toEqual([])\n })\n }\n\n for (const entry of importEntries) {\n it(`${entry.name}: SUCCEEDS on an ungated vault — otherwise its refusal below is free`, async () => {\n // Same falsifiability requirement as the export side: without an\n // importCapability grant the denial case refuses for the wrong reason.\n const vault = await fixture.vault()\n await expect(\n entry.run(vault),\n `${entry.name} failed on an ungated vault — check the importCapability grant`,\n ).resolves.toSatisfy(() => true)\n })\n\n it(`${entry.name}: REFUSES when assertCanImport denies — with the KIT'S error`, async () => {\n const seen: Observation = { decryptCalls: [] }\n const vault = denyGates(await fixture.vault(), fixture.tier, fixture.format, seen)\n await expect(entry.run(vault)).rejects.toThrow(ExportDeniedByConformanceKit)\n })\n\n it(`${entry.name}: refuses BEFORE reading any record`, async () => {\n // Import planning READS the vault to diff against it (`diffVault`\n // routes through `exportStream`), so a gate moved after the plan\n // decrypts before refusing — the same silent break as the export side.\n const seen: Observation = { decryptCalls: [] }\n const vault = denyGates(await fixture.vault(), fixture.tier, fixture.format, seen)\n await expect(entry.run(vault)).rejects.toThrow(ExportDeniedByConformanceKit)\n expect(\n seen.decryptCalls,\n `${entry.name} read records before the import gate refused`,\n ).toEqual([])\n })\n }\n\n const writeTitle = fixture.writeWithoutAcknowledgement\n ? 'write: REFUSES without acknowledgeRisks'\n : 'write: SKIPPED — fixture declares no acknowledgement case, so the plaintext-on-disk gate is UNVERIFIED here'\n\n it(writeTitle, async () => {\n const write = fixture.writeWithoutAcknowledgement\n if (!write) {\n // Passes loudly. Omitting the case would make an unchecked security\n // gate indistinguishable from a checked one in the output.\n expect(write).toBeUndefined()\n return\n }\n const vault = await fixture.vault()\n // Matched on the MESSAGE, not merely on \"it threw\". `rejects.toThrow()`\n // alone passes when the export gate refuses first — which is exactly\n // what happened here before this line existed, and it made the case\n // unable to fail. The flag name is the one string every such message\n // contains by construction.\n await expect(write(vault, '/tmp/conformance-should-not-exist')).rejects.toThrow(\n /acknowledgeRisks/i,\n )\n })\n })\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/test-format-conformance** — the `as-*` export/import gate,\n * published as an executable suite.\n *\n * The `as-*` family is the one place plaintext leaves the vault. Every export\n * is gated by `vault.assertCanExport(tier, format)` before producing anything,\n * and that call is the whole security boundary: a projection that skips it\n * hands out decrypted records to a caller the vault would have refused. The\n * import side is `vault.assertCanImport`, gating what may be planned INTO a\n * vault.\n *\n * ## Two entry-point shapes, one contract\n *\n * The 0.7 line inverted four formats: the entry point moved from a function\n * taking the vault as an ARGUMENT (`toString(vault, opts)`) to a METHOD ON the\n * vault (`vault.export(asCsv(), {})`). Both shapes carry the same obligation,\n * and this kit checks both with one mechanism — see the next section, because\n * getting that mechanism wrong is precisely how this kit went blind once.\n *\n * ## Gated is not the property. Gated BEFORE decrypting is.\n *\n * A gate called after `exportStream` has already run is a gate that refuses\n * the caller and decrypts anyway. So the suite asserts BOTH:\n *\n * - every entry point REJECTS when the gate denies — and rejects with THIS\n * KIT'S OWN ERROR, so the refusal is attributable to the gate rather than\n * to a miswired fixture throwing something else; and\n * - it rejects having read NOTHING — `exportStream` is never called.\n *\n * The second is the one a delegation refactor breaks silently: move the gate\n * downstream and every existing test still passes.\n *\n * ## Why the kit PATCHES THE INSTANCE, and no longer proxies it (#1209)\n *\n * The first version wrapped the vault in a `Proxy` whose `get` trap replaced\n * `assertCanExport`, forwarding calls with `value.apply(target, args)`. That\n * works when the entry point takes the vault as an argument — the package\n * calls `proxy.assertCanExport(...)` and the trap fires. It CANNOT work for a\n * method on the vault: `vault.export` runs with `this` bound to the real\n * object (`apply(receiver, …)` is not an option — `Vault` has private fields,\n * and a Proxy receiver breaks private-field access), so the gate it consults\n * is the unproxied one and the denial is silently bypassed. Both assertions\n * passed vacuously; nothing turned red.\n *\n * Patching own properties onto the REAL instance intercepts both shapes,\n * because property lookup happens at call time and an own property shadows the\n * prototype method — including for hub's own INTERNAL delegation\n * (`exportJSON()` calls `this.exportStream(...)`, which the Proxy never saw\n * and the patch does). Private fields keep working because it IS the real\n * object. The patch mutates the fixture's vault, which is why `vault()` must\n * build a fresh one per case — a requirement the fixture already carries.\n *\n * If hub ever routes its gate around `vault.assertCanExport` (say, by inlining\n * the capability check), this mechanism fails LOUD — the ungated call\n * succeeds, the denial test goes red — not silent. That is the acceptable\n * failure direction.\n *\n * ## What is observed, and what deliberately is not\n *\n * `exportStream` is the decrypting PRIMITIVE, and the only method recorded.\n * `vault.export` / `vault.import` are NOT recorded: under the inverted shape\n * they are the entry points themselves (and `download`/`write` call\n * `vault.export` internally), so recording them would fail every correct\n * inverted format spuriously. The first version also recorded a `snapshot`\n * method that `Vault` does not have — a guessed identifier, which is a query\n * that cannot falsify. The list is now exactly the primitives that exist.\n *\n * @packageDocumentation\n */\nimport { describe, it, expect } from 'vitest'\nimport type { Vault, ExportFormat, NoydbStore } from '@noy-db/hub'\n\n/** One plaintext-producing entry point, named as a consumer would call it. */\nexport interface FormatEntryPoint {\n /** Shown in the test title, e.g. `'toString'` or `'vault.export'`. */\n readonly name: string\n /** Call it against the supplied vault. Arguments are the fixture's business. */\n run(vault: Vault): Promise<unknown>\n}\n\n/** Everything an `as-*` package must supply to be checked against the gate. */\nexport interface FormatFixture {\n /**\n * The TIER the package passes to `assertCanExport`. The `as-*` family is\n * two capability classes, not one — discovered by wiring `as-noydb`, which\n * calls `assertCanExport('bundle')` and never mentions plaintext because it\n * emits an encrypted pod. A kit that assumed one tier would have made that\n * fixture describe itself wrongly while still passing.\n */\n readonly tier: 'plaintext' | 'bundle'\n /**\n * The format tag, e.g. `'csv'`. REQUIRED for the plaintext tier and\n * meaningless for `bundle` — hub itself throws when a plaintext check\n * arrives without one, so the pairing is asserted rather than assumed.\n */\n readonly format?: ExportFormat\n /**\n * A REAL vault with at least one record. Built fresh per case, so an entry\n * point that mutates it cannot leak into the next assertion — and because\n * the kit PATCHES the instance it is handed, reuse would leak the patch.\n *\n * For a format using the inverted shape (`vault.export(...)`), the vault\n * must be created with `formatsStrategy: withFormats()` — without it the\n * CAN-export guard fails with `FormatsNotEnabledError` before proving\n * anything about the fixture's grants.\n */\n vault(): Promise<Vault>\n /**\n * The same vault, plus the {@link ObservedStore} it was built on (#1211).\n *\n * REQUIRED. It is declared optional only so the type does not break a\n * consumer mid-upgrade; a fixture without it FAILS the before-reading case\n * with a migration message rather than silently falling back to the weaker\n * lexical observation. A silent fallback would let a package look conformant\n * while observed by the mechanism this replaces, with nothing in the output\n * saying which one ran.\n *\n * Wrap with `observeStore(...)` where the store is CREATED and pass the\n * result to `createNoydb` — a wrapper applied after the vault exists\n * intercepts nothing, because the vault captured its store at construction.\n */\n observableVault?(): Promise<{ vault: Vault; store: ObservedStore }>\n /**\n * EVERY plaintext-producing export entry point — not a representative one.\n * A format with four exports and one listed here reports a green suite for\n * the three nobody checked.\n */\n readonly exports: ReadonlyArray<FormatEntryPoint>\n /**\n * Import entry points (`vault.import(...)`, legacy `fromString`), gated by\n * `assertCanImport`. Optional because not every format decodes — but a\n * format that ships a `decode` and declares no imports here is reporting a\n * green suite for a gate nobody checked, and the suite says so out loud.\n *\n * The fixture's vault must hold an `importCapability` grant for the format,\n * or the denial case is unfalsifiable: the refusal arrives from the missing\n * grant rather than from the kit's denial, and nothing distinguishes that\n * from a working gate.\n */\n readonly imports?: ReadonlyArray<FormatEntryPoint>\n /**\n * The on-disk write path, if the package has one. It must refuse without\n * `acknowledgeRisks: true`; pass a call that OMITS the flag.\n *\n * The vault this receives is the fixture's own — NOT a denying one — and it\n * must be export-CAPABLE. A vault that would refuse the export anyway makes\n * the case unfalsifiable: the refusal arrives from the gate upstream and the\n * acknowledgement is never reached. That is not hypothetical; it is what the\n * first version of this kit did, and deleting the acknowledgement guard from\n * as-csv left the suite green.\n */\n writeWithoutAcknowledgement?: (vault: Vault, path: string) => Promise<unknown>\n}\n\n/**\n * Thrown by the kit's denial patch so a refusal is ATTRIBUTABLE to the gate.\n *\n * The denial tests match on this class, not on \"it threw\". A bare\n * `rejects.toThrow()` passes on any error — a miswired fixture raising\n * `TypeError`, a vault missing `withFormats()` — which is exactly the state a\n * brand-new fixture is most likely to be in. The first version of this kit\n * defined this class for that purpose and then never matched on it.\n */\nexport class ExportDeniedByConformanceKit extends Error {\n constructor(gate: 'export' | 'import', tier: string, format?: string) {\n super(`conformance: assertCan${gate === 'export' ? 'Export' : 'Import'} denied '${tier}'${format ? ` / '${format}'` : ''}`)\n this.name = 'ExportDeniedByConformanceKit'\n }\n}\n\ninterface Observation {\n decryptCalls: string[]\n}\n\n/**\n * A `NoydbStore` that counts the reads passing through it (#1211).\n *\n * ## Why the kit owns this and the FIXTURE applies it\n *\n * §7 of the design left open whether the kit should wrap the fixture's store\n * or the fixture should hand back a pre-wrapped one. Building it settled the\n * question: **a wrapper applied after `vault()` returns intercepts nothing**,\n * because the vault captured its store at construction. So the wrapping has to\n * happen where the store is created — inside the fixture.\n *\n * The counting logic still lives HERE rather than in nine fixtures: a fixture\n * that miscounts would make its own package look conformant, which is the one\n * thing a shared kit exists to prevent. The fixture threads it; the kit owns\n * what it means.\n *\n * ## What is counted\n *\n * `get` and `list` only — the read surface an export must traverse to produce\n * plaintext. `put`/`delete`/`loadAll`/`saveAll` are untouched and unwrapped.\n */\nexport interface ObservedStore extends NoydbStore {\n /** Reads recorded since the last {@link ObservedStore.__resetReads}. */\n __reads(): number\n /** Zero the counter. The kit calls this to open its measurement window. */\n __resetReads(): void\n}\n\n/**\n * Wrap a store so the conformance kit can count reads through it.\n * Apply this where the store is CREATED, and pass the result to `createNoydb`.\n */\nexport function observeStore(inner: NoydbStore): ObservedStore {\n let reads = 0\n return {\n ...inner,\n get: (...args: Parameters<NoydbStore['get']>) => { reads += 1; return inner.get(...args) },\n list: (...args: Parameters<NoydbStore['list']>) => { reads += 1; return inner.list(...args) },\n __reads: () => reads,\n __resetReads: () => { reads = 0 },\n } as ObservedStore\n}\n\n/**\n * Patch a REAL vault in place: both gates deny with the kit's own error, and\n * the decrypting primitive is recorded. Returns the same instance.\n *\n * Own-property assignment shadows the prototype methods, so the patch fires\n * for the argument shape (`toString(vault)` → `vault.assertCanExport(...)`),\n * the inverted shape (`vault.export(...)` → `contextFor(this)` → property\n * lookup at call time), and hub's internal delegation (`exportJSON()` →\n * `this.exportStream(...)`).\n */\nfunction denyGates(vault: Vault, tier: string, format: string | undefined, seen: Observation): Vault {\n const v = vault as unknown as Record<string, unknown>\n v['assertCanExport'] = () => {\n throw new ExportDeniedByConformanceKit('export', tier, format)\n }\n v['assertCanImport'] = () => {\n throw new ExportDeniedByConformanceKit('import', tier, format)\n }\n const realStream = (vault.exportStream as (...a: unknown[]) => unknown).bind(vault)\n v['exportStream'] = (...args: unknown[]) => {\n seen.decryptCalls.push('exportStream')\n return realStream(...args)\n }\n return vault\n}\n\n/**\n * Run the shared `as-*` gate contract against one format.\n *\n * @param name - shown in the suite title, e.g. `'as-csv'`.\n */\nexport function runFormatConformanceTests(name: string, fixture: FormatFixture): void {\n describe(`${name} — as-* export gate conformance`, () => {\n it('declares a tier, and a format iff the tier needs one', () => {\n // Hub throws on `assertCanExport('plaintext')` with no format, so a\n // fixture in that state describes a call the package cannot be making.\n if (fixture.tier === 'plaintext') {\n expect(fixture.format, 'the plaintext tier requires a format').toBeTruthy()\n } else {\n expect(fixture.format, `the '${fixture.tier}' tier takes no format`).toBeUndefined()\n }\n })\n\n it('declares at least one export entry point', () => {\n // A fixture with an empty list would pass every case below without\n // running anything — a live suite iterating an empty array.\n expect(fixture.exports.length).toBeGreaterThan(0)\n })\n\n for (const entry of fixture.exports) {\n it(`${entry.name}: SUCCEEDS on an ungated vault — otherwise its refusal below is free`, async () => {\n // Per ENTRY, not only exports[0]: a refusal is only evidence when the\n // same call would otherwise succeed, and each entry point can be\n // miswired independently. A fixture whose `format` tag does not match\n // what the package passes — or which forgets the exportCapability\n // grant, or omits `formatsStrategy: withFormats()` on an inverted\n // vault — makes the denial pass by refusing for the wrong reason.\n const vault = await fixture.vault()\n // `toSatisfy(() => true)`, not `toBeDefined()`: `download`/`write`\n // return Promise<void>, and their resolved value is legitimately\n // undefined. The assertion is \"it RESOLVES\" — the guard is about the\n // call not refusing, not about what it returns. (Found the moment this\n // guard went per-entry; the old exports[0]-only guard happened to\n // always land on a value-returning entry.)\n await expect(\n entry.run(vault),\n `${entry.name} failed on an ungated vault — check the \\`format\\` tag, the exportCapability grant, and (for vault.export entries) formatsStrategy: withFormats()`,\n ).resolves.toSatisfy(() => true)\n })\n\n it(`${entry.name}: REFUSES when assertCanExport denies — with the KIT'S error`, async () => {\n const seen: Observation = { decryptCalls: [] }\n const vault = denyGates(await fixture.vault(), fixture.tier, fixture.format, seen)\n // Matched on the class: a bare toThrow() passes on ANY error, which\n // makes a miswired fixture indistinguishable from a working gate.\n await expect(entry.run(vault)).rejects.toThrow(ExportDeniedByConformanceKit)\n })\n\n it(`${entry.name}: refuses BEFORE reading any record — observed at the STORE`, async () => {\n // #1211. The observation is STRUCTURAL: it counts reads leaving the\n // store, not calls to a named vault method. A store cannot be bypassed\n // by an API reshape — every record any entry point produces is bytes\n // read from it — whereas #1209 happened precisely because a reshape\n // moved the gate out of the place the observer was looking.\n //\n // No silent fallback: a fixture without `observableVault` FAILS here.\n // Reverting to the lexical observation would let a package look\n // conformant while watched by the weaker mechanism, with nothing in\n // the output saying which one ran.\n expect(\n fixture.observableVault,\n `${entry.name}: fixture must supply observableVault() — wrap the store with observeStore() `\n + 'where it is CREATED and return it alongside the vault (#1211). A wrapper applied after '\n + 'the vault exists intercepts nothing.',\n ).toBeTypeOf('function')\n\n const built = await fixture.observableVault!()\n const vault = denyGates(built.vault, fixture.tier, fixture.format, { decryptCalls: [] })\n\n // The WINDOW opens here, after vault construction. Store reads happen\n // at openVault (keyring, fence) before any export runs, so a total\n // count would pass on those alone — i.e. on an export that did\n // nothing. Counting only across the call makes the false pass require\n // a read CAUSED BY the call.\n built.store.__resetReads()\n await expect(entry.run(vault)).rejects.toThrow(ExportDeniedByConformanceKit)\n expect(\n built.store.__reads(),\n `${entry.name} read from the store before the export gate refused`,\n ).toBe(0)\n })\n\n it(`${entry.name}: the ungated call DOES read the store — the control for the case above`, async () => {\n // Without this, `reads === 0` above is satisfied by an export served\n // from a warm cache, or by an entry point that reads nothing at all —\n // both indistinguishable from \"the gate refused first\". This is the\n // trap an adversarial harness elsewhere in this family hit: reads\n // through an already-used vault came from cache and never reached the\n // store, so the harness proved nothing while appearing to validate the\n // product's central claim.\n const built = await fixture.observableVault!()\n built.store.__resetReads()\n await entry.run(built.vault)\n expect(\n built.store.__reads(),\n `${entry.name} produced output without reading the store — the refusal case above cannot `\n + 'distinguish a working gate from an entry point that reads nothing',\n ).toBeGreaterThan(0)\n })\n }\n\n const importEntries = fixture.imports ?? []\n const importTitle = importEntries.length\n ? null\n : 'imports: SKIPPED — fixture declares none, so the assertCanImport gate is UNVERIFIED here'\n if (importTitle) {\n it(importTitle, () => {\n // Passes loudly. A format that ships a `decode` and declares no import\n // entries is leaving a gate unchecked, and the output should say so\n // rather than staying quiet — a documented absence, not a hole.\n expect(importEntries).toEqual([])\n })\n }\n\n for (const entry of importEntries) {\n it(`${entry.name}: SUCCEEDS on an ungated vault — otherwise its refusal below is free`, async () => {\n // Same falsifiability requirement as the export side: without an\n // importCapability grant the denial case refuses for the wrong reason.\n const vault = await fixture.vault()\n await expect(\n entry.run(vault),\n `${entry.name} failed on an ungated vault — check the importCapability grant`,\n ).resolves.toSatisfy(() => true)\n })\n\n it(`${entry.name}: REFUSES when assertCanImport denies — with the KIT'S error`, async () => {\n const seen: Observation = { decryptCalls: [] }\n const vault = denyGates(await fixture.vault(), fixture.tier, fixture.format, seen)\n await expect(entry.run(vault)).rejects.toThrow(ExportDeniedByConformanceKit)\n })\n\n it(`${entry.name}: refuses BEFORE reading any record`, async () => {\n // Import planning READS the vault to diff against it (`diffVault`\n // routes through `exportStream`), so a gate moved after the plan\n // decrypts before refusing — the same silent break as the export side.\n const seen: Observation = { decryptCalls: [] }\n const vault = denyGates(await fixture.vault(), fixture.tier, fixture.format, seen)\n await expect(entry.run(vault)).rejects.toThrow(ExportDeniedByConformanceKit)\n expect(\n seen.decryptCalls,\n `${entry.name} read records before the import gate refused`,\n ).toEqual([])\n })\n }\n\n const writeTitle = fixture.writeWithoutAcknowledgement\n ? 'write: REFUSES without acknowledgeRisks'\n : 'write: SKIPPED — fixture declares no acknowledgement case, so the plaintext-on-disk gate is UNVERIFIED here'\n\n it(writeTitle, async () => {\n const write = fixture.writeWithoutAcknowledgement\n if (!write) {\n // Passes loudly. Omitting the case would make an unchecked security\n // gate indistinguishable from a checked one in the output.\n expect(write).toBeUndefined()\n return\n }\n const vault = await fixture.vault()\n // Matched on the MESSAGE, not merely on \"it threw\". `rejects.toThrow()`\n // alone passes when the export gate refuses first — which is exactly\n // what happened here before this line existed, and it made the case\n // unable to fail. The flag name is the one string every such message\n // contains by construction.\n await expect(write(vault, '/tmp/conformance-should-not-exist')).rejects.toThrow(\n /acknowledgeRisks/i,\n )\n })\n })\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noy-db/test-format-conformance",
|
|
3
|
-
"version": "0.7.0-pre.
|
|
3
|
+
"version": "0.7.0-pre.17",
|
|
4
4
|
"description": "Parameterized contract tests for noy-db as-* formats — the export-gate suite every plaintext projection must pass",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "vLannaAi <vicio@lanna.ai>",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"vitest": "^3.0.0",
|
|
36
|
-
"@noy-db/hub": "^0.7.0-pre.
|
|
36
|
+
"@noy-db/hub": "^0.7.0-pre.17"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"vitest": "^3.0.0",
|
|
40
|
-
"@noy-db/hub": "0.7.0-pre.
|
|
40
|
+
"@noy-db/hub": "0.7.0-pre.17"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
|
43
43
|
"noy-db",
|