@staix/vault-mem 0.2.1 → 0.2.3

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/src/writer.ts CHANGED
@@ -10,7 +10,7 @@ import { VaultIndex, MAX_NOTE_BYTES } from "./vault-index.ts";
10
10
  import { parseFrontmatter } from "./frontmatter.ts";
11
11
  import { canonicalPath, isDenied } from "./platform.ts";
12
12
  import { moveSourcePath, notePath, planOperation, refuse, WriteRefusal } from "./write-operations.ts";
13
- import { validateWrite } from "./write-validation.ts";
13
+ import { MAX_REPORT_BYTES, validateWrite } from "./write-validation.ts";
14
14
  import type { PiiSurface } from "./config.ts";
15
15
  import type { LogBinding } from "./log-types.ts";
16
16
  import type { ChangeReceipt, PlannedChange, WriteIssue, WriteReceipt, WriteRequest } from "./write-types.ts";
@@ -41,22 +41,22 @@ function safePath(root: string, path: string): string {
41
41
  function stamp(stat: { dev: bigint; ino: bigint; size: bigint; mtimeNs: bigint; ctimeNs: bigint }): string {
42
42
  return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
43
43
  }
44
- function readPlain(root: string, path: string): string | null {
44
+ function readPlain(root: string, path: string, maximum = MAX_NOTE_BYTES): string | null {
45
45
  const file = safePath(root, path);
46
46
  let fd: number;
47
47
  try { fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK); }
48
48
  catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return null; throw error; }
49
49
  try {
50
50
  const before = fstatSync(fd, { bigint: true });
51
- if (!before.isFile() || before.size > BigInt(MAX_NOTE_BYTES)) refuse("V09", "file exceeds read bound or is not regular", path);
52
- const bytes = Buffer.alloc(Math.min(Number(before.size) + 1, MAX_NOTE_BYTES + 1));
51
+ if (!before.isFile() || before.size > BigInt(maximum)) refuse("V09", "file exceeds read bound or is not regular", path);
52
+ const bytes = Buffer.alloc(Math.min(Number(before.size) + 1, maximum + 1));
53
53
  let size = 0;
54
54
  while (size < bytes.length) {
55
55
  const count = readSync(fd, bytes, size, bytes.length - size, null); size += count;
56
56
  if (!count) break;
57
57
  }
58
58
  const after = fstatSync(fd, { bigint: true });
59
- if (size > MAX_NOTE_BYTES || stamp(before) !== stamp(after)) refuse("V09", "file changed during read", path);
59
+ if (size > maximum || stamp(before) !== stamp(after)) refuse("V09", "file changed during read", path);
60
60
  const buffer = bytes.subarray(0, size), text = buffer.toString("utf8");
61
61
  if (!Buffer.from(text).equals(buffer)) refuse("V01", "note must be valid UTF-8", path);
62
62
  return text;
@@ -150,7 +150,7 @@ export function applyWrite(home: string, request: WriteRequest, options: { env?:
150
150
  // Planning validates the report's exact relative path before it can be read.
151
151
  planOperation(request.operation, notes, null);
152
152
  if (isDenied(join(root, request.operation.path), config.denyPaths)) refuse("V06", "report output path is denied");
153
- reportBefore = readPlain(root, request.operation.path);
153
+ reportBefore = readPlain(root, request.operation.path, MAX_REPORT_BYTES);
154
154
  }
155
155
  changes = planOperation(request.operation, notes, reportBefore);
156
156
  for (const change of changes) if (isDenied(join(root, change.path), config.denyPaths)) refuse("V06", "planned mutation path is denied", change.path);
@@ -196,7 +196,7 @@ export function applyWrite(home: string, request: WriteRequest, options: { env?:
196
196
  if (current.size !== notes.size || [...current].some(([path, text]) => notes.get(path) !== text)) refuse("V09", "vault changed while staging");
197
197
  for (const change of changes) {
198
198
  const file = safePath(root, change.path);
199
- if (readPlain(root, change.path) !== change.before) refuse("V09", "file changed while staging", change.path);
199
+ if (readPlain(root, change.path, request.operation.op === "report" ? MAX_REPORT_BYTES : MAX_NOTE_BYTES) !== change.before) refuse("V09", "file changed while staging", change.path);
200
200
  if (change.after === null) unlinkSync(file);
201
201
  else { const temp = staged.get(change.path)!; renameSync(temp, file); temps.delete(temp); }
202
202
  committed.push(change);
@@ -209,7 +209,7 @@ export function applyWrite(home: string, request: WriteRequest, options: { env?:
209
209
  for (const change of [...committed].reverse()) {
210
210
  try {
211
211
  const file = safePath(root, change.path);
212
- if (readPlain(root, change.path) !== change.after) throw new Error("external write detected before rollback");
212
+ if (readPlain(root, change.path, request.operation.op === "report" ? MAX_REPORT_BYTES : MAX_NOTE_BYTES) !== change.after) throw new Error("external write detected before rollback");
213
213
  if (change.before === null) unlinkSync(file);
214
214
  else { const backup = backups.get(change.path)!; renameSync(backup, file); temps.delete(backup); }
215
215
  } catch {