@secure-exec/core 0.2.0-rc.1 → 0.2.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 (60) hide show
  1. package/dist/generated/isolate-runtime.d.ts +2 -2
  2. package/dist/generated/isolate-runtime.js +2 -2
  3. package/dist/index.d.ts +17 -4
  4. package/dist/index.js +10 -2
  5. package/dist/isolate-runtime/require-setup.js +1489 -239
  6. package/dist/isolate-runtime/setup-dynamic-import.js +31 -0
  7. package/dist/kernel/device-backend.d.ts +14 -0
  8. package/dist/kernel/device-backend.js +251 -0
  9. package/dist/kernel/device-layer.js +9 -0
  10. package/dist/kernel/file-lock.js +2 -3
  11. package/dist/kernel/index.d.ts +4 -4
  12. package/dist/kernel/index.js +3 -3
  13. package/dist/kernel/kernel.js +141 -122
  14. package/dist/kernel/mount-table.d.ts +75 -0
  15. package/dist/kernel/mount-table.js +353 -0
  16. package/dist/kernel/permissions.d.ts +9 -0
  17. package/dist/kernel/permissions.js +33 -1
  18. package/dist/kernel/proc-backend.d.ts +30 -0
  19. package/dist/kernel/proc-backend.js +428 -0
  20. package/dist/kernel/proc-layer.js +6 -0
  21. package/dist/kernel/process-table.d.ts +3 -1
  22. package/dist/kernel/process-table.js +23 -3
  23. package/dist/kernel/pty.d.ts +3 -2
  24. package/dist/kernel/pty.js +13 -2
  25. package/dist/kernel/socket-table.d.ts +7 -0
  26. package/dist/kernel/socket-table.js +99 -35
  27. package/dist/kernel/types.d.ts +45 -4
  28. package/dist/kernel/types.js +9 -0
  29. package/dist/kernel/vfs.d.ts +30 -2
  30. package/dist/kernel/vfs.js +19 -2
  31. package/dist/shared/api-types.d.ts +6 -0
  32. package/dist/shared/bridge-contract.d.ts +21 -3
  33. package/dist/shared/bridge-contract.js +2 -0
  34. package/dist/shared/console-formatter.js +8 -8
  35. package/dist/shared/global-exposure.js +95 -0
  36. package/dist/shared/in-memory-fs.d.ts +14 -59
  37. package/dist/shared/in-memory-fs.js +97 -597
  38. package/dist/shared/permissions.js +5 -0
  39. package/dist/test/block-store-conformance.d.ts +34 -0
  40. package/dist/test/block-store-conformance.js +251 -0
  41. package/dist/test/metadata-store-conformance.d.ts +37 -0
  42. package/dist/test/metadata-store-conformance.js +646 -0
  43. package/dist/test/vfs-conformance.d.ts +65 -0
  44. package/dist/test/vfs-conformance.js +842 -0
  45. package/dist/types.d.ts +1 -0
  46. package/dist/vfs/chunked-vfs.d.ts +66 -0
  47. package/dist/vfs/chunked-vfs.js +1290 -0
  48. package/dist/vfs/host-block-store.d.ts +19 -0
  49. package/dist/vfs/host-block-store.js +97 -0
  50. package/dist/vfs/memory-block-store.d.ts +16 -0
  51. package/dist/vfs/memory-block-store.js +45 -0
  52. package/dist/vfs/memory-metadata.d.ts +75 -0
  53. package/dist/vfs/memory-metadata.js +528 -0
  54. package/dist/vfs/sqlite-metadata.d.ts +91 -0
  55. package/dist/vfs/sqlite-metadata.js +582 -0
  56. package/dist/vfs/types.d.ts +210 -0
  57. package/dist/vfs/types.js +8 -0
  58. package/package.json +20 -1
  59. package/dist/kernel/inode-table.d.ts +0 -43
  60. package/dist/kernel/inode-table.js +0 -85
@@ -198,6 +198,10 @@ export function wrapFileSystem(fs, permissions) {
198
198
  checkFs("read", path);
199
199
  return fs.pread(path, offset, length);
200
200
  },
201
+ pwrite: async (path, offset, data) => {
202
+ checkFs("write", path);
203
+ return fs.pwrite(path, offset, data);
204
+ },
201
205
  };
202
206
  }
203
207
  /** Wrap a NetworkAdapter so external client operations pass through the network permission check. */
