@sema-agent/core 7.30.0 → 7.31.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/CHANGELOG.md +274 -0
  2. package/dist/agents/cross-session-judge.d.ts +4 -12
  3. package/dist/agents/cross-session-judge.js +4 -11
  4. package/dist/agents/peer-session-drain.js +0 -5
  5. package/dist/agents/send-message-tool.js +1 -0
  6. package/dist/agents/subagent.d.ts +1 -0
  7. package/dist/agents/subagent.js +1 -0
  8. package/dist/core/ask-origin.d.ts +6 -0
  9. package/dist/core/ask-origin.js +10 -0
  10. package/dist/core/checkpoint-store.d.ts +14 -0
  11. package/dist/core/checkpoint-store.js +1 -0
  12. package/dist/core/engine-notice.d.ts +4 -0
  13. package/dist/core/governance-codes.d.ts +1 -1
  14. package/dist/core/governance-codes.js +3 -0
  15. package/dist/core/memory-engine/data-plane.d.ts +3 -1
  16. package/dist/core/memory-engine/data-plane.js +1 -1
  17. package/dist/core/memory-engine/delegation-settlement.d.ts +44 -3
  18. package/dist/core/memory-engine/delegation-settlement.js +38 -3
  19. package/dist/core/memory-engine/engine.d.ts +123 -26
  20. package/dist/core/memory-engine/engine.js +426 -149
  21. package/dist/core/memory-engine/entry-scan.d.ts +23 -0
  22. package/dist/core/memory-engine/entry-scan.js +55 -0
  23. package/dist/core/memory-engine/file-backend.d.ts +16 -23
  24. package/dist/core/memory-engine/file-backend.js +122 -99
  25. package/dist/core/memory-engine/layer-observations.d.ts +109 -0
  26. package/dist/core/memory-engine/layer-observations.js +148 -0
  27. package/dist/core/memory-engine/layout.d.ts +11 -0
  28. package/dist/core/memory-engine/layout.js +20 -4
  29. package/dist/core/memory-engine/types.d.ts +26 -2
  30. package/dist/core/parent-spec-seat.d.ts +4 -1
  31. package/dist/core/parent-spec-seat.js +2 -0
  32. package/dist/core/park-selfcheck.js +1 -0
  33. package/dist/core/runner/announce-once-ledger.d.ts +26 -0
  34. package/dist/core/runner/announce-once-ledger.js +37 -0
  35. package/dist/core/runner/contracts.d.ts +22 -5
  36. package/dist/core/runner/denial-limit-arms.d.ts +2 -2
  37. package/dist/core/runner/prepare-acquire-reconcile.d.ts +0 -4
  38. package/dist/core/runner/prepare-acquire-reconcile.js +1 -3
  39. package/dist/core/runner/prepare-file-history.d.ts +13 -15
  40. package/dist/core/runner/prepare-file-history.js +57 -43
  41. package/dist/core/runner/prepare-hands-readface.d.ts +2 -2
  42. package/dist/core/runner/prepare-listings.d.ts +3 -1
  43. package/dist/core/runner/prepare-listings.js +2 -2
  44. package/dist/core/runner/prepare-memory.js +16 -5
  45. package/dist/core/runner/prepare-park-ask.js +27 -16
  46. package/dist/core/runner/prepare-prompt-inputs.d.ts +8 -1
  47. package/dist/core/runner/prepare-prompt-inputs.js +5 -3
  48. package/dist/core/runner/prepare-task.js +7 -5
  49. package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -0
  50. package/dist/core/runner/prepare-wiring-manifest.js +2 -1
  51. package/dist/core/runner/run-attachment-seats.js +1 -1
  52. package/dist/core/runner/run-leg.js +61 -52
  53. package/dist/core/runner/run-settle-and-teardown.js +4 -0
  54. package/dist/core/runner/run-turn-boundary.js +6 -6
  55. package/dist/core/runner/turn-attachments.d.ts +6 -6
  56. package/dist/core/runner-deps.d.ts +3 -1
  57. package/dist/core/secret-env.d.ts +11 -0
  58. package/dist/core/secret-env.js +22 -1
  59. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
  60. package/dist/core/store-contracts/checkpoint-store-contract.js +22 -0
  61. package/dist/core/task-result.d.ts +1 -1
  62. package/dist/core/task-spec.d.ts +17 -8
  63. package/dist/core/tool-spec.d.ts +1 -1
  64. package/dist/core/wiring-manifest.d.ts +10 -0
  65. package/dist/core/wiring-manifest.js +7 -1
  66. package/dist/engine/execution-env/node-execution-env.d.ts +6 -0
  67. package/dist/engine/execution-env/node-execution-env.js +15 -17
  68. package/dist/engine/harness/agent-harness.js +7 -5
  69. package/dist/engine/lsp/node-lsp-manager.d.ts +7 -0
  70. package/dist/engine/lsp/node-lsp-manager.js +11 -3
  71. package/dist/index.d.ts +1 -1
  72. package/dist/index.js +1 -1
  73. package/dist/shell/lex.d.ts +8 -3
  74. package/dist/shell/lex.js +2 -2
  75. package/dist/tools/fs/bash-readonly-classifier.d.ts +7 -1
  76. package/dist/tools/fs/bash-readonly-classifier.js +7 -3
  77. package/dist/tools/fs/shell-read-establish.d.ts +1 -1
  78. package/dist/tools/fs/shell-read-establish.js +6 -34
  79. package/dist/tools/fs/shell-read-identity.d.ts +13 -0
  80. package/dist/tools/fs/shell-read-identity.js +39 -0
  81. package/dist/tools/fs/shell-read-judge.d.ts +8 -0
  82. package/dist/tools/fs/shell-read-judge.js +7 -0
  83. package/dist/tools/fs/shell-read-plan.d.ts +5 -1
  84. package/dist/tools/fs/shell-read-plan.js +13 -21
  85. package/dist/tools/fs/shell-read-word.d.ts +16 -2
  86. package/dist/tools/fs/shell-read-word.js +40 -5
  87. package/package.json +1 -1
  88. package/test/export-surface.snapshot.json +7 -1
  89. package/wire-changes/core@7.30.1.json +154 -0
  90. package/wire-changes/core@7.31.0.json +705 -0
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { closeSync, constants as fsConstants, existsSync, fchmodSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { accessSync, closeSync, constants as fsConstants, existsSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join, relative, sep } from "node:path";
4
4
  import { uuidv7 } from "../../internal/harness.js";
5
5
  import { MAX_MEMORY_BYTES, composeMemoryBlock, firstSentence } from "../memory.js";
@@ -11,12 +11,13 @@ import { MEMORY_PROVENANCE_RECALL_SENTENCE, memoryExposureIndexRow, parseMemoryE
11
11
  import { isInstructionEntry } from "./header-hints.js";
12
12
  import { ConsolidationRefusedError, CONSOLIDATION_FORCE_THROTTLE_FLOOR_MS, CONSOLIDATION_SEAT_FREEZE_GRACE_MS, consolidationTypeEligible, deriveProductSlug, deriveSupersededSet, isValidConsolidationPlanId, listConsolidationPlanIds, productAddPatch, quarantineCorruptPlan, readConsolidationGateFile, readConsolidationPlan, readIntentCredentials, recordActiveSetBaseline, recordConsolidationSession, sessionsSinceExcludingCurrent, recordIntentCredentials, scanRefusalCeiling, screenConsolidationOptions, supersessionFuseCeiling, updateConsolidationGate, writeConsolidationPlan, } from "./consolidation.js";
13
13
  import { deriveShadowedSet } from "./shadowing.js";
14
- import { classifySessionSettlements, clearHoldResolution, closeSessionAccount, disposeHold, effectiveSettlements, expireOverdueSettlements, foreignDanglingSessionAccounts, markHoldReleased, openInstructionHold, openSessionAccount, readHoldCustody, readHolds, reconcileHolds, replayExternalSettlementEffects, resolveHoldRecord, resolveSessionAccountRecord, resolveSettlementRecord, sessionSettlements, sessionUnattributedSet, emptyUnattributedSet, convertV1SidecarKeys, } from "./delegation-settlement.js";
14
+ import { classifySessionSettlements, clearHoldResolution, closeSessionAccount, disposeHold, effectiveSettlements, expireOverdueSettlements, foreignDanglingSessionAccounts, openLayerWriters, terminateSessionAccount, sessionAccountLeg, markHoldReleased, openInstructionHold, openSessionAccount, readHoldCustody, readHolds, reconcileHolds, replayExternalSettlementEffects, resolveHoldRecord, resolveSessionAccountRecord, resolveSettlementRecord, sessionSettlements, sessionUnattributedSet, emptyUnattributedSet, convertV1SidecarKeys, } from "./delegation-settlement.js";
15
15
  import { noReplaceRestore, retireStagingToCustody, stagingPathBeside } from "./delegation-settlement.js";
16
+ import { landedUndecided, layerDigest, layerObservationLookup, readOnlyLayerCandidates, readOnlyLayerCensus, recordLayerObservations, settleUndecidedLayerObservations, observationBacked, sameObservation, undecidedLayerObservations } from "./layer-observations.js";
16
17
  import { markOriginClearanceTombstoned, openOriginClearance, readOriginClearances, settleOriginClearance } from "./origin-clearance.js";
17
18
  import { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME, canonicalJsonStringify, captureErasureInput, erasureRequestInvalid, erasureSelectHash, scanEntryFiles, } from "./file-backend.js";
18
19
  import { assembleMemoryExportBundle, computeMemoryBundleHash, memoryBundleInvalid, } from "./export-bundle.js";
19
- import { QUARANTINE_DIR, SCAN_FUSE_THRESHOLD, quarantineAndTombstone, restoreContainedBaseline, readIndexRevs, writeIndexRevs, bumpScanFuse, scanFuseKey, canonicalize, adoptRootScopeLayout, clearScanFuse, adoptCanonicalKeyedControlDir, deriveControlPlaneDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, ensureDirExists, markSessionPolluted, readSessionPollution, fileSessionCaptureRecordStore, recordRetrievedAccount, writeFileNoFollow, readRetrievedAccount, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirFor, appendChallengeEvents, appendLineageAudit, rebuildStrictControlPlaneLedger, isStrictControlPlaneLedgerCorrupt, CHALLENGE_LEDGER_MAX_EVENTS, adjudicateLineagePending, challengedEntryIds, clearLineageForEntries, discardLineagePending, lineageAccountOfEntry, lineageContributionsOfSession, lineageLatchedIds, promoteLineagePending, readChallengeEvents, readChallengedHistory, readLineageRecord, recordChallengedHistory, recordLineageCredential, reconcileLineage, readProjectionDebts, resolveChallengeEvent, settleProjectionDebts, stageLineagePending, stageProjectionDebts, } from "./layout.js";
20
+ import { QUARANTINE_DIR, SCAN_FUSE_THRESHOLD, quarantineAndTombstone, restoreContainedBaseline, readIndexRevs, writeIndexRevs, bumpScanFuse, scanFuseKey, canonicalize, adoptRootScopeLayout, clearScanFuse, adoptCanonicalKeyedControlDir, deriveControlPlaneDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, ensureDirExists, markSessionPolluted, readSessionPollution, fileSessionCaptureRecordStore, recordRetrievedAccount, writeFileNoFollow, withPermissionRemedy, permissionRemedyOf, readRetrievedAccount, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirFor, appendChallengeEvents, appendLineageAudit, rebuildStrictControlPlaneLedger, isStrictControlPlaneLedgerCorrupt, CHALLENGE_LEDGER_MAX_EVENTS, adjudicateLineagePending, challengedEntryIds, clearLineageForEntries, discardLineagePending, lineageAccountOfEntry, lineageContributionsOfSession, lineageLatchedIds, promoteLineagePending, readChallengeEvents, readChallengedHistory, readLineageRecord, recordChallengedHistory, recordLineageCredential, reconcileLineage, readProjectionDebts, resolveChallengeEvent, settleProjectionDebts, stageLineagePending, stageProjectionDebts, } from "./layout.js";
20
21
  import { createSessionIncarnationSeat, recalledForAnyIdentity, recalledForIdentity, rememberForIdentity } from "./session-incarnation.js";
21
22
  import { scanMemoryFileName, scanMemoryWrite, scanRemediation } from "./scan.js";
22
23
  import { writableRootOf } from "./types.js";
@@ -301,6 +302,11 @@ export function memoryConsolidationRefusedNotice(input) {
301
302
  },
302
303
  };
303
304
  }
