functionalscript 0.32.4 → 0.34.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 (90) hide show
  1. package/README.md +1 -1
  2. package/fs/asserts/module.f.d.ts +1 -1
  3. package/fs/asserts/module.f.js +4 -1
  4. package/fs/bnf/data/module.f.d.ts +0 -103
  5. package/fs/bnf/data/module.f.js +10 -246
  6. package/fs/bnf/data/proof.f.d.ts +0 -7
  7. package/fs/bnf/data/proof.f.js +3 -636
  8. package/fs/bnf/descent/module.f.d.ts +56 -0
  9. package/fs/bnf/descent/module.f.js +119 -0
  10. package/fs/bnf/descent/proof.f.d.ts +5 -0
  11. package/fs/bnf/descent/proof.f.js +296 -0
  12. package/fs/bnf/ll1/module.f.d.ts +72 -0
  13. package/fs/bnf/ll1/module.f.js +153 -0
  14. package/fs/bnf/ll1/proof.f.d.ts +9 -0
  15. package/fs/bnf/ll1/proof.f.js +358 -0
  16. package/fs/cas/cli/module.f.d.ts +6 -0
  17. package/fs/cas/cli/module.f.js +56 -0
  18. package/fs/cas/cli/proof.f.d.ts +14 -0
  19. package/fs/cas/cli/proof.f.js +149 -0
  20. package/fs/cas/mcp/module.f.d.ts +9 -17
  21. package/fs/cas/mcp/module.f.js +117 -83
  22. package/fs/cas/mcp/proof.f.d.ts +5 -1
  23. package/fs/cas/mcp/proof.f.js +79 -32
  24. package/fs/cas/module.f.d.ts +33 -27
  25. package/fs/cas/module.f.js +151 -126
  26. package/fs/cas/proof.f.d.ts +16 -13
  27. package/fs/cas/proof.f.js +255 -130
  28. package/fs/ci/config/module.f.d.ts +15 -15
  29. package/fs/ci/config/module.f.js +15 -15
  30. package/fs/ci/proof.f.js +8 -6
  31. package/fs/cli/proof.f.js +2 -12
  32. package/fs/djs/parser/module.f.d.ts +0 -5
  33. package/fs/djs/proof.f.js +5 -6
  34. package/fs/djs/serializer/module.f.d.ts +1 -3
  35. package/fs/djs/tokenizer-new/module.f.js +3 -2
  36. package/fs/djs/tokenizer-new/proof.f.d.ts +3 -0
  37. package/fs/djs/tokenizer-new/proof.f.js +458 -296
  38. package/fs/djs/transpiler/proof.f.js +10 -10
  39. package/fs/effects/list/module.f.d.ts +31 -0
  40. package/fs/effects/list/module.f.js +16 -0
  41. package/fs/effects/module.f.d.ts +11 -5
  42. package/fs/effects/module.f.js +16 -6
  43. package/fs/effects/node/module.f.d.ts +29 -3
  44. package/fs/effects/node/module.f.js +35 -1
  45. package/fs/effects/node/module.js +32 -18
  46. package/fs/effects/node/proof.f.js +36 -36
  47. package/fs/effects/node/virtual/module.f.d.ts +16 -2
  48. package/fs/effects/node/virtual/module.f.js +129 -20
  49. package/fs/effects/node/virtual/proof.f.d.ts +4 -0
  50. package/fs/effects/node/virtual/proof.f.js +47 -14
  51. package/fs/effects/proof.f.d.ts +4 -0
  52. package/fs/effects/proof.f.js +18 -1
  53. package/fs/emergent_testing/proof.f.js +3 -11
  54. package/fs/fjs/module.f.js +2 -8
  55. package/fs/fjs/proof.f.js +2 -11
  56. package/fs/html/module.f.js +6 -8
  57. package/fs/js/tokenizer/module.f.d.ts +5 -0
  58. package/fs/js/tokenizer/module.f.js +13 -0
  59. package/fs/json/serializer/module.f.js +0 -1
  60. package/fs/mcp/module.f.d.ts +1 -1
  61. package/fs/mime/module.f.d.ts +101 -5
  62. package/fs/mime/module.f.js +159 -6
  63. package/fs/mime/proof.f.d.ts +25 -0
  64. package/fs/mime/proof.f.js +193 -3
  65. package/fs/text/code_point/module.f.d.ts +39 -2
  66. package/fs/text/code_point/module.f.js +74 -2
  67. package/fs/text/code_point/proof.f.d.ts +8 -0
  68. package/fs/text/code_point/proof.f.js +72 -0
  69. package/fs/text/sgr/proof.f.js +2 -11
  70. package/fs/text/utf16/module.f.js +4 -26
  71. package/fs/text/utf8/module.f.d.ts +27 -0
  72. package/fs/text/utf8/module.f.js +8 -14
  73. package/fs/types/bigint/module.f.d.ts +8 -15
  74. package/fs/types/bigint/module.f.js +9 -22
  75. package/fs/types/bigint/proof.f.d.ts +0 -2
  76. package/fs/types/bigint/proof.f.js +6 -16
  77. package/fs/types/function/operator/module.f.d.ts +26 -0
  78. package/fs/types/list/module.f.d.ts +3 -0
  79. package/fs/types/result/module.d.ts +2 -4
  80. package/fs/types/result/module.js +9 -3
  81. package/package.json +3 -3
  82. package/issues/demo/data/data.f.js +0 -12
  83. package/issues/demo/data/shared.f.js +0 -3
  84. package/issues/demo/fs/app.js +0 -4
  85. package/issues/demo/fs/math.f.js +0 -4
  86. package/issues/demo/sample/proof.f.js +0 -13
  87. /package/{issues → todo}/031-json.f.d.ts +0 -0
  88. /package/{issues → todo}/031-json.f.js +0 -0
  89. /package/{issues → todo}/proof.f.d.ts +0 -0
  90. /package/{issues → todo}/proof.f.js +0 -0
