harnery 0.45.1 → 0.45.2

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 (119) hide show
  1. package/dist/commands/workflow.d.ts.map +1 -1
  2. package/dist/commands/workflow.js +6 -45
  3. package/dist/core/agents/coord-client.d.ts.map +1 -1
  4. package/dist/core/agents/coord-client.js +0 -18
  5. package/dist/core/agents/state/pidmap.js +2 -2
  6. package/dist/core/agents/state/proc-start.d.ts +20 -31
  7. package/dist/core/agents/state/proc-start.d.ts.map +1 -1
  8. package/dist/core/agents/state/proc-start.js +37 -70
  9. package/dist/core/agents/state/service-process.d.ts +61 -0
  10. package/dist/core/agents/state/service-process.d.ts.map +1 -0
  11. package/dist/core/agents/state/service-process.js +143 -0
  12. package/dist/core/context/index.d.ts +6 -4
  13. package/dist/core/context/index.d.ts.map +1 -1
  14. package/dist/core/context/index.js +194 -256
  15. package/dist/core/context/observation.d.ts +9 -9
  16. package/dist/core/context/observation.d.ts.map +1 -1
  17. package/dist/core/context/observation.js +37 -6
  18. package/dist/core/events/v3/runtime-build.d.ts +14 -0
  19. package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
  20. package/dist/core/events/v3/runtime-build.js +44 -0
  21. package/dist/core/governor/index.d.ts +2 -1
  22. package/dist/core/governor/index.d.ts.map +1 -1
  23. package/dist/core/governor/index.js +2 -1
  24. package/dist/core/governor/service-read.d.ts +3 -10
  25. package/dist/core/governor/service-read.d.ts.map +1 -1
  26. package/dist/core/governor/service-read.js +10 -94
  27. package/dist/core/governor/service.d.ts +3 -7
  28. package/dist/core/governor/service.d.ts.map +1 -1
  29. package/dist/core/governor/service.js +17 -182
  30. package/dist/core/hooks/resolve/owner.js +2 -2
  31. package/dist/core/resources/sampler.js +2 -2
  32. package/dist/core/resources/service-status.d.ts +2 -14
  33. package/dist/core/resources/service-status.d.ts.map +1 -1
  34. package/dist/core/resources/service-status.js +3 -23
  35. package/dist/core/resources/service.d.ts +3 -7
  36. package/dist/core/resources/service.d.ts.map +1 -1
  37. package/dist/core/resources/service.js +12 -101
  38. package/dist/core/resources/status.d.ts.map +1 -1
  39. package/dist/core/resources/status.js +3 -2
  40. package/dist/core/semantic/service.d.ts.map +1 -1
  41. package/dist/core/semantic/service.js +9 -2
  42. package/dist/core/servers/index.d.ts +0 -2
  43. package/dist/core/servers/index.d.ts.map +1 -1
  44. package/dist/core/servers/index.js +3 -18
  45. package/dist/core/supervisor/service.d.ts +4 -7
  46. package/dist/core/supervisor/service.d.ts.map +1 -1
  47. package/dist/core/supervisor/service.js +23 -122
  48. package/dist/core/supervisor/services.js +2 -2
  49. package/dist/core/supervisor/status.d.ts +3 -17
  50. package/dist/core/supervisor/status.d.ts.map +1 -1
  51. package/dist/core/supervisor/status.js +7 -70
  52. package/dist/core/workflow/engine.js +2 -1
  53. package/dist/core/workflow/index.d.ts +3 -4
  54. package/dist/core/workflow/index.d.ts.map +1 -1
  55. package/dist/core/workflow/index.js +2 -2
  56. package/dist/core/workflow/proof.d.ts.map +1 -1
  57. package/dist/core/workflow/proof.js +30 -74
  58. package/dist/core/workflow/resume-lock.d.ts +17 -0
  59. package/dist/core/workflow/resume-lock.d.ts.map +1 -0
  60. package/dist/core/workflow/resume-lock.js +158 -0
  61. package/dist/core/workflow/run-state.d.ts +0 -21
  62. package/dist/core/workflow/run-state.d.ts.map +1 -1
  63. package/dist/core/workflow/run-state.js +3 -174
  64. package/dist/core/workflow/workspaces/leases.d.ts +4 -0
  65. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  66. package/dist/core/workflow/workspaces/leases.js +1 -1
  67. package/dist/core/workflow/workspaces/local-git.js +91 -210
  68. package/dist/core/workflow/workspaces/ownership.d.ts +0 -7
  69. package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -1
  70. package/dist/core/workflow/workspaces/ownership.js +3 -130
  71. package/dist/core/workflow/workspaces/reclaim.d.ts +4 -7
  72. package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
  73. package/dist/core/workflow/workspaces/reclaim.js +0 -4
  74. package/dist/core/workflow/workspaces/recovery.d.ts +1 -1
  75. package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
  76. package/dist/core/workflow/workspaces/recovery.js +1 -1
  77. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  78. package/dist/core/workflow/workspaces/state.js +1 -4
  79. package/package.json +1 -1
  80. package/src/commands/workflow.ts +8 -58
  81. package/src/core/agents/coord-client.ts +0 -20
  82. package/src/core/agents/state/pidmap.ts +2 -2
  83. package/src/core/agents/state/proc-start.ts +41 -69
  84. package/src/core/agents/state/service-process.ts +217 -0
  85. package/src/core/context/index.ts +207 -272
  86. package/src/core/context/observation.ts +40 -9
  87. package/src/core/events/v3/runtime-build.ts +62 -0
  88. package/src/core/governor/index.ts +1 -2
  89. package/src/core/governor/service-read.ts +15 -114
  90. package/src/core/governor/service.ts +36 -220
  91. package/src/core/hooks/resolve/owner.ts +2 -2
  92. package/src/core/resources/sampler.ts +3 -2
  93. package/src/core/resources/service-status.ts +6 -35
  94. package/src/core/resources/service.ts +23 -133
  95. package/src/core/resources/status.ts +3 -2
  96. package/src/core/semantic/service.ts +8 -2
  97. package/src/core/servers/index.ts +3 -23
  98. package/src/core/supervisor/service.ts +33 -155
  99. package/src/core/supervisor/services.ts +2 -2
  100. package/src/core/supervisor/status.ts +10 -85
  101. package/src/core/workflow/engine.ts +1 -1
  102. package/src/core/workflow/index.ts +2 -13
  103. package/src/core/workflow/proof.ts +35 -83
  104. package/src/core/workflow/resume-lock.ts +179 -0
  105. package/src/core/workflow/run-state.ts +3 -207
  106. package/src/core/workflow/workspaces/leases.ts +1 -1
  107. package/src/core/workflow/workspaces/local-git.ts +120 -239
  108. package/src/core/workflow/workspaces/ownership.ts +3 -158
  109. package/src/core/workflow/workspaces/reclaim.ts +3 -9
  110. package/src/core/workflow/workspaces/recovery.ts +1 -1
  111. package/src/core/workflow/workspaces/state.ts +1 -5
  112. package/dist/core/agents/state/process-handle.d.ts +0 -26
  113. package/dist/core/agents/state/process-handle.d.ts.map +0 -1
  114. package/dist/core/agents/state/process-handle.js +0 -121
  115. package/dist/core/workflow/recovery-audit.d.ts +0 -30
  116. package/dist/core/workflow/recovery-audit.d.ts.map +0 -1
  117. package/dist/core/workflow/recovery-audit.js +0 -116
  118. package/src/core/agents/state/process-handle.ts +0 -165
  119. package/src/core/workflow/recovery-audit.ts +0 -181
