opencode-swarm 7.164.11 → 7.164.12

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 (107) hide show
  1. package/.opencode/skills/engineering-conventions/SKILL.md +1 -1
  2. package/dist/background/pending-delegations.d.ts +7 -2
  3. package/dist/background/pr-subscriptions.d.ts +3 -3
  4. package/dist/cli/{coder-settlement-0rfk8y11.js → coder-settlement-yh1q5fr3.js} +15 -16
  5. package/dist/cli/{config-doctor-j17b7qq2.js → config-doctor-bd4yzmh7.js} +3 -3
  6. package/dist/cli/{core-evhjjrtt.js → core-y0s1vmyc.js} +2 -2
  7. package/dist/cli/{curation-policy-vj6z5hvr.js → curation-policy-gs1qakn7.js} +5 -6
  8. package/dist/cli/{curator-vqd5yeck.js → curator-brqack0j.js} +48 -46
  9. package/dist/cli/{curator-drift-ez81vg2t.js → curator-drift-1sg9yk2m.js} +14 -12
  10. package/dist/cli/curator-llm-factory-dtsjc3zf.js +70 -0
  11. package/dist/cli/{dispatch-dqybvj8v.js → dispatch-c7xjt7js.js} +3 -3
  12. package/dist/cli/{evidence-summary-service-937x7rbg.js → evidence-summary-service-nqbcvp30.js} +24 -23
  13. package/dist/cli/{gate-evidence-q1cehr4k.js → gate-evidence-9stdtpej.js} +9 -10
  14. package/dist/cli/guardrail-explain-3ff1z3fm.js +71 -0
  15. package/dist/cli/{guardrail-log-3g93f3g2.js → guardrail-log-sf39x00b.js} +10 -10
  16. package/dist/cli/{guardrail-reset-qe2r5qzq.js → guardrail-reset-9wr6a60s.js} +48 -46
  17. package/dist/cli/hive-promoter-bqtj8261.js +85 -0
  18. package/dist/cli/{index-kv2tdkk5.js → index-03enxdaq.js} +1 -1
  19. package/dist/cli/{index-ggrp3v00.js → index-0byep4pe.js} +15 -17
  20. package/dist/cli/{index-2bgr671r.js → index-2bhz5k91.js} +20 -2
  21. package/dist/cli/{index-mmjamc9b.js → index-3fnym6ac.js} +5 -5
  22. package/dist/cli/{index-d9entpn3.js → index-40twgsyh.js} +4 -4
  23. package/dist/cli/{index-ggtmsm84.js → index-4mjmnysc.js} +1 -1
  24. package/dist/cli/{index-e90em0x4.js → index-4vb56w2k.js} +12 -17
  25. package/dist/cli/{index-r656jh9j.js → index-5wc5pbzc.js} +3700 -2609
  26. package/dist/cli/{index-w3gtqey0.js → index-6chhqq2n.js} +4 -4
  27. package/dist/cli/{index-qbw3yw2d.js → index-888pq4xx.js} +11 -11
  28. package/dist/cli/{index-3146wmgv.js → index-8d90gzaz.js} +1 -1
  29. package/dist/cli/{index-5jx22kwc.js → index-8vw2pjgr.js} +544 -15
  30. package/dist/cli/{index-b2nj04q6.js → index-9ss2m4rs.js} +1 -434
  31. package/dist/cli/{index-x43debgx.js → index-ad9064j9.js} +15 -17
  32. package/dist/cli/{index-5kcvrpkt.js → index-b25t91a3.js} +1 -1
  33. package/dist/cli/index-c45662wc.js +361 -0
  34. package/dist/cli/{index-aqppaaqx.js → index-cgewrpw5.js} +3 -2
  35. package/dist/cli/{index-zfs3zqeq.js → index-d3mpxsmy.js} +771 -21
  36. package/dist/cli/{index-y1yqmkv7.js → index-d7j3n5ra.js} +52 -50
  37. package/dist/cli/{index-4vb6qa73.js → index-ebce775n.js} +36 -2
  38. package/dist/cli/{index-dsg9dw3b.js → index-eed0gkv6.js} +2 -2
  39. package/dist/cli/index-es64amqt.js +675 -0
  40. package/dist/cli/{index-9gxbjkts.js → index-ewv8qa8q.js} +22 -19
  41. package/dist/cli/{index-1ww7wj8d.js → index-hb7bh4r2.js} +15 -15
  42. package/dist/cli/{index-kc2tgcm7.js → index-hg09xemc.js} +4 -4
  43. package/dist/cli/{index-w95vxjde.js → index-hvz4ad2t.js} +39 -8
  44. package/dist/cli/{index-77z98j76.js → index-k0s8n6m5.js} +314 -130
  45. package/dist/cli/{index-7m32fjb4.js → index-kxv193aq.js} +9 -9
  46. package/dist/cli/{index-hp22pzn5.js → index-m8xrbdmn.js} +2 -2
  47. package/dist/cli/{index-4gqxppcx.js → index-mee9maf5.js} +2 -2
  48. package/dist/cli/{index-r3mvsbtq.js → index-p612v1mv.js} +7 -1
  49. package/dist/cli/{index-r5q5gd2a.js → index-qgajv9rd.js} +2 -2
  50. package/dist/cli/{index-zjg4chy2.js → index-qqq04n70.js} +2 -2
  51. package/dist/cli/{index-ww6zm5hr.js → index-r2p0pgst.js} +3 -3
  52. package/dist/cli/{index-47kh2103.js → index-r69gzes2.js} +14 -14
  53. package/dist/cli/{index-vdn32nws.js → index-sphahrmq.js} +3 -3
  54. package/dist/cli/{index-rhqdsc1x.js → index-vw66q1rw.js} +9 -9
  55. package/dist/cli/{index-kch6yenm.js → index-wasdrhev.js} +7 -7
  56. package/dist/cli/{index-c5v1w13g.js → index-wknpkfm8.js} +807 -629
  57. package/dist/cli/{index-pp2nya9v.js → index-x39hjdf1.js} +2 -4
  58. package/dist/cli/{index-n8688d12.js → index-xxfk62zd.js} +2 -2
  59. package/dist/cli/index-yvqg6kn5.js +429 -0
  60. package/dist/cli/{index-nqsj0g5y.js → index-ztm0q26v.js} +8 -8
  61. package/dist/cli/index.js +50 -48
  62. package/dist/cli/{knowledge-escalator-j04g450p.js → knowledge-escalator-01vva51d.js} +17 -18
  63. package/dist/cli/{knowledge-events-53z45rs0.js → knowledge-events-dpfwh1hs.js} +15 -16
  64. package/dist/cli/{knowledge-link-a6kfg3qm.js → knowledge-link-2rvp966j.js} +4 -5
  65. package/dist/cli/{knowledge-store-whm0gagt.js → knowledge-store-aw58ae7b.js} +6 -7
  66. package/dist/cli/{knowledge-validator-h8m7065g.js → knowledge-validator-yhd31ryf.js} +8 -9
  67. package/dist/cli/{pending-delegations-6hkpe5ze.js → pending-delegations-fjetcz8e.js} +17 -8
  68. package/dist/cli/{pr-subscriptions-11zca1mh.js → pr-subscriptions-cwwtfs8w.js} +17 -8
  69. package/dist/cli/{pr-workflow-gate-6g29rkg7.js → pr-workflow-gate-5xwtg1k2.js} +50 -48
  70. package/dist/cli/{runner-5abw3t35.js → runner-mb51twxt.js} +6 -7
  71. package/dist/cli/{scan-cursor-mkfx410t.js → scan-cursor-j11qwf0v.js} +9 -10
  72. package/dist/cli/{schema-jbbam3ev.js → schema-0xp3bde3.js} +1 -1
  73. package/dist/cli/{scope-persistence-q1ct4k42.js → scope-persistence-brrk806y.js} +19 -12
  74. package/dist/cli/{skill-generator-cv8fg96x.js → skill-generator-j8qzmet9.js} +20 -21
  75. package/dist/cli/snapshot-coordination-init-h0xmtftq.js +84 -0
  76. package/dist/cli/{telemetry-mbqqamsj.js → telemetry-m84ap0pp.js} +1 -1
  77. package/dist/cli/{workspace-snapshot-eh18vgkn.js → workspace-snapshot-6z9rk7xw.js} +3 -3
  78. package/dist/cli/{worktree-collision-ownership-xgnn8dsq.js → worktree-collision-ownership-j9zykj18.js} +21 -12
  79. package/dist/cli/{worktree-isolation-k2dgfmeg.js → worktree-isolation-jd39xz6h.js} +48 -46
  80. package/dist/commands/close.d.ts +13 -6
  81. package/dist/commands/reset-session.d.ts +4 -0
  82. package/dist/db/coordination-store.d.ts +85 -0
  83. package/dist/db/durability.d.ts +22 -5
  84. package/dist/db/index.d.ts +1 -0
  85. package/dist/db/project-db.d.ts +12 -0
  86. package/dist/index.js +810 -705
  87. package/dist/memory/schema.d.ts +3 -3
  88. package/dist/pr-review/authorization.d.ts +5 -4
  89. package/dist/pr-review/persistence.d.ts +10 -1
  90. package/dist/scope/scope-binding.d.ts +10 -0
  91. package/dist/scope/scope-persistence.d.ts +10 -0
  92. package/dist/services/handoff-service.d.ts +2 -0
  93. package/dist/services/status-service.d.ts +4 -0
  94. package/dist/session/snapshot-coordination-init.d.ts +49 -0
  95. package/dist/session/snapshot-reader.d.ts +2 -0
  96. package/dist/session/snapshot-store.d.ts +42 -0
  97. package/dist/session/snapshot-writer.d.ts +4 -1
  98. package/dist/state.d.ts +6 -3
  99. package/dist/turbo/epic/state.d.ts +4 -20
  100. package/dist/utils/bun-compat.d.ts +10 -0
  101. package/dist/utils/external-tool-runner.d.ts +2 -0
  102. package/package.json +3 -2
  103. package/dist/cli/curator-llm-factory-sxcpmcqq.js +0 -68
  104. package/dist/cli/guardrail-explain-xv9aw1bf.js +0 -69
  105. package/dist/cli/hive-promoter-09yc9mp5.js +0 -83
  106. package/dist/cli/index-xnb3tk7x.js +0 -24
  107. package/dist/cli/{ast-diff-ekd9v10f.js → ast-diff-b0esr2kf.js} +3 -3
@@ -1,31 +1,42 @@
1
1
  // @bun
2
2
  import {
3
- computePlanStructureHash,
4
- derivePlanId
5
- } from "./index-e90em0x4.js";
6
- import {
7
- canonicalExistingFilesystemPath,
8
- legacyCanonicalExistingFilesystemPath
9
- } from "./index-ry16d01s.js";
3
+ computePlanStructureHash
4
+ } from "./index-4vb56w2k.js";
10
5
  import {
11
6
  appendCoreEventSync
12
- } from "./index-4gqxppcx.js";
7
+ } from "./index-mee9maf5.js";
13
8
  import {
14
9
  unsafePathTextReason
15
10
  } from "./index-556ja8an.js";
16
11
  import {
17
- PlanSchema
18
- } from "./index-rhqdsc1x.js";
12
+ deleteCoordinationState,
13
+ derivePlanId,
14
+ importCoordinationOnce,
15
+ listCoordinationStates,
16
+ transitionCoordinationState,
17
+ transitionCoordinationStateWithinTransaction,
18
+ withCoordinationTransaction
19
+ } from "./index-c45662wc.js";
20
+ import {
21
+ getProjectDb
22
+ } from "./index-es64amqt.js";
23
+ import {
24
+ canonicalExistingFilesystemPath,
25
+ legacyCanonicalExistingFilesystemPath
26
+ } from "./index-ry16d01s.js";
19
27
  import {
20
28
  assertProjectRoot
21
- } from "./index-2bgr671r.js";
29
+ } from "./index-2bhz5k91.js";
22
30
  import {
23
31
  atomicWriteSwarmFile,
24
32
  atomicWriteSwarmFileSync
25
- } from "./index-9gxbjkts.js";
33
+ } from "./index-ewv8qa8q.js";
26
34
  import {
27
35
  require_proper_lockfile
28
36
  } from "./index-7g4c7s5r.js";
