Skip to content

Commit 787e278

Browse files
committed
chore: satisfy the gate families the #16608 diff derives
- the changeset carries its ADR-0087 disposition: an enforcement-ORDER change moves no authorable key, spelling or stored shape, so no conversion entry and nothing for an upgrader to hand-edit (check:adr-0087-registration); - the fail-closed leg's engine double routes delete/update/findOne through the real dispatch predicates, so it cannot be looser than ObjectQL (check:engine-double-contract); - `@objectstack/driver-sqlite-wasm` — the conformance cell's second driver family — is read from the producer's SOURCE on both axes: a vitest alias (check:test-source-alias) and a bare-key tsconfig `paths` rule (check:type-source-resolution). Measured: the paths route adds zero diagnostics from other packages here; the test layer still compiles at 0. Refs #16608. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
1 parent 0492ca6 commit 787e278

4 files changed

Lines changed: 50 additions & 4 deletions

File tree

.changeset/insert-check-post-image.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
fix(plugin-security)!: the insert-side RLS `check` is evaluated on the row that will be STORED — after `beforeInsert` — instead of on the caller's raw payload (#16608)
77

8+
<!-- adr-0087: not-required (no-migration-prescription) an enforcement-ORDER change: no authorable key, spelling or stored shape moves, so a stored `sys_metadata` row needs no conversion and an upgrader has nothing to hand-edit. What changes is which image the existing `check` predicate is evaluated against; the remedy for a newly-refused insert is to fix the policy or the data, not to migrate metadata. -->
9+
810
**BREAKING** — an accept-set narrowing on the write gate's refusal behaviour. An insert that is admitted today can be refused after this change.
911

1012
`check` validates the row a write produces — the PostgreSQL `WITH CHECK` analog. `update` reached that row by merging the caller's pre-image with the change set. `insert` could not: it has no pre-image, and the security middleware runs BEFORE the engine's operation, so its post-image was `opCtx.data` — the caller's payload as it arrived, ahead of `applyFieldDefaults` and ahead of every `beforeInsert` hook.

packages/plugins/plugin-security/src/insert-check-post-image.test.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@
7676
*/
7777

7878
import { describe, it, expect, afterEach, vi } from 'vitest';
79+
import {
80+
assertEngineDeleteDispatch,
81+
assertEngineUpdateDispatch,
82+
assertEngineFindOnePredicate,
83+
type EngineFindOneQueryInput,
84+
type EngineUpdateDispatchData,
85+
type EngineUpdateDispatchInput,
86+
type EngineDeleteDispatchInput,
87+
} from '@objectstack/metadata-core';
7988
import { ObjectQL } from '@objectstack/objectql';
8089
import { SqlDriver } from '@objectstack/driver-sql';
8190
import { SqliteWasmDriver } from '@objectstack/driver-sqlite-wasm';
@@ -480,10 +489,23 @@ describe('[#16608] fail-closed — an engine that does not run the installed che
480489
registerMiddleware: (mw: (opCtx: unknown, next: () => Promise<void>) => Promise<void>) => middlewares.push(mw),
481490
getSchema: (name: string) => OBJECTS.find((o) => o.name === name),
482491
async find() { return []; },
483-
async findOne() { return null; },
492+
// The write verbs route through the real engine's dispatch predicates —
493+
// a double looser than `ObjectQL` turns a green suite into no suite
494+
// (`check:engine-double-contract`, from #4434). This double refuses a
495+
// call the engine refuses even though the leg below never makes one.
496+
async findOne(object: string, query: EngineFindOneQueryInput) {
497+
assertEngineFindOnePredicate(object, query);
498+
return null;
499+
},
484500
async insert(_object: string, data: Record<string, unknown>) { rows.push({ ...data }); return data; },
485-
async update(_object: string, data: unknown) { return data; },
486-
async delete() { return true; },
501+
async update(_object: string, data: EngineUpdateDispatchData, options?: EngineUpdateDispatchInput | null) {
502+
assertEngineUpdateDispatch(data, options);
503+
return data;
504+
},
505+
async delete(_object: string, options?: EngineDeleteDispatchInput | null) {
506+
assertEngineDeleteDispatch(options);
507+
return true;
508+
},
487509
};
488510
const services: Record<string, unknown> = {
489511
manifest: { register: vi.fn() },

packages/plugins/plugin-security/tsconfig.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,17 @@
2525
// `@objectstack/types/*` subpath, and a `paths` target that matches
2626
// nothing on disk would silently fall back to node resolution (see the
2727
// rest package's block for the measured traps).
28+
// [#16608] `@objectstack/driver-sqlite-wasm` is imported as a VALUE by
29+
// `src/insert-check-post-image.test.ts`, whose conformance cell runs on two
30+
// driver families. Same rule, same reason and the same bare (star-free)
31+
// key shape as the entry above — an exact match, so it cannot swallow a
32+
// subpath, and this package imports none. It is declared HERE and not in
33+
// `tsconfig.test.json` because a child that declared its own `paths` would
34+
// REPLACE this map rather than merge into it, silently sending
35+
// `@objectstack/types` back to `dist/`.
2836
"paths": {
29-
"@objectstack/types": ["../../types/src/index.ts"]
37+
"@objectstack/types": ["../../types/src/index.ts"],
38+
"@objectstack/driver-sqlite-wasm": ["../../drivers/driver-sqlite-wasm/src/index.ts"]
3039
}
3140
},
3241
"include": [

packages/plugins/plugin-security/vitest.config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,19 @@ export default defineConfig({
7171
find: /^@objectstack\/metadata-protocol$/,
7272
replacement: path.resolve(__dirname, '../../metadata-protocol/src/index.ts'),
7373
},
74+
// [#16608] `insert-check-post-image.test.ts` imports `SqliteWasmDriver`
75+
// as a VALUE: its conformance cell runs on two driver families, because
76+
// "the check judges the row that will be stored" is a claim about the
77+
// write gate that must not turn out to depend on which backend a
78+
// deployment happens to run. Same reason as `driver-sql` above — left
79+
// unaliased the specifier resolves through the workspace link to `dist/`,
80+
// a BUILD ARTIFACT, and a dist merely BEHIND runs GREEN against the
81+
// driver's old behaviour while saying nothing (`check:test-source-alias`
82+
// refuses exactly that).
83+
{
84+
find: /^@objectstack\/driver-sqlite-wasm$/,
85+
replacement: path.resolve(__dirname, '../../drivers/driver-sqlite-wasm/src/index.ts'),
86+
},
7487
],
7588
},
7689
});

0 commit comments

Comments
 (0)