@@ -267,6 +271,7 @@ export function createFsStub() {
267
271
  truncate: async (path) => stub("open", path),
268
272
  realpath: async (path) => stub("realpath", path),
269
273
  pread: async (path) => stub("open", path),
274
+ pwrite: async (path) => stub("open", path),
270
275
  };
271
276
  }
272
277
  /** Create a stub network adapter where every operation throws ENOSYS. */
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Shared FsBlockStore conformance test suite.
3
+ *
4
+ * Every FsBlockStore implementation must pass the tests in this suite.
5
+ * Optional test groups are gated on capability flags declared in the config.
6
+ *
7
+ * Usage:
8
+ *
9
+ * ```typescript
10
+ * import { defineBlockStoreTests } from "@secure-exec/core/test/block-store-conformance";
11
+ *
12
+ * defineBlockStoreTests({
13
+ * name: "InMemoryBlockStore",
14
+ * createStore: () => new InMemoryBlockStore(),
15
+ * capabilities: { copy: true },
16
+ * });
17
+ * ```
18
+ */
19
+ import type { FsBlockStore } from "../vfs/types.js";
20
+ export interface BlockStoreConformanceCapabilities {
21
+ /** Whether the store implements the optional copy() method. */
22
+ copy: boolean;
23
+ }
24
+ export interface BlockStoreConformanceConfig {
25
+ /** Human-readable name shown in the describe block. */
26
+ name: string;
27
+ /** Create a fresh block store instance for each test. */
28
+ createStore: () => Promise<FsBlockStore> | FsBlockStore;
29
+ /** Optional teardown called after each test. */
30
+ cleanup?: () => Promise<void>;
31
+ /** Which optional capabilities the store supports. */
32
+ capabilities: BlockStoreConformanceCapabilities;
33
+ }
34
+ export declare function defineBlockStoreTests(config: BlockStoreConformanceConfig): void;
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Shared FsBlockStore conformance test suite.
3
+ *
4
+ * Every FsBlockStore implementation must pass the tests in this suite.
5
+ * Optional test groups are gated on capability flags declared in the config.
6
+ *
7
+ * Usage:
8
+ *
9
+ * ```typescript
10
+ * import { defineBlockStoreTests } from "@secure-exec/core/test/block-store-conformance";
11
+ *
12
+ * defineBlockStoreTests({
13
+ * name: "InMemoryBlockStore",
14
+ * createStore: () => new InMemoryBlockStore(),
15
+ * capabilities: { copy: true },
16
+ * });
17
+ * ```
18
+ */
19
+ import { describe, beforeEach, afterEach, expect, test } from "vitest";
20
+ // ---------------------------------------------------------------------------
21
+ // Error code helper
22
+ // ---------------------------------------------------------------------------
23
+ function hasErrorCode(err, code) {
24
+ if (typeof err !== "object" || err === null)
25
+ return false;
26
+ const e = err;
27
+ if (e.code === code)
28
+ return true;
29
+ if (typeof e.message === "string" && e.message.startsWith(`${code}:`))
30
+ return true;
31
+ return false;
32
+ }
33
+ function expectErrorCode(err, code) {
34
+ expect(err).toBeInstanceOf(Error);
35
+ expect(hasErrorCode(err, code)).toBe(true);
36
+ }
37
+ // ---------------------------------------------------------------------------
38
+ // Helpers
39
+ // ---------------------------------------------------------------------------
40
+ /** Create a Uint8Array of the given size filled with a repeating byte pattern. */
41
+ function makeData(size, seed = 0x42) {
42
+ const buf = new Uint8Array(size);
43
+ for (let i = 0; i < size; i++) {
44
+ buf[i] = (seed + i) & 0xff;
45
+ }
46
+ return buf;
47
+ }
48
+ // ---------------------------------------------------------------------------
49
+ // Test suite
50
+ // ---------------------------------------------------------------------------
51
+ export function defineBlockStoreTests(config) {
52
+ const { name, capabilities } = config;
53
+ describe(name, () => {
54
+ let store;
55
+ beforeEach(async () => {
56
+ store = await config.createStore();
57
+ });
58
+ afterEach(async () => {
59
+ if (config.cleanup)
60
+ await config.cleanup();
61
+ });
62
+ // ---------------------------------------------------------------
63
+ // write + read round-trip
64
+ // ---------------------------------------------------------------
65
+ describe("write + read", () => {
66
+ test("round-trip small data", async () => {
67
+ const data = makeData(64);
68
+ await store.write("key1", data);
69
+ const result = await store.read("key1");
70
+ expect(result).toEqual(data);
71
+ });
72
+ test("round-trip large data (>4MB)", async () => {
73
+ const data = makeData(4 * 1024 * 1024 + 1, 0xab);
74
+ await store.write("large", data);
75
+ const result = await store.read("large");
76
+ expect(result).toEqual(data);
77
+ });
78
+ test("write overwrites existing key", async () => {
79
+ const data1 = makeData(32, 0x11);
80
+ const data2 = makeData(64, 0x22);
81
+ await store.write("key", data1);
82
+ await store.write("key", data2);
83
+ const result = await store.read("key");
84
+ expect(result).toEqual(data2);
85
+ });
86
+ });
87
+ // ---------------------------------------------------------------
88
+ // readRange
89
+ // ---------------------------------------------------------------
90
+ describe("readRange", () => {
91
+ test("partial read from start", async () => {
92
+ const data = makeData(100);
93
+ await store.write("key", data);
94
+ const result = await store.readRange("key", 0, 10);
95
+ expect(result).toEqual(data.slice(0, 10));
96
+ });
97
+ test("partial read from middle", async () => {
98
+ const data = makeData(100);
99
+ await store.write("key", data);
100
+ const result = await store.readRange("key", 20, 30);
101
+ expect(result).toEqual(data.slice(20, 50));
102
+ });
103
+ test("partial read at end", async () => {
104
+ const data = makeData(100);
105
+ await store.write("key", data);
106
+ const result = await store.readRange("key", 90, 10);
107
+ expect(result).toEqual(data.slice(90, 100));
108
+ });
109
+ test("readRange beyond block size returns short read", async () => {
110
+ const data = makeData(50);
111
+ await store.write("key", data);
112
+ const result = await store.readRange("key", 40, 100);
113
+ expect(result).toEqual(data.slice(40, 50));
114
+ expect(result.length).toBe(10);
115
+ });
116
+ test("readRange nonexistent key throws ENOENT", async () => {
117
+ try {
118
+ await store.readRange("missing", 0, 10);
119
+ expect.fail("should have thrown");
120
+ }
121
+ catch (err) {
122
+ expectErrorCode(err, "ENOENT");
123
+ }
124
+ });
125
+ test("readRange with offset exactly at block size returns empty Uint8Array", async () => {
126
+ const data = makeData(50);
127
+ await store.write("key", data);
128
+ const result = await store.readRange("key", 50, 10);
129
+ expect(result.length).toBe(0);
130
+ });
131
+ test("readRange with offset=0, length=0 returns empty Uint8Array", async () => {
132
+ const data = makeData(50);
133
+ await store.write("key", data);
134
+ const result = await store.readRange("key", 0, 0);
135
+ expect(result.length).toBe(0);
136
+ });
137
+ });
138
+ // ---------------------------------------------------------------
139
+ // read errors
140
+ // ---------------------------------------------------------------
141
+ describe("read errors", () => {
142
+ test("read nonexistent key throws ENOENT", async () => {
143
+ try {
144
+ await store.read("nonexistent");
145
+ expect.fail("should have thrown");
146
+ }
147
+ catch (err) {
148
+ expectErrorCode(err, "ENOENT");
149
+ }
150
+ });
151
+ });
152
+ // ---------------------------------------------------------------
153
+ // delete
154
+ // ---------------------------------------------------------------
155
+ describe("delete", () => {
156
+ test("delete then read throws ENOENT", async () => {
157
+ const data = makeData(16);
158
+ await store.write("key", data);
159
+ await store.delete("key");
160
+ try {
161
+ await store.read("key");
162
+ expect.fail("should have thrown");
163
+ }
164
+ catch (err) {
165
+ expectErrorCode(err, "ENOENT");
166
+ }
167
+ });
168
+ test("delete nonexistent key is no-op", async () => {
169
+ // Should not throw.
170
+ await store.delete("nonexistent");
171
+ });
172
+ });
173
+ // ---------------------------------------------------------------
174
+ // deleteMany
175
+ // ---------------------------------------------------------------
176
+ describe("deleteMany", () => {
177
+ test("deleteMany removes multiple keys", async () => {
178
+ await store.write("a", makeData(8, 0x01));
179
+ await store.write("b", makeData(8, 0x02));
180
+ await store.write("c", makeData(8, 0x03));
181
+ await store.deleteMany(["a", "b"]);
182
+ // a and b should be gone.
183
+ try {
184
+ await store.read("a");
185
+ expect.fail("should have thrown");
186
+ }
187
+ catch (err) {
188
+ expectErrorCode(err, "ENOENT");
189
+ }
190
+ try {
191
+ await store.read("b");
192
+ expect.fail("should have thrown");
193
+ }
194
+ catch (err) {
195
+ expectErrorCode(err, "ENOENT");
196
+ }
197
+ // c should still exist.
198
+ const result = await store.read("c");
199
+ expect(result).toEqual(makeData(8, 0x03));
200
+ });
201
+ test("deleteMany with nonexistent keys is no-op", async () => {
202
+ await store.write("x", makeData(4));
203
+ // Should not throw even if some keys don't exist.
204
+ await store.deleteMany(["x", "nonexistent1", "nonexistent2"]);
205
+ try {
206
+ await store.read("x");
207
+ expect.fail("should have thrown");
208
+ }
209
+ catch (err) {
210
+ expectErrorCode(err, "ENOENT");
211
+ }
212
+ });
213
+ test("deleteMany empty array is no-op", async () => {
214
+ // Should not throw.
215
+ await store.deleteMany([]);
216
+ });
217
+ });
218
+ // ---------------------------------------------------------------
219
+ // copy (gated)
220
+ // ---------------------------------------------------------------
221
+ describe.skipIf(!capabilities.copy)("copy", () => {
222
+ test("copy round-trip", async () => {
223
+ const data = makeData(128, 0xcc);
224
+ await store.write("src", data);
225
+ await store.copy("src", "dst");
226
+ const result = await store.read("dst");
227
+ expect(result).toEqual(data);
228
+ });
229
+ test("copy creates independent data", async () => {
230
+ const data = makeData(64, 0xdd);
231
+ await store.write("src", data);
232
+ await store.copy("src", "dst");
233
+ // Overwrite source.
234
+ const newData = makeData(32, 0xee);
235
+ await store.write("src", newData);
236
+ // Destination should still have original data.
237
+ const result = await store.read("dst");
238
+ expect(result).toEqual(data);
239
+ });
240
+ test("copy nonexistent source throws ENOENT", async () => {
241
+ try {
242
+ await store.copy("missing", "dst");
243
+ expect.fail("should have thrown");
244
+ }
245
+ catch (err) {
246
+ expectErrorCode(err, "ENOENT");
247
+ }
248
+ });
249
+ });
250
+ });
251
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Shared FsMetadataStore conformance test suite.
3
+ *
4
+ * Every FsMetadataStore implementation must pass the tests in this suite.
5
+ * Optional test groups are gated on capability flags declared in the config.
6
+ *
7
+ * Usage:
8
+ *
9
+ * ```typescript
10
+ * import { defineMetadataStoreTests } from "@secure-exec/core/test/metadata-store-conformance";
11
+ *
12
+ * defineMetadataStoreTests({
13
+ * name: "InMemoryMetadataStore",
14
+ * createStore: () => new InMemoryMetadataStore(),
15
+ * capabilities: { versioning: false },
16
+ * });
17
+ * ```
18
+ *
19
+ * The `versioning` capability flag gates tests for the optional
20
+ * FsMetadataStoreVersioning interface (deferred to US-013).
21
+ */
22
+ import type { FsMetadataStore } from "../vfs/types.js";
23
+ export interface MetadataStoreConformanceCapabilities {
24
+ /** Whether the store implements FsMetadataStoreVersioning. */
25
+ versioning: boolean;
26
+ }
27
+ export interface MetadataStoreConformanceConfig {
28
+ /** Human-readable name shown in the describe block. */
29
+ name: string;
30
+ /** Create a fresh metadata store instance for each test. */
31
+ createStore: () => Promise<FsMetadataStore> | FsMetadataStore;
32
+ /** Optional teardown called after each test. */
33
+ cleanup?: () => Promise<void>;
34
+ /** Which optional capabilities the store supports. */
35
+ capabilities: MetadataStoreConformanceCapabilities;
36
+ }
37
+ export declare function defineMetadataStoreTests(config: MetadataStoreConformanceConfig): void;