@@ -1,14 +1,14 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, writeFileSync, } from "node:fs";
3
+ import { hostname } from "node:os";
3
4
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
4
5
  import { stateDirMode, stateFileMode } from "../../storage/modes.js";
5
- import { beginWorkflowRecovery, validateWorkflowRecoveryAuthorization } from "../recovery-audit.js";
6
- import { acquireWorkflowResumeLease } from "../run-state.js";
6
+ import { acquireWorkflowResumeLease, readWorkflowResumeLock, workflowResumeLockPath, } from "../resume-lock.js";
7
7
  import { WorkspaceAttestationError } from "./attestation-error.js";
8
8
  import { cleanupWorkspace } from "./cleanup.js";
9
9
  import { git, gitMaybe, gitMaybeWithDirectoryDescriptor, gitOperations, isAncestor, nulEntries, overwriteRiskPaths, parseWorktreeInventory, worktreeInventory, } from "./git.js";
10
- import { acquireNoClobberLease } from "./leases.js";
11
- import { claimOverlapsWorkspace, reserveWorkspaceReclaim, } from "./ownership.js";
10
+ import { acquireNoClobberLease, readOwner } from "./leases.js";
11
+ import { claimOverlapsWorkspace } from "./ownership.js";
12
12
  import { assertContainedExisting, assertPathIdentity, candidateUnderRoot, containsPath, createContainedDirectories, descriptorBackedPathsSupported, descriptorPath, filesystemIdentity, openContainedDirectory, sameFilesystemIdentity, validateConfiguredRoot, } from "./paths.js";
13
13
  import { prepareReclaim, WorkflowReclaimError, } from "./reclaim.js";
14
14
  import { hasActiveWorkflowEvidence, readWorkflowLiveness } from "./recovery.js";
@@ -598,7 +598,6 @@ function reclaimPreview(coordRoot, binding, mode) {
598
598
  run_id: binding.run_id,
599
599
  mode,
600
600
  liveness: readWorkflowLiveness(coordRoot, binding.run_id),
601
- evidence_sha256: reclaimEvidenceDigest(coordRoot, binding),
602
601
  ownership: "unproven",
603
602
  dirty_paths: null,
604
603
  lease_holder: reclaimLeaseHolders(coordRoot, binding),
@@ -619,34 +618,12 @@ function reclaimPreview(coordRoot, binding, mode) {
619
618
  }
620
619
  return preview;
621
620
  }