@@ -1,15 +1,18 @@
1
+ import { type FileCasOperation } from './module.f.ts';
2
+ import { type Effect } from '../effects/module.f.ts';
3
+ import { type ReadFile, type WriteFile, type Rm, type Mkdir } from '../effects/node/module.f.ts';
4
+ type TestOp = FileCasOperation | WriteFile | ReadFile | Rm | Mkdir;
1
5
  export declare const proof: {
2
- mainAdd: () => void;
3
- mainAddWrongArgs: () => void;
4
- mainGetFound: () => void;
5
- mainGetNotFound: () => void;
6
- mainGetWrongArgs: () => void;
7
- mainGetInvalidHash: () => void;
8
- mainList: () => void;
9
- mainListEmptyStore: () => void;
10
- mainListCorruptStore: () => void;
11
- mainNoCmd: () => void;
12
- mainUnknownCmd: () => void;
13
- casWrite: () => void;
14
- casReadPassthrough: () => void;
6
+ addBigFile: () => Effect<TestOp, void>;
7
+ addAndGetBigFile: () => Effect<TestOp, void>;
8
+ casWriteRead: () => void;
9
+ casReadMissingShard: () => void;
10
+ casWriteMultiChunk: () => void;
11
+ casWriteDedup: () => void;
12
+ casWriteErrorItemAborts: () => void;
13
+ casWriteReadExceedsMaxLength: () => void;
14
+ casWriteGcReclaimsExpired: () => void;
15
+ casUploadSuccess: () => void;
16
+ casUploadFailureKeepsSource: () => void;
15
17
  };
18
+ export {};
package/fs/cas/proof.f.js CHANGED
@@ -1,169 +1,294 @@
1
- import { cas, commands } from "./module.f.js";
2
- import { sha256 } from "../crypto/sha2/module.f.js";
3
- import { empty, length, vec8 } from "../types/bit_vec/module.f.js";
1
+ import { length, maxLength, msb, vec, vec8 } from "../types/bit_vec/module.f.js";
2
+ import { cBase32ToVec, vecToCBase32 } from "../cbase32/module.f.js";
3
+ import { computeSync, sha256 } from "../crypto/sha2/module.f.js";
4
+ import { fileCas, casAddFile, casUpload } from "./module.f.js";
4
5
  import { pure } from "../effects/module.f.js";
5
- import { run } from "../effects/mock/module.f.js";
6
+ import { mkdir, writeFile, rm, readFile, access } from "../effects/node/module.f.js";
7
+ import { error, ok } from "../types/result/module.f.js";
6
8
  import { emptyState, virtual } from "../effects/node/virtual/module.f.js";
