@amalgm/shell 0.1.54 → 0.1.55

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.
@@ -3,9 +3,10 @@ import { existsSync, lstatSync, realpathSync, readFileSync, readdirSync, readlin
3
3
  import { open } from "node:fs/promises";
4
4
  import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { buildUserHomeManifest, buildUserManifest, liveMachineStateDir, scopedAmalgmDir, shippedUserHomeDeclaration, } from "@amalgm/core/identity";
6
- import { CHUNK_BYTES, CONTENT_CONTRACT, ENTITY_CLOUD_CONTRACT, ENTITY_CLOUD_SCHEMA_VERSION, artifactForRecord, canonicalVersion, checkContentManifest, classifyDirectory, classifyFile, classifyRegisteredRoot, createLocalEntityRecord, createEntityRecordAuthorityPort, convergeUserGround, createUserGroundEnrollmentPolicy, isRepositoryMetadataEntry, download as downloadArtifact, encodeEntityRecord, EntityRecordRail, membershipHash, parseSnapshot, pathIsSuspect, privateEntityResourceId, sameRecords, snapshotFromRecords, stableJson, travelingRecords, upload as uploadArtifact, userGroundRecords, } from "@amalgm/live";
6
+ import { CHUNK_BYTES, CONTENT_CONTRACT, ENTITY_CLOUD_CONTRACT, ENTITY_CLOUD_SCHEMA_VERSION, artifactForRecord, canonicalVersion, checkContentManifest, classifyDirectory, classifyFile, classifyRegisteredRoot, createLocalEntityRecord, createEntityRecordAuthorityPort, convergeUserGround, createUserGroundEnrollmentPolicy, isRepositoryMetadataEntry, download as downloadArtifact, encodeEntityRecord, EntityApplyRail, EntityRecordRail, membershipHash, parseSnapshot, pathIsSuspect, privateEntityResourceId, sameRecords, snapshotFromRecords, stableJson, travelingRecords, upload as uploadArtifact, userGroundRecords, } from "@amalgm/live";
7
7
  import Database from "better-sqlite3";
8
8
  import { atomicCopy, atomicWrite, ensurePrivateDir } from "./filesystem.js";
9
+ import { EntityApplyHost } from "./entity-apply-host.js";
9
10
  import { EntityRecordSqliteStore } from "./entity-record-store.js";
10
11
  import { ContentCacheDownload, captureContentFile, hashContentFile, sealCapturedArtifact, } from "./content-cache-host.js";
11
12
  import { decodeContentWireBytes, encodeContentWireBytes } from "./content-wire-codec.js";
@@ -13,7 +14,7 @@ import { exactTextReplay, planGroundDetection, reconcileGroundUUIDs, } from "./d
13
14
  import { NamedDetectRuntime } from "./detection/runtime.js";
14
15
  import { WORKSPACE_UUID as UUID, createFilesRegisterPorts, ensureWorkspaceBinding, ensureWorkspaceReference, pathExists, pathWithin, referenceWorkspaceId, selectKnownRegistrationId, workspaceBindingDir, } from "./files-register-host.js";
15
16
  import { applyRepositoryFiles, captureRepository, inspectRepositoryTransportFile, hasGitMarker, } from "./git-repository-host.js";
16
- import { inspectGitRegistration, } from "./git-registration-host.js";
17
+ import { canonicalizeGitIdentity, inspectGitRegistration, sameGitIdentity, } from "./git-registration-host.js";
17
18
  import { projectMaterializedGraph } from "./materialized-graph.js";
18
19
  import { NodeWatchHost, } from "./watching/index.js";
19
20
  import { WireClient, WireRequestError } from "./wire-client.js";
@@ -44,9 +45,12 @@ export class UserGroundHost {
44
45
  syncing = null;
45
46
  recordRail = null;
46
47
  recordStore = null;
48
+ applyRail = null;
49
+ applyHost = null;
47
50
  namedDetect = null;
48
51
  coldOperation = false;
49
52
  closing = false;
53
+ groundEffectTail = Promise.resolve();
50
54
  port;
51
55
  constructor(options) {
52
56
  this.options = options;
@@ -88,6 +92,7 @@ export class UserGroundHost {
88
92
  watching: true,
89
93
  };
90
94
  await this.startRecordRail(this.activeIdentity);
95
+ await this.startApplyRail(this.activeIdentity);
91
96
  this.converged = true;
92
97
  return evidence;
93
98
  },
@@ -148,26 +153,38 @@ export class UserGroundHost {
148
153
  return cloud;
149
154
  },
150
155
  materializeWorkspace: async (input) => {
151
- const installed = await installCloudWorkspace({
152
- identity,
153
- userRoot,
154
- database: this.databasePath(identity),
155
- cacheDir: this.cacheDir(identity),
156
- bindingDir,
157
- resourceId: privateEntityResourceId(identity.userId, sha256Hex),
158
- workspace: input.workspace,
159
- reference: input.reference,
160
- references: input.references,
161
- records: input.records,
162
- destinationParent: input.destinationParent,
163
- cloudHead: input.cloudHead,
164
- readContent: (artifact) => this.downloadContent(privateEntityResourceId(identity.userId, sha256Hex), this.cacheDir(identity), artifact),
165
- onStage: (stage) => this.options.onWorkspaceAddStage?.({
166
- workspaceId: input.workspace.uuid,
167
- stage,
168
- }),
169
- });
170
- return installed;
156
+ const release = await this.acquireGroundEffect();
157
+ try {
158
+ const installed = await installCloudWorkspace({
159
+ identity,
160
+ userRoot,
161
+ database: this.databasePath(identity),
162
+ cacheDir: this.cacheDir(identity),
163
+ bindingDir,
164
+ resourceId: privateEntityResourceId(identity.userId, sha256Hex),
165
+ workspace: input.workspace,
166
+ reference: input.reference,
167
+ references: input.references,
168
+ records: input.records,
169
+ destinationParent: input.destinationParent,
170
+ cloudHead: input.cloudHead,
171
+ readContent: (artifact) => this.downloadContent(privateEntityResourceId(identity.userId, sha256Hex), this.cacheDir(identity), artifact),
172
+ onStage: (stage) => this.options.onWorkspaceAddStage?.({
173
+ workspaceId: input.workspace.uuid,
174
+ stage,
175
+ }),
176
+ });
177
+ // Add changes both disk and the exact SQLite projection. Open the
178
+ // selected root's singular Watch coverage before Detect can observe
179
+ // either side of that transaction; the public cover port below
180
+ // independently proves the same coverage before clearing intent.
181
+ this.ensureWatchers(identity, installed.alreadyMaterialized ? [] : [input.workspace.uuid]);
182
+ assertHealthyWatch(this.watchHost.evidence(), input.workspace.uuid);
183
+ return installed;
184
+ }
185
+ finally {
186
+ release();
187
+ }
171
188
  },
172
189
  coverWorkspace: async ({ workspaceId, baseline }) => {
173
190
  const pending = readWorkspaceAddIntent(this.databasePath(identity), workspaceId);
@@ -189,37 +206,43 @@ export class UserGroundHost {
189
206
  const userRoot = this.userRoot(identity);
190
207
  const bindingDir = workspaceBindingDir(userRoot, identity.deviceId);
191
208
  for (const intent of readWorkspaceAddIntents(database)) {
192
- const selection = workspaceAddSelection(intent);
209
+ const release = await this.acquireGroundEffect();
193
210
  const visibleAcrossBlindInterval = pathExists(intent.destinationPath);
194
- const stagedAcrossBlindInterval = pathExists(intent.stagingPath);
195
- if (stagedAcrossBlindInterval && readRows(database).some((row) => row.resourceId === intent.resourceId && row.rootUUID === intent.workspaceId)) {
196
- // Rows prove that staging was verified before the crash, not that its
197
- // bytes remained unchanged while no watcher existed. Return this
198
- // private install to the intent-only stage and reproduce it from the
199
- // verified immutable cache before making it visible.
200
- deleteRootRows(database, intent.resourceId, intent.workspaceId);
211
+ try {
212
+ const selection = workspaceAddSelection(intent);
213
+ const stagedAcrossBlindInterval = pathExists(intent.stagingPath);
214
+ if (stagedAcrossBlindInterval && readRows(database).some((row) => row.resourceId === intent.resourceId && row.rootUUID === intent.workspaceId)) {
215
+ // Rows prove that staging was verified before the crash, not that its
216
+ // bytes remained unchanged while no watcher existed. Return this
217
+ // private install to the intent-only stage and reproduce it from the
218
+ // verified immutable cache before making it visible.
219
+ deleteRootRows(database, intent.resourceId, intent.workspaceId);
220
+ }
221
+ await installCloudWorkspace({
222
+ identity,
223
+ userRoot,
224
+ database,
225
+ cacheDir: this.cacheDir(identity),
226
+ bindingDir,
227
+ resourceId: intent.resourceId,
228
+ workspace: selection.workspace,
229
+ reference: selection.reference,
230
+ references: intent.references,
231
+ records: intent.records,
232
+ destinationParent: dirname(intent.destinationPath),
233
+ cloudHead: intent.cloudHead,
234
+ readContent: (artifact) => this.downloadContent(intent.resourceId, this.cacheDir(identity), artifact),
235
+ onStage: (stage) => this.options.onWorkspaceAddStage?.({
236
+ workspaceId: intent.workspaceId,
237
+ stage,
238
+ }),
239
+ });
240
+ this.ensureWatchers(identity, visibleAcrossBlindInterval ? [] : [intent.workspaceId]);
241
+ assertHealthyWatch(this.watchHost.evidence(), intent.workspaceId);
242
+ }
243
+ finally {
244
+ release();
201
245
  }
202
- await installCloudWorkspace({
203
- identity,
204
- userRoot,
205
- database,
206
- cacheDir: this.cacheDir(identity),
207
- bindingDir,
208
- resourceId: intent.resourceId,
209
- workspace: selection.workspace,
210
- reference: selection.reference,
211
- references: intent.references,
212
- records: intent.records,
213
- destinationParent: dirname(intent.destinationPath),
214
- cloudHead: intent.cloudHead,
215
- readContent: (artifact) => this.downloadContent(intent.resourceId, this.cacheDir(identity), artifact),
216
- onStage: (stage) => this.options.onWorkspaceAddStage?.({
217
- workspaceId: intent.workspaceId,
218
- stage,
219
- }),
220
- });
221
- this.ensureWatchers(identity, visibleAcrossBlindInterval ? [] : [intent.workspaceId]);
222
- assertHealthyWatch(this.watchHost.evidence(), intent.workspaceId);
223
246
  if (visibleAcrossBlindInterval) {
224
247
  // Ground that was visible while the runtime was absent may have been
225
248
  // edited after its verified reveal. It is an ordinary startup blind
@@ -258,8 +281,12 @@ export class UserGroundHost {
258
281
  throw stageError("Watch coverage", error);
259
282
  }
260
283
  let rows = readRows(this.databasePath(identity));
261
- if (rows.some((row) => row.uuid === workspaceId)
262
- && this.cloudState.records.some((record) => record.uuid === workspaceId)) {
284
+ const hasRegisteredBoundary = (records) => records.some((record) => record.uuid === workspaceId && record.status === "active")
285
+ && records.some((record) => record.type === "reference"
286
+ && record.status === "active"
287
+ && record.payloadVersion === workspaceId);
288
+ if (hasRegisteredBoundary(rows.map(portableRecord))
289
+ && hasRegisteredBoundary(this.cloudState.records)) {
263
290
  this.finishColdOperation();
264
291
  return {
265
292
  registered: true,
@@ -271,11 +298,13 @@ export class UserGroundHost {
271
298
  // Register is a cold declaration, not a filesystem edit. It performs one
272
299
  // complete reconciliation and publishes one graph head; it must not mint
273
300
  // a parallel stream of Detect records for the same initial contents.
274
- const observations = this.watchHost.observations();
275
- this.watchDirty = false;
301
+ let releaseGround = null;
276
302
  try {
277
303
  if (this.syncing)
278
304
  await this.syncing;
305
+ releaseGround = await this.acquireGroundEffect();
306
+ const observations = this.watchHost.observations();
307
+ this.watchDirty = false;
279
308
  const scanned = await reconcileGround({
280
309
  identity,
281
310
  userRoot: this.userRoot(identity),
@@ -306,6 +335,7 @@ export class UserGroundHost {
306
335
  throw stageError("entity registration or cloud publication", error);
307
336
  }
308
337
  finally {
338
+ releaseGround?.();
309
339
  this.finishColdOperation();
310
340
  }
311
341
  try {
@@ -317,11 +347,11 @@ export class UserGroundHost {
317
347
  throw stageError("Watch coverage", error);
318
348
  }
319
349
  rows = readRows(this.databasePath(identity));
320
- if (!rows.some((row) => row.uuid === workspaceId)) {
321
- throw stageError("entity registration", new Error(`workspace ${workspaceId} was not recorded`));
350
+ if (!hasRegisteredBoundary(rows.map(portableRecord))) {
351
+ throw stageError("entity registration", new Error(`workspace ${workspaceId} and its durable reference were not recorded`));
322
352
  }
323
- if (!this.cloudState.records.some((record) => record.uuid === workspaceId)) {
324
- throw stageError("cloud graph commit", new Error(`workspace ${workspaceId} is absent from the committed head`));
353
+ if (!hasRegisteredBoundary(this.cloudState.records)) {
354
+ throw stageError("cloud graph commit", new Error(`workspace ${workspaceId} or its durable reference is absent from the committed head`));
325
355
  }
326
356
  return {
327
357
  registered: true,
@@ -357,6 +387,7 @@ export class UserGroundHost {
357
387
  }
358
388
  } while (!this.watchHost.settled(cutoff));
359
389
  await this.recordRail?.flush();
390
+ await this.applyRail?.flush();
360
391
  if (!this.closing)
361
392
  this.ensureWatchers(identity);
362
393
  }
@@ -410,6 +441,7 @@ export class UserGroundHost {
410
441
  },
411
442
  random: Math.random,
412
443
  },
444
+ onInboxCommitted: () => this.applyRail?.recordsAvailable(),
413
445
  });
414
446
  this.recordStore = store;
415
447
  this.recordRail = rail;
@@ -426,6 +458,62 @@ export class UserGroundHost {
426
458
  throw pipelineStageError("entity record subscribe", error);
427
459
  }
428
460
  }
461
+ async startApplyRail(identity) {
462
+ if (!this.cloudState)
463
+ throw new Error("entity Apply rail requires cloud authority");
464
+ if (this.applyRail || this.applyHost)
465
+ throw new Error("entity Apply rail already started");
466
+ let host;
467
+ host = new EntityApplyHost({
468
+ databaseFile: this.databasePath(identity),
469
+ userRoot: this.userRoot(identity),
470
+ cacheDir: this.cacheDir(identity),
471
+ bindingDir: workspaceBindingDir(this.userRoot(identity), identity.deviceId),
472
+ readContent: (artifact) => this.downloadContent(this.cloudState.resourceId, this.cacheDir(identity), artifact),
473
+ captureLocal: async (intent, plan, position) => {
474
+ // Apply never suppresses Watch. This explicit whole-root suspicion is
475
+ // the synchronous proof boundary used only when Apply observed a
476
+ // save racing its reveal and must make that save durable first.
477
+ this.watchHost.suspectAll();
478
+ this.watchDirty = true;
479
+ await this.syncNow();
480
+ if (!host.store.hasLocalWorkAfter(intent.authorityId, intent.entityId, intent.throughSequence)) {
481
+ throw new Error(`Apply ${position} capture for ${plan.target.type} ${plan.target.name} (${intent.entityId}) produced no durable local record`);
482
+ }
483
+ },
484
+ acquireGround: () => this.acquireGroundEffect(),
485
+ });
486
+ const rail = new EntityApplyRail({
487
+ store: host.store,
488
+ effects: host.effects,
489
+ schedule: {
490
+ schedule(delayMs, callback) {
491
+ const timer = setTimeout(callback, delayMs);
492
+ timer.unref();
493
+ return { cancel: () => clearTimeout(timer) };
494
+ },
495
+ random: Math.random,
496
+ },
497
+ sha256Hex,
498
+ now: Date.now,
499
+ // Path cones and repository territories are wider than one UUID. Until
500
+ // the host exposes those locks, one serial machine-effect owner is the
501
+ // only honest concurrency setting.
502
+ concurrency: 1,
503
+ });
504
+ this.applyHost = host;
505
+ this.applyRail = rail;
506
+ try {
507
+ await rail.start();
508
+ }
509
+ catch (error) {
510
+ await rail.stop();
511
+ host.close();
512
+ this.applyRail = null;
513
+ this.applyHost = null;
514
+ throw pipelineStageError("entity Apply resume", error);
515
+ }
516
+ }
429
517
  async activateRuntimeTunnel(gatewayPort, runtimeToken) {
430
518
  // Files convergence and Watch/Detect own ground currency. Advertising an
431
519
  // HTTP tunnel must never trigger or wait for a second filesystem pass.
@@ -444,8 +532,10 @@ export class UserGroundHost {
444
532
  this.watchHost.close({ catchUp: false });
445
533
  this.namedDetect?.close();
446
534
  this.namedDetect = null;
535
+ const applyStopped = this.applyRail?.stop();
447
536
  const recordStopped = this.recordRail?.stop();
448
537
  await this.wire.close();
538
+ await applyStopped;
449
539
  await recordStopped;
450
540
  // An upload already inside its retry helper may have reopened the wire
451
541
  // after the first close. The stopped rail prevents new queue work; this
@@ -454,8 +544,18 @@ export class UserGroundHost {
454
544
  this.recordStore?.close();
455
545
  this.recordStore = null;
456
546
  this.recordRail = null;
547
+ this.applyHost?.close();
548
+ this.applyHost = null;
549
+ this.applyRail = null;
457
550
  return;
458
551
  }
552
+ await this.recordRail?.flush();
553
+ await this.applyRail?.flush();
554
+ const applyStopped = this.applyRail?.stop();
555
+ await applyStopped;
556
+ this.applyHost?.close();
557
+ this.applyHost = null;
558
+ this.applyRail = null;
459
559
  // Command shutdown is the last catch-up boundary. A filesystem callback
460
560
  // may still be queued, so watcher silence cannot be used as evidence yet.
461
561
  this.watchHost.suspectAll();
@@ -798,6 +898,9 @@ export class UserGroundHost {
798
898
  rootId: workspaceId,
799
899
  directory,
800
900
  materialized: true,
901
+ // Every materialized root has one reserved machine-control subtree.
902
+ // Apply stages and retains recovery bytes here; Watch never enrolls it.
903
+ excludedSubtrees: [join(directory, ".amalgm")],
801
904
  });
802
905
  }
803
906
  const health = this.watchHost.reconcile([...wanted.values()], {
@@ -856,7 +959,24 @@ export class UserGroundHost {
856
959
  this.scheduleRescan();
857
960
  }
858
961
  }
962
+ async acquireGroundEffect() {
963
+ let release;
964
+ const held = new Promise((resolve) => { release = resolve; });
965
+ const preceding = this.groundEffectTail;
966
+ this.groundEffectTail = preceding.then(() => held);
967
+ await preceding;
968
+ return release;
969
+ }
859
970
  async syncLocalChanges(identity) {
971
+ const release = await this.acquireGroundEffect();
972
+ try {
973
+ await this.syncLocalChangesUnlocked(identity);
974
+ }
975
+ finally {
976
+ release();
977
+ }
978
+ }
979
+ async syncLocalChangesUnlocked(identity) {
860
980
  if (!this.cloudState)
861
981
  throw new Error("cloud state is unavailable for user-ground Watch");
862
982
  const observations = this.watchHost.observations();
@@ -885,6 +1005,7 @@ export class UserGroundHost {
885
1005
  });
886
1006
  }
887
1007
  this.watchHost.settle(observations);
1008
+ this.applyRail?.localStateChanged();
888
1009
  this.recordRail?.localRecordsAvailable();
889
1010
  return;
890
1011
  }
@@ -935,6 +1056,7 @@ export class UserGroundHost {
935
1056
  });
936
1057
  this.namedDetect?.refreshEnrollmentPolicy();
937
1058
  this.watchHost.settle(observations);
1059
+ this.applyRail?.localStateChanged();
938
1060
  this.recordRail?.localRecordsAvailable();
939
1061
  }
940
1062
  async publishPendingSnapshotsBeforeLookup(identity, resourceId) {
@@ -1482,14 +1604,22 @@ function commitDetectedState(file, input) {
1482
1604
  for (const uuid of input.materializedRemovals ?? [])
1483
1605
  removeMaterialized.run(uuid);
1484
1606
  const upsertMaterialized = prepareGroundUpsert(database, "entities");
1607
+ // A valid graph may move or re-root several UUIDs through one another's
1608
+ // old address slots. Release every changed UUID's old slot first, then
1609
+ // install the complete new projection in this same transaction; row
1610
+ // iteration order must never decide whether an atomic graph is valid.
1485
1611
  for (const row of input.acceptedMaterializedRows ?? []) {
1486
- upsertMaterialized.run(...groundRowValues(row));
1612
+ removeMaterialized.run(row.uuid);
1613
+ }
1614
+ for (const row of input.acceptedMaterializedRows ?? []) {
1615
+ runGroundUpsert(database, "entities", upsertMaterialized, row);
1487
1616
  }
1488
1617
  for (const uuid of input.notebookRemovals)
1489
1618
  remove.run(uuid);
1490
1619
  const upsertNotebook = prepareGroundUpsert(database, "detection_notebook");
1491
- for (const row of input.acceptedNotebookRows)
1492
- upsertNotebook.run(...groundRowValues(row));
1620
+ for (const row of input.acceptedNotebookRows) {
1621
+ runGroundUpsert(database, "detection_notebook", upsertNotebook, row);
1622
+ }
1493
1623
  })();
1494
1624
  }
1495
1625
  finally {
@@ -1617,6 +1747,19 @@ function prepareGroundUpsert(database, table) {
1617
1747
  content_verified_at_ms = excluded.content_verified_at_ms
1618
1748
  `);
1619
1749
  }
1750
+ function runGroundUpsert(database, table, statement, row) {
1751
+ try {
1752
+ statement.run(...groundRowValues(row));
1753
+ }
1754
+ catch (error) {
1755
+ const occupant = database.prepare(`
1756
+ SELECT uuid, absolute_path AS absolutePath FROM ${table}
1757
+ WHERE resource_id = ? AND root_uuid = ? AND relative_path = ?
1758
+ `).get(row.resourceId, row.rootUUID, row.relativePath);
1759
+ throw new Error(`${table} projection could not place ${row.uuid} at ${row.rootUUID}:${row.relativePath}; `
1760
+ + `occupied by ${occupant?.uuid ?? "no retained row"} (${occupant?.absolutePath ?? "unknown"})`, { cause: error });
1761
+ }
1762
+ }
1620
1763
  function persistRows(file, identity, resourceId, rootUUID, rows, previousRows = [], options = {}) {
1621
1764
  const database = initializeDatabase(file);
1622
1765
  try {
@@ -1631,6 +1774,7 @@ function persistRows(file, identity, resourceId, rootUUID, rows, previousRows =
1631
1774
  const upsert = prepareGroundUpsert(database, "entities");
1632
1775
  const upsertNotebook = prepareGroundUpsert(database, "detection_notebook");
1633
1776
  const remove = database.prepare("DELETE FROM entities WHERE uuid = ? AND resource_id = ? AND root_uuid = ?");
1777
+ const releaseChangedSlot = database.prepare("DELETE FROM entities WHERE uuid = ?");
1634
1778
  const removeNotebook = database.prepare("DELETE FROM detection_notebook WHERE uuid = ? AND resource_id = ? AND root_uuid = ?");
1635
1779
  const previousByUuid = new Map(previousRows.map((row) => [row.uuid, row]));
1636
1780
  const materializedIds = new Set(database.prepare("SELECT uuid FROM entities").all()
@@ -1671,10 +1815,13 @@ function persistRows(file, identity, resourceId, rootUUID, rows, previousRows =
1671
1815
  }
1672
1816
  }
1673
1817
  for (const row of changed)
1674
- upsert.run(...groundRowValues(capturedGroundRow(resourceId, rootUUID, row)));
1818
+ releaseChangedSlot.run(row.record.uuid);
1819
+ for (const row of changed) {
1820
+ runGroundUpsert(database, "entities", upsert, capturedGroundRow(resourceId, rootUUID, row));
1821
+ }
1675
1822
  if (options.updateNotebook !== false) {
1676
1823
  for (const row of notebookChanged) {
1677
- upsertNotebook.run(...groundRowValues(capturedGroundRow(resourceId, rootUUID, row)));
1824
+ runGroundUpsert(database, "detection_notebook", upsertNotebook, capturedGroundRow(resourceId, rootUUID, row));
1678
1825
  }
1679
1826
  }
1680
1827
  });
@@ -2087,6 +2234,11 @@ async function reconcileRoot(input) {
2087
2234
  const children = readdirSync(directory, { withFileTypes: true })
2088
2235
  .sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
2089
2236
  for (const child of children) {
2237
+ // Every materialization boundary reserves its root .amalgm/ directory
2238
+ // for machine intents, staging, and recovery. This is structural host
2239
+ // state, never enrollment policy and never an entity.
2240
+ if (base === "" && child.name === ".amalgm")
2241
+ continue;
2090
2242
  if (isRepositoryMetadataEntry(parentType, child.name))
2091
2243
  continue;
2092
2244
  const relativePath = base ? `${base}/${child.name}` : child.name;
@@ -2302,7 +2454,7 @@ async function reconcileRoot(input) {
2302
2454
  .sort((left, right) => right.relativePath.length - left.relativePath.length)[0] ?? null;
2303
2455
  try {
2304
2456
  for (const repository of repositories) {
2305
- const repoIdentity = entries
2457
+ let repoIdentity = entries
2306
2458
  .filter((entry) => repositoryOwner(entry)?.uuid === repository.uuid)
2307
2459
  .map((entry) => ({
2308
2460
  path: repository.relativePath
@@ -2365,7 +2517,30 @@ async function reconcileRoot(input) {
2365
2517
  .sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0)
2366
2518
  : null;
2367
2519
  evidence && (evidence.repositoryCaptures += 1);
2368
- const captured = captureRepository(repository.absolutePath, repoIdentity, prior, previousIdentity);
2520
+ let captured = null;
2521
+ let coherent = false;
2522
+ for (let attempt = 0; attempt < 3; attempt += 1) {
2523
+ captured = captureRepository(repository.absolutePath, repoIdentity, prior, previousIdentity);
2524
+ const after = canonicalizeGitIdentity(repoIdentity, inspectGitRegistration(repository.absolutePath));
2525
+ if (sameGitIdentity(after, repoIdentity)) {
2526
+ coherent = true;
2527
+ break;
2528
+ }
2529
+ repoIdentity = after;
2530
+ }
2531
+ if (!coherent || !captured) {
2532
+ throw new Error(`repository did not settle during capture: ${repository.absolutePath}`);
2533
+ }
2534
+ for (const identityEntry of repoIdentity) {
2535
+ const entry = entries.find((candidate) => repositoryOwner(candidate)?.uuid === repository.uuid
2536
+ && (repository.relativePath
2537
+ ? candidate.relativePath.slice(repository.relativePath.length + 1)
2538
+ : candidate.relativePath) === identityEntry.path);
2539
+ if (!entry)
2540
+ continue;
2541
+ entry.type = identityEntry.type;
2542
+ entry.payloadVersion = identityEntry.payloadVersion ?? null;
2543
+ }
2369
2544
  repository.payloadVersion = captured.stateId;
2370
2545
  repository.transportVersion = captured.transportVersion;
2371
2546
  if (captured.bytes) {