@amalgm/shell 0.1.75 → 0.1.76

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.
@@ -333,66 +333,85 @@ export class UserGroundHost {
333
333
  return { register, add };
334
334
  }
335
335
  /** Resume the same host effect used by the SDK command before readiness.
336
- * Staged ground has no binding, so recovery cannot race Watch or Detect. */
336
+ * Staged ground has no binding, so recovery cannot race Watch or Detect.
337
+ * Each intent is one workspace's durable responsibility, not the machine's:
338
+ * an intent that cannot be satisfied now is reported and stays durable for
339
+ * the next start or an explicit re-Add, while readiness proceeds. */
337
340
  async resumeWorkspaceAdds(identity) {
338
- const database = this.databasePath(identity);
339
- const userRoot = this.userRoot(identity);
340
- const bindingDir = workspaceBindingDir(userRoot, identity.deviceId);
341
- for (const intent of readWorkspaceAddIntents(database)) {
342
- const release = await this.acquireGroundScope([
343
- intent.destinationPath,
344
- intent.stagingPath,
345
- userRoot,
346
- ]);
347
- const visibleAcrossBlindInterval = pathExists(intent.destinationPath);
341
+ for (const intent of readWorkspaceAddIntents(this.databasePath(identity))) {
348
342
  try {
349
- const selection = workspaceAddSelection(intent);
350
- const stagedAcrossBlindInterval = pathExists(intent.stagingPath);
351
- if (stagedAcrossBlindInterval && hasRootRows(database, intent.resourceId, intent.workspaceId)) {
352
- // Rows prove that staging was verified before the crash, not that its
353
- // bytes remained unchanged while no watcher existed. Return this
354
- // private install to the intent-only stage and reproduce it from the
355
- // verified immutable cache before making it visible.
356
- deleteRootRows(database, intent.resourceId, intent.workspaceId);
357
- }
358
- await installCloudWorkspace({
359
- identity,
360
- userRoot,
361
- database,
362
- cacheDir: this.cacheDir(identity),
363
- bindingDir,
364
- resourceId: intent.resourceId,
365
- workspace: selection.workspace,
366
- reference: selection.reference,
367
- references: intent.references,
368
- records: intent.records,
369
- destinationParent: dirname(intent.destinationPath),
370
- cloudHead: intent.cloudHead,
371
- readContent: (artifact) => this.downloadContent(intent.resourceId, this.cacheDir(identity), artifact, 10, { journey: "add", workspaceId: intent.workspaceId }),
372
- onStage: (stage) => this.options.onWorkspaceAddStage?.({
373
- workspaceId: intent.workspaceId,
374
- stage,
375
- }),
376
- });
377
- this.ensureWatchers(identity, visibleAcrossBlindInterval ? [] : [intent.workspaceId]);
378
- assertHealthyWatch(this.watchHost.evidence(), intent.workspaceId);
343
+ await this.resumeWorkspaceAdd(identity, intent);
379
344
  }
380
- finally {
381
- release();
345
+ catch (error) {
346
+ const message = error instanceof Error ? error.message : String(error);
347
+ this.stage({
348
+ primitive: "download",
349
+ stage: "add-resume",
350
+ status: "failed",
351
+ workspaceId: intent.workspaceId,
352
+ measurements: { error: message },
353
+ });
354
+ this.options.onApplyError?.(new Error(`workspace Add ${intent.workspaceId} could not resume and stays pending: ${message}`, { cause: error }));
382
355
  }
383
- if (visibleAcrossBlindInterval) {
384
- // Ground that was visible while the runtime was absent may have been
385
- // edited after its verified reveal. It is an ordinary startup blind
386
- // interval, so Watch must retain complete suspicion and Detect must
387
- // settle it before the Add responsibility can be cleared.
388
- await this.flushObservedChanges();
356
+ }
357
+ }
358
+ async resumeWorkspaceAdd(identity, intent) {
359
+ const database = this.databasePath(identity);
360
+ const userRoot = this.userRoot(identity);
361
+ const bindingDir = workspaceBindingDir(userRoot, identity.deviceId);
362
+ const release = await this.acquireGroundScope([
363
+ intent.destinationPath,
364
+ intent.stagingPath,
365
+ userRoot,
366
+ ]);
367
+ const visibleAcrossBlindInterval = pathExists(intent.destinationPath);
368
+ try {
369
+ const selection = workspaceAddSelection(intent);
370
+ const stagedAcrossBlindInterval = pathExists(intent.stagingPath);
371
+ if (stagedAcrossBlindInterval && hasRootRows(database, intent.resourceId, intent.workspaceId)) {
372
+ // Rows prove that staging was verified before the crash, not that its
373
+ // bytes remained unchanged while no watcher existed. Return this
374
+ // private install to the intent-only stage and reproduce it from the
375
+ // verified immutable cache before making it visible.
376
+ deleteRootRows(database, intent.resourceId, intent.workspaceId);
389
377
  }
390
- await this.options.onWorkspaceAddStage?.({
391
- workspaceId: intent.workspaceId,
392
- stage: "watching",
378
+ await installCloudWorkspace({
379
+ identity,
380
+ userRoot,
381
+ database,
382
+ cacheDir: this.cacheDir(identity),
383
+ bindingDir,
384
+ resourceId: intent.resourceId,
385
+ workspace: selection.workspace,
386
+ reference: selection.reference,
387
+ references: intent.references,
388
+ records: intent.records,
389
+ destinationParent: dirname(intent.destinationPath),
390
+ cloudHead: intent.cloudHead,
391
+ readContent: (artifact) => this.downloadContent(intent.resourceId, this.cacheDir(identity), artifact, 10, { journey: "add", workspaceId: intent.workspaceId }),
392
+ onStage: (stage) => this.options.onWorkspaceAddStage?.({
393
+ workspaceId: intent.workspaceId,
394
+ stage,
395
+ }),
393
396
  });
394
- deleteWorkspaceAddIntent(database, intent.workspaceId);
397
+ this.ensureWatchers(identity, visibleAcrossBlindInterval ? [] : [intent.workspaceId]);
398
+ assertHealthyWatch(this.watchHost.evidence(), intent.workspaceId);
399
+ }
400
+ finally {
401
+ release();
395
402
  }
403
+ if (visibleAcrossBlindInterval) {
404
+ // Ground that was visible while the runtime was absent may have been
405
+ // edited after its verified reveal. It is an ordinary startup blind
406
+ // interval, so Watch must retain complete suspicion and Detect must
407
+ // settle it before the Add responsibility can be cleared.
408
+ await this.flushObservedChanges();
409
+ }
410
+ await this.options.onWorkspaceAddStage?.({
411
+ workspaceId: intent.workspaceId,
412
+ stage: "watching",
413
+ });
414
+ deleteWorkspaceAddIntent(database, intent.workspaceId);
396
415
  }
397
416
  /** Finish the user-visible register journey inside the one persistent Files
398
417
  * owner. The declaration remains durable if any later stage fails, and a
@@ -746,7 +765,18 @@ export class UserGroundHost {
746
765
  },
747
766
  sha256Hex,
748
767
  now: Date.now,
749
- onBackgroundError: (error) => this.options.onApplyError?.(error),
768
+ // A failed Apply lane is one entity's outcome; the rail keeps every
769
+ // other lane moving and retries this one. Surface it as evidence so a
770
+ // persistently failing entity is visible rather than silent.
771
+ onBackgroundError: (error) => {
772
+ this.options.onApplyError?.(error);
773
+ this.stage({
774
+ primitive: "apply",
775
+ stage: "lane",
776
+ status: "failed",
777
+ measurements: { error: error instanceof Error ? error.message : String(error) },
778
+ });
779
+ },
750
780
  // The host now coordinates by address cone, so Apply's final guard waits
751
781
  // only for effects on its own addresses. The rail itself still drains
752
782
  // one entity at a time: parallel private preparation multiplies content
@@ -2949,14 +2979,21 @@ async function reconcileGround(input) {
2949
2979
  }
2950
2980
  async function reconcileRoot(input) {
2951
2981
  const { identity, resourceId, rootUUID, rootName, rootPath, rootType, database, cacheDir, policy, bindingDir, suspects = null, evidence, } = input;
2952
- const existingRows = input.existingRows
2953
- ?? readRows(database).filter((row) => row.resourceId === resourceId && row.rootUUID === rootUUID);
2954
- const existingByPath = new Map(existingRows.flatMap((row) => {
2982
+ // Prior rows reach this scan from every binding this root encloses. A row's
2983
+ // stored relativePath is relative to the root that last committed it, which
2984
+ // is a different frame when an enclosing boundary was just registered
2985
+ // (child/README.md is stored as "README.md" under the child root). Address
2986
+ // every prior row in this scan's frame before anything compares it with an
2987
+ // observed entry; absolutePath is the only coordinate both frames share.
2988
+ const existingRows = (input.existingRows
2989
+ ?? readRows(database).filter((row) => row.resourceId === resourceId && row.rootUUID === rootUUID))
2990
+ .flatMap((row) => {
2955
2991
  if (!pathWithin(rootPath, row.absolutePath))
2956
2992
  return [];
2957
- const local = slashPath(relative(rootPath, row.absolutePath));
2958
- return [[local, row]];
2959
- }));
2993
+ const relativePath = slashPath(relative(rootPath, row.absolutePath));
2994
+ return [relativePath === row.relativePath ? row : { ...row, relativePath }];
2995
+ });
2996
+ const existingByPath = new Map(existingRows.map((row) => [row.relativePath, row]));
2960
2997
  const registeredBoundaries = input.registeredBoundaries ?? new Map();
2961
2998
  if (suspects !== null
2962
2999
  && suspects.length === 0
@@ -4126,6 +4163,19 @@ async function installCloudWorkspace(input) {
4126
4163
  alreadyMaterialized: pending === null,
4127
4164
  };
4128
4165
  }
4166
+ // One machine materializes each UUID in exactly one place. The selected
4167
+ // closure may enclose a boundary this machine already added elsewhere; a
4168
+ // fresh install would re-root those rows under the new destination while
4169
+ // the old binding stayed bound and watched over ground with no rows, and
4170
+ // the next Detect pass there would mint a second identity for every file.
4171
+ const selectedUUIDs = new Set(records.map((record) => record.uuid));
4172
+ const enclosedElsewhere = allLocalRows.filter((row) => row.uuid !== workspace.uuid && selectedUUIDs.has(row.uuid));
4173
+ if (enclosedElsewhere.length > 0) {
4174
+ const roots = [...new Set(enclosedElsewhere.map((row) => row.rootUUID))]
4175
+ .map((rootUUID) => allLocalRows.find((row) => row.uuid === rootUUID)?.absolutePath ?? rootUUID);
4176
+ throw new Error(`files add: workspace ${workspace.uuid} encloses ${enclosedElsewhere.length} entities already `
4177
+ + `materialized on this machine under ${roots.join(", ")}; remove that materialization first`);
4178
+ }
4129
4179
  const parent = realpathSync(resolve(destinationParent));
4130
4180
  if (!statSync(parent).isDirectory())
4131
4181
  throw new Error("destination must be a directory");