305
+ const INDEX_WRITE_REFUSED = "the derived memory index could NOT be written";
306
+ function unexpectedLayerFile(handle, c) {
307
+ return { path: relative(handle.memoryDir, c.path), code: "readonly_layer", reason: "a file appeared in a read-only inherited memory layer during this session — it is recorded as an unexpected write and never adopted into memory" };
308
+ }
309
+ const STALE_LEG_NOTE = "memory session account: this harvest belongs to an earlier leg of the session (a later leg re-opened the account while it ran) — its account close and leg end were not written over the later leg";
304
310
  export class MemoryEngine {
305
311
  backend;
306
312
  memoryDir;
@@ -315,6 +321,7 @@ export class MemoryEngine {
315
321
  holdSettleTimeoutMs;
316
322
  provenance;
317
323
  onIncident;
324
+ unsettleableDisclosed = false;
318
325
  backendPinnedRoot;
319
326
  constructor(opts) {
320
327
  if (opts.provenance !== undefined && opts.provenance !== "off" && opts.provenance !== "carry") {
@@ -360,6 +367,18 @@ export class MemoryEngine {
360
367
  return this.provenance;
361
368
  }
362
369
  consolidation;
370
+ endSessionLeg(handle, sessionId) {
371
+ return handle.accountLeg !== undefined ? this.endLeg(sessionId, handle.accountLeg) : undefined;
372
+ }
373
+ endLeg(sessionId, leg) {
374
+ try {
375
+ return terminateSessionAccount(this.controlDir, { sessionId, now: this.now, leg });
376
+ }
377
+ catch (err) {
378
+ this.discloseSessionAccountIncident("the end of this session's leg could not be recorded — it keeps reading as a live writer of its layer (read-only restores there are withheld)", err);
379
+ return undefined;
380
+ }
381
+ }
363
382
  discloseSessionAccountIncident(what, cause) {
364
383
  const sink = this.onIncident;
365
384
  if (sink === undefined)
@@ -372,6 +391,208 @@ export class MemoryEngine {
372
391
  catch {
373
392
  }
374
393
  }
394
+ async detectReadOnlyLayerChanges(handle, report, admitsNothing) {
395
+ const candidates = [];
396
+ for (const m of handle.materialized.filter((x) => x.readonly)) {
397
+ const text = readSafe(m.path);
398
+ if (text === undefined) {
399
+ report.missing.push(m.relPath);
400
+ continue;
401
+ }
402
+ if (revOfText(text, m.id) === m.rev)
403
+ continue;
404
+ if ((await this.committedContentUnadopted(m.id)) === text)
405
+ continue;
406
+ const stood = handle.layerBaseline?.get(m.path) === layerDigest(text);
407
+ const rejection = admitsNothing
408
+ ? { path: m.relPath, code: "readonly_layer", reason: `a read-only inherited memory layer's file ${stood ? "differed from its committed content when this session started (not this session's write)" : "differs from what this session was served"}; this session admits nothing, so it was not restored — the bytes stay on disk for a session that may write to adjudicate` }
409
+ : { path: m.relPath, code: "readonly_layer", reason: stood ? "a read-only inherited memory layer's file differed from its committed content when this session started (not this session's write)" : "a read-only inherited memory layer was modified; changes to inherited layers are not writable from this session" };
410
+ report.rejections.push(rejection);
411
+ if (!admitsNothing)
412
+ candidates.push({ path: m.path, relPath: m.relPath, scope: m.scope, id: m.id, stood, rejection });
413
+ }
414
+ return candidates;
415
+ }
416
+ async committedContentUnadopted(id) {
417
+ const zeroCopy = this.backendPinnedRoot !== undefined && canonicalize(this.backendPinnedRoot) === canonicalize(this.memoryDir);
418
+ const view = zeroCopy ? undefined : this.backend.restrictedAdoptionView?.({ audit: false });
419
+ if (view === undefined)
420
+ return this.committedContentFor(id);
421
+ try {
422
+ const [entry] = await view.getByIds([id]);
423
+ if (entry !== undefined && entry.id === id)
424
+ return serializeEntryFile(entry);
425
+ }
426
+ catch {
427
+ }
428
+ return this.backend.readCommittedShadow?.(id) ?? readSafe(join(this.controlDir, "shadow", `${id}.md`));
429
+ }
430
+ async restoreReadOnlyLayers(candidates, report, selfLeg) {
431
+ let writers;
432
+ for (const c of candidates) {
433
+ const text = readSafe(c.path);
434
+ const committed = await this.committedContentUnadopted(c.id);
435
+ if (text !== undefined && text === committed) {
436
+ report.rejections = report.rejections.filter((r) => r !== c.rejection);
437
+ continue;
438
+ }
439
+ if (committed === undefined)
440
+ continue;
441
+ try {
442
+ writers ??= openLayerWriters(this.controlDir, selfLeg);
443
+ }
444
+ catch {
445
+ writers = { scopes: new Set(), unscoped: 1 };
446
+ }
447
+ if (writers.unscoped > 0 || writers.scopes.has(c.scope)) {
448
+ c.rejection.reason = `a read-only inherited memory layer's file ${c.stood ? "differed from its committed content when this session started (not this session's write)" : "differs from what this session was served"}; a session that writes this layer is open (the bytes may be its uncommitted edit), so it was not restored`;
449
+ report.warnings.push(`read-only layer file ${quoteId(c.relPath)} was not restored: a session that writes this layer is open, and the bytes may be its uncommitted edit`);
450
+ continue;
451
+ }
452
+ try {
453
+ writeFileNoFollow(c.path, committed);
454
+ report.restored.push(c.relPath);
455
+ }
456
+ catch (err) {
457
+ report.warnings.push(`read-only layer file ${quoteId(c.relPath)} could NOT be restored to its committed content — the modified bytes are still on disk: ${this.writeFailureText(err)}`);
458
+ }
459
+ }
460
+ }
461
+ async observeReadOnlyLayers(handle, report, sessionId) {
462
+ const served = new Set(handle.materialized.filter((m) => m.readonly).map((m) => m.path));
463
+ const batch = { rows: [], rejections: [], undecided: [], landed: false };
464
+ for (const c of readOnlyLayerCandidates(this.memoryDir, this.controlDir, handle)) {
465
+ const by = { scope: c.scope, rel: c.rel, ...(sessionId !== undefined ? { sessionId } : {}), observedAt: this.now() };
466
+ if (c.id === undefined) {
467
+ batch.rows.push({ ...by, observedRev: c.digest });
468
+ }
469
+ else {
470
+ const committed = await this.committedRowOf(c.id);
471
+ if (committed === "undecidable") {
472
+ const row = { ...by, entryId: c.id, observedRev: c.observedRev, undecided: true, observationId: uuidv7() };
473
+ batch.rows.push(row);
474
+ batch.undecided.push({ candidate: c, row });
475
+ continue;
476
+ }
477
+ if (committed !== undefined && committed.scope === c.scope && committed.slug === c.slug && committed.rev === c.observedRev)
478
+ continue;
479
+ batch.rows.push({ ...by, entryId: c.id, ...(committed !== undefined ? { rev: committed.rev } : {}), observedRev: c.observedRev });
480
+ }
481
+ if (!served.has(c.path)) {
482
+ batch.rejections.push(unexpectedLayerFile(handle, c));
483
+ }
484
+ }
485
+ this.landObservations(batch, report);
486
+ return batch;
487
+ }
488
+ landObservations(batch, report) {
489
+ try {
490
+ recordLayerObservations(this.controlDir, batch.rows);
491
+ }
492
+ catch (err) {
493
+ batch.failure = this.writeFailureText(err);
494
+ return;
495
+ }
496
+ batch.landed = true;
497
+ delete batch.failure;
498
+ report.rejections.push(...batch.rejections);
499
+ }
500
+ concludeObservations(batch, report) {
501
+ if (batch !== undefined && !batch.landed) {
502
+ report.warnings.push(`${batch.rows.length} unexpected write(s) into read-only memory layers could NOT be recorded — a later session's read may adopt them: ${batch.failure ?? "unknown failure"}`);
503
+ }
504
+ this.discloseUndecidedObservations(landedUndecided(batch).map((u) => u.candidate), report);
505
+ }
506
+ discloseUndecidedObservations(undecided, report) {
507
+ for (const c of undecided) {
508
+ report.warnings.push(`${quoteId(relative(this.memoryDir, c.path))}: changed in a read-only memory layer during this session, and whether a commit backs it could not be established (a memory transaction was pending, or the committed account could not be read) — recorded as undecided (not adopted), settled once a writing session can read the committed account`);
509
+ }
510
+ }
511
+ async settleUndecidedObservations(handle, report, own) {
512
+ let rows;
513
+ try {
514
+ rows = undecidedLayerObservations(this.controlDir);
515
+ }
516
+ catch (err) {
517
+ report.warnings.push(`undecided read-only-layer observations could not be read for settlement — they stay undecided (not adopted): ${this.writeFailureText(err)}`);
518
+ return;
519
+ }
520
+ const verdicts = new Map();
521
+ for (const row of rows) {
522
+ const committed = row.entryId !== undefined ? await this.committedRowOf(row.entryId) : "undecidable";
523
+ if (committed !== "undecidable")
524
+ verdicts.set(row, observationBacked(row, committed) ? "backed" : committed !== undefined ? { rev: committed.rev } : {});
525
+ }
526
+ try {
527
+ settleUndecidedLayerObservations(this.controlDir, verdicts);
528
+ }
529
+ catch (err) {
530
+ report.warnings.push(`undecided read-only-layer observations could not be settled — they stay undecided (not adopted): ${this.writeFailureText(err)}`);
531
+ return;
532
+ }
533
+ const served = new Set(handle.materialized.filter((m) => m.readonly).map((m) => m.path));
534
+ const stillUndecided = [];
535
+ for (const { candidate: c, row: mine } of own) {
536
+ const row = rows.find((r) => sameObservation(r, mine));
537
+ if (row === undefined) {
538
+ const refused = served.has(c.path) ? false : await this.refusedNow(mine.entryId, c.observedRev);
539
+ if (refused === true)
540
+ report.rejections.push(unexpectedLayerFile(handle, c));
541
+ else if (refused === undefined)
542
+ report.warnings.push(`${quoteId(relative(this.memoryDir, c.path))}: changed in a read-only memory layer during this session; another writing session settled its record first, and whether memory refuses these bytes now could not be read`);
543
+ continue;
544
+ }
545
+ const verdict = verdicts.get(row);
546
+ if (verdict === undefined)
547
+ stillUndecided.push(c);
548
+ else if (verdict !== "backed" && !served.has(c.path))
549
+ report.rejections.push(unexpectedLayerFile(handle, c));
550
+ }
551
+ this.discloseUndecidedObservations(stillUndecided, report);
552
+ }
553
+ async refusedNow(entryId, observedRev) {
554
+ const committed = entryId !== undefined ? await this.committedRowOf(entryId) : "undecidable";
555
+ if (entryId === undefined || committed === "undecidable")
556
+ return undefined;
557
+ try {
558
+ return layerObservationLookup(this.controlDir).find(entryId, committed?.rev, observedRev) !== undefined;
559
+ }
560
+ catch {
561
+ return undefined;
562
+ }
563
+ }
564
+ discloseUnsettleableObservations() {
565
+ if (this.unsettleableDisclosed || typeof this.backend.committedSnapshotOf === "function")
566
+ return;
567
+ this.unsettleableDisclosed = true;
568
+ const sink = this.onIncident;
569
+ if (sink === undefined)
570
+ return;
571
+ try {
572
+ const e = new Error("memory read-only layers: this backend has no committed-snapshot face (committedSnapshotOf), so whether a commit backs a change observed in a read-only layer cannot be established — every such change is recorded as undecided (not adopted), and undecided observations cannot settle on this backend");
573
+ e.code = "memory.observation_settlement_unavailable";
574
+ sink(e);
575
+ }
576
+ catch {
577
+ }
578
+ }
579
+ async committedRowOf(id) {
580
+ const face = this.backend.committedSnapshotOf;
581
+ if (face === undefined)
582
+ return "undecidable";
583
+ try {
584
+ const snap = await face.call(this.backend, id);
585
+ if (snap.state !== "row")
586
+ return undefined;
587
+ return { rev: snap.rev, ...(snap.binding.state === "bound" ? { scope: snap.binding.scope, slug: snap.binding.slug } : {}) };
588
+ }
589
+ catch {
590
+ return "undecidable";
591
+ }
592
+ }
593
+ writeFailureText(err) {
594
+ return thrownText(withPermissionRemedy(err, this.memoryDir));
595
+ }
375
596
  discloseAnnounceFailure(stage, cause) {
376
597
  const sink = this.onIncident;
377
598
  if (sink === undefined)
@@ -557,7 +778,7 @@ export class MemoryEngine {
557
778
  }
558
779
  }
559
780
  catch (err) {
560
- out.failures.push({ path: MEMORY_INDEX_FILENAME, detail: `index sweep failed: ${thrownText(err)}` });
781
+ out.failures.push({ path: MEMORY_INDEX_FILENAME, detail: `index sweep failed: ${this.writeFailureText(err)}` });
561
782
  }
562
783
  }
563
784
  const scanned = scanEntryFiles(writableRoot, {
@@ -726,6 +947,14 @@ export class MemoryEngine {
726
947
  return this.challengeAndAnnounce(ids.map((entryId) => ({ eventId: `${requestId}:${entryId}`, entryId, reason })));
727
948
  }
728
949
  async eraseMemoryEntries(input) {
950
+ try {
951
+ return await this.eraseMemoryEntriesCore(input);
952
+ }
953
+ catch (err) {
954
+ throw withPermissionRemedy(err, this.memoryDir);
955
+ }
956
+ }
957
+ async eraseMemoryEntriesCore(input) {
729
958
  const captured = captureErasureInput(input);
730
959
  const bad = erasureRequestInvalid(captured);
731
960
  if (bad !== undefined) {
@@ -867,7 +1096,7 @@ export class MemoryEngine {
867
1096
  }
868
1097
  }
869
1098
  catch (err) {
870
- uncleared.push(`${join(this.memoryDir, "<scope homes>", MEMORY_INDEX_FILENAME)} (index sweep failed: ${thrownText(err)})`);
1099
+ uncleared.push(`${join(this.memoryDir, "<scope homes>", MEMORY_INDEX_FILENAME)} (index sweep failed: ${this.writeFailureText(err)})`);
871
1100
  }
872
1101
  if (uncleared.length > 0) {
873
1102
  try {
@@ -1168,12 +1397,23 @@ export class MemoryEngine {
1168
1397
  adoptRootScopeLayout(this.memoryDir, this.controlDir, { ownerCandidate: writeScope, onIncident: this.onIncident, convertSidecars: convertV1SidecarKeys });
1169
1398
  }
1170
1399
  async materialize(scopes, writeScope, opts) {
1400
+ const legSeat = {};
1401
+ try {
1402
+ return await this.materializeCore(scopes, writeScope, opts, legSeat);
1403
+ }
1404
+ catch (err) {
1405
+ if (legSeat.leg !== undefined && opts?.sessionId !== undefined)
1406
+ this.endLeg(opts.sessionId, legSeat.leg);
1407
+ throw withPermissionRemedy(err, this.memoryDir);
1408
+ }
1409
+ }
1410
+ async materializeCore(scopes, writeScope, opts, legSeat) {
1171
1411
  ensureDirExists(this.memoryDir);
1172
1412
  ensureDirExists(this.controlDir);
1173
1413
  await this.adoptLayoutBeforeMount(writeScope);
1174
- chmodNoFollow(this.memoryDir, 0o755);
1175
1414
  const restricted = opts?.adoptionRestricted === true;
1176
1415
  let sessionAccountFailed = false;
1416
+ let accountLeg;
1177
1417
  const readBackend = restricted
1178
1418
  ? (this.backend.restrictedAdoptionView?.({ audit: true, writeScope }) ?? this.backend)
1179
1419
  : this.backend;
@@ -1182,9 +1422,13 @@ export class MemoryEngine {
1182
1422
  scopeDirs.set(scope, registerScope(this.memoryDir, this.controlDir, scope));
1183
1423
  if (writeScope !== null && !scopeDirs.has(writeScope))
1184
1424
  scopeDirs.set(writeScope, registerScope(this.memoryDir, this.controlDir, writeScope));
1185
- const sweepAnnounced = new Set();
1186
1425
  for (const dir of scopeDirs.values())
1187
- this.chmodScopeTree(dir, 0o755, 0o644, { announced: sweepAnnounced });
1426
+ this.censusScopeTree(dir);
1427
+ const layerBaseline = this.backendPinnedRoot !== undefined && canonicalize(this.backendPinnedRoot) === canonicalize(this.memoryDir)
1428
+ ? readOnlyLayerCensus([...scopeDirs].filter(([scope]) => scope !== writeScope).map(([, dir]) => dir))
1429
+ : undefined;
1430
+ if (layerBaseline !== undefined)
1431
+ this.discloseUnsettleableObservations();
1188
1432
  if (this.provenance === "carry" && opts?.sessionId !== undefined && writeScope !== null && !restricted) {
1189
1433
  let accountFailed = false;
1190
1434
  try {
@@ -1300,10 +1544,19 @@ export class MemoryEngine {
1300
1544
  }
1301
1545
  }
1302
1546
  }
1303
- openSessionAccount(this.controlDir, { sessionId: opts.sessionId, now: this.now, unattributed });
1547
+ accountLeg = legSeat.leg = uuidv7();
1548
+ openSessionAccount(this.controlDir, { sessionId: opts.sessionId, now: this.now, unattributed, writeScope, leg: accountLeg });
1304
1549
  }
1305
1550
  catch (err) {
1306
1551
  accountFailed = true;
1552
+ if (accountLeg !== undefined) {
1553
+ try {
1554
+ if (sessionAccountLeg(this.controlDir, opts.sessionId) !== accountLeg)
1555
+ accountLeg = legSeat.leg = undefined;
1556
+ }
1557
+ catch {
1558
+ }
1559
+ }
1307
1560
  this.discloseSessionAccountIncident("crash-residue attribution is degraded for this session (the harvest boundary refuses fail-closed while this stands)", err);
1308
1561
  }
1309
1562
  if (accountFailed)
@@ -1334,6 +1587,8 @@ export class MemoryEngine {
1334
1587
  ...(opts?.shadowNames !== undefined ? { shadowNames: opts.shadowNames } : {}),
1335
1588
  ...(restricted ? { adoptionRestricted: true } : {}),
1336
1589
  ...(sessionAccountFailed ? { sessionAccountFailed: true } : {}),
1590
+ ...(layerBaseline !== undefined ? { layerBaseline } : {}),
1591
+ ...(accountLeg !== undefined ? { accountLeg } : {}),
1337
1592
  };
1338
1593
  if (handle.writableRoot !== null)
1339
1594
  ensureDirExists(handle.writableRoot);
@@ -1359,7 +1614,7 @@ export class MemoryEngine {
1359
1614
  const { deleted: _stubDrop, ...stubFm } = entry.frontmatter;
1360
1615
  const text = stub ? serializeEntryFile({ id: entry.id, frontmatter: stubFm, body: STUB_ARCHIVED_LINE }) : fullText;
1361
1616
  const target = join(dir, `${entry.slug}.md`);
1362
- if (!(zeroCopy && restricted))
1617
+ if (!zeroCopy)
1363
1618
  this.writeIfChanged(target, text);
1364
1619
  const onDiskRev = stub ? revOfText(text, entry.id) : entry.rev;
1365
1620
  const path = canonicalize(target);
@@ -1444,6 +1699,15 @@ export class MemoryEngine {
1444
1699
  catch {
1445
1700
  }
1446
1701
  }
1702
+ const indexRefusals = materializeHealWarnings.filter((w) => w.startsWith(INDEX_WRITE_REFUSED));
1703
+ if (indexRefusals.length > 0) {
1704
+ try {
1705
+ enqueueMemoryAnnouncement(this.controlDir, { kind: "gate", at: this.now(), items: capItems(indexRefusals.map((w) => inlineUntrusted(w, 500))) });
1706
+ }
1707
+ catch (err) {
1708
+ this.discloseAnnounceFailure("materialize index-write enqueue", err);
1709
+ }
1710
+ }
1447
1711
  if (indexWrite && handle.indexOnDiskUntrusted !== true) {
1448
1712
  const dropNotes = materializeHealWarnings.filter((w) => w.includes("opaque external-origin handle"));
1449
1713
  if (dropNotes.length > 0) {
@@ -1455,16 +1719,20 @@ export class MemoryEngine {
1455
1719
  }
1456
1720
  }
1457
1721
  }
1458
- for (const [scope, dir] of scopeDirs) {
1459
- if (scope === writeScope)
1460
- continue;
1461
- this.chmodScopeTree(dir, 0o555, 0o444, {
1462
- announced: sweepAnnounced,
1463
- });
1464
- }
1465
1722
  if (handle.writableRoot !== null) {
1466
1723
  const writableRoot = handle.writableRoot;
1467
1724
  const files = new Map();
1725
+ const refused = new Map();
1726
+ const probeWritable = (p) => {
1727
+ try {
1728
+ accessSync(p, fsConstants.W_OK);
1729
+ }
1730
+ catch (err) {
1731
+ if (err.code !== "ENOENT" && !refused.has(p))
1732
+ refused.set(p, err);
1733
+ }
1734
+ };
1735
+ probeWritable(writableRoot);
1468
1736
  for (const f of scanEntryFiles(writableRoot, { maxDepth: this.maxDepth })) {
1469
1737
  const canonical = canonicalize(f.path);
1470
1738
  if (canonical !== writableRoot && !canonical.startsWith(`${writableRoot}${sep}`))
@@ -1472,6 +1740,19 @@ export class MemoryEngine {
1472
1740
  const text = readSafe(canonical);
1473
1741
  if (text !== undefined)
1474
1742
  files.set(canonical, planeDigest(text));
1743
+ probeWritable(dirname(canonical));
1744
+ probeWritable(canonical);
1745
+ }
1746
+ const [firstRefused] = refused;
1747
+ if (firstRefused !== undefined) {
1748
+ const [path, err] = firstRefused;
1749
+ const remedy = permissionRemedyOf(Object.assign(new Error("write refused"), { code: err?.code, path }), this.memoryDir);
1750
+ try {
1751
+ enqueueMemoryAnnouncement(this.controlDir, { kind: "gate", at: this.now(), items: [inlineUntrusted(`this session's memory write home cannot be written at ${refused.size} path(s) (${path}${refused.size > 1 ? ", …" : ""}): ${remedy ?? thrownText(err)}`, 700)] });
1752
+ }
1753
+ catch (err) {
1754
+ this.discloseAnnounceFailure("write-home permission enqueue", err);
1755
+ }
1475
1756
  }
1476
1757
  const indexOnDisk = readSafe(join(writableRoot, MEMORY_INDEX_FILENAME));
1477
1758
  handle.planeBaseline = { files, ...(indexOnDisk !== undefined ? { index: indexOnDisk } : {}) };
@@ -1524,14 +1805,17 @@ export class MemoryEngine {
1524
1805
  };
1525
1806
  }
1526
1807
  gateWrite(handle, canonicalPath, content) {
1527
- const root = handle.writableRoot ?? handle.memoryDir;
1528
- if (canonicalPath !== root && !canonicalPath.startsWith(`${root}${sep}`))
1808
+ const inside = (root) => canonicalPath === root || canonicalPath.startsWith(`${root}${sep}`);
1809
+ if (!inside(handle.memoryDir))
1529
1810
  return { ok: true };
1530
- if (handle.writeScope === null) {
1811
+ const root = handle.writableRoot;
1812
+ if (handle.writeScope === null || root === null || !inside(root)) {
1531
1813
  return {
1532
1814
  ok: false,
1533
1815
  code: "read_only_layering",
1534
- reason: "this session's memory is read-only (no write scope) — the engine does not accept writes into the memory domain. Nothing was written.",
1816
+ reason: handle.writeScope === null || root === null
1817
+ ? "this session's memory is read-only (no write scope) — the engine does not accept writes into the memory domain. Nothing was written."
1818
+ : `this path is in a read-only inherited memory layer — this session writes memory only under ${root}. Nothing was written.`,
1535
1819
  muted: false,
1536
1820
  };
1537
1821
  }
@@ -1555,7 +1839,18 @@ export class MemoryEngine {
1555
1839
  return { ok: false, code: f.code, reason, muted };
1556
1840
  }
1557
1841
  async harvest(handle, opts) {
1558
- const report = await this.harvestCore(handle, opts);
1842
+ const out = {};
1843
+ try {
1844
+ out.report = await this.harvestCore(handle, opts);
1845
+ }
1846
+ catch (err) {
1847
+ throw withPermissionRemedy(err, this.memoryDir);
1848
+ }
1849
+ finally {
1850
+ if (opts?.phase === "terminal" && opts.sessionId !== undefined && this.endSessionLeg(handle, opts.sessionId) === "stale" && out.report !== undefined && !out.report.warnings.includes(STALE_LEG_NOTE))
1851
+ out.report.warnings.push(STALE_LEG_NOTE);
1852
+ }
1853
+ const report = out.report;
1559
1854
  try {
1560
1855
  const named = report.reportId !== undefined ? { reportId: report.reportId } : {};
1561
1856
  const gateItems = gateAnnouncementItems(report);
@@ -1606,7 +1901,16 @@ export class MemoryEngine {
1606
1901
  const writeScope = handle.writeScope;
1607
1902
  const captureRead = opts?.sessionId !== undefined ? await this.sessionCaptureOptOutOrFault(opts.sessionId) : { fault: false };
1608
1903
  const captureOptOut = captureRead.record;
1609
- if (writeScope === null || opts?.admitNothing !== undefined || captureOptOut !== undefined || captureRead.fault) {
1904
+ const admitsNothing = writeScope === null || opts?.admitNothing !== undefined || captureOptOut !== undefined || captureRead.fault;
1905
+ const readOnlyRestorable = await this.detectReadOnlyLayerChanges(handle, report, admitsNothing);
1906
+ let observed;
1907
+ try {
1908
+ observed = await this.observeReadOnlyLayers(handle, report, opts?.sessionId);
1909
+ }
1910
+ catch (err) {
1911
+ report.warnings.push(`read-only memory layers could not be examined for unexpected writes (nothing was recorded): ${this.writeFailureText(err)}`);
1912
+ }
1913
+ if (admitsNothing) {
1610
1914
  try {
1611
1915
  this.backend.auditRestrictedDivergence?.([...new Set([...handle.scopes, ...(writeScope !== null ? [writeScope] : [])])], (handle.adoptionRestricted === true || captureOptOut !== undefined) && writeScope !== null ? { idlessWriteScope: writeScope } : undefined);
1612
1916
  }
@@ -1627,9 +1931,10 @@ export class MemoryEngine {
1627
1931
  }
1628
1932
  }
1629
1933
  catch (err) {
1630
- report.warnings.push(`capture opt-out boundary sweep failed: ${thrownText(err)} — residue on the writable memory root may await a later session's adoption`);
1934
+ report.warnings.push(`capture opt-out boundary sweep failed: ${this.writeFailureText(err)} — residue on the writable memory root may await a later session's adoption`);
1631
1935
  }
1632
1936
  }
1937
+ this.concludeObservations(observed, report);
1633
1938
  if (opts?.admitNothing !== undefined)
1634
1939
  report.warnings.push(opts.admitNothing.reason);
1635
1940
  else if (captureOptOut !== undefined) {
@@ -1643,6 +1948,7 @@ export class MemoryEngine {
1643
1948
  if (carry && handle.sessionAccountFailed === true) {
1644
1949
  report.ok = false;
1645
1950
  report.incident = { kind: "sidecar_corrupt", detail: "memory session account could not be opened at materialize — the crash-residue classification for this session never persisted, so the harvest refuses fail-closed (repair the control plane and re-materialize)" };
1951
+ this.concludeObservations(observed, report);
1646
1952
  return report;
1647
1953
  }
1648
1954
  try {
@@ -1650,9 +1956,15 @@ export class MemoryEngine {
1650
1956
  }
1651
1957
  catch (err) {
1652
1958
  report.ok = false;
1653
- report.incident = { kind: "sidecar_corrupt", detail: `memory control plane refused: ${thrownText(err)}` };
1959
+ report.incident = { kind: "sidecar_corrupt", detail: `memory control plane refused: ${this.writeFailureText(err)}` };
1960
+ this.concludeObservations(observed, report);
1654
1961
  return report;
1655
1962
  }
1963
+ if (observed !== undefined && !observed.landed)
1964
+ this.landObservations(observed, report);
1965
+ if (observed !== undefined && !observed.landed)
1966
+ this.concludeObservations(observed, report);
1967
+ await this.settleUndecidedObservations(handle, report, landedUndecided(observed));
1656
1968
  try {
1657
1969
  if (carry) {
1658
1970
  reconcileHolds(this.controlDir, { homeOf: (scope) => scopeDirFor(this.memoryDir, this.controlDir, scope), now: this.now });
@@ -1834,7 +2146,7 @@ export class MemoryEngine {
1834
2146
  return true;
1835
2147
  }
1836
2148
  catch (err) {
1837
- report.warnings.push(`memory index could NOT be restored to its pre-session state: ${thrownText(err)}`);
2149
+ report.warnings.push(`memory index could NOT be restored to its pre-session state: ${this.writeFailureText(err)}`);
1838
2150
  return false;
1839
2151
  }
1840
2152
  };
@@ -1864,39 +2176,7 @@ export class MemoryEngine {
1864
2176
  signalContainment((c) => (c.indexRolledBack = true));
1865
2177
  report.warnings.push(`memory harvest refused by an incident while this session is marked externally exposed — no entries were committed; instruction-form files were withheld and the derived index rolled back: ${inlineUntrusted(pollutedReason, 200)}`);
1866
2178
  };
1867
- for (const m of handle.materialized.filter((x) => x.readonly)) {
1868
- const text = readSafe(m.path);
1869
- if (text === undefined) {
1870
- report.missing.push(m.relPath);
1871
- continue;
1872
- }
1873
- if (revOfText(text, m.id) !== m.rev) {
1874
- report.rejections.push({ path: m.relPath, code: "readonly_layer", reason: "a read-only inherited memory layer was modified; changes to inherited layers are not writable from this session" });
1875
- const committed = await this.committedContentFor(m.id);
1876
- if (committed !== undefined) {
1877
- let fd;
1878
- try {
1879
- fd = openNoFollowForMode(m.path);
1880
- if (fd !== undefined)
1881
- fchmodSync(fd, 0o644);
1882
- writeFileNoFollow(m.path, committed);
1883
- report.restored.push(m.relPath);
1884
- }
1885
- catch {
1886
- }
1887
- finally {
1888
- if (fd !== undefined) {
1889
- try {
1890
- fchmodSync(fd, 0o444);
1891
- }
1892
- catch {
1893
- }
1894
- closeSync(fd);
1895
- }
1896
- }
1897
- }
1898
- }
1899
- }
2179
+ await this.restoreReadOnlyLayers(readOnlyRestorable, report, handle.accountLeg);
1900
2180
  const missingWritable = writable.filter((m) => !recordByPath.has(m.path) && !existsSync(m.path) && !presentIds.has(m.id));
1901
2181
  report.missing.push(...missingWritable.map((m) => m.relPath));
1902
2182
  if (writable.length > 0 && missingWritable.length / writable.length > MASS_DELETION_FUSE_RATIO) {
@@ -1927,7 +2207,8 @@ export class MemoryEngine {
1927
2207
  writeFileNoFollow(m.path, content);
1928
2208
  report.restored.push(m.relPath);
1929
2209
  }
1930
- catch {
2210
+ catch (err) {
2211
+ report.warnings.push(`${m.relPath}: the committed copy of this missing memory file could NOT be restored: ${this.writeFailureText(err)}`);
1931
2212
  }
1932
2213
  }
1933
2214
  const baselinePaths = new Set(writable.map((m) => m.path));
@@ -1964,6 +2245,8 @@ export class MemoryEngine {
1964
2245
  const pendingProjections = [];
1965
2246
  const idsAddedThisHarvest = new Set();
1966
2247
  const modelIdAdds = new Set();
2248
+ const stoodAsFound = new Set();
2249
+ let observedTable;
1967
2250
  let processed = 0;
1968
2251
  for (let i = 0; i < kept.length; i++) {
1969
2252
  const f = kept[i];
@@ -1980,10 +2263,14 @@ export class MemoryEngine {
1980
2263
  continue;
1981
2264
  }
1982
2265
  const fastBaseId = idByBasePath.get(f.canonical);
2266
+ if (fastBaseId !== undefined && !stubByPath.has(f.canonical) && revOfText(f.text, fastBaseId) !== revByBasePath.get(f.canonical) && handle.planeBaseline?.files.get(f.canonical) === planeDigest(f.text)) {
2267
+ stoodAsFound.add(rel);
2268
+ continue;
2269
+ }
1983
2270
  if (fastBaseId !== undefined && !stubByPath.has(f.canonical) && revOfText(f.text, fastBaseId) === revByBasePath.get(f.canonical)) {
1984
2271
  const fastDebt = projectionDebts.get(debtSeat(writeScope, homeRel(writableRoot, f.canonical)));
1985
2272
  if (fastDebt !== undefined && fastDebt.entryId === fastBaseId)
1986
- await this.repairProjectionSeat(seatOf(f.canonical), f.canonical, fastBaseId, debtClears);
2273
+ await this.repairProjectionSeat(seatOf(f.canonical), f.canonical, fastBaseId, debtClears, report.warnings);
1987
2274
  continue;
1988
2275
  }
1989
2276
  processed++;
@@ -2043,6 +2330,21 @@ export class MemoryEngine {
2043
2330
  const parsed = f.parsed;
2044
2331
  let baseId = idByBasePath.get(f.canonical);
2045
2332
  let id = baseId ?? parsed.id;
2333
+ if (baseId === undefined && handle.planeBaseline?.files.get(f.canonical) === planeDigest(f.text)) {
2334
+ const observed = (observedTable ??= layerObservationLookup(this.controlDir));
2335
+ const committedRow = parsed.id !== undefined && observed.covers(parsed.id) ? await this.committedRowOf(parsed.id) : undefined;
2336
+ if (committedRow === "undecidable") {
2337
+ report.rejections.push({ path: rel, code: "deferred", reason: "memory write deferred: this file's id is named by the read-only-layer observation table and the committed account could not be read whole — retried next harvest" });
2338
+ continue;
2339
+ }
2340
+ const recorded = parsed.id === undefined
2341
+ ? observed.findNote(writeScope, homeRel(writableRoot, f.canonical), planeDigest(f.text)) !== undefined
2342
+ : observed.covers(parsed.id) && observed.find(parsed.id, committedRow?.rev, revOfText(f.text, parsed.id)) !== undefined;
2343
+ if (recorded) {
2344
+ report.rejections.push({ path: rel, code: "readonly_layer", reason: "this file appeared while a session held this memory layer read-only (recorded as an unexpected write) — it is not committed as this session's; remove it, or edit it to commit this session's own version" });
2345
+ continue;
2346
+ }
2347
+ }
2046
2348
  let debtReconciled = false;
2047
2349
  if (id === undefined && projectionDebts.size > 0) {
2048
2350
  const debt = projectionDebts.get(debtSeat(writeScope, homeRel(writableRoot, f.canonical)));
@@ -2129,7 +2431,7 @@ export class MemoryEngine {
2129
2431
  report.restored.push(rel);
2130
2432
  }
2131
2433
  catch (err) {
2132
- report.warnings.push(`${rel}: the committed content could not be restored after the hold captured this session's edit — the entry is unavailable on the plane until release or the next restore: ${thrownText(err)}`);
2434
+ report.warnings.push(`${rel}: the committed content could not be restored after the hold captured this session's edit — the entry is unavailable on the plane until release or the next restore: ${this.writeFailureText(err)}`);
2133
2435
  }
2134
2436
  }
2135
2437
  }
@@ -2176,7 +2478,7 @@ export class MemoryEngine {
2176
2478
  if (revOfText(f.text, id) === revByBasePath.get(f.canonical)) {
2177
2479
  const seatDebt = projectionDebts.get(debtSeat(writeScope, homeRel(writableRoot, f.canonical)));
2178
2480
  if (seatDebt !== undefined && seatDebt.entryId === id)
2179
- await this.repairProjectionSeat(seatOf(f.canonical), f.canonical, id, debtClears);
2481
+ await this.repairProjectionSeat(seatOf(f.canonical), f.canonical, id, debtClears, report.warnings);
2180
2482
  continue;
2181
2483
  }
2182
2484
  pendingProjections.push({ path: f.canonical, entry, needed: minted || parsed.id !== id || needsCompletion(parsed, fm), ...(debtReconciled ? { idCompletion: true } : {}) });
@@ -2200,7 +2502,7 @@ export class MemoryEngine {
2200
2502
  report.rejections.push({
2201
2503
  path: rel,
2202
2504
  code: "filename",
2203
- reason: `memory write blocked: this file carries the entry id of another memory file (a copy is not a rename) and its frontmatter could not be rewritten (${thrownText(err)}) — remove the "id:" line so it can be committed as a new entry`,
2505
+ reason: `memory write blocked: this file carries the entry id of another memory file (a copy is not a rename) and its frontmatter could not be rewritten (${this.writeFailureText(err)}) — remove the "id:" line so it can be committed as a new entry`,
2204
2506
  });
2205
2507
  continue;
2206
2508
  }
@@ -2219,7 +2521,7 @@ export class MemoryEngine {
2219
2521
  report.rejections.push({
2220
2522
  path: rel,
2221
2523
  code: "filename",
2222
- reason: `memory write blocked: an external-origin commit does not adopt the file's model-written entry id and its frontmatter could not be rewritten (${thrownText(err)}) — remove the "id:" line so it can be committed under an engine-minted id`,
2524
+ reason: `memory write blocked: an external-origin commit does not adopt the file's model-written entry id and its frontmatter could not be rewritten (${this.writeFailureText(err)}) — remove the "id:" line so it can be committed under an engine-minted id`,
2223
2525
  });
2224
2526
  continue;
2225
2527
  }
@@ -2341,7 +2643,7 @@ export class MemoryEngine {
2341
2643
  report.rejections.push({
2342
2644
  path: flipRec.rel,
2343
2645
  code: "filename",
2344
- reason: `memory write blocked: an external-origin commit does not adopt the file's model-written entry id and its frontmatter could not be rewritten (${thrownText(err)}) — remove the "id:" line so it can be committed under an engine-minted id`,
2646
+ reason: `memory write blocked: an external-origin commit does not adopt the file's model-written entry id and its frontmatter could not be rewritten (${this.writeFailureText(err)}) — remove the "id:" line so it can be committed under an engine-minted id`,
2345
2647
  });
2346
2648
  droppedIds.add(p.id);
2347
2649
  continue;
@@ -2458,7 +2760,7 @@ export class MemoryEngine {
2458
2760
  }
2459
2761
  }
2460
2762
  report.ok = false;
2461
- report.incident = { kind: "sidecar_corrupt", detail: `memory commit refused: ${thrownText(err)}` };
2763
+ report.incident = { kind: "sidecar_corrupt", detail: `memory commit refused: ${this.writeFailureText(err)}` };
2462
2764
  return report;
2463
2765
  }
2464
2766
  if (lineageArmed) {
@@ -2514,18 +2816,25 @@ export class MemoryEngine {
2514
2816
  }
2515
2817
  }
2516
2818
  const appliedIds = new Set(patchReport.applied.filter((a) => a.op !== "delete").map((a) => a.id));
2819
+ const planeAfterFailedWriteBack = this.backendPinnedRoot !== undefined && canonicalize(this.backendPinnedRoot) === canonicalize(this.memoryDir)
2820
+ ? "this plane is the store itself: the next read adopts them as an out-of-session edit unless the committed copy is put back first"
2821
+ : "the next materialize re-projects the committed copy over them";
2517
2822
  for (const p of pendingProjections) {
2518
2823
  const pRel = relative(handle.memoryDir, p.path);
2519
2824
  const pSeat = seatOf(p.path);
2520
2825
  if (appliedIds.has(p.entry.id)) {
2521
- const wrote = this.writeBackProjection(p.path, p.entry, p.needed);
2522
- if (p.idCompletion === true) {
2523
- if (wrote)
2826
+ const writeBackFailure = this.writeBackProjection(p.path, p.entry, p.needed);
2827
+ if (p.idCompletion !== true) {
2828
+ if (writeBackFailure !== undefined)
2829
+ report.warnings.push(`${pRel}: the entry committed but its projection write-back FAILED — the plane keeps this session's bytes (${planeAfterFailedWriteBack}): ${writeBackFailure}`);
2830
+ }
2831
+ else {
2832
+ if (writeBackFailure === undefined)
2524
2833
  debtClears.push({ ...pSeat, entryId: p.entry.id });
2525
2834
  else {
2526
2835
  const landed = patchReport.applied.find((a) => a.id === p.entry.id)?.slug;
2527
2836
  const suffixNote = landed !== undefined && landed !== p.entry.slug ? ` NOTE: the entry landed at slug ${JSON.stringify(landed)} (collision suffix) — the seat claim will not re-bind; host reconciliation may be needed.` : "";
2528
- report.warnings.push(`${pRel}: the entry committed but its id write-back FAILED — the projection-debt row stands, so the file re-binds to its committed id at the next harvest (and the next materialize repairs the plane); it is never re-admitted as a duplicate.${suffixNote}`);
2837
+ report.warnings.push(`${pRel}: the entry committed but its id write-back FAILED — the projection-debt row stands, so the file re-binds to its committed id at the next harvest (and the next materialize repairs the plane); it is never re-admitted as a duplicate.${suffixNote} Cause: ${writeBackFailure}`);
2529
2838
  }
2530
2839
  }
2531
2840
  continue;
@@ -2538,7 +2847,8 @@ export class MemoryEngine {
2538
2847
  writeFileNoFollow(p.path, committed);
2539
2848
  planeHoldsCommitted = true;
2540
2849
  }
2541
- catch {
2850
+ catch (err) {
2851
+ report.warnings.push(`${pRel}: this session's version lost to a concurrent commit and the committed copy could NOT be written back over it — the plane keeps this session's bytes (${planeAfterFailedWriteBack}): ${this.writeFailureText(err)}`);
2542
2852
  }
2543
2853
  }
2544
2854
  else if (committed !== undefined) {
@@ -2587,15 +2897,17 @@ export class MemoryEngine {
2587
2897
  }
2588
2898
  const headers = await this.backend.listHeaders([...new Set([...handle.scopes, writeScope])]);
2589
2899
  handle.indexText = this.rebuildIndex(handle, headers, { write: true, ignoreOnDisk: indexGate !== undefined }, report.warnings);
2590
- await this.rebaseline(handle, new Set(report.degraded?.pending ?? []), new Set(report.rejections.map((r) => r.path)));
2900
+ await this.rebaseline(handle, new Set([...(report.degraded?.pending ?? []), ...stoodAsFound]), new Set(report.rejections.map((r) => r.path)));
2591
2901
  if (carry && lineageSessionId !== undefined && report.degraded === undefined) {
2592
2902
  const deferredSeats = [...new Set(report.rejections.filter((r) => r.code === "deferred").map((r) => r.path))];
2593
2903
  const shown = deferredSeats.slice(0, MAX_DISCLOSED_DEFERRED_SEATS);
2594
2904
  const rest = deferredSeats.length - shown.length;
2595
2905
  const seatList = `${shown.join(", ")}${rest > 0 ? ` (+${rest} more — see the rejections)` : ""}`;
2596
2906
  try {
2597
- const closed = closeSessionAccount(this.controlDir, { sessionId: lineageSessionId, now: this.now });
2598
- if (closed && deferredSeats.length > 0) {
2907
+ const closed = handle.accountLeg !== undefined ? closeSessionAccount(this.controlDir, { sessionId: lineageSessionId, now: this.now, leg: handle.accountLeg }) : "absent";
2908
+ if (closed === "stale" && !report.warnings.includes(STALE_LEG_NOTE))
2909
+ report.warnings.push(STALE_LEG_NOTE);
2910
+ if (closed === "written" && deferredSeats.length > 0) {
2599
2911
  report.warnings.push(`memory session account closed with ${deferredSeats.length} file(s) still DEFERRED on the model-visible plane — their projection-debt rows STAND and are re-judged by the next harvest's consult, not by this close: ${seatList}`);
2600
2912
  }
2601
2913
  }
@@ -4288,6 +4600,8 @@ export class MemoryEngine {
4288
4600
  const id = parsed.id ?? handle.baseIds.get(path);
4289
4601
  if (id === undefined)
4290
4602
  continue;
4603
+ if (uncommitted && handle.baseIds.get(path) === undefined)
4604
+ continue;
4291
4605
  const stub = stubs.get(path);
4292
4606
  const priorRev = deferred ? handle.baseRevs.get(path) : undefined;
4293
4607
  const priorId = deferred ? handle.baseIds.get(path) : undefined;
@@ -4509,8 +4823,9 @@ export class MemoryEngine {
4509
4823
  writeFileNoFollow(indexPath, text);
4510
4824
  wrote = true;
4511
4825
  }
4512
- catch {
4826
+ catch (err) {
4513
4827
  handle.indexOnDiskUntrusted = true;
4828
+ warnings?.push(`${INDEX_WRITE_REFUSED}: ${this.writeFailureText(err)}`);
4514
4829
  }
4515
4830
  if (wrote)
4516
4831
  writeIndexRevs(this.controlDir, nextIndexRevs);
@@ -4597,7 +4912,7 @@ export class MemoryEngine {
4597
4912
  cleared = true;
4598
4913
  }
4599
4914
  catch (err) {
4600
- detail = `${detail}; the derived index could NOT be cleared: ${thrownText(err)}`;
4915
+ detail = `${detail}; the derived index could NOT be cleared: ${this.writeFailureText(err)}`;
4601
4916
  }
4602
4917
  const outcome = [
4603
4918
  captured ? "the offending prose was captured to quarantine" : "the quarantine capture FAILED — the prose was not preserved",
@@ -4621,19 +4936,19 @@ export class MemoryEngine {
4621
4936
  }
4622
4937
  writeBackProjection(path, entry, needed) {
4623
4938
  if (!needed)
4624
- return true;
4939
+ return undefined;
4625
4940
  const text = serializeEntryFile(entry);
4626
4941
  if (readNoFollowSafe(path) === text)
4627
- return true;
4942
+ return undefined;
4628
4943
  try {
4629
4944
  writeFileNoFollow(path, text);
4630
- return true;
4945
+ return undefined;
4631
4946
  }
4632
- catch {
4633
- return false;
4947
+ catch (err) {
4948
+ return this.writeFailureText(err);
4634
4949
  }
4635
4950
  }
4636
- async repairProjectionSeat(seat, canonical, entryId, debtClears) {
4951
+ async repairProjectionSeat(seat, canonical, entryId, debtClears, warnings) {
4637
4952
  const committed = await this.committedContentFor(entryId);
4638
4953
  if (committed === undefined)
4639
4954
  return;
@@ -4641,7 +4956,8 @@ export class MemoryEngine {
4641
4956
  try {
4642
4957
  writeFileNoFollow(canonical, committed);
4643
4958
  }
4644
- catch {
4959
+ catch (err) {
4960
+ warnings.push(`${relative(this.memoryDir, canonical)}: the committed copy could NOT be written over this id-less seat — the projection-debt row stands and the repair retries next pass: ${this.writeFailureText(err)}`);
4645
4961
  return;
4646
4962
  }
4647
4963
  }
@@ -4662,18 +4978,18 @@ export class MemoryEngine {
4662
4978
  return { state: "unknown" };
4663
4979
  }
4664
4980
  }
4665
- chmodScopeTree(dir, dirMode, fileMode, opts = {}) {
4981
+ censusScopeTree(dir) {
4666
4982
  if (!existsSync(dir))
4667
4983
  return;
4668
- const sweepSkipped = [];
4669
- const sweptLinks = [];
4984
+ const unreadable = [];
4985
+ const links = [];
4670
4986
  const walk = (d) => {
4671
4987
  let names = [];
4672
4988
  try {
4673
4989
  names = readdirSync(d);
4674
4990
  }
4675
4991
  catch {
4676
- sweepSkipped.push(d);
4992
+ unreadable.push(d);
4677
4993
  return;
4678
4994
  }
4679
4995
  for (const name of names) {
@@ -4683,50 +4999,34 @@ export class MemoryEngine {
4683
4999
  st = lstatSync(p);
4684
5000
  }
4685
5001
  catch {
4686
- sweepSkipped.push(p);
4687
- continue;
4688
- }
4689
- if (st.isSymbolicLink()) {
4690
- sweptLinks.push(p);
5002
+ unreadable.push(p);
4691
5003
  continue;
4692
5004
  }
4693
- if (st.isDirectory())
5005
+ if (st.isSymbolicLink())
5006
+ links.push(p);
5007
+ else if (st.isDirectory())
4694
5008
  walk(p);
4695
- else if (st.isFile() && name.endsWith(".md") && name !== MEMORY_INDEX_FILENAME)
4696
- chmodNoFollow(p, fileMode);
4697
5009
  }
4698
- chmodNoFollow(d, dirMode);
4699
5010
  };
4700
5011
  walk(dir);
4701
- const said = opts.announced;
4702
- const anomalyKey = (kind, path) => JSON.stringify([kind, path]);
4703
- const unsaid = (paths, kind) => (said === undefined ? paths : paths.filter((p) => !said.has(anomalyKey(kind, p))));
4704
- const skipped = unsaid(sweepSkipped, "unreadable");
4705
- const links = unsaid(sweptLinks, "symlink");
4706
- if (skipped.length + links.length > 0) {
4707
- try {
4708
- enqueueMemoryAnnouncement(this.controlDir, {
4709
- kind: "gate",
4710
- at: this.now(),
4711
- items: skipped
4712
- .slice(0, 12)
4713
- .map((p) => `mode sweep could not read ${JSON.stringify(inlineUntrusted(relative(dir, p) || "."))} — files under it keep their previous mode`)
4714
- .concat(skipped.length > 12 ? [`…and ${skipped.length - 12} more sweepSkipped path(s)`] : [])
4715
- .concat(links
4716
- .slice(0, 12)
4717
- .map((p) => `mode sweep skipped the symlink ${JSON.stringify(inlineUntrusted(relative(dir, p) || "."))} — links are not part of a memory scope tree and are never followed`))
4718
- .concat(links.length > 12 ? [`…and ${links.length - 12} more skipped symlink(s)`] : []),
4719
- });
4720
- if (said !== undefined) {
4721
- for (const p of skipped)
4722
- said.add(anomalyKey("unreadable", p));
4723
- for (const p of links)
4724
- said.add(anomalyKey("symlink", p));
4725
- }
4726
- }
4727
- catch (err) {
4728
- this.discloseAnnounceFailure("mode-sweep enqueue", err);
4729
- }
5012
+ if (unreadable.length + links.length === 0)
5013
+ return;
5014
+ try {
5015
+ enqueueMemoryAnnouncement(this.controlDir, {
5016
+ kind: "gate",
5017
+ at: this.now(),
5018
+ items: unreadable
5019
+ .slice(0, 12)
5020
+ .map((p) => `layer census could not read ${JSON.stringify(inlineUntrusted(relative(dir, p) || "."))} — nothing under it was examined`)
5021
+ .concat(unreadable.length > 12 ? [`…and ${unreadable.length - 12} more unreadable path(s)`] : [])
5022
+ .concat(links
5023
+ .slice(0, 12)
5024
+ .map((p) => `layer census skipped the symlink ${JSON.stringify(inlineUntrusted(relative(dir, p) || "."))} — links are not part of a memory scope tree and are never followed`))
5025
+ .concat(links.length > 12 ? [`…and ${links.length - 12} more skipped symlink(s)`] : []),
5026
+ });
5027
+ }
5028
+ catch (err) {
5029
+ this.discloseAnnounceFailure("layer-census enqueue", err);
4730
5030
  }
4731
5031
  }
4732
5032
  }
@@ -4813,16 +5113,6 @@ function readSafe(path) {
4813
5113
  return undefined;
4814
5114
  }
4815
5115
  }
4816
- function openNoFollowForMode(path) {
4817
- for (const flags of [fsConstants.O_RDONLY, fsConstants.O_WRONLY]) {
4818
- try {
4819
- return openSync(path, flags | fsConstants.O_NOFOLLOW);
4820
- }
4821
- catch {
4822
- }
4823
- }
4824
- return undefined;
4825
- }
4826
5116
  function readNoFollowSafe(path) {
4827
5117
  let fd;
4828
5118
  try {
@@ -4837,19 +5127,6 @@ function readNoFollowSafe(path) {
4837
5127
  closeSync(fd);
4838
5128
  }
4839
5129
  }
4840
- function chmodNoFollow(path, mode) {
4841
- const fd = openNoFollowForMode(path);
4842
- if (fd === undefined)
4843
- return;
4844
- try {
4845
- fchmodSync(fd, mode);
4846
- }
4847
- catch {
4848
- }
4849
- finally {
4850
- closeSync(fd);
4851
- }
4852
- }
4853
5130
  function needsCompletion(parsed, fm) {
4854
5131
  return (!parsed.hadFrontmatter ||
4855
5132
  parsed.frontmatter.name !== fm.name ||