@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.
@@ -612,6 +612,7 @@ var ERROR_CODES = [
612
612
  "quarantine",
613
613
  "capacity",
614
614
  "unavailable",
615
+ "registry-locked",
615
616
  "auth-unavailable",
616
617
  "recording-exhausted",
617
618
  "receipt-inconclusive"
@@ -5352,7 +5353,7 @@ function sdkNetworkEnvironment(ambient, scoped) {
5352
5353
 
5353
5354
  // src/service/coordination.ts
5354
5355
  import { randomBytes as randomBytes2, randomUUID as randomUUID4 } from "node:crypto";
5355
- import { opendirSync, realpathSync as realpathSync3 } from "node:fs";
5356
+ import { opendirSync } from "node:fs";
5356
5357
  import { hostname as hostname2 } from "node:os";
5357
5358
  import { dirname as dirname5, isAbsolute as isAbsolute4, join as join6, relative as relative3, sep as sep3 } from "node:path";
5358
5359
  var registrationKeys = [
@@ -5444,6 +5445,21 @@ function names(path, allowed) {
5444
5445
  dir.closeSync();
5445
5446
  }
5446
5447
  }
5448
+ function decodeRegistryLock(snapshot) {
5449
+ const o = closed(json(snapshot), ["schema", "token", "pid", "host"]);
5450
+ return {
5451
+ schema: literal(o.schema, "cadence-service-registry-lock/1"),
5452
+ token: decodeId(o.token),
5453
+ pid: integer(o.pid, 1, 2147483647),
5454
+ host: text(o.host, 255, true)
5455
+ };
5456
+ }
5457
+ var registryNames = [
5458
+ "format.json",
5459
+ "roots.json",
5460
+ "registry.lock",
5461
+ "recovery-v1"
5462
+ ];
5447
5463
  function recoveryOwnerStatus(pid, host) {
5448
5464
  if (host !== hostname2())
5449
5465
  return "ambiguous";
@@ -5473,19 +5489,25 @@ function recoveryDigest(registry, selected) {
5473
5489
  }
5474
5490
 
5475
5491
  class ServiceCoordination {
5476
- store;
5492
+ project;
5477
5493
  files;
5478
- directory;
5479
5494
  registryDirectory;
5480
5495
  stateHome;
5481
5496
  constructor(workspace, stateHome) {
5482
- this.store = new TaskStore(resolveWorkspace(workspace).root, stateHome);
5483
- const namespace = dirname5(this.store.privateRoot);
5497
+ this.project = workspace === null ? undefined : new TaskStore(resolveWorkspace(workspace).root, stateHome);
5498
+ this.stateHome = this.project ? dirname5(dirname5(this.project.privateRoot)) : canonicalAmbientRoot(stateHome ?? defaultStateHome());
5499
+ const namespace = join6(this.stateHome, "cadence-assistant-v1");
5484
5500
  this.files = new PrivateFiles(namespace);
5485
- this.stateHome = dirname5(namespace);
5486
- this.directory = join6(this.store.privateRoot, "service-v1");
5487
5501
  this.registryDirectory = join6(namespace, "service-registry-v1");
5488
5502
  }
5503
+ get store() {
5504
+ if (!this.project)
5505
+ throw new ContractError("forbidden");
5506
+ return this.project;
5507
+ }
5508
+ get directory() {
5509
+ return join6(this.store.privateRoot, "service-v1");
5510
+ }
5489
5511
  path(name) {
5490
5512
  return join6(this.directory, name);
5491
5513
  }
@@ -5495,11 +5517,7 @@ class ServiceCoordination {
5495
5517
  registry() {
5496
5518
  if (!this.files.directory(this.registryDirectory))
5497
5519
  return;
5498
- names(this.registryDirectory, [
5499
- "format.json",
5500
- "roots.json",
5501
- "registry.lock"
5502
- ]);
5520
+ names(this.registryDirectory, registryNames);
5503
5521
  const format = this.files.read(this.registryPath("format.json"));
5504
5522
  const snapshot = this.files.read(this.registryPath("roots.json"), REGISTRY_BYTES);
5505
5523
  if (!format || !snapshot)
@@ -5510,7 +5528,7 @@ class ServiceCoordination {
5510
5528
  const o = closed(json(snapshot, REGISTRY_BYTES), ["schema", "entries"]);
5511
5529
  const entries = list(o.entries, REGISTRY_ENTRIES, decodeReservation);
5512
5530
  for (const [i, row] of entries.entries()) {
5513
- if (row.stateHome !== this.stateHome || realpathSync3(row.root) !== row.root)
5531
+ if (row.stateHome !== this.stateHome || canonicalAmbientRoot(row.root) !== row.root)
5514
5532
  refused();
5515
5533
  if (entries.slice(0, i).some((other) => rootsOverlap(row.root, other.root) || row.endpoint === other.endpoint))
5516
5534
  refused();
@@ -5524,10 +5542,10 @@ class ServiceCoordination {
5524
5542
  }
5525
5543
  };
5526
5544
  }
5527
- assertNamespace(walks) {
5528
- if (!this.files.directory(this.directory, false, walks))
5545
+ assertNamespace(walks, directory = this.directory) {
5546
+ if (!this.files.directory(directory, false, walks))
5529
5547
  refused();
5530
- names(this.directory, [
5548
+ names(directory, [
5531
5549
  "format.json",
5532
5550
  "config.json",
5533
5551
  "registration.json",
@@ -5539,15 +5557,16 @@ class ServiceCoordination {
5539
5557
  "history-v2",
5540
5558
  "conversations-v1"
5541
5559
  ]);
5542
- this.files.directory(join6(this.directory, "history-v2"), false, walks);
5560
+ this.files.directory(join6(directory, "history-v2"), false, walks);
5543
5561
  }
5544
- selected() {
5562
+ selected(directory = this.directory, root = this.store.root, stoppedOnly = false) {
5545
5563
  const walks = new DirectoryWalks;
5546
- if (!this.files.directory(this.directory, false, walks))
5564
+ if (!this.files.directory(directory, false, walks))
5547
5565
  return;
5548
- this.assertNamespace(walks);
5549
- const manifest = this.files.read(this.path("format.json"), undefined, walks);
5550
- const config = this.files.read(this.path("config.json"), undefined, walks);
5566
+ this.assertNamespace(walks, directory);
5567
+ const path = (name) => join6(directory, name);
5568
+ const manifest = this.files.read(path("format.json"), undefined, walks);
5569
+ const config = this.files.read(path("config.json"), undefined, walks);
5551
5570
  if (!manifest || !config)
5552
5571
  refused();
5553
5572
  const f = closed(json(manifest), [
@@ -5557,20 +5576,22 @@ class ServiceCoordination {
5557
5576
  "configIdentity"
5558
5577
  ]);
5559
5578
  literal(f.schema, "cadence-service-format/1");
5560
- literal(f.root, this.store.root);
5579
+ literal(f.root, root);
5561
5580
  literal(f.stateHome, this.stateHome);
5562
5581
  literal(f.configIdentity, config.identity);
5563
5582
  const c = decodeOwnerConfig(json(config));
5564
- const hostConfig = this.files.read(this.path("host-config.json"), undefined, walks);
5583
+ const hostConfig = this.files.read(path("host-config.json"), undefined, walks);
5565
5584
  if (hostConfig && hostConfig.identity !== c.policyIdentity)
5566
5585
  refused();
5567
- const registration = this.files.read(this.path("registration.json"), undefined, walks);
5568
- const owner = this.files.read(this.path("owner.lock"), undefined, walks);
5569
- const capability = this.files.read(this.path("local-capability.json"), undefined, walks);
5570
- const checkout = this.files.read(this.store.lockPath, 8192, walks);
5586
+ const registration = this.files.read(path("registration.json"), undefined, walks);
5587
+ const owner = this.files.read(path("owner.lock"), undefined, walks);
5588
+ const capability = this.files.read(path("local-capability.json"), undefined, walks);
5589
+ const checkout = this.files.read(join6(dirname5(directory), "checkout.lock"), 8192, walks);
5571
5590
  const value = registration ? decodeRegistration(json(registration)) : undefined;
5572
- if (value && (value.root !== this.store.root || value.stateHome !== this.stateHome || value.configIdentity !== config.identity || !endpointMatches(c.endpoint, value.endpoint)))
5591
+ if (value && (value.root !== root || value.stateHome !== this.stateHome || value.configIdentity !== config.identity || !endpointMatches(c.endpoint, value.endpoint)))
5573
5592
  refused();
5593
+ if (stoppedOnly && (checkout || value && value.phase !== "stopped"))
5594
+ throw new ContractError("quarantine");
5574
5595
  if (value && value.phase !== "stopped") {
5575
5596
  if (!owner || !checkout || !capability)
5576
5597
  refused();
@@ -5650,18 +5671,21 @@ class ServiceCoordination {
5650
5671
  }
5651
5672
  withRegistry(operation) {
5652
5673
  this.files.directory(this.registryDirectory, true);
5653
- names(this.registryDirectory, [
5654
- "format.json",
5655
- "roots.json",
5656
- "registry.lock"
5657
- ]);
5674
+ names(this.registryDirectory, registryNames);
5658
5675
  const lockPath = this.registryPath("registry.lock");
5659
- const lock = this.files.create(lockPath, encoded({
5660
- schema: "cadence-service-registry-lock/1",
5661
- token: randomUUID4(),
5662
- pid: process.pid,
5663
- host: hostname2()
5664
- }));
5676
+ let lock;
5677
+ try {
5678
+ lock = this.files.create(lockPath, encoded({
5679
+ schema: "cadence-service-registry-lock/1",
5680
+ token: randomUUID4(),
5681
+ pid: process.pid,
5682
+ host: hostname2()
5683
+ }));
5684
+ } catch (error) {
5685
+ if (error instanceof Error && "code" in error && error.code === "EEXIST")
5686
+ throw new ContractError("registry-locked");
5687
+ throw error;
5688
+ }
5665
5689
  let dirty = false;
5666
5690
  try {
5667
5691
  let format = this.files.read(this.registryPath("format.json"));
@@ -5685,11 +5709,7 @@ class ServiceCoordination {
5685
5709
  }, () => {
5686
5710
  this.files.assert(lockPath, lock);
5687
5711
  this.files.assert(this.registryPath("format.json"), format);
5688
- names(this.registryDirectory, [
5689
- "format.json",
5690
- "roots.json",
5691
- "registry.lock"
5692
- ]);
5712
+ names(this.registryDirectory, registryNames);
5693
5713
  });
5694
5714
  this.files.remove(lockPath, lock);
5695
5715
  return result;
@@ -5700,6 +5720,7 @@ class ServiceCoordination {
5700
5720
  }
5701
5721
  }
5702
5722
  acquire(input, resolvedEndpoint = input.endpoint, initializeNew, reselectExisting) {
5723
+ this.store;
5703
5724
  const config = decodeOwnerConfig(input);
5704
5725
  const bound = endpoint(resolvedEndpoint);
5705
5726
  if (!endpointMatches(config.endpoint, bound))
@@ -5821,6 +5842,154 @@ class ServiceCoordination {
5821
5842
  });
5822
5843
  });
5823
5844
  }
5845
+ registryLockDiagnostic() {
5846
+ try {
5847
+ const lock = this.files.read(this.registryPath("registry.lock"));
5848
+ if (!lock)
5849
+ return;
5850
+ const value = decodeRegistryLock(lock);
5851
+ return {
5852
+ reason: "registry-locked",
5853
+ owner: recoveryOwnerStatus(value.pid, value.host)
5854
+ };
5855
+ } catch {
5856
+ return { reason: "registry-locked", owner: "unreadable" };
5857
+ }
5858
+ }
5859
+ registryRecoveryState() {
5860
+ const lock = this.files.read(this.registryPath("registry.lock"));
5861
+ if (!lock)
5862
+ throw new ContractError("conflict");
5863
+ const value = decodeRegistryLock(lock);
5864
+ names(this.registryDirectory, registryNames);
5865
+ const format = this.files.read(this.registryPath("format.json"));
5866
+ const roots = this.files.read(this.registryPath("roots.json"), REGISTRY_BYTES);
5867
+ const registry = !format && !roots ? undefined : this.registry();
5868
+ const entries = registry?.value.entries ?? [];
5869
+ const protectedRoots = new Set(entries.map((row) => hash2(row.root)));
5870
+ const proofs = [];
5871
+ const directory = opendirSync(this.files.namespace);
5872
+ try {
5873
+ let count = 0;
5874
+ for (let entry = directory.readSync();entry; entry = directory.readSync()) {
5875
+ if (++count > 1024)
5876
+ throw new ContractError("capacity");
5877
+ if (!/^[a-f0-9]{64}$/.test(entry.name) || protectedRoots.has(entry.name))
5878
+ continue;
5879
+ const service = join6(this.files.namespace, entry.name, "service-v1");
5880
+ if (!this.files.directory(service))
5881
+ continue;
5882
+ const manifest = this.files.read(join6(service, "format.json"));
5883
+ if (!manifest)
5884
+ refused();
5885
+ const f = closed(json(manifest), [
5886
+ "schema",
5887
+ "root",
5888
+ "stateHome",
5889
+ "configIdentity"
5890
+ ]);
5891
+ const root = pathValue(f.root);
5892
+ if (hash2(root) !== entry.name)
5893
+ refused();
5894
+ const selected = this.selected(service, root, true);
5895
+ if (!selected)
5896
+ throw new ContractError("quarantine");
5897
+ proofs.push([
5898
+ entry.name,
5899
+ ...[
5900
+ selected.manifest,
5901
+ selected.config,
5902
+ selected.hostConfig,
5903
+ selected.registration
5904
+ ].map((file) => file ? [file.identity, file.stamp] : null)
5905
+ ]);
5906
+ }
5907
+ } finally {
5908
+ directory.closeSync();
5909
+ }
5910
+ proofs.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b), "en"));
5911
+ const stateDigest = hash2(encoded([
5912
+ lock.identity,
5913
+ lock.stamp,
5914
+ format ? [format.identity, format.stamp] : null,
5915
+ roots ? [roots.identity, roots.stamp] : null,
5916
+ proofs
5917
+ ]));
5918
+ const epoch = hash2(encoded([lock.identity, lock.stamp]));
5919
+ const archive = this.registryPath(join6("recovery-v1", epoch));
5920
+ const attempts = [];
5921
+ let previousResolver = null;
5922
+ if (this.files.directory(archive)) {
5923
+ names(archive, Array.from({ length: 128 }, (_, n) => `${n}.json`));
5924
+ for (let n = 0;n < 128; n++) {
5925
+ const attempt = this.files.read(join6(archive, `${n}.json`), 32768);
5926
+ if (!attempt) {
5927
+ names(archive, attempts.map((_, i) => `${i}.json`));
5928
+ break;
5929
+ }
5930
+ const o = closed(json(attempt, 32768), [
5931
+ "schema",
5932
+ "lock",
5933
+ "pid",
5934
+ "host",
5935
+ "digest"
5936
+ ]);
5937
+ literal(o.schema, "cadence-registry-recovery/1");
5938
+ literal(o.lock, lock.bytes.toString("utf8"));
5939
+ decodeHash(o.digest);
5940
+ previousResolver = recoveryOwnerStatus(integer(o.pid, 1, 2147483647), text(o.host, 255, true));
5941
+ attempts.push(attempt);
5942
+ }
5943
+ }
5944
+ const inspection = {
5945
+ scope: "global-registry",
5946
+ epoch,
5947
+ coordinationDigest: hash2(encoded([stateDigest, attempts.map((a) => [a.identity, a.stamp])])),
5948
+ owner: recoveryOwnerStatus(value.pid, value.host),
5949
+ process: { pid: value.pid, host: value.host },
5950
+ reservations: entries.length,
5951
+ previousResolver,
5952
+ resolution: "local-interactive-only"
5953
+ };
5954
+ return { lock, archive, attempts, stateDigest, inspection };
5955
+ }
5956
+ inspectRegistryRecovery() {
5957
+ return this.registryRecoveryState().inspection;
5958
+ }
5959
+ resolveRegistryRecovery(expected, assertAuthority) {
5960
+ assertAuthority();
5961
+ const state = this.registryRecoveryState();
5962
+ if (encoded(state.inspection) !== encoded(expected))
5963
+ throw new ContractError("conflict");
5964
+ if (expected.owner !== "absent" || expected.previousResolver !== null && expected.previousResolver !== "absent")
5965
+ throw new ContractError("quarantine");
5966
+ if (state.attempts.length >= 128)
5967
+ throw new ContractError("capacity");
5968
+ assertAuthority();
5969
+ const receiptPath = join6(state.archive, `${state.attempts.length}.json`);
5970
+ const receipt = this.files.create(receiptPath, encoded({
5971
+ schema: "cadence-registry-recovery/1",
5972
+ lock: state.lock.bytes.toString("utf8"),
5973
+ pid: process.pid,
5974
+ host: hostname2(),
5975
+ digest: expected.coordinationDigest
5976
+ }), 32768);
5977
+ assertAuthority();
5978
+ const current = this.registryRecoveryState();
5979
+ if (current.stateDigest !== state.stateDigest || current.inspection.owner !== "absent" || current.attempts.length !== state.attempts.length + 1)
5980
+ throw new ContractError("conflict");
5981
+ for (const [n, attempt] of state.attempts.entries())
5982
+ this.files.assert(join6(state.archive, `${n}.json`), attempt, 32768);
5983
+ this.files.assert(receiptPath, receipt, 32768);
5984
+ assertAuthority();
5985
+ this.files.remove(this.registryPath("registry.lock"), state.lock);
5986
+ return {
5987
+ status: "resolved",
5988
+ scope: "global-registry",
5989
+ recoveryId: expected.epoch,
5990
+ notice: "registry-only-project-fences-preserved"
5991
+ };
5992
+ }
5824
5993
  prepareRecoveryRelease(expected, assertAuthority) {
5825
5994
  assertAuthority();
5826
5995
  if (this.recoverySnapshot().digest !== expected)
@@ -5973,6 +6142,7 @@ class OwnerLease {
5973
6142
  // src/service/gateway-store.ts
5974
6143
  import { randomBytes as randomBytes3, randomUUID as randomUUID5 } from "node:crypto";
5975
6144
  import { opendirSync as opendirSync2 } from "node:fs";
6145
+ import { hostname as hostname3 } from "node:os";
5976
6146
  import { join as join7, resolve as resolve3 } from "node:path";
5977
6147
 
5978
6148
  // src/service/contracts/gateway.ts
@@ -6016,6 +6186,8 @@ function decodeGatewayProject(value) {
6016
6186
  return project;
6017
6187
  }
6018
6188
  function registration(value) {
6189
+ const hasHost = !!value && typeof value === "object" && Object.hasOwn(value, "host");
6190
+ const hasRecovery = !!value && typeof value === "object" && Object.hasOwn(value, "recovery");
6019
6191
  const o = closed(value, [
6020
6192
  "schema",
6021
6193
  "epoch",
@@ -6024,8 +6196,22 @@ function registration(value) {
6024
6196
  "capability",
6025
6197
  "selection",
6026
6198
  "pid",
6027
- "phase"
6199
+ "phase",
6200
+ ...hasHost ? ["host"] : [],
6201
+ ...hasRecovery ? ["recovery"] : []
6028
6202
  ]);
6203
+ let recovery;
6204
+ if (hasRecovery) {
6205
+ const r = closed(o.recovery, ["receipt", "sha256", "stamp"]);
6206
+ const receipt = text(r.receipt, 128, true);
6207
+ if (!/^recovery-v1\/[a-f0-9]{64}\/(?:0|[1-9][0-9]{0,2})\.json$/.test(receipt))
6208
+ throw new ContractError("corrupt-selected");
6209
+ recovery = {
6210
+ receipt,
6211
+ sha256: decodeHash(r.sha256),
6212
+ stamp: text(r.stamp, 512, true)
6213
+ };
6214
+ }
6029
6215
  const endpoint2 = text(o.endpoint, 128, true), capability = text(o.capability, 43, true);
6030
6216
  if (!/^http:\/\/(?:127\.0\.0\.1|\[::1\]):[1-9][0-9]{0,4}$/.test(endpoint2) || !/^[A-Za-z0-9_-]{43}$/.test(capability))
6031
6217
  throw new ContractError("corrupt-selected");
@@ -6036,7 +6222,9 @@ function registration(value) {
6036
6222
  endpoint: endpoint2,
6037
6223
  capability,
6038
6224
  selection: decodeHash(o.selection),
6039
- pid: integer(o.pid, 1),
6225
+ pid: integer(o.pid, 1, 2147483647),
6226
+ ...hasHost ? { host: text(o.host, 255, true) } : {},
6227
+ ...recovery ? { recovery } : {},
6040
6228
  phase: oneOf(o.phase, ["alive", "stopping", "stopped", "quarantined"])
6041
6229
  };
6042
6230
  }
@@ -6096,6 +6284,8 @@ class GatewayStore {
6096
6284
  const previous = this.read();
6097
6285
  if (previous && previous.phase !== "stopped")
6098
6286
  throw new ContractError("quarantine");
6287
+ if (previous?.recovery)
6288
+ this.associatedProjects();
6099
6289
  this.lock = this.files.create(this.path("gateway.lock"), encoded({ epoch: randomUUID5() }));
6100
6290
  const source = { serverConfig: this.serverConfig, token };
6101
6291
  if (!previous)
@@ -6115,6 +6305,8 @@ class GatewayStore {
6115
6305
  capability: randomBytes3(32).toString("base64url"),
6116
6306
  selection: hash2(inputBytes),
6117
6307
  pid: process.pid,
6308
+ host: hostname3(),
6309
+ ...previous?.recovery ? { recovery: previous.recovery } : {},
6118
6310
  phase: "alive"
6119
6311
  };
6120
6312
  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));
@@ -6158,6 +6350,21 @@ class GatewayStore {
6158
6350
  }
6159
6351
  return out;
6160
6352
  }
6353
+ associatedProjects() {
6354
+ const projects = this.projects(), recovery = this.read()?.recovery;
6355
+ if (!recovery)
6356
+ return projects;
6357
+ const receipt = this.files.read(this.path(recovery.receipt), 65536);
6358
+ if (!receipt || receipt.identity !== recovery.sha256 || receipt.stamp !== recovery.stamp)
6359
+ throw new ContractError("corrupt-selected");
6360
+ const value = json(receipt, 65536);
6361
+ literal(value.schema, "cadence-workbench-recovery/1");
6362
+ const retired = list(value.retiredProjects, 128, (entry) => {
6363
+ const row = closed(entry, ["id", "stamp"]);
6364
+ return { id: decodeHash(row.id), stamp: text(row.stamp, 512, true) };
6365
+ });
6366
+ return projects.filter((project) => !retired.some((row) => row.id === project.id && row.stamp === this.files.read(this.path(`projects/${project.id}.json`))?.stamp));
6367
+ }
6161
6368
  saveProject(project) {
6162
6369
  this.assert();
6163
6370
  const p = decodeGatewayProject(project), path = this.path(`projects/${p.id}.json`), before = this.files.read(path);
@@ -6173,205 +6380,10 @@ class GatewayStore {
6173
6380
  }
6174
6381
  }
6175
6382
 
6176
- // src/service/runtime.ts
6177
- import { homedir as homedir2 } from "node:os";
6178
- import { dirname as dirname6, isAbsolute as isAbsolute5, join as join8, resolve as resolve4 } from "node:path";
6179
- var STARTUP_INPUT_BYTES = 16 * 1024;
6180
- function absolute(value) {
6181
- const result = text(value, 4096, true);
6182
- if (!isAbsolute5(result))
6183
- throw new ContractError("invalid");
6184
- return result;
6185
- }
6186
- function decodeHostConfig(value) {
6187
- const o = closed(value, [
6188
- "schema",
6189
- "http",
6190
- "cwd",
6191
- "agentDir",
6192
- "tokenSource",
6193
- "tokenFile"
6194
- ]);
6195
- const tokenSource = oneOf(o.tokenSource, [
6196
- "generated",
6197
- "environment",
6198
- "file",
6199
- "config"
6200
- ]);
6201
- const tokenFile = o.tokenFile === null ? null : absolute(o.tokenFile);
6202
- if ((tokenSource === "file" || tokenSource === "config") !== !!tokenFile)
6203
- throw new ContractError("invalid");
6204
- return {
6205
- schema: literal(o.schema, "cadence-service-host/1"),
6206
- http: decodeHttpPolicy(o.http),
6207
- cwd: absolute(o.cwd),
6208
- agentDir: absolute(o.agentDir),
6209
- tokenSource,
6210
- tokenFile
6211
- };
6212
- }
6213
- function reselectable(old, next) {
6214
- const kept = (c) => encoded({
6215
- ...c,
6216
- http: { ...c.http, port: 0 },
6217
- tokenSource: null,
6218
- tokenFile: null
6219
- });
6220
- return next.tokenSource === "config" && kept(old) === kept(next);
6221
- }
6222
- function readHostConfig(c) {
6223
- const owner = c.configuration();
6224
- if (!owner)
6225
- return;
6226
- const snapshot = c.files.read(c.path("host-config.json"));
6227
- if (!snapshot)
6228
- throw new ContractError("corrupt-selected");
6229
- const config = decodeHostConfig(json(snapshot));
6230
- if (!config.http.port || owner.endpoint !== localOrigin(config.http, config.http.port) || owner.policyIdentity !== hash2(encoded(config)))
6231
- throw new ContractError("conflict");
6232
- return config;
6233
- }
6234
- function hostEnvironment(ambient) {
6235
- let route;
6236
- const facts = networkSnapshot(ambient);
6237
- try {
6238
- if (facts.NODE_OPTIONS.present)
6239
- throw new Error;
6240
- route = sdkNetworkEnvironment(facts, networkSnapshot(undefined));
6241
- } catch {
6242
- throw new ContractError("unsupported-context");
6243
- }
6244
- const result = Object.create(null);
6245
- for (const key of Object.keys(ambient)) {
6246
- const d = Object.getOwnPropertyDescriptor(ambient, key);
6247
- if (!d || !("value" in d) || typeof d.value !== "string")
6248
- throw new ContractError("invalid");
6249
- if (/^(?:CADENCE_|PI_|ABEL_|TRELLIS_|GIT_)/i.test(key) || networkKeys.some((name) => name === key))
6250
- continue;
6251
- result[key] = d.value;
6252
- }
6253
- if (route.redactionLiterals.length) {
6254
- result.HTTP_PROXY = route.env.HTTP_PROXY;
6255
- result.HTTPS_PROXY = route.env.HTTPS_PROXY;
6256
- result.NODE_USE_ENV_PROXY = "1";
6257
- }
6258
- result.PI_OFFLINE = "1";
6259
- result.NO_COLOR = "1";
6260
- if (!sameNetwork(facts, networkSnapshot(ambient)))
6261
- throw new ContractError("stale");
6262
- return result;
6263
- }
6264
- function selectStart(options, ambient) {
6265
- const workspace = resolveWorkspace(options.workspace), c = new ServiceCoordination(workspace.root, options.stateHome);
6266
- const old = readHostConfig(c);
6267
- const env = Object.getOwnPropertyDescriptor(ambient, "CADENCE_WEB_TOKEN");
6268
- if (env && (!("value" in env) || typeof env.value !== "string"))
6269
- throw new ContractError("invalid");
6270
- let fromFile;
6271
- if (options.config) {
6272
- const path = resolve4(options.config), f = new PrivateFiles(dirname6(path));
6273
- const bytes = f.read(path);
6274
- if (!bytes)
6275
- throw new ContractError("corrupt-selected");
6276
- fromFile = decodeHostConfig(json(bytes));
6277
- }
6278
- const base = old ?? fromFile ?? decodeHostConfig({
6279
- schema: "cadence-service-host/1",
6280
- cwd: workspace.cwd,
6281
- http: {
6282
- schema: "cadence-service-http/1",
6283
- host: options.host ?? "127.0.0.1",
6284
- port: options.port ?? 17423,
6285
- proxy: null
6286
- },
6287
- agentDir: resolve4(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join8(ambient.HOME ?? ambient.USERPROFILE ?? homedir2(), ".pi", "agent")),
6288
- tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
6289
- tokenFile: options.tokenFile ? resolve4(options.tokenFile) : null
6290
- });
6291
- const server = options.serverConfig !== undefined ? resolve4(options.serverConfig) : old?.tokenSource === "config" ? old.tokenFile : null;
6292
- let selected = base;
6293
- if (server) {
6294
- if (options.tokenFile !== undefined)
6295
- throw new ContractError("conflict");
6296
- const { port } = readServerConfig(server).config;
6297
- selected = decodeHostConfig({
6298
- ...base,
6299
- http: { ...base.http, port },
6300
- tokenSource: "config",
6301
- tokenFile: server
6302
- });
6303
- }
6304
- 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 && resolve4(options.agentDir) !== selected.agentDir || options.tokenFile !== undefined && resolve4(options.tokenFile) !== selected.tokenFile || env && selected.tokenSource !== "environment" || selected.tokenSource === "environment" && !env || resolveWorkspace(selected.cwd).root !== workspace.root)
6305
- throw new ContractError("conflict");
6306
- return {
6307
- workspace: workspace.root,
6308
- stateHome: c.stateHome,
6309
- config: selected,
6310
- environmentToken: env?.value ?? null
6311
- };
6312
- }
6313
- function decodeHostInput(value) {
6314
- const o = closed(value, [
6315
- "workspace",
6316
- "stateHome",
6317
- "config",
6318
- "environmentToken"
6319
- ]);
6320
- const config = decodeHostConfig(o.config);
6321
- const environmentToken = o.environmentToken === null ? null : text(o.environmentToken, 1024, true);
6322
- if (config.tokenSource === "environment" !== (environmentToken !== null))
6323
- throw new ContractError("invalid");
6324
- return {
6325
- workspace: absolute(o.workspace),
6326
- stateHome: absolute(o.stateHome),
6327
- config,
6328
- environmentToken
6329
- };
6330
- }
6331
- var STARTUP_STAGES = [
6332
- "package",
6333
- "pi-agent-directory",
6334
- "pi-auth",
6335
- "pi-models",
6336
- "pi-runtime",
6337
- "service"
6338
- ];
6339
-
6340
- class StartupRefusal extends ContractError {
6341
- stage;
6342
- quarantined;
6343
- constructor(code2, stage, quarantined = false) {
6344
- super(code2);
6345
- this.stage = stage;
6346
- this.quarantined = quarantined;
6347
- }
6348
- }
6349
- function startupReport(error) {
6350
- if (error instanceof StartupRefusal)
6351
- return {
6352
- status: error.quarantined ? "quarantined" : "refused",
6353
- error: error.code,
6354
- stage: error.stage
6355
- };
6356
- return {
6357
- status: "refused",
6358
- error: error instanceof ContractError ? error.code : "unavailable",
6359
- stage: "service"
6360
- };
6361
- }
6362
- function decodeStartupReport(value) {
6363
- const o = closed(value, ["status", "error", "stage"]);
6364
- return {
6365
- status: oneOf(o.status, ["refused", "quarantined"]),
6366
- error: oneOf(o.error, ERROR_CODES),
6367
- stage: oneOf(o.stage, STARTUP_STAGES)
6368
- };
6369
- }
6370
-
6371
6383
  // src/history/recovery.ts
6372
6384
  import { createHash as createHash8, randomUUID as randomUUID7 } from "node:crypto";
6373
6385
  import { lstatSync as lstatSync7, opendirSync as opendirSync4 } from "node:fs";
6374
- import { join as join10, relative as relative4 } from "node:path";
6386
+ import { join as join9, relative as relative4 } from "node:path";
6375
6387
 
6376
6388
  // src/service/recovery.ts
6377
6389
  async function inspectRecovery(coordination, history) {
@@ -6403,7 +6415,7 @@ import {
6403
6415
  statfsSync
6404
6416
  } from "node:fs";
6405
6417
  import { open } from "node:fs/promises";
6406
- import { dirname as dirname8, join as join9 } from "node:path";
6418
+ import { dirname as dirname7, join as join8 } from "node:path";
6407
6419
 
6408
6420
  // src/history/admission.ts
6409
6421
  function decodeRunAdmission(value) {
@@ -6432,13 +6444,13 @@ import {
6432
6444
  openSync as openSync3,
6433
6445
  readSync as readSync3
6434
6446
  } from "node:fs";
6435
- import { dirname as dirname7 } from "node:path";
6447
+ import { dirname as dirname6 } from "node:path";
6436
6448
  function bad() {
6437
6449
  throw new ContractError("corrupt-selected");
6438
6450
  }
6439
6451
  var stamp2 = (s) => [s.dev, s.ino, s.size, s.mode, s.uid, s.nlink, s.ctimeMs, s.mtimeMs].join(":");
6440
6452
  function selected(files, path, read, walks) {
6441
- if (!files.files.directory(dirname7(path), false, walks))
6453
+ if (!files.files.directory(dirname6(path), false, walks))
6442
6454
  bad();
6443
6455
  const before = lstatSync5(path);
6444
6456
  if (!before.isFile() || before.nlink !== 1 || process.platform !== "win32" && (before.mode & 63 || process.getuid && before.uid !== process.getuid()))
@@ -6972,7 +6984,7 @@ async function retryStalled(read) {
6972
6984
  } catch (error) {
6973
6985
  if (!(error instanceof StalledQuantum) || retry >= STALL_RETRIES)
6974
6986
  throw error;
6975
- await new Promise((resolve5) => setImmediate(resolve5));
6987
+ await new Promise((resolve4) => setImmediate(resolve4));
6976
6988
  }
6977
6989
  }
6978
6990
  }
@@ -6993,14 +7005,14 @@ class HistoryFiles {
6993
7005
  this.coordination = coordination;
6994
7006
  this.format = format;
6995
7007
  this.formatSnapshot = formatSnapshot;
6996
- this.directory = join9(coordination.directory, "history-v2");
7008
+ this.directory = join8(coordination.directory, "history-v2");
6997
7009
  this.files = coordination.files;
6998
7010
  }
6999
7011
  static open(coordination) {
7000
- const dir = join9(coordination.directory, "history-v2");
7012
+ const dir = join8(coordination.directory, "history-v2");
7001
7013
  if (!coordination.files.directory(dir))
7002
7014
  return;
7003
- const file = coordination.files.read(join9(dir, "format.json"));
7015
+ const file = coordination.files.read(join8(dir, "format.json"));
7004
7016
  if (!file)
7005
7017
  corrupt();
7006
7018
  const format = decodeFormat(json(file));
@@ -7016,7 +7028,7 @@ class HistoryFiles {
7016
7028
  existing.assertWritable();
7017
7029
  return existing;
7018
7030
  }
7019
- const dir = join9(c.directory, "history-v2");
7031
+ const dir = join8(c.directory, "history-v2");
7020
7032
  const format = {
7021
7033
  schema: FORMAT,
7022
7034
  storeId: randomUUID6(),
@@ -7026,19 +7038,19 @@ class HistoryFiles {
7026
7038
  c.files.directory(dir, true);
7027
7039
  for (const name of ["commands", "ledger", "runs", "indexes"]) {
7028
7040
  lease.assert();
7029
- c.files.directory(join9(dir, name), true);
7041
+ c.files.directory(join8(dir, name), true);
7030
7042
  }
7031
7043
  lease.assert();
7032
- const keyPath = join9(dir, "read-proof-key.json");
7044
+ const keyPath = join8(dir, "read-proof-key.json");
7033
7045
  c.files.create(keyPath, encoded({
7034
7046
  schema: "cadence-history-read-key/1",
7035
7047
  storeId: format.storeId,
7036
7048
  key: randomBytes4(32).toString("base64url")
7037
7049
  }));
7038
7050
  lease.assert();
7039
- const snapshot = c.files.create(join9(dir, "format.json"), encoded(format));
7051
+ const snapshot = c.files.create(join8(dir, "format.json"), encoded(format));
7040
7052
  const result = new HistoryFiles(c, format, snapshot);
7041
- result.syncParents(join9(dir, "format.json"));
7053
+ result.syncParents(join8(dir, "format.json"));
7042
7054
  result.assertWritable();
7043
7055
  return result;
7044
7056
  }
@@ -7073,7 +7085,7 @@ class HistoryFiles {
7073
7085
  throw new ContractError("quarantine");
7074
7086
  }
7075
7087
  assertFormat() {
7076
- this.files.assert(join9(this.directory, "format.json"), this.formatSnapshot);
7088
+ this.files.assert(join8(this.directory, "format.json"), this.formatSnapshot);
7077
7089
  }
7078
7090
  readProofMaterial(budget) {
7079
7091
  if (this.format.schema !== FORMAT)
@@ -7082,7 +7094,7 @@ class HistoryFiles {
7082
7094
  this.assertReadFormat(budget);
7083
7095
  else
7084
7096
  this.assertFormat();
7085
- const selected2 = this.readBudgeted(join9(this.directory, "read-proof-key.json"), 512, budget);
7097
+ const selected2 = this.readBudgeted(join8(this.directory, "read-proof-key.json"), 512, budget);
7086
7098
  if (!selected2 || this.proofSnapshot && (selected2.identity !== this.proofSnapshot.identity || selected2.stamp !== this.proofSnapshot.stamp))
7087
7099
  corrupt();
7088
7100
  try {
@@ -7110,17 +7122,17 @@ class HistoryFiles {
7110
7122
  return;
7111
7123
  }
7112
7124
  this.assertFormat();
7113
- assertIncarnation(this, join9(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
7125
+ assertIncarnation(this, join8(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
7114
7126
  }
7115
7127
  segmentPath(segment) {
7116
- return join9(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
7128
+ return join8(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
7117
7129
  }
7118
7130
  artifactPath(ref) {
7119
- return join9(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
7131
+ return join8(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
7120
7132
  }
7121
7133
  admissionPath(runId) {
7122
7134
  decodeId(runId);
7123
- return join9(this.directory, "runs", runId, "admission.json");
7135
+ return join8(this.directory, "runs", runId, "admission.json");
7124
7136
  }
7125
7137
  writeAdmission(lease, input) {
7126
7138
  lease.assert();
@@ -7146,7 +7158,7 @@ class HistoryFiles {
7146
7158
  readCommand(commandId, budget) {
7147
7159
  decodeId(commandId);
7148
7160
  this.assertReadFormat(budget);
7149
- return this.readBudgeted(join9(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
7161
+ return this.readBudgeted(join8(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
7150
7162
  }
7151
7163
  readAdmission(runId, identity, budget) {
7152
7164
  return this.readAdmissionIdentity(runId, hash2(canonicalJson(identity)), budget);
@@ -7167,7 +7179,7 @@ class HistoryFiles {
7167
7179
  syncParents(file) {
7168
7180
  if (process.platform === "win32")
7169
7181
  return;
7170
- for (let path = dirname8(file);; path = dirname8(path)) {
7182
+ for (let path = dirname7(file);; path = dirname7(path)) {
7171
7183
  if (!this.files.directory(path))
7172
7184
  corrupt();
7173
7185
  const fd = openSync4(path, constants4.O_RDONLY | constants4.O_DIRECTORY);
@@ -7178,13 +7190,13 @@ class HistoryFiles {
7178
7190
  }
7179
7191
  if (path === this.coordination.directory)
7180
7192
  break;
7181
- if (path === dirname8(path))
7193
+ if (path === dirname7(path))
7182
7194
  corrupt();
7183
7195
  }
7184
7196
  }
7185
7197
  assertReadFormat(budget) {
7186
7198
  budget.reserve(this.formatSnapshot.bytes.length);
7187
- this.files.assert(join9(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
7199
+ this.files.assert(join8(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
7188
7200
  }
7189
7201
  assertPublished(segment, end, seq) {
7190
7202
  const fence = publicationFences.get(this.directory);
@@ -7222,7 +7234,7 @@ class HistoryFiles {
7222
7234
  const dir = opendirSync3(path);
7223
7235
  try {
7224
7236
  for (let e = dir.readSync();e; e = dir.readSync()) {
7225
- const p = join9(path, e.name), st = lstatSync6(p);
7237
+ const p = join8(path, e.name), st = lstatSync6(p);
7226
7238
  if (st.isDirectory())
7227
7239
  bytes += walk(p, depth + 1);
7228
7240
  else {
@@ -7257,7 +7269,7 @@ class HistoryFiles {
7257
7269
  state.bytes = 0;
7258
7270
  state.identity = null;
7259
7271
  let count = 0;
7260
- const ledger = join9(this.directory, "ledger");
7272
+ const ledger = join8(this.directory, "ledger");
7261
7273
  if (!this.files.directory(ledger))
7262
7274
  corrupt();
7263
7275
  const dir = opendirSync3(ledger);
@@ -7574,7 +7586,7 @@ class HistoryFiles {
7574
7586
  checkpoint(lease, state) {
7575
7587
  lease.assert();
7576
7588
  this.assertWritable();
7577
- const path = join9(this.directory, "checkpoint.json"), old = this.files.read(path);
7589
+ const path = join8(this.directory, "checkpoint.json"), old = this.files.read(path);
7578
7590
  const body = encoded({
7579
7591
  schema: "cadence-history-checkpoint/2",
7580
7592
  storeId: this.format.storeId,
@@ -8181,7 +8193,7 @@ function digestHistory(files, exclude) {
8181
8193
  dir.closeSync();
8182
8194
  }
8183
8195
  for (const name of names2.sort()) {
8184
- const selected2 = join10(path, name);
8196
+ const selected2 = join9(path, name);
8185
8197
  if (selected2 === exclude)
8186
8198
  continue;
8187
8199
  const rel = relative4(files.directory, selected2);
@@ -8369,6 +8381,201 @@ class HistoryRecovery {
8369
8381
  }
8370
8382
  }
8371
8383
 
8384
+ // src/service/runtime.ts
8385
+ import { homedir as homedir2 } from "node:os";
8386
+ import { dirname as dirname8, isAbsolute as isAbsolute5, join as join10, resolve as resolve4 } from "node:path";
8387
+ var STARTUP_INPUT_BYTES = 16 * 1024;
8388
+ function absolute(value) {
8389
+ const result = text(value, 4096, true);
8390
+ if (!isAbsolute5(result))
8391
+ throw new ContractError("invalid");
8392
+ return result;
8393
+ }
8394
+ function decodeHostConfig(value) {
8395
+ const o = closed(value, [
8396
+ "schema",
8397
+ "http",
8398
+ "cwd",
8399
+ "agentDir",
8400
+ "tokenSource",
8401
+ "tokenFile"
8402
+ ]);
8403
+ const tokenSource = oneOf(o.tokenSource, [
8404
+ "generated",
8405
+ "environment",
8406
+ "file",
8407
+ "config"
8408
+ ]);
8409
+ const tokenFile = o.tokenFile === null ? null : absolute(o.tokenFile);
8410
+ if ((tokenSource === "file" || tokenSource === "config") !== !!tokenFile)
8411
+ throw new ContractError("invalid");
8412
+ return {
8413
+ schema: literal(o.schema, "cadence-service-host/1"),
8414
+ http: decodeHttpPolicy(o.http),
8415
+ cwd: absolute(o.cwd),
8416
+ agentDir: absolute(o.agentDir),
8417
+ tokenSource,
8418
+ tokenFile
8419
+ };
8420
+ }
8421
+ function reselectable(old, next) {
8422
+ const kept = (c) => encoded({
8423
+ ...c,
8424
+ http: { ...c.http, port: 0 },
8425
+ tokenSource: null,
8426
+ tokenFile: null
8427
+ });
8428
+ return next.tokenSource === "config" && kept(old) === kept(next);
8429
+ }
8430
+ function readHostConfig(c) {
8431
+ const owner = c.configuration();
8432
+ if (!owner)
8433
+ return;
8434
+ const snapshot = c.files.read(c.path("host-config.json"));
8435
+ if (!snapshot)
8436
+ throw new ContractError("corrupt-selected");
8437
+ const config = decodeHostConfig(json(snapshot));
8438
+ if (!config.http.port || owner.endpoint !== localOrigin(config.http, config.http.port) || owner.policyIdentity !== hash2(encoded(config)))
8439
+ throw new ContractError("conflict");
8440
+ return config;
8441
+ }
8442
+ function hostEnvironment(ambient) {
8443
+ let route;
8444
+ const facts = networkSnapshot(ambient);
8445
+ try {
8446
+ if (facts.NODE_OPTIONS.present)
8447
+ throw new Error;
8448
+ route = sdkNetworkEnvironment(facts, networkSnapshot(undefined));
8449
+ } catch {
8450
+ throw new ContractError("unsupported-context");
8451
+ }
8452
+ const result = Object.create(null);
8453
+ for (const key of Object.keys(ambient)) {
8454
+ const d = Object.getOwnPropertyDescriptor(ambient, key);
8455
+ if (!d || !("value" in d) || typeof d.value !== "string")
8456
+ throw new ContractError("invalid");
8457
+ if (/^(?:CADENCE_|PI_|ABEL_|TRELLIS_|GIT_)/i.test(key) || networkKeys.some((name) => name === key))
8458
+ continue;
8459
+ result[key] = d.value;
8460
+ }
8461
+ if (route.redactionLiterals.length) {
8462
+ result.HTTP_PROXY = route.env.HTTP_PROXY;
8463
+ result.HTTPS_PROXY = route.env.HTTPS_PROXY;
8464
+ result.NODE_USE_ENV_PROXY = "1";
8465
+ }
8466
+ result.PI_OFFLINE = "1";
8467
+ result.NO_COLOR = "1";
8468
+ if (!sameNetwork(facts, networkSnapshot(ambient)))
8469
+ throw new ContractError("stale");
8470
+ return result;
8471
+ }
8472
+ function selectStart(options, ambient) {
8473
+ const workspace = resolveWorkspace(options.workspace), c = new ServiceCoordination(workspace.root, options.stateHome);
8474
+ const old = readHostConfig(c);
8475
+ const env = Object.getOwnPropertyDescriptor(ambient, "CADENCE_WEB_TOKEN");
8476
+ if (env && (!("value" in env) || typeof env.value !== "string"))
8477
+ throw new ContractError("invalid");
8478
+ let fromFile;
8479
+ if (options.config) {
8480
+ const path = resolve4(options.config), f = new PrivateFiles(dirname8(path));
8481
+ const bytes = f.read(path);
8482
+ if (!bytes)
8483
+ throw new ContractError("corrupt-selected");
8484
+ fromFile = decodeHostConfig(json(bytes));
8485
+ }
8486
+ const base = old ?? fromFile ?? decodeHostConfig({
8487
+ schema: "cadence-service-host/1",
8488
+ cwd: workspace.cwd,
8489
+ http: {
8490
+ schema: "cadence-service-http/1",
8491
+ host: options.host ?? "127.0.0.1",
8492
+ port: options.port ?? 17423,
8493
+ proxy: null
8494
+ },
8495
+ agentDir: resolve4(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join10(ambient.HOME ?? ambient.USERPROFILE ?? homedir2(), ".pi", "agent")),
8496
+ tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
8497
+ tokenFile: options.tokenFile ? resolve4(options.tokenFile) : null
8498
+ });
8499
+ const server = options.serverConfig !== undefined ? resolve4(options.serverConfig) : old?.tokenSource === "config" ? old.tokenFile : null;
8500
+ let selected2 = base;
8501
+ if (server) {
8502
+ if (options.tokenFile !== undefined)
8503
+ throw new ContractError("conflict");
8504
+ const { port } = readServerConfig(server).config;
8505
+ selected2 = decodeHostConfig({
8506
+ ...base,
8507
+ http: { ...base.http, port },
8508
+ tokenSource: "config",
8509
+ tokenFile: server
8510
+ });
8511
+ }
8512
+ 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 && resolve4(options.agentDir) !== selected2.agentDir || options.tokenFile !== undefined && resolve4(options.tokenFile) !== selected2.tokenFile || env && selected2.tokenSource !== "environment" || selected2.tokenSource === "environment" && !env || resolveWorkspace(selected2.cwd).root !== workspace.root)
8513
+ throw new ContractError("conflict");
8514
+ return {
8515
+ workspace: workspace.root,
8516
+ stateHome: c.stateHome,
8517
+ config: selected2,
8518
+ environmentToken: env?.value ?? null
8519
+ };
8520
+ }
8521
+ function decodeHostInput(value) {
8522
+ const o = closed(value, [
8523
+ "workspace",
8524
+ "stateHome",
8525
+ "config",
8526
+ "environmentToken"
8527
+ ]);
8528
+ const config = decodeHostConfig(o.config);
8529
+ const environmentToken = o.environmentToken === null ? null : text(o.environmentToken, 1024, true);
8530
+ if (config.tokenSource === "environment" !== (environmentToken !== null))
8531
+ throw new ContractError("invalid");
8532
+ return {
8533
+ workspace: absolute(o.workspace),
8534
+ stateHome: absolute(o.stateHome),
8535
+ config,
8536
+ environmentToken
8537
+ };
8538
+ }
8539
+ var STARTUP_STAGES = [
8540
+ "package",
8541
+ "pi-agent-directory",
8542
+ "pi-auth",
8543
+ "pi-models",
8544
+ "pi-runtime",
8545
+ "service"
8546
+ ];
8547
+
8548
+ class StartupRefusal extends ContractError {
8549
+ stage;
8550
+ quarantined;
8551
+ constructor(code2, stage, quarantined = false) {
8552
+ super(code2);
8553
+ this.stage = stage;
8554
+ this.quarantined = quarantined;
8555
+ }
8556
+ }
8557
+ function startupReport(error) {
8558
+ if (error instanceof StartupRefusal)
8559
+ return {
8560
+ status: error.quarantined ? "quarantined" : "refused",
8561
+ error: error.code,
8562
+ stage: error.stage
8563
+ };
8564
+ return {
8565
+ status: "refused",
8566
+ error: error instanceof ContractError ? error.code : "unavailable",
8567
+ stage: "service"
8568
+ };
8569
+ }
8570
+ function decodeStartupReport(value) {
8571
+ const o = closed(value, ["status", "error", "stage"]);
8572
+ return {
8573
+ status: oneOf(o.status, ["refused", "quarantined"]),
8574
+ error: oneOf(o.error, ERROR_CODES),
8575
+ stage: oneOf(o.stage, STARTUP_STAGES)
8576
+ };
8577
+ }
8578
+
8372
8579
  // src/service/cli.ts
8373
8580
  function nativeCall(c, operation, reveal = false, controlBody) {
8374
8581
  const access = c.localAccess();
@@ -8433,6 +8640,13 @@ function nativeCall(c, operation, reveal = false, controlBody) {
8433
8640
  }
8434
8641
  var pause = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
8435
8642
  async function serviceStatus(c, diagnose = false) {
8643
+ const registryLock = c.registryLockDiagnostic?.();
8644
+ if (registryLock)
8645
+ return {
8646
+ status: "unavailable",
8647
+ ...registryLock,
8648
+ recovery: "cadence recover inspect --workspace <project> [--state-home <state-home>]"
8649
+ };
8436
8650
  const observed = c.inspect();
8437
8651
  if (observed.status === "absent" || observed.status === "stopped")
8438
8652
  return observed;
@@ -8456,7 +8670,12 @@ async function stopService(c) {
8456
8670
  }
8457
8671
  const deadline = performance.now() + LIMITS.stopWaitMs;
8458
8672
  while (performance.now() < deadline) {
8459
- const observed = c.inspect();
8673
+ let observed;
8674
+ try {
8675
+ observed = c.inspect();
8676
+ } catch {
8677
+ observed = { status: "unavailable" };
8678
+ }
8460
8679
  if ("epoch" in observed && observed.epoch === epoch && observed.status === "stopped")
8461
8680
  return { status: "stopped", epoch };
8462
8681
  if (observed.status === "quarantined")
@@ -8470,11 +8689,24 @@ async function stopService(c) {
8470
8689
  function failedStatus(result) {
8471
8690
  return !!(result && typeof result === "object" && ("status" in result) && ["unconfirmed", "unavailable", "quarantined", "refused"].includes(String(result.status)));
8472
8691
  }
8692
+ function managementExitCode(result, foreground = false) {
8693
+ if (foreground) {
8694
+ try {
8695
+ decodeStartupReport(result);
8696
+ return 78;
8697
+ } catch {}
8698
+ }
8699
+ return 1;
8700
+ }
8473
8701
  function serverConfigHint(error) {
8474
8702
  return `Cadence changed nothing: ${error.path} ${error.reason}.
8475
8703
  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.
8476
8704
  `;
8477
8705
  }
8706
+ function registryGuidance(workspace, stateHome) {
8707
+ 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.
8708
+ `;
8709
+ }
8478
8710
  function startupGuidance(result, input) {
8479
8711
  let report;
8480
8712
  try {
@@ -8482,6 +8714,8 @@ function startupGuidance(result, input) {
8482
8714
  } catch {
8483
8715
  return "";
8484
8716
  }
8717
+ if (report.error === "registry-locked")
8718
+ return registryGuidance(input.workspace, input.stateHome);
8485
8719
  const agent = input.config.agentDir;
8486
8720
  const inspect = `Inspect: cadence-service diagnose|recover inspect --workspace ${JSON.stringify(input.workspace)}
8487
8721
  `;
@@ -8509,6 +8743,15 @@ ${inspect}` : "";
8509
8743
  async function startService(options, foreground, hostUrl) {
8510
8744
  const network = networkSnapshot(process.env);
8511
8745
  const env = hostEnvironment(process.env);
8746
+ const c = new ServiceCoordination(options.workspace, options.stateHome);
8747
+ if (c.registryLockDiagnostic()) {
8748
+ process.stderr.write(registryGuidance(c.store.root, c.stateHome));
8749
+ return {
8750
+ status: "refused",
8751
+ error: "registry-locked",
8752
+ stage: "service"
8753
+ };
8754
+ }
8512
8755
  let input;
8513
8756
  try {
8514
8757
  input = decodeHostInput(selectStart(options, process.env));
@@ -8521,7 +8764,6 @@ async function startService(options, foreground, hostUrl) {
8521
8764
  `;
8522
8765
  if (Buffer.byteLength(serialized) > STARTUP_INPUT_BYTES)
8523
8766
  throw new ContractError("oversize");
8524
- const c = new ServiceCoordination(input.workspace, input.stateHome);
8525
8767
  const existing = await serviceStatus(c);
8526
8768
  if (existing && typeof existing === "object" && "status" in existing && existing.status === "alive") {
8527
8769
  const running = readHostConfig(c);
@@ -8618,6 +8860,8 @@ async function launchHost(input, c, foreground, hostUrl, cwd, env, network) {
8618
8860
  process.stdout.write(`${JSON.stringify(result)}
8619
8861
  `);
8620
8862
  await closure;
8863
+ if (failedStatus(result))
8864
+ process.exitCode = managementExitCode(result, true);
8621
8865
  return null;
8622
8866
  }
8623
8867
  return result;
@@ -8632,8 +8876,8 @@ async function privateLink(workspace, stateHome, config) {
8632
8876
  }
8633
8877
  class ProjectOpenRefusal extends ContractError {
8634
8878
  noOwner;
8635
- constructor(noOwner) {
8636
- super("unavailable");
8879
+ constructor(noOwner, code2 = "unavailable") {
8880
+ super(code2);
8637
8881
  this.noOwner = noOwner;
8638
8882
  }
8639
8883
  }
@@ -8642,6 +8886,8 @@ async function openProjectDirectory(workspace, stateHome, agentDir, hostUrl) {
8642
8886
  throw new ContractError("forbidden");
8643
8887
  effectiveWorkspace(workspace, stateHome);
8644
8888
  const c = new ServiceCoordination(workspace, stateHome);
8889
+ if (c.registryLockDiagnostic())
8890
+ throw new ProjectOpenRefusal(true, "registry-locked");
8645
8891
  const kept = readHostConfig(c);
8646
8892
  const options = {
8647
8893
  workspace,
@@ -8650,7 +8896,7 @@ async function openProjectDirectory(workspace, stateHome, agentDir, hostUrl) {
8650
8896
  };
8651
8897
  const result = await startService(options, false, hostUrl);
8652
8898
  if (!result || typeof result !== "object" || !("status" in result) || result.status !== "alive")
8653
- throw new ProjectOpenRefusal(!!result && typeof result === "object" && "status" in result && result.status === "refused" && c.inspect().status === "absent");
8899
+ throw new ProjectOpenRefusal(!!result && typeof result === "object" && "status" in result && result.status === "refused" && (("error" in result) && result.error === "registry-locked" || c.inspect().status === "absent"), result && typeof result === "object" && "error" in result && result.error === "registry-locked" ? "registry-locked" : "unavailable");
8654
8900
  const link = await privateLink(c.store.root, stateHome);
8655
8901
  if (!link)
8656
8902
  throw new ContractError("unavailable");
@@ -9524,7 +9770,7 @@ import {
9524
9770
  opendirSync as opendirSync5,
9525
9771
  openSync as openSync5,
9526
9772
  readSync as readSync4,
9527
- realpathSync as realpathSync4
9773
+ realpathSync as realpathSync3
9528
9774
  } from "node:fs";
9529
9775
  import { dirname as dirname10, isAbsolute as isAbsolute6, join as join12, relative as relative5, resolve as resolve6, sep as sep4 } from "node:path";
9530
9776
 
@@ -9899,7 +10145,7 @@ class WorkbenchFiles {
9899
10145
  excluded;
9900
10146
  constructor(root, excluded, secrets) {
9901
10147
  this.secrets = secrets;
9902
- this.root = realpathSync4(root);
10148
+ this.root = realpathSync3(root);
9903
10149
  this.excluded = excluded.flatMap((path) => [
9904
10150
  resolve6(path),
9905
10151
  canonicalAmbientRoot(path)
@@ -9915,7 +10161,7 @@ class WorkbenchFiles {
9915
10161
  directory(path) {
9916
10162
  if (!isAbsolute6(path) || !safeDirectory(path))
9917
10163
  throw new ContractError("invalid");
9918
- const canonical = realpathSync4(path);
10164
+ const canonical = realpathSync3(path);
9919
10165
  this.allowed(canonical);
9920
10166
  return canonical;
9921
10167
  }
@@ -10028,7 +10274,7 @@ async function createGateway(value, hostUrl, source) {
10028
10274
  const input = decodeGatewayInput(value), store = new GatewayStore(input.serverConfig, input.stateHome);
10029
10275
  const web = loadWebAssets(source?.webDirectory);
10030
10276
  const build2 = {
10031
- contractHash: "9c917c085d034e08764de359cee49aea748c395a63ed629fe67d149b97850fa1",
10277
+ contractHash: "944f3b53bd5b7f1d3f30868e0947d900e356ae9a2bcfb9f591edd988b3d5eb3a",
10032
10278
  assets: {
10033
10279
  sourceHash: web.manifest.sourceHash,
10034
10280
  contractHash: web.manifest.contractHash,
@@ -10193,7 +10439,7 @@ Content-Length: 0\r
10193
10439
  return result;
10194
10440
  }
10195
10441
  function binding(id) {
10196
- const project = store.projects().find((p) => p.id === id);
10442
+ const project = store.associatedProjects().find((p) => p.id === id);
10197
10443
  if (!project)
10198
10444
  throw new ContractError("unavailable");
10199
10445
  const c = new ServiceCoordination(project.path, input.stateHome), local = c.localAccess();
@@ -10236,7 +10482,7 @@ Content-Length: 0\r
10236
10482
  const originals = await Promise.allSettled([...opens.values()].map((o) => o.result));
10237
10483
  const uncertainOpening = originals.some((r) => r.status === "rejected" && !(r.reason instanceof ProjectOpenRefusal && r.reason.noOwner));
10238
10484
  const retired = await Promise.allSettled([...retirements]);
10239
- const targets = store.projects();
10485
+ const targets = store.associatedProjects();
10240
10486
  await joinProjectStops(targets, async (p) => {
10241
10487
  const c = new ServiceCoordination(p.path, input.stateHome), observed = await serviceStatus(c);
10242
10488
  const status = observed && typeof observed === "object" && "status" in observed ? observed.status : "unavailable";
@@ -17132,7 +17378,7 @@ import {
17132
17378
  constants as constants6,
17133
17379
  fsyncSync as fsyncSync4,
17134
17380
  openSync as openSync6,
17135
- realpathSync as realpathSync5
17381
+ realpathSync as realpathSync4
17136
17382
  } from "node:fs";
17137
17383
  import { dirname as dirname12, isAbsolute as isAbsolute7, join as join16, relative as relative6, sep as sep5 } from "node:path";
17138
17384
  function summary(text2, maximum, runId) {
@@ -17183,7 +17429,7 @@ function publicationRecord(store, reader, intent, link, run, request3, expected,
17183
17429
  else {
17184
17430
  if (action.kind !== "create" || !request3.cwd || store.list().length >= 1000)
17185
17431
  bad11();
17186
- const cwd = realpathSync5(request3.cwd), within = relative6(store.root, cwd);
17432
+ const cwd = realpathSync4(request3.cwd), within = relative6(store.root, cwd);
17187
17433
  if (isAbsolute7(within) || within === ".." || within.startsWith(`..${sep5}`))
17188
17434
  bad11();
17189
17435
  record2 = {
@@ -19261,7 +19507,7 @@ class OwnerModelChoice {
19261
19507
  }
19262
19508
 
19263
19509
  // src/service/model-config.ts
19264
- import { lstatSync as lstatSync11, realpathSync as realpathSync6 } from "node:fs";
19510
+ import { lstatSync as lstatSync11, realpathSync as realpathSync5 } from "node:fs";
19265
19511
  import { dirname as dirname13, isAbsolute as isAbsolute8, join as join18, resolve as resolve7 } from "node:path";
19266
19512
  var CONFIG_BYTES2 = 1024 * 1024;
19267
19513
  var ENTRY_LIMIT = 256;
@@ -19335,7 +19581,7 @@ function piSource(agentDir, home, cwd, env) {
19335
19581
  if (selected2 === "~" || selected2.startsWith("~/"))
19336
19582
  selected2 = home + selected2.slice(1);
19337
19583
  selected2 = resolve7(cwd, selected2);
19338
- const directory = join18(realpathSync6(dirname13(selected2)), selected2.slice(dirname13(selected2).length + 1));
19584
+ const directory = join18(realpathSync5(dirname13(selected2)), selected2.slice(dirname13(selected2).length + 1));
19339
19585
  if (!lstatSync11(directory).isDirectory() || lstatSync11(directory).isSymbolicLink())
19340
19586
  refuse2();
19341
19587
  const files = new PrivateFiles(directory, false);
@@ -20773,7 +21019,7 @@ class ServiceOwner {
20773
21019
 
20774
21020
  // src/service/task-actions.ts
20775
21021
  import { randomUUID as randomUUID18 } from "node:crypto";
20776
- import { realpathSync as realpathSync7 } from "node:fs";
21022
+ import { realpathSync as realpathSync6 } from "node:fs";
20777
21023
 
20778
21024
  // src/task-diff.ts
20779
21025
  import { spawnSync as spawnSync2 } from "node:child_process";
@@ -23947,9 +24193,9 @@ class TaskActions {
23947
24193
  selection: structuredClone(options.selection),
23948
24194
  ownerSecrets: [...options.ownerSecrets]
23949
24195
  };
23950
- this.cwd = realpathSync7(options.cwd);
24196
+ this.cwd = realpathSync6(options.cwd);
23951
24197
  const config = readHostConfig(options.host.coordination);
23952
- if (!config || realpathSync7(config.cwd) !== this.cwd)
24198
+ if (!config || realpathSync6(config.cwd) !== this.cwd)
23953
24199
  throw new ContractError("unsupported-context");
23954
24200
  this.launchCwd = config.cwd;
23955
24201
  this.checkEnvironment = options.host.checkEnvironment;
@@ -24073,7 +24319,7 @@ class TaskActions {
24073
24319
  if (this.closed || this.options.host.owner.state !== "idle" || this.options.host.writer.quarantined)
24074
24320
  throw new ContractError("quarantine");
24075
24321
  this.options.host.owner.lease.assert();
24076
- if (!this.sources.current() || realpathSync7(this.cwd) !== this.cwd || realpathSync7(this.launchCwd) !== this.cwd || this.options.host.checkEnvironment !== this.checkEnvironment)
24322
+ if (!this.sources.current() || realpathSync6(this.cwd) !== this.cwd || realpathSync6(this.launchCwd) !== this.cwd || this.options.host.checkEnvironment !== this.checkEnvironment)
24077
24323
  throw new ContractError("stale");
24078
24324
  }
24079
24325
  choiceSnapshot() {
@@ -24189,7 +24435,7 @@ class TaskActions {
24189
24435
  const access = this.currentAccess();
24190
24436
  const committedSeq = snapshot ?? h.writer.committedSeq;
24191
24437
  const selected2 = taskId ? h.coordination.store.loadSnapshot(taskId) : null;
24192
- if (selected2 && realpathSync7(selected2.record.cwd) !== this.cwd)
24438
+ if (selected2 && realpathSync6(selected2.record.cwd) !== this.cwd)
24193
24439
  throw new ContractError("unsupported-context");
24194
24440
  const secrets = this.viewSecrets();
24195
24441
  const roles = this.sources.roles.map(({ role: role2, sha256 }) => ({
@@ -24695,7 +24941,7 @@ class TaskActions {
24695
24941
  this.assertChoice(expected);
24696
24942
  held.assertHeld();
24697
24943
  const actual = h.coordination.store.loadSnapshot(task.id), want = published ?? task;
24698
- if (!actual || actual.identity !== want.identity || actual.record.revision !== want.revision || realpathSync7(actual.record.cwd) !== this.cwd || this.generation !== expected.checkoutGeneration || h.writer.controlRevision(task.id) !== expected.controlRev + 1 || this.scheduler.activeTask(task.id))
24944
+ if (!actual || actual.identity !== want.identity || actual.record.revision !== want.revision || realpathSync6(actual.record.cwd) !== this.cwd || this.generation !== expected.checkoutGeneration || h.writer.controlRevision(task.id) !== expected.controlRev + 1 || this.scheduler.activeTask(task.id))
24699
24945
  throw new ContractError("stale");
24700
24946
  const changes2 = captureChanges(h.coordination.store.root);
24701
24947
  if (changes2.fingerprint !== expected.observation.identity || changes2.complete !== expected.observation.complete)
@@ -24880,7 +25126,7 @@ import { join as join23 } from "node:path";
24880
25126
 
24881
25127
  // src/service/conversations.ts
24882
25128
  import { randomUUID as randomUUID19 } from "node:crypto";
24883
- import { lstatSync as lstatSync13, opendirSync as opendirSync7, realpathSync as realpathSync8, statfsSync as statfsSync2 } from "node:fs";
25129
+ import { lstatSync as lstatSync13, opendirSync as opendirSync7, realpathSync as realpathSync7, statfsSync as statfsSync2 } from "node:fs";
24884
25130
  import { dirname as dirname14, join as join22 } from "node:path";
24885
25131
  var TURN_BYTES = 384 * 1024;
24886
25132
  var META_BYTES = 64 * 1024;
@@ -25001,7 +25247,7 @@ class Conversations {
25001
25247
  constructor(o) {
25002
25248
  this.o = o;
25003
25249
  this.root = join22(o.coordination.directory, "conversations-v1");
25004
- this.cwd = realpathSync8(o.cwd);
25250
+ this.cwd = realpathSync7(o.cwd);
25005
25251
  }
25006
25252
  currentDirectory() {
25007
25253
  const current2 = resolveWorkspace(this.o.cwd);
@@ -25661,7 +25907,7 @@ function refuseAt(stage, step) {
25661
25907
  }
25662
25908
  }
25663
25909
  async function createServiceHost(value, sourceBuild) {
25664
- const embedded = "9c917c085d034e08764de359cee49aea748c395a63ed629fe67d149b97850fa1";
25910
+ const embedded = "944f3b53bd5b7f1d3f30868e0947d900e356ae9a2bcfb9f591edd988b3d5eb3a";
25665
25911
  if (embedded && sourceBuild)
25666
25912
  throw new ContractError("unsupported-context");
25667
25913
  const contractHash = decodeHash(embedded ?? sourceBuild?.contractHash);