@volter/supercode-interchange 0.1.11 → 0.1.12

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 (2) hide show
  1. package/file-io.mjs +8 -2
  2. package/package.json +1 -1
package/file-io.mjs CHANGED
@@ -3,6 +3,10 @@ import { parseNativeJson } from './native-json.mjs';
3
3
 
4
4
  const stale = (path) => Object.assign(new Error(`${path} changed during its metadata read; previous index rows are retained`), { code: 'ESTALE' });
5
5
  const same = (a, b) => a.dev === b.dev && a.ino === b.ino && a.size === b.size && a.mtimeNs === b.mtimeNs;
6
+ // A file its writer appended to while it was read is the same file with more after the bytes read: the facts read are
7
+ // those of the open's size, and the next read takes the rest. A live transcript is written more often than a large
8
+ // read takes, so refusing an append would refuse every read of it.
9
+ const sameOrAppended = (before, now) => same(before, now) || before.dev === now.dev && before.ino === now.ino && now.size > before.size;
6
10
 
7
11
  export function fileFingerprint(info) {
8
12
  return { device: String(info.dev), inode: String(info.ino), size: String(info.size), mtime_ns: String(info.mtimeNs) };
@@ -19,7 +23,9 @@ export function appendableFingerprint(previous, current) {
19
23
 
20
24
  /** Read one open identity; verify both the handle and its current path before
21
25
  * returning facts for a transaction. A changed path never commits new offsets
22
- * beside facts read from its predecessor. The caller owns failure retry. */
26
+ * beside facts read from its predecessor: a file replaced, truncated or rewritten
27
+ * in place is stale, while one only appended to answers for the size it was
28
+ * opened at. The caller owns failure retry. */
23
29
  export async function withSessionFile(path, work, metrics = {}) {
24
30
  const handle = await open(path, 'r');
25
31
  metrics.files_opened = (metrics.files_opened ?? 0) + 1;
@@ -51,7 +57,7 @@ export async function withSessionFile(path, work, metrics = {}) {
51
57
  const after = await handle.stat({ bigint: true });
52
58
  let current;
53
59
  try { current = await stat(path, { bigint: true }); } catch { throw stale(path); }
54
- if (!same(before, after) || !same(after, current)) throw stale(path);
60
+ if (!sameOrAppended(before, after) || !sameOrAppended(before, current) || current.size < after.size) throw stale(path);
55
61
  return { result, fingerprint: reader.fingerprint, size, modified_ms: reader.modified_ms };
56
62
  } finally { await handle.close(); }
57
63
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-interchange",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "type": "module",
5
5
  "description": "Read-only harness metadata, transcript codecs and session projections for Supercode",
6
6
  "license": "MIT OR Apache-2.0",