37
+ import {
38
+ PlanSchema
39
+ } from "./index-vw66q1rw.js";
29
40
  import {
30
41
  __toESM
31
42
  } from "./index-a76rekgs.js";
@@ -158,6 +169,17 @@ function registerScopeBinding(binding) {
158
169
  pendingScopeBindings.set(key, binding);
159
170
  return { ok: true, binding };
160
171
  }
172
+ function getActiveScopeBindingsForSession(input) {
173
+ sweepExpired();
174
+ const workspaceIdentity = canonicalWorkspaceIdentity(input.directory);
175
+ if (!workspaceIdentity || !input.activeSessionId.trim())
176
+ return [];
177
+ const now = Date.now();
178
+ return [...pendingScopeBindings.values()].filter((binding) => binding.workspaceIdentity === workspaceIdentity && binding.ownerSessionId === input.activeSessionId && binding.activation === "active" && binding.lifecycleState === "live" && binding.expiresAt > now && isDispatchCorrelated(binding));
179
+ }
180
+ function isDispatchCorrelated(binding) {
181
+ return typeof binding.dispatchCallId === "string" && binding.dispatchCallId.length > 0 && binding.ownerMessageId === binding.dispatchCallId && typeof binding.parentOwnerSessionId === "string" && binding.parentOwnerSessionId.length > 0 && binding.ownerSessionId !== binding.parentOwnerSessionId && binding.parentCallId === binding.dispatchCallId;
182
+ }
161
183
  function clearScopeBindings(predicate) {
162
184
  const removed = [];
163
185
  if (!predicate) {
@@ -278,28 +300,22 @@ var MAX_SCOPE_BYTES = 2 * 1024 * 1024;
278
300
  var MAX_BINDING_FILES_TO_SCAN = 1e4;
279
301
  var MAX_TOMBSTONE_AGE_MS = 7 * 24 * 60 * 60 * 1000;
280
302
  var MAX_DURABLE_TOMBSTONES = 256;
303
+ var SCOPE_BINDING_COORDINATION_NAMESPACE = "scope-binding";
304
+ var SCOPE_BINDING_COORDINATION_IMPORT_SOURCE = "scope-binding-v2";
281
305
  var liveBindingCapacity = MAX_PENDING_SCOPE_BINDINGS;
282
306
  var bindingFileScanCapacity = MAX_BINDING_FILES_TO_SCAN;
283
307
  var maintenanceFileScanCapacity = MAX_BINDING_FILES_TO_SCAN * 2;
284
308
  var MAX_MAINTENANCE_JOBS = 64;
285
309
  var maintenanceJobs = new Map;
286
310
  var lockfileWithSync = import_proper_lockfile.default;
287
- var syncLockWait = new Int32Array(new SharedArrayBuffer(4));
288
- function lockSyncWithBoundedRetry(targetPath, attempts = 41) {
289
- let lastError;
290
- for (let attempt = 0;attempt < attempts; attempt++) {
291
- try {
292
- return lockfileWithSync.lockSync(targetPath, {
293
- stale: LOCK_STALE_MS,
294
- realpath: false
295
- });
296
- } catch (error) {
297
- lastError = error;
298
- if (attempt + 1 < attempts)
299
- Atomics.wait(syncLockWait, 0, 0, 25);
300
- }
311
+
312
+ class ScopeCoordinationAbort extends Error {
313
+ failure;
314
+ constructor(failure) {
315
+ super(failure.message);
316
+ this.failure = failure;
317
+ this.name = "ScopeCoordinationAbort";
301
318
  }
302
- throw lastError instanceof Error ? lastError : new Error("Lock unavailable");
303
319
  }
304
320
  var WINDOWS_RESERVED = new Set([
305
321
  "CON",
@@ -438,9 +454,6 @@ function ensureLockTargetSync(targetPath) {
438
454
  return false;
439
455
  }
440
456
  }
441
- function exactBindingPrefix(taskId) {
442
- return `binding-${taskId}-`;
443
- }
444
457
  function readBoundedFile(filePath) {
445
458
  const nofollow = fs.constants.O_NOFOLLOW ?? 0;
446
459
  let fd;
@@ -464,6 +477,365 @@ function readBoundedFile(filePath) {
464
477
  } catch {}
465
478
  }
466
479
  }
480
+ function scopeBindingArchivePath(filePath) {
481
+ return `${filePath}.imported`;
482
+ }
483
+ function collisionSafeScopeBindingArchivePath(filePath) {
484
+ const canonical = scopeBindingArchivePath(filePath);
485
+ if (!fs.existsSync(canonical))
486
+ return canonical;
487
+ for (let suffix = 1;suffix <= 1e4; suffix += 1) {
488
+ const candidate = `${canonical}.${suffix}`;
489
+ if (!fs.existsSync(candidate))
490
+ return candidate;
491
+ }
492
+ throw new Error(`No collision-safe archive path available for ${path2.basename(filePath)}.`);
493
+ }
494
+ function scopeBindingShadowPayload(binding) {
495
+ return JSON.stringify(binding, null, 2);
496
+ }
497
+ function coordinationScopeEntityKey(binding) {
498
+ return binding.generationId;
499
+ }
500
+ function parseCoordinationScopeBinding(directory, row) {
501
+ try {
502
+ const binding = validateScopeBindingPayload(directory, JSON.parse(row.payload));
503
+ if (!binding || binding.generationId !== row.entityKey || binding.revision !== row.revision || binding.lifecycleState !== row.status) {
504
+ return null;
505
+ }
506
+ return binding;
507
+ } catch {
508
+ return null;
509
+ }
510
+ }
511
+ function transitionScopeBindingState(directory, binding, expectedRevision) {
512
+ const payload = scopeBindingShadowPayload(binding);
513
+ const idempotencyKey = createHash("sha256").update(`${binding.bindingId}\x00${binding.generationId}\x00${binding.revision}\x00${binding.lifecycleState}\x00${payload}`).digest("hex");
514
+ try {
515
+ const result = transitionCoordinationState(directory, {
516
+ namespace: SCOPE_BINDING_COORDINATION_NAMESPACE,
517
+ entityKey: coordinationScopeEntityKey(binding),
518
+ expectedRevision,
519
+ generation: 1,
520
+ status: binding.lifecycleState,
521
+ payload,
522
+ event: {
523
+ streamId: `scope-binding:${binding.bindingId}`,
524
+ idempotencyKey,
525
+ eventType: binding.lifecycleState,
526
+ payload
527
+ }
528
+ });
529
+ if (result.outcome !== "applied" && result.outcome !== "duplicate") {
530
+ return {
531
+ ok: false,
532
+ code: result.outcome === "revision_conflict" || result.outcome === "stale_generation" ? "SCOPE_BINDING_STALE" : "SCOPE_BINDING_PERSISTENCE_FAILED",
533
+ message: `Scope binding transition failed: ${result.outcome}.`
534
+ };
535
+ }
536
+ const persisted = result.state && parseCoordinationScopeBinding(directory, result.state);
537
+ return persisted ? { ok: true, value: persisted } : persistenceFailure("Scope binding transition could not be re-read from coordination state.");
538
+ } catch (error) {
539
+ return persistenceFailure(`Scope binding transition failed: ${error instanceof Error ? error.message : "unknown coordination error"}`);
540
+ }
541
+ }
542
+ function transitionScopeBindingStateWithinTransaction(directory, binding, expectedRevision) {
543
+ const payload = scopeBindingShadowPayload(binding);
544
+ const idempotencyKey = createHash("sha256").update(`${binding.bindingId}\x00${binding.generationId}\x00${binding.revision}\x00${binding.lifecycleState}\x00${payload}`).digest("hex");
545
+ try {
546
+ const result = transitionCoordinationStateWithinTransaction(directory, {
547
+ namespace: SCOPE_BINDING_COORDINATION_NAMESPACE,
548
+ entityKey: coordinationScopeEntityKey(binding),
549
+ expectedRevision,
550
+ generation: 1,
551
+ status: binding.lifecycleState,
552
+ payload,
553
+ event: {
554
+ streamId: `scope-binding:${binding.bindingId}`,
555
+ idempotencyKey,
556
+ eventType: binding.lifecycleState,
557
+ payload
558
+ }
559
+ });
560
+ if (result.outcome !== "applied" && result.outcome !== "duplicate") {
561
+ return {
562
+ ok: false,
563
+ code: result.outcome === "revision_conflict" || result.outcome === "stale_generation" ? "SCOPE_BINDING_STALE" : "SCOPE_BINDING_PERSISTENCE_FAILED",
564
+ message: `Scope binding transition failed: ${result.outcome}.`
565
+ };
566
+ }
567
+ const persisted = result.state && parseCoordinationScopeBinding(directory, result.state);
568
+ return persisted ? { ok: true, value: persisted } : persistenceFailure("Scope binding transition could not be re-read from coordination state.");
569
+ } catch (error) {
570
+ return persistenceFailure(`Scope binding transition failed: ${error instanceof Error ? error.message : "unknown coordination error"}`);
571
+ }
572
+ }
573
+ function coordinationTimestampFromMs(value, label) {
574
+ if (!Number.isFinite(value)) {
575
+ throw new Error(`${label} must be a finite epoch-millisecond timestamp.`);
576
+ }
577
+ const iso = new Date(value).toISOString();
578
+ if (Number.isNaN(Date.parse(iso))) {
579
+ throw new Error(`${label} must be a valid epoch-millisecond timestamp.`);
580
+ }
581
+ return iso;
582
+ }
583
+ function importScopeBindingStateRows(directory, bindings) {
584
+ const db = getProjectDb(directory);
585
+ for (const binding of bindings) {
586
+ if (!Number.isSafeInteger(binding.revision) || binding.revision < 1) {
587
+ throw new Error(`Legacy binding ${binding.generationId} has invalid revision ${binding.revision}.`);
588
+ }
589
+ db.run(`INSERT INTO coordination_state
590
+ (namespace, entity_key, revision, generation, status, payload, updated_at)
591
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, [
592
+ SCOPE_BINDING_COORDINATION_NAMESPACE,
593
+ coordinationScopeEntityKey(binding),
594
+ binding.revision,
595
+ 1,
596
+ binding.lifecycleState,
597
+ scopeBindingShadowPayload(binding),
598
+ coordinationTimestampFromMs(binding.updatedAt, "binding.updatedAt")
599
+ ]);
600
+ }
601
+ }
602
+ function writeScopeBindingShadow(directory, binding) {
603
+ try {
604
+ const scopesDir = getScopesDir(directory);
605
+ fs.mkdirSync(scopesDir, { recursive: true });
606
+ if (!isScopesDirSafe(directory, scopesDir))
607
+ return;
608
+ atomicWriteSync(getBindingFilePath(directory, binding), scopeBindingShadowPayload(binding));
609
+ } catch {}
610
+ }
611
+ function archiveImportedScopeAuthorityFiles(paths) {
612
+ for (const filePath of paths) {
613
+ try {
614
+ if (!fs.existsSync(filePath))
615
+ continue;
616
+ const archivedPath = collisionSafeScopeBindingArchivePath(filePath);
617
+ fs.renameSync(filePath, archivedPath);
618
+ } catch {}
619
+ }
620
+ }
621
+ function fingerprintCommittedScopeImportSource(directory) {
622
+ const scopesDir = getScopesDir(directory);
623
+ try {
624
+ fs.lstatSync(scopesDir);
625
+ } catch (error) {
626
+ if (error.code === "ENOENT")
627
+ return null;
628
+ throw error;
629
+ }
630
+ if (!isScopesDirSafe(directory, scopesDir))
631
+ return null;
632
+ const names = fs.readdirSync(scopesDir).sort();
633
+ if (names.length > bindingFileScanCapacity)
634
+ return null;
635
+ const authorityFiles = [];
636
+ for (const name of names) {
637
+ if (!name.startsWith("binding-") || !name.endsWith(".json"))
638
+ continue;
639
+ const filePath = path2.join(scopesDir, name);
640
+ authorityFiles.push(filePath);
641
+ const intentPath = getRetirementIntentPath(filePath);
642
+ if (fs.existsSync(intentPath))
643
+ authorityFiles.push(intentPath);
644
+ }
645
+ for (const name of names) {
646
+ if (/^claim-[a-f0-9]{40}\.json$/i.test(name)) {
647
+ authorityFiles.push(path2.join(scopesDir, name));
648
+ }
649
+ }
650
+ if (authorityFiles.length === 0)
651
+ return null;
652
+ const digest = createHash("sha256");
653
+ for (const filePath of authorityFiles) {
654
+ const raw = readBoundedFile(filePath);
655
+ if (raw === null)
656
+ return null;
657
+ digest.update(path2.basename(filePath));
658
+ digest.update(raw);
659
+ }
660
+ return { authorityFiles, sourceDigest: digest.digest("hex") };
661
+ }
662
+ function collectLegacyScopeBindingsForImport(directory) {
663
+ const scopesDir = getScopesDir(directory);
664
+ try {
665
+ fs.lstatSync(scopesDir);
666
+ } catch (error) {
667
+ if (error.code === "ENOENT") {
668
+ return {
669
+ bindings: [],
670
+ authorityFiles: [],
671
+ sourceDigest: "empty",
672
+ rowCount: 0
673
+ };
674
+ }
675
+ throw error;
676
+ }
677
+ if (!isScopesDirSafe(directory, scopesDir)) {
678
+ throw new Error("The durable scope store failed containment checks.");
679
+ }
680
+ if (!migrateLegacyBindingsSync(directory)) {
681
+ throw new Error("The durable binding set could not be completely evaluated.");
682
+ }
683
+ const names = fs.readdirSync(scopesDir).sort();
684
+ if (names.length > bindingFileScanCapacity) {
685
+ throw new Error("The durable binding set exceeds the scan capacity.");
686
+ }
687
+ const authorityFiles = [];
688
+ const bindings = [];
689
+ for (const name of names) {
690
+ if (!name.startsWith("binding-") || !name.endsWith(".json"))
691
+ continue;
692
+ const filePath = path2.join(scopesDir, name);
693
+ authorityFiles.push(filePath);
694
+ const raw = readBoundedFile(filePath);
695
+ if (!raw) {
696
+ throw new Error(`Legacy scope binding ${name} is unreadable.`);
697
+ }
698
+ const binding = validateScopeBindingPayload(directory, JSON.parse(raw));
699
+ if (!binding || !exactFilenameMatches(directory, filePath, binding)) {
700
+ throw new Error(`Legacy scope binding ${name} is malformed.`);
701
+ }
702
+ const intentPath = getRetirementIntentPath(filePath);
703
+ if (hasDurableRetirementIntent(filePath, binding)) {
704
+ authorityFiles.push(intentPath);
705
+ let createdAt = Date.now();
706
+ const rawIntent = readBoundedFile(intentPath);
707
+ if (!rawIntent) {
708
+ throw new Error(`Legacy retirement intent ${path2.basename(intentPath)} is unreadable.`);
709
+ }
710
+ const parsedIntent = JSON.parse(rawIntent);
711
+ if (parsedIntent.bindingId !== binding.bindingId || parsedIntent.generationId !== binding.generationId) {
712
+ throw new Error(`Legacy retirement intent ${path2.basename(intentPath)} mismatches its generation.`);
713
+ }
714
+ if (typeof parsedIntent.createdAt === "number")
715
+ createdAt = parsedIntent.createdAt;
716
+ bindings.push({
717
+ ...binding,
718
+ lifecycleState: "revoked",
719
+ updatedAt: createdAt,
720
+ expiresAt: Math.min(binding.expiresAt, createdAt)
721
+ });
722
+ continue;
723
+ }
724
+ bindings.push(binding);
725
+ }
726
+ for (const name of names) {
727
+ if (!/^claim-[a-f0-9]{40}\.json$/i.test(name))
728
+ continue;
729
+ const filePath = path2.join(scopesDir, name);
730
+ authorityFiles.push(filePath);
731
+ const raw = readBoundedFile(filePath);
732
+ if (!raw)
733
+ throw new Error(`Legacy claim receipt ${name} is unreadable.`);
734
+ const parsed = JSON.parse(raw);
735
+ if (parsed.version !== 1 || !isScopeBindingIdentity(parsed.predecessorGenerationId) || !isScopeBindingIdentity(parsed.winnerGenerationId) || typeof parsed.childSessionId !== "string" || !parsed.childSessionId.trim() || typeof parsed.dispatchCallId !== "string" || !parsed.dispatchCallId.trim()) {
736
+ throw new Error(`Legacy claim receipt ${name} is malformed.`);
737
+ }
738
+ const winner = bindings.find((binding) => binding.generationId === parsed.winnerGenerationId);
739
+ if (!winner) {
740
+ throw new Error(`Legacy claim receipt ${name} has no winner generation.`);
741
+ }
742
+ if (winner.lifecycleState === "live" && winner.ownerSessionId !== parsed.childSessionId) {
743
+ throw new Error(`Legacy claim receipt ${name} disagrees with the winner generation.`);
744
+ }
745
+ }
746
+ const digest = createHash("sha256");
747
+ for (const filePath of authorityFiles) {
748
+ if (!fs.existsSync(filePath))
749
+ continue;
750
+ digest.update(path2.basename(filePath));
751
+ digest.update(fs.readFileSync(filePath));
752
+ }
753
+ return {
754
+ bindings,
755
+ authorityFiles,
756
+ sourceDigest: digest.digest("hex"),
757
+ rowCount: bindings.length
758
+ };
759
+ }
760
+ function ensureScopeBindingAuthorityImported(directory) {
761
+ try {
762
+ const authorityExists = listCoordinationStates(directory, SCOPE_BINDING_COORDINATION_NAMESPACE, 1).length > 0;
763
+ if (authorityExists) {
764
+ const importStatement = getProjectDb(directory).prepare("SELECT source_digest FROM coordination_import WHERE source = ?");
765
+ let importRow;
766
+ try {
767
+ importRow = importStatement.get(SCOPE_BINDING_COORDINATION_IMPORT_SOURCE);
768
+ } finally {
769
+ importStatement.finalize();
770
+ }
771
+ const committedSource = fingerprintCommittedScopeImportSource(directory);
772
+ if (!importRow || !committedSource || importRow.source_digest !== committedSource.sourceDigest) {
773
+ return { ok: true, value: undefined };
774
+ }
775
+ }
776
+ const legacy = collectLegacyScopeBindingsForImport(directory);
777
+ if (legacy.rowCount === 0 && legacy.authorityFiles.length === 0) {
778
+ return { ok: true, value: undefined };
779
+ }
780
+ const importedBindings = legacy.bindings.map((binding) => ({ ...binding }));
781
+ if (authorityExists) {
782
+ archiveImportedScopeAuthorityFiles(legacy.authorityFiles);
783
+ for (const binding of importedBindings) {
784
+ writeScopeBindingShadow(directory, binding);
785
+ }
786
+ return { ok: true, value: undefined };
787
+ }
788
+ const outcome = importCoordinationOnce(directory, {
789
+ source: SCOPE_BINDING_COORDINATION_IMPORT_SOURCE,
790
+ sourceDigest: legacy.sourceDigest,
791
+ rowCount: legacy.rowCount,
792
+ emptyNamespace: SCOPE_BINDING_COORDINATION_NAMESPACE
793
+ }, () => {
794
+ importScopeBindingStateRows(directory, importedBindings);
795
+ });
796
+ if (outcome === "imported" || outcome === "already_imported") {
797
+ archiveImportedScopeAuthorityFiles(legacy.authorityFiles);
798
+ for (const binding of importedBindings) {
799
+ writeScopeBindingShadow(directory, binding);
800
+ }
801
+ }
802
+ return { ok: true, value: undefined };
803
+ } catch (error) {
804
+ return persistenceFailure(`Scope binding import failed closed: ${error instanceof Error ? error.message : "unknown import error"}`);
805
+ }
806
+ }
807
+ function readAllAuthoritativeScopeBindings(directory, options = {}) {
808
+ const imported = ensureScopeBindingAuthorityImported(directory);
809
+ if (!imported.ok)
810
+ return imported;
811
+ try {
812
+ const scanCapacity = options.maintenanceScan ? maintenanceFileScanCapacity : bindingFileScanCapacity;
813
+ const rows = listCoordinationStates(directory, SCOPE_BINDING_COORDINATION_NAMESPACE, scanCapacity + 1);
814
+ if (rows.length > scanCapacity) {
815
+ return {
816
+ ok: false,
817
+ code: "SCOPE_BINDING_STORE_OVERLOADED",
818
+ message: "The durable binding set exceeds the scan capacity."
819
+ };
820
+ }
821
+ const bindings = rows.map((row) => parseCoordinationScopeBinding(directory, row));
822
+ if (bindings.some((binding) => binding === null)) {
823
+ return persistenceFailure("The authoritative scope binding set contains malformed coordination rows.");
824
+ }
825
+ const complete = bindings;
826
+ const liveCount = complete.filter((binding) => binding.lifecycleState === "live" && binding.expiresAt > Date.now()).length;
827
+ if (options.enforceLiveCapacity !== false && liveCount > liveBindingCapacity) {
828
+ return {
829
+ ok: false,
830
+ code: "SCOPE_BINDING_STORE_OVERLOADED",
831
+ message: "The durable binding set exceeds the live binding capacity."
832
+ };
833
+ }
834
+ return { ok: true, value: complete };
835
+ } catch (error) {
836
+ return persistenceFailure(`Authoritative scope binding read failed: ${error instanceof Error ? error.message : "unknown coordination error"}`);
837
+ }
838
+ }
467
839
  function deterministicIdentity(value) {
468
840
  const hex = createHash("sha256").update(value).digest("hex").slice(0, 32);
469
841
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20)}`;
@@ -683,49 +1055,38 @@ async function writeScopeBindingToDisk(directory, binding) {
683
1055
  const files = normalizeScopeFiles(binding.files);
684
1056
  if (!workspaceIdentity || workspaceIdentity !== binding.workspaceIdentity || !files || !binding.ownerSessionId || !binding.ownerMessageId || !isScopeBindingIdentity(binding.bindingId) || !isScopeBindingIdentity(binding.generationId) || binding.revision < 1 || binding.lifecycleState === "live" && binding.expiresAt <= Date.now())
685
1057
  return persistenceFailure("Binding identity, workspace, scope, or lease is invalid.");
686
- const scopesDir = getScopesDir(directory);
687
- const scopePath = getBindingFilePath(directory, binding);
688
- try {
689
- fs.mkdirSync(scopesDir, { recursive: true });
690
- } catch {
691
- return persistenceFailure("Could not create the durable scope store.");
1058
+ const imported = ensureScopeBindingAuthorityImported(directory);
1059
+ if (!imported.ok)
1060
+ return imported;
1061
+ const all = readAllAuthoritativeScopeBindings(directory, {
1062
+ enforceLiveCapacity: false
1063
+ });
1064
+ if (!all.ok)
1065
+ return all;
1066
+ const current = all.value.find((candidate) => candidate.generationId === binding.generationId);
1067
+ if (current && current.bindingId !== binding.bindingId) {
1068
+ return {
1069
+ ok: false,
1070
+ code: "SCOPE_BINDING_STALE",
1071
+ message: "The exact generation identity collides with another binding."
1072
+ };
692
1073
  }
693
- if (!isScopesDirSafe(directory, scopesDir))
694
- return persistenceFailure("The durable scope store failed containment checks.");
695
- const content = JSON.stringify({ ...binding, files }, null, 2);
696
- try {
697
- const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT;
698
- const nofollow = fs.constants.O_NOFOLLOW ?? 0;
699
- const fd = fs.openSync(scopePath, flags | nofollow);
700
- fs.closeSync(fd);
701
- } catch {
702
- return persistenceFailure("Could not create the exact generation record.");
1074
+ if (current && current.revision === binding.revision && current.lifecycleState === binding.lifecycleState && JSON.stringify(current.files) === JSON.stringify(files)) {
1075
+ writeScopeBindingShadow(directory, current);
1076
+ return { ok: true, value: current };
703
1077
  }
704
- let release;
705
- try {
706
- const lockPath = getGenerationLockPath(scopePath);
707
- if (!await ensureLockTarget(lockPath))
708
- return persistenceFailure("Could not prepare exact generation lock.");
709
- release = await import_proper_lockfile.default.lock(lockPath, {
710
- stale: LOCK_STALE_MS,
711
- retries: { retries: 3, minTimeout: 50, maxTimeout: 200 },
712
- realpath: false
713
- });
714
- await atomicWrite(scopePath, content);
715
- const verifiedRaw = readBoundedFile(scopePath);
716
- const verified = verifiedRaw ? validateScopeBindingPayload(directory, JSON.parse(verifiedRaw)) : null;
717
- if (!verified || verified.bindingId !== binding.bindingId || verified.generationId !== binding.generationId || verified.revision !== binding.revision)
718
- return persistenceFailure("Exact generation verification failed after write.");
719
- return { ok: true, value: verified };
720
- } catch (error) {
721
- return persistenceFailure(`Exact generation write failed: ${error instanceof Error ? error.message : "unknown I/O error"}`);
722
- } finally {
723
- if (release) {
724
- try {
725
- await release();
726
- } catch {}
727
- }
1078
+ const expectedRevision = current ? binding.revision - 1 : null;
1079
+ if (binding.revision !== (current?.revision ?? 0) + 1) {
1080
+ return {
1081
+ ok: false,
1082
+ code: "SCOPE_BINDING_STALE",
1083
+ message: "Exact generation write requires the next CAS revision."
1084
+ };
728
1085
  }
1086
+ const persisted = transitionScopeBindingState(directory, { ...binding, files }, expectedRevision);
1087
+ if (persisted.ok)
1088
+ writeScopeBindingShadow(directory, persisted.value);
1089
+ return persisted;
729
1090
  }
730
1091
  function persistenceFailure(message) {
731
1092
  return { ok: false, code: "SCOPE_BINDING_PERSISTENCE_FAILED", message };
@@ -746,6 +1107,69 @@ async function persistAndRegisterScopeBinding(directory, binding) {
746
1107
  message: rollback.ok ? admission.message : `Memory admission failed and rollback failed: ${rollback.message}`
747
1108
  };
748
1109
  }
1110
+ function rollbackAdmissionFailure(directory, persisted, predecessors, admission) {
1111
+ const now = Date.now();
1112
+ const revokedCandidate = {
1113
+ ...persisted,
1114
+ revision: persisted.revision + 1,
1115
+ lifecycleState: "revoked",
1116
+ updatedAt: now,
1117
+ expiresAt: Math.min(persisted.expiresAt, now)
1118
+ };
1119
+ const restoredCandidates = predecessors.map(({ original, superseded }) => ({
1120
+ ...original,
1121
+ revision: superseded.revision + 1,
1122
+ lifecycleState: "live",
1123
+ updatedAt: now
1124
+ }));
1125
+ let failureMessage = null;
1126
+ try {
1127
+ withCoordinationTransaction(directory, () => {
1128
+ const revoked = _scopePersistenceInternals.transitionScopeBindingStateWithinTransaction(directory, revokedCandidate, persisted.revision);
1129
+ if (!revoked.ok) {
1130
+ failureMessage = revoked.message;
1131
+ throw new Error("Scope rollback revocation transition failed.");
1132
+ }
1133
+ for (let index = 0;index < restoredCandidates.length; index += 1) {
1134
+ const restored = restoredCandidates[index];
1135
+ const superseded = predecessors[index].superseded;
1136
+ const result = _scopePersistenceInternals.transitionScopeBindingStateWithinTransaction(directory, restored, superseded.revision);
1137
+ if (!result.ok) {
1138
+ failureMessage = result.message;
1139
+ throw new Error("Scope rollback predecessor restore failed.");
1140
+ }
1141
+ }
1142
+ });
1143
+ } catch (error) {
1144
+ return persistenceFailure(`Memory admission failed and durable rollback failed: ${failureMessage ?? (error instanceof Error ? error.message : "unknown coordination error")}`);
1145
+ }
1146
+ installScopeBindingTombstone(revokedCandidate);
1147
+ writeScopeBindingShadow(directory, revokedCandidate);
1148
+ const admitted = [];
1149
+ for (const restored of restoredCandidates) {
1150
+ writeScopeBindingShadow(directory, restored);
1151
+ const memory = updateExactScopeBinding(restored);
1152
+ const admissionResult = memory.ok ? memory : registerScopeBinding(restored);
1153
+ if (!admissionResult.ok) {
1154
+ for (const applied of admitted) {
1155
+ installScopeBindingTombstone({
1156
+ ...applied,
1157
+ revision: applied.revision + 1,
1158
+ lifecycleState: "revoked",
1159
+ updatedAt: Date.now(),
1160
+ expiresAt: Math.min(applied.expiresAt, Date.now())
1161
+ });
1162
+ }
1163
+ return persistenceFailure(`Memory admission failed and predecessor re-admission failed: ${admissionResult.message}`);
1164
+ }
1165
+ admitted.push(restored);
1166
+ }
1167
+ return {
1168
+ ok: false,
1169
+ code: admission.code,
1170
+ message: admission.message
1171
+ };
1172
+ }
749
1173
  function readScopeBindingFromDisk(input) {
750
1174
  const resolved = resolveScopeBindingFromDisk({
751
1175
  ...input,
@@ -756,12 +1180,14 @@ function readScopeBindingFromDisk(input) {
756
1180
  function resolveScopeBindingFromDisk(input) {
757
1181
  if (!isSafeTaskId(input.taskId))
758
1182
  return { status: "not_declared" };
759
- const completeSet = readAllExactBindings(input.directory);
760
- if (!completeSet) {
761
- scheduleScopeBindingMaintenance(input.directory);
1183
+ const completeSet = readAllAuthoritativeScopeBindings(input.directory);
1184
+ if (!completeSet.ok) {
1185
+ if (completeSet.code === "SCOPE_BINDING_STORE_OVERLOADED") {
1186
+ scheduleScopeBindingMaintenance(input.directory);
1187
+ }
762
1188
  return { status: "overloaded" };
763
1189
  }
764
- const candidates = completeSet.filter((parsed) => parsed.taskId === input.taskId && parsed.planId === derivePlanId(input.plan) && parsed.planStructureHash === computePlanStructureHash(input.plan) && parsed.ownerSessionId === input.ownerSessionId && (input.requireDispatchCorrelation !== true || isDispatchCorrelated(parsed)) && (input.requireDeclaration !== true || parsed.activation === "declaration" && parsed.dispatchCallId === undefined) && (input.parentCallId === undefined || parsed.parentCallId === input.parentCallId));
1190
+ const candidates = completeSet.value.filter((parsed) => parsed.taskId === input.taskId && parsed.planId === derivePlanId(input.plan) && parsed.planStructureHash === computePlanStructureHash(input.plan) && parsed.ownerSessionId === input.ownerSessionId && (input.requireDispatchCorrelation !== true || isDispatchCorrelated2(parsed)) && (input.requireDeclaration !== true || parsed.activation === "declaration" && parsed.dispatchCallId === undefined) && (input.parentCallId === undefined || parsed.parentCallId === input.parentCallId));
765
1191
  const live = candidates.filter((candidate) => candidate.lifecycleState === "live" && candidate.expiresAt > Date.now());
766
1192
  if (live.length > 1)
767
1193
  return {
@@ -772,13 +1198,29 @@ function resolveScopeBindingFromDisk(input) {
772
1198
  if (live.length === 1)
773
1199
  return { status: "found", binding: live[0] };
774
1200
  const expired = candidates.filter((candidate) => candidate.lifecycleState !== "live" || candidate.expiresAt <= Date.now());
1201
+ if (candidates.length > 0 && !input.includeExpired) {
1202
+ return { status: "not_declared" };
1203
+ }
775
1204
  return input.includeExpired && expired.length > 0 ? {
776
1205
  status: "expired",
777
1206
  candidates: expired.slice(0, 8),
778
1207
  totalCandidates: expired.length
779
- } : { status: "not_declared" };
1208
+ } : (() => {
1209
+ const inMemory = input.requireDeclaration === true ? [] : getActiveScopeBindingsForSession({
1210
+ directory: input.directory,
1211
+ activeSessionId: input.ownerSessionId
1212
+ }).filter((binding) => binding.taskId === input.taskId && binding.planId === derivePlanId(input.plan) && binding.planStructureHash === computePlanStructureHash(input.plan) && (input.requireDispatchCorrelation !== true || isDispatchCorrelated2(binding)) && (input.parentCallId === undefined || binding.parentCallId === input.parentCallId));
1213
+ if (inMemory.length > 1) {
1214
+ return {
1215
+ status: "ambiguous",
1216
+ candidates: inMemory.slice(0, 8),
1217
+ totalCandidates: inMemory.length
1218
+ };
1219
+ }
1220
+ return inMemory.length === 1 ? { status: "found", binding: inMemory[0] } : { status: "not_declared" };
1221
+ })();
780
1222
  }
781
- function isDispatchCorrelated(binding) {
1223
+ function isDispatchCorrelated2(binding) {
782
1224
  return binding.activation === "active" && typeof binding.dispatchCallId === "string" && binding.dispatchCallId.length > 0 && binding.ownerMessageId === binding.dispatchCallId && typeof binding.parentOwnerSessionId === "string" && binding.parentOwnerSessionId.length > 0 && binding.ownerSessionId !== binding.parentOwnerSessionId && binding.parentCallId === binding.dispatchCallId;
783
1225
  }
784
1226
  function clearScopeBindingFromDisk(input) {
@@ -788,285 +1230,156 @@ function clearScopeBindingFromDisk(input) {
788
1230
  } catch (error) {
789
1231
  return persistenceFailure(`Scope retirement project-root validation failed: ${error instanceof Error ? error.message : "unknown validation error"}`);
790
1232
  }
791
- let release;
792
- try {
793
- const resolvedWorkspace = fs.realpathSync(input.directory);
794
- const scopePath = getBindingFilePath(resolvedWorkspace, input.binding);
795
- if (!isScopesDirSafe(resolvedWorkspace, getScopesDir(resolvedWorkspace)))
796
- return persistenceFailure("The durable scope store failed containment checks.");
797
- const retirementIntentPath = getRetirementIntentPath(scopePath);
798
- atomicWriteSync(retirementIntentPath, JSON.stringify({
799
- version: 1,
800
- bindingId: input.binding.bindingId,
801
- generationId: input.binding.generationId,
802
- createdAt: Date.now()
803
- }));
804
- scheduleScopeBindingMaintenance(resolvedWorkspace);
805
- const lockPath = getGenerationLockPath(scopePath);
806
- if (!ensureLockTargetSync(lockPath))
807
- return persistenceFailure("Could not prepare exact retirement lock.");
808
- release = lockSyncWithBoundedRetry(lockPath);
809
- const raw = readBoundedFile(scopePath);
810
- if (!raw) {
811
- try {
812
- fs.unlinkSync(retirementIntentPath);
813
- } catch {}
814
- return localRetirement ? { ok: true, value: localRetirement } : persistenceFailure("Exact generation disappeared before serialized retirement.");
815
- }
816
- const current = validateScopeBindingPayload(resolvedWorkspace, JSON.parse(raw));
817
- if (!current || current.bindingId !== input.binding.bindingId || current.generationId !== input.binding.generationId)
818
- return persistenceFailure("Exact generation identity changed before serialized retirement.");
819
- if (current.lifecycleState !== "live") {
820
- installScopeBindingTombstone(current);
821
- try {
822
- fs.unlinkSync(retirementIntentPath);
823
- } catch {}
824
- return { ok: true, value: current };
825
- }
826
- const now = Date.now();
827
- const tombstone = {
828
- ...current,
829
- revision: current.revision + 1,
830
- lifecycleState: "revoked",
831
- updatedAt: now,
832
- expiresAt: Math.min(current.expiresAt, now)
833
- };
834
- atomicWriteSync(scopePath, JSON.stringify(tombstone, null, 2));
835
- const verifiedRaw = readBoundedFile(scopePath);
836
- const verified = verifiedRaw ? validateScopeBindingPayload(resolvedWorkspace, JSON.parse(verifiedRaw)) : null;
837
- if (!verified || verified.revision !== tombstone.revision || verified.lifecycleState !== "revoked")
838
- return persistenceFailure("Serialized retirement verification failed.");
839
- installScopeBindingTombstone(verified);
840
- try {
841
- fs.unlinkSync(retirementIntentPath);
842
- } catch {}
843
- scheduleScopeBindingMaintenance(resolvedWorkspace);
844
- return { ok: true, value: verified };
845
- } catch (error) {
846
- return persistenceFailure(`Serialized retirement failed: ${error instanceof Error ? error.message : "unknown I/O error"}`);
847
- } finally {
848
- try {
849
- release?.();
850
- } catch {}
1233
+ const imported = ensureScopeBindingAuthorityImported(input.directory);
1234
+ if (!imported.ok)
1235
+ return imported;
1236
+ const all = readAllAuthoritativeScopeBindings(input.directory, {
1237
+ enforceLiveCapacity: false
1238
+ });
1239
+ if (!all.ok)
1240
+ return all;
1241
+ const current = all.value.find((candidate) => candidate.generationId === input.binding.generationId && candidate.bindingId === input.binding.bindingId);
1242
+ if (!current) {
1243
+ return localRetirement ? { ok: true, value: localRetirement } : persistenceFailure("Exact generation disappeared before serialized retirement.");
851
1244
  }
852
- }
853
- async function tombstoneScopeBinding(directory, binding, reason) {
854
- const currentPath = getBindingFilePath(directory, binding);
855
- const generationLockPath = getGenerationLockPath(currentPath);
856
- const lockReady = await ensureLockTarget(generationLockPath);
857
- if (!lockReady) {
858
- installFailedRevocationOverlay(binding, reason);
859
- return persistenceFailure("Could not prepare exact generation lock.");
1245
+ if (current.lifecycleState !== "live") {
1246
+ installScopeBindingTombstone(current);
1247
+ writeScopeBindingShadow(input.directory, current);
1248
+ return { ok: true, value: current };
860
1249
  }
861
- let release;
862
- try {
863
- release = await import_proper_lockfile.default.lock(generationLockPath, {
864
- stale: LOCK_STALE_MS,
865
- retries: { retries: 3, minTimeout: 50, maxTimeout: 200 },
866
- realpath: false
867
- });
868
- const raw = readBoundedFile(currentPath);
869
- if (!raw) {
870
- installFailedRevocationOverlay(binding, reason);
871
- return persistenceFailure("Exact generation disappeared before tombstone.");
872
- }
873
- const current = validateScopeBindingPayload(directory, JSON.parse(raw));
874
- if (!current || current.bindingId !== binding.bindingId || current.generationId !== binding.generationId) {
875
- installFailedRevocationOverlay(binding, reason);
876
- return persistenceFailure("Exact generation identity changed before tombstone.");
877
- }
878
- if (current.revision !== binding.revision)
879
- return {
880
- ok: false,
881
- code: "SCOPE_BINDING_STALE",
882
- message: "Tombstone CAS revision is stale."
883
- };
884
- if (current.lifecycleState !== "live")
885
- return { ok: true, value: current };
886
- const now = Date.now();
887
- const tombstone = {
888
- ...current,
889
- revision: current.revision + 1,
890
- lifecycleState: reason,
891
- updatedAt: now,
892
- expiresAt: Math.min(current.expiresAt, now)
1250
+ const now = Date.now();
1251
+ const tombstone = {
1252
+ ...current,
1253
+ revision: current.revision + 1,
1254
+ lifecycleState: "revoked",
1255
+ updatedAt: now,
1256
+ expiresAt: Math.min(current.expiresAt, now)
1257
+ };
1258
+ const retired = transitionScopeBindingState(input.directory, tombstone, current.revision);
1259
+ if (!retired.ok)
1260
+ return retired;
1261
+ installScopeBindingTombstone(retired.value);
1262
+ writeScopeBindingShadow(input.directory, retired.value);
1263
+ scheduleScopeBindingMaintenance(input.directory);
1264
+ return retired;
1265
+ }
1266
+ async function tombstoneScopeBinding(directory, binding, reason) {
1267
+ const imported = ensureScopeBindingAuthorityImported(directory);
1268
+ if (!imported.ok) {
1269
+ installFailedRevocationOverlay(binding, reason);
1270
+ return imported;
1271
+ }
1272
+ const all = readAllAuthoritativeScopeBindings(directory, {
1273
+ enforceLiveCapacity: false
1274
+ });
1275
+ if (!all.ok) {
1276
+ installFailedRevocationOverlay(binding, reason);
1277
+ return all;
1278
+ }
1279
+ const current = all.value.find((candidate) => candidate.generationId === binding.generationId && candidate.bindingId === binding.bindingId);
1280
+ if (!current) {
1281
+ installFailedRevocationOverlay(binding, reason);
1282
+ return persistenceFailure("Exact generation disappeared before tombstone.");
1283
+ }
1284
+ if (current.revision !== binding.revision) {
1285
+ return {
1286
+ ok: false,
1287
+ code: "SCOPE_BINDING_STALE",
1288
+ message: "Tombstone CAS revision is stale."
893
1289
  };
894
- await atomicWrite(currentPath, JSON.stringify(tombstone, null, 2));
895
- const verifiedRaw = readBoundedFile(currentPath);
896
- const verified = verifiedRaw ? validateScopeBindingPayload(directory, JSON.parse(verifiedRaw)) : null;
897
- if (!verified || verified.revision !== tombstone.revision || verified.lifecycleState !== reason) {
898
- installFailedRevocationOverlay(binding, reason);
899
- return persistenceFailure("Tombstone verification failed.");
900
- }
901
- clearExactScopeBinding(binding);
902
- installScopeBindingTombstone(verified);
903
- scheduleScopeBindingMaintenance(directory);
904
- return { ok: true, value: verified };
905
- } catch (error) {
1290
+ }
1291
+ if (current.lifecycleState !== "live")
1292
+ return { ok: true, value: current };
1293
+ const now = Date.now();
1294
+ const tombstone = {
1295
+ ...current,
1296
+ revision: current.revision + 1,
1297
+ lifecycleState: reason,
1298
+ updatedAt: now,
1299
+ expiresAt: Math.min(current.expiresAt, now)
1300
+ };
1301
+ const persisted = transitionScopeBindingState(directory, tombstone, current.revision);
1302
+ if (!persisted.ok) {
906
1303
  installFailedRevocationOverlay(binding, reason);
907
- return persistenceFailure(`Tombstone failed: ${error instanceof Error ? error.message : "unknown I/O error"}`);
908
- } finally {
909
- if (release)
910
- try {
911
- await release();
912
- } catch {}
1304
+ return persisted;
913
1305
  }
1306
+ clearExactScopeBinding(binding);
1307
+ installScopeBindingTombstone(persisted.value);
1308
+ writeScopeBindingShadow(directory, persisted.value);
1309
+ scheduleScopeBindingMaintenance(directory);
1310
+ return persisted;
914
1311
  }
915
1312
  async function ensureLockTarget(targetPath) {
916
1313
  return ensureLockTargetSync(targetPath);
917
1314
  }
918
1315
  async function refreshScopeBindingLease(input) {
919
- let scopePath;
920
- try {
921
- scopePath = getBindingFilePath(input.directory, input);
922
- } catch {
923
- return persistenceFailure("Invalid refresh identity.");
924
- }
925
- const generationLockPath = getGenerationLockPath(scopePath);
926
- const createdGenerationLockTarget = !fs.existsSync(generationLockPath);
927
- if (!await ensureLockTarget(generationLockPath))
928
- return persistenceFailure("Could not prepare refresh lock.");
929
- let release;
930
- try {
931
- release = await import_proper_lockfile.default.lock(generationLockPath, {
932
- stale: LOCK_STALE_MS,
933
- retries: { retries: 3, minTimeout: 50, maxTimeout: 200 },
934
- realpath: false
935
- });
936
- if (hasDurableRetirementIntent(scopePath, input))
937
- return {
938
- ok: false,
939
- code: "SCOPE_BINDING_STALE",
940
- message: "Refresh cannot renew a generation with durable retirement intent."
941
- };
942
- const raw = readBoundedFile(scopePath);
943
- const current = raw ? validateScopeBindingPayload(input.directory, JSON.parse(raw)) : null;
944
- if (!current || current.bindingId !== input.bindingId || current.generationId !== input.generationId || current.taskId !== input.taskId || current.ownerSessionId !== input.activeSessionId)
945
- return {
946
- ok: false,
947
- code: "SCOPE_BINDING_STALE",
948
- message: "Refresh identity is not the exact current session/task generation."
949
- };
950
- if (current.lifecycleState !== "live" || current.expiresAt <= Date.now() || current.revision !== input.expectedRevision)
951
- return {
952
- ok: false,
953
- code: current.expiresAt <= Date.now() ? "SCOPE_BINDING_EXPIRED" : "SCOPE_BINDING_STALE",
954
- message: "Refresh cannot renew an expired, tombstoned, or stale generation."
955
- };
956
- const now = Date.now();
957
- const refreshed = {
958
- ...current,
959
- revision: current.revision + 1,
960
- updatedAt: now,
961
- leaseStartedAt: now,
962
- expiresAt: now + Math.max(1, input.ttlMs ?? DEFAULT_SCOPE_BINDING_TTL_MS)
1316
+ const imported = ensureScopeBindingAuthorityImported(input.directory);
1317
+ if (!imported.ok)
1318
+ return imported;
1319
+ const all = readAllAuthoritativeScopeBindings(input.directory, {
1320
+ enforceLiveCapacity: false
1321
+ });
1322
+ if (!all.ok)
1323
+ return all;
1324
+ const current = all.value.find((candidate) => candidate.bindingId === input.bindingId && candidate.generationId === input.generationId && candidate.taskId === input.taskId && candidate.ownerSessionId === input.activeSessionId);
1325
+ if (!current) {
1326
+ return {
1327
+ ok: false,
1328
+ code: "SCOPE_BINDING_STALE",
1329
+ message: "Refresh identity is not the exact current session/task generation."
963
1330
  };
964
- await atomicWrite(scopePath, JSON.stringify(refreshed, null, 2));
965
- const verifyRaw = readBoundedFile(scopePath);
966
- const verified = verifyRaw ? validateScopeBindingPayload(input.directory, JSON.parse(verifyRaw)) : null;
967
- if (!verified || verified.revision !== refreshed.revision || verified.expiresAt !== refreshed.expiresAt)
968
- return persistenceFailure("Refreshed lease verification failed.");
969
- updateExactScopeBinding(verified);
970
- return { ok: true, value: verified };
971
- } catch (error) {
972
- return persistenceFailure(`Lease refresh failed: ${error instanceof Error ? error.message : "unknown I/O error"}`);
973
- } finally {
974
- if (release)
975
- try {
976
- await release();
977
- } catch {}
978
- if (createdGenerationLockTarget && !fs.existsSync(scopePath)) {
979
- try {
980
- fs.unlinkSync(generationLockPath);
981
- } catch {}
982
- }
983
1331
  }
984
- }
985
- function claimBaseName(binding) {
986
- const digest = createHash("sha256").update(`${binding.bindingId}\x00${binding.generationId}\x00${binding.dispatchCallId ?? ""}`).digest("hex").slice(0, 40);
987
- return `claim-${digest}`;
988
- }
989
- function readAllExactBindings(directory, options = {}) {
990
- const scopesDir = getScopesDir(directory);
991
- try {
992
- fs.lstatSync(scopesDir);
993
- } catch (error) {
994
- return error.code === "ENOENT" ? [] : null;
995
- }
996
- if (!isScopesDirSafe(directory, scopesDir))
997
- return null;
998
- const scanCapacity = options.maintenanceScan ? maintenanceFileScanCapacity : bindingFileScanCapacity;
999
- if (!migrateLegacyBindingsSync(directory, scanCapacity))
1000
- return null;
1001
- let names;
1002
- try {
1003
- names = fs.readdirSync(scopesDir).sort();
1004
- } catch {
1005
- return [];
1006
- }
1007
- if (names.length > scanCapacity)
1008
- return null;
1009
- const bindings = [];
1010
- for (const name of names) {
1011
- if (!name.startsWith("binding-") || !name.endsWith(".json"))
1012
- continue;
1013
- const filePath = path2.join(scopesDir, name);
1014
- const raw = readBoundedFile(filePath);
1015
- if (!raw)
1016
- continue;
1017
- try {
1018
- const binding = validateScopeBindingPayload(directory, JSON.parse(raw));
1019
- if (binding && exactFilenameMatches(directory, filePath, binding)) {
1020
- if (hasDurableRetirementIntent(filePath, binding)) {
1021
- let createdAt = Date.now();
1022
- try {
1023
- const rawIntent = readBoundedFile(getRetirementIntentPath(filePath));
1024
- const parsedIntent = rawIntent ? JSON.parse(rawIntent) : null;
1025
- if (typeof parsedIntent?.createdAt === "number")
1026
- createdAt = parsedIntent.createdAt;
1027
- } catch {}
1028
- bindings.push({
1029
- ...binding,
1030
- lifecycleState: "revoked",
1031
- updatedAt: createdAt,
1032
- expiresAt: Math.min(binding.expiresAt, createdAt)
1033
- });
1034
- } else
1035
- bindings.push(binding);
1036
- }
1037
- } catch {}
1332
+ if (current.lifecycleState !== "live" || current.expiresAt <= Date.now() || current.revision !== input.expectedRevision) {
1333
+ return {
1334
+ ok: false,
1335
+ code: current.expiresAt <= Date.now() ? "SCOPE_BINDING_EXPIRED" : "SCOPE_BINDING_STALE",
1336
+ message: "Refresh cannot renew an expired, tombstoned, or stale generation."
1337
+ };
1038
1338
  }
1039
- const liveCount = bindings.filter((binding) => binding.lifecycleState === "live" && binding.expiresAt > Date.now()).length;
1040
- if (options.enforceLiveCapacity !== false && liveCount > liveBindingCapacity)
1041
- return null;
1042
- return bindings;
1339
+ const now = Date.now();
1340
+ const refreshed = {
1341
+ ...current,
1342
+ revision: current.revision + 1,
1343
+ updatedAt: now,
1344
+ leaseStartedAt: now,
1345
+ expiresAt: now + Math.max(1, input.ttlMs ?? DEFAULT_SCOPE_BINDING_TTL_MS)
1346
+ };
1347
+ const persisted = transitionScopeBindingState(input.directory, refreshed, current.revision);
1348
+ if (!persisted.ok)
1349
+ return persisted;
1350
+ updateExactScopeBinding(persisted.value);
1351
+ writeScopeBindingShadow(input.directory, persisted.value);
1352
+ return persisted;
1043
1353
  }
1044
1354
  async function claimScopeBindingForChildDurably(input) {
1045
1355
  if (!input.parentSessionId.trim() || !input.childSessionId.trim() || !input.dispatchCallId.trim() || input.parentSessionId === input.childSessionId)
1046
1356
  return persistenceFailure("Claim requires distinct non-empty parent/child identities and an exact dispatch call.");
1047
- const initial = readAllExactBindings(input.directory);
1048
- if (!initial)
1049
- return {
1050
- ok: false,
1051
- code: "SCOPE_BINDING_STORE_OVERLOADED",
1052
- message: "The complete durable binding set could not be evaluated."
1053
- };
1054
- const pending = initial.filter((binding) => binding.lifecycleState === "live" && binding.expiresAt > Date.now() && binding.activation === "pending_child" && binding.ownerSessionId === input.parentSessionId && binding.dispatchCallId === input.dispatchCallId && binding.ownerMessageId === input.dispatchCallId);
1055
- if (pending.length > 1)
1357
+ const imported = ensureScopeBindingAuthorityImported(input.directory);
1358
+ if (!imported.ok)
1359
+ return imported;
1360
+ const initial = readAllAuthoritativeScopeBindings(input.directory, {
1361
+ enforceLiveCapacity: false
1362
+ });
1363
+ if (!initial.ok)
1364
+ return initial;
1365
+ const pending = initial.value.filter((binding) => binding.lifecycleState === "live" && binding.expiresAt > Date.now() && binding.activation === "pending_child" && binding.ownerSessionId === input.parentSessionId && binding.dispatchCallId === input.dispatchCallId && binding.ownerMessageId === input.dispatchCallId);
1366
+ if (pending.length > 1) {
1056
1367
  return {
1057
1368
  ok: false,
1058
1369
  code: "SCOPE_BINDING_AMBIGUOUS",
1059
1370
  message: "Multiple pending generations match this exact Task dispatch."
1060
1371
  };
1372
+ }
1061
1373
  let predecessor = pending[0];
1062
1374
  if (!predecessor) {
1063
- const successors = initial.filter((binding) => binding.lifecycleState === "live" && binding.expiresAt > Date.now() && binding.activation === "active" && binding.parentOwnerSessionId === input.parentSessionId && binding.dispatchCallId === input.dispatchCallId && binding.predecessorGenerationId);
1064
- if (successors.length > 1)
1375
+ const successors = initial.value.filter((binding) => binding.lifecycleState === "live" && binding.expiresAt > Date.now() && binding.activation === "active" && binding.parentOwnerSessionId === input.parentSessionId && binding.dispatchCallId === input.dispatchCallId && binding.predecessorGenerationId);
1376
+ if (successors.length > 1) {
1065
1377
  return {
1066
1378
  ok: false,
1067
1379
  code: "SCOPE_BINDING_AMBIGUOUS",
1068
1380
  message: "Multiple active successors match this Task dispatch."
1069
1381
  };
1382
+ }
1070
1383
  const existing = successors[0];
1071
1384
  if (existing?.ownerSessionId === input.childSessionId) {
1072
1385
  const admission = registerScopeBinding(existing);
@@ -1080,7 +1393,7 @@ async function claimScopeBindingForChildDurably(input) {
1080
1393
  ok: false,
1081
1394
  code: "SCOPE_BINDING_ALREADY_CLAIMED",
1082
1395
  message: "This Task dispatch was already claimed by a different child session."
1083
- } : initial.some((candidate) => candidate.ownerSessionId === input.parentSessionId && candidate.dispatchCallId === input.dispatchCallId) ? {
1396
+ } : initial.value.some((candidate) => candidate.ownerSessionId === input.parentSessionId && candidate.dispatchCallId === input.dispatchCallId) ? {
1084
1397
  ok: false,
1085
1398
  code: "SCOPE_BINDING_EXPIRED",
1086
1399
  message: "No live pending generation remains for this Task dispatch."
@@ -1090,136 +1403,91 @@ async function claimScopeBindingForChildDurably(input) {
1090
1403
  message: "No pending generation matches this Task dispatch."
1091
1404
  };
1092
1405
  }
1093
- const claimPath = path2.join(getScopesDir(input.directory), `${claimBaseName(predecessor)}.json`);
1094
- if (!await ensureLockTarget(claimPath))
1095
- return persistenceFailure("Could not prepare the stable predecessor claim lock.");
1096
- let release;
1097
1406
  try {
1098
- release = await import_proper_lockfile.default.lock(claimPath, {
1099
- stale: LOCK_STALE_MS,
1100
- retries: { retries: 5, minTimeout: 50, maxTimeout: 250 },
1101
- realpath: false
1102
- });
1103
- const currentSet = readAllExactBindings(input.directory);
1104
- if (!currentSet)
1105
- return {
1106
- ok: false,
1107
- code: "SCOPE_BINDING_STORE_OVERLOADED",
1108
- message: "The durable binding set became incomplete during claim."
1109
- };
1110
- const currentPredecessor = currentSet.find((binding) => binding.generationId === predecessor.generationId && binding.bindingId === predecessor.bindingId);
1111
- const allSuccessors = currentSet.filter((binding) => binding.predecessorGenerationId === predecessor.generationId && binding.bindingId === predecessor.bindingId);
1112
- const successors = allSuccessors.filter((binding) => binding.lifecycleState === "live" && binding.expiresAt > Date.now());
1113
- if (successors.length > 1)
1114
- return {
1115
- ok: false,
1116
- code: "SCOPE_BINDING_AMBIGUOUS",
1117
- message: "Incompatible successors exist for one predecessor generation."
1118
- };
1119
- const receiptRaw = readBoundedFile(claimPath);
1120
- let receipt = null;
1121
- if (receiptRaw) {
1122
- try {
1123
- const parsed = JSON.parse(receiptRaw);
1124
- if (parsed.version !== 1 || parsed.predecessorGenerationId !== predecessor.generationId || !isScopeBindingIdentity(parsed.winnerGenerationId) || typeof parsed.childSessionId !== "string" || parsed.dispatchCallId !== input.dispatchCallId)
1125
- return persistenceFailure("Claim winner receipt is malformed or belongs to another predecessor.");
1126
- receipt = parsed;
1127
- } catch {
1128
- return persistenceFailure("Claim winner receipt is malformed.");
1407
+ let claimed = null;
1408
+ let retired = null;
1409
+ let alreadyClaimed = null;
1410
+ withCoordinationTransaction(input.directory, () => {
1411
+ const currentSet = readAllAuthoritativeScopeBindings(input.directory, {
1412
+ enforceLiveCapacity: false
1413
+ });
1414
+ if (!currentSet.ok) {
1415
+ throw new ScopeCoordinationAbort(currentSet);
1129
1416
  }
1130
- }
1131
- if (receipt) {
1132
- const receiptWinner = allSuccessors.find((candidate) => candidate.generationId === receipt?.winnerGenerationId);
1133
- if (!receiptWinner)
1134
- return persistenceFailure("Claim receipt has no verified successor; pending remains fail-closed for restart reconciliation.");
1135
- if (receiptWinner.lifecycleState !== "live" || receiptWinner.expiresAt <= Date.now()) {
1136
- fs.writeFileSync(claimPath, "");
1137
- receipt = null;
1417
+ const currentPredecessor = currentSet.value.find((binding) => binding.generationId === predecessor.generationId && binding.bindingId === predecessor.bindingId);
1418
+ const liveSuccessors = currentSet.value.filter((binding) => binding.predecessorGenerationId === predecessor.generationId && binding.bindingId === predecessor.bindingId && binding.lifecycleState === "live" && binding.expiresAt > Date.now());
1419
+ if (liveSuccessors.length > 1) {
1420
+ throw new ScopeCoordinationAbort({
1421
+ ok: false,
1422
+ code: "SCOPE_BINDING_AMBIGUOUS",
1423
+ message: "Incompatible successors exist for one predecessor generation."
1424
+ });
1138
1425
  }
1139
- }
1140
- if (receipt && successors[0] && receipt.winnerGenerationId !== successors[0].generationId)
1141
- return {
1142
- ok: false,
1143
- code: "SCOPE_BINDING_AMBIGUOUS",
1144
- message: "Claim receipt and durable successor disagree."
1145
- };
1146
- const existing = successors[0];
1147
- if (existing) {
1148
- if (existing.ownerSessionId !== input.childSessionId)
1149
- return {
1426
+ const existing = liveSuccessors[0];
1427
+ if (existing) {
1428
+ if (existing.ownerSessionId !== input.childSessionId) {
1429
+ throw new ScopeCoordinationAbort({
1430
+ ok: false,
1431
+ code: "SCOPE_BINDING_ALREADY_CLAIMED",
1432
+ message: "This pending generation has a winner in another child session."
1433
+ });
1434
+ }
1435
+ alreadyClaimed = existing;
1436
+ return;
1437
+ }
1438
+ if (!currentPredecessor || currentPredecessor.lifecycleState !== "live" || currentPredecessor.expiresAt <= Date.now()) {
1439
+ throw new ScopeCoordinationAbort({
1150
1440
  ok: false,
1151
- code: "SCOPE_BINDING_ALREADY_CLAIMED",
1152
- message: "This pending generation has a winner in another child session."
1153
- };
1154
- await writeClaimReceipt(claimPath, predecessor, existing, input);
1155
- if (currentPredecessor?.lifecycleState === "live")
1156
- await tombstoneScopeBinding(input.directory, currentPredecessor, "superseded");
1157
- const admission2 = registerScopeBinding(existing);
1158
- return admission2.ok ? { ok: true, value: { previous: predecessor, claimed: existing } } : {
1441
+ code: "SCOPE_BINDING_EXPIRED",
1442
+ message: "The pending predecessor expired before claim."
1443
+ });
1444
+ }
1445
+ predecessor = currentPredecessor;
1446
+ const nextClaimed = createClaimedScopeBinding(predecessor, input);
1447
+ const claimedWrite = _scopePersistenceInternals.transitionScopeBindingStateWithinTransaction(input.directory, nextClaimed, null);
1448
+ if (!claimedWrite.ok) {
1449
+ throw new ScopeCoordinationAbort(claimedWrite);
1450
+ }
1451
+ const superseded = {
1452
+ ...predecessor,
1453
+ revision: predecessor.revision + 1,
1454
+ lifecycleState: "superseded",
1455
+ updatedAt: claimedWrite.value.updatedAt,
1456
+ expiresAt: Math.min(predecessor.expiresAt, claimedWrite.value.updatedAt)
1457
+ };
1458
+ const retiredWrite = _scopePersistenceInternals.transitionScopeBindingStateWithinTransaction(input.directory, superseded, predecessor.revision);
1459
+ if (!retiredWrite.ok) {
1460
+ throw new ScopeCoordinationAbort(retiredWrite);
1461
+ }
1462
+ claimed = claimedWrite.value;
1463
+ retired = retiredWrite.value;
1464
+ });
1465
+ if (alreadyClaimed) {
1466
+ const admission2 = registerScopeBinding(alreadyClaimed);
1467
+ return admission2.ok ? {
1468
+ ok: true,
1469
+ value: { previous: predecessor, claimed: alreadyClaimed }
1470
+ } : {
1159
1471
  ok: false,
1160
1472
  code: admission2.code,
1161
1473
  message: admission2.message
1162
1474
  };
1163
1475
  }
1164
- if (!currentPredecessor || currentPredecessor.lifecycleState !== "live" || currentPredecessor.expiresAt <= Date.now())
1165
- return {
1166
- ok: false,
1167
- code: "SCOPE_BINDING_EXPIRED",
1168
- message: "The pending predecessor expired before claim."
1169
- };
1170
- predecessor = currentPredecessor;
1171
- const claimed = createClaimedScopeBinding(predecessor, input);
1172
- const persisted = await writeScopeBindingToDisk(input.directory, claimed);
1173
- if (!persisted.ok)
1174
- return persisted;
1175
- const receiptWrite = await writeClaimReceipt(claimPath, predecessor, persisted.value, input);
1176
- if (!receiptWrite.ok) {
1177
- await tombstoneScopeBinding(input.directory, persisted.value, "revoked");
1178
- return receiptWrite;
1476
+ if (!predecessor || !claimed || !retired) {
1477
+ return persistenceFailure("Claim transaction did not settle a winner.");
1179
1478
  }
1180
- const admission = registerScopeBinding(persisted.value);
1479
+ writeScopeBindingShadow(input.directory, claimed);
1480
+ writeScopeBindingShadow(input.directory, retired);
1481
+ const admission = registerScopeBinding(claimed);
1181
1482
  if (!admission.ok) {
1182
- await tombstoneScopeBinding(input.directory, persisted.value, "revoked");
1183
- return {
1184
- ok: false,
1185
- code: admission.code,
1186
- message: admission.message
1187
- };
1188
- }
1189
- const retired = await tombstoneScopeBinding(input.directory, predecessor, "superseded");
1190
- if (!retired.ok) {
1191
- await tombstoneScopeBinding(input.directory, persisted.value, "revoked");
1192
- return persistenceFailure(`Successor was persisted but predecessor retirement failed: ${retired.message}`);
1483
+ return rollbackAdmissionFailure(input.directory, claimed, [{ original: predecessor, superseded: retired }], admission);
1193
1484
  }
1194
- return {
1195
- ok: true,
1196
- value: { previous: predecessor, claimed: persisted.value }
1197
- };
1485
+ installScopeBindingTombstone(retired);
1486
+ return { ok: true, value: { previous: predecessor, claimed } };
1198
1487
  } catch (error) {
1199
- return persistenceFailure(`Claim transaction failed: ${error instanceof Error ? error.message : "unknown I/O error"}`);
1200
- } finally {
1201
- if (release)
1202
- try {
1203
- await release();
1204
- } catch {}
1205
- }
1206
- }
1207
- async function writeClaimReceipt(claimPath, previous, claimed, input) {
1208
- const receipt = {
1209
- version: 1,
1210
- predecessorGenerationId: previous.generationId,
1211
- winnerGenerationId: claimed.generationId,
1212
- childSessionId: input.childSessionId,
1213
- dispatchCallId: input.dispatchCallId,
1214
- createdAt: Date.now()
1215
- };
1216
- try {
1217
- await atomicWrite(claimPath, JSON.stringify(receipt, null, 2));
1218
- const raw = readBoundedFile(claimPath);
1219
- const verified = raw ? JSON.parse(raw) : null;
1220
- return verified?.winnerGenerationId === receipt.winnerGenerationId && verified.childSessionId === receipt.childSessionId ? { ok: true, value: verified } : persistenceFailure("Claim winner receipt verification failed.");
1221
- } catch (error) {
1222
- return persistenceFailure(`Claim winner receipt write failed: ${error instanceof Error ? error.message : "unknown I/O error"}`);
1488
+ if (error instanceof ScopeCoordinationAbort)
1489
+ return error.failure;
1490
+ return persistenceFailure(`Claim transaction failed: ${error instanceof Error ? error.message : "unknown coordination error"}`);
1223
1491
  }
1224
1492
  }
1225
1493
  async function replaceExistingScopeDeclaration(input) {
@@ -1231,58 +1499,69 @@ async function replaceExistingScopeDeclaration(input) {
1231
1499
  const { binding } = input;
1232
1500
  if (binding.activation !== "declaration" || binding.dispatchCallId !== undefined)
1233
1501
  return persistenceFailure("declare_scope may persist only a declaration generation.");
1234
- const digest = createHash("sha256").update(`${binding.workspaceIdentity}\x00${binding.taskId}\x00${binding.ownerSessionId}`).digest("hex").slice(0, 40);
1235
- const lockPath = path2.join(getScopesDir(input.directory), `declaration-${digest}.lock-target`);
1236
- if (!await ensureLockTarget(lockPath))
1237
- return persistenceFailure("Could not prepare declaration transaction lock.");
1238
- let release;
1502
+ const imported = ensureScopeBindingAuthorityImported(input.directory);
1503
+ if (!imported.ok)
1504
+ return imported;
1505
+ const all = readAllAuthoritativeScopeBindings(input.directory, {
1506
+ enforceLiveCapacity: false
1507
+ });
1508
+ if (!all.ok)
1509
+ return all;
1510
+ const owned = all.value.filter((candidate) => candidate.lifecycleState === "live" && candidate.expiresAt > Date.now() && candidate.workspaceIdentity === binding.workspaceIdentity && candidate.taskId === binding.taskId && candidate.ownerSessionId === binding.ownerSessionId && candidate.generationId !== binding.generationId);
1511
+ if (owned.length > 0 && !input.replaceExisting) {
1512
+ return {
1513
+ ok: false,
1514
+ code: "SCOPE_BINDING_AMBIGUOUS",
1515
+ message: "A live owned generation already exists; retry declare_scope with replace_existing=true to revoke it atomically."
1516
+ };
1517
+ }
1239
1518
  try {
1240
- release = await import_proper_lockfile.default.lock(lockPath, {
1241
- stale: LOCK_STALE_MS,
1242
- retries: { retries: 5, minTimeout: 50, maxTimeout: 250 },
1243
- realpath: false
1244
- });
1245
- const all = readAllExactBindings(input.directory);
1246
- if (!all)
1247
- return {
1248
- ok: false,
1249
- code: "SCOPE_BINDING_STORE_OVERLOADED",
1250
- message: "The durable binding set cannot be completely evaluated."
1251
- };
1252
- const owned = all.filter((candidate) => candidate.lifecycleState === "live" && candidate.expiresAt > Date.now() && candidate.workspaceIdentity === binding.workspaceIdentity && candidate.taskId === binding.taskId && candidate.ownerSessionId === binding.ownerSessionId && candidate.generationId !== binding.generationId);
1253
- if (owned.length > 0 && !input.replaceExisting)
1254
- return {
1255
- ok: false,
1256
- code: "SCOPE_BINDING_AMBIGUOUS",
1257
- message: "A live owned generation already exists; retry declare_scope with replace_existing=true to revoke it atomically."
1258
- };
1259
- const persisted = await writeScopeBindingToDisk(input.directory, binding);
1260
- if (!persisted.ok)
1261
- return persisted;
1262
- for (const prior of owned) {
1263
- const retired = await tombstoneScopeBinding(input.directory, prior, "superseded");
1264
- if (!retired.ok) {
1265
- await tombstoneScopeBinding(input.directory, persisted.value, "revoked");
1266
- return persistenceFailure(`Replacement could not retire generation ${prior.generationId}: ${retired.message}`);
1519
+ let persisted = null;
1520
+ const retired = [];
1521
+ withCoordinationTransaction(input.directory, () => {
1522
+ const create = _scopePersistenceInternals.transitionScopeBindingStateWithinTransaction(input.directory, binding, null);
1523
+ if (!create.ok) {
1524
+ throw new ScopeCoordinationAbort(create);
1525
+ }
1526
+ persisted = create.value;
1527
+ for (const prior of owned) {
1528
+ const superseded = {
1529
+ ...prior,
1530
+ revision: prior.revision + 1,
1531
+ lifecycleState: "superseded",
1532
+ updatedAt: create.value.updatedAt,
1533
+ expiresAt: Math.min(prior.expiresAt, create.value.updatedAt)
1534
+ };
1535
+ const result = _scopePersistenceInternals.transitionScopeBindingStateWithinTransaction(input.directory, superseded, prior.revision);
1536
+ if (!result.ok) {
1537
+ throw new ScopeCoordinationAbort(result);
1538
+ }
1539
+ retired.push(result.value);
1267
1540
  }
1541
+ });
1542
+ if (!persisted) {
1543
+ return persistenceFailure("Declaration transaction did not persist a generation.");
1544
+ }
1545
+ writeScopeBindingShadow(input.directory, persisted);
1546
+ for (const prior of retired) {
1547
+ writeScopeBindingShadow(input.directory, prior);
1548
+ installScopeBindingTombstone(prior);
1268
1549
  }
1269
- const admission = registerScopeBinding(persisted.value);
1550
+ const admission = registerScopeBinding(persisted);
1270
1551
  if (!admission.ok) {
1271
- await tombstoneScopeBinding(input.directory, persisted.value, "revoked");
1272
- return {
1273
- ok: false,
1274
- code: admission.code,
1275
- message: admission.message
1276
- };
1552
+ if (retired.length !== owned.length) {
1553
+ return persistenceFailure("Memory admission failed after an incomplete predecessor transition.");
1554
+ }
1555
+ return rollbackAdmissionFailure(input.directory, persisted, owned.map((original, index) => ({
1556
+ original,
1557
+ superseded: retired[index]
1558
+ })), admission);
1277
1559
  }
1278
- return { ok: true, value: persisted.value };
1560
+ return { ok: true, value: persisted };
1279
1561
  } catch (error) {
1280
- return persistenceFailure(`Declaration transaction failed: ${error instanceof Error ? error.message : "unknown I/O error"}`);
1281
- } finally {
1282
- if (release)
1283
- try {
1284
- await release();
1285
- } catch {}
1562
+ if (error instanceof ScopeCoordinationAbort)
1563
+ return error.failure;
1564
+ return persistenceFailure(`Declaration transaction failed: ${error instanceof Error ? error.message : "unknown coordination error"}`);
1286
1565
  }
1287
1566
  }
1288
1567
  function scheduleScopeBindingMaintenance(directory) {
@@ -1322,32 +1601,13 @@ async function pruneScopeBindingTombstones(directory) {
1322
1601
  retries: { retries: 3, minTimeout: 50, maxTimeout: 200 },
1323
1602
  realpath: false
1324
1603
  });
1325
- let all = readAllExactBindings(directory, {
1604
+ const readAll = readAllAuthoritativeScopeBindings(directory, {
1326
1605
  enforceLiveCapacity: false,
1327
1606
  maintenanceScan: true
1328
1607
  });
1329
- if (!all)
1330
- return {
1331
- ok: false,
1332
- code: "SCOPE_BINDING_STORE_OVERLOADED",
1333
- message: "The durable binding set cannot be completely evaluated."
1334
- };
1335
- for (const binding of all) {
1336
- const filePath = getBindingFilePath(directory, binding);
1337
- if (!hasDurableRetirementIntent(filePath, binding))
1338
- continue;
1339
- const finalized = await tombstoneScopeBinding(directory, binding, "revoked");
1340
- if (finalized.ok)
1341
- try {
1342
- fs.unlinkSync(getRetirementIntentPath(filePath));
1343
- } catch {}
1344
- }
1345
- all = readAllExactBindings(directory, {
1346
- enforceLiveCapacity: false,
1347
- maintenanceScan: true
1348
- });
1349
- if (!all)
1350
- return persistenceFailure("The durable binding set changed during maintenance reconciliation.");
1608
+ if (!readAll.ok)
1609
+ return readAll;
1610
+ const all = readAll.value;
1351
1611
  const tombstones = all.filter((binding) => binding.lifecycleState !== "live").sort((left, right) => left.updatedAt - right.updatedAt);
1352
1612
  const now = Date.now();
1353
1613
  const removable = tombstones.filter((binding, index) => now - binding.updatedAt > MAX_TOMBSTONE_AGE_MS || index < tombstones.length - MAX_DURABLE_TOMBSTONES);
@@ -1371,6 +1631,7 @@ async function pruneScopeBindingTombstones(directory) {
1371
1631
  const latest = latestRaw ? validateScopeBindingPayload(directory, JSON.parse(latestRaw)) : null;
1372
1632
  if (!latest || latest.generationId !== binding.generationId || latest.revision !== binding.revision || latest.lifecycleState === "live" && !hasDurableRetirementIntent(filePath, latest))
1373
1633
  continue;
1634
+ deleteCoordinationState(directory, SCOPE_BINDING_COORDINATION_NAMESPACE, binding.generationId, binding.revision);
1374
1635
  fs.unlinkSync(filePath);
1375
1636
  removedExact = true;
1376
1637
  removed++;
@@ -1475,47 +1736,33 @@ function attemptIdleScopeBindingRevival(directory, resolution) {
1475
1736
  return null;
1476
1737
  if (hasDeliberateScopeBindingDenyOverlay(candidate))
1477
1738
  return null;
1478
- let release;
1479
- try {
1480
- const resolvedWorkspace = fs.realpathSync(directory);
1481
- assertProjectRoot(resolvedWorkspace);
1482
- const scopePath = getBindingFilePath(resolvedWorkspace, candidate);
1483
- if (!isScopesDirSafe(resolvedWorkspace, getScopesDir(resolvedWorkspace)))
1484
- return null;
1485
- const lockPath = getGenerationLockPath(scopePath);
1486
- if (!ensureLockTargetSync(lockPath))
1487
- return null;
1488
- release = lockSyncWithBoundedRetry(lockPath);
1489
- const raw = readBoundedFile(scopePath);
1490
- if (!raw)
1491
- return null;
1492
- const current = validateScopeBindingPayload(resolvedWorkspace, JSON.parse(raw));
1493
- if (!current || current.bindingId !== candidate.bindingId || current.generationId !== candidate.generationId || current.revision !== candidate.revision || current.lifecycleState !== "live" || current.expiresAt > Date.now() || Date.now() - current.expiresAt > SCOPE_BINDING_AUTO_REVIVE_WINDOW_MS)
1494
- return null;
1495
- const revivedAt = Date.now();
1496
- const revived = {
1497
- ...current,
1498
- revision: current.revision + 1,
1499
- updatedAt: revivedAt,
1500
- leaseStartedAt: revivedAt,
1501
- expiresAt: revivedAt + DEFAULT_SCOPE_BINDING_TTL_MS
1502
- };
1503
- atomicWriteSync(scopePath, JSON.stringify(revived, null, 2));
1504
- const verifiedRaw = readBoundedFile(scopePath);
1505
- const verified = verifiedRaw ? validateScopeBindingPayload(resolvedWorkspace, JSON.parse(verifiedRaw)) : null;
1506
- if (!verified || verified.revision !== revived.revision || verified.lifecycleState !== "live" || verified.expiresAt !== revived.expiresAt)
1507
- return null;
1508
- clearSweepTombstoneForRevival(revived);
1509
- appendScopeBindingRevivalEvent(resolvedWorkspace, revived);
1510
- return revived;
1511
- } catch {
1739
+ const imported = ensureScopeBindingAuthorityImported(directory);
1740
+ if (!imported.ok)
1741
+ return null;
1742
+ const all = readAllAuthoritativeScopeBindings(directory, {
1743
+ enforceLiveCapacity: false
1744
+ });
1745
+ if (!all.ok)
1746
+ return null;
1747
+ const current = all.value.find((binding) => binding.bindingId === candidate.bindingId && binding.generationId === candidate.generationId);
1748
+ if (!current || current.revision !== candidate.revision || current.lifecycleState !== "live" || current.expiresAt > Date.now() || Date.now() - current.expiresAt > SCOPE_BINDING_AUTO_REVIVE_WINDOW_MS) {
1512
1749
  return null;
1513
- } finally {
1514
- if (release)
1515
- try {
1516
- release();
1517
- } catch {}
1518
1750
  }
1751
+ const revivedAt = Date.now();
1752
+ const revived = {
1753
+ ...current,
1754
+ revision: current.revision + 1,
1755
+ updatedAt: revivedAt,
1756
+ leaseStartedAt: revivedAt,
1757
+ expiresAt: revivedAt + DEFAULT_SCOPE_BINDING_TTL_MS
1758
+ };
1759
+ const persisted = transitionScopeBindingState(directory, revived, current.revision);
1760
+ if (!persisted.ok)
1761
+ return null;
1762
+ writeScopeBindingShadow(directory, persisted.value);
1763
+ clearSweepTombstoneForRevival(persisted.value);
1764
+ appendScopeBindingRevivalEvent(directory, persisted.value);
1765
+ return persisted.value;
1519
1766
  }
1520
1767
  function resolveAuthorizedScopeBindingForPlan(input) {
1521
1768
  const resolution = resolveAuthorizedScopeBindingForPlanDetailed(input);
@@ -1603,87 +1850,16 @@ function resolveAuthorizedScopeBindingForSessionDetailed(input) {
1603
1850
  totalCandidates: expiredTotal
1604
1851
  } : { status: "not_declared" };
1605
1852
  }
1606
- function readPrFeedbackBindingFile(directory, filePath, activeSessionId, taskId) {
1607
- try {
1608
- const leaf = fs.lstatSync(filePath);
1609
- if (!leaf.isFile() || leaf.isSymbolicLink())
1610
- return null;
1611
- if (normalizePathForComparison(fs.realpathSync(filePath)) !== normalizePathForComparison(filePath))
1612
- return null;
1613
- } catch {
1614
- return null;
1615
- }
1616
- const nofollow = fs.constants.O_NOFOLLOW ?? 0;
1617
- let fd;
1618
- try {
1619
- fd = fs.openSync(filePath, fs.constants.O_RDONLY | nofollow);
1620
- } catch {
1621
- return null;
1622
- }
1623
- let raw = "";
1624
- try {
1625
- const stat = fs.fstatSync(fd);
1626
- if (!stat.isFile() || stat.size > MAX_SCOPE_BYTES)
1627
- return null;
1628
- const buffer = Buffer.alloc(stat.size);
1629
- fs.readSync(fd, buffer, 0, stat.size, 0);
1630
- raw = buffer.toString("utf8");
1631
- } catch {
1632
- return null;
1633
- } finally {
1634
- try {
1635
- fs.closeSync(fd);
1636
- } catch {}
1637
- }
1638
- let parsed;
1639
- try {
1640
- parsed = validateScopeBindingPayload(directory, JSON.parse(raw));
1641
- } catch {
1642
- return null;
1643
- }
1644
- if (!parsed)
1645
- return null;
1646
- if (hasScopeBindingDenyOverlay(parsed))
1647
- return null;
1648
- const now = Date.now();
1649
- if (parsed.source !== "pr_feedback" || taskId !== undefined && parsed.taskId !== taskId || parsed.ownerSessionId !== activeSessionId || parsed.activation !== "active" || typeof parsed.dispatchCallId !== "string" || !parsed.dispatchCallId || parsed.ownerMessageId !== parsed.dispatchCallId || typeof parsed.parentOwnerSessionId !== "string" || !parsed.parentOwnerSessionId || parsed.ownerSessionId === parsed.parentOwnerSessionId || parsed.parentCallId !== parsed.dispatchCallId || parsed.workflowSessionId !== parsed.parentOwnerSessionId || typeof parsed.workflowRevisionDigest !== "string" || !parsed.workflowRevisionDigest || parsed.planId !== `pr-feedback:${parsed.workflowSessionId}` || parsed.planStructureHash !== parsed.workflowRevisionDigest || parsed.declaredAt > now || parsed.expiresAt <= now || parsed.lifecycleState !== "live")
1650
- return null;
1651
- const expectedPath = getBindingFilePath(directory, parsed);
1652
- if (normalizePathForComparison(expectedPath) !== normalizePathForComparison(filePath))
1653
- return null;
1654
- return parsed;
1655
- }
1656
1853
  function resolveAuthorizedPrFeedbackScopeBindingFromDisk(input) {
1657
1854
  if (!input.activeSessionId.trim())
1658
1855
  return null;
1659
- const scopesDir = getScopesDir(input.directory);
1660
- if (!isScopesDirSafe(input.directory, scopesDir))
1661
- return null;
1662
- if (!migrateLegacyBindingsSync(input.directory))
1856
+ const authoritative = readAllAuthoritativeScopeBindings(input.directory, {
1857
+ enforceLiveCapacity: false
1858
+ });
1859
+ if (!authoritative.ok)
1663
1860
  return null;
1664
- let candidates;
1665
- if (input.taskId) {
1666
- if (!isSafeTaskId(input.taskId))
1667
- return null;
1668
- try {
1669
- const names = fs.readdirSync(scopesDir);
1670
- if (names.length > MAX_BINDING_FILES_TO_SCAN)
1671
- return null;
1672
- candidates = names.filter((name) => name.startsWith(exactBindingPrefix(input.taskId)) && name.endsWith(".json")).map((name) => path2.join(scopesDir, name));
1673
- } catch {
1674
- return null;
1675
- }
1676
- } else {
1677
- try {
1678
- const names = fs.readdirSync(scopesDir);
1679
- if (names.length > MAX_BINDING_FILES_TO_SCAN)
1680
- return null;
1681
- candidates = names.filter((name) => name.startsWith("binding-") && name.endsWith(".json")).map((name) => path2.join(scopesDir, name));
1682
- } catch {
1683
- return null;
1684
- }
1685
- }
1686
- const matches = candidates.map((candidate) => readPrFeedbackBindingFile(input.directory, candidate, input.activeSessionId, input.taskId)).filter((binding) => binding !== null);
1861
+ const now = Date.now();
1862
+ const matches = authoritative.value.filter((parsed) => parsed.source === "pr_feedback" && (!input.taskId || parsed.taskId === input.taskId) && parsed.ownerSessionId === input.activeSessionId && parsed.activation === "active" && typeof parsed.dispatchCallId === "string" && parsed.dispatchCallId.length > 0 && parsed.ownerMessageId === parsed.dispatchCallId && typeof parsed.parentOwnerSessionId === "string" && parsed.parentOwnerSessionId.length > 0 && parsed.ownerSessionId !== parsed.parentOwnerSessionId && parsed.parentCallId === parsed.dispatchCallId && parsed.workflowSessionId === parsed.parentOwnerSessionId && typeof parsed.workflowRevisionDigest === "string" && parsed.workflowRevisionDigest.length > 0 && parsed.planId === `pr-feedback:${parsed.workflowSessionId}` && parsed.planStructureHash === parsed.workflowRevisionDigest && parsed.declaredAt <= now && parsed.expiresAt > now && parsed.lifecycleState === "live" && !hasScopeBindingDenyOverlay(parsed));
1687
1863
  if (matches.length !== 1)
1688
1864
  return null;
1689
1865
  const admission = registerScopeBinding(matches[0]);
@@ -1696,6 +1872,8 @@ function atomicWriteSync(targetPath, content) {
1696
1872
  atomicWriteSwarmFileSync(targetPath, content);
1697
1873
  }
1698
1874
  var _scopePersistenceInternals = {
1875
+ transitionScopeBindingState,
1876
+ transitionScopeBindingStateWithinTransaction,
1699
1877
  get liveBindingCapacity() {
1700
1878
  return liveBindingCapacity;
1701
1879
  },