@abelxiaoxing/cadence 2.0.14 → 2.0.16

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.
@@ -6,7 +6,7 @@ import { spawn } from "node:child_process";
6
6
  import { mkdirSync as mkdirSync6 } from "node:fs";
7
7
  import { request } from "node:http";
8
8
  import { homedir as homedir6 } from "node:os";
9
- import { dirname as dirname13, join as join15, resolve as resolve7 } from "node:path";
9
+ import { dirname as dirname13, join as join16, resolve as resolve7 } from "node:path";
10
10
  import { fileURLToPath as fileURLToPath2 } from "node:url";
11
11
 
12
12
  // src/install-config.mjs
@@ -327,6 +327,7 @@ var ERROR_CODES = [
327
327
  "quarantine",
328
328
  "capacity",
329
329
  "unavailable",
330
+ "registry-locked",
330
331
  "auth-unavailable",
331
332
  "recording-exhausted",
332
333
  "receipt-inconclusive"
@@ -3232,6 +3233,7 @@ function buildUnit(u) {
3232
3233
  "KillMode=mixed",
3233
3234
  "TimeoutStopSec=90",
3234
3235
  "Restart=on-failure",
3236
+ "RestartPreventExitStatus=78",
3235
3237
  "RestartSec=10",
3236
3238
  "",
3237
3239
  "[Install]",
@@ -3425,7 +3427,7 @@ function autostartStatus(s) {
3425
3427
 
3426
3428
  // src/service/coordination.ts
3427
3429
  import { randomBytes as randomBytes2, randomUUID as randomUUID3 } from "node:crypto";
3428
- import { opendirSync, realpathSync as realpathSync4 } from "node:fs";
3430
+ import { opendirSync } from "node:fs";
3429
3431
  import { hostname as hostname2 } from "node:os";
3430
3432
  import { dirname as dirname5, isAbsolute as isAbsolute4, join as join5, relative as relative3, sep as sep4 } from "node:path";
3431
3433
  var registrationKeys = [
@@ -3517,6 +3519,21 @@ function names(path2, allowed) {
3517
3519
  dir.closeSync();
3518
3520
  }
3519
3521
  }
3522
+ function decodeRegistryLock(snapshot) {
3523
+ const o = closed(json(snapshot), ["schema", "token", "pid", "host"]);
3524
+ return {
3525
+ schema: literal(o.schema, "cadence-service-registry-lock/1"),
3526
+ token: decodeId(o.token),
3527
+ pid: integer(o.pid, 1, 2147483647),
3528
+ host: text(o.host, 255, true)
3529
+ };
3530
+ }
3531
+ var registryNames = [
3532
+ "format.json",
3533
+ "roots.json",
3534
+ "registry.lock",
3535
+ "recovery-v1"
3536
+ ];
3520
3537
  function recoveryOwnerStatus(pid, host) {
3521
3538
  if (host !== hostname2())
3522
3539
  return "ambiguous";
@@ -3546,19 +3563,25 @@ function recoveryDigest(registry, selected) {
3546
3563
  }
3547
3564
 
3548
3565
  class ServiceCoordination {
3549
- store;
3566
+ project;
3550
3567
  files;
3551
- directory;
3552
3568
  registryDirectory;
3553
3569
  stateHome;
3554
3570
  constructor(workspace, stateHome) {
3555
- this.store = new TaskStore(resolveWorkspace(workspace).root, stateHome);
3556
- const namespace = dirname5(this.store.privateRoot);
3571
+ this.project = workspace === null ? undefined : new TaskStore(resolveWorkspace(workspace).root, stateHome);
3572
+ this.stateHome = this.project ? dirname5(dirname5(this.project.privateRoot)) : canonicalAmbientRoot(stateHome ?? defaultStateHome());
3573
+ const namespace = join5(this.stateHome, "cadence-assistant-v1");
3557
3574
  this.files = new PrivateFiles(namespace);
3558
- this.stateHome = dirname5(namespace);
3559
- this.directory = join5(this.store.privateRoot, "service-v1");
3560
3575
  this.registryDirectory = join5(namespace, "service-registry-v1");
3561
3576
  }
3577
+ get store() {
3578
+ if (!this.project)
3579
+ throw new ContractError("forbidden");
3580
+ return this.project;
3581
+ }
3582
+ get directory() {
3583
+ return join5(this.store.privateRoot, "service-v1");
3584
+ }
3562
3585
  path(name) {
3563
3586
  return join5(this.directory, name);
3564
3587
  }
@@ -3568,11 +3591,7 @@ class ServiceCoordination {
3568
3591
  registry() {
3569
3592
  if (!this.files.directory(this.registryDirectory))
3570
3593
  return;
3571
- names(this.registryDirectory, [
3572
- "format.json",
3573
- "roots.json",
3574
- "registry.lock"
3575
- ]);
3594
+ names(this.registryDirectory, registryNames);
3576
3595
  const format = this.files.read(this.registryPath("format.json"));
3577
3596
  const snapshot = this.files.read(this.registryPath("roots.json"), REGISTRY_BYTES);
3578
3597
  if (!format || !snapshot)
@@ -3583,7 +3602,7 @@ class ServiceCoordination {
3583
3602
  const o = closed(json(snapshot, REGISTRY_BYTES), ["schema", "entries"]);
3584
3603
  const entries = list(o.entries, REGISTRY_ENTRIES, decodeReservation);
3585
3604
  for (const [i, row] of entries.entries()) {
3586
- if (row.stateHome !== this.stateHome || realpathSync4(row.root) !== row.root)
3605
+ if (row.stateHome !== this.stateHome || canonicalAmbientRoot(row.root) !== row.root)
3587
3606
  refused();
3588
3607
  if (entries.slice(0, i).some((other) => rootsOverlap(row.root, other.root) || row.endpoint === other.endpoint))
3589
3608
  refused();
@@ -3597,10 +3616,10 @@ class ServiceCoordination {
3597
3616
  }
3598
3617
  };
3599
3618
  }
3600
- assertNamespace(walks) {
3601
- if (!this.files.directory(this.directory, false, walks))
3619
+ assertNamespace(walks, directory2 = this.directory) {
3620
+ if (!this.files.directory(directory2, false, walks))
3602
3621
  refused();
3603
- names(this.directory, [
3622
+ names(directory2, [
3604
3623
  "format.json",
3605
3624
  "config.json",
3606
3625
  "registration.json",
@@ -3612,15 +3631,16 @@ class ServiceCoordination {
3612
3631
  "history-v2",
3613
3632
  "conversations-v1"
3614
3633
  ]);
3615
- this.files.directory(join5(this.directory, "history-v2"), false, walks);
3634
+ this.files.directory(join5(directory2, "history-v2"), false, walks);
3616
3635
  }
3617
- selected() {
3636
+ selected(directory2 = this.directory, root = this.store.root, stoppedOnly = false) {
3618
3637
  const walks = new DirectoryWalks;
3619
- if (!this.files.directory(this.directory, false, walks))
3638
+ if (!this.files.directory(directory2, false, walks))
3620
3639
  return;
3621
- this.assertNamespace(walks);
3622
- const manifest = this.files.read(this.path("format.json"), undefined, walks);
3623
- const config = this.files.read(this.path("config.json"), undefined, walks);
3640
+ this.assertNamespace(walks, directory2);
3641
+ const path2 = (name) => join5(directory2, name);
3642
+ const manifest = this.files.read(path2("format.json"), undefined, walks);
3643
+ const config = this.files.read(path2("config.json"), undefined, walks);
3624
3644
  if (!manifest || !config)
3625
3645
  refused();
3626
3646
  const f = closed(json(manifest), [
@@ -3630,20 +3650,22 @@ class ServiceCoordination {
3630
3650
  "configIdentity"
3631
3651
  ]);
3632
3652
  literal(f.schema, "cadence-service-format/1");
3633
- literal(f.root, this.store.root);
3653
+ literal(f.root, root);
3634
3654
  literal(f.stateHome, this.stateHome);
3635
3655
  literal(f.configIdentity, config.identity);
3636
3656
  const c = decodeOwnerConfig(json(config));
3637
- const hostConfig = this.files.read(this.path("host-config.json"), undefined, walks);
3657
+ const hostConfig = this.files.read(path2("host-config.json"), undefined, walks);
3638
3658
  if (hostConfig && hostConfig.identity !== c.policyIdentity)
3639
3659
  refused();
3640
- const registration = this.files.read(this.path("registration.json"), undefined, walks);
3641
- const owner = this.files.read(this.path("owner.lock"), undefined, walks);
3642
- const capability = this.files.read(this.path("local-capability.json"), undefined, walks);
3643
- const checkout = this.files.read(this.store.lockPath, 8192, walks);
3660
+ const registration = this.files.read(path2("registration.json"), undefined, walks);
3661
+ const owner = this.files.read(path2("owner.lock"), undefined, walks);
3662
+ const capability = this.files.read(path2("local-capability.json"), undefined, walks);
3663
+ const checkout = this.files.read(join5(dirname5(directory2), "checkout.lock"), 8192, walks);
3644
3664
  const value = registration ? decodeRegistration(json(registration)) : undefined;
3645
- if (value && (value.root !== this.store.root || value.stateHome !== this.stateHome || value.configIdentity !== config.identity || !endpointMatches(c.endpoint, value.endpoint)))
3665
+ if (value && (value.root !== root || value.stateHome !== this.stateHome || value.configIdentity !== config.identity || !endpointMatches(c.endpoint, value.endpoint)))
3646
3666
  refused();
3667
+ if (stoppedOnly && (checkout || value && value.phase !== "stopped"))
3668
+ throw new ContractError("quarantine");
3647
3669
  if (value && value.phase !== "stopped") {
3648
3670
  if (!owner || !checkout || !capability)
3649
3671
  refused();
@@ -3723,18 +3745,21 @@ class ServiceCoordination {
3723
3745
  }
3724
3746
  withRegistry(operation) {
3725
3747
  this.files.directory(this.registryDirectory, true);
3726
- names(this.registryDirectory, [
3727
- "format.json",
3728
- "roots.json",
3729
- "registry.lock"
3730
- ]);
3748
+ names(this.registryDirectory, registryNames);
3731
3749
  const lockPath = this.registryPath("registry.lock");
3732
- const lock = this.files.create(lockPath, encoded({
3733
- schema: "cadence-service-registry-lock/1",
3734
- token: randomUUID3(),
3735
- pid: process.pid,
3736
- host: hostname2()
3737
- }));
3750
+ let lock;
3751
+ try {
3752
+ lock = this.files.create(lockPath, encoded({
3753
+ schema: "cadence-service-registry-lock/1",
3754
+ token: randomUUID3(),
3755
+ pid: process.pid,
3756
+ host: hostname2()
3757
+ }));
3758
+ } catch (error) {
3759
+ if (error instanceof Error && "code" in error && error.code === "EEXIST")
3760
+ throw new ContractError("registry-locked");
3761
+ throw error;
3762
+ }
3738
3763
  let dirty = false;
3739
3764
  try {
3740
3765
  let format = this.files.read(this.registryPath("format.json"));
@@ -3758,11 +3783,7 @@ class ServiceCoordination {
3758
3783
  }, () => {
3759
3784
  this.files.assert(lockPath, lock);
3760
3785
  this.files.assert(this.registryPath("format.json"), format);
3761
- names(this.registryDirectory, [
3762
- "format.json",
3763
- "roots.json",
3764
- "registry.lock"
3765
- ]);
3786
+ names(this.registryDirectory, registryNames);
3766
3787
  });
3767
3788
  this.files.remove(lockPath, lock);
3768
3789
  return result;
@@ -3773,6 +3794,7 @@ class ServiceCoordination {
3773
3794
  }
3774
3795
  }
3775
3796
  acquire(input, resolvedEndpoint = input.endpoint, initializeNew, reselectExisting) {
3797
+ this.store;
3776
3798
  const config = decodeOwnerConfig(input);
3777
3799
  const bound = endpoint(resolvedEndpoint);
3778
3800
  if (!endpointMatches(config.endpoint, bound))
@@ -3894,6 +3916,154 @@ class ServiceCoordination {
3894
3916
  });
3895
3917
  });
3896
3918
  }
3919
+ registryLockDiagnostic() {
3920
+ try {
3921
+ const lock = this.files.read(this.registryPath("registry.lock"));
3922
+ if (!lock)
3923
+ return;
3924
+ const value = decodeRegistryLock(lock);
3925
+ return {
3926
+ reason: "registry-locked",
3927
+ owner: recoveryOwnerStatus(value.pid, value.host)
3928
+ };
3929
+ } catch {
3930
+ return { reason: "registry-locked", owner: "unreadable" };
3931
+ }
3932
+ }
3933
+ registryRecoveryState() {
3934
+ const lock = this.files.read(this.registryPath("registry.lock"));
3935
+ if (!lock)
3936
+ throw new ContractError("conflict");
3937
+ const value = decodeRegistryLock(lock);
3938
+ names(this.registryDirectory, registryNames);
3939
+ const format = this.files.read(this.registryPath("format.json"));
3940
+ const roots = this.files.read(this.registryPath("roots.json"), REGISTRY_BYTES);
3941
+ const registry = !format && !roots ? undefined : this.registry();
3942
+ const entries = registry?.value.entries ?? [];
3943
+ const protectedRoots = new Set(entries.map((row) => hash(row.root)));
3944
+ const proofs = [];
3945
+ const directory2 = opendirSync(this.files.namespace);
3946
+ try {
3947
+ let count = 0;
3948
+ for (let entry = directory2.readSync();entry; entry = directory2.readSync()) {
3949
+ if (++count > 1024)
3950
+ throw new ContractError("capacity");
3951
+ if (!/^[a-f0-9]{64}$/.test(entry.name) || protectedRoots.has(entry.name))
3952
+ continue;
3953
+ const service = join5(this.files.namespace, entry.name, "service-v1");
3954
+ if (!this.files.directory(service))
3955
+ continue;
3956
+ const manifest = this.files.read(join5(service, "format.json"));
3957
+ if (!manifest)
3958
+ refused();
3959
+ const f = closed(json(manifest), [
3960
+ "schema",
3961
+ "root",
3962
+ "stateHome",
3963
+ "configIdentity"
3964
+ ]);
3965
+ const root = pathValue(f.root);
3966
+ if (hash(root) !== entry.name)
3967
+ refused();
3968
+ const selected = this.selected(service, root, true);
3969
+ if (!selected)
3970
+ throw new ContractError("quarantine");
3971
+ proofs.push([
3972
+ entry.name,
3973
+ ...[
3974
+ selected.manifest,
3975
+ selected.config,
3976
+ selected.hostConfig,
3977
+ selected.registration
3978
+ ].map((file) => file ? [file.identity, file.stamp] : null)
3979
+ ]);
3980
+ }
3981
+ } finally {
3982
+ directory2.closeSync();
3983
+ }
3984
+ proofs.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b), "en"));
3985
+ const stateDigest = hash(encoded([
3986
+ lock.identity,
3987
+ lock.stamp,
3988
+ format ? [format.identity, format.stamp] : null,
3989
+ roots ? [roots.identity, roots.stamp] : null,
3990
+ proofs
3991
+ ]));
3992
+ const epoch = hash(encoded([lock.identity, lock.stamp]));
3993
+ const archive = this.registryPath(join5("recovery-v1", epoch));
3994
+ const attempts = [];
3995
+ let previousResolver = null;
3996
+ if (this.files.directory(archive)) {
3997
+ names(archive, Array.from({ length: 128 }, (_, n) => `${n}.json`));
3998
+ for (let n = 0;n < 128; n++) {
3999
+ const attempt = this.files.read(join5(archive, `${n}.json`), 32768);
4000
+ if (!attempt) {
4001
+ names(archive, attempts.map((_, i) => `${i}.json`));
4002
+ break;
4003
+ }
4004
+ const o = closed(json(attempt, 32768), [
4005
+ "schema",
4006
+ "lock",
4007
+ "pid",
4008
+ "host",
4009
+ "digest"
4010
+ ]);
4011
+ literal(o.schema, "cadence-registry-recovery/1");
4012
+ literal(o.lock, lock.bytes.toString("utf8"));
4013
+ decodeHash(o.digest);
4014
+ previousResolver = recoveryOwnerStatus(integer(o.pid, 1, 2147483647), text(o.host, 255, true));
4015
+ attempts.push(attempt);
4016
+ }
4017
+ }
4018
+ const inspection = {
4019
+ scope: "global-registry",
4020
+ epoch,
4021
+ coordinationDigest: hash(encoded([stateDigest, attempts.map((a) => [a.identity, a.stamp])])),
4022
+ owner: recoveryOwnerStatus(value.pid, value.host),
4023
+ process: { pid: value.pid, host: value.host },
4024
+ reservations: entries.length,
4025
+ previousResolver,
4026
+ resolution: "local-interactive-only"
4027
+ };
4028
+ return { lock, archive, attempts, stateDigest, inspection };
4029
+ }
4030
+ inspectRegistryRecovery() {
4031
+ return this.registryRecoveryState().inspection;
4032
+ }
4033
+ resolveRegistryRecovery(expected, assertAuthority) {
4034
+ assertAuthority();
4035
+ const state = this.registryRecoveryState();
4036
+ if (encoded(state.inspection) !== encoded(expected))
4037
+ throw new ContractError("conflict");
4038
+ if (expected.owner !== "absent" || expected.previousResolver !== null && expected.previousResolver !== "absent")
4039
+ throw new ContractError("quarantine");
4040
+ if (state.attempts.length >= 128)
4041
+ throw new ContractError("capacity");
4042
+ assertAuthority();
4043
+ const receiptPath = join5(state.archive, `${state.attempts.length}.json`);
4044
+ const receipt = this.files.create(receiptPath, encoded({
4045
+ schema: "cadence-registry-recovery/1",
4046
+ lock: state.lock.bytes.toString("utf8"),
4047
+ pid: process.pid,
4048
+ host: hostname2(),
4049
+ digest: expected.coordinationDigest
4050
+ }), 32768);
4051
+ assertAuthority();
4052
+ const current = this.registryRecoveryState();
4053
+ if (current.stateDigest !== state.stateDigest || current.inspection.owner !== "absent" || current.attempts.length !== state.attempts.length + 1)
4054
+ throw new ContractError("conflict");
4055
+ for (const [n, attempt] of state.attempts.entries())
4056
+ this.files.assert(join5(state.archive, `${n}.json`), attempt, 32768);
4057
+ this.files.assert(receiptPath, receipt, 32768);
4058
+ assertAuthority();
4059
+ this.files.remove(this.registryPath("registry.lock"), state.lock);
4060
+ return {
4061
+ status: "resolved",
4062
+ scope: "global-registry",
4063
+ recoveryId: expected.epoch,
4064
+ notice: "registry-only-project-fences-preserved"
4065
+ };
4066
+ }
3897
4067
  prepareRecoveryRelease(expected, assertAuthority) {
3898
4068
  assertAuthority();
3899
4069
  if (this.recoverySnapshot().digest !== expected)
@@ -4045,7 +4215,7 @@ class OwnerLease {
4045
4215
 
4046
4216
  // src/service/gateway-cli.ts
4047
4217
  import { homedir as homedir5 } from "node:os";
4048
- import { dirname as dirname8, join as join9, resolve as resolve6 } from "node:path";
4218
+ import { dirname as dirname10, join as join12, resolve as resolve6 } from "node:path";
4049
4219
  import { fileURLToPath } from "node:url";
4050
4220
 
4051
4221
  // src/service/config.ts
@@ -4099,6 +4269,7 @@ function localOrigin(policy, actualPort) {
4099
4269
  // src/service/gateway-store.ts
4100
4270
  import { randomBytes as randomBytes4, randomUUID as randomUUID5 } from "node:crypto";
4101
4271
  import { opendirSync as opendirSync2 } from "node:fs";
4272
+ import { hostname as hostname3 } from "node:os";
4102
4273
  import { join as join7, resolve as resolve4 } from "node:path";
4103
4274
 
4104
4275
  // src/service/auth.ts
@@ -4473,6 +4644,8 @@ function decodeGatewayProject(value) {
4473
4644
  return project;
4474
4645
  }
4475
4646
  function registration(value) {
4647
+ const hasHost = !!value && typeof value === "object" && Object.hasOwn(value, "host");
4648
+ const hasRecovery = !!value && typeof value === "object" && Object.hasOwn(value, "recovery");
4476
4649
  const o = closed(value, [
4477
4650
  "schema",
4478
4651
  "epoch",
@@ -4481,8 +4654,22 @@ function registration(value) {
4481
4654
  "capability",
4482
4655
  "selection",
4483
4656
  "pid",
4484
- "phase"
4657
+ "phase",
4658
+ ...hasHost ? ["host"] : [],
4659
+ ...hasRecovery ? ["recovery"] : []
4485
4660
  ]);
4661
+ let recovery;
4662
+ if (hasRecovery) {
4663
+ const r = closed(o.recovery, ["receipt", "sha256", "stamp"]);
4664
+ const receipt = text(r.receipt, 128, true);
4665
+ if (!/^recovery-v1\/[a-f0-9]{64}\/(?:0|[1-9][0-9]{0,2})\.json$/.test(receipt))
4666
+ throw new ContractError("corrupt-selected");
4667
+ recovery = {
4668
+ receipt,
4669
+ sha256: decodeHash(r.sha256),
4670
+ stamp: text(r.stamp, 512, true)
4671
+ };
4672
+ }
4486
4673
  const endpoint2 = text(o.endpoint, 128, true), capability = text(o.capability, 43, true);
4487
4674
  if (!/^http:\/\/(?:127\.0\.0\.1|\[::1\]):[1-9][0-9]{0,4}$/.test(endpoint2) || !/^[A-Za-z0-9_-]{43}$/.test(capability))
4488
4675
  throw new ContractError("corrupt-selected");
@@ -4493,7 +4680,9 @@ function registration(value) {
4493
4680
  endpoint: endpoint2,
4494
4681
  capability,
4495
4682
  selection: decodeHash(o.selection),
4496
- pid: integer(o.pid, 1),
4683
+ pid: integer(o.pid, 1, 2147483647),
4684
+ ...hasHost ? { host: text(o.host, 255, true) } : {},
4685
+ ...recovery ? { recovery } : {},
4497
4686
  phase: oneOf(o.phase, ["alive", "stopping", "stopped", "quarantined"])
4498
4687
  };
4499
4688
  }
@@ -4553,6 +4742,8 @@ class GatewayStore {
4553
4742
  const previous = this.read();
4554
4743
  if (previous && previous.phase !== "stopped")
4555
4744
  throw new ContractError("quarantine");
4745
+ if (previous?.recovery)
4746
+ this.associatedProjects();
4556
4747
  this.lock = this.files.create(this.path("gateway.lock"), encoded({ epoch: randomUUID5() }));
4557
4748
  const source = { serverConfig: this.serverConfig, token };
4558
4749
  if (!previous)
@@ -4572,6 +4763,8 @@ class GatewayStore {
4572
4763
  capability: randomBytes4(32).toString("base64url"),
4573
4764
  selection: hash(inputBytes),
4574
4765
  pid: process.pid,
4766
+ host: hostname3(),
4767
+ ...previous?.recovery ? { recovery: previous.recovery } : {},
4575
4768
  phase: "alive"
4576
4769
  };
4577
4770
  this.selected = this.selected ? this.files.replace(this.path("gateway.json"), this.selected, encoded(this.value)) : this.files.create(this.path("gateway.json"), encoded(this.value));
@@ -4615,6 +4808,21 @@ class GatewayStore {
4615
4808
  }
4616
4809
  return out;
4617
4810
  }
4811
+ associatedProjects() {
4812
+ const projects = this.projects(), recovery = this.read()?.recovery;
4813
+ if (!recovery)
4814
+ return projects;
4815
+ const receipt = this.files.read(this.path(recovery.receipt), 65536);
4816
+ if (!receipt || receipt.identity !== recovery.sha256 || receipt.stamp !== recovery.stamp)
4817
+ throw new ContractError("corrupt-selected");
4818
+ const value = json(receipt, 65536);
4819
+ literal(value.schema, "cadence-workbench-recovery/1");
4820
+ const retired = list(value.retiredProjects, 128, (entry) => {
4821
+ const row = closed(entry, ["id", "stamp"]);
4822
+ return { id: decodeHash(row.id), stamp: text(row.stamp, 512, true) };
4823
+ });
4824
+ return projects.filter((project) => !retired.some((row) => row.id === project.id && row.stamp === this.files.read(this.path(`projects/${project.id}.json`))?.stamp));
4825
+ }
4618
4826
  saveProject(project) {
4619
4827
  this.assert();
4620
4828
  const p = decodeGatewayProject(project), path2 = this.path(`projects/${p.id}.json`), before = this.files.read(path2);
@@ -4630,1202 +4838,660 @@ class GatewayStore {
4630
4838
  }
4631
4839
  }
4632
4840
 
4633
- // src/service/runtime.ts
4634
- import { homedir as homedir4 } from "node:os";
4635
- import { dirname as dirname7, isAbsolute as isAbsolute6, join as join8, resolve as resolve5 } from "node:path";
4636
- var STARTUP_INPUT_BYTES = 16 * 1024;
4637
- function absolute(value) {
4638
- const result = text(value, 4096, true);
4639
- if (!isAbsolute6(result))
4640
- throw new ContractError("invalid");
4641
- return result;
4642
- }
4643
- function decodeHostConfig(value) {
4644
- const o = closed(value, [
4645
- "schema",
4646
- "http",
4647
- "cwd",
4648
- "agentDir",
4649
- "tokenSource",
4650
- "tokenFile"
4651
- ]);
4652
- const tokenSource = oneOf(o.tokenSource, [
4653
- "generated",
4654
- "environment",
4655
- "file",
4656
- "config"
4657
- ]);
4658
- const tokenFile = o.tokenFile === null ? null : absolute(o.tokenFile);
4659
- if ((tokenSource === "file" || tokenSource === "config") !== !!tokenFile)
4660
- throw new ContractError("invalid");
4841
+ // src/service/recovery-cli.ts
4842
+ import { isatty } from "node:tty";
4843
+
4844
+ // src/history/recovery.ts
4845
+ import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
4846
+ import { lstatSync as lstatSync7, opendirSync as opendirSync4 } from "node:fs";
4847
+ import { join as join9, relative as relative4 } from "node:path";
4848
+
4849
+ // src/service/recovery.ts
4850
+ async function inspectRecovery(coordination, history) {
4851
+ const selected = coordination.recoverySnapshot();
4852
+ const facts = history ? await history.inspect(selected.registration.epoch) : null;
4853
+ if (facts)
4854
+ decodeHash(facts.digest);
4855
+ if (coordination.recoverySnapshot().digest !== selected.digest)
4856
+ throw new ContractError("conflict");
4661
4857
  return {
4662
- schema: literal(o.schema, "cadence-service-host/1"),
4663
- http: decodeHttpPolicy(o.http),
4664
- cwd: absolute(o.cwd),
4665
- agentDir: absolute(o.agentDir),
4666
- tokenSource,
4667
- tokenFile
4858
+ epoch: selected.registration.epoch,
4859
+ coordinationDigest: selected.digest,
4860
+ history: facts,
4861
+ owner: recoveryOwnerStatus(selected.registration.pid, selected.registration.host),
4862
+ resolution: "local-interactive-only"
4668
4863
  };
4669
4864
  }
4670
- function readHostConfig(c) {
4671
- const owner = c.configuration();
4672
- if (!owner)
4673
- return;
4674
- const snapshot = c.files.read(c.path("host-config.json"));
4675
- if (!snapshot)
4676
- throw new ContractError("corrupt-selected");
4677
- const config = decodeHostConfig(json(snapshot));
4678
- if (!config.http.port || owner.endpoint !== localOrigin(config.http, config.http.port) || owner.policyIdentity !== hash(encoded(config)))
4865
+ async function resolveRecovery(coordination, confirmation, history) {
4866
+ if (typeof confirmation.assertLocalAuthority !== "function" || confirmation.acknowledgeEscapedProcesses !== true || confirmation.processes !== "externally-resolved" || confirmation.credentials !== "independently-resolved" || confirmation.storage !== "independently-resolved")
4867
+ throw new ContractError("forbidden");
4868
+ const expected = structuredClone(confirmation.expected);
4869
+ const authority = () => confirmation.assertLocalAuthority(structuredClone(expected));
4870
+ authority();
4871
+ const release = coordination.prepareRecoveryRelease(expected.coordinationDigest, authority);
4872
+ const current = await inspectRecovery(coordination, history);
4873
+ if (JSON.stringify(current) !== JSON.stringify(expected))
4679
4874
  throw new ContractError("conflict");
4680
- return config;
4681
- }
4682
- function hostEnvironment(ambient) {
4683
- let route;
4684
- const facts = networkSnapshot(ambient);
4685
- try {
4686
- if (facts.NODE_OPTIONS.present)
4687
- throw new Error;
4688
- route = sdkNetworkEnvironment(facts, networkSnapshot(undefined));
4689
- } catch {
4690
- throw new ContractError("unsupported-context");
4691
- }
4692
- const result = Object.create(null);
4693
- for (const key of Object.keys(ambient)) {
4694
- const d = Object.getOwnPropertyDescriptor(ambient, key);
4695
- if (!d || !("value" in d) || typeof d.value !== "string")
4696
- throw new ContractError("invalid");
4697
- if (/^(?:CADENCE_|PI_|ABEL_|TRELLIS_|GIT_)/i.test(key) || networkKeys.some((name) => name === key))
4698
- continue;
4699
- result[key] = d.value;
4700
- }
4701
- if (route.redactionLiterals.length) {
4702
- result.HTTP_PROXY = route.env.HTTP_PROXY;
4703
- result.HTTPS_PROXY = route.env.HTTPS_PROXY;
4704
- result.NODE_USE_ENV_PROXY = "1";
4705
- }
4706
- result.PI_OFFLINE = "1";
4707
- result.NO_COLOR = "1";
4708
- if (!sameNetwork(facts, networkSnapshot(ambient)))
4709
- throw new ContractError("stale");
4710
- return result;
4711
- }
4712
- function selectStart(options, ambient) {
4713
- const workspace = resolveWorkspace(options.workspace), c = new ServiceCoordination(workspace.root, options.stateHome);
4714
- const old = readHostConfig(c);
4715
- const env = Object.getOwnPropertyDescriptor(ambient, "CADENCE_WEB_TOKEN");
4716
- if (env && (!("value" in env) || typeof env.value !== "string"))
4717
- throw new ContractError("invalid");
4718
- let fromFile;
4719
- if (options.config) {
4720
- const path2 = resolve5(options.config), f = new PrivateFiles(dirname7(path2));
4721
- const bytes = f.read(path2);
4722
- if (!bytes)
4723
- throw new ContractError("corrupt-selected");
4724
- fromFile = decodeHostConfig(json(bytes));
4725
- }
4726
- const base = old ?? fromFile ?? decodeHostConfig({
4727
- schema: "cadence-service-host/1",
4728
- cwd: workspace.cwd,
4729
- http: {
4730
- schema: "cadence-service-http/1",
4731
- host: options.host ?? "127.0.0.1",
4732
- port: options.port ?? 17423,
4733
- proxy: null
4734
- },
4735
- agentDir: resolve5(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join8(ambient.HOME ?? ambient.USERPROFILE ?? homedir4(), ".pi", "agent")),
4736
- tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
4737
- tokenFile: options.tokenFile ? resolve5(options.tokenFile) : null
4875
+ if (current.owner !== "absent" || !current.history)
4876
+ throw new ContractError("quarantine");
4877
+ const fact = await history.append({
4878
+ ...confirmation,
4879
+ expected
4738
4880
  });
4739
- const server = options.serverConfig !== undefined ? resolve5(options.serverConfig) : old?.tokenSource === "config" ? old.tokenFile : null;
4740
- let selected = base;
4741
- if (server) {
4742
- if (options.tokenFile !== undefined)
4743
- throw new ContractError("conflict");
4744
- const { port } = readServerConfig(server).config;
4745
- selected = decodeHostConfig({
4746
- ...base,
4747
- http: { ...base.http, port },
4748
- tokenSource: "config",
4749
- tokenFile: server
4750
- });
4751
- }
4752
- if (fromFile && encoded(fromFile) !== encoded(selected) || options.port !== undefined && options.port !== selected.http.port || options.host !== undefined && options.host !== selected.http.host || options.agentDir !== undefined && resolve5(options.agentDir) !== selected.agentDir || options.tokenFile !== undefined && resolve5(options.tokenFile) !== selected.tokenFile || env && selected.tokenSource !== "environment" || selected.tokenSource === "environment" && !env || resolveWorkspace(selected.cwd).root !== workspace.root)
4881
+ authority();
4882
+ if (fact.epoch !== expected.epoch || fact.coordinationDigest !== expected.coordinationDigest || fact.historyDigest !== expected.history?.digest)
4753
4883
  throw new ContractError("conflict");
4884
+ release(() => history.assertCommitted(fact));
4754
4885
  return {
4755
- workspace: workspace.root,
4756
- stateHome: c.stateHome,
4757
- config: selected,
4758
- environmentToken: env?.value ?? null
4759
- };
4760
- }
4761
- function decodeHostInput(value) {
4762
- const o = closed(value, [
4763
- "workspace",
4764
- "stateHome",
4765
- "config",
4766
- "environmentToken"
4767
- ]);
4768
- const config = decodeHostConfig(o.config);
4769
- const environmentToken = o.environmentToken === null ? null : text(o.environmentToken, 1024, true);
4770
- if (config.tokenSource === "environment" !== (environmentToken !== null))
4771
- throw new ContractError("invalid");
4772
- return {
4773
- workspace: absolute(o.workspace),
4774
- stateHome: absolute(o.stateHome),
4775
- config,
4776
- environmentToken
4777
- };
4778
- }
4779
- function portValue(value) {
4780
- if (!/^(0|[1-9][0-9]{0,4})$/.test(value))
4781
- throw new ContractError("invalid");
4782
- return integer(Number(value), 0, 65535);
4783
- }
4784
- var STARTUP_STAGES = [
4785
- "package",
4786
- "pi-agent-directory",
4787
- "pi-auth",
4788
- "pi-models",
4789
- "pi-runtime",
4790
- "service"
4791
- ];
4792
- function decodeStartupReport(value) {
4793
- const o = closed(value, ["status", "error", "stage"]);
4794
- return {
4795
- status: oneOf(o.status, ["refused", "quarantined"]),
4796
- error: oneOf(o.error, ERROR_CODES),
4797
- stage: oneOf(o.stage, STARTUP_STAGES)
4886
+ status: "resolved",
4887
+ epoch: fact.epoch,
4888
+ recoveryId: fact.recoveryId,
4889
+ committedSeq: fact.committedSeq,
4890
+ notice: "operator-acknowledgment-not-certification"
4798
4891
  };
4799
4892
  }
4800
4893
 
4801
- // src/service/gateway-cli.ts
4802
- async function gatewayMain(argv, hostUrl) {
4803
- const operation = argv.shift();
4804
- if (![
4805
- "start",
4806
- "status",
4807
- "diagnose",
4808
- "stop",
4809
- "token-link",
4810
- "token-rotate"
4811
- ].includes(operation ?? ""))
4894
+ // src/history/files.ts
4895
+ import { createHash as createHash5, randomBytes as randomBytes5, randomUUID as randomUUID6 } from "node:crypto";
4896
+ import {
4897
+ closeSync as closeSync5,
4898
+ constants as constants5,
4899
+ fstatSync as fstatSync4,
4900
+ fsyncSync as fsyncSync3,
4901
+ lstatSync as lstatSync6,
4902
+ opendirSync as opendirSync3,
4903
+ openSync as openSync5,
4904
+ statfsSync
4905
+ } from "node:fs";
4906
+ import { open } from "node:fs/promises";
4907
+ import { dirname as dirname8, join as join8 } from "node:path";
4908
+
4909
+ // src/history/admission.ts
4910
+ function decodeRunAdmission(value) {
4911
+ const o = closed(value, ["schema", "runId", "identity", "graph"]);
4912
+ const result = {
4913
+ schema: literal(o.schema, "cadence-run-admission/2"),
4914
+ runId: decodeId(o.runId),
4915
+ identity: decodeRunIdentity(o.identity),
4916
+ graph: nullable(o.graph, decodeGraph)
4917
+ };
4918
+ const { identity, graph } = result;
4919
+ if (identity.kind === "start" !== (graph !== null))
4812
4920
  throw new ContractError("invalid");
4813
- let config = serverConfigPath(homedir5()), stateHome = defaultStateHome(), agentDir, policyFile;
4814
- let foreground = false, reveal = false;
4815
- const seen = new Set;
4816
- while (argv.length) {
4817
- const key = argv.shift();
4818
- if (seen.has(key))
4819
- throw new ContractError("invalid");
4820
- seen.add(key);
4821
- if (key === "--foreground" && operation === "start")
4822
- foreground = true;
4823
- else if (key === "--reveal" && operation === "token-link")
4824
- reveal = true;
4825
- else if (argv[0] && ["--server-config", "--state-home", "--agent-dir", "--config"].includes(key)) {
4826
- const value = resolve6(argv.shift());
4827
- if (key === "--server-config")
4828
- config = value;
4829
- if (key === "--state-home")
4830
- stateHome = value;
4831
- if (key === "--agent-dir")
4832
- agentDir = value;
4833
- if (key === "--config")
4834
- policyFile = value;
4835
- } else
4836
- throw new ContractError("invalid");
4837
- }
4838
- const store = new GatewayStore(config, stateHome);
4839
- let result;
4840
- if (operation === "start") {
4841
- const network = networkSnapshot(process.env), env = hostEnvironment(process.env);
4842
- if (!seen.has("--server-config"))
4843
- ensureServerConfig(homedir5());
4844
- const server = readServerConfig(config).config;
4845
- const selectedFile = store.files.read(store.path("input.json"));
4846
- const selected = selectedFile ? decodeGatewayInput(json(selectedFile)) : undefined;
4847
- let policy = selected?.policy ?? decodeHttpPolicy({
4848
- schema: "cadence-service-http/1",
4849
- host: "127.0.0.1",
4850
- port: server.port,
4851
- proxy: null
4852
- });
4853
- if (policyFile) {
4854
- const file = new PrivateFiles(dirname8(policyFile)).read(policyFile);
4855
- if (!file)
4856
- throw new ContractError("invalid");
4857
- policy = decodeHttpPolicy(json(file));
4858
- }
4859
- const input = decodeGatewayInput({
4860
- kind: "workbench",
4861
- serverConfig: config,
4862
- stateHome: store.stateHome,
4863
- agentDir: agentDir ?? selected?.agentDir ?? resolve6(process.env.PI_CODING_AGENT_DIR ?? join9(homedir5(), ".pi", "agent")),
4864
- policy: { ...policy, port: server.port }
4865
- });
4866
- const status = await serviceStatus(store);
4867
- if (status && typeof status === "object" && "status" in status && status.status === "alive") {
4868
- if (!selected || encoded(selected) !== encoded(input))
4869
- throw new ContractError("conflict");
4870
- result = status;
4871
- } else
4872
- result = await launchHost(input, store, foreground, hostUrl, dirname8(dirname8(fileURLToPath(hostUrl))), env, network);
4873
- if (!foreground && result && typeof result === "object" && "status" in result && result.status === "alive") {
4874
- const reply = closed(await nativeCall(store, "token-link", true), [
4875
- "link"
4876
- ]);
4877
- process.stderr.write(`Cadence workbench: ${text(reply.link, 4096, true)}
4878
- No project or default workspace was created.
4879
- `);
4880
- }
4881
- } else if (operation === "stop")
4882
- result = await stopService(store);
4883
- else if (operation === "status" || operation === "diagnose")
4884
- result = await serviceStatus(store, operation === "diagnose");
4885
- else {
4886
- if (operation === "token-link" && !reveal)
4887
- throw new ContractError("forbidden");
4888
- result = await nativeCall(store, operation, reveal);
4889
- }
4890
- if (result !== null)
4891
- process.stdout.write(`${JSON.stringify(result)}
4892
- `);
4893
- if (result && typeof result === "object" && "status" in result && ["unconfirmed", "unavailable", "quarantined", "refused"].includes(String(result.status)))
4894
- process.exitCode = 1;
4921
+ if (graph && (graph.taskId !== identity.context.task?.id || graph.taskKind !== identity.context.task.kind || graph.taskRevision > identity.context.task.revision || graph.revision !== identity.graph?.revision || hash(canonicalJson(graph)) !== identity.graph.sha256 || canonicalJson(graph.assignments.map((a) => a.id)) !== canonicalJson(identity.assignments.map((a) => a.assignmentId)) || graph.assignments.some((a) => canonicalJson(a.model) !== canonicalJson(identity.context.model))))
4922
+ throw new ContractError("conflict");
4923
+ return result;
4895
4924
  }
4896
4925
 
4897
- // src/service/launchd.ts
4898
- import { rmSync as rmSync2 } from "node:fs";
4899
- import { dirname as dirname9, join as join10 } from "node:path";
4900
- var LABEL = "com.abelxiaoxing.cadence";
4901
- var MARKER2 = `<?xml version="1.0" encoding="UTF-8"?>
4902
- <!-- Generated by @abelxiaoxing/cadence`;
4903
- function plistPath(s) {
4904
- return join10(s.home, "Library", "LaunchAgents", `${LABEL}.plist`);
4926
+ // src/history/read-io.ts
4927
+ import {
4928
+ closeSync as closeSync4,
4929
+ constants as constants4,
4930
+ fstatSync as fstatSync3,
4931
+ lstatSync as lstatSync5,
4932
+ openSync as openSync4,
4933
+ readSync as readSync3
4934
+ } from "node:fs";
4935
+ import { dirname as dirname7 } from "node:path";
4936
+ function bad() {
4937
+ throw new ContractError("corrupt-selected");
4905
4938
  }
4906
- var xml = (value) => checked(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
4907
- function buildPlist(u) {
4908
- const string = (value) => ` <string>${xml(value)}</string>`;
4909
- return [
4910
- '<?xml version="1.0" encoding="UTF-8"?>',
4911
- "<!-- Generated by @abelxiaoxing/cadence; `cadence server install` and `restart` rewrite this file. -->",
4912
- '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
4913
- '<plist version="1.0">',
4914
- "<dict>",
4915
- "\t<key>Label</key>",
4916
- ` <string>${LABEL}</string>`,
4917
- "\t<key>ProgramArguments</key>",
4918
- "\t<array>",
4919
- ...[
4920
- u.node,
4921
- u.cli,
4922
- ...u.workspace === null ? ["workbench", "start", "--foreground"] : ["start", "--foreground", "--workspace", u.workspace],
4923
- "--server-config",
4924
- u.serverConfig
4925
- ].map(string),
4926
- "\t</array>",
4927
- ...u.workspace === null ? ["<!-- Cadence workbench gateway; no project workspace -->"] : [],
4928
- "\t<key>WorkingDirectory</key>",
4929
- ` <string>${xml(u.workspace ?? dirname9(dirname9(u.cli)))}</string>`,
4930
- "\t<key>EnvironmentVariables</key>",
4931
- "\t<dict>",
4932
- ...u.environment.flatMap(([k, v]) => [
4933
- ` <key>${xml(k)}</key>`,
4934
- string(v)
4935
- ]),
4936
- "\t</dict>",
4937
- "\t<key>RunAtLoad</key>",
4938
- "\t<true/>",
4939
- "\t<key>ProcessType</key>",
4940
- "\t<string>Background</string>",
4941
- "\t<key>StandardOutPath</key>",
4942
- ` <string>${xml(u.log)}</string>`,
4943
- "\t<key>StandardErrorPath</key>",
4944
- ` <string>${xml(u.log)}</string>`,
4945
- "</dict>",
4946
- "</plist>",
4947
- ""
4948
- ].join(`
4949
- `);
4939
+ var stamp2 = (s) => [s.dev, s.ino, s.size, s.mode, s.uid, s.nlink, s.ctimeMs, s.mtimeMs].join(":");
4940
+ function selected(files, path2, read, walks) {
4941
+ if (!files.files.directory(dirname7(path2), false, walks))
4942
+ bad();
4943
+ const before = lstatSync5(path2);
4944
+ if (!before.isFile() || before.nlink !== 1 || process.platform !== "win32" && (before.mode & 63 || process.getuid && before.uid !== process.getuid()))
4945
+ bad();
4946
+ const fd = openSync4(path2, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
4947
+ try {
4948
+ if (stamp2(fstatSync3(fd)) !== stamp2(before))
4949
+ bad();
4950
+ const result = read(fd, before.size, stamp2(before));
4951
+ if (stamp2(fstatSync3(fd)) !== stamp2(before) || stamp2(lstatSync5(path2)) !== stamp2(before))
4952
+ bad();
4953
+ return result;
4954
+ } finally {
4955
+ closeSync4(fd);
4956
+ }
4950
4957
  }
4951
- function plistWorkspace(text2) {
4952
- if (text2?.includes("<!-- Cadence workbench gateway; no project workspace -->"))
4953
- return null;
4954
- const m = text2?.match(/^\t<key>WorkingDirectory<\/key>\n\t<string>([^<]*)<\/string>$/m);
4955
- return m?.[1].replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
4958
+ function assertIncarnation(files, path2, expected) {
4959
+ selected(files, path2, (_fd, _size, current) => {
4960
+ if (current !== expected)
4961
+ bad();
4962
+ });
4956
4963
  }
4957
- var domain = (s) => `gui/${s.uid}`;
4958
- var target = (s) => `${domain(s)}/${LABEL}`;
4959
- var loaded = (s) => s.launchctl(["print", target(s)]).status === 0;
4960
- function disabled(s) {
4961
- return new RegExp(`"${LABEL.replaceAll(".", "\\.")}" => (true|disabled)`).test(s.launchctl(["print-disabled", domain(s)]).stdout);
4964
+ function statSize(files, path2, maximum) {
4965
+ return selected(files, path2, (_fd, size) => {
4966
+ if (size > maximum)
4967
+ bad();
4968
+ return size;
4969
+ });
4962
4970
  }
4963
- async function configureLaunchAgent(s, mode, requested) {
4964
- if (s.uid === undefined)
4965
- return { status: "unavailable" };
4966
- const path2 = plistPath(s);
4967
- const previous = readGenerated(path2, MARKER2);
4968
- if (previous === null)
4969
- return { status: "foreign", unit: path2 };
4970
- if (mode === "restart" && previous === undefined)
4971
- return { status: "absent" };
4972
- const sdk = sdkPackages(s.root);
4973
- const kept = plistWorkspace(previous);
4974
- const { workspace, redirectedFrom } = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : kept);
4975
- if (workspace !== null && !sdk)
4976
- return { status: "missing-pi-sdk" };
4977
- const refused2 = await settle(s, kept);
4978
- if (refused2)
4979
- return { ...refused2, unit: path2 };
4980
- if (loaded(s))
4981
- s.launchctl(["bootout", target(s)]);
4982
- write(path2, buildPlist({
4983
- node: s.node,
4984
- cli: join10(s.root, "src", "service-cli.mjs"),
4985
- workspace,
4986
- serverConfig: serverConfigPath(s.home),
4987
- environment: serviceEnvironment(s),
4988
- log: join10(s.home, "Library", "Logs", "cadence.log")
4989
- }));
4990
- const result = {
4991
- unit: path2,
4992
- workspace,
4993
- ...redirectedFrom ? { redirectedFrom } : {}
4971
+
4972
+ // src/history/types.ts
4973
+ import { createHash as createHash4 } from "node:crypto";
4974
+ var RECORD_BYTES = LIMITS.controlBytes + 64 * 1024;
4975
+ var LEGACY_FORMAT = "cadence-history-store/2";
4976
+ var FORMAT = "cadence-history-store/3";
4977
+ function decodeFormat(value) {
4978
+ const o = closed(value, ["schema", "storeId", "workspaceId", "rootIdentity"]);
4979
+ return {
4980
+ schema: oneOf(o.schema, [FORMAT, LEGACY_FORMAT]),
4981
+ storeId: decodeId(o.storeId),
4982
+ workspaceId: decodeId(o.workspaceId),
4983
+ rootIdentity: decodeHash(o.rootIdentity)
4994
4984
  };
4995
- if (mode === "install")
4996
- s.launchctl(["enable", target(s)]);
4997
- else if (disabled(s))
4998
- return { status: "disabled", ...result };
4999
- if (workspace !== null && !regularFile(join10(agentDirectory(s), "auth.json")))
5000
- return { status: "waiting-for-pi-login", ...result };
5001
- const started = s.launchctl(["bootstrap", domain(s), path2]).status === 0;
5002
- return { status: started ? "started" : "failed", ...result };
5003
- }
5004
- async function stopLaunchAgent(s) {
5005
- const path2 = plistPath(s);
5006
- const previous = readGenerated(path2, MARKER2);
5007
- if (previous === null)
5008
- return { status: "foreign", unit: path2 };
5009
- if (previous === undefined)
5010
- return { status: "absent" };
5011
- const workspace = plistWorkspace(previous);
5012
- const refused2 = await settle(s, workspace);
5013
- return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
5014
- }
5015
- async function removeLaunchAgent(s) {
5016
- const path2 = plistPath(s);
5017
- const previous = readGenerated(path2, MARKER2);
5018
- if (previous === null)
5019
- return { status: "foreign", unit: path2 };
5020
- if (previous === undefined)
5021
- return { status: "absent" };
5022
- const refused2 = await settle(s, plistWorkspace(previous));
5023
- if (refused2)
5024
- return { ...refused2, unit: path2 };
5025
- if (s.uid !== undefined && loaded(s))
5026
- s.launchctl(["bootout", target(s)]);
5027
- rmSync2(path2);
5028
- return { status: "removed", unit: path2 };
5029
4985
  }
5030
- function launchAgentStatus(s) {
5031
- const path2 = plistPath(s);
5032
- const previous = readGenerated(path2, MARKER2);
5033
- if (previous === null)
5034
- return { status: "foreign", unit: path2 };
5035
- if (previous === undefined)
5036
- return { status: "absent" };
5037
- const printed = s.uid === undefined ? undefined : s.launchctl(["print", target(s)]);
4986
+ function decodeArtifact(value) {
4987
+ const o = closed(value, ["runId", "artifactId", "part", "bytes", "sha256"]);
5038
4988
  return {
5039
- status: printed?.status !== 0 ? "unloaded" : /^\s*state = running$/m.test(printed.stdout) ? "active" : "inactive",
5040
- enabled: s.uid !== undefined && disabled(s) ? "disabled" : "enabled",
5041
- unit: path2,
5042
- workspace: plistWorkspace(previous)
4989
+ runId: decodeId(o.runId),
4990
+ artifactId: decodeId(o.artifactId),
4991
+ part: integer(o.part),
4992
+ bytes: integer(o.bytes, 1, LIMITS.artifactPartBytes),
4993
+ sha256: decodeHash(o.sha256)
5043
4994
  };
5044
4995
  }
5045
-
5046
- // src/service/login-run.ts
5047
- import { rmSync as rmSync3 } from "node:fs";
5048
- import { dirname as dirname10, isAbsolute as isAbsolute7, join as join11 } from "node:path";
5049
- var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
5050
- var APPROVED_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run";
5051
- var RUN_VALUE = "Cadence";
5052
- var MARKER3 = "' Generated by @abelxiaoxing/cadence";
5053
- function launcherPath(s) {
5054
- const local = s.env.LOCALAPPDATA;
5055
- return join11(local && isAbsolute7(local) ? local : join11(s.home, "AppData", "Local"), "cadence-server", "cadence.vbs");
5056
- }
5057
- var literal2 = (value) => `"${checked(value).replaceAll('"', '""')}"`;
5058
- function argument2(value) {
5059
- if (checked(value).includes('"'))
5060
- throw new ContractError("invalid");
5061
- return `"${value}"`;
4996
+ function decodeRetainedDetail(value) {
4997
+ const o = closed(value, [
4998
+ "detailId",
4999
+ "origin",
5000
+ "owner",
5001
+ "descriptor",
5002
+ "content",
5003
+ "coverage"
5004
+ ]);
5005
+ const origin = closed(o.origin, ["kind", "id"]);
5006
+ const r = {
5007
+ detailId: decodeId(o.detailId),
5008
+ origin: {
5009
+ kind: oneOf(origin.kind, ["worker", "owner"]),
5010
+ id: decodeId(origin.id)
5011
+ },
5012
+ owner: decodeRecordOwner(o.owner),
5013
+ descriptor: nullable(o.descriptor, decodeDetailDescriptor),
5014
+ content: decodeContentRef(o.content),
5015
+ coverage: decodeCoverage(o.coverage)
5016
+ };
5017
+ if (r.origin.kind === "worker" !== (r.descriptor !== null) || r.descriptor && r.content.field !== r.descriptor.field)
5018
+ invalid();
5019
+ return r;
5062
5020
  }
5063
- function buildLauncher(u) {
5064
- const command = `${argument2(u.node)} ${argument2(u.cli)} ${u.workspace === null ? "workbench start" : `start --workspace ${argument2(u.workspace)}`} --server-config ${argument2(u.serverConfig)}`;
5065
- return [
5066
- `${MARKER3}; \`cadence server install\` and \`restart\` rewrite this file.`,
5067
- `' Started at login by ${RUN_KEY}\\${RUN_VALUE}; window style 0 hides the console.`,
5068
- 'Set shell = CreateObject("WScript.Shell")',
5069
- ...u.environment.length ? ['Set env = shell.Environment("Process")'] : [],
5070
- ...u.environment.map(([k, v]) => `env(${literal2(k)}) = ${literal2(v)}`),
5071
- ...u.workspace === null ? ["' Cadence workbench gateway; no project workspace"] : [],
5072
- `shell.CurrentDirectory = ${literal2(u.workspace ?? dirname10(dirname10(u.cli)))}`,
5073
- `shell.Run ${literal2(command)}, 0, False`,
5074
- ""
5075
- ].join(`\r
5076
- `);
5021
+ function decodeSavedEvent(value, owner) {
5022
+ const kind = closedTag(value);
5023
+ if (kind === "capture") {
5024
+ const o2 = closed(value, ["kind", "id", "value"]), v = decodeRecordingEvent(o2.value);
5025
+ if (owner || v.event.kind === "stored-chunk" || v.event.kind === "stored-end")
5026
+ invalid();
5027
+ return { kind, id: decodeId(o2.id), value: v };
5028
+ }
5029
+ if (kind === "owner") {
5030
+ const o2 = closed(value, ["kind", "id", "value"]);
5031
+ if (!owner)
5032
+ invalid();
5033
+ const v = decodeOwnerRecordingInput({
5034
+ link: owner,
5035
+ observedMs: 0,
5036
+ events: [o2.value]
5037
+ }).events[0];
5038
+ if (v.kind === "content-chunk" || v.kind === "content-end")
5039
+ invalid();
5040
+ return { kind, id: decodeId(o2.id), value: v };
5041
+ }
5042
+ if (kind === "chunk") {
5043
+ const o2 = closed(value, [
5044
+ "kind",
5045
+ "detailId",
5046
+ "source",
5047
+ "artifact",
5048
+ "offset",
5049
+ "start",
5050
+ "length"
5051
+ ]);
5052
+ const source2 = nullable(o2.source, decodeSourceRange);
5053
+ if (source2 === null !== (owner !== null))
5054
+ invalid();
5055
+ const artifact = decodeArtifact(o2.artifact), offset = integer(o2.offset, 0, LIMITS.detailBytes), start = integer(o2.start, 0, LIMITS.artifactPartBytes), length = integer(o2.length, 1, LIMITS.chunkBytes);
5056
+ if (start + length > artifact.bytes || offset + length > LIMITS.detailBytes)
5057
+ invalid();
5058
+ return {
5059
+ kind,
5060
+ detailId: decodeId(o2.detailId),
5061
+ source: source2,
5062
+ artifact,
5063
+ offset,
5064
+ start,
5065
+ length
5066
+ };
5067
+ }
5068
+ const o = closed(value, ["kind", "detail", "source"]);
5069
+ literal(kind, "end");
5070
+ const detail = decodeRetainedDetail(o.detail), source = nullable(o.source, decodeSourceRange);
5071
+ if (detail.origin.kind === "owner" !== (owner !== null) || source === null !== (owner !== null))
5072
+ invalid();
5073
+ return { kind: "end", detail, source };
5077
5074
  }
5078
- var utf16 = (text2) => Buffer.concat([Buffer.from([255, 254]), Buffer.from(text2, "utf16le")]);
5079
- function launcherWorkspace(text2) {
5080
- if (text2?.includes("' Cadence workbench gateway; no project workspace"))
5081
- return null;
5082
- return text2?.match(/^shell\.CurrentDirectory = "((?:[^"]|"")*)"\r?$/m)?.[1].replaceAll('""', '"');
5075
+ function closedTag(value) {
5076
+ if (!value || typeof value !== "object" || !("kind" in value) || typeof value.kind !== "string")
5077
+ invalid();
5078
+ return value.kind;
5083
5079
  }
5084
- var runEntry = (path2, s) => `"${wscript(s.env)}" "${path2}"`;
5085
- var present = (s, key) => s.reg(["query", key, "/v", RUN_VALUE]).status === 0;
5086
- function approvedOff(s) {
5087
- const r = s.reg(["query", APPROVED_KEY, "/v", RUN_VALUE]);
5088
- const first = r.status === 0 && /REG_BINARY\s+([0-9A-F]{2})/i.exec(r.stdout);
5089
- return !!first && (Number.parseInt(first[1], 16) & 1) === 1;
5080
+ function decodeAdmissionIntent(value) {
5081
+ const o = closed(value, [
5082
+ "commandId",
5083
+ "operationId",
5084
+ "runId",
5085
+ "identity",
5086
+ "createdTaskId"
5087
+ ]);
5088
+ return {
5089
+ commandId: decodeId(o.commandId),
5090
+ operationId: decodeId(o.operationId),
5091
+ runId: nullable(o.runId, decodeId),
5092
+ identity: decodeCommandIdentity(o.identity),
5093
+ createdTaskId: nullable(o.createdTaskId, decodeId)
5094
+ };
5090
5095
  }
5091
- async function configureLoginRun(s, mode, requested) {
5092
- const path2 = launcherPath(s);
5093
- const previous = readGenerated(path2, MARKER3);
5094
- if (previous === null)
5095
- return { status: "foreign", unit: path2 };
5096
- if (previous === undefined && present(s, RUN_KEY))
5097
- return { status: "foreign", unit: `${RUN_KEY}\\${RUN_VALUE}` };
5098
- if (mode === "restart" && previous === undefined)
5099
- return { status: "absent" };
5100
- const sdk = sdkPackages(s.root);
5101
- const kept = launcherWorkspace(previous);
5102
- const { workspace, redirectedFrom } = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : kept);
5103
- if (workspace !== null && !sdk)
5104
- return { status: "missing-pi-sdk" };
5105
- const refused2 = await settle(s, kept);
5106
- if (refused2)
5107
- return { ...refused2, unit: path2 };
5108
- const environment = s.env.PI_CODING_AGENT_DIR ? [["PI_CODING_AGENT_DIR", agentDirectory(s)]] : [];
5109
- write(path2, utf16(buildLauncher({
5110
- node: s.node,
5111
- cli: join11(s.root, "src", "service-cli.mjs"),
5112
- workspace,
5113
- serverConfig: serverConfigPath(s.home),
5114
- environment
5115
- })));
5116
- const result = {
5117
- unit: path2,
5118
- workspace,
5119
- ...redirectedFrom ? { redirectedFrom } : {}
5096
+ function decodeAuthIntent(value) {
5097
+ const o = closed(value, [
5098
+ "operationId",
5099
+ "epoch",
5100
+ "runId",
5101
+ "childRunId",
5102
+ "sourceSelectionId",
5103
+ "validityMs"
5104
+ ]);
5105
+ return {
5106
+ operationId: decodeId(o.operationId),
5107
+ epoch: decodeId(o.epoch),
5108
+ runId: decodeId(o.runId),
5109
+ childRunId: decodeId(o.childRunId),
5110
+ sourceSelectionId: decodeId(o.sourceSelectionId),
5111
+ validityMs: integer(o.validityMs, 1)
5120
5112
  };
5121
- if (mode === "install") {
5122
- const entry = ["/v", RUN_VALUE, "/t", "REG_SZ", "/d", runEntry(path2, s)];
5123
- if (s.reg(["add", RUN_KEY, ...entry, "/f"]).status !== 0)
5124
- return { status: "failed", ...result };
5125
- s.reg(["delete", APPROVED_KEY, "/v", RUN_VALUE, "/f"]);
5126
- }
5127
- if (workspace !== null && !regularFile(join11(agentDirectory(s), "auth.json")))
5128
- return { status: "waiting-for-pi-login", ...result };
5129
- return { status: s.launch(path2) ? "started" : "failed", ...result };
5130
5113
  }
5131
- async function stopLoginRun(s) {
5132
- const path2 = launcherPath(s);
5133
- const previous = readGenerated(path2, MARKER3);
5134
- if (previous === null)
5135
- return { status: "foreign", unit: path2 };
5136
- if (previous === undefined)
5137
- return { status: "absent" };
5138
- const workspace = launcherWorkspace(previous);
5139
- const refused2 = await settle(s, workspace);
5140
- return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
5114
+ function decodeAuthSettlement(value) {
5115
+ const o = closed(value, ["operationId", "outcome", "admissionClosed"]);
5116
+ return {
5117
+ operationId: decodeId(o.operationId),
5118
+ outcome: oneOf(o.outcome, ["resolved", "refused", "failed"]),
5119
+ admissionClosed: bool(o.admissionClosed)
5120
+ };
5141
5121
  }
5142
- async function removeLoginRun(s) {
5143
- const path2 = launcherPath(s);
5144
- const previous = readGenerated(path2, MARKER3);
5145
- if (previous === null)
5146
- return { status: "foreign", unit: path2 };
5147
- if (previous === undefined)
5148
- return { status: "absent" };
5149
- const refused2 = await settle(s, launcherWorkspace(previous));
5150
- if (refused2)
5151
- return { ...refused2, unit: path2 };
5152
- for (const key of [RUN_KEY, APPROVED_KEY])
5153
- if (present(s, key))
5154
- s.reg(["delete", key, "/v", RUN_VALUE, "/f"]);
5155
- rmSync3(path2);
5156
- return { status: "removed", unit: path2 };
5157
- }
5158
- async function loginRunStatus(s) {
5159
- const path2 = launcherPath(s);
5160
- const previous = readGenerated(path2, MARKER3);
5161
- if (previous === null)
5162
- return { status: "foreign", unit: path2 };
5163
- if (previous === undefined)
5164
- return { status: "absent" };
5165
- const workspace = launcherWorkspace(previous);
5166
- const owner = workspace !== undefined ? await s.ownerStatus(workspace, serverConfigPath(s.home)) : "unavailable";
5122
+ function decodeRecoveryBridge(v) {
5123
+ const o = closed(v, [
5124
+ "kind",
5125
+ "recoveryId",
5126
+ "epoch",
5127
+ "coordinationDigest",
5128
+ "historyDigest",
5129
+ "previous",
5130
+ "acknowledgments"
5131
+ ]);
5132
+ const p = closed(o.previous, [
5133
+ "segment",
5134
+ "bytes",
5135
+ "sha256",
5136
+ "committedBytes",
5137
+ "committedSeq",
5138
+ "highSeq"
5139
+ ]);
5140
+ const a = closed(o.acknowledgments, ["processes", "credentials", "storage"]);
5141
+ const previous = {
5142
+ segment: integer(p.segment),
5143
+ bytes: integer(p.bytes, 0, LIMITS.segmentBytes),
5144
+ sha256: decodeHash(p.sha256),
5145
+ committedBytes: integer(p.committedBytes, 0, LIMITS.segmentBytes),
5146
+ committedSeq: decodeSequence(p.committedSeq),
5147
+ highSeq: decodeSequence(p.highSeq)
5148
+ };
5149
+ if (previous.committedBytes > previous.bytes || BigInt(previous.highSeq) < BigInt(previous.committedSeq))
5150
+ invalid();
5151
+ if (a.processes !== true || a.credentials !== true || a.storage !== true)
5152
+ invalid();
5167
5153
  return {
5168
- status: owner === "alive" ? "active" : "inactive",
5169
- enabled: present(s, RUN_KEY) && !approvedOff(s) ? "enabled" : "disabled",
5170
- unit: path2,
5171
- workspace
5154
+ kind: literal(o.kind, "recovery"),
5155
+ recoveryId: decodeId(o.recoveryId),
5156
+ epoch: decodeId(o.epoch),
5157
+ coordinationDigest: decodeHash(o.coordinationDigest),
5158
+ historyDigest: decodeHash(o.historyDigest),
5159
+ previous,
5160
+ acknowledgments: { processes: true, credentials: true, storage: true }
5172
5161
  };
5173
5162
  }
5174
-
5175
- // src/service/recovery-cli.ts
5176
- import { isatty } from "node:tty";
5177
-
5178
- // src/history/recovery.ts
5179
- import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
5180
- import { lstatSync as lstatSync7, opendirSync as opendirSync4 } from "node:fs";
5181
- import { join as join13, relative as relative4 } from "node:path";
5182
-
5183
- // src/service/recovery.ts
5184
- async function inspectRecovery(coordination, history) {
5185
- const selected = coordination.recoverySnapshot();
5186
- const facts = history ? await history.inspect(selected.registration.epoch) : null;
5187
- if (facts)
5188
- decodeHash(facts.digest);
5189
- if (coordination.recoverySnapshot().digest !== selected.digest)
5190
- throw new ContractError("conflict");
5163
+ function validBatchSequence(rows, previous) {
5164
+ const bridge = rows[0]?.value;
5165
+ const base = bridge?.kind === "recovery" ? bridge.previous.highSeq : previous;
5166
+ if (bridge?.kind === "recovery" && (rows.length !== 1 || bridge.previous.committedSeq !== previous))
5167
+ return false;
5168
+ return rows.every((row, i) => row.seq === String(BigInt(base) + BigInt(i) + 1n) && (i === 0 || row.value.kind !== "recovery"));
5169
+ }
5170
+ function decodeResourcePolicy(v) {
5171
+ const o = closed(v, [
5172
+ "generation",
5173
+ "quotaBytes",
5174
+ "episodeReservationBytes",
5175
+ "freeSpaceFloorBytes",
5176
+ "episodeControlBytes"
5177
+ ]);
5191
5178
  return {
5192
- epoch: selected.registration.epoch,
5193
- coordinationDigest: selected.digest,
5194
- history: facts,
5195
- owner: recoveryOwnerStatus(selected.registration.pid, selected.registration.host),
5196
- resolution: "local-interactive-only"
5179
+ generation: integer(o.generation, 1),
5180
+ quotaBytes: integer(o.quotaBytes, LIMITS.episodeControlBytes),
5181
+ episodeReservationBytes: integer(o.episodeReservationBytes, LIMITS.episodeControlBytes),
5182
+ freeSpaceFloorBytes: literal(o.freeSpaceFloorBytes, LIMITS.freeSpaceFloorBytes),
5183
+ episodeControlBytes: literal(o.episodeControlBytes, LIMITS.episodeControlBytes)
5197
5184
  };
5198
5185
  }
5199
- async function resolveRecovery(coordination, confirmation, history) {
5200
- if (typeof confirmation.assertLocalAuthority !== "function" || confirmation.acknowledgeEscapedProcesses !== true || confirmation.processes !== "externally-resolved" || confirmation.credentials !== "independently-resolved" || confirmation.storage !== "independently-resolved")
5201
- throw new ContractError("forbidden");
5202
- const expected = structuredClone(confirmation.expected);
5203
- const authority = () => confirmation.assertLocalAuthority(structuredClone(expected));
5204
- authority();
5205
- const release = coordination.prepareRecoveryRelease(expected.coordinationDigest, authority);
5206
- const current = await inspectRecovery(coordination, history);
5207
- if (JSON.stringify(current) !== JSON.stringify(expected))
5208
- throw new ContractError("conflict");
5209
- if (current.owner !== "absent" || !current.history)
5210
- throw new ContractError("quarantine");
5211
- const fact = await history.append({
5212
- ...confirmation,
5213
- expected
5214
- });
5215
- authority();
5216
- if (fact.epoch !== expected.epoch || fact.coordinationDigest !== expected.coordinationDigest || fact.historyDigest !== expected.history?.digest)
5217
- throw new ContractError("conflict");
5218
- release(() => history.assertCommitted(fact));
5186
+ function decodeJournalValue(value) {
5187
+ const kind = closedTag(value);
5188
+ switch (kind) {
5189
+ case "recovery":
5190
+ return decodeRecoveryBridge(value);
5191
+ case "owner-epoch": {
5192
+ const o = closed(value, ["kind", "epoch"]);
5193
+ return { kind, epoch: decodeId(o.epoch) };
5194
+ }
5195
+ case "admission-compact": {
5196
+ const hasPolicy = Object.hasOwn(value, "policy");
5197
+ const o = closed(value, [
5198
+ "kind",
5199
+ "intent",
5200
+ "receipt",
5201
+ ...hasPolicy ? ["policy"] : []
5202
+ ]);
5203
+ const intent = decodeAdmissionIntent(o.intent);
5204
+ return decodeJournalValue({
5205
+ kind: "admission",
5206
+ intent,
5207
+ record: { identity: intent.identity, receipt: o.receipt },
5208
+ ...hasPolicy ? { policy: o.policy } : {}
5209
+ });
5210
+ }
5211
+ case "admission": {
5212
+ const hasPolicy = typeof value === "object" && value !== null && Object.hasOwn(value, "policy");
5213
+ const o = closed(value, [
5214
+ "kind",
5215
+ "intent",
5216
+ "record",
5217
+ ...hasPolicy ? ["policy"] : []
5218
+ ]), intent = decodeAdmissionIntent(o.intent), record = decodeStoredCommand(o.record);
5219
+ if (record.receipt.commandId !== intent.commandId || record.receipt.operationId !== intent.operationId || record.receipt.runId !== intent.runId || canonicalJson(record.identity) !== canonicalJson(intent.identity) || (record.receipt.created?.taskId ?? null) !== intent.createdTaskId)
5220
+ invalid();
5221
+ const policy = hasPolicy ? decodeResourcePolicy(o.policy) : undefined;
5222
+ if (policy && intent.identity.kind === "action" && policy.generation !== intent.identity.expected.configRev)
5223
+ invalid();
5224
+ return { kind, intent, record, ...policy ? { policy } : {} };
5225
+ }
5226
+ case "auth-preparation": {
5227
+ const o = closed(value, ["kind", "intent"]);
5228
+ return { kind, intent: decodeAuthIntent(o.intent) };
5229
+ }
5230
+ case "auth-settlement": {
5231
+ const o = closed(value, ["kind", "result"]);
5232
+ return { kind, result: decodeAuthSettlement(o.result) };
5233
+ }
5234
+ case "launch": {
5235
+ const o = closed(value, ["kind", "link", "operationId"]);
5236
+ return {
5237
+ kind,
5238
+ link: decodeRecordingLink(o.link),
5239
+ operationId: decodeId(o.operationId)
5240
+ };
5241
+ }
5242
+ case "settlement": {
5243
+ const o = closed(value, ["kind", "link", "completion"]), link = decodeRecordingLink(o.link), completion = decodeWorkerCompletion(o.completion);
5244
+ if (link.childRunId !== completion.childRunId)
5245
+ invalid();
5246
+ return { kind, link, completion };
5247
+ }
5248
+ case "capture": {
5249
+ const o = closed(value, ["kind", "link", "source", "events"]);
5250
+ return {
5251
+ kind,
5252
+ link: decodeRecordingLink(o.link),
5253
+ source: decodeSourceRange(o.source),
5254
+ events: list(o.events, LIMITS.ownerQueueFrames, (v) => decodeSavedEvent(v, null))
5255
+ };
5256
+ }
5257
+ case "owner": {
5258
+ const o = closed(value, ["kind", "link", "observedMs", "events"]), link = decodeOwnerLink(o.link);
5259
+ return {
5260
+ kind,
5261
+ link,
5262
+ observedMs: integer(o.observedMs),
5263
+ events: list(o.events, LIMITS.ownerQueueFrames, (v) => decodeSavedEvent(v, link))
5264
+ };
5265
+ }
5266
+ default:
5267
+ return invalid();
5268
+ }
5269
+ }
5270
+ function decodeJournalLine(value) {
5271
+ if (closedTag(value) === "record") {
5272
+ const o2 = closed(value, ["kind", "seq", "id", "value"]);
5273
+ return {
5274
+ kind: "record",
5275
+ seq: decodeSequence(o2.seq),
5276
+ id: decodeId(o2.id),
5277
+ value: decodeJournalValue(o2.value)
5278
+ };
5279
+ }
5280
+ const indexed = typeof value === "object" && value !== null && Object.hasOwn(value, "navigationRoot");
5281
+ const o = closed(value, [
5282
+ ...indexed ? ["navigationRoot"] : [],
5283
+ "kind",
5284
+ "previous",
5285
+ "first",
5286
+ "last",
5287
+ "count",
5288
+ "sha256",
5289
+ "artifacts"
5290
+ ]);
5291
+ literal(o.kind, "commit");
5219
5292
  return {
5220
- status: "resolved",
5221
- epoch: fact.epoch,
5222
- recoveryId: fact.recoveryId,
5223
- committedSeq: fact.committedSeq,
5224
- notice: "operator-acknowledgment-not-certification"
5293
+ kind: "commit",
5294
+ ...indexed ? { navigationRoot: nullable(o.navigationRoot, decodeHash) } : {},
5295
+ previous: decodeSequence(o.previous),
5296
+ first: decodeSequence(o.first),
5297
+ last: decodeSequence(o.last),
5298
+ count: integer(o.count, 1, LIMITS.segmentRecords),
5299
+ sha256: decodeHash(o.sha256),
5300
+ artifacts: list(o.artifacts, LIMITS.ownerQueueFrames, decodeArtifact)
5225
5301
  };
5226
5302
  }
5227
-
5228
- // src/history/files.ts
5229
- import { createHash as createHash5, randomBytes as randomBytes5, randomUUID as randomUUID6 } from "node:crypto";
5230
- import {
5231
- closeSync as closeSync5,
5232
- constants as constants5,
5233
- fstatSync as fstatSync4,
5234
- fsyncSync as fsyncSync3,
5235
- lstatSync as lstatSync6,
5236
- opendirSync as opendirSync3,
5237
- openSync as openSync5,
5238
- statfsSync
5239
- } from "node:fs";
5240
- import { open } from "node:fs/promises";
5241
- import { dirname as dirname12, join as join12 } from "node:path";
5242
-
5243
- // src/history/admission.ts
5244
- function decodeRunAdmission(value) {
5245
- const o = closed(value, ["schema", "runId", "identity", "graph"]);
5246
- const result = {
5247
- schema: literal(o.schema, "cadence-run-admission/2"),
5248
- runId: decodeId(o.runId),
5249
- identity: decodeRunIdentity(o.identity),
5250
- graph: nullable(o.graph, decodeGraph)
5303
+ var COMPLETE = {
5304
+ observed: "complete",
5305
+ source: "complete",
5306
+ redacted: false,
5307
+ omissions: []
5308
+ };
5309
+ function captureCoverage(event) {
5310
+ if (event.kind === "capture-status")
5311
+ return event.coverage;
5312
+ if (event.kind === "omitted-block")
5313
+ return { ...COMPLETE, source: "limited", omissions: [event.block.reason] };
5314
+ return null;
5315
+ }
5316
+ function combineCoverage(a, b) {
5317
+ return {
5318
+ observed: a.observed === "complete" && b.observed === "complete" ? "complete" : "incomplete",
5319
+ source: a.source === "unavailable" || b.source === "unavailable" ? "unavailable" : a.source === "limited" || b.source === "limited" ? "limited" : "complete",
5320
+ redacted: a.redacted || b.redacted,
5321
+ omissions: [...new Set([...a.omissions, ...b.omissions])]
5251
5322
  };
5252
- const { identity, graph } = result;
5253
- if (identity.kind === "start" !== (graph !== null))
5254
- throw new ContractError("invalid");
5255
- if (graph && (graph.taskId !== identity.context.task?.id || graph.taskKind !== identity.context.task.kind || graph.taskRevision > identity.context.task.revision || graph.revision !== identity.graph?.revision || hash(canonicalJson(graph)) !== identity.graph.sha256 || canonicalJson(graph.assignments.map((a) => a.id)) !== canonicalJson(identity.assignments.map((a) => a.assignmentId)) || graph.assignments.some((a) => canonicalJson(a.model) !== canonicalJson(identity.context.model))))
5256
- throw new ContractError("conflict");
5257
- return result;
5258
5323
  }
5259
-
5260
- // src/history/read-io.ts
5261
- import {
5262
- closeSync as closeSync4,
5263
- constants as constants4,
5264
- fstatSync as fstatSync3,
5265
- lstatSync as lstatSync5,
5266
- openSync as openSync4,
5267
- readSync as readSync3
5268
- } from "node:fs";
5269
- import { dirname as dirname11 } from "node:path";
5270
- function bad() {
5271
- throw new ContractError("corrupt-selected");
5324
+ function rowArtifacts(row) {
5325
+ return row.value.kind === "capture" || row.value.kind === "owner" ? row.value.events.flatMap((e) => e.kind === "chunk" ? [e.artifact] : []) : [];
5272
5326
  }
5273
- var stamp2 = (s) => [s.dev, s.ino, s.size, s.mode, s.uid, s.nlink, s.ctimeMs, s.mtimeMs].join(":");
5274
- function selected(files, path2, read, walks) {
5275
- if (!files.files.directory(dirname11(path2), false, walks))
5276
- bad();
5277
- const before = lstatSync5(path2);
5278
- if (!before.isFile() || before.nlink !== 1 || process.platform !== "win32" && (before.mode & 63 || process.getuid && before.uid !== process.getuid()))
5279
- bad();
5280
- const fd = openSync4(path2, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
5281
- try {
5282
- if (stamp2(fstatSync3(fd)) !== stamp2(before))
5283
- bad();
5284
- const result = read(fd, before.size, stamp2(before));
5285
- if (stamp2(fstatSync3(fd)) !== stamp2(before) || stamp2(lstatSync5(path2)) !== stamp2(before))
5286
- bad();
5287
- return result;
5288
- } finally {
5289
- closeSync4(fd);
5327
+ function batchArtifacts(rows) {
5328
+ const unique = new Map;
5329
+ for (const ref of rows.flatMap(rowArtifacts)) {
5330
+ const key = `${ref.runId}/${ref.artifactId}/${ref.part}`, previous = unique.get(key);
5331
+ if (previous && canonicalJson(previous) !== canonicalJson(ref))
5332
+ invalid();
5333
+ unique.set(key, ref);
5290
5334
  }
5335
+ return [...unique.values()];
5291
5336
  }
5292
- function assertIncarnation(files, path2, expected) {
5293
- selected(files, path2, (_fd, _size, current) => {
5294
- if (current !== expected)
5295
- bad();
5296
- });
5297
- }
5298
- function statSize(files, path2, maximum) {
5299
- return selected(files, path2, (_fd, size) => {
5300
- if (size > maximum)
5301
- bad();
5302
- return size;
5303
- });
5337
+ var decodedLines = new Map;
5338
+ var decodedBytes = 0;
5339
+ var lineKey = (text2) => createHash4("sha256").update(text2).digest("hex");
5340
+ function remember(text2, value) {
5341
+ const key = lineKey(text2);
5342
+ if (decodedLines.has(key))
5343
+ return;
5344
+ const bytes = Buffer.byteLength(text2);
5345
+ while (decodedLines.size && (decodedLines.size >= 2 * LIMITS.segmentRecords || decodedBytes + bytes > 2 * LIMITS.segmentBytes)) {
5346
+ const first = decodedLines.keys().next().value;
5347
+ if (!first)
5348
+ break;
5349
+ decodedBytes -= decodedLines.get(first)?.bytes ?? 0;
5350
+ decodedLines.delete(first);
5351
+ }
5352
+ if (bytes <= 2 * LIMITS.segmentBytes) {
5353
+ decodedLines.set(key, { bytes, value });
5354
+ decodedBytes += bytes;
5355
+ }
5304
5356
  }
5305
-
5306
- // src/history/types.ts
5307
- import { createHash as createHash4 } from "node:crypto";
5308
- var RECORD_BYTES = LIMITS.controlBytes + 64 * 1024;
5309
- var LEGACY_FORMAT = "cadence-history-store/2";
5310
- var FORMAT = "cadence-history-store/3";
5311
- function decodeFormat(value) {
5312
- const o = closed(value, ["schema", "storeId", "workspaceId", "rootIdentity"]);
5357
+ function compactAdmission(value) {
5358
+ if (value.kind !== "record" || value.value.kind !== "admission")
5359
+ return value;
5360
+ const { intent, record, policy } = value.value;
5313
5361
  return {
5314
- schema: oneOf(o.schema, [FORMAT, LEGACY_FORMAT]),
5315
- storeId: decodeId(o.storeId),
5316
- workspaceId: decodeId(o.workspaceId),
5317
- rootIdentity: decodeHash(o.rootIdentity)
5362
+ ...value,
5363
+ value: {
5364
+ kind: "admission-compact",
5365
+ intent,
5366
+ receipt: record.receipt,
5367
+ ...policy ? { policy } : {}
5368
+ }
5318
5369
  };
5319
5370
  }
5320
- function decodeArtifact(value) {
5321
- const o = closed(value, ["runId", "artifactId", "part", "bytes", "sha256"]);
5322
- return {
5323
- runId: decodeId(o.runId),
5324
- artifactId: decodeId(o.artifactId),
5325
- part: integer(o.part),
5326
- bytes: integer(o.bytes, 1, LIMITS.artifactPartBytes),
5327
- sha256: decodeHash(o.sha256)
5328
- };
5371
+ function encodeJournalText(input) {
5372
+ const value = decodeJournalLine(input), text2 = canonicalJson(compactAdmission(value), RECORD_BYTES);
5373
+ remember(text2, value);
5374
+ return `${text2}
5375
+ `;
5329
5376
  }
5330
- function decodeRetainedDetail(value) {
5331
- const o = closed(value, [
5332
- "detailId",
5333
- "origin",
5334
- "owner",
5335
- "descriptor",
5336
- "content",
5337
- "coverage"
5338
- ]);
5339
- const origin = closed(o.origin, ["kind", "id"]);
5340
- const r = {
5341
- detailId: decodeId(o.detailId),
5342
- origin: {
5343
- kind: oneOf(origin.kind, ["worker", "owner"]),
5344
- id: decodeId(origin.id)
5345
- },
5346
- owner: decodeRecordOwner(o.owner),
5347
- descriptor: nullable(o.descriptor, decodeDetailDescriptor),
5348
- content: decodeContentRef(o.content),
5349
- coverage: decodeCoverage(o.coverage)
5350
- };
5351
- if (r.origin.kind === "worker" !== (r.descriptor !== null) || r.descriptor && r.content.field !== r.descriptor.field)
5377
+ function decodeJournalText(line) {
5378
+ const text2 = line.endsWith(`
5379
+ `) ? line.slice(0, -1) : line;
5380
+ const epoch = /^\{"id":"([0-9a-f-]{36})","kind":"record","seq":"([0-9]+)","value":\{"epoch":"([0-9a-f-]{36})","kind":"owner-epoch"\}\}$/.exec(text2);
5381
+ if (epoch)
5382
+ return {
5383
+ kind: "record",
5384
+ id: decodeId(epoch[1]),
5385
+ seq: decodeSequence(epoch[2]),
5386
+ value: { kind: "owner-epoch", epoch: decodeId(epoch[3]) }
5387
+ };
5388
+ const key = lineKey(text2), cached = decodedLines.get(key);
5389
+ if (cached)
5390
+ return structuredClone(cached.value);
5391
+ const raw = JSON.parse(text2);
5392
+ const value = decodeJournalLine(raw);
5393
+ const compact = raw?.kind === "record" && raw.value?.kind === "admission-compact";
5394
+ if (canonicalJson(compact ? compactAdmission(value) : value, RECORD_BYTES) !== text2)
5352
5395
  invalid();
5353
- return r;
5396
+ remember(text2, value);
5397
+ return structuredClone(value);
5354
5398
  }
5355
- function decodeSavedEvent(value, owner) {
5356
- const kind = closedTag(value);
5357
- if (kind === "capture") {
5358
- const o2 = closed(value, ["kind", "id", "value"]), v = decodeRecordingEvent(o2.value);
5359
- if (owner || v.event.kind === "stored-chunk" || v.event.kind === "stored-end")
5360
- invalid();
5361
- return { kind, id: decodeId(o2.id), value: v };
5399
+
5400
+ // src/history/files.ts
5401
+ class HistoryReadBudget {
5402
+ clock;
5403
+ started = performance.now();
5404
+ bytes = 0;
5405
+ walks = new DirectoryWalks;
5406
+ constructor(clock = true) {
5407
+ this.clock = clock;
5362
5408
  }
5363
- if (kind === "owner") {
5364
- const o2 = closed(value, ["kind", "id", "value"]);
5365
- if (!owner)
5366
- invalid();
5367
- const v = decodeOwnerRecordingInput({
5368
- link: owner,
5369
- observedMs: 0,
5370
- events: [o2.value]
5371
- }).events[0];
5372
- if (v.kind === "content-chunk" || v.kind === "content-end")
5373
- invalid();
5374
- return { kind, id: decodeId(o2.id), value: v };
5409
+ static authority() {
5410
+ return new HistoryReadBudget(false);
5375
5411
  }
5376
- if (kind === "chunk") {
5377
- const o2 = closed(value, [
5378
- "kind",
5379
- "detailId",
5380
- "source",
5381
- "artifact",
5382
- "offset",
5383
- "start",
5384
- "length"
5385
- ]);
5386
- const source2 = nullable(o2.source, decodeSourceRange);
5387
- if (source2 === null !== (owner !== null))
5388
- invalid();
5389
- const artifact = decodeArtifact(o2.artifact), offset = integer(o2.offset, 0, LIMITS.detailBytes), start = integer(o2.start, 0, LIMITS.artifactPartBytes), length = integer(o2.length, 1, LIMITS.chunkBytes);
5390
- if (start + length > artifact.bytes || offset + length > LIMITS.detailBytes)
5391
- invalid();
5392
- return {
5393
- kind,
5394
- detailId: decodeId(o2.detailId),
5395
- source: source2,
5396
- artifact,
5397
- offset,
5398
- start,
5399
- length
5400
- };
5412
+ reserve(bytes) {
5413
+ if (!Number.isSafeInteger(bytes) || bytes < 0 || this.bytes + bytes > LIMITS.searchBytes || this.elapsed)
5414
+ throw new ContractError("receipt-inconclusive");
5415
+ this.bytes += bytes;
5416
+ }
5417
+ get elapsed() {
5418
+ return this.clock && performance.now() - this.started >= LIMITS.searchMs;
5401
5419
  }
5402
- const o = closed(value, ["kind", "detail", "source"]);
5403
- literal(kind, "end");
5404
- const detail = decodeRetainedDetail(o.detail), source = nullable(o.source, decodeSourceRange);
5405
- if (detail.origin.kind === "owner" !== (owner !== null) || source === null !== (owner !== null))
5406
- invalid();
5407
- return { kind: "end", detail, source };
5408
5420
  }
5409
- function closedTag(value) {
5410
- if (!value || typeof value !== "object" || !("kind" in value) || typeof value.kind !== "string")
5411
- invalid();
5412
- return value.kind;
5413
- }
5414
- function decodeAdmissionIntent(value) {
5415
- const o = closed(value, [
5416
- "commandId",
5417
- "operationId",
5418
- "runId",
5419
- "identity",
5420
- "createdTaskId"
5421
- ]);
5422
- return {
5423
- commandId: decodeId(o.commandId),
5424
- operationId: decodeId(o.operationId),
5425
- runId: nullable(o.runId, decodeId),
5426
- identity: decodeCommandIdentity(o.identity),
5427
- createdTaskId: nullable(o.createdTaskId, decodeId)
5428
- };
5429
- }
5430
- function decodeAuthIntent(value) {
5431
- const o = closed(value, [
5432
- "operationId",
5433
- "epoch",
5434
- "runId",
5435
- "childRunId",
5436
- "sourceSelectionId",
5437
- "validityMs"
5438
- ]);
5439
- return {
5440
- operationId: decodeId(o.operationId),
5441
- epoch: decodeId(o.epoch),
5442
- runId: decodeId(o.runId),
5443
- childRunId: decodeId(o.childRunId),
5444
- sourceSelectionId: decodeId(o.sourceSelectionId),
5445
- validityMs: integer(o.validityMs, 1)
5446
- };
5447
- }
5448
- function decodeAuthSettlement(value) {
5449
- const o = closed(value, ["operationId", "outcome", "admissionClosed"]);
5450
- return {
5451
- operationId: decodeId(o.operationId),
5452
- outcome: oneOf(o.outcome, ["resolved", "refused", "failed"]),
5453
- admissionClosed: bool(o.admissionClosed)
5454
- };
5455
- }
5456
- function decodeRecoveryBridge(v) {
5457
- const o = closed(v, [
5458
- "kind",
5459
- "recoveryId",
5460
- "epoch",
5461
- "coordinationDigest",
5462
- "historyDigest",
5463
- "previous",
5464
- "acknowledgments"
5465
- ]);
5466
- const p = closed(o.previous, [
5467
- "segment",
5468
- "bytes",
5469
- "sha256",
5470
- "committedBytes",
5471
- "committedSeq",
5472
- "highSeq"
5473
- ]);
5474
- const a = closed(o.acknowledgments, ["processes", "credentials", "storage"]);
5475
- const previous = {
5476
- segment: integer(p.segment),
5477
- bytes: integer(p.bytes, 0, LIMITS.segmentBytes),
5478
- sha256: decodeHash(p.sha256),
5479
- committedBytes: integer(p.committedBytes, 0, LIMITS.segmentBytes),
5480
- committedSeq: decodeSequence(p.committedSeq),
5481
- highSeq: decodeSequence(p.highSeq)
5482
- };
5483
- if (previous.committedBytes > previous.bytes || BigInt(previous.highSeq) < BigInt(previous.committedSeq))
5484
- invalid();
5485
- if (a.processes !== true || a.credentials !== true || a.storage !== true)
5486
- invalid();
5487
- return {
5488
- kind: literal(o.kind, "recovery"),
5489
- recoveryId: decodeId(o.recoveryId),
5490
- epoch: decodeId(o.epoch),
5491
- coordinationDigest: decodeHash(o.coordinationDigest),
5492
- historyDigest: decodeHash(o.historyDigest),
5493
- previous,
5494
- acknowledgments: { processes: true, credentials: true, storage: true }
5495
- };
5496
- }
5497
- function validBatchSequence(rows, previous) {
5498
- const bridge = rows[0]?.value;
5499
- const base = bridge?.kind === "recovery" ? bridge.previous.highSeq : previous;
5500
- if (bridge?.kind === "recovery" && (rows.length !== 1 || bridge.previous.committedSeq !== previous))
5501
- return false;
5502
- return rows.every((row, i) => row.seq === String(BigInt(base) + BigInt(i) + 1n) && (i === 0 || row.value.kind !== "recovery"));
5503
- }
5504
- function decodeResourcePolicy(v) {
5505
- const o = closed(v, [
5506
- "generation",
5507
- "quotaBytes",
5508
- "episodeReservationBytes",
5509
- "freeSpaceFloorBytes",
5510
- "episodeControlBytes"
5511
- ]);
5512
- return {
5513
- generation: integer(o.generation, 1),
5514
- quotaBytes: integer(o.quotaBytes, LIMITS.episodeControlBytes),
5515
- episodeReservationBytes: integer(o.episodeReservationBytes, LIMITS.episodeControlBytes),
5516
- freeSpaceFloorBytes: literal(o.freeSpaceFloorBytes, LIMITS.freeSpaceFloorBytes),
5517
- episodeControlBytes: literal(o.episodeControlBytes, LIMITS.episodeControlBytes)
5518
- };
5421
+ function corrupt() {
5422
+ throw new ContractError("corrupt-selected");
5519
5423
  }
5520
- function decodeJournalValue(value) {
5521
- const kind = closedTag(value);
5522
- switch (kind) {
5523
- case "recovery":
5524
- return decodeRecoveryBridge(value);
5525
- case "owner-epoch": {
5526
- const o = closed(value, ["kind", "epoch"]);
5527
- return { kind, epoch: decodeId(o.epoch) };
5528
- }
5529
- case "admission-compact": {
5530
- const hasPolicy = Object.hasOwn(value, "policy");
5531
- const o = closed(value, [
5532
- "kind",
5533
- "intent",
5534
- "receipt",
5535
- ...hasPolicy ? ["policy"] : []
5536
- ]);
5537
- const intent = decodeAdmissionIntent(o.intent);
5538
- return decodeJournalValue({
5539
- kind: "admission",
5540
- intent,
5541
- record: { identity: intent.identity, receipt: o.receipt },
5542
- ...hasPolicy ? { policy: o.policy } : {}
5543
- });
5544
- }
5545
- case "admission": {
5546
- const hasPolicy = typeof value === "object" && value !== null && Object.hasOwn(value, "policy");
5547
- const o = closed(value, [
5548
- "kind",
5549
- "intent",
5550
- "record",
5551
- ...hasPolicy ? ["policy"] : []
5552
- ]), intent = decodeAdmissionIntent(o.intent), record = decodeStoredCommand(o.record);
5553
- if (record.receipt.commandId !== intent.commandId || record.receipt.operationId !== intent.operationId || record.receipt.runId !== intent.runId || canonicalJson(record.identity) !== canonicalJson(intent.identity) || (record.receipt.created?.taskId ?? null) !== intent.createdTaskId)
5554
- invalid();
5555
- const policy = hasPolicy ? decodeResourcePolicy(o.policy) : undefined;
5556
- if (policy && intent.identity.kind === "action" && policy.generation !== intent.identity.expected.configRev)
5557
- invalid();
5558
- return { kind, intent, record, ...policy ? { policy } : {} };
5559
- }
5560
- case "auth-preparation": {
5561
- const o = closed(value, ["kind", "intent"]);
5562
- return { kind, intent: decodeAuthIntent(o.intent) };
5563
- }
5564
- case "auth-settlement": {
5565
- const o = closed(value, ["kind", "result"]);
5566
- return { kind, result: decodeAuthSettlement(o.result) };
5567
- }
5568
- case "launch": {
5569
- const o = closed(value, ["kind", "link", "operationId"]);
5570
- return {
5571
- kind,
5572
- link: decodeRecordingLink(o.link),
5573
- operationId: decodeId(o.operationId)
5574
- };
5575
- }
5576
- case "settlement": {
5577
- const o = closed(value, ["kind", "link", "completion"]), link = decodeRecordingLink(o.link), completion = decodeWorkerCompletion(o.completion);
5578
- if (link.childRunId !== completion.childRunId)
5579
- invalid();
5580
- return { kind, link, completion };
5424
+ var publicationFences = new Map;
5425
+ var activity = new Map;
5426
+
5427
+ class HistoryFiles {
5428
+ coordination;
5429
+ format;
5430
+ formatSnapshot;
5431
+ directory;
5432
+ files;
5433
+ proofSnapshot;
5434
+ constructor(coordination, format, formatSnapshot) {
5435
+ this.coordination = coordination;
5436
+ this.format = format;
5437
+ this.formatSnapshot = formatSnapshot;
5438
+ this.directory = join8(coordination.directory, "history-v2");
5439
+ this.files = coordination.files;
5440
+ }
5441
+ static open(coordination) {
5442
+ const dir = join8(coordination.directory, "history-v2");
5443
+ if (!coordination.files.directory(dir))
5444
+ return;
5445
+ const file = coordination.files.read(join8(dir, "format.json"));
5446
+ if (!file)
5447
+ corrupt();
5448
+ const format = decodeFormat(json(file));
5449
+ if (format.rootIdentity !== hash(coordination.store.root))
5450
+ corrupt();
5451
+ return new HistoryFiles(coordination, format, file);
5452
+ }
5453
+ static initialize(lease) {
5454
+ lease.assert();
5455
+ const c = lease.coordination;
5456
+ const existing = HistoryFiles.open(c);
5457
+ if (existing) {
5458
+ existing.assertWritable();
5459
+ return existing;
5581
5460
  }
5582
- case "capture": {
5583
- const o = closed(value, ["kind", "link", "source", "events"]);
5584
- return {
5585
- kind,
5586
- link: decodeRecordingLink(o.link),
5587
- source: decodeSourceRange(o.source),
5588
- events: list(o.events, LIMITS.ownerQueueFrames, (v) => decodeSavedEvent(v, null))
5589
- };
5461
+ const dir = join8(c.directory, "history-v2");
5462
+ const format = {
5463
+ schema: FORMAT,
5464
+ storeId: randomUUID6(),
5465
+ workspaceId: randomUUID6(),
5466
+ rootIdentity: hash(c.store.root)
5467
+ };
5468
+ c.files.directory(dir, true);
5469
+ for (const name of ["commands", "ledger", "runs", "indexes"]) {
5470
+ lease.assert();
5471
+ c.files.directory(join8(dir, name), true);
5590
5472
  }
5591
- case "owner": {
5592
- const o = closed(value, ["kind", "link", "observedMs", "events"]), link = decodeOwnerLink(o.link);
5593
- return {
5594
- kind,
5595
- link,
5596
- observedMs: integer(o.observedMs),
5597
- events: list(o.events, LIMITS.ownerQueueFrames, (v) => decodeSavedEvent(v, link))
5598
- };
5473
+ lease.assert();
5474
+ const keyPath = join8(dir, "read-proof-key.json");
5475
+ c.files.create(keyPath, encoded({
5476
+ schema: "cadence-history-read-key/1",
5477
+ storeId: format.storeId,
5478
+ key: randomBytes5(32).toString("base64url")
5479
+ }));
5480
+ lease.assert();
5481
+ const snapshot = c.files.create(join8(dir, "format.json"), encoded(format));
5482
+ const result = new HistoryFiles(c, format, snapshot);
5483
+ result.syncParents(join8(dir, "format.json"));
5484
+ result.assertWritable();
5485
+ return result;
5486
+ }
5487
+ activity() {
5488
+ const key = `${this.directory}/${this.format.storeId}`;
5489
+ let state = activity.get(key);
5490
+ if (!state) {
5491
+ state = { pending: 0, generation: 0 };
5492
+ activity.set(key, state);
5599
5493
  }
5600
- default:
5601
- return invalid();
5602
- }
5603
- }
5604
- function decodeJournalLine(value) {
5605
- if (closedTag(value) === "record") {
5606
- const o2 = closed(value, ["kind", "seq", "id", "value"]);
5607
- return {
5608
- kind: "record",
5609
- seq: decodeSequence(o2.seq),
5610
- id: decodeId(o2.id),
5611
- value: decodeJournalValue(o2.value)
5612
- };
5613
- }
5614
- const indexed = typeof value === "object" && value !== null && Object.hasOwn(value, "navigationRoot");
5615
- const o = closed(value, [
5616
- ...indexed ? ["navigationRoot"] : [],
5617
- "kind",
5618
- "previous",
5619
- "first",
5620
- "last",
5621
- "count",
5622
- "sha256",
5623
- "artifacts"
5624
- ]);
5625
- literal(o.kind, "commit");
5626
- return {
5627
- kind: "commit",
5628
- ...indexed ? { navigationRoot: nullable(o.navigationRoot, decodeHash) } : {},
5629
- previous: decodeSequence(o.previous),
5630
- first: decodeSequence(o.first),
5631
- last: decodeSequence(o.last),
5632
- count: integer(o.count, 1, LIMITS.segmentRecords),
5633
- sha256: decodeHash(o.sha256),
5634
- artifacts: list(o.artifacts, LIMITS.ownerQueueFrames, decodeArtifact)
5635
- };
5636
- }
5637
- var COMPLETE = {
5638
- observed: "complete",
5639
- source: "complete",
5640
- redacted: false,
5641
- omissions: []
5642
- };
5643
- function captureCoverage(event) {
5644
- if (event.kind === "capture-status")
5645
- return event.coverage;
5646
- if (event.kind === "omitted-block")
5647
- return { ...COMPLETE, source: "limited", omissions: [event.block.reason] };
5648
- return null;
5649
- }
5650
- function combineCoverage(a, b) {
5651
- return {
5652
- observed: a.observed === "complete" && b.observed === "complete" ? "complete" : "incomplete",
5653
- source: a.source === "unavailable" || b.source === "unavailable" ? "unavailable" : a.source === "limited" || b.source === "limited" ? "limited" : "complete",
5654
- redacted: a.redacted || b.redacted,
5655
- omissions: [...new Set([...a.omissions, ...b.omissions])]
5656
- };
5657
- }
5658
- function rowArtifacts(row) {
5659
- return row.value.kind === "capture" || row.value.kind === "owner" ? row.value.events.flatMap((e) => e.kind === "chunk" ? [e.artifact] : []) : [];
5660
- }
5661
- function batchArtifacts(rows) {
5662
- const unique = new Map;
5663
- for (const ref of rows.flatMap(rowArtifacts)) {
5664
- const key = `${ref.runId}/${ref.artifactId}/${ref.part}`, previous = unique.get(key);
5665
- if (previous && canonicalJson(previous) !== canonicalJson(ref))
5666
- invalid();
5667
- unique.set(key, ref);
5668
- }
5669
- return [...unique.values()];
5670
- }
5671
- var decodedLines = new Map;
5672
- var decodedBytes = 0;
5673
- var lineKey = (text2) => createHash4("sha256").update(text2).digest("hex");
5674
- function remember(text2, value) {
5675
- const key = lineKey(text2);
5676
- if (decodedLines.has(key))
5677
- return;
5678
- const bytes = Buffer.byteLength(text2);
5679
- while (decodedLines.size && (decodedLines.size >= 2 * LIMITS.segmentRecords || decodedBytes + bytes > 2 * LIMITS.segmentBytes)) {
5680
- const first = decodedLines.keys().next().value;
5681
- if (!first)
5682
- break;
5683
- decodedBytes -= decodedLines.get(first)?.bytes ?? 0;
5684
- decodedLines.delete(first);
5685
- }
5686
- if (bytes <= 2 * LIMITS.segmentBytes) {
5687
- decodedLines.set(key, { bytes, value });
5688
- decodedBytes += bytes;
5689
- }
5690
- }
5691
- function compactAdmission(value) {
5692
- if (value.kind !== "record" || value.value.kind !== "admission")
5693
- return value;
5694
- const { intent, record, policy } = value.value;
5695
- return {
5696
- ...value,
5697
- value: {
5698
- kind: "admission-compact",
5699
- intent,
5700
- receipt: record.receipt,
5701
- ...policy ? { policy } : {}
5702
- }
5703
- };
5704
- }
5705
- function encodeJournalText(input) {
5706
- const value = decodeJournalLine(input), text2 = canonicalJson(compactAdmission(value), RECORD_BYTES);
5707
- remember(text2, value);
5708
- return `${text2}
5709
- `;
5710
- }
5711
- function decodeJournalText(line) {
5712
- const text2 = line.endsWith(`
5713
- `) ? line.slice(0, -1) : line;
5714
- const epoch = /^\{"id":"([0-9a-f-]{36})","kind":"record","seq":"([0-9]+)","value":\{"epoch":"([0-9a-f-]{36})","kind":"owner-epoch"\}\}$/.exec(text2);
5715
- if (epoch)
5716
- return {
5717
- kind: "record",
5718
- id: decodeId(epoch[1]),
5719
- seq: decodeSequence(epoch[2]),
5720
- value: { kind: "owner-epoch", epoch: decodeId(epoch[3]) }
5721
- };
5722
- const key = lineKey(text2), cached = decodedLines.get(key);
5723
- if (cached)
5724
- return structuredClone(cached.value);
5725
- const raw = JSON.parse(text2);
5726
- const value = decodeJournalLine(raw);
5727
- const compact = raw?.kind === "record" && raw.value?.kind === "admission-compact";
5728
- if (canonicalJson(compact ? compactAdmission(value) : value, RECORD_BYTES) !== text2)
5729
- invalid();
5730
- remember(text2, value);
5731
- return structuredClone(value);
5732
- }
5733
-
5734
- // src/history/files.ts
5735
- class HistoryReadBudget {
5736
- clock;
5737
- started = performance.now();
5738
- bytes = 0;
5739
- walks = new DirectoryWalks;
5740
- constructor(clock = true) {
5741
- this.clock = clock;
5742
- }
5743
- static authority() {
5744
- return new HistoryReadBudget(false);
5745
- }
5746
- reserve(bytes) {
5747
- if (!Number.isSafeInteger(bytes) || bytes < 0 || this.bytes + bytes > LIMITS.searchBytes || this.elapsed)
5748
- throw new ContractError("receipt-inconclusive");
5749
- this.bytes += bytes;
5750
- }
5751
- get elapsed() {
5752
- return this.clock && performance.now() - this.started >= LIMITS.searchMs;
5753
- }
5754
- }
5755
- function corrupt() {
5756
- throw new ContractError("corrupt-selected");
5757
- }
5758
- var publicationFences = new Map;
5759
- var activity = new Map;
5760
-
5761
- class HistoryFiles {
5762
- coordination;
5763
- format;
5764
- formatSnapshot;
5765
- directory;
5766
- files;
5767
- proofSnapshot;
5768
- constructor(coordination, format, formatSnapshot) {
5769
- this.coordination = coordination;
5770
- this.format = format;
5771
- this.formatSnapshot = formatSnapshot;
5772
- this.directory = join12(coordination.directory, "history-v2");
5773
- this.files = coordination.files;
5774
- }
5775
- static open(coordination) {
5776
- const dir = join12(coordination.directory, "history-v2");
5777
- if (!coordination.files.directory(dir))
5778
- return;
5779
- const file = coordination.files.read(join12(dir, "format.json"));
5780
- if (!file)
5781
- corrupt();
5782
- const format = decodeFormat(json(file));
5783
- if (format.rootIdentity !== hash(coordination.store.root))
5784
- corrupt();
5785
- return new HistoryFiles(coordination, format, file);
5786
- }
5787
- static initialize(lease) {
5788
- lease.assert();
5789
- const c = lease.coordination;
5790
- const existing = HistoryFiles.open(c);
5791
- if (existing) {
5792
- existing.assertWritable();
5793
- return existing;
5794
- }
5795
- const dir = join12(c.directory, "history-v2");
5796
- const format = {
5797
- schema: FORMAT,
5798
- storeId: randomUUID6(),
5799
- workspaceId: randomUUID6(),
5800
- rootIdentity: hash(c.store.root)
5801
- };
5802
- c.files.directory(dir, true);
5803
- for (const name of ["commands", "ledger", "runs", "indexes"]) {
5804
- lease.assert();
5805
- c.files.directory(join12(dir, name), true);
5806
- }
5807
- lease.assert();
5808
- const keyPath = join12(dir, "read-proof-key.json");
5809
- c.files.create(keyPath, encoded({
5810
- schema: "cadence-history-read-key/1",
5811
- storeId: format.storeId,
5812
- key: randomBytes5(32).toString("base64url")
5813
- }));
5814
- lease.assert();
5815
- const snapshot = c.files.create(join12(dir, "format.json"), encoded(format));
5816
- const result = new HistoryFiles(c, format, snapshot);
5817
- result.syncParents(join12(dir, "format.json"));
5818
- result.assertWritable();
5819
- return result;
5820
- }
5821
- activity() {
5822
- const key = `${this.directory}/${this.format.storeId}`;
5823
- let state = activity.get(key);
5824
- if (!state) {
5825
- state = { pending: 0, generation: 0 };
5826
- activity.set(key, state);
5827
- }
5828
- return state;
5494
+ return state;
5829
5495
  }
5830
5496
  get writeGeneration() {
5831
5497
  return this.activity().generation;
@@ -5849,7 +5515,7 @@ class HistoryFiles {
5849
5515
  throw new ContractError("quarantine");
5850
5516
  }
5851
5517
  assertFormat() {
5852
- this.files.assert(join12(this.directory, "format.json"), this.formatSnapshot);
5518
+ this.files.assert(join8(this.directory, "format.json"), this.formatSnapshot);
5853
5519
  }
5854
5520
  readProofMaterial(budget) {
5855
5521
  if (this.format.schema !== FORMAT)
@@ -5858,7 +5524,7 @@ class HistoryFiles {
5858
5524
  this.assertReadFormat(budget);
5859
5525
  else
5860
5526
  this.assertFormat();
5861
- const selected2 = this.readBudgeted(join12(this.directory, "read-proof-key.json"), 512, budget);
5527
+ const selected2 = this.readBudgeted(join8(this.directory, "read-proof-key.json"), 512, budget);
5862
5528
  if (!selected2 || this.proofSnapshot && (selected2.identity !== this.proofSnapshot.identity || selected2.stamp !== this.proofSnapshot.stamp))
5863
5529
  corrupt();
5864
5530
  try {
@@ -5886,17 +5552,17 @@ class HistoryFiles {
5886
5552
  return;
5887
5553
  }
5888
5554
  this.assertFormat();
5889
- assertIncarnation(this, join12(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
5555
+ assertIncarnation(this, join8(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
5890
5556
  }
5891
5557
  segmentPath(segment) {
5892
- return join12(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
5558
+ return join8(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
5893
5559
  }
5894
5560
  artifactPath(ref) {
5895
- return join12(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
5561
+ return join8(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
5896
5562
  }
5897
5563
  admissionPath(runId) {
5898
5564
  decodeId(runId);
5899
- return join12(this.directory, "runs", runId, "admission.json");
5565
+ return join8(this.directory, "runs", runId, "admission.json");
5900
5566
  }
5901
5567
  writeAdmission(lease, input) {
5902
5568
  lease.assert();
@@ -5922,7 +5588,7 @@ class HistoryFiles {
5922
5588
  readCommand(commandId, budget) {
5923
5589
  decodeId(commandId);
5924
5590
  this.assertReadFormat(budget);
5925
- return this.readBudgeted(join12(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
5591
+ return this.readBudgeted(join8(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
5926
5592
  }
5927
5593
  readAdmission(runId, identity, budget) {
5928
5594
  return this.readAdmissionIdentity(runId, hash(canonicalJson(identity)), budget);
@@ -5943,7 +5609,7 @@ class HistoryFiles {
5943
5609
  syncParents(file) {
5944
5610
  if (process.platform === "win32")
5945
5611
  return;
5946
- for (let path2 = dirname12(file);; path2 = dirname12(path2)) {
5612
+ for (let path2 = dirname8(file);; path2 = dirname8(path2)) {
5947
5613
  if (!this.files.directory(path2))
5948
5614
  corrupt();
5949
5615
  const fd = openSync5(path2, constants5.O_RDONLY | constants5.O_DIRECTORY);
@@ -5954,13 +5620,13 @@ class HistoryFiles {
5954
5620
  }
5955
5621
  if (path2 === this.coordination.directory)
5956
5622
  break;
5957
- if (path2 === dirname12(path2))
5623
+ if (path2 === dirname8(path2))
5958
5624
  corrupt();
5959
5625
  }
5960
5626
  }
5961
5627
  assertReadFormat(budget) {
5962
5628
  budget.reserve(this.formatSnapshot.bytes.length);
5963
- this.files.assert(join12(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
5629
+ this.files.assert(join8(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
5964
5630
  }
5965
5631
  assertPublished(segment, end, seq) {
5966
5632
  const fence = publicationFences.get(this.directory);
@@ -5998,7 +5664,7 @@ class HistoryFiles {
5998
5664
  const dir = opendirSync3(path2);
5999
5665
  try {
6000
5666
  for (let e = dir.readSync();e; e = dir.readSync()) {
6001
- const p = join12(path2, e.name), st = lstatSync6(p);
5667
+ const p = join8(path2, e.name), st = lstatSync6(p);
6002
5668
  if (st.isDirectory())
6003
5669
  bytes += walk(p, depth + 1);
6004
5670
  else {
@@ -6033,7 +5699,7 @@ class HistoryFiles {
6033
5699
  state.bytes = 0;
6034
5700
  state.identity = null;
6035
5701
  let count = 0;
6036
- const ledger = join12(this.directory, "ledger");
5702
+ const ledger = join8(this.directory, "ledger");
6037
5703
  if (!this.files.directory(ledger))
6038
5704
  corrupt();
6039
5705
  const dir = opendirSync3(ledger);
@@ -6350,7 +6016,7 @@ class HistoryFiles {
6350
6016
  checkpoint(lease, state) {
6351
6017
  lease.assert();
6352
6018
  this.assertWritable();
6353
- const path2 = join12(this.directory, "checkpoint.json"), old = this.files.read(path2);
6019
+ const path2 = join8(this.directory, "checkpoint.json"), old = this.files.read(path2);
6354
6020
  const body = encoded({
6355
6021
  schema: "cadence-history-checkpoint/2",
6356
6022
  storeId: this.format.storeId,
@@ -6754,341 +6420,1216 @@ function projectRunObservation(runId, epoch, o, child) {
6754
6420
  result: null
6755
6421
  };
6756
6422
  }
6757
-
6758
- // src/history/recovery.ts
6759
- function digestHistory(files, exclude) {
6760
- files.assertFormat();
6761
- const digest = createHash6("sha256");
6762
- let count = 0;
6763
- const walk = (path2, depth) => {
6764
- if (depth > 8 || !files.files.directory(path2))
6765
- throw new ContractError("corrupt-selected");
6766
- const directory2 = lstatSync7(path2);
6767
- digest.update(`incarnation:${directory2.dev}:${directory2.ino}
6768
- `);
6769
- const dir = opendirSync4(path2), names2 = [];
6770
- try {
6771
- for (let e = dir.readSync();e; e = dir.readSync()) {
6772
- if (++count > LIMITS.dataFrames)
6773
- throw new ContractError("capacity");
6774
- names2.push(e.name);
6775
- }
6776
- } finally {
6777
- dir.closeSync();
6778
- }
6779
- for (const name of names2.sort()) {
6780
- const selected2 = join13(path2, name);
6781
- if (selected2 === exclude)
6782
- continue;
6783
- const rel = relative4(files.directory, selected2);
6784
- if (lstatSync7(selected2).isDirectory()) {
6785
- digest.update(`directory:${Buffer.byteLength(rel)}:${rel}
6786
- `);
6787
- walk(selected2, depth + 1);
6788
- } else {
6789
- const value = files.files.read(selected2, LIMITS.segmentBytes);
6790
- if (!value)
6791
- throw new ContractError("conflict");
6792
- digest.update(`file:${Buffer.byteLength(rel)}:${rel}:${value.stamp}:${value.bytes.length}:`).update(value.bytes);
6793
- }
6794
- }
6423
+
6424
+ // src/history/recovery.ts
6425
+ function digestHistory(files, exclude) {
6426
+ files.assertFormat();
6427
+ const digest = createHash6("sha256");
6428
+ let count = 0;
6429
+ const walk = (path2, depth) => {
6430
+ if (depth > 8 || !files.files.directory(path2))
6431
+ throw new ContractError("corrupt-selected");
6432
+ const directory2 = lstatSync7(path2);
6433
+ digest.update(`incarnation:${directory2.dev}:${directory2.ino}
6434
+ `);
6435
+ const dir = opendirSync4(path2), names2 = [];
6436
+ try {
6437
+ for (let e = dir.readSync();e; e = dir.readSync()) {
6438
+ if (++count > LIMITS.dataFrames)
6439
+ throw new ContractError("capacity");
6440
+ names2.push(e.name);
6441
+ }
6442
+ } finally {
6443
+ dir.closeSync();
6444
+ }
6445
+ for (const name of names2.sort()) {
6446
+ const selected2 = join9(path2, name);
6447
+ if (selected2 === exclude)
6448
+ continue;
6449
+ const rel = relative4(files.directory, selected2);
6450
+ if (lstatSync7(selected2).isDirectory()) {
6451
+ digest.update(`directory:${Buffer.byteLength(rel)}:${rel}
6452
+ `);
6453
+ walk(selected2, depth + 1);
6454
+ } else {
6455
+ const value = files.files.read(selected2, LIMITS.segmentBytes);
6456
+ if (!value)
6457
+ throw new ContractError("conflict");
6458
+ digest.update(`file:${Buffer.byteLength(rel)}:${rel}:${value.stamp}:${value.bytes.length}:`).update(value.bytes);
6459
+ }
6460
+ }
6461
+ };
6462
+ walk(files.directory, 0);
6463
+ files.assertFormat();
6464
+ return digest.digest("hex");
6465
+ }
6466
+
6467
+ class HistoryRecovery {
6468
+ coordination;
6469
+ committed = new WeakMap;
6470
+ constructor(coordination) {
6471
+ this.coordination = coordination;
6472
+ }
6473
+ files() {
6474
+ const files = HistoryFiles.open(this.coordination);
6475
+ if (!files)
6476
+ throw new ContractError("unavailable");
6477
+ return files;
6478
+ }
6479
+ assertCommitted(fact) {
6480
+ const assert = this.committed.get(fact);
6481
+ if (!assert)
6482
+ throw new ContractError("conflict");
6483
+ assert();
6484
+ }
6485
+ async inspect(epoch) {
6486
+ decodeId(epoch);
6487
+ const files = this.files(), before = digestHistory(files), state = emptyScan();
6488
+ const auth = new Set, processes = new Set, publication = new Set, unfinished = new Set, metadata = new Set, runs = new Map;
6489
+ for (const batch of files.scan(state))
6490
+ for (const row of batch.rows) {
6491
+ const v = row.value;
6492
+ if (v.kind === "recovery") {
6493
+ auth.clear();
6494
+ processes.clear();
6495
+ publication.clear();
6496
+ unfinished.clear();
6497
+ metadata.clear();
6498
+ runs.clear();
6499
+ }
6500
+ if (v.kind === "admission" && v.intent.identity.kind === "action") {
6501
+ if (v.intent.runId) {
6502
+ if (runs.size >= LIMITS.episodes || runs.has(v.intent.runId))
6503
+ throw new ContractError("corrupt-selected");
6504
+ runs.set(v.intent.runId, new RunState(v.intent.runId));
6505
+ } else
6506
+ metadata.add(v.intent.operationId);
6507
+ }
6508
+ for (const [id, state2] of runs) {
6509
+ state2.apply(row, batch.marker.last);
6510
+ if (state2.drained)
6511
+ runs.delete(id);
6512
+ }
6513
+ if (v.kind === "capture" || v.kind === "owner")
6514
+ for (const e of v.events) {
6515
+ if (e.kind === "capture" && e.value.event.kind === "detail-begin" && v.kind === "capture")
6516
+ unfinished.add(`worker/${v.link.childRunId}/${e.value.event.detailId}`);
6517
+ if (e.kind === "owner" && e.value.kind === "content-begin" && v.kind === "owner")
6518
+ unfinished.add(`owner/${v.link.operationId}/${e.value.detailId}`);
6519
+ if (e.kind === "end")
6520
+ unfinished.delete(`${e.detail.origin.kind}/${e.detail.origin.id}/${e.detail.detailId}`);
6521
+ }
6522
+ if (v.kind === "auth-preparation")
6523
+ auth.add(v.intent.operationId);
6524
+ if (v.kind === "auth-settlement")
6525
+ auth.delete(v.result.operationId);
6526
+ if (v.kind === "launch")
6527
+ processes.add(v.link.childRunId);
6528
+ if (v.kind === "settlement" && v.completion.native.closure !== "unconfirmed")
6529
+ processes.delete(v.link.childRunId);
6530
+ if (v.kind === "owner") {
6531
+ for (const e of v.events)
6532
+ if (e.kind === "owner" && e.value.kind === "fact") {
6533
+ const f = e.value.fact;
6534
+ if (f.kind === "check-start")
6535
+ processes.add(f.checkId);
6536
+ if (f.kind === "check-settled" && f.native.closure !== "unconfirmed")
6537
+ processes.delete(f.checkId);
6538
+ if (f.kind === "publication-intent")
6539
+ publication.add(f.publicationId);
6540
+ if (f.kind === "publication-settled") {
6541
+ if (f.status === "published") {
6542
+ publication.delete(f.publicationId);
6543
+ metadata.delete(v.link.operationId);
6544
+ } else
6545
+ publication.add(f.publicationId);
6546
+ }
6547
+ }
6548
+ }
6549
+ }
6550
+ if (before !== digestHistory(files))
6551
+ throw new ContractError("conflict");
6552
+ return {
6553
+ digest: before,
6554
+ processes: processes.size ? "unknown" : "externally-resolved",
6555
+ credentials: auth.size ? "unknown" : "independently-resolved",
6556
+ storage: state.tail || publication.size || unfinished.size || metadata.size || runs.size ? "unknown" : "independently-resolved"
6557
+ };
6558
+ }
6559
+ async append(confirmation, observeWrite) {
6560
+ if (typeof confirmation.assertLocalAuthority !== "function" || confirmation.acknowledgeEscapedProcesses !== true || confirmation.processes !== "externally-resolved" || confirmation.credentials !== "independently-resolved" || confirmation.storage !== "independently-resolved")
6561
+ throw new ContractError("forbidden");
6562
+ const expected = structuredClone(confirmation.expected), files = this.files();
6563
+ files.assertWritable();
6564
+ if (!expected.history || expected.owner !== "absent")
6565
+ throw new ContractError("quarantine");
6566
+ const current = await inspectRecovery(this.coordination, this);
6567
+ if (JSON.stringify(current) !== JSON.stringify(expected))
6568
+ throw new ContractError("conflict");
6569
+ const state = files.inspect(), recoveryId = randomUUID7();
6570
+ const bridge = {
6571
+ kind: "recovery",
6572
+ recoveryId,
6573
+ epoch: expected.epoch,
6574
+ coordinationDigest: expected.coordinationDigest,
6575
+ historyDigest: expected.history.digest,
6576
+ previous: {
6577
+ segment: state.segment,
6578
+ bytes: state.bytes,
6579
+ sha256: state.identity ?? hash(""),
6580
+ committedBytes: state.committedBytes ?? state.bytes,
6581
+ committedSeq: state.committedSeq,
6582
+ highSeq: state.highSeq ?? state.committedSeq
6583
+ },
6584
+ acknowledgments: { processes: true, credentials: true, storage: true }
6585
+ };
6586
+ const path2 = files.segmentPath(state.segment + 1);
6587
+ const assertCurrent = async () => {
6588
+ await confirmation.assertLocalAuthority(structuredClone(expected));
6589
+ const selected2 = await inspectRecovery(this.coordination);
6590
+ if (selected2.owner !== "absent")
6591
+ throw new ContractError("quarantine");
6592
+ if (selected2.epoch !== expected.epoch || selected2.coordinationDigest !== expected.coordinationDigest || digestHistory(files, path2) !== expected.history?.digest || this.coordination.recoverySnapshot().digest !== expected.coordinationDigest)
6593
+ throw new ContractError("conflict");
6594
+ };
6595
+ await confirmation.assertLocalAuthority(structuredClone(expected));
6596
+ if (digestHistory(files) !== expected.history.digest)
6597
+ throw new ContractError("conflict");
6598
+ const row = {
6599
+ kind: "record",
6600
+ id: randomUUID7(),
6601
+ seq: String(BigInt(bridge.previous.highSeq) + 1n),
6602
+ value: bridge
6603
+ };
6604
+ let quota = LIMITS.checkoutQuotaBytes;
6605
+ for (const batch of files.scan())
6606
+ for (const row2 of batch.rows)
6607
+ if (row2.value.kind === "admission" && row2.value.policy)
6608
+ quota = row2.value.policy.quotaBytes;
6609
+ const bytes = Buffer.byteLength(files.encodeBatch(state, [row]).body + files.encodeBatch(state, [row]).trailer);
6610
+ if (files.usage() + bytes > quota || files.freeBytes() < LIMITS.freeSpaceFloorBytes + bytes)
6611
+ throw new ContractError("recording-exhausted");
6612
+ const after = await files.appendRecovery(state, row, assertCurrent, observeWrite);
6613
+ const assert = () => {
6614
+ files.assertWritable();
6615
+ files.assertRecoveryIdle();
6616
+ files.files.assert(path2, after.snapshot, LIMITS.segmentBytes);
6617
+ if (digestHistory(files, path2) !== expected.history?.digest)
6618
+ throw new ContractError("conflict");
6619
+ };
6620
+ assert();
6621
+ const fact = {
6622
+ recoveryId,
6623
+ committedSeq: after.state.committedSeq,
6624
+ epoch: expected.epoch,
6625
+ coordinationDigest: expected.coordinationDigest,
6626
+ historyDigest: expected.history.digest,
6627
+ segmentIdentity: after.state.identity ?? ""
6628
+ };
6629
+ this.committed.set(fact, assert);
6630
+ return fact;
6631
+ }
6632
+ }
6633
+
6634
+ // src/service/gateway-recovery.ts
6635
+ import { opendirSync as opendirSync5 } from "node:fs";
6636
+ import { hostname as hostname4 } from "node:os";
6637
+ import { join as join10 } from "node:path";
6638
+ function required(resources, name) {
6639
+ const file = resources.get(name);
6640
+ if (!file)
6641
+ throw new ContractError("corrupt-selected");
6642
+ return file;
6643
+ }
6644
+ function names2(store, path2, maximum) {
6645
+ if (!store.files.directory(path2))
6646
+ return [];
6647
+ const stream = opendirSync5(path2), out = [];
6648
+ try {
6649
+ for (let row = stream.readSync();row; row = stream.readSync()) {
6650
+ if (out.length >= maximum || !row.isFile() && !row.isDirectory())
6651
+ throw new ContractError("corrupt-selected");
6652
+ out.push(row.name);
6653
+ }
6654
+ } finally {
6655
+ stream.closeSync();
6656
+ }
6657
+ return out.sort();
6658
+ }
6659
+ function capture(store) {
6660
+ const out = new Map;
6661
+ const keep = (name) => {
6662
+ const file = store.files.read(store.path(name));
6663
+ if (!file)
6664
+ throw new ContractError("corrupt-selected");
6665
+ out.set(name, file);
6666
+ };
6667
+ for (const name of names2(store, store.directory, 9)) {
6668
+ if ([
6669
+ "gateway.json",
6670
+ "gateway.lock",
6671
+ "input.json",
6672
+ "access-config.json",
6673
+ "access.json"
6674
+ ].includes(name))
6675
+ keep(name);
6676
+ else if (name === "projects" || name === "commands") {
6677
+ for (const child of names2(store, store.path(name), name === "projects" ? 128 : 2048)) {
6678
+ if (name === "projects" ? !/^[a-f0-9]{64}\.json$/.test(child) : !/^[a-f0-9-]{36}(?:\.result)?\.json$/.test(child))
6679
+ throw new ContractError("corrupt-selected");
6680
+ keep(`${name}/${child}`);
6681
+ }
6682
+ } else if (name !== "recovery-v1" || !store.files.directory(store.path(name)))
6683
+ throw new ContractError("corrupt-selected");
6684
+ }
6685
+ for (const name of [
6686
+ "gateway.json",
6687
+ "input.json",
6688
+ "access-config.json",
6689
+ "access.json"
6690
+ ])
6691
+ if (!out.has(name))
6692
+ throw new ContractError("corrupt-selected");
6693
+ for (const [name, file] of out) {
6694
+ if (!name.startsWith("commands/"))
6695
+ continue;
6696
+ if (name.endsWith(".result.json")) {
6697
+ if (!out.has(name.replace(".result.json", ".json")))
6698
+ throw new ContractError("corrupt-selected");
6699
+ decodeProjectEntry(json(file));
6700
+ } else {
6701
+ decodeId(name.slice("commands/".length, -5));
6702
+ const intent = closed(json(file), ["path", "epoch"]);
6703
+ text(intent.path, 4096, true);
6704
+ decodeId(intent.epoch);
6705
+ }
6706
+ }
6707
+ return out;
6708
+ }
6709
+ function signature(resources) {
6710
+ return hash(encoded([...resources].sort(([a], [b]) => a.localeCompare(b, "en")).map(([name, file]) => [
6711
+ name,
6712
+ file.stamp,
6713
+ ["gateway.json", "access.json", "access-config.json"].includes(name) ? null : file.identity
6714
+ ])));
6715
+ }
6716
+ function state(store) {
6717
+ const registration2 = store.read();
6718
+ if (!registration2)
6719
+ throw new ContractError("unavailable");
6720
+ const resources = capture(store), lock = resources.get("gateway.lock");
6721
+ if (!lock)
6722
+ throw new ContractError("unavailable");
6723
+ decodeId(closed(json(lock), ["epoch"]).epoch);
6724
+ const input = decodeGatewayInput(json(required(resources, "input.json")));
6725
+ if (input.serverConfig !== store.serverConfig || input.stateHome !== store.stateHome)
6726
+ throw new ContractError("conflict");
6727
+ const projects = store.projects();
6728
+ store.associatedProjects();
6729
+ const incarnation = hash(encoded([lock.identity, lock.stamp]));
6730
+ if (registration2.phase === "stopped" && !registration2.recovery?.receipt.startsWith(`recovery-v1/${incarnation}/`))
6731
+ throw new ContractError("corrupt-selected");
6732
+ const archive = store.path(`recovery-v1/${incarnation}`);
6733
+ const attempts = [];
6734
+ let previousResolver = null;
6735
+ const entries = names2(store, archive, 128);
6736
+ for (let n = 0;n < entries.length; n++) {
6737
+ if (!entries.includes(`${n}.json`))
6738
+ throw new ContractError("corrupt-selected");
6739
+ const file = store.files.read(join10(archive, `${n}.json`), 65536);
6740
+ if (!file)
6741
+ throw new ContractError("corrupt-selected");
6742
+ const row = closed(json(file, 65536), [
6743
+ "schema",
6744
+ "lock",
6745
+ "epoch",
6746
+ "digest",
6747
+ "pid",
6748
+ "host",
6749
+ "legacyHost",
6750
+ "retiredProjects"
6751
+ ]);
6752
+ literal(row.schema, "cadence-workbench-recovery/1");
6753
+ literal(row.lock, lock.bytes.toString("utf8"));
6754
+ literal(row.epoch, registration2.epoch);
6755
+ decodeHash(row.digest);
6756
+ if (!Array.isArray(row.retiredProjects) || row.retiredProjects.length > 128)
6757
+ throw new ContractError("corrupt-selected");
6758
+ for (const entry of row.retiredProjects) {
6759
+ const project = closed(entry, ["id", "stamp"]);
6760
+ decodeHash(project.id);
6761
+ text(project.stamp, 512, true);
6762
+ }
6763
+ if (typeof row.legacyHost !== "boolean")
6764
+ throw new ContractError("corrupt-selected");
6765
+ previousResolver = recoveryOwnerStatus(integer(row.pid, 1, 2147483647), text(row.host, 255, true));
6766
+ attempts.push(file);
6767
+ }
6768
+ if (encoded(store.read()) !== encoded(registration2))
6769
+ throw new ContractError("conflict");
6770
+ for (const [name, file] of resources)
6771
+ store.files.assert(store.path(name), file);
6772
+ const localOwner = recoveryOwnerStatus(registration2.pid, registration2.host ?? hostname4());
6773
+ const inspection = {
6774
+ scope: "workbench",
6775
+ epoch: registration2.epoch,
6776
+ coordinationDigest: hash(encoded([
6777
+ signature(resources),
6778
+ attempts.map((a) => [a.identity, a.stamp])
6779
+ ])),
6780
+ owner: registration2.host ? localOwner : localOwner === "live" ? "live" : "ambiguous",
6781
+ process: { pid: registration2.pid, host: registration2.host ?? null },
6782
+ legacyHost: !registration2.host,
6783
+ previousResolver,
6784
+ projects: projects.length,
6785
+ openings: [...resources.keys()].filter((name) => name.startsWith("commands/") && !name.endsWith(".result.json")).length,
6786
+ resolution: registration2.host ? "local-interactive-only" : "local-host-confirmation-required"
6787
+ };
6788
+ return {
6789
+ registration: registration2,
6790
+ resources,
6791
+ lock,
6792
+ archive,
6793
+ incarnation,
6794
+ attempts,
6795
+ localOwner,
6796
+ inspection
6797
+ };
6798
+ }
6799
+ function inspectGatewayRecovery(store) {
6800
+ const before = state(store), after = state(store);
6801
+ if (encoded(before.inspection) !== encoded(after.inspection))
6802
+ throw new ContractError("conflict");
6803
+ return before.inspection;
6804
+ }
6805
+ function resolveGatewayRecovery(store, expected, assertAuthority, legacyHostConfirmed = false) {
6806
+ assertAuthority();
6807
+ const selected2 = state(store);
6808
+ if (encoded(selected2.inspection) !== encoded(expected))
6809
+ throw new ContractError("conflict");
6810
+ const absent2 = () => {
6811
+ assertAuthority();
6812
+ if (expected.legacyHost && !legacyHostConfirmed || recoveryOwnerStatus(selected2.registration.pid, selected2.registration.host ?? hostname4()) !== "absent")
6813
+ throw new ContractError("quarantine");
6814
+ };
6815
+ absent2();
6816
+ if (expected.previousResolver !== null && expected.previousResolver !== "absent")
6817
+ throw new ContractError("quarantine");
6818
+ if (selected2.attempts.length >= 128)
6819
+ throw new ContractError("capacity");
6820
+ const receiptPath = join10(selected2.archive, `${selected2.attempts.length}.json`);
6821
+ const receipt = store.files.create(receiptPath, encoded({
6822
+ schema: "cadence-workbench-recovery/1",
6823
+ lock: selected2.lock.bytes.toString("utf8"),
6824
+ epoch: expected.epoch,
6825
+ digest: expected.coordinationDigest,
6826
+ pid: process.pid,
6827
+ host: hostname4(),
6828
+ legacyHost: expected.legacyHost,
6829
+ retiredProjects: [...selected2.resources].filter(([name]) => name.startsWith("projects/")).map(([name, file]) => ({
6830
+ id: name.slice("projects/".length, -5),
6831
+ stamp: file.stamp
6832
+ }))
6833
+ }), 65536);
6834
+ const assert = () => {
6835
+ absent2();
6836
+ store.associatedProjects();
6837
+ const current = capture(store);
6838
+ if (signature(current) !== signature(selected2.resources))
6839
+ throw new ContractError("conflict");
6840
+ for (const [name, file] of selected2.resources)
6841
+ store.files.assert(store.path(name), file);
6842
+ if (names2(store, selected2.archive, 128).length !== selected2.attempts.length + 1)
6843
+ throw new ContractError("conflict");
6844
+ for (const [n, file] of selected2.attempts.entries())
6845
+ store.files.assert(join10(selected2.archive, `${n}.json`), file, 65536);
6846
+ store.files.assert(receiptPath, receipt, 65536);
6847
+ };
6848
+ assert();
6849
+ const registration2 = store.files.replace(store.path("gateway.json"), required(selected2.resources, "gateway.json"), encoded({
6850
+ ...selected2.registration,
6851
+ phase: "stopped",
6852
+ recovery: {
6853
+ receipt: `recovery-v1/${selected2.incarnation}/${selected2.attempts.length}.json`,
6854
+ sha256: receipt.identity,
6855
+ stamp: receipt.stamp
6856
+ }
6857
+ }));
6858
+ selected2.resources.set("gateway.json", registration2);
6859
+ assert();
6860
+ store.files.remove(store.path("gateway.lock"), selected2.lock);
6861
+ return {
6862
+ status: "resolved",
6863
+ scope: "workbench",
6864
+ epoch: expected.epoch,
6865
+ notice: "operator-acknowledgment-only-project-fences-preserved"
6866
+ };
6867
+ }
6868
+
6869
+ // src/service/recovery-cli.ts
6870
+ var warning = "PID absence is not proof that descendants stopped or credential writes settled. Inspect/stop old processes externally, independently inspect the selected Pi credential store and SDK locks, and inspect retained history/storage. Cadence does not kill, repair, reset, migrate or certify those resources. Unknown historical outcomes remain unknown. A new Start is required.";
6871
+ function native() {
6872
+ if (!isatty(0) || !isatty(1) || !process.stdin.isTTY || !process.stdout.isTTY)
6873
+ throw new ContractError("forbidden");
6874
+ }
6875
+ async function confirm(expected) {
6876
+ native();
6877
+ if (process.stdin.readableLength)
6878
+ throw new ContractError("forbidden");
6879
+ const identity = JSON.stringify(expected);
6880
+ const deadline = performance.now() + LIMITS.grantMs;
6881
+ const prompts = [
6882
+ ...expected.legacyHost ? [
6883
+ [
6884
+ "Legacy registration has no host identity. Independently verify this state originated on THIS host and is not shared/copied from another host. Type local-host",
6885
+ "local-host"
6886
+ ]
6887
+ ] : [],
6888
+ [
6889
+ "Confirm old processes stopped externally; acknowledge escaped-descendant uncertainty. Type processes",
6890
+ "processes"
6891
+ ],
6892
+ [
6893
+ "Confirm independent credential/refresh/writeback inspection; PID absence does not settle auth. Type credentials",
6894
+ "credentials"
6895
+ ],
6896
+ [
6897
+ "Confirm independent storage inspection; preserve damaged bytes and unknown outcomes. Type storage",
6898
+ "storage"
6899
+ ],
6900
+ [
6901
+ `${expected.scope === "global-registry" ? "Release ONLY the global registry lock; all project fences remain." : expected.scope === "workbench" ? "Release ONLY the workbench fence and retire its old project associations; all project fences remain." : "Release ONLY the displayed unchanged service-owned fences."} Type resolve ${expected.epoch}`,
6902
+ `resolve ${expected.epoch}`
6903
+ ]
6904
+ ];
6905
+ await new Promise((resolve5, reject) => {
6906
+ let index = 0, line = "", done = false;
6907
+ const raw = process.stdin.isRaw;
6908
+ const finish = (ok) => {
6909
+ if (done)
6910
+ return;
6911
+ done = true;
6912
+ clearTimeout(timer);
6913
+ process.stdin.removeListener("data", input);
6914
+ process.stdin.removeListener("end", fail);
6915
+ process.stdin.removeListener("error", fail);
6916
+ process.stdin.setRawMode(raw);
6917
+ process.stdin.pause();
6918
+ process.stdout.write(`\x1B[?2004l
6919
+ `);
6920
+ if (ok)
6921
+ resolve5();
6922
+ else
6923
+ reject(new ContractError("forbidden"));
6924
+ };
6925
+ const fail = () => finish(false);
6926
+ const show = () => process.stdout.write(`${prompts[index]?.[0]}: `);
6927
+ const input = (chunk) => {
6928
+ if (performance.now() >= deadline || chunk.length > 128)
6929
+ return fail();
6930
+ for (let n = 0;n < chunk.length; n++) {
6931
+ const byte = chunk[n];
6932
+ if (byte === 13 || byte === 10) {
6933
+ if (n !== chunk.length - 1 || line !== prompts[index]?.[1])
6934
+ return fail();
6935
+ line = "";
6936
+ process.stdout.write(`
6937
+ `);
6938
+ if (++index === prompts.length)
6939
+ return finish(true);
6940
+ show();
6941
+ } else if (byte === 127) {
6942
+ line = line.slice(0, -1);
6943
+ } else if (byte !== undefined && byte >= 32 && byte <= 126 && line.length < 80) {
6944
+ line += String.fromCharCode(byte);
6945
+ } else
6946
+ return fail();
6947
+ }
6948
+ };
6949
+ const timer = setTimeout(fail, LIMITS.grantMs);
6950
+ process.stdin.setRawMode(true);
6951
+ process.stdout.write("\x1B[?2004h");
6952
+ process.stdin.on("data", input).once("end", fail).once("error", fail).resume();
6953
+ show();
6954
+ });
6955
+ return () => {
6956
+ native();
6957
+ if (performance.now() >= deadline || JSON.stringify(expected) !== identity)
6958
+ throw new ContractError("stale");
6959
+ };
6960
+ }
6961
+ async function localRegistryRecovery(c, operation) {
6962
+ if (operation === "resolve")
6963
+ native();
6964
+ const registryLock = c.registryLockDiagnostic();
6965
+ if (registryLock) {
6966
+ let expected;
6967
+ try {
6968
+ expected = c.inspectRegistryRecovery();
6969
+ } catch {
6970
+ const blocked = {
6971
+ scope: "global-registry",
6972
+ ...registryLock,
6973
+ coordination: "unavailable",
6974
+ resolution: "blocked-preserve-state",
6975
+ notice: "Registry state is incomplete, changed or unsafe. Preserve it; no lock or project resource was released."
6976
+ };
6977
+ if (operation === "inspect")
6978
+ return blocked;
6979
+ process.stdout.write(`${JSON.stringify(blocked)}
6980
+ `);
6981
+ throw new ContractError("quarantine");
6982
+ }
6983
+ const view = {
6984
+ ...expected,
6985
+ process: {
6986
+ ...expected.process,
6987
+ host: diagnosticText(expected.process.host, 255)
6988
+ },
6989
+ notice: `Global registry lock blocks every project in this state home. Recovery preserves its original bytes and all project reservations, owner/checkout locks and history. Inspect project fences separately afterward. ${warning}`
6990
+ };
6991
+ if (operation === "inspect")
6992
+ return view;
6993
+ process.stdout.write(`${JSON.stringify(view)}
6994
+ `);
6995
+ if (expected.owner !== "absent" || expected.previousResolver !== null && expected.previousResolver !== "absent")
6996
+ throw new ContractError("quarantine");
6997
+ const assertFresh = await confirm(expected);
6998
+ return c.resolveRegistryRecovery(expected, assertFresh);
6999
+ }
7000
+ return;
7001
+ }
7002
+ async function localRecovery(c, operation) {
7003
+ const registry = await localRegistryRecovery(c, operation);
7004
+ if (registry !== undefined)
7005
+ return registry;
7006
+ const history = new HistoryRecovery(c);
7007
+ const expected = await inspectRecovery(c, history);
7008
+ const registration2 = c.recoverySnapshot().registration;
7009
+ const view = {
7010
+ ...expected,
7011
+ process: {
7012
+ pid: registration2.pid,
7013
+ host: diagnosticText(registration2.host, 255)
7014
+ },
7015
+ notice: warning
7016
+ };
7017
+ if (operation === "inspect")
7018
+ return view;
7019
+ process.stdout.write(`${JSON.stringify(view)}
7020
+ `);
7021
+ if (expected.owner !== "absent")
7022
+ throw new ContractError("quarantine");
7023
+ const assertFresh = await confirm(expected);
7024
+ return resolveRecovery(c, {
7025
+ expected,
7026
+ acknowledgeEscapedProcesses: true,
7027
+ processes: "externally-resolved",
7028
+ credentials: "independently-resolved",
7029
+ storage: "independently-resolved",
7030
+ assertLocalAuthority: (choice) => {
7031
+ assertFresh();
7032
+ if (JSON.stringify(choice) !== JSON.stringify(expected))
7033
+ throw new ContractError("stale");
7034
+ }
7035
+ }, history);
7036
+ }
7037
+ async function localGatewayRecovery(store, operation) {
7038
+ const registry = await localRegistryRecovery(new ServiceCoordination(null, store.stateHome), operation);
7039
+ if (registry !== undefined)
7040
+ return registry;
7041
+ let expected;
7042
+ try {
7043
+ const observed = store.inspect();
7044
+ if (observed.status === "absent" || observed.status === "stopped") {
7045
+ if (operation === "resolve")
7046
+ throw new ContractError("unavailable");
7047
+ return { ...observed, scope: "workbench" };
7048
+ }
7049
+ expected = inspectGatewayRecovery(store);
7050
+ } catch (error) {
7051
+ if (operation === "resolve")
7052
+ throw error;
7053
+ return {
7054
+ status: "unavailable",
7055
+ scope: "workbench",
7056
+ resolution: "blocked-preserve-state",
7057
+ error: error instanceof ContractError ? error.code : "unavailable",
7058
+ notice: "Workbench state is incomplete, changed or unsafe. Preserve it; no resource was released."
7059
+ };
7060
+ }
7061
+ const view = {
7062
+ ...expected,
7063
+ process: {
7064
+ ...expected.process,
7065
+ host: expected.process.host === null ? null : diagnosticText(expected.process.host, 255)
7066
+ },
7067
+ notice: `Recovery releases ONLY the workbench fence and retires its exact old project associations. Project references, reservations, owner/checkout locks, opening receipts and history remain unchanged. Explicit Open must associate a project again; recover affected project owners separately. ${warning}`
7068
+ };
7069
+ if (operation === "inspect")
7070
+ return view;
7071
+ process.stdout.write(`${JSON.stringify(view)}
7072
+ `);
7073
+ if (!expected.legacyHost && expected.owner !== "absent" || expected.owner === "live" || expected.previousResolver !== null && expected.previousResolver !== "absent")
7074
+ throw new ContractError("quarantine");
7075
+ const authority = await confirm(expected);
7076
+ return resolveGatewayRecovery(store, expected, authority, expected.legacyHost);
7077
+ }
7078
+
7079
+ // src/service/runtime.ts
7080
+ import { homedir as homedir4 } from "node:os";
7081
+ import { dirname as dirname9, isAbsolute as isAbsolute6, join as join11, resolve as resolve5 } from "node:path";
7082
+ var STARTUP_INPUT_BYTES = 16 * 1024;
7083
+ function absolute(value) {
7084
+ const result = text(value, 4096, true);
7085
+ if (!isAbsolute6(result))
7086
+ throw new ContractError("invalid");
7087
+ return result;
7088
+ }
7089
+ function decodeHostConfig(value) {
7090
+ const o = closed(value, [
7091
+ "schema",
7092
+ "http",
7093
+ "cwd",
7094
+ "agentDir",
7095
+ "tokenSource",
7096
+ "tokenFile"
7097
+ ]);
7098
+ const tokenSource = oneOf(o.tokenSource, [
7099
+ "generated",
7100
+ "environment",
7101
+ "file",
7102
+ "config"
7103
+ ]);
7104
+ const tokenFile = o.tokenFile === null ? null : absolute(o.tokenFile);
7105
+ if ((tokenSource === "file" || tokenSource === "config") !== !!tokenFile)
7106
+ throw new ContractError("invalid");
7107
+ return {
7108
+ schema: literal(o.schema, "cadence-service-host/1"),
7109
+ http: decodeHttpPolicy(o.http),
7110
+ cwd: absolute(o.cwd),
7111
+ agentDir: absolute(o.agentDir),
7112
+ tokenSource,
7113
+ tokenFile
7114
+ };
7115
+ }
7116
+ function readHostConfig(c) {
7117
+ const owner = c.configuration();
7118
+ if (!owner)
7119
+ return;
7120
+ const snapshot = c.files.read(c.path("host-config.json"));
7121
+ if (!snapshot)
7122
+ throw new ContractError("corrupt-selected");
7123
+ const config = decodeHostConfig(json(snapshot));
7124
+ if (!config.http.port || owner.endpoint !== localOrigin(config.http, config.http.port) || owner.policyIdentity !== hash(encoded(config)))
7125
+ throw new ContractError("conflict");
7126
+ return config;
7127
+ }
7128
+ function hostEnvironment(ambient) {
7129
+ let route;
7130
+ const facts = networkSnapshot(ambient);
7131
+ try {
7132
+ if (facts.NODE_OPTIONS.present)
7133
+ throw new Error;
7134
+ route = sdkNetworkEnvironment(facts, networkSnapshot(undefined));
7135
+ } catch {
7136
+ throw new ContractError("unsupported-context");
7137
+ }
7138
+ const result = Object.create(null);
7139
+ for (const key of Object.keys(ambient)) {
7140
+ const d = Object.getOwnPropertyDescriptor(ambient, key);
7141
+ if (!d || !("value" in d) || typeof d.value !== "string")
7142
+ throw new ContractError("invalid");
7143
+ if (/^(?:CADENCE_|PI_|ABEL_|TRELLIS_|GIT_)/i.test(key) || networkKeys.some((name) => name === key))
7144
+ continue;
7145
+ result[key] = d.value;
7146
+ }
7147
+ if (route.redactionLiterals.length) {
7148
+ result.HTTP_PROXY = route.env.HTTP_PROXY;
7149
+ result.HTTPS_PROXY = route.env.HTTPS_PROXY;
7150
+ result.NODE_USE_ENV_PROXY = "1";
7151
+ }
7152
+ result.PI_OFFLINE = "1";
7153
+ result.NO_COLOR = "1";
7154
+ if (!sameNetwork(facts, networkSnapshot(ambient)))
7155
+ throw new ContractError("stale");
7156
+ return result;
7157
+ }
7158
+ function selectStart(options, ambient) {
7159
+ const workspace = resolveWorkspace(options.workspace), c = new ServiceCoordination(workspace.root, options.stateHome);
7160
+ const old = readHostConfig(c);
7161
+ const env = Object.getOwnPropertyDescriptor(ambient, "CADENCE_WEB_TOKEN");
7162
+ if (env && (!("value" in env) || typeof env.value !== "string"))
7163
+ throw new ContractError("invalid");
7164
+ let fromFile;
7165
+ if (options.config) {
7166
+ const path2 = resolve5(options.config), f = new PrivateFiles(dirname9(path2));
7167
+ const bytes = f.read(path2);
7168
+ if (!bytes)
7169
+ throw new ContractError("corrupt-selected");
7170
+ fromFile = decodeHostConfig(json(bytes));
7171
+ }
7172
+ const base = old ?? fromFile ?? decodeHostConfig({
7173
+ schema: "cadence-service-host/1",
7174
+ cwd: workspace.cwd,
7175
+ http: {
7176
+ schema: "cadence-service-http/1",
7177
+ host: options.host ?? "127.0.0.1",
7178
+ port: options.port ?? 17423,
7179
+ proxy: null
7180
+ },
7181
+ agentDir: resolve5(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join11(ambient.HOME ?? ambient.USERPROFILE ?? homedir4(), ".pi", "agent")),
7182
+ tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
7183
+ tokenFile: options.tokenFile ? resolve5(options.tokenFile) : null
7184
+ });
7185
+ const server = options.serverConfig !== undefined ? resolve5(options.serverConfig) : old?.tokenSource === "config" ? old.tokenFile : null;
7186
+ let selected2 = base;
7187
+ if (server) {
7188
+ if (options.tokenFile !== undefined)
7189
+ throw new ContractError("conflict");
7190
+ const { port } = readServerConfig(server).config;
7191
+ selected2 = decodeHostConfig({
7192
+ ...base,
7193
+ http: { ...base.http, port },
7194
+ tokenSource: "config",
7195
+ tokenFile: server
7196
+ });
7197
+ }
7198
+ if (fromFile && encoded(fromFile) !== encoded(selected2) || options.port !== undefined && options.port !== selected2.http.port || options.host !== undefined && options.host !== selected2.http.host || options.agentDir !== undefined && resolve5(options.agentDir) !== selected2.agentDir || options.tokenFile !== undefined && resolve5(options.tokenFile) !== selected2.tokenFile || env && selected2.tokenSource !== "environment" || selected2.tokenSource === "environment" && !env || resolveWorkspace(selected2.cwd).root !== workspace.root)
7199
+ throw new ContractError("conflict");
7200
+ return {
7201
+ workspace: workspace.root,
7202
+ stateHome: c.stateHome,
7203
+ config: selected2,
7204
+ environmentToken: env?.value ?? null
7205
+ };
7206
+ }
7207
+ function decodeHostInput(value) {
7208
+ const o = closed(value, [
7209
+ "workspace",
7210
+ "stateHome",
7211
+ "config",
7212
+ "environmentToken"
7213
+ ]);
7214
+ const config = decodeHostConfig(o.config);
7215
+ const environmentToken = o.environmentToken === null ? null : text(o.environmentToken, 1024, true);
7216
+ if (config.tokenSource === "environment" !== (environmentToken !== null))
7217
+ throw new ContractError("invalid");
7218
+ return {
7219
+ workspace: absolute(o.workspace),
7220
+ stateHome: absolute(o.stateHome),
7221
+ config,
7222
+ environmentToken
7223
+ };
7224
+ }
7225
+ function portValue(value) {
7226
+ if (!/^(0|[1-9][0-9]{0,4})$/.test(value))
7227
+ throw new ContractError("invalid");
7228
+ return integer(Number(value), 0, 65535);
7229
+ }
7230
+ var STARTUP_STAGES = [
7231
+ "package",
7232
+ "pi-agent-directory",
7233
+ "pi-auth",
7234
+ "pi-models",
7235
+ "pi-runtime",
7236
+ "service"
7237
+ ];
7238
+ function decodeStartupReport(value) {
7239
+ const o = closed(value, ["status", "error", "stage"]);
7240
+ return {
7241
+ status: oneOf(o.status, ["refused", "quarantined"]),
7242
+ error: oneOf(o.error, ERROR_CODES),
7243
+ stage: oneOf(o.stage, STARTUP_STAGES)
7244
+ };
7245
+ }
7246
+
7247
+ // src/service/gateway-cli.ts
7248
+ async function gatewayMain(argv, hostUrl) {
7249
+ const operation = argv.shift();
7250
+ if (![
7251
+ "start",
7252
+ "status",
7253
+ "diagnose",
7254
+ "stop",
7255
+ "token-link",
7256
+ "token-rotate",
7257
+ "recover"
7258
+ ].includes(operation ?? ""))
7259
+ throw new ContractError("invalid");
7260
+ const recovery = operation === "recover" ? argv.shift() : undefined;
7261
+ if (operation === "recover" && recovery !== "inspect" && recovery !== "resolve")
7262
+ throw new ContractError("invalid");
7263
+ let config = serverConfigPath(homedir5()), stateHome = defaultStateHome(), agentDir, policyFile;
7264
+ let foreground = false, reveal = false;
7265
+ const seen = new Set;
7266
+ while (argv.length) {
7267
+ const key = argv.shift();
7268
+ if (seen.has(key))
7269
+ throw new ContractError("invalid");
7270
+ seen.add(key);
7271
+ if (key === "--foreground" && operation === "start")
7272
+ foreground = true;
7273
+ else if (key === "--reveal" && operation === "token-link")
7274
+ reveal = true;
7275
+ else if (argv[0] && (operation === "recover" ? ["--server-config", "--state-home"] : ["--server-config", "--state-home", "--agent-dir", "--config"]).includes(key)) {
7276
+ const value = resolve6(argv.shift());
7277
+ if (key === "--server-config")
7278
+ config = value;
7279
+ if (key === "--state-home")
7280
+ stateHome = value;
7281
+ if (key === "--agent-dir")
7282
+ agentDir = value;
7283
+ if (key === "--config")
7284
+ policyFile = value;
7285
+ } else
7286
+ throw new ContractError("invalid");
7287
+ }
7288
+ const store = new GatewayStore(config, stateHome);
7289
+ let result;
7290
+ if (operation === "start") {
7291
+ const network = networkSnapshot(process.env), env = hostEnvironment(process.env);
7292
+ if (!seen.has("--server-config"))
7293
+ ensureServerConfig(homedir5());
7294
+ const server = readServerConfig(config).config;
7295
+ const selectedFile = store.files.read(store.path("input.json"));
7296
+ const selected2 = selectedFile ? decodeGatewayInput(json(selectedFile)) : undefined;
7297
+ let policy = selected2?.policy ?? decodeHttpPolicy({
7298
+ schema: "cadence-service-http/1",
7299
+ host: "127.0.0.1",
7300
+ port: server.port,
7301
+ proxy: null
7302
+ });
7303
+ if (policyFile) {
7304
+ const file = new PrivateFiles(dirname10(policyFile)).read(policyFile);
7305
+ if (!file)
7306
+ throw new ContractError("invalid");
7307
+ policy = decodeHttpPolicy(json(file));
7308
+ }
7309
+ const input = decodeGatewayInput({
7310
+ kind: "workbench",
7311
+ serverConfig: config,
7312
+ stateHome: store.stateHome,
7313
+ agentDir: agentDir ?? selected2?.agentDir ?? resolve6(process.env.PI_CODING_AGENT_DIR ?? join12(homedir5(), ".pi", "agent")),
7314
+ policy: { ...policy, port: server.port }
7315
+ });
7316
+ const status = await serviceStatus(store);
7317
+ if (status && typeof status === "object" && "status" in status && status.status === "alive") {
7318
+ if (!selected2 || encoded(selected2) !== encoded(input))
7319
+ throw new ContractError("conflict");
7320
+ result = status;
7321
+ } else
7322
+ result = await launchHost(input, store, foreground, hostUrl, dirname10(dirname10(fileURLToPath(hostUrl))), env, network);
7323
+ if (!foreground && result && typeof result === "object" && "status" in result && result.status === "alive") {
7324
+ const reply = closed(await nativeCall(store, "token-link", true), [
7325
+ "link"
7326
+ ]);
7327
+ process.stderr.write(`Cadence workbench: ${text(reply.link, 4096, true)}
7328
+ No project or default workspace was created.
7329
+ `);
7330
+ }
7331
+ } else if (operation === "recover" && (recovery === "inspect" || recovery === "resolve"))
7332
+ result = await localGatewayRecovery(store, recovery);
7333
+ else if (operation === "stop")
7334
+ result = await stopService(store);
7335
+ else if (operation === "status" || operation === "diagnose")
7336
+ result = await serviceStatus(store, operation === "diagnose");
7337
+ else {
7338
+ if (operation === "token-link" && !reveal)
7339
+ throw new ContractError("forbidden");
7340
+ result = await nativeCall(store, operation, reveal);
7341
+ }
7342
+ if (result !== null)
7343
+ process.stdout.write(`${JSON.stringify(result)}
7344
+ `);
7345
+ if (result && typeof result === "object" && "status" in result && ["unconfirmed", "unavailable", "quarantined", "refused"].includes(String(result.status)))
7346
+ process.exitCode = managementExitCode(result, foreground);
7347
+ if (result && typeof result === "object" && "status" in result && ["unavailable", "quarantined", "refused", "unconfirmed"].includes(String(result.status)))
7348
+ process.stderr.write(`Preserve state; inspect locally: cadence workbench recover inspect --server-config ${JSON.stringify(config)} --state-home ${JSON.stringify(store.stateHome)}. Eligible recovery requires recover resolve in a local terminal; it never starts work.
7349
+ `);
7350
+ }
7351
+
7352
+ // src/service/launchd.ts
7353
+ import { rmSync as rmSync2 } from "node:fs";
7354
+ import { dirname as dirname11, join as join13 } from "node:path";
7355
+ var LABEL = "com.abelxiaoxing.cadence";
7356
+ var MARKER2 = `<?xml version="1.0" encoding="UTF-8"?>
7357
+ <!-- Generated by @abelxiaoxing/cadence`;
7358
+ function plistPath(s) {
7359
+ return join13(s.home, "Library", "LaunchAgents", `${LABEL}.plist`);
7360
+ }
7361
+ var xml = (value) => checked(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
7362
+ function buildPlist(u) {
7363
+ const string = (value) => ` <string>${xml(value)}</string>`;
7364
+ return [
7365
+ '<?xml version="1.0" encoding="UTF-8"?>',
7366
+ "<!-- Generated by @abelxiaoxing/cadence; `cadence server install` and `restart` rewrite this file. -->",
7367
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
7368
+ '<plist version="1.0">',
7369
+ "<dict>",
7370
+ "\t<key>Label</key>",
7371
+ ` <string>${LABEL}</string>`,
7372
+ "\t<key>ProgramArguments</key>",
7373
+ "\t<array>",
7374
+ ...[
7375
+ u.node,
7376
+ u.cli,
7377
+ ...u.workspace === null ? ["workbench", "start", "--foreground"] : ["start", "--foreground", "--workspace", u.workspace],
7378
+ "--server-config",
7379
+ u.serverConfig
7380
+ ].map(string),
7381
+ "\t</array>",
7382
+ ...u.workspace === null ? ["<!-- Cadence workbench gateway; no project workspace -->"] : [],
7383
+ "\t<key>WorkingDirectory</key>",
7384
+ ` <string>${xml(u.workspace ?? dirname11(dirname11(u.cli)))}</string>`,
7385
+ "\t<key>EnvironmentVariables</key>",
7386
+ "\t<dict>",
7387
+ ...u.environment.flatMap(([k, v]) => [
7388
+ ` <key>${xml(k)}</key>`,
7389
+ string(v)
7390
+ ]),
7391
+ "\t</dict>",
7392
+ "\t<key>RunAtLoad</key>",
7393
+ "\t<true/>",
7394
+ "\t<key>ProcessType</key>",
7395
+ "\t<string>Background</string>",
7396
+ "\t<key>StandardOutPath</key>",
7397
+ ` <string>${xml(u.log)}</string>`,
7398
+ "\t<key>StandardErrorPath</key>",
7399
+ ` <string>${xml(u.log)}</string>`,
7400
+ "</dict>",
7401
+ "</plist>",
7402
+ ""
7403
+ ].join(`
7404
+ `);
7405
+ }
7406
+ function plistWorkspace(text2) {
7407
+ if (text2?.includes("<!-- Cadence workbench gateway; no project workspace -->"))
7408
+ return null;
7409
+ const m = text2?.match(/^\t<key>WorkingDirectory<\/key>\n\t<string>([^<]*)<\/string>$/m);
7410
+ return m?.[1].replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
7411
+ }
7412
+ var domain = (s) => `gui/${s.uid}`;
7413
+ var target = (s) => `${domain(s)}/${LABEL}`;
7414
+ var loaded = (s) => s.launchctl(["print", target(s)]).status === 0;
7415
+ function disabled(s) {
7416
+ return new RegExp(`"${LABEL.replaceAll(".", "\\.")}" => (true|disabled)`).test(s.launchctl(["print-disabled", domain(s)]).stdout);
7417
+ }
7418
+ async function configureLaunchAgent(s, mode, requested) {
7419
+ if (s.uid === undefined)
7420
+ return { status: "unavailable" };
7421
+ const path2 = plistPath(s);
7422
+ const previous = readGenerated(path2, MARKER2);
7423
+ if (previous === null)
7424
+ return { status: "foreign", unit: path2 };
7425
+ if (mode === "restart" && previous === undefined)
7426
+ return { status: "absent" };
7427
+ const sdk = sdkPackages(s.root);
7428
+ const kept = plistWorkspace(previous);
7429
+ const { workspace, redirectedFrom } = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : kept);
7430
+ if (workspace !== null && !sdk)
7431
+ return { status: "missing-pi-sdk" };
7432
+ const refused2 = await settle(s, kept);
7433
+ if (refused2)
7434
+ return { ...refused2, unit: path2 };
7435
+ if (loaded(s))
7436
+ s.launchctl(["bootout", target(s)]);
7437
+ write(path2, buildPlist({
7438
+ node: s.node,
7439
+ cli: join13(s.root, "src", "service-cli.mjs"),
7440
+ workspace,
7441
+ serverConfig: serverConfigPath(s.home),
7442
+ environment: serviceEnvironment(s),
7443
+ log: join13(s.home, "Library", "Logs", "cadence.log")
7444
+ }));
7445
+ const result = {
7446
+ unit: path2,
7447
+ workspace,
7448
+ ...redirectedFrom ? { redirectedFrom } : {}
7449
+ };
7450
+ if (mode === "install")
7451
+ s.launchctl(["enable", target(s)]);
7452
+ else if (disabled(s))
7453
+ return { status: "disabled", ...result };
7454
+ if (workspace !== null && !regularFile(join13(agentDirectory(s), "auth.json")))
7455
+ return { status: "waiting-for-pi-login", ...result };
7456
+ const started = s.launchctl(["bootstrap", domain(s), path2]).status === 0;
7457
+ return { status: started ? "started" : "failed", ...result };
7458
+ }
7459
+ async function stopLaunchAgent(s) {
7460
+ const path2 = plistPath(s);
7461
+ const previous = readGenerated(path2, MARKER2);
7462
+ if (previous === null)
7463
+ return { status: "foreign", unit: path2 };
7464
+ if (previous === undefined)
7465
+ return { status: "absent" };
7466
+ const workspace = plistWorkspace(previous);
7467
+ const refused2 = await settle(s, workspace);
7468
+ return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
7469
+ }
7470
+ async function removeLaunchAgent(s) {
7471
+ const path2 = plistPath(s);
7472
+ const previous = readGenerated(path2, MARKER2);
7473
+ if (previous === null)
7474
+ return { status: "foreign", unit: path2 };
7475
+ if (previous === undefined)
7476
+ return { status: "absent" };
7477
+ const refused2 = await settle(s, plistWorkspace(previous));
7478
+ if (refused2)
7479
+ return { ...refused2, unit: path2 };
7480
+ if (s.uid !== undefined && loaded(s))
7481
+ s.launchctl(["bootout", target(s)]);
7482
+ rmSync2(path2);
7483
+ return { status: "removed", unit: path2 };
7484
+ }
7485
+ function launchAgentStatus(s) {
7486
+ const path2 = plistPath(s);
7487
+ const previous = readGenerated(path2, MARKER2);
7488
+ if (previous === null)
7489
+ return { status: "foreign", unit: path2 };
7490
+ if (previous === undefined)
7491
+ return { status: "absent" };
7492
+ const printed = s.uid === undefined ? undefined : s.launchctl(["print", target(s)]);
7493
+ return {
7494
+ status: printed?.status !== 0 ? "unloaded" : /^\s*state = running$/m.test(printed.stdout) ? "active" : "inactive",
7495
+ enabled: s.uid !== undefined && disabled(s) ? "disabled" : "enabled",
7496
+ unit: path2,
7497
+ workspace: plistWorkspace(previous)
6795
7498
  };
6796
- walk(files.directory, 0);
6797
- files.assertFormat();
6798
- return digest.digest("hex");
6799
7499
  }
6800
7500
 
6801
- class HistoryRecovery {
6802
- coordination;
6803
- committed = new WeakMap;
6804
- constructor(coordination) {
6805
- this.coordination = coordination;
6806
- }
6807
- files() {
6808
- const files = HistoryFiles.open(this.coordination);
6809
- if (!files)
6810
- throw new ContractError("unavailable");
6811
- return files;
6812
- }
6813
- assertCommitted(fact) {
6814
- const assert = this.committed.get(fact);
6815
- if (!assert)
6816
- throw new ContractError("conflict");
6817
- assert();
6818
- }
6819
- async inspect(epoch) {
6820
- decodeId(epoch);
6821
- const files = this.files(), before = digestHistory(files), state = emptyScan();
6822
- const auth = new Set, processes = new Set, publication = new Set, unfinished = new Set, metadata = new Set, runs = new Map;
6823
- for (const batch of files.scan(state))
6824
- for (const row of batch.rows) {
6825
- const v = row.value;
6826
- if (v.kind === "recovery") {
6827
- auth.clear();
6828
- processes.clear();
6829
- publication.clear();
6830
- unfinished.clear();
6831
- metadata.clear();
6832
- runs.clear();
6833
- }
6834
- if (v.kind === "admission" && v.intent.identity.kind === "action") {
6835
- if (v.intent.runId) {
6836
- if (runs.size >= LIMITS.episodes || runs.has(v.intent.runId))
6837
- throw new ContractError("corrupt-selected");
6838
- runs.set(v.intent.runId, new RunState(v.intent.runId));
6839
- } else
6840
- metadata.add(v.intent.operationId);
6841
- }
6842
- for (const [id, state2] of runs) {
6843
- state2.apply(row, batch.marker.last);
6844
- if (state2.drained)
6845
- runs.delete(id);
6846
- }
6847
- if (v.kind === "capture" || v.kind === "owner")
6848
- for (const e of v.events) {
6849
- if (e.kind === "capture" && e.value.event.kind === "detail-begin" && v.kind === "capture")
6850
- unfinished.add(`worker/${v.link.childRunId}/${e.value.event.detailId}`);
6851
- if (e.kind === "owner" && e.value.kind === "content-begin" && v.kind === "owner")
6852
- unfinished.add(`owner/${v.link.operationId}/${e.value.detailId}`);
6853
- if (e.kind === "end")
6854
- unfinished.delete(`${e.detail.origin.kind}/${e.detail.origin.id}/${e.detail.detailId}`);
6855
- }
6856
- if (v.kind === "auth-preparation")
6857
- auth.add(v.intent.operationId);
6858
- if (v.kind === "auth-settlement")
6859
- auth.delete(v.result.operationId);
6860
- if (v.kind === "launch")
6861
- processes.add(v.link.childRunId);
6862
- if (v.kind === "settlement" && v.completion.native.closure !== "unconfirmed")
6863
- processes.delete(v.link.childRunId);
6864
- if (v.kind === "owner") {
6865
- for (const e of v.events)
6866
- if (e.kind === "owner" && e.value.kind === "fact") {
6867
- const f = e.value.fact;
6868
- if (f.kind === "check-start")
6869
- processes.add(f.checkId);
6870
- if (f.kind === "check-settled" && f.native.closure !== "unconfirmed")
6871
- processes.delete(f.checkId);
6872
- if (f.kind === "publication-intent")
6873
- publication.add(f.publicationId);
6874
- if (f.kind === "publication-settled") {
6875
- if (f.status === "published") {
6876
- publication.delete(f.publicationId);
6877
- metadata.delete(v.link.operationId);
6878
- } else
6879
- publication.add(f.publicationId);
6880
- }
6881
- }
6882
- }
6883
- }
6884
- if (before !== digestHistory(files))
6885
- throw new ContractError("conflict");
6886
- return {
6887
- digest: before,
6888
- processes: processes.size ? "unknown" : "externally-resolved",
6889
- credentials: auth.size ? "unknown" : "independently-resolved",
6890
- storage: state.tail || publication.size || unfinished.size || metadata.size || runs.size ? "unknown" : "independently-resolved"
6891
- };
6892
- }
6893
- async append(confirmation, observeWrite) {
6894
- if (typeof confirmation.assertLocalAuthority !== "function" || confirmation.acknowledgeEscapedProcesses !== true || confirmation.processes !== "externally-resolved" || confirmation.credentials !== "independently-resolved" || confirmation.storage !== "independently-resolved")
6895
- throw new ContractError("forbidden");
6896
- const expected = structuredClone(confirmation.expected), files = this.files();
6897
- files.assertWritable();
6898
- if (!expected.history || expected.owner !== "absent")
6899
- throw new ContractError("quarantine");
6900
- const current = await inspectRecovery(this.coordination, this);
6901
- if (JSON.stringify(current) !== JSON.stringify(expected))
6902
- throw new ContractError("conflict");
6903
- const state = files.inspect(), recoveryId = randomUUID7();
6904
- const bridge = {
6905
- kind: "recovery",
6906
- recoveryId,
6907
- epoch: expected.epoch,
6908
- coordinationDigest: expected.coordinationDigest,
6909
- historyDigest: expected.history.digest,
6910
- previous: {
6911
- segment: state.segment,
6912
- bytes: state.bytes,
6913
- sha256: state.identity ?? hash(""),
6914
- committedBytes: state.committedBytes ?? state.bytes,
6915
- committedSeq: state.committedSeq,
6916
- highSeq: state.highSeq ?? state.committedSeq
6917
- },
6918
- acknowledgments: { processes: true, credentials: true, storage: true }
6919
- };
6920
- const path2 = files.segmentPath(state.segment + 1);
6921
- const assertCurrent = async () => {
6922
- await confirmation.assertLocalAuthority(structuredClone(expected));
6923
- const selected2 = await inspectRecovery(this.coordination);
6924
- if (selected2.owner !== "absent")
6925
- throw new ContractError("quarantine");
6926
- if (selected2.epoch !== expected.epoch || selected2.coordinationDigest !== expected.coordinationDigest || digestHistory(files, path2) !== expected.history?.digest || this.coordination.recoverySnapshot().digest !== expected.coordinationDigest)
6927
- throw new ContractError("conflict");
6928
- };
6929
- await confirmation.assertLocalAuthority(structuredClone(expected));
6930
- if (digestHistory(files) !== expected.history.digest)
6931
- throw new ContractError("conflict");
6932
- const row = {
6933
- kind: "record",
6934
- id: randomUUID7(),
6935
- seq: String(BigInt(bridge.previous.highSeq) + 1n),
6936
- value: bridge
6937
- };
6938
- let quota = LIMITS.checkoutQuotaBytes;
6939
- for (const batch of files.scan())
6940
- for (const row2 of batch.rows)
6941
- if (row2.value.kind === "admission" && row2.value.policy)
6942
- quota = row2.value.policy.quotaBytes;
6943
- const bytes = Buffer.byteLength(files.encodeBatch(state, [row]).body + files.encodeBatch(state, [row]).trailer);
6944
- if (files.usage() + bytes > quota || files.freeBytes() < LIMITS.freeSpaceFloorBytes + bytes)
6945
- throw new ContractError("recording-exhausted");
6946
- const after = await files.appendRecovery(state, row, assertCurrent, observeWrite);
6947
- const assert = () => {
6948
- files.assertWritable();
6949
- files.assertRecoveryIdle();
6950
- files.files.assert(path2, after.snapshot, LIMITS.segmentBytes);
6951
- if (digestHistory(files, path2) !== expected.history?.digest)
6952
- throw new ContractError("conflict");
6953
- };
6954
- assert();
6955
- const fact = {
6956
- recoveryId,
6957
- committedSeq: after.state.committedSeq,
6958
- epoch: expected.epoch,
6959
- coordinationDigest: expected.coordinationDigest,
6960
- historyDigest: expected.history.digest,
6961
- segmentIdentity: after.state.identity ?? ""
6962
- };
6963
- this.committed.set(fact, assert);
6964
- return fact;
6965
- }
7501
+ // src/service/login-run.ts
7502
+ import { rmSync as rmSync3 } from "node:fs";
7503
+ import { dirname as dirname12, isAbsolute as isAbsolute7, join as join14 } from "node:path";
7504
+ var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
7505
+ var APPROVED_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run";
7506
+ var RUN_VALUE = "Cadence";
7507
+ var MARKER3 = "' Generated by @abelxiaoxing/cadence";
7508
+ function launcherPath(s) {
7509
+ const local = s.env.LOCALAPPDATA;
7510
+ return join14(local && isAbsolute7(local) ? local : join14(s.home, "AppData", "Local"), "cadence-server", "cadence.vbs");
6966
7511
  }
6967
-
6968
- // src/service/recovery-cli.ts
6969
- var warning = "PID absence is not proof that descendants stopped or credential writes settled. Inspect/stop old processes externally, independently inspect the selected Pi credential store and SDK locks, and inspect retained history/storage. Cadence does not kill, repair, reset, migrate or certify those resources. Unknown historical outcomes remain unknown. A new Start is required.";
6970
- function native() {
6971
- if (!isatty(0) || !isatty(1) || !process.stdin.isTTY || !process.stdout.isTTY)
6972
- throw new ContractError("forbidden");
7512
+ var literal2 = (value) => `"${checked(value).replaceAll('"', '""')}"`;
7513
+ function argument2(value) {
7514
+ if (checked(value).includes('"'))
7515
+ throw new ContractError("invalid");
7516
+ return `"${value}"`;
6973
7517
  }
6974
- async function confirm(expected) {
6975
- native();
6976
- if (process.stdin.readableLength)
6977
- throw new ContractError("forbidden");
6978
- const identity = JSON.stringify(expected);
6979
- const deadline = performance.now() + LIMITS.grantMs;
6980
- const prompts = [
6981
- [
6982
- "Confirm old processes stopped externally; acknowledge escaped-descendant uncertainty. Type processes",
6983
- "processes"
6984
- ],
6985
- [
6986
- "Confirm independent credential/refresh/writeback inspection; PID absence does not settle auth. Type credentials",
6987
- "credentials"
6988
- ],
6989
- [
6990
- "Confirm independent storage inspection; preserve damaged bytes and unknown outcomes. Type storage",
6991
- "storage"
6992
- ],
6993
- [
6994
- `Release ONLY the displayed unchanged service-owned fences. Type resolve ${expected.epoch}`,
6995
- `resolve ${expected.epoch}`
6996
- ]
6997
- ];
6998
- await new Promise((resolve7, reject) => {
6999
- let index = 0, line = "", done = false;
7000
- const raw = process.stdin.isRaw;
7001
- const finish = (ok) => {
7002
- if (done)
7003
- return;
7004
- done = true;
7005
- clearTimeout(timer);
7006
- process.stdin.removeListener("data", input);
7007
- process.stdin.removeListener("end", fail);
7008
- process.stdin.removeListener("error", fail);
7009
- process.stdin.setRawMode(raw);
7010
- process.stdin.pause();
7011
- process.stdout.write(`\x1B[?2004l
7012
- `);
7013
- if (ok)
7014
- resolve7();
7015
- else
7016
- reject(new ContractError("forbidden"));
7017
- };
7018
- const fail = () => finish(false);
7019
- const show = () => process.stdout.write(`${prompts[index]?.[0]}: `);
7020
- const input = (chunk) => {
7021
- if (performance.now() >= deadline || chunk.length > 128)
7022
- return fail();
7023
- for (let n = 0;n < chunk.length; n++) {
7024
- const byte = chunk[n];
7025
- if (byte === 13 || byte === 10) {
7026
- if (n !== chunk.length - 1 || line !== prompts[index]?.[1])
7027
- return fail();
7028
- line = "";
7029
- process.stdout.write(`
7518
+ function buildLauncher(u) {
7519
+ const command = `${argument2(u.node)} ${argument2(u.cli)} ${u.workspace === null ? "workbench start" : `start --workspace ${argument2(u.workspace)}`} --server-config ${argument2(u.serverConfig)}`;
7520
+ return [
7521
+ `${MARKER3}; \`cadence server install\` and \`restart\` rewrite this file.`,
7522
+ `' Started at login by ${RUN_KEY}\\${RUN_VALUE}; window style 0 hides the console.`,
7523
+ 'Set shell = CreateObject("WScript.Shell")',
7524
+ ...u.environment.length ? ['Set env = shell.Environment("Process")'] : [],
7525
+ ...u.environment.map(([k, v]) => `env(${literal2(k)}) = ${literal2(v)}`),
7526
+ ...u.workspace === null ? ["' Cadence workbench gateway; no project workspace"] : [],
7527
+ `shell.CurrentDirectory = ${literal2(u.workspace ?? dirname12(dirname12(u.cli)))}`,
7528
+ `shell.Run ${literal2(command)}, 0, False`,
7529
+ ""
7530
+ ].join(`\r
7030
7531
  `);
7031
- if (++index === prompts.length)
7032
- return finish(true);
7033
- show();
7034
- } else if (byte === 127) {
7035
- line = line.slice(0, -1);
7036
- } else if (byte !== undefined && byte >= 32 && byte <= 126 && line.length < 80) {
7037
- line += String.fromCharCode(byte);
7038
- } else
7039
- return fail();
7040
- }
7041
- };
7042
- const timer = setTimeout(fail, LIMITS.grantMs);
7043
- process.stdin.setRawMode(true);
7044
- process.stdout.write("\x1B[?2004h");
7045
- process.stdin.on("data", input).once("end", fail).once("error", fail).resume();
7046
- show();
7047
- });
7048
- return () => {
7049
- native();
7050
- if (performance.now() >= deadline || JSON.stringify(expected) !== identity)
7051
- throw new ContractError("stale");
7532
+ }
7533
+ var utf16 = (text2) => Buffer.concat([Buffer.from([255, 254]), Buffer.from(text2, "utf16le")]);
7534
+ function launcherWorkspace(text2) {
7535
+ if (text2?.includes("' Cadence workbench gateway; no project workspace"))
7536
+ return null;
7537
+ return text2?.match(/^shell\.CurrentDirectory = "((?:[^"]|"")*)"\r?$/m)?.[1].replaceAll('""', '"');
7538
+ }
7539
+ var runEntry = (path2, s) => `"${wscript(s.env)}" "${path2}"`;
7540
+ var present = (s, key) => s.reg(["query", key, "/v", RUN_VALUE]).status === 0;
7541
+ function approvedOff(s) {
7542
+ const r = s.reg(["query", APPROVED_KEY, "/v", RUN_VALUE]);
7543
+ const first = r.status === 0 && /REG_BINARY\s+([0-9A-F]{2})/i.exec(r.stdout);
7544
+ return !!first && (Number.parseInt(first[1], 16) & 1) === 1;
7545
+ }
7546
+ async function configureLoginRun(s, mode, requested) {
7547
+ const path2 = launcherPath(s);
7548
+ const previous = readGenerated(path2, MARKER3);
7549
+ if (previous === null)
7550
+ return { status: "foreign", unit: path2 };
7551
+ if (previous === undefined && present(s, RUN_KEY))
7552
+ return { status: "foreign", unit: `${RUN_KEY}\\${RUN_VALUE}` };
7553
+ if (mode === "restart" && previous === undefined)
7554
+ return { status: "absent" };
7555
+ const sdk = sdkPackages(s.root);
7556
+ const kept = launcherWorkspace(previous);
7557
+ const { workspace, redirectedFrom } = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : kept);
7558
+ if (workspace !== null && !sdk)
7559
+ return { status: "missing-pi-sdk" };
7560
+ const refused2 = await settle(s, kept);
7561
+ if (refused2)
7562
+ return { ...refused2, unit: path2 };
7563
+ const environment = s.env.PI_CODING_AGENT_DIR ? [["PI_CODING_AGENT_DIR", agentDirectory(s)]] : [];
7564
+ write(path2, utf16(buildLauncher({
7565
+ node: s.node,
7566
+ cli: join14(s.root, "src", "service-cli.mjs"),
7567
+ workspace,
7568
+ serverConfig: serverConfigPath(s.home),
7569
+ environment
7570
+ })));
7571
+ const result = {
7572
+ unit: path2,
7573
+ workspace,
7574
+ ...redirectedFrom ? { redirectedFrom } : {}
7052
7575
  };
7576
+ if (mode === "install") {
7577
+ const entry = ["/v", RUN_VALUE, "/t", "REG_SZ", "/d", runEntry(path2, s)];
7578
+ if (s.reg(["add", RUN_KEY, ...entry, "/f"]).status !== 0)
7579
+ return { status: "failed", ...result };
7580
+ s.reg(["delete", APPROVED_KEY, "/v", RUN_VALUE, "/f"]);
7581
+ }
7582
+ if (workspace !== null && !regularFile(join14(agentDirectory(s), "auth.json")))
7583
+ return { status: "waiting-for-pi-login", ...result };
7584
+ return { status: s.launch(path2) ? "started" : "failed", ...result };
7053
7585
  }
7054
- async function localRecovery(c, operation) {
7055
- if (operation === "resolve")
7056
- native();
7057
- const history = new HistoryRecovery(c);
7058
- const expected = await inspectRecovery(c, history);
7059
- const registration2 = c.recoverySnapshot().registration;
7060
- const view = {
7061
- ...expected,
7062
- process: {
7063
- pid: registration2.pid,
7064
- host: diagnosticText(registration2.host, 255)
7065
- },
7066
- notice: warning
7586
+ async function stopLoginRun(s) {
7587
+ const path2 = launcherPath(s);
7588
+ const previous = readGenerated(path2, MARKER3);
7589
+ if (previous === null)
7590
+ return { status: "foreign", unit: path2 };
7591
+ if (previous === undefined)
7592
+ return { status: "absent" };
7593
+ const workspace = launcherWorkspace(previous);
7594
+ const refused2 = await settle(s, workspace);
7595
+ return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
7596
+ }
7597
+ async function removeLoginRun(s) {
7598
+ const path2 = launcherPath(s);
7599
+ const previous = readGenerated(path2, MARKER3);
7600
+ if (previous === null)
7601
+ return { status: "foreign", unit: path2 };
7602
+ if (previous === undefined)
7603
+ return { status: "absent" };
7604
+ const refused2 = await settle(s, launcherWorkspace(previous));
7605
+ if (refused2)
7606
+ return { ...refused2, unit: path2 };
7607
+ for (const key of [RUN_KEY, APPROVED_KEY])
7608
+ if (present(s, key))
7609
+ s.reg(["delete", key, "/v", RUN_VALUE, "/f"]);
7610
+ rmSync3(path2);
7611
+ return { status: "removed", unit: path2 };
7612
+ }
7613
+ async function loginRunStatus(s) {
7614
+ const path2 = launcherPath(s);
7615
+ const previous = readGenerated(path2, MARKER3);
7616
+ if (previous === null)
7617
+ return { status: "foreign", unit: path2 };
7618
+ if (previous === undefined)
7619
+ return { status: "absent" };
7620
+ const workspace = launcherWorkspace(previous);
7621
+ const owner = workspace !== undefined ? await s.ownerStatus(workspace, serverConfigPath(s.home)) : "unavailable";
7622
+ return {
7623
+ status: owner === "alive" ? "active" : "inactive",
7624
+ enabled: present(s, RUN_KEY) && !approvedOff(s) ? "enabled" : "disabled",
7625
+ unit: path2,
7626
+ workspace
7067
7627
  };
7068
- if (operation === "inspect")
7069
- return view;
7070
- process.stdout.write(`${JSON.stringify(view)}
7071
- `);
7072
- if (expected.owner !== "absent")
7073
- throw new ContractError("quarantine");
7074
- const assertFresh = await confirm(expected);
7075
- return resolveRecovery(c, {
7076
- expected,
7077
- acknowledgeEscapedProcesses: true,
7078
- processes: "externally-resolved",
7079
- credentials: "independently-resolved",
7080
- storage: "independently-resolved",
7081
- assertLocalAuthority: (choice) => {
7082
- assertFresh();
7083
- if (JSON.stringify(choice) !== JSON.stringify(expected))
7084
- throw new ContractError("stale");
7085
- }
7086
- }, history);
7087
7628
  }
7088
7629
 
7089
7630
  // src/service/system-service.ts
7090
- import { existsSync as existsSync2, realpathSync as realpathSync5, rmSync as rmSync4 } from "node:fs";
7091
- import { isAbsolute as isAbsolute8, join as join14 } from "node:path";
7631
+ import { existsSync as existsSync2, realpathSync as realpathSync4, rmSync as rmSync4 } from "node:fs";
7632
+ import { isAbsolute as isAbsolute8, join as join15 } from "node:path";
7092
7633
  var MARKER4 = "# Generated by @abelxiaoxing/cadence";
7093
7634
  function boundary(s) {
7094
7635
  if (s.platform !== "linux" || s.scope !== "system")
@@ -7130,8 +7671,8 @@ function sameStateEnvironment(s, previous) {
7130
7671
  return false;
7131
7672
  values.set(key, value.slice(at + 1).replaceAll("%%", "%"));
7132
7673
  }
7133
- const state = s.env.XDG_STATE_HOME;
7134
- return values.get("HOME") === s.home && values.get("XDG_STATE_HOME") === (state && isAbsolute8(state) ? state : undefined);
7674
+ const state2 = s.env.XDG_STATE_HOME;
7675
+ return values.get("HOME") === s.home && values.get("XDG_STATE_HOME") === (state2 && isAbsolute8(state2) ? state2 : undefined);
7135
7676
  } catch {
7136
7677
  return false;
7137
7678
  }
@@ -7169,12 +7710,12 @@ async function configureSystemService(s, mode, requested) {
7169
7710
  const selected2 = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : unitWorkspace(previous), false);
7170
7711
  if (selected2.workspace !== null && !sdk)
7171
7712
  return { status: "missing-pi-sdk", scope: "system" };
7172
- const selectedReal = selected2.workspace && existsSync2(selected2.workspace) ? realpathSync5(selected2.workspace) : undefined;
7713
+ const selectedReal = selected2.workspace && existsSync2(selected2.workspace) ? realpathSync4(selected2.workspace) : undefined;
7173
7714
  const agentDir = agentDirectory(s);
7174
7715
  const content = buildUnit({
7175
7716
  system: true,
7176
7717
  node: s.node,
7177
- cli: join14(s.root, "src", "service-cli.mjs"),
7718
+ cli: join15(s.root, "src", "service-cli.mjs"),
7178
7719
  workspace: selected2.workspace,
7179
7720
  serverConfig: serviceConfigPath(s),
7180
7721
  agentDir,
@@ -7206,7 +7747,7 @@ async function configureSystemService(s, mode, requested) {
7206
7747
  return { status: "foreign", ...result };
7207
7748
  if (selectedReal && selected2.workspace) {
7208
7749
  try {
7209
- if (realpathSync5(selected2.workspace) !== selectedReal)
7750
+ if (realpathSync4(selected2.workspace) !== selectedReal)
7210
7751
  return { status: "workspace-changed", ...result };
7211
7752
  } catch {
7212
7753
  return { status: "workspace-changed", ...result };
@@ -7223,7 +7764,7 @@ async function configureSystemService(s, mode, requested) {
7223
7764
  return { status: "failed", ...result, ...selected2 };
7224
7765
  if (mode === "install" && s.systemctl(["enable", s.unit]).status !== 0)
7225
7766
  return { status: "failed", ...result, ...selected2 };
7226
- if (selected2.workspace !== null && !regularFile(join14(agentDir, "auth.json")))
7767
+ if (selected2.workspace !== null && !regularFile(join15(agentDir, "auth.json")))
7227
7768
  return { status: "waiting-for-pi-login", ...result, ...selected2 };
7228
7769
  s.systemctl(["reset-failed", s.unit]);
7229
7770
  return {
@@ -7355,6 +7896,13 @@ function nativeCall(c, operation, reveal = false, controlBody) {
7355
7896
  }
7356
7897
  var pause = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
7357
7898
  async function serviceStatus(c, diagnose = false) {
7899
+ const registryLock = c.registryLockDiagnostic?.();
7900
+ if (registryLock)
7901
+ return {
7902
+ status: "unavailable",
7903
+ ...registryLock,
7904
+ recovery: "cadence recover inspect --workspace <project> [--state-home <state-home>]"
7905
+ };
7358
7906
  const observed2 = c.inspect();
7359
7907
  if (observed2.status === "absent" || observed2.status === "stopped")
7360
7908
  return observed2;
@@ -7378,7 +7926,12 @@ async function stopService(c) {
7378
7926
  }
7379
7927
  const deadline = performance.now() + LIMITS.stopWaitMs;
7380
7928
  while (performance.now() < deadline) {
7381
- const observed2 = c.inspect();
7929
+ let observed2;
7930
+ try {
7931
+ observed2 = c.inspect();
7932
+ } catch {
7933
+ observed2 = { status: "unavailable" };
7934
+ }
7382
7935
  if ("epoch" in observed2 && observed2.epoch === epoch && observed2.status === "stopped")
7383
7936
  return { status: "stopped", epoch };
7384
7937
  if (observed2.status === "quarantined")
@@ -7392,11 +7945,34 @@ async function stopService(c) {
7392
7945
  function failedStatus(result) {
7393
7946
  return !!(result && typeof result === "object" && ("status" in result) && ["unconfirmed", "unavailable", "quarantined", "refused"].includes(String(result.status)));
7394
7947
  }
7948
+ function managementExitCode(result, foreground = false) {
7949
+ if (foreground) {
7950
+ try {
7951
+ decodeStartupReport(result);
7952
+ return 78;
7953
+ } catch {}
7954
+ }
7955
+ return 1;
7956
+ }
7957
+ function observedServerResult(result, service) {
7958
+ return {
7959
+ ...result,
7960
+ service,
7961
+ ...result.status === "started" && service !== "alive" ? {
7962
+ managerStatus: result.status,
7963
+ status: service === "quarantined" ? "quarantined" : "unconfirmed"
7964
+ } : {}
7965
+ };
7966
+ }
7395
7967
  function serverConfigHint(error) {
7396
7968
  return `Cadence changed nothing: ${error.path} ${error.reason}.
7397
7969
  Fix it and run the command again; the file holds the WebUI port and token as ${PORT_KEY}=<1-65535> and ${TOKEN_KEY}=<at least 32 characters>, one line each.
7398
7970
  `;
7399
7971
  }
7972
+ function registryGuidance(workspace, stateHome) {
7973
+ return `The shared registry lock blocks project access in this state home. No automatic lock deletion. Inspect locally: cadence recover inspect --workspace ${JSON.stringify(workspace)} --state-home ${JSON.stringify(stateHome)}; eligible dead locks require cadence recover resolve in a local terminal.
7974
+ `;
7975
+ }
7400
7976
  function startupGuidance(result, input) {
7401
7977
  let report;
7402
7978
  try {
@@ -7404,6 +7980,8 @@ function startupGuidance(result, input) {
7404
7980
  } catch {
7405
7981
  return "";
7406
7982
  }
7983
+ if (report.error === "registry-locked")
7984
+ return registryGuidance(input.workspace, input.stateHome);
7407
7985
  const agent = input.config.agentDir;
7408
7986
  const inspect = `Inspect: cadence-service diagnose|recover inspect --workspace ${JSON.stringify(input.workspace)}
7409
7987
  `;
@@ -7415,10 +7993,10 @@ function startupGuidance(result, input) {
7415
7993
  return `Pi's agent directory ${agent} must be an existing real directory (not a symlink) owned by you.
7416
7994
  `;
7417
7995
  case "pi-auth":
7418
- return `${join15(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
7996
+ return `${join16(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
7419
7997
  `;
7420
7998
  case "pi-models":
7421
- return `${join15(agent, "models.json")} must be your own single-link regular file using only fields Cadence's pinned Pi SDK supports; a field only newer Pi understands is refused.
7999
+ return `${join16(agent, "models.json")} must be your own single-link regular file using only fields Cadence's pinned Pi SDK supports; a field only newer Pi understands is refused.
7422
8000
  `;
7423
8001
  case "pi-runtime":
7424
8002
  return `Loading Pi's SDK model runtime failed after the owner registered (optional SDK packages missing, or sources the SDK refuses); it stays quarantined.
@@ -7431,6 +8009,15 @@ ${inspect}` : "";
7431
8009
  async function startService(options, foreground, hostUrl) {
7432
8010
  const network = networkSnapshot(process.env);
7433
8011
  const env = hostEnvironment(process.env);
8012
+ const c = new ServiceCoordination(options.workspace, options.stateHome);
8013
+ if (c.registryLockDiagnostic()) {
8014
+ process.stderr.write(registryGuidance(c.store.root, c.stateHome));
8015
+ return {
8016
+ status: "refused",
8017
+ error: "registry-locked",
8018
+ stage: "service"
8019
+ };
8020
+ }
7434
8021
  let input;
7435
8022
  try {
7436
8023
  input = decodeHostInput(selectStart(options, process.env));
@@ -7443,7 +8030,6 @@ async function startService(options, foreground, hostUrl) {
7443
8030
  `;
7444
8031
  if (Buffer.byteLength(serialized) > STARTUP_INPUT_BYTES)
7445
8032
  throw new ContractError("oversize");
7446
- const c = new ServiceCoordination(input.workspace, input.stateHome);
7447
8033
  const existing = await serviceStatus(c);
7448
8034
  if (existing && typeof existing === "object" && "status" in existing && existing.status === "alive") {
7449
8035
  const running = readHostConfig(c);
@@ -7540,6 +8126,8 @@ async function launchHost(input, c, foreground, hostUrl, cwd, env, network) {
7540
8126
  process.stdout.write(`${JSON.stringify(result)}
7541
8127
  `);
7542
8128
  await closure;
8129
+ if (failedStatus(result))
8130
+ process.exitCode = managementExitCode(result, true);
7543
8131
  return null;
7544
8132
  }
7545
8133
  return result;
@@ -7626,7 +8214,7 @@ async function serverAction(s, action, requested, linger) {
7626
8214
  }
7627
8215
  }
7628
8216
  function serverGuidance(s, action, result, link) {
7629
- const cli = piUserInstall(s) ? `node ${JSON.stringify(join15(s.root, "src", "service-cli.mjs"))}` : "cadence";
8217
+ const cli = piUserInstall(s) ? `node ${JSON.stringify(join16(s.root, "src", "service-cli.mjs"))}` : "cadence";
7630
8218
  const note = result.redirectedFrom ? `Cadence workspace: ${result.workspace} (${result.redirectedFrom} contains Cadence's private state)
7631
8219
  ` : "";
7632
8220
  return note + serverStatusGuidance(s, action, result, cli, link);
@@ -7658,6 +8246,11 @@ function managerDetail(s) {
7658
8246
  }
7659
8247
  function serverStatusGuidance(s, action, result, cli, link) {
7660
8248
  const workspace = JSON.stringify(result.workspace ?? "");
8249
+ if (result.workspace === null && ["quarantined", "unconfirmed", "unavailable"].includes(result.status))
8250
+ return `Cadence workbench is not ready (${result.status}); no successful startup or Stop is inferred.
8251
+ Inspect locally: ${cli} workbench recover inspect --server-config ${JSON.stringify(serviceConfigPath(s))}
8252
+ Use the definition's same account and state home; eligible recovery requires recover resolve in a local terminal. Preserve locks and history.
8253
+ `;
7661
8254
  if (result.workspace === null && result.status === "started")
7662
8255
  return `Cadence unified workbench: ${link ?? result.endpoint ?? result.service ?? "unconfirmed"}
7663
8256
  No default workspace was created. Projects are opened explicitly.
@@ -7688,7 +8281,7 @@ ${manage}
7688
8281
  ${linger}`;
7689
8282
  }
7690
8283
  case "waiting-for-pi-login":
7691
- return `Cadence is registered for ${result.workspace}; it starts once Pi has ${join15(agentDirectory(s), "auth.json")}.
8284
+ return `Cadence is registered for ${result.workspace}; it starts once Pi has ${join16(agentDirectory(s), "auth.json")}.
7692
8285
  Log in to Pi, then: ${cli} server restart${flags}
7693
8286
  ${config}
7694
8287
  ${lingerGuidance(result.linger)}`;
@@ -7734,7 +8327,10 @@ ${lingerGuidance(result.linger)}`;
7734
8327
  `;
7735
8328
  case "unconfirmed":
7736
8329
  case "quarantined":
7737
- return `Cadence changed nothing: its owner's orderly Stop was not confirmed (${result.status}). Inspect: ${cli} diagnose --workspace ${workspace}
8330
+ return `Cadence owner reports ${result.status}; successful startup or orderly Stop is not confirmed.
8331
+ Inspect: ${cli} diagnose --workspace ${workspace}
8332
+ Local recovery: ${cli} recover inspect --workspace ${workspace}
8333
+ Preserve uncertain state; recovery requires separate fresh terminal confirmation.
7738
8334
  `;
7739
8335
  case "failed":
7740
8336
  return `Could not register or start Cadence's service; ${managerDetail(s)}.
@@ -7838,9 +8434,26 @@ async function serverMain(argv, hostUrl) {
7838
8434
  }
7839
8435
  let link;
7840
8436
  if (result.status === "started" && result.workspace !== undefined) {
7841
- Object.assign(result, await observeStarted(result.workspace, serviceConfigPath(s)));
8437
+ const workspace = result.workspace;
8438
+ const observation = await observeStarted(workspace, serviceConfigPath(s));
8439
+ result = {
8440
+ ...observedServerResult(result, observation.service),
8441
+ ...observation
8442
+ };
7842
8443
  if (result.service === "alive")
7843
- link = await privateLink(result.workspace, undefined, serviceConfigPath(s));
8444
+ link = await privateLink(workspace, undefined, serviceConfigPath(s));
8445
+ }
8446
+ if (action === "status" && result.workspace !== undefined && ["active", "activating", "failed"].includes(result.status)) {
8447
+ const target2 = result.workspace === null ? new GatewayStore(serviceConfigPath(s)) : new ServiceCoordination(result.workspace);
8448
+ const observed2 = await serviceStatus(target2);
8449
+ if (observed2 && typeof observed2 === "object" && "status" in observed2) {
8450
+ const service = String(observed2.status);
8451
+ result = {
8452
+ ...result,
8453
+ service,
8454
+ ...service === "quarantined" ? { managerStatus: result.status, status: service } : {}
8455
+ };
8456
+ }
7844
8457
  }
7845
8458
  process.stderr.write(serverGuidance(s, action, result, link));
7846
8459
  process.stdout.write(`${JSON.stringify(result)}
@@ -7891,7 +8504,7 @@ async function projectMain(argv, hostUrl) {
7891
8504
  mkdirSync6(options.workspace, { recursive: true });
7892
8505
  effectiveWorkspace(options.workspace, options.stateHome);
7893
8506
  const c = new ServiceCoordination(options.workspace, options.stateHome);
7894
- const kept = readHostConfig(c);
8507
+ const kept = c.registryLockDiagnostic() ? undefined : readHostConfig(c);
7895
8508
  if (!kept)
7896
8509
  options.port = 0;
7897
8510
  const result = await startService(options, false, hostUrl);
@@ -7913,6 +8526,8 @@ async function projectMain(argv, hostUrl) {
7913
8526
  process.exitCode = 1;
7914
8527
  }
7915
8528
  async function managementMain(argv, hostUrl) {
8529
+ const foregroundStart = argv.includes("--foreground") && (argv[0] === "start" || argv[0] === "workbench" && argv[1] === "start");
8530
+ const recoveryCommand = argv[0] === "recover" || argv[0] === "workbench" && argv[1] === "recover";
7916
8531
  try {
7917
8532
  if (!process.versions.node || process.versions.bun)
7918
8533
  throw new ContractError("unsupported-context");
@@ -7927,15 +8542,16 @@ async function managementMain(argv, hostUrl) {
7927
8542
  "diagnose",
7928
8543
  "stop",
7929
8544
  "token-link",
7930
- "token-rotate"
8545
+ "token-rotate",
8546
+ "recover"
7931
8547
  ].includes(command ?? "") && !argv.includes("--workspace")) {
7932
8548
  await gatewayMain([command, ...argv], hostUrl);
7933
8549
  return;
7934
8550
  }
7935
8551
  if (command === "--help" && !argv.length) {
7936
8552
  process.stdout.write(`cadence start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--workspace PATH] [--state-home PATH]
7937
- Without --workspace these commands address the unified workbench (except project recovery); no default project directory is created.
7938
- cadence workbench start|status|diagnose|stop|token-link --reveal|token-rotate [--server-config PATH] [--state-home PATH]
8553
+ Without --workspace these commands (including recover) address the unified workbench, never cwd; no default project directory is created. Project recovery requires explicit --workspace PATH. Recovery checks the account-wide registry lock first.
8554
+ cadence workbench start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--server-config PATH] [--state-home PATH]
7939
8555
  cadence server install [--workspace PATH] [--no-linger]|status|restart|stop|uninstall
7940
8556
  Linux server options: --system (root, no linger) or --user; --name NAME selects an independent service; install also accepts --port PORT (required for a new named service). Root defaults to --system. No automatic sudo or profile migration.
7941
8557
  cadence project open [PATH] [--create] [--agent-dir PATH] [--state-home PATH]
@@ -8039,11 +8655,18 @@ Recovery resolve requires this local native terminal, exact unchanged dead-owner
8039
8655
  process.stdout.write(`${JSON.stringify(result)}
8040
8656
  `);
8041
8657
  if (failedStatus(result))
8042
- process.exitCode = 1;
8043
- } catch {
8044
- process.stderr.write(`{"error":"service-refused"}
8658
+ process.exitCode = managementExitCode(result, foreground);
8659
+ } catch (error) {
8660
+ process.stderr.write(`${JSON.stringify({
8661
+ error: "service-refused",
8662
+ ...recoveryCommand ? {
8663
+ code: error instanceof ContractError ? error.code : "unavailable",
8664
+ stage: "recovery",
8665
+ notice: "Use workbench recover for account/workbench state or explicit --workspace for an existing project; preserve state on refusal."
8666
+ } : {}
8667
+ })}
8045
8668
  `);
8046
- process.exitCode = 1;
8669
+ process.exitCode = foregroundStart && (error instanceof ContractError || error instanceof ServerConfigRefusal) ? 78 : 1;
8047
8670
  }
8048
8671
  }
8049
8672