622
- function reclaimEvidenceDigest(coordRoot, binding) {
623
- const evidence = (name) => {
624
- try {
625
- return readFileSync(join(coordRoot, ".harnery", "workflows", binding.run_id, name), "utf8");
626
- }
627
- catch (error) {
628
- if (error.code === "ENOENT")
629
- return null;
630
- throw error;
631
- }
632
- };
633
- return stableDigest({
634
- binding,
635
- proof: evidence("proof.json"),
636
- transcript: evidence("transcript.jsonl"),
637
- });
638
- }
639
- function assertReclaimLiveness(coordRoot, binding, recovery) {
621
+ function assertReclaimLiveness(coordRoot, binding, force = false) {
640
622
  const liveness = readWorkflowLiveness(coordRoot, binding.run_id);
641
- if (liveness === "active" || (recovery && hasActiveWorkflowEvidence(coordRoot, binding.run_id))) {
623
+ if (liveness === "active" || (force && hasActiveWorkflowEvidence(coordRoot, binding.run_id))) {
642
624
  throw new WorkflowReclaimError("workflow_reclaim_run_live", `run ${binding.run_id} has active evidence; its workspace was left untouched`);
643
625
  }
644
- if (recovery && reclaimEvidenceDigest(coordRoot, binding) !== recovery.evidenceSha256) {
645
- throw new WorkflowReclaimError("workflow_reclaim_recovery_mismatch", "workflow reclaim requires the exact current preview evidence digest");
646
- }
647
- if (liveness === "inactive")
648
- return;
649
- if (liveness === "unknown" && recovery)
626
+ if (liveness === "inactive" || force)
650
627
  return;
651
628
  throw new WorkflowReclaimError("workflow_reclaim_liveness_unknown", `run ${binding.run_id} has missing, corrupt, or ambiguous liveness evidence; its workspace was left untouched`);
652
629
  }
@@ -677,7 +654,7 @@ function assertPeerWorkspaceAuthority(coordRoot, claim) {
677
654
  }
678
655
  if (!claimOverlapsWorkspace(peer, claim.workspace_root))
679
656
  continue;
680
- // Process termination, archival and an expired reservation do not release
657
+ // Process termination, archival and an expired lease do not release
681
658
  // a resource. Require the provider's release record AND current absence.
682
659
  if (binding && events.some((event) => event.event === "released")) {
683
660
  const current = attestSync(coordRoot, binding);
@@ -691,185 +668,99 @@ function assertPeerWorkspaceAuthority(coordRoot, claim) {
691
668
  throw new WorkflowReclaimError("workflow_reclaim_ownership_unproven", error.message);
692
669
  }
693
670
  }
694
- function reclaimAuthorityGuard(coordRoot, binding, claim, reservation, recovery) {
695
- try {
696
- reservation.assertCurrent();
697
- assertReclaimLiveness(coordRoot, binding, recovery);
698
- assertReclaimOwnership(coordRoot, binding);
699
- assertPeerWorkspaceAuthority(coordRoot, claim);
700
- }
701
- catch (error) {
702
- if (error instanceof WorkflowReclaimError)
703
- throw error;
704
- throw new WorkflowReclaimError("workflow_reclaim_ownership_unproven", error.message);
705
- }
706
- }
707
671
  async function reclaim(coordRoot, provider, binding, options) {
708
672
  if (options.mode !== "salvage" && options.mode !== "discard") {
709
673
  throw new Error("unsupported workspace reclaim mode");
710
674
  }
711
675
  if (!options.yes)
712
676
  return reclaimPreview(coordRoot, binding, options.mode);
713
- if (options.unknownLiveness)
714
- validateWorkflowRecoveryAuthorization(options.unknownLiveness);
715
- // Liveness comes before terminal-proof cleanup helpers. A live run may not
716
- // have a proof yet, and a missing proof must never be discovered after reset.
717
- assertReclaimLiveness(coordRoot, binding, options.unknownLiveness);
677
+ // Liveness comes before leases and terminal-proof cleanup helpers. A live run
678
+ // may not have a proof yet, and a missing proof must never be found after reset.
679
+ assertReclaimLiveness(coordRoot, binding, options.force);
718
680
  const claim = assertReclaimOwnership(coordRoot, binding);
719
- let reservation;
720
- try {
721
- reservation = reserveWorkspaceReclaim(coordRoot, claim);
722
- }
723
- catch (error) {
724
- throw new WorkflowReclaimError("workflow_reclaim_lease_held", error.message);
725
- }
726
- try {
727
- reclaimAuthorityGuard(coordRoot, binding, claim, reservation, options.unknownLiveness);
728
- let releaseResume;
681
+ assertPeerWorkspaceAuthority(coordRoot, claim);
682
+ const releases = [];
683
+ const hold = (acquire) => {
729
684
  try {
730
- releaseResume = acquireWorkflowResumeLease(coordRoot, binding.run_id);
685
+ releases.unshift(acquire());
731
686
  }
732
687
  catch (error) {
733
688
  throw new WorkflowReclaimError("workflow_reclaim_lease_held", error.message);
734
689
  }
735
- try {
736
- let releaseBinding;
737
- try {
738
- releaseBinding = acquireLease(coordRoot, workspaceLockPath(coordRoot, PROVIDER_ID, binding.binding_id), claim, "binding");
739
- }
740
- catch (error) {
741
- throw new WorkflowReclaimError("workflow_reclaim_lease_held", error.message);
742
- }
743
- try {
744
- let releaseRepository;
745
- try {
746
- releaseRepository = acquireRepositoryLease(coordRoot, claim);
747
- }
748
- catch (error) {
749
- throw new WorkflowReclaimError("workflow_reclaim_lease_held", error.message);
750
- }
751
- try {
752
- assertReclaimLiveness(coordRoot, binding, options.unknownLiveness);
753
- assertReclaimOwnership(coordRoot, binding);
754
- const preview = reclaimPreview(coordRoot, binding, options.mode);
755
- const audit = options.unknownLiveness
756
- ? beginWorkflowRecovery(coordRoot, {
757
- run_id: binding.run_id,
758
- action: "reclaim_unknown_liveness",
759
- actor: options.unknownLiveness.actor,
760
- reason: options.unknownLiveness.reason,
761
- target_sha256: options.unknownLiveness.evidenceSha256,
762
- target: {
763
- binding_id: binding.binding_id,
764
- workspace_root: binding.workspace_root,
765
- mode: options.mode,
766
- liveness: preview.liveness,
767
- },
768
- })
769
- : undefined;
770
- let preparation;
771
- // Keep ordinary cleanup's terminal-evidence checks, intent and receipts.
772
- // Its provider callback uses the leases already held here instead of
773
- // trying to acquire them again. Preparation occurs only after all of
774
- // cleanup's path, ownership, ref and registration checks have succeeded.
775
- const cleanupResult = await cleanupWorkspace({
776
- coordRoot,
777
- runId: binding.run_id,
778
- provider: {
779
- ...provider,
780
- cleanup: async (candidate, intent) => {
781
- assertReclaimLiveness(coordRoot, candidate, options.unknownLiveness);
782
- assertReclaimOwnership(coordRoot, candidate);
783
- return cleanupUnderLease(coordRoot, candidate, claim, intent, {
784
- mode: options.mode,
785
- recovery: options.unknownLiveness,
786
- beforeEffect: () => {
787
- audit?.assertCurrent();
788
- reclaimAuthorityGuard(coordRoot, candidate, claim, reservation, options.unknownLiveness);
789
- },
790
- beforeRefEffect: () => {
791
- audit?.assertCurrent();
792
- reservation.assertCurrent();
793
- assertReclaimLiveness(coordRoot, candidate, options.unknownLiveness);
794
- requiredClaim(coordRoot, candidate);
795
- assertPeerWorkspaceAuthority(coordRoot, claim);
796
- },
797
- prepared: (value) => {
798
- preparation = value;
799
- },
800
- });
801
- },
802
- },
803
- });
804
- if (!preparation &&
805
- cleanupResult.status !== "released" &&
806
- cleanupResult.status !== "already_released") {
807
- throw new WorkflowReclaimError("workflow_reclaim_ownership_unproven", ("reason" in cleanupResult ? cleanupResult.reason : undefined) ??
808
- "workspace cleanup preconditions could not be proved");
809
- }
810
- if (cleanupResult.status === "released" || cleanupResult.status === "already_released")
811
- audit?.complete();
812
- return {
813
- ...preview,
814
- action: "reclaimed",
815
- lease_holder: [],
816
- preparation: preparation ?? {
817
- action: "already_gone",
818
- detail: "the workspace is already released",
819
- },
820
- cleanup: cleanupResult,
821
- };
822
- }
823
- finally {
824
- releaseRepository();
825
- }
826
- }
827
- finally {
828
- releaseBinding();
829
- }
690
+ };
691
+ try {
692
+ hold(() => acquireWorkflowResumeLease(coordRoot, binding.run_id));
693
+ hold(() => acquireLease(coordRoot, workspaceLockPath(coordRoot, PROVIDER_ID, binding.binding_id), claim, "binding"));
694
+ hold(() => acquireRepositoryLease(coordRoot, claim));
695
+ const preview = reclaimPreview(coordRoot, binding, options.mode);
696
+ if (options.force && preview.liveness !== "inactive") {
697
+ appendWorkspaceEvent(coordRoot, claim, "reclaim_forced", {
698
+ run_id: binding.run_id,
699
+ mode: options.mode,
700
+ liveness: preview.liveness,
701
+ pid: process.pid,
702
+ host: hostname(),
703
+ });
830
704
  }
831
- finally {
832
- releaseResume();
705
+ let preparation;
706
+ // Keep ordinary cleanup's terminal-evidence checks, intent and receipts.
707
+ // Its provider callback uses the leases already held here. Preparation
708
+ // occurs only after cleanup's liveness, ownership, path, ref and
709
+ // registration checks have succeeded under those leases.
710
+ const cleanupResult = await cleanupWorkspace({
711
+ coordRoot,
712
+ runId: binding.run_id,
713
+ provider: {
714
+ ...provider,
715
+ cleanup: async (candidate, intent) => cleanupUnderLease(coordRoot, candidate, claim, intent, {
716
+ mode: options.mode,
717
+ force: options.force,
718
+ prepared: (value) => {
719
+ preparation = value;
720
+ },
721
+ }),
722
+ },
723
+ });
724
+ if (!preparation &&
725
+ cleanupResult.status !== "released" &&
726
+ cleanupResult.status !== "already_released") {
727
+ throw new WorkflowReclaimError("workflow_reclaim_ownership_unproven", ("reason" in cleanupResult ? cleanupResult.reason : undefined) ??
728
+ "workspace cleanup preconditions could not be proved");
833
729
  }
730
+ return {
731
+ ...preview,
732
+ action: "reclaimed",
733
+ lease_holder: [],
734
+ preparation: preparation ?? {
735
+ action: "already_gone",
736
+ detail: "the workspace is already released",
737
+ },
738
+ cleanup: cleanupResult,
739
+ };
834
740
  }
835
741
  finally {
836
- reservation.release();
742
+ for (const release of releases)
743
+ release();
837
744
  }
838
745
  }
839
746
  function reclaimLeaseHolders(coordRoot, binding) {
840
- const paths = [
747
+ const holders = [];
748
+ if (existsSync(workflowResumeLockPath(coordRoot, binding.run_id))) {
749
+ const owner = readWorkflowResumeLock(coordRoot, binding.run_id);
750
+ holders.push(owner
751
+ ? { scope: "resume", pid: owner.pid, host: owner.host }
752
+ : { scope: "resume", detail: "lease holder could not be read" });
753
+ }
754
+ const leases = [
841
755
  ["binding", workspaceLockPath(coordRoot, PROVIDER_ID, binding.binding_id)],
842
756
  ];
843
757
  if (binding.repository) {
844
- const key = stableDigest({ common: binding.repository.common_dir.identity });
845
- paths.push([
846
- "repository",
847
- join(coordRoot, ".harnery", "workspaces", PROVIDER_ID, ".leases", `repository-${key}.lock`),
848
- ]);
849
- }
850
- const holders = [];
851
- try {
852
- const owner = JSON.parse(readFileSync(join(coordRoot, ".harnery", "workflows", binding.run_id, "resume.lock"), "utf8"));
853
- holders.push({
854
- scope: "resume",
855
- pid: typeof owner.pid === "number" ? owner.pid : undefined,
856
- host: typeof owner.host === "string" ? owner.host : undefined,
857
- });
858
- }
859
- catch (error) {
860
- if (error.code !== "ENOENT") {
861
- holders.push({ scope: "resume", detail: "lease holder could not be read" });
862
- }
758
+ leases.push(["repository", repositoryLeasePath(coordRoot, binding.repository)]);
863
759
  }
864
- for (const [scope, path] of paths) {
760
+ for (const [scope, path] of leases) {
865
761
  try {
866
- const owner = JSON.parse(readFileSync(join(path, "current"), "utf8"));
867
- holders.push({
868
- scope,
869
- owner_id: typeof owner.owner_id === "string" ? owner.owner_id : undefined,
870
- pid: typeof owner.pid === "number" ? owner.pid : undefined,
871
- host: typeof owner.host === "string" ? owner.host : undefined,
872
- });
762
+ const { owner } = readOwner(join(path, "current"));
763
+ holders.push({ scope, owner_id: owner.owner_id, pid: owner.pid, host: owner.host });
873
764
  }
874
765
  catch (error) {
875
766
  if (error.code !== "ENOENT") {
@@ -887,18 +778,7 @@ async function cleanup(coordRoot, binding, intent) {
887
778
  try {
888
779
  const releaseRepository = acquireRepositoryLease(coordRoot, claim);
889
780
  try {
890
- const reservation = reserveWorkspaceReclaim(coordRoot, claim);
891
- try {
892
- const authorityGuard = () => {
893
- reservation.assertCurrent();
894
- assertPeerWorkspaceAuthority(coordRoot, claim);
895
- };
896
- authorityGuard();
897
- return cleanupUnderLease(coordRoot, binding, claim, intent, undefined, authorityGuard);
898
- }
899
- finally {
900
- reservation.release();
901
- }
781
+ return cleanupUnderLease(coordRoot, binding, claim, intent);
902
782
  }
903
783
  finally {
904
784
  releaseRepository();
@@ -908,7 +788,7 @@ async function cleanup(coordRoot, binding, intent) {
908
788
  releaseBinding();
909
789
  }
910
790
  }
911
- function cleanupUnderLease(coordRoot, binding, claim, intent, reclaimRequest, beforeRemoval) {
791
+ function cleanupUnderLease(coordRoot, binding, claim, intent, reclaimRequest) {
912
792
  const repository = binding.repository;
913
793
  if (!repository ||
914
794
  intent.schema_version !== 1 ||
@@ -946,7 +826,7 @@ function cleanupUnderLease(coordRoot, binding, claim, intent, reclaimRequest, be
946
826
  };
947
827
  }
948
828
  const liveness = readWorkflowLiveness(coordRoot, binding.run_id);
949
- if (liveness !== "inactive" && !(liveness === "unknown" && reclaimRequest?.recovery)) {
829
+ if (liveness !== "inactive" && !(liveness === "unknown" && reclaimRequest?.force)) {
950
830
  return {
951
831
  ...base,
952
832
  status: "blocked",
@@ -1017,14 +897,14 @@ function cleanupUnderLease(coordRoot, binding, claim, intent, reclaimRequest, be
1017
897
  };
1018
898
  }
1019
899
  if (reclaimRequest) {
1020
- assertReclaimLiveness(coordRoot, binding, reclaimRequest.recovery);
1021
- assertReclaimOwnership(coordRoot, binding);
900
+ // The last liveness check before a destructive reset.
901
+ assertReclaimLiveness(coordRoot, binding, reclaimRequest.force);
1022
902
  revalidateClaimResources(claim, root);
1023
903
  reclaimRequest.prepared(prepareReclaim({
1024
904
  worktreePath: binding.workspace_root,
1025
905
  mode: reclaimRequest.mode,
1026
906
  runId: binding.run_id,
1027
- beforeEffect: reclaimRequest.beforeEffect,
907
+ beforeEffect: () => assertPeerWorkspaceAuthority(coordRoot, claim),
1028
908
  }));
1029
909
  }
1030
910
  appendWorkspaceEvent(coordRoot, claim, "cleanup_pending", {
@@ -1036,8 +916,7 @@ function cleanupUnderLease(coordRoot, binding, claim, intent, reclaimRequest, be
1036
916
  target_ref_oid: targetRef.out,
1037
917
  });
1038
918
  if (before.workspace_exists) {
1039
- reclaimRequest?.beforeEffect();
1040
- beforeRemoval?.();
919
+ assertPeerWorkspaceAuthority(coordRoot, claim);
1041
920
  revalidateClaimResources(claim, root);
1042
921
  assertContainedExisting(root, binding.workspace_root, "workspace cleanup path");
1043
922
  git(repository.source_root.realpath, ["worktree", "remove", binding.workspace_root]);
@@ -1057,7 +936,6 @@ function cleanupUnderLease(coordRoot, binding, claim, intent, reclaimRequest, be
1057
936
  return { ...base, status: "blocked", reason: "workspace ref changed during cleanup" };
1058
937
  }
1059
938
  revalidateClaimResources(claim, root);
1060
- reclaimRequest?.beforeRefEffect();
1061
939
  const deleted = gitMaybe(repository.source_root.realpath, [
1062
940
  "update-ref",
1063
941
  "-d",
@@ -1810,9 +1688,13 @@ function capabilityDigestForClaim(claim) {
1810
1688
  void claim;
1811
1689
  return stableDigest(basis);
1812
1690
  }
1691
+ function repositoryLeasePath(coordRoot, repository) {
1692
+ const key = stableDigest({ common: repository.common_dir.identity });
1693
+ return join(resolve(coordRoot), ".harnery", "workspaces", PROVIDER_ID, ".leases", `repository-${key}.lock`);
1694
+ }
1813
1695
  function acquireRepositoryLease(coordRoot, claim) {
1814
- const leaseDir = join(resolve(coordRoot), ".harnery", "workspaces", PROVIDER_ID, ".leases");
1815
- mkdirSync(leaseDir, { recursive: true, mode: stateDirMode() });
1696
+ const path = repositoryLeasePath(coordRoot, claim.repository);
1697
+ mkdirSync(dirname(path), { recursive: true, mode: stateDirMode() });
1816
1698
  // Mutual exclusion keys on the Git common directory alone. `git worktree add`,
1817
1699
  // `prune`, and the shared-`config` migration all write to the common directory's
1818
1700
  // administrative area, and once worktrees and submodules are allowed, several
@@ -1821,8 +1703,7 @@ function acquireRepositoryLease(coordRoot, claim) {
1821
1703
  // nested — writable roots. Including the writable root in the key would hand those
1822
1704
  // agents different locks and let their admin-area writes race. The writable root
1823
1705
  // stays in the lease metadata for diagnostics, not in the exclusion key.
1824
- const key = stableDigest({ common: claim.repository.common_dir.identity });
1825
- return acquireLease(coordRoot, join(leaseDir, `repository-${key}.lock`), claim, "repository");
1706
+ return acquireLease(coordRoot, path, claim, "repository");
1826
1707
  }
1827
1708
  function acquireLease(coordRoot, path, claim, scope) {
1828
1709
  const authoritySha256 = stableDigest({
@@ -1,11 +1,4 @@
1
1
  import type { WorkspaceClaim } from "./state.js";
2
- export interface WorkspaceReclaimReservation {
3
- assertCurrent(): void;
4
- release(): void;
5
- }
6
- /** The immutable claim is the authority; the lease reserves its destructive path. */
7
- export declare function reserveWorkspaceReclaim(coordRoot: string, claim: WorkspaceClaim): WorkspaceReclaimReservation;
8
- export declare function publishWorkspaceClaim<T>(coordRoot: string, claim: WorkspaceClaim, publish: () => T): T;
9
2
  export declare function assertClaimAuthority(claim: WorkspaceClaim, providerId: string, id: string): void;
10
3
  export declare function claimOverlapsWorkspace(claim: WorkspaceClaim, workspaceRoot: string): boolean;
11
4
  //# sourceMappingURL=ownership.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ownership.d.ts","sourceRoot":"","sources":["../../../../src/core/workflow/workspaces/ownership.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA8CjD,MAAM,WAAW,2BAA2B;IAC1C,aAAa,IAAI,IAAI,CAAC;IACtB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,qFAAqF;AACrF,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,cAAc,GACpB,2BAA2B,CAgC7B;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,MAAM,CAAC,GACf,CAAC,CA8DH;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAwBhG;AAmCD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAU5F"}
1
+ {"version":3,"file":"ownership.d.ts","sourceRoot":"","sources":["../../../../src/core/workflow/workspaces/ownership.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAwBhG;AAmCD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAU5F"}
@@ -1,135 +1,8 @@
1
- import { lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
2
- import { basename, dirname, isAbsolute, join, resolve } from "node:path";
3
- import { readJsonRecord, stableDigest } from "../durable-record.js";
4
- import { acquireNoClobberLease } from "./leases.js";
1
+ import { lstatSync, realpathSync } from "node:fs";
2
+ import { basename, dirname, isAbsolute, resolve } from "node:path";
3
+ import { stableDigest } from "../durable-record.js";
5
4
  import { containsPath } from "./paths.js";
6
5
  import { isWorkspaceAllocationRequest } from "./validate.js";
7
- const STALE_MS = 5 * 60 * 1_000;
8
- const RESERVATION_SCOPE = "workspace-reclaim";
9
- function leaseRoot(coordRoot, providerId) {
10
- return join(resolve(coordRoot), ".harnery", "workspaces", providerId, ".leases");
11
- }
12
- /** Serialize only publication and reservation registration, never Git work. */
13
- function withPublicationGate(coordRoot, providerId, operation) {
14
- const authority = stableDigest({ provider_id: providerId, scope: "workspace-publication" });
15
- const gate = acquireNoClobberLease({
16
- path: join(leaseRoot(coordRoot, providerId), "publication.lock"),
17
- scope: "workspace-publication",
18
- authoritySha256: authority,
19
- staleAfterMs: STALE_MS,
20
- validateStaleOwner: (owner) => owner.authority_sha256 === authority,
21
- });
22
- try {
23
- return operation();
24
- }
25
- finally {
26
- gate.release();
27
- }
28
- }
29
- function reservationPath(coordRoot, claim) {
30
- return join(leaseRoot(coordRoot, claim.provider_id), `reclaim-${claim.binding_id}.lock`);
31
- }
32
- function acquireReservation(coordRoot, claim) {
33
- const authority = stableDigest(claim);
34
- return acquireNoClobberLease({
35
- path: reservationPath(coordRoot, claim),
36
- scope: RESERVATION_SCOPE,
37
- authoritySha256: authority,
38
- staleAfterMs: STALE_MS,
39
- metadata: { binding_id: claim.binding_id, request_sha256: claim.request_sha256 },
40
- validateStaleOwner: (owner) => owner.authority_sha256 === authority &&
41
- owner.metadata?.binding_id === claim.binding_id &&
42
- owner.metadata?.request_sha256 === claim.request_sha256,
43
- });
44
- }
45
- /** The immutable claim is the authority; the lease reserves its destructive path. */
46
- export function reserveWorkspaceReclaim(coordRoot, claim) {
47
- assertClaimAuthority(claim, claim.provider_id, claim.binding_id);
48
- const lease = withPublicationGate(coordRoot, claim.provider_id, () => acquireReservation(coordRoot, claim));
49
- return {
50
- assertCurrent() {
51
- const path = reservationPath(coordRoot, claim);
52
- const current = join(path, "current");
53
- const pin = join(path, lease.owner.owner_file);
54
- const a = lstatSync(current, { bigint: true });
55
- const b = lstatSync(pin, { bigint: true });
56
- if (!a.isFile() ||
57
- !b.isFile() ||
58
- a.dev !== b.dev ||
59
- a.ino !== b.ino ||
60
- readFileSync(current, "utf8") !== readFileSync(pin, "utf8") ||
61
- stableDigest(readJsonRecord(current, "reclaim reservation")) !== stableDigest(lease.owner)) {
62
- throw new Error("workspace reclaim reservation owner changed");
63
- }
64
- const persisted = readJsonRecord(join(dirname(dirname(path)), claim.binding_id, "claim.json"), "reserved workspace claim");
65
- if (stableDigest(persisted) !== stableDigest(claim)) {
66
- throw new Error("workspace reclaim reservation claim changed");
67
- }
68
- },
69
- release: () => lease.release(),
70
- };
71
- }
72
- export function publishWorkspaceClaim(coordRoot, claim, publish) {
73
- assertClaimAuthority(claim, claim.provider_id, claim.binding_id);
74
- return withPublicationGate(coordRoot, claim.provider_id, () => {
75
- for (const entry of readdirSync(leaseRoot(coordRoot, claim.provider_id), {
76
- withFileTypes: true,
77
- })) {
78
- if (!entry.name.startsWith("reclaim-") || !entry.name.endsWith(".lock"))
79
- continue;
80
- if (!entry.isDirectory())
81
- throw new Error("workspace reclaim reservation is not a directory");
82
- const path = join(leaseRoot(coordRoot, claim.provider_id), entry.name);
83
- const current = join(path, "current");
84
- try {
85
- lstatSync(current);
86
- }
87
- catch (error) {
88
- if (error.code === "ENOENT")
89
- continue;
90
- throw error;
91
- }
92
- const id = entry.name.slice("reclaim-".length, -".lock".length);
93
- const reserved = readJsonRecord(join(dirname(leaseRoot(coordRoot, claim.provider_id)), id, "claim.json"), "reserved workspace claim");
94
- assertClaimAuthority(reserved, claim.provider_id, id);
95
- // Unknown reservation authority is never interpreted as an unrelated owner.
96
- const owner = readJsonRecord(current, "reclaim reservation");
97
- if (owner.schema_version !== 1 ||
98
- typeof owner.owner_id !== "string" ||
99
- !/^[a-f0-9-]{36}$/.test(owner.owner_id) ||
100
- owner.owner_file !== `owner-${owner.owner_id}.json` ||
101
- !Number.isSafeInteger(owner.pid) ||
102
- owner.pid <= 0 ||
103
- typeof owner.host !== "string" ||
104
- owner.host.length === 0 ||
105
- !Number.isFinite(Date.parse(owner.created_at)) ||
106
- !Number.isFinite(Date.parse(owner.expires_at)) ||
107
- Date.parse(owner.expires_at) < Date.parse(owner.created_at) ||
108
- owner.scope !== RESERVATION_SCOPE ||
109
- owner.authority_sha256 !== stableDigest(reserved) ||
110
- owner.metadata?.binding_id !== id ||
111
- owner.metadata?.request_sha256 !== reserved.request_sha256) {
112
- throw new Error("workspace reclaim reservation authority is unproven");
113
- }
114
- const a = lstatSync(current, { bigint: true });
115
- const pin = join(path, owner.owner_file);
116
- const b = lstatSync(pin, { bigint: true });
117
- if (!a.isFile() ||
118
- !b.isFile() ||
119
- a.dev !== b.dev ||
120
- a.ino !== b.ino ||
121
- readFileSync(current, "utf8") !== readFileSync(pin, "utf8"))
122
- throw new Error("workspace reclaim reservation owner is unproven");
123
- if (!claimOverlapsWorkspace(claim, reserved.workspace_root))
124
- continue;
125
- // This succeeds only for an expired, provably dead, exact prior owner.
126
- // It retires the reservation, never the immutable claim or resource.
127
- const recovered = acquireReservation(coordRoot, reserved);
128
- recovered.release();
129
- }
130
- return publish();
131
- });
132
- }
133
6
  export function assertClaimAuthority(claim, providerId, id) {
134
7
  if (claim?.schema_version !== 1 ||
135
8
  !/^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/.test(providerId) ||
@@ -14,11 +14,10 @@
14
14
  * always has. So reclaim adds no second removal path that could diverge from the
15
15
  * audited one, and a force-delete of live work exists nowhere in the codebase.
16
16
  */
17
- import type { WorkflowRecoveryAuthorization } from "../recovery-audit.js";
18
17
  import type { WorkflowLiveness } from "./recovery.js";
19
18
  import type { WorkspaceCleanupAttempt, WorkspaceCleanupReceipt } from "./types.js";
20
19
  export type ReclaimMode = "salvage" | "discard";
21
- export type WorkflowReclaimRefusalCode = "workflow_reclaim_run_live" | "workflow_reclaim_liveness_unknown" | "workflow_reclaim_recovery_mismatch" | "workflow_reclaim_ownership_unproven" | "workflow_reclaim_lease_held";
20
+ export type WorkflowReclaimRefusalCode = "workflow_reclaim_run_live" | "workflow_reclaim_liveness_unknown" | "workflow_reclaim_ownership_unproven" | "workflow_reclaim_lease_held";
22
21
  export declare class WorkflowReclaimError extends Error {
23
22
  readonly code: WorkflowReclaimRefusalCode;
24
23
  constructor(code: WorkflowReclaimRefusalCode, message: string);
@@ -26,9 +25,8 @@ export declare class WorkflowReclaimError extends Error {
26
25
  export interface WorkspaceReclaimOptions {
27
26
  mode: ReclaimMode;
28
27
  yes?: boolean;
29
- unknownLiveness?: WorkflowRecoveryAuthorization & {
30
- evidenceSha256: string;
31
- };
28
+ /** Admit missing or corrupt liveness evidence; active evidence still refuses. */
29
+ force?: boolean;
32
30
  }
33
31
  export interface ReclaimLeaseHolder {
34
32
  scope: "binding" | "repository" | "resume";
@@ -42,7 +40,6 @@ export interface WorkspaceReclaimPreview {
42
40
  run_id: string;
43
41
  mode: ReclaimMode;
44
42
  liveness: WorkflowLiveness;
45
- evidence_sha256: string;
46
43
  ownership: "ok" | "unproven";
47
44
  ownership_detail?: string;
48
45
  dirty_paths: string[] | null;
@@ -72,7 +69,7 @@ export interface PrepareReclaimInput {
72
69
  worktreePath: string;
73
70
  mode: ReclaimMode;
74
71
  runId: string;
75
- /** Revalidate the held reservation and peer authority immediately before effects. */
72
+ /** Recheck peer authority once, immediately before the first destructive step. */
76
73
  beforeEffect?: () => void;
77
74
  }
78
75
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"reclaim.d.ts","sourceRoot":"","sources":["../../../../src/core/workflow/workspaces/reclaim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,sBAAsB,CAAC;AAE1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAEnF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;AAEhD,MAAM,MAAM,0BAA0B,GAClC,2BAA2B,GAC3B,mCAAmC,GACnC,oCAAoC,GACpC,qCAAqC,GACrC,6BAA6B,CAAC;AAElC,qBAAa,oBAAqB,SAAQ,KAAK;IAE3C,QAAQ,CAAC,IAAI,EAAE,0BAA0B;gBAAhC,IAAI,EAAE,0BAA0B,EACzC,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,eAAe,CAAC,EAAE,6BAA6B,GAAG;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9E;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,SAAS,GAAG,YAAY,GAAG,QAAQ,CAAC;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,IAAI,GAAG,UAAU,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,kBAAkB,EAAE,CAAC;CACpC;AAED,MAAM,MAAM,sBAAsB,GAC9B,uBAAuB,GACvB,CAAC,IAAI,CAAC,uBAAuB,EAAE,QAAQ,CAAC,GAAG;IACzC,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,kBAAkB,CAAC;IAChC,OAAO,EAAE,uBAAuB,GAAG,uBAAuB,CAAC;CAC5D,CAAC,CAAC;AAEP,MAAM,MAAM,kBAAkB,GAC1B;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/C,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,kBAAkB,CAwD7E;AAYD;qEACqE;AACrE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD"}
1
+ {"version":3,"file":"reclaim.d.ts","sourceRoot":"","sources":["../../../../src/core/workflow/workspaces/reclaim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAEnF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;AAEhD,MAAM,MAAM,0BAA0B,GAClC,2BAA2B,GAC3B,mCAAmC,GACnC,qCAAqC,GACrC,6BAA6B,CAAC;AAElC,qBAAa,oBAAqB,SAAQ,KAAK;IAE3C,QAAQ,CAAC,IAAI,EAAE,0BAA0B;gBAAhC,IAAI,EAAE,0BAA0B,EACzC,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,iFAAiF;IACjF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,SAAS,GAAG,YAAY,GAAG,QAAQ,CAAC;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,SAAS,EAAE,IAAI,GAAG,UAAU,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,kBAAkB,EAAE,CAAC;CACpC;AAED,MAAM,MAAM,sBAAsB,GAC9B,uBAAuB,GACvB,CAAC,IAAI,CAAC,uBAAuB,EAAE,QAAQ,CAAC,GAAG;IACzC,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,kBAAkB,CAAC;IAChC,OAAO,EAAE,uBAAuB,GAAG,uBAAuB,CAAC;CAC5D,CAAC,CAAC;AAEP,MAAM,MAAM,kBAAkB,GAC1B;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/C,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,kBAAkB,CAoD7E;AAYD;qEACqE;AACrE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD"}