7
- import { dispatch } from "../cli/module.f.js";
8
- const makeOptions = (args) => ({
9
- args,
10
- env: {},
11
- home: '.',
12
- std: { stdout: { isTTY: false }, stderr: { isTTY: false } },
13
- testContext: { test: async () => { } },
14
- bunTestContext: { test: async () => { } },
15
- playwrightTestContext: { test: async () => { } },
16
- engine: 'node',
9
+ import { join } from "../path/module.f.js";
10
+ import { nonEmpty, empty } from "../effects/list/module.f.js";
11
+ const testDir = './test-cas-cli';
12
+ // Create a 128 KiB big file content (at the max Vec size limit)
13
+ // This tests the boundary where files are at the chunk size limit
14
+ const createBigFileContent = () => {
15
+ const byteCount = 128n * 1024n; // 128 KiB
16
+ // Create a repeating pattern: 0x42 repeated across the file
17
+ return vec(byteCount * 8n)(0x42424242n);
18
+ };
19
+ // Test adding a big file and verifying the hash
20
+ const testAddBigFile = () => mkdir(testDir, { recursive: true }).step(() => {
21
+ const bigContent = createBigFileContent();
22
+ const bigFilePath = `${testDir}/big-file.bin`;
23
+ return writeFile(bigFilePath, bigContent).step(writeRes => {
24
+ if (writeRes[0] === 'error') {
25
+ throw new Error(`Failed to write test file: ${writeRes[1]}`);
26
+ }
27
+ const cas = fileCas(sha256)(testDir);
28
+ return casAddFile(cas)(bigFilePath).step(addRes => {
29
+ if (addRes[0] === 'error') {
30
+ throw new Error(`Failed to add file to CAS: ${addRes[1]}`);
31
+ }
32
+ const hash = addRes[1];
33
+ // Verify hash is 256 bits (SHA-256)
34
+ if (length(hash) !== 256n) {
35
+ throw new Error(`Expected hash length 256 bits, got ${length(hash)}`);
36
+ }
37
+ // Verify hash can be encoded/decoded
38
+ const hashCBase32 = vecToCBase32(hash);
39
+ const decodedHash = cBase32ToVec(hashCBase32);
40
+ if (decodedHash === null) {
41
+ throw new Error('Failed to decode hash from base32');
42
+ }
43
+ return rm(testDir).step(() => pure(undefined));
44
+ });
45
+ });
46
+ });
47
+ // Test adding and retrieving a big file
48
+ const testAddAndGetBigFile = () => mkdir(testDir, { recursive: true })
49
+ .step(() => {
50
+ const bigContent = createBigFileContent();
51
+ const bigFilePath = `${testDir}/big-file.bin`;
52
+ return writeFile(bigFilePath, bigContent)
53
+ .step(writeRes => {
54
+ if (writeRes[0] === 'error') {
55
+ throw new Error(`Failed to write test file: ${writeRes[1]}`);
56
+ }
57
+ const cas = fileCas(sha256)(testDir);
58
+ return casAddFile(cas)(bigFilePath)
59
+ .step(addRes => {
60
+ if (addRes[0] === 'error') {
61
+ throw new Error(`Failed to add file to CAS: ${addRes[1]}`);
62
+ }
63
+ const hash = addRes[1];
64
+ const storedPath = cas.url(hash);
65
+ // Verify file is stored at the expected location
66
+ return readFile(storedPath).step(readRes => {
67
+ if (readRes[0] === 'error') {
68
+ throw new Error(`Failed to read stored file: ${readRes[1]}`);
69
+ }
70
+ const storedContent = readRes[1];
71
+ // Verify content is the same size as original
72
+ const storedLen = length(storedContent);
73
+ const originalLen = length(bigContent);
74
+ if (storedLen !== originalLen) {
75
+ throw new Error(`Content size mismatch: stored ${storedLen} bits, expected ${originalLen} bits`);
76
+ }
77
+ return rm(testDir).step(() => pure(undefined));
78
+ });
79
+ });
80
+ });
17
81
  });
18
- const main = dispatch(commands);
19
82
  export const proof = {
20
- mainAdd: () => {
83
+ addBigFile: testAddBigFile,
84
+ addAndGetBigFile: testAddAndGetBigFile,
85
+ //
86
+ casWriteRead: () => {
87
+ // Round-trip a single-chunk payload through the real streaming CAS: `write` returns
88
+ // the content hash, and `read` streams the same bytes back as `ok` chunk items.
21
89
  const content = vec8(0x2an);
22
- const state = { ...emptyState, root: { myfile: content } };
23
- const [finalState, exitCode] = virtual(state)(main(makeOptions(['add', 'myfile'])));
24
- if (exitCode !== 0) {
25
- throw ['expected exit 0', exitCode];
26
- }
27
- if (finalState.stdout.length === 0) {
28
- throw 'expected hash in stdout';
90
+ const c = fileCas(sha256)('.');
91
+ const payload = nonEmpty(ok(content), empty());
92
+ const [state1, writeResult] = virtual(emptyState)(c.write(payload));
93
+ if (writeResult[0] !== 'ok') {
94
+ throw ['expected write ok', writeResult];
29
95
  }
30
- },
31
- mainAddWrongArgs: () => {
32
- const [finalState, exitCode] = virtual(emptyState)(main(makeOptions(['add'])));
33
- if (exitCode !== 1) {
34
- throw ['expected exit 1', exitCode];
96
+ const hash = writeResult[1];
97
+ if (length(hash) !== 256n) {
98
+ throw ['expected 256-bit hash', length(hash)];
35
99
  }
36
- if (finalState.stderr.length === 0) {
37
- throw 'expected error in stderr';
100
+ if (msb.cmp(hash)(computeSync(sha256)([content])) !== 0) {
101
+ throw 'write hash mismatch';
38
102
  }
39
- },
40
- mainGetFound: () => {
41
- const content = vec8(0x2an);
42
- const state = { ...emptyState, root: { myfile: content } };
43
- const [state1, exitCode1] = virtual(state)(main(makeOptions(['add', 'myfile'])));
44
- if (exitCode1 !== 0) {
45
- throw ['expected add exit 0', exitCode1];
103
+ const drain = (acc) => (stream) => stream.step((node) => {
104
+ if (node === undefined) {
105
+ return pure(ok(acc));
106
+ }
107
+ const { first, tail } = node;
108
+ if (first[0] === 'error') {
109
+ return pure(first);
110
+ }
111
+ return drain([...acc, first[1]])(tail);
112
+ });
113
+ const [, readResult] = virtual(state1)(drain([])(c.read(hash)));
114
+ if (readResult[0] !== 'ok') {
115
+ throw ['expected read ok', readResult];
46
116
  }
47
- const hashStr = state1.stdout.trim();
48
- const [, exitCode2] = virtual(state1)(main(makeOptions(['get', hashStr, 'output'])));
49
- if (exitCode2 !== 0) {
50
- throw ['expected get exit 0', exitCode2];
117
+ if (msb.cmp(msb.listToVec(readResult[1]))(content) !== 0) {
118
+ throw 'read content mismatch';
51
119
  }
52
120
  },
53
- mainGetNotFound: () => {
54
- // valid cBase32 hash that has not been stored
55
- const content = vec8(0x2an);
56
- const state = { ...emptyState, root: { myfile: content } };
57
- const [state1] = virtual(state)(main(makeOptions(['add', 'myfile'])));
58
- const hashStr = state1.stdout.trim();
59
- // use an empty store so the hash is not found
60
- const [finalState, exitCode] = virtual(emptyState)(main(makeOptions(['get', hashStr, 'output'])));
61
- if (exitCode !== 1) {
62
- throw ['expected exit 1', exitCode];
121
+ casReadMissingShard: () => {
122
+ // A missing shard surfaces as an explicit error *item*, never as end-of-stream.
123
+ const c = fileCas(sha256)('.');
124
+ const hash = computeSync(sha256)([vec8(0x2an)]);
125
+ const node = virtual(emptyState)(c.read(hash))[1];
126
+ if (node === undefined) {
127
+ throw 'missing shard must not be EOF';
63
128
  }
64
- if (finalState.stderr.length === 0) {
65
- throw 'expected error in stderr';
129
+ if (node.first[0] !== 'error') {
130
+ throw ['expected error item', node.tail];
66
131
  }
67
132
  },
68
- mainGetWrongArgs: () => {
69
- const [finalState, exitCode] = virtual(emptyState)(main(makeOptions(['get'])));
70
- if (exitCode !== 1) {
71
- throw ['expected exit 1', exitCode];
133
+ casWriteMultiChunk: () => {
134
+ // A multi-chunk payload streams through `writeBytes` chunk-by-chunk (the lease is
135
+ // renewed between chunks); the hash equals the SHA-256 of the concatenated bytes,
136
+ // and read streams the same content back.
137
+ const chunks = [vec8(0x11n), vec8(0x22n), vec8(0x33n)];
138
+ const c = fileCas(sha256)('.');
139
+ const payload = chunks.reduceRight((tail, chunk) => nonEmpty(ok(chunk), tail), empty());
140
+ const [state1, writeResult] = virtual(emptyState)(c.write(payload));
141
+ if (writeResult[0] !== 'ok') {
142
+ throw ['expected write ok', writeResult];
72
143
  }
73
- if (finalState.stderr.length === 0) {
74
- throw 'expected error in stderr';
144
+ const hash = writeResult[1];
145
+ if (msb.cmp(hash)(computeSync(sha256)(chunks)) !== 0) {
146
+ throw 'multi-chunk write hash mismatch';
75
147
  }
76
- },
77
- mainGetInvalidHash: () => {
78
- const [finalState, exitCode] = virtual(emptyState)(main(makeOptions(['get', 'not-a-valid-hash', 'output'])));
79
- if (exitCode !== 1) {
80
- throw ['expected exit 1', exitCode];
148
+ const drain = (acc) => (stream) => stream.step((node) => {
149
+ if (node === undefined) {
150
+ return pure(ok(acc));
151
+ }
152
+ const { first, tail } = node;
153
+ if (first[0] === 'error') {
154
+ return pure(first);
155
+ }
156
+ return drain([...acc, first[1]])(tail);
157
+ });
158
+ const [, readResult] = virtual(state1)(drain([])(c.read(hash)));
159
+ if (readResult[0] !== 'ok') {
160
+ throw ['expected read ok', readResult];
81
161
  }
82
- if (finalState.stderr.length === 0) {
83
- throw 'expected error in stderr';
162
+ const expected = msb.concat(msb.concat(chunks[0])(chunks[1]))(chunks[2]);
163
+ if (msb.cmp(msb.listToVec(readResult[1]))(expected) !== 0) {
164
+ throw 'multi-chunk read content mismatch';
84
165
  }
85
166
  },
86
- mainList: () => {
167
+ casWriteDedup: () => {
168
+ // Same content ⇒ same hash; the second upload's replace-`rename` publishes over the
169
+ // first, leaving exactly one shard in the store.
87
170
  const content = vec8(0x2an);
88
- const state = { ...emptyState, root: { myfile: content } };
89
- const [state1] = virtual(state)(main(makeOptions(['add', 'myfile'])));
90
- const [, exitCode] = virtual(state1)(main(makeOptions(['list'])));
91
- if (exitCode !== 0) {
92
- throw ['expected exit 0', exitCode];
171
+ const c = fileCas(sha256)('.');
172
+ const payload = () => nonEmpty(ok(content), empty());
173
+ const [state1, w1] = virtual(emptyState)(c.write(payload()));
174
+ const [state2, w2] = virtual(state1)(c.write(payload()));
175
+ if (w1[0] !== 'ok' || w2[0] !== 'ok') {
176
+ throw ['expected both writes ok', w1, w2];
177
+ }
178
+ if (msb.cmp(w1[1])(w2[1]) !== 0) {
179
+ throw 'dedup hash mismatch';
180
+ }
181
+ const [, hashes] = virtual(state2)(c.list());
182
+ if (hashes.length !== 1) {
183
+ throw ['expected one shard after dedup', hashes.length];
93
184
  }
94
185
  },
95
- mainListEmptyStore: () => {
96
- // A fresh directory has no `.cas` yet; listing must succeed (empty),
97
- // not crash unwrapping a readdir ENOENT.
98
- const [finalState, exitCode] = virtual(emptyState)(main(makeOptions(['list'])));
99
- if (exitCode !== 0) {
100
- throw ['expected exit 0', exitCode];
186
+ casWriteErrorItemAborts: () => {
187
+ // An error item mid-stream deletes the partial staging file and fails; nothing is
188
+ // published, so the store stays empty.
189
+ const c = fileCas(sha256)('.');
190
+ const okItem = ok(vec8(0x11n));
191
+ const errItem = error({ code: 'BOOM' });
192
+ const payload = nonEmpty(okItem, nonEmpty(errItem, empty()));
193
+ const [state1, result] = virtual(emptyState)(c.write(payload));
194
+ if (result[0] !== 'error') {
195
+ throw ['expected write error', result];
101
196
  }
102
- if (finalState.stdout !== '') {
103
- throw ['expected empty stdout', finalState.stdout];
197
+ const [, hashes] = virtual(state1)(c.list());
198
+ if (hashes.length !== 0) {
199
+ throw ['expected nothing published on abort', hashes];
104
200
  }
105
201
  },
106
- mainListCorruptStore: () => {
107
- // `.cas` exists but is a file, not a directory: a real storage error
108
- // that must surface, not be masked as an empty list.
109
- const state = { ...emptyState, root: { '.cas': vec8(0x2an) } };
110
- let threw = false;
111
- try {
112
- virtual(state)(main(makeOptions(['list'])));
202
+ casWriteReadExceedsMaxLength: () => {
203
+ // The point of streaming: a payload larger than a single `Vec`'s `maxLength`
204
+ // (128 KiB) must round-trip. `write` lands the chunks on disk without ever
205
+ // holding them as one `Vec`, and `read` streams them back the same way — so the
206
+ // round-trip is verified by hashing the read stream incrementally rather than
207
+ // concatenating it (which would itself overflow `maxLength`).
208
+ const big = vec(maxLength)(0xabn); // one full-size chunk: exactly maxLength bits
209
+ const tail = vec8(0x2an); // one more byte ⇒ total > maxLength
210
+ const chunks = [big, tail];
211
+ const c = fileCas(sha256)('.');
212
+ const payload = chunks.reduceRight((tl, chunk) => nonEmpty(ok(chunk), tl), empty());
213
+ const [state1, w] = virtual(emptyState)(c.write(payload));
214
+ if (w[0] !== 'ok') {
215
+ throw ['expected write ok', w];
216
+ }
217
+ const hash = w[1];
218
+ if (msb.cmp(hash)(computeSync(sha256)(chunks)) !== 0) {
219
+ throw 'oversized write hash mismatch';
113
220
  }
114
- catch {
115
- threw = true;
221
+ // Fold the read stream straight into a fresh SHA-2 state — never one `Vec`.
222
+ const rehash = (state) => (stream) => stream.step((node) => {
223
+ if (node === undefined) {
224
+ return pure(ok(sha256.end(state)));
225
+ }
226
+ const { first, tail } = node;
227
+ if (first[0] === 'error') {
228
+ return pure(first);
229
+ }
230
+ return rehash(sha256.append(first[1])(state))(tail);
231
+ });
232
+ const [, readBack] = virtual(state1)(rehash(sha256.init)(c.read(hash)));
233
+ if (readBack[0] !== 'ok') {
234
+ throw ['expected read ok', readBack];
116
235
  }
117
- if (!threw) {
118
- throw 'expected list to surface the storage error';
236
+ if (msb.cmp(readBack[1])(hash) !== 0) {
237
+ throw 'oversized read-back hash mismatch';
119
238
  }
120
239
  },
121
- mainNoCmd: () => {
122
- const [finalState, exitCode] = virtual(emptyState)(main(makeOptions([])));
123
- if (exitCode !== 1) {
124
- throw ['expected exit 1', exitCode];
240
+ casWriteGcReclaimsExpired: () => {
241
+ // A staging file whose deadline is in the past is reclaimed by the GC that `write`
242
+ // runs before staging its own file.
243
+ const stalePath = join('.', '.cas', '_stage', '0000000000000000000-stale');
244
+ const state0 = {
245
+ ...emptyState,
246
+ epochNs: 1_000_000,
247
+ root: { '.cas': { '_stage': { '0000000000000000000-stale': [vec8(0x99n)] } } },
248
+ };
249
+ const content = vec8(0x2an);
250
+ const c = fileCas(sha256)('.');
251
+ const x = c.write(nonEmpty(ok(content), empty()));
252
+ const [state1, w] = virtual(state0)(x);
253
+ if (w[0] !== 'ok') {
254
+ throw ['expected write ok', w];
125
255
  }
126
- if (finalState.stderr.length === 0) {
127
- throw 'expected error in stderr';
256
+ const [, present] = virtual(state1)(access(stalePath));
257
+ if (present[0] !== 'error') {
258
+ throw 'expected GC to reclaim the expired staging file';
128
259
  }
129
260
  },
130
- mainUnknownCmd: () => {
131
- const [finalState, exitCode] = virtual(emptyState)(main(makeOptions(['bogus'])));
132
- if (exitCode !== 1) {
133
- throw ['expected exit 1', exitCode];
261
+ casUploadSuccess: () => {
262
+ // A successful upload returns the hash and deletes the source file from cas_upload/.
263
+ const content = vec8(0x2an);
264
+ const state0 = { ...emptyState, root: { 'cas_upload': { 'myfile': [content] } } };
265
+ const [state1, result] = virtual(state0)(casUpload('.')('myfile'));
266
+ if (result[0] !== 'ok') {
267
+ throw ['expected casUpload ok', result];
134
268
  }
135
- if (finalState.stderr.length === 0) {
136
- throw 'expected error in stderr';
269
+ if (length(result[1]) !== 256n) {
270
+ throw ['expected 256-bit hash', length(result[1])];
137
271
  }
138
- },
139
- casWrite: () => {
140
- const store = {
141
- read: (_key) => pure(undefined),
142
- write: (_key, _value) => pure(undefined),
143
- list: () => pure([]),
144
- };
145
- const c = cas(sha256)(store);
146
- const [, hash] = run({})(undefined)(c.write(empty));
147
- // sha256 of empty input produces a 256-bit hash
148
- if (length(hash) !== 256n) {
149
- throw ['expected 256-bit hash', length(hash)];
272
+ // Source must be deleted after successful publish.
273
+ const srcPath = join('.', 'cas_upload', 'myfile');
274
+ const [, srcAccess] = virtual(state1)(access(srcPath));
275
+ if (srcAccess[0] !== 'error') {
276
+ throw 'expected source to be deleted after successful upload';
150
277
  }
151
278
  },
152
- casReadPassthrough: () => {
153
- const stored = empty;
154
- const store = {
155
- read: (_key) => pure(stored),
156
- write: (_key, _value) => pure(undefined),
157
- list: () => pure([stored]),
158
- };
159
- const c = cas(sha256)(store);
160
- const [, readResult] = run({})(undefined)(c.read(empty));
161
- if (readResult !== stored) {
162
- throw ['read should pass through', readResult];
163
- }
164
- const [, listResult] = run({})(undefined)(c.list());
165
- if (listResult.length !== 1) {
166
- throw ['list should pass through', listResult];
279
+ casUploadFailureKeepsSource: () => {
280
+ // A missing source file causes write to fail; casUpload returns error and the
281
+ // source is left in place (trivially: it was never there, but the upload is not published).
282
+ const state0 = { ...emptyState, root: {} };
283
+ const [state1, result] = virtual(state0)(casUpload('.')('nonexistent'));
284
+ if (result[0] !== 'error') {
285
+ throw ['expected casUpload to fail on missing source', result];
286
+ }
287
+ // Nothing published to the store.
288
+ const c = fileCas(sha256)('.');
289
+ const [, hashes] = virtual(state1)(c.list());
290
+ if (hashes.length !== 0) {
291
+ throw ['expected nothing published on failed upload', hashes];
167
292
  }
168
293
  },
169
294
  };
@@ -7,8 +7,8 @@
7
7
  */
8
8
  export declare const images: {
9
9
  readonly ubuntu: {
10
- readonly intel: "ubuntu-24.04";
11
- readonly arm: "ubuntu-24.04-arm";
10
+ readonly intel: "ubuntu-26.04";
11
+ readonly arm: "ubuntu-26.04-arm";
12
12
  };
13
13
  readonly macos: {
14
14
  readonly intel: "macos-26-intel";
@@ -19,24 +19,24 @@ export declare const images: {
19
19
  readonly arm: "windows-11-arm";
20
20
  };
21
21
  };
22
- export declare const functionalscript: "0.30.0";
22
+ export declare const functionalscript: "0.33.0";
23
23
  export declare const bun = "1.3.14";
24
- export declare const deno = "2.8.3";
25
- export declare const playwright = "1.61.0";
24
+ export declare const deno = "2.9.0";
25
+ export declare const playwright = "1.61.1";
26
26
  export declare const node: {
27
- readonly default: "26.3.0";
28
- readonly node22: "22.22.3";
29
- readonly node24: "24.16.0";
27
+ readonly default: "26.4.0";
28
+ readonly node22: "22.23.1";
29
+ readonly node24: "24.18.0";
30
30
  };
31
- export declare const wasmtime = "45.0.2";
31
+ export declare const wasmtime = "46.0.1";
32
32
  export declare const wasmer = "7.1.0";
33
- export declare const tsgo = "7.0.0-dev.20260615.1";
33
+ export declare const tsgo = "7.0.0-dev.20260627.2";
34
34
  export declare const actions: {
35
- readonly 'actions/checkout': "v6";
36
- readonly 'actions/setup-node': "v6";
37
- readonly 'actions/cache': "v5";
38
- readonly 'denoland/setup-deno': "v2";
39
- readonly 'oven-sh/setup-bun': "v2";
35
+ readonly 'actions/checkout': "v7";
36
+ readonly 'actions/setup-node': "v6.4.0";
37
+ readonly 'actions/cache': "v6.1.0";
38
+ readonly 'denoland/setup-deno': "v2.0.4";
39
+ readonly 'oven-sh/setup-bun': "v2.2.0";
40
40
  readonly 'bytecodealliance/actions/wasmtime/setup': "v1";
41
41
  readonly 'wasmerio/setup-wasmer': "v3.1";
42
42
  readonly 'dtolnay/rust-toolchain': "1.96.0";
@@ -8,8 +8,8 @@
8
8
  // https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for-public-repositories
9
9
  export const images = {
10
10
  ubuntu: {
11
- intel: 'ubuntu-24.04',
12
- arm: 'ubuntu-24.04-arm'
11
+ intel: 'ubuntu-26.04',
12
+ arm: 'ubuntu-26.04-arm'
13
13
  },
14
14
  macos: {
15
15
  intel: 'macos-26-intel',
@@ -24,40 +24,40 @@ export const images = {
24
24
  // published FunctionalScript release; do not tie it to package.json's current
25
25
  // in-repo version. A separate maintenance job advances this pin.
26
26
  // https://www.npmjs.com/package/functionalscript
27
- export const functionalscript = '0.30.0';
27
+ export const functionalscript = '0.33.0';
28
28
  // https://bun.sh/
29
29
  export const bun = '1.3.14';
30
30
  // https://deno.com/
31
- export const deno = '2.8.3';
31
+ export const deno = '2.9.0';
32
32
  // https://www.npmjs.com/package/playwright
33
- export const playwright = '1.61.0';
33
+ export const playwright = '1.61.1';
34
34
  // https://nodejs.org/en/download
35
35
  export const node = {
36
- default: '26.3.0',
37
- node22: '22.22.3',
38
- node24: '24.16.0',
36
+ default: '26.4.0',
37
+ node22: '22.23.1',
38
+ node24: '24.18.0',
39
39
  };
40
40
  // https://github.com/bytecodealliance/wasmtime/releases
41
- export const wasmtime = '45.0.2';
41
+ export const wasmtime = '46.0.1';
42
42
  // https://github.com/wasmerio/wasmer/releases
43
43
  export const wasmer = '7.1.0';
44
44
  // https://www.npmjs.com/package/@typescript/native-preview?activeTab=versions
45
- export const tsgo = '7.0.0-dev.20260615.1';
45
+ export const tsgo = '7.0.0-dev.20260627.2';
46
46
  // GitHub Action versions used by CI step builders. The key is the action
47
47
  // `owner/name`; call sites compose the full ref as
48
48
  // `` `${name}@${actions[name]}` ``.
49
49
  // Note: dtolnay/rust-toolchain value is a Rust version, not an action version.
50
50
  export const actions = {
51
51
  // https://github.com/marketplace/actions/checkout
52
- 'actions/checkout': 'v6',
52
+ 'actions/checkout': 'v7',
53
53
  // https://github.com/marketplace/actions/setup-node-js-environment
54
- 'actions/setup-node': 'v6',
54
+ 'actions/setup-node': 'v6.4.0',
55
55
  // https://github.com/marketplace/actions/cache
56
- 'actions/cache': 'v5',
56
+ 'actions/cache': 'v6.1.0',
57
57
  // https://github.com/marketplace/actions/setup-deno
58
- 'denoland/setup-deno': 'v2',
58
+ 'denoland/setup-deno': 'v2.0.4',
59
59
  // https://github.com/marketplace/actions/setup-bun
60
- 'oven-sh/setup-bun': 'v2',
60
+ 'oven-sh/setup-bun': 'v2.2.0',
61
61
  // https://github.com/bytecodealliance/actions
62
62
  'bytecodealliance/actions/wasmtime/setup': 'v1',
63
63
  // https://github.com/wasmerio/setup-wasmer
package/fs/ci/proof.f.js CHANGED
@@ -14,18 +14,20 @@ const makeState = (rust, packageJson) => ({
14
14
  ...emptyState,
15
15
  root: {
16
16
  '.github': { workflows: {} },
17
- ...(packageJson !== undefined ? { 'package.json': utf8(packageJson) } : {}),
18
- ...(rust ? { 'Cargo.toml': emptyVec } : {}),
17
+ ...(packageJson !== undefined ? { 'package.json': [utf8(packageJson)] } : {}),
18
+ ...(rust ? { 'Cargo.toml': [emptyVec] } : {}),
19
19
  },
20
20
  });
21
21
  const workflow = (state) => {
22
22
  const dotGithub = state.root['.github'];
23
- assert(typeof dotGithub === 'object', dotGithub);
23
+ assert(typeof dotGithub === 'object' && !Array.isArray(dotGithub), dotGithub);
24
24
  const workflows = dotGithub['workflows'];
25
- assert(typeof workflows === 'object', workflows);
25
+ assert(typeof workflows === 'object' && !Array.isArray(workflows), workflows);
26
26
  const file = workflows['ci.yml'];
27
- assert(isVec(file), file);
28
- return unwrap(parseGitHubAction(jsonParse(utf8ToString(file))));
27
+ if (!Array.isArray(file) || file.length === 0) {
28
+ throw file;
29
+ }
30
+ return unwrap(parseGitHubAction(jsonParse(utf8ToString(file[0]))));
29
31
  };
30
32
  const run = (rust, nodeExtra = () => []) => {
31
33
  const [state, result] = virtual(makeState(rust))(ci({ nodeExtra }));
package/fs/cli/proof.f.js CHANGED
@@ -1,17 +1,7 @@
1
1
  import { pure } from "../effects/module.f.js";
2
- import {} from "../effects/node/module.f.js";
3
- import { emptyState, virtual } from "../effects/node/virtual/module.f.js";
2
+ import { defaultNodeProgramOptions, emptyState, virtual } from "../effects/node/virtual/module.f.js";
4
3
  import { dispatch } from "./module.f.js";
5
- const makeOptions = (args) => ({
6
- args,
7
- env: {},
8
- home: '.',
9
- std: { stdout: { isTTY: false }, stderr: { isTTY: false } },
10
- testContext: { test: async () => { } },
11
- bunTestContext: { test: async () => { } },
12
- playwrightTestContext: { test: async () => { } },
13
- engine: 'node',
14
- });
4
+ const makeOptions = (args) => ({ ...defaultNodeProgramOptions, args });
15
5
  const echoCommands = [
16
6
  {
17
7
  names: ['echo', 'e'],