@sema-agent/core 7.49.4 → 7.49.5

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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 7.49.5 — 2026-10-13
2
+
3
+ **Hard-breaking changes (#1701):** none. **Closed sets:** none. **Package exports:** +0 / −0 names (`posixModeBitsMeaningful` lives in `src/core/posix-mode-face.ts` and is not on the package index); member changes, all optional: `vetPeerRegistryDirectory(dir, { platform? })`, `judgePeerRecordFile(stat, selfUid, platform?)`, `new FileDurableRulePartitionProvider(dir, onError, { platform? })`. **Behavior:** on Windows the rule store and the peer registry stop reading the platform's `0o666` as "writable by group or others" — a loosening on exactly one platform, and the one that restores the store's purpose there (open to veto: it departs from "refuse what you cannot prove safe" for that platform); POSIX hosts are unchanged.
4
+
5
+ ### A platform with no POSIX permission bits is not "writable by everyone" (#1701; fix; security fail-open on Windows; hard-breaking: none, open to veto; exports: +0 / −0) (@test @server @cli @client-core)
6
+
7
+ - **Before (7.49.4 and earlier).** The permission-rule store refused its whole directory when `(mode & 0o022) !== 0` — "writable by group or others" — and loaded ZERO rules. Windows has no POSIX permission bits: Node reports `0o666` for every file and directory there (a directory created with mode `0o700` too), so on a Windows host the check fired on every run. A persisted `deny` / `ask` row never applied (the engine disclosed "loading zero rules" and went on), no `allow` row was read either, the write face refused every write (an "always allow" could not be saved), and the org state file in the same directory was refused with it. The peer registry's directory chain (`vetPeerRegistryDirectory`) and a registration file (`judgePeerRecordFile`) judged the same bits and refused with `group_or_world_writable` on every Windows path. A POSIX host loaded normally.
8
+ - **Now.** ONE predicate, `posixModeBitsMeaningful(platform = process.platform)` (`src/core/posix-mode-face.ts`: `platform !== "win32"`), is read by every site that tests those bits: the store's directory vetting (bucket read, org state read, the write face's lock, the adoption marker writer), the peer registry's chain walk and its file judge, and the execution env's file-mode report (`HAS_POSIX_MODE_FACE`, which already asked this question for itself and now reads the same predicate — no second spelling of the fact). Where the bits are not meaningful the group/other-writable judgment is not made. The owner judgment is unchanged (it was already skipped where `process.getuid` is absent); symlink, not-a-directory, unreadable, checksum and shape refusals are unchanged. On a platform with bits (macOS, Linux) nothing changes: a `0o777` directory is still refused whole. The store says it ONCE per store instance through its existing error disclosure (`onError`): the directory is not checked for being writable by other accounts, this platform reports no POSIX permission bits, so who else can write there is not verified here. The peer registry's two functions are pure verdicts with no disclosure seat; they only skip. There is no ACL-level check (KL-1701-a).
9
+ - **Who needs it.** Every Windows host that persists permission rules (a `deny` / `ask` row, an org snapshot, a rule imported through the consent flow) — until now none of them took part in a decision there.
10
+ - **Who is hurt.** None by design. What changes for a Windows host: persisted rows now take part — `allow` rows silence the asks they were approved to silence, `deny` / `ask` rows apply, org state loads — which is the contract on every other platform. A host that was (unknowingly) running with zero persisted rules will see asks and refusals appear and approved commands stop asking. A directory another Windows account can write to is no longer refused by this check (KL-1701-a).
11
+ - **Compensation.** Nothing to do on POSIX. A Windows deployment that wants the old refusal on a shared machine has no switch for it (the old refusal was this bug, not a policy); restrict the rule directory's ACL to the engine's account.
12
+ - **Consumers.** server / cli / client-core on Windows: the `onError` sink of `FileDurableRulePartitionProvider` now receives one extra sentence per provider instance on win32 (it contains the directory); a host that treats every `onError` line as an error should read this one as a notice. Nothing else on the wire or the card changes.
13
+ - Pins: `test/backlog1701-win32-mode-bits.test.ts` (① the predicate; ② the rule store on a REAL `0o777` directory with the platform injected — win32 loads the deny row, says it once however many reads, says it again per instance, still refuses a symlink / corrupt bucket, the write face and the org read follow it; darwin / linux still refuse whole with no skip notice; a `0o700` darwin directory loads with no notice; a throwing sink changes nothing; ③ the same deny row on the real Runner — win32 refuses the command, a `0o777` darwin directory is the pre-fix reading and runs it; ④ the peer registry — the file judge and the chain walk with a `0o777` ancestor, the other arms unchanged; ⑤ no source file outside the predicate tests those bits without importing it, and the execution env's mode face flips with the platform its module loads under).
14
+
1
15
  ## 7.49.4 — 2026-10-13
2
16
 
3
17
  **Rebuild of 7.49.3 — no source change.** The npm tarball of 7.49.3 (shasum `77a7fd37…`, now deprecated) carried the `dist/` of 7.49.2: it was published with `--ignore-scripts` from a checkout whose `dist/` had not been rebuilt (the release-candidate build and gates had run on a detached gate copy). None of the four 7.49.3 entries below (#1685, #1691, #1692, #1693) were in that package. 7.49.4 is the same source as tag `v7.49.3` built in the publishing checkout and compared byte-for-byte with the gated dist before publish; every 7.49.3 entry, criterion (`docs/release-criteria/7.49.3.json`) and wire manifest applies to 7.49.4. **Hard-breaking changes:** none. **Closed sets:** none. **Package exports:** +0 / −0 (`npm run handoff:diff v7.49.3 HEAD`). Consumers pinned to 7.49.3 must move to 7.49.4 (@server @cli @web-admin @client-core @test).
@@ -216,20 +216,25 @@ export type PeerRegistryDirectoryVerdict = {
216
216
  path: string;
217
217
  };
218
218
  /** Vet a registry directory and every ancestor. `uid` defaults to the process uid; on a platform with
219
- * no uid (win32) the owner arm is skipped. Refuses on the FIRST failing ancestor (closest first). */
219
+ * no uid (win32) the owner arm is skipped. Refuses on the FIRST failing ancestor (closest first).
220
+ * `platform` (default: this process's) decides whether the permission bits are judged at all
221
+ * ({@link posixModeBitsMeaningful}): where they are not meaningful the group/world-writable arm is skipped,
222
+ * and nothing in this verdict says so — the caller that reads it owns any disclosure. */
220
223
  export declare function vetPeerRegistryDirectory(dir: string, opts?: {
221
224
  uid?: number;
222
225
  stopAt?: string;
226
+ platform?: NodeJS.Platform;
223
227
  }): Promise<PeerRegistryDirectoryVerdict>;
224
228
  /** §2.1 ③ — the read-side judgment on ONE registration file's stat facts (pure; the reader supplies
225
229
  * the stat): owner must be this uid, mode must carry no group/world write bit, and it must be a
226
- * regular file under the read cap. A refused file is skipped LOUDLY by the reader. */
230
+ * regular file under the read cap. A refused file is skipped LOUDLY by the reader. The mode arm is judged
231
+ * only where `platform` (default: this process's) has permission bits to judge ({@link posixModeBitsMeaningful}). */
227
232
  export declare function judgePeerRecordFile(stat: {
228
233
  uid?: number;
229
234
  mode: number;
230
235
  isFile: boolean;
231
236
  size: number;
232
- }, selfUid: number | undefined): {
237
+ }, selfUid: number | undefined, platform?: NodeJS.Platform): {
233
238
  ok: true;
234
239
  } | {
235
240
  ok: false;
@@ -2,6 +2,7 @@ import { lstat } from "node:fs/promises";
2
2
  import { isAbsolute, dirname, resolve as resolvePath } from "node:path";
3
3
  import { formatPeerNameRef, mintPeerRef, normalizePeerName, parsePeerNameRef, PEER_REF_RE } from "./cross-session-ref.js";
4
4
  import { inlineUntrusted } from "../core/untrusted-text.js";
5
+ import { posixModeBitsMeaningful } from "../core/posix-mode-face.js";
5
6
  export const PEER_SESSION_RECORD_SCHEMA_VERSION = 1;
6
7
  export const PEER_SESSION_RECORD_MAX_BYTES = 262_144;
7
8
  export const PEER_PRESENCE_MAX_CHARS = 120;
@@ -252,6 +253,7 @@ export async function vetPeerRegistryDirectory(dir, opts) {
252
253
  if (!isAbsolute(dir))
253
254
  return { ok: false, code: "not_absolute", path: dir };
254
255
  const uid = opts?.uid ?? (typeof process.getuid === "function" ? process.getuid() : undefined);
256
+ const judgeModeBits = posixModeBitsMeaningful(opts?.platform);
255
257
  const stopAt = opts?.stopAt !== undefined ? resolvePath(opts.stopAt) : undefined;
256
258
  let cursor = resolvePath(dir);
257
259
  for (;;) {
@@ -271,7 +273,7 @@ export async function vetPeerRegistryDirectory(dir, opts) {
271
273
  if (!isRoot) {
272
274
  if (uid !== undefined && st.uid !== uid)
273
275
  return { ok: false, code: "foreign_owner", path: cursor };
274
- if ((st.mode & 0o022) !== 0)
276
+ if (judgeModeBits && (st.mode & 0o022) !== 0)
275
277
  return { ok: false, code: "group_or_world_writable", path: cursor };
276
278
  }
277
279
  if (isRoot)
@@ -279,12 +281,12 @@ export async function vetPeerRegistryDirectory(dir, opts) {
279
281
  cursor = parent;
280
282
  }
281
283
  }
282
- export function judgePeerRecordFile(stat, selfUid) {
284
+ export function judgePeerRecordFile(stat, selfUid, platform) {
283
285
  if (!stat.isFile)
284
286
  return { ok: false, code: "not_regular" };
285
287
  if (selfUid !== undefined && stat.uid !== undefined && stat.uid !== selfUid)
286
288
  return { ok: false, code: "foreign_owner" };
287
- if ((stat.mode & 0o022) !== 0)
289
+ if (posixModeBitsMeaningful(platform) && (stat.mode & 0o022) !== 0)
288
290
  return { ok: false, code: "group_or_world_writable" };
289
291
  if (stat.size > PEER_SESSION_RECORD_MAX_BYTES)
290
292
  return { ok: false, code: "too_large" };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Does this platform report POSIX permission bits worth judging? Windows' `Stats.mode` is a projection of
3
+ * the read-only attribute (`0o666` / `0o444`; a directory created with mode `0o700` reads `0o666` too), not
4
+ * permission bits — so a group/world-writable test over it fires on every file and says nothing about who may
5
+ * write. Every reader of `mode & 0o022` (and the file-mode report of the execution env) asks THIS predicate,
6
+ * so a platform never judges a bit it cannot have.
7
+ *
8
+ * `platform` is a parameter so a caller can ask about the platform it was told, not only the one it runs on.
9
+ */
10
+ export declare function posixModeBitsMeaningful(platform?: NodeJS.Platform): boolean;
@@ -0,0 +1,3 @@
1
+ export function posixModeBitsMeaningful(platform = process.platform) {
2
+ return platform !== "win32";
3
+ }
@@ -9,6 +9,7 @@ import { ExecutionError, err, FileError, ok, toError, } from "../harness/types.j
9
9
  import { killProcessTree, shutdownDebug } from "./kill-tree.js";
10
10
  import { __resetSecretEnvScrubNoticeAnnouncement, announceSecretEnvScrub, inheritEnvPolicyProblem, inheritedHostEnv, scrubSecretEnv } from "../../core/secret-env.js";
11
11
  import { physicalPathOfAsync } from "../../core/physical-path.js";
12
+ import { posixModeBitsMeaningful } from "../../core/posix-mode-face.js";
12
13
  import { walkSymlinkChain } from "./symlink-chain.js";
13
14
  import { RollingTailBuffer, markTruncated, newStreamCursorState, sliceStreamIncrement, } from "../../core/exec-output-tail.js";
14
15
  import { BackgroundShellError } from "../../core/background-shell.js";
@@ -41,7 +42,7 @@ function fileKindFromStats(stats) {
41
42
  }
42
43
  return undefined;
43
44
  }
44
- const HAS_POSIX_MODE_FACE = process.platform !== "win32";
45
+ const HAS_POSIX_MODE_FACE = posixModeBitsMeaningful();
45
46
  function fileInfoFromStats(path, stats) {
46
47
  const kind = fileKindFromStats(stats);
47
48
  if (!kind) {
@@ -113,15 +113,23 @@ export type RuleAdoptionFile = {
113
113
  export declare function readRuleAdoptionFile(dir: string): RuleAdoptionFile | undefined;
114
114
  /** Atomically publish the adoption marker (same write-temp → fsync → rename discipline as the store). */
115
115
  export declare function writeRuleAdoptionFile(dir: string, content: RuleAdoptionFile): void;
116
+ /** What a directory vetting reads from its host. Absent ⇒ this process's platform, and a skipped judgment goes unsaid. */
117
+ interface DirVetting {
118
+ /** The platform whose permission bits are read (default: this process's — see {@link posixModeBitsMeaningful}). */
119
+ readonly platform?: NodeJS.Platform;
120
+ /** Said when the group/other-writable judgment was NOT made because the platform reports no permission bits. */
121
+ readonly modeBitsUnjudged?: (dir: string) => void;
122
+ }
116
123
  /** One principal's rule file. */
117
124
  declare class FileDurableRulePartition implements WritableDurableRulePartition {
118
125
  private readonly dir;
119
126
  private readonly file;
120
127
  private readonly acquireWriteLock;
121
128
  private readonly onError?;
129
+ private readonly vetting?;
122
130
  readonly durability: StoreDurability;
123
131
  readonly fidelity: StoreFidelity;
124
- constructor(dir: string, file: string, acquireWriteLock: () => void, onError?: ((message: string) => void) | undefined);
132
+ constructor(dir: string, file: string, acquireWriteLock: () => void, onError?: ((message: string) => void) | undefined, vetting?: DirVetting | undefined);
125
133
  /** Read and validate. Any refusal answers with an empty set AND says so — never a silent empty store. The answer's
126
134
  * sentence names no location ({@link refusalText}); the disclosure that accompanies it does. */
127
135
  private read;
@@ -198,9 +206,13 @@ export declare class FileDurableRulePartitionProvider implements DurableRulePart
198
206
  private readonly onError?;
199
207
  private lock;
200
208
  private orgPersistence;
209
+ /** What every directory vetting of this instance reads; it says an unjudged mode check ONCE per instance. */
210
+ private readonly vetting;
201
211
  constructor(dir: string, onError?: ((message: string) => void) | undefined, opts?: {
202
212
  /** The data root whose adoption marker gates construction (design/183 I6). Absent ⇒ no root gate. */
203
213
  dataRoot?: string;
214
+ /** The platform whose permission bits the directory vetting reads. Absent ⇒ this process's platform. */
215
+ platform?: NodeJS.Platform;
204
216
  });
205
217
  /** The org partition's durable home in this directory (design/182 §7.4 contract; `org.json`). Shares
206
218
  * this provider's writer lock — an install is a write. */
@@ -2,6 +2,7 @@ import { closeSync, constants as FS, existsSync, fsyncSync, lstatSync, mkdirSync
2
2
  import { join } from "node:path";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { isRuleBehavior } from "../../core/permission-rule-model.js";
5
+ import { posixModeBitsMeaningful } from "../../core/posix-mode-face.js";
5
6
  import { PERMISSION_RULE_WRITER, applySyncJoin, applyTombstones, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, foldDelta, unknownRuleWriteDeltaError, } from "../../core/permission-rule-store.js";
6
7
  import { canonicalize } from "../../core/canonical-json.js";
7
8
  import { BootLock, FileStoreLockError } from "./fs-atomic.js";
@@ -118,13 +119,17 @@ const EMPTY_READ = { rules: [], tombstones: [], rev: 0 };
118
119
  function checksumOf(body) {
119
120
  return `sha256:${createHash("sha256").update(canonicalize(body), "utf8").digest("hex")}`;
120
121
  }
121
- function unsafeDirFault(dir) {
122
+ function unsafeDirFault(dir, vetting) {
122
123
  mkdirSync(dir, { recursive: true, mode: 0o700 });
123
124
  const st = lstatSync(dir);
124
125
  if (st.isSymbolicLink() || !st.isDirectory())
125
126
  return "is a symlink or not a directory";
126
127
  if (typeof process.getuid === "function" && st.uid !== process.getuid())
127
128
  return "is owned by another user";
129
+ if (!posixModeBitsMeaningful(vetting?.platform)) {
130
+ vetting?.modeBitsUnjudged?.(dir);
131
+ return undefined;
132
+ }
128
133
  if ((st.mode & 0o022) !== 0)
129
134
  return `is writable by group or others (mode ${(st.mode & 0o777).toString(8)}) — anyone who can write there can grant themselves permissions`;
130
135
  return undefined;
@@ -135,15 +140,18 @@ function assertSafeDir(dir) {
135
140
  throw new Error(`permission-rule store directory ${dir} ${fault}; refusing to use it`);
136
141
  }
137
142
  function refusalText(onError, sentence, location) {
143
+ discloseTo(onError, `${sentence} (at ${location})`);
144
+ return `${sentence}. The location is reported to the store's error disclosure (onError), not in this message.`;
145
+ }
146
+ function discloseTo(onError, message) {
138
147
  try {
139
- onError?.(`${sentence} (at ${location})`);
148
+ onError?.(message);
140
149
  }
141
150
  catch {
142
151
  }
143
- return `${sentence}. The location is reported to the store's error disclosure (onError), not in this message.`;
144
152
  }
145
- function assertSafeDirAtRequest(dir, onError, act) {
146
- const fault = unsafeDirFault(dir);
153
+ function assertSafeDirAtRequest(dir, onError, act, vetting) {
154
+ const fault = unsafeDirFault(dir, vetting);
147
155
  if (fault !== undefined)
148
156
  throw new Error(refusalText(onError, `the permission-rule store directory ${fault}; refusing to ${act} it`, dir));
149
157
  }
@@ -171,13 +179,15 @@ class FileDurableRulePartition {
171
179
  file;
172
180
  acquireWriteLock;
173
181
  onError;
182
+ vetting;
174
183
  durability = "durable";
175
184
  fidelity = "json";
176
- constructor(dir, file, acquireWriteLock, onError) {
185
+ constructor(dir, file, acquireWriteLock, onError, vetting) {
177
186
  this.dir = dir;
178
187
  this.file = file;
179
188
  this.acquireWriteLock = acquireWriteLock;
180
189
  this.onError = onError;
190
+ this.vetting = vetting;
181
191
  }
182
192
  read() {
183
193
  const bucket = "this owner's permission-rule bucket";
@@ -185,7 +195,7 @@ class FileDurableRulePartition {
185
195
  const unreadable = (sentence, location = this.file) => ({ unreadable: refusalText(this.onError, sentence, location) });
186
196
  let raw;
187
197
  try {
188
- const dirFault = unsafeDirFault(this.dir);
198
+ const dirFault = unsafeDirFault(this.dir, this.vetting);
189
199
  if (dirFault !== undefined)
190
200
  return unreadable(`the permission-rule store directory ${dirFault}; loading zero rules`, this.dir);
191
201
  const st = lstatSync(this.file);
@@ -228,11 +238,7 @@ class FileDurableRulePartition {
228
238
  return { file: parsed };
229
239
  }
230
240
  disclose(message) {
231
- try {
232
- this.onError?.(message);
233
- }
234
- catch {
235
- }
241
+ discloseTo(this.onError, message);
236
242
  }
237
243
  screenDurableRows(file) {
238
244
  const isSession = (scope) => scope?.kind === "session";
@@ -246,7 +252,7 @@ class FileDurableRulePartition {
246
252
  return { rules, tombstones, quarantined };
247
253
  }
248
254
  write(next) {
249
- assertSafeDirAtRequest(this.dir, this.onError, "write through");
255
+ assertSafeDirAtRequest(this.dir, this.onError, "write through", this.vetting);
250
256
  atomicPublish(this.dir, this.file, JSON.stringify({ ...next, checksum: checksumOf(next) }, null, 2));
251
257
  }
252
258
  async list() {
@@ -392,11 +398,22 @@ export class FileDurableRulePartitionProvider {
392
398
  onError;
393
399
  lock;
394
400
  orgPersistence;
401
+ vetting;
395
402
  constructor(dir, onError, opts) {
396
403
  this.dir = dir;
397
404
  this.onError = onError;
398
405
  if (opts?.dataRoot !== undefined)
399
406
  assertAdoptionBootGate(opts.dataRoot, "FileDurableRulePartitionProvider");
407
+ let said = false;
408
+ this.vetting = {
409
+ platform: opts?.platform,
410
+ modeBitsUnjudged: (where) => {
411
+ if (said)
412
+ return;
413
+ said = true;
414
+ discloseTo(this.onError, `the permission-rule store directory is not checked for being writable by other accounts: this platform reports no POSIX permission bits, so who else can write there is not verified here (at ${where})`);
415
+ },
416
+ };
400
417
  }
401
418
  adoptionMarker() {
402
419
  const judged = judgeRuleAdoptionFile(this.dir);
@@ -410,13 +427,13 @@ export class FileDurableRulePartitionProvider {
410
427
  return this.fileOrgPersistence();
411
428
  }
412
429
  fileOrgPersistence() {
413
- this.orgPersistence ??= new FileOrgRuleStatePersistence(this.dir, this.acquireWriteLock, this.onError);
430
+ this.orgPersistence ??= new FileOrgRuleStatePersistence(this.dir, this.acquireWriteLock, this.onError, this.vetting);
414
431
  return this.orgPersistence;
415
432
  }
416
433
  acquireWriteLock = () => {
417
434
  if (this.lock !== undefined)
418
435
  return;
419
- assertSafeDirAtRequest(this.dir, this.onError, "write through");
436
+ assertSafeDirAtRequest(this.dir, this.onError, "write through", this.vetting);
420
437
  const lock = new BootLock(join(this.dir, "PERMISSION-RULES-WRITER-LOCK"));
421
438
  try {
422
439
  lock.acquire();
@@ -437,11 +454,11 @@ export class FileDurableRulePartitionProvider {
437
454
  return this.readOnlySourceView(principalRuleFileName(principal), principalRuleFileName(rec.toPrincipal), "adopted" in adoption || adoption.marker.phase >= 4);
438
455
  }
439
456
  }
440
- return new FileDurableRulePartition(this.dir, join(this.dir, principalRuleFileName(principal)), this.acquireWriteLock, this.onError);
457
+ return new FileDurableRulePartition(this.dir, join(this.dir, principalRuleFileName(principal)), this.acquireWriteLock, this.onError, this.vetting);
441
458
  }
442
459
  readOnlySourceView(sourceName, targetName, flipped) {
443
460
  const pick = flipped ? targetName : existsSync(join(this.dir, sourceName)) ? sourceName : targetName;
444
- return readOnlyRuleStoreView(new FileDurableRulePartition(this.dir, join(this.dir, pick), this.acquireWriteLock, this.onError));
461
+ return readOnlyRuleStoreView(new FileDurableRulePartition(this.dir, join(this.dir, pick), this.acquireWriteLock, this.onError, this.vetting));
445
462
  }
446
463
  forLocalOwner() {
447
464
  const adoption = this.adoptionMarker();
@@ -451,7 +468,7 @@ export class FileDurableRulePartitionProvider {
451
468
  return this.readOnlySourceView(LOCAL_OWNER_FILE, principalRuleFileName(rec.toPrincipal), "adopted" in adoption || adoption.marker.phase >= 4);
452
469
  }
453
470
  }
454
- return new FileDurableRulePartition(this.dir, join(this.dir, LOCAL_OWNER_FILE), this.acquireWriteLock, this.onError);
471
+ return new FileDurableRulePartition(this.dir, join(this.dir, LOCAL_OWNER_FILE), this.acquireWriteLock, this.onError, this.vetting);
455
472
  }
456
473
  dispose() {
457
474
  this.lock?.release();
@@ -462,12 +479,14 @@ class FileOrgRuleStatePersistence {
462
479
  dir;
463
480
  acquireWriteLock;
464
481
  onError;
482
+ vetting;
465
483
  file;
466
484
  writeChain = Promise.resolve();
467
- constructor(dir, acquireWriteLock, onError) {
485
+ constructor(dir, acquireWriteLock, onError, vetting) {
468
486
  this.dir = dir;
469
487
  this.acquireWriteLock = acquireWriteLock;
470
488
  this.onError = onError;
489
+ this.vetting = vetting;
471
490
  this.file = join(dir, ORG_STATE_FILE);
472
491
  }
473
492
  readSync() {
@@ -477,7 +496,7 @@ class FileOrgRuleStatePersistence {
477
496
  const ioFault = (err) => `could not be read (${err?.code ?? err?.name ?? "unknown"})`;
478
497
  let dirFault;
479
498
  try {
480
- dirFault = unsafeDirFault(this.dir);
499
+ dirFault = unsafeDirFault(this.dir, this.vetting);
481
500
  }
482
501
  catch (err) {
483
502
  return refuse(`the permission-rule store directory ${ioFault(err)}`, `${this.dir}: ${thrownText(err)}`);
@@ -524,7 +543,7 @@ class FileOrgRuleStatePersistence {
524
543
  }
525
544
  installOrgStateSync(state) {
526
545
  this.acquireWriteLock();
527
- assertSafeDirAtRequest(this.dir, this.onError, "write through");
546
+ assertSafeDirAtRequest(this.dir, this.onError, "write through", this.vetting);
528
547
  state = jsonForm(state);
529
548
  const cur = this.readSync();
530
549
  if (cur !== undefined && state.revisionHighWater < cur.revisionHighWater) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "7.49.4",
3
+ "version": "7.49.5",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -0,0 +1,104 @@
1
+ {
2
+ "repo": "core",
3
+ "version": "7.49.5",
4
+ "from": "40a79ac3341fc6750aae1664c64840a44abaa205",
5
+ "to": "9ba02c33f3eea253894b0132dca7ed98c390fb08",
6
+ "added": [],
7
+ "removed": [],
8
+ "changed": [],
9
+ "narrowed": [],
10
+ "closedSetMembers": [],
11
+ "closedSetsUnreadable": [],
12
+ "typeKeys": [],
13
+ "typeKeysUnreadableArms": [],
14
+ "typeKeysFlattenedCount": 223,
15
+ "signatures": [
16
+ {
17
+ "name": "FileDurableRulePartitionProvider.constructor",
18
+ "at": [
19
+ "src/stores/file/permission-rule-store.ts:671"
20
+ ],
21
+ "from": [
22
+ "(dir: string, onError?: (message: string) => void, opts?: { dataRoot?: string; })"
23
+ ],
24
+ "to": [
25
+ "(dir: string, onError?: (message: string) => void, opts?: { dataRoot?: string; platform?: NodeJS.Platform; })"
26
+ ],
27
+ "changes": [
28
+ {
29
+ "aspect": "param",
30
+ "detail": "param 3 `opts` type widened: { dataRoot?: string; [ -> platform?: NodeJS.Platform; ]}",
31
+ "breaking": false
32
+ }
33
+ ],
34
+ "breaking": false,
35
+ "criteriaCovered": false
36
+ },
37
+ {
38
+ "name": "judgePeerRecordFile",
39
+ "at": [
40
+ "src/agents/peer-directory.ts:484"
41
+ ],
42
+ "from": [
43
+ "(stat: { uid?: number; mode: number; isFile: boolean; size: number; }, selfUid: number | undefined): { ok: false; code: \"not_regular\" | \"foreign_owner\" | \"group_or_world_writable\" | \"too_large\"; } | { ok: true; }"
44
+ ],
45
+ "to": [
46
+ "(stat: { uid?: number; mode: number; isFile: boolean; size: number; }, selfUid: number | undefined, platform?: NodeJS.Platform): { ok: false; code: \"not_regular\" | \"foreign_owner\" | \"group_or_world_writable\" | \"too_large\"; } | { ok: true; }"
47
+ ],
48
+ "changes": [
49
+ {
50
+ "aspect": "param",
51
+ "detail": "+optional param `platform?: NodeJS.Platform`",
52
+ "breaking": false
53
+ }
54
+ ],
55
+ "breaking": false,
56
+ "criteriaCovered": false
57
+ },
58
+ {
59
+ "name": "vetPeerRegistryDirectory",
60
+ "at": [
61
+ "src/agents/peer-directory.ts:452"
62
+ ],
63
+ "from": [
64
+ "(dir: string, opts?: { uid?: number; stopAt?: string; }): Promise<PeerRegistryDirectoryVerdict>"
65
+ ],
66
+ "to": [
67
+ "(dir: string, opts?: { uid?: number; stopAt?: string; platform?: NodeJS.Platform; }): Promise<PeerRegistryDirectoryVerdict>"
68
+ ],
69
+ "changes": [
70
+ {
71
+ "aspect": "param",
72
+ "detail": "param 2 `opts` type widened: …umber; stopAt?: string; [ -> platform?: NodeJS.Platform; ]}",
73
+ "breaking": false
74
+ }
75
+ ],
76
+ "breaking": false,
77
+ "criteriaCovered": false
78
+ }
79
+ ],
80
+ "declarationMoves": [],
81
+ "mintMoves": [],
82
+ "criteriaObserves": [
83
+ {
84
+ "kind": "typeMember",
85
+ "name": "AskRequest.classes",
86
+ "assertions": [
87
+ "G1.a",
88
+ "G2.a"
89
+ ]
90
+ }
91
+ ],
92
+ "consumersHint": {
93
+ "cli": [
94
+ "FileDurableRulePartitionProvider"
95
+ ],
96
+ "server": [
97
+ "FileDurableRulePartitionProvider"
98
+ ],
99
+ "sdk": [],
100
+ "note": "scanned cli@7006be65 server@72f3abe9 sdk@f1b30d4 — a name listed is MENTIONED there today, which is evidence of consumption, not proof of it. Ledger files (**/docs/core-consumption/**, **/docs/wire-consumption/**, **/*LEDGER*.json) are not evidence of use — server: 2 name(s) matched ONLY in bookkeeping files and were set aside (judgePeerRecordFile, vetPeerRegistryDirectory)"
101
+ },
102
+ "breakingCount": 0,
103
+ "breakingItems": []
104
+ }