@abelxiaoxing/cadence 2.0.6 → 2.0.8

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.
@@ -3,8 +3,8 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // src/service/host.ts
6
- import { lstatSync as lstatSync10, opendirSync as opendirSync3 } from "node:fs";
7
- import { join as join17 } from "node:path";
6
+ import { lstatSync as lstatSync11, opendirSync as opendirSync3 } from "node:fs";
7
+ import { join as join18 } from "node:path";
8
8
 
9
9
  // src/agent-registry.ts
10
10
  import { createHash as createHash2 } from "node:crypto";
@@ -14749,7 +14749,7 @@ import {
14749
14749
  randomUUID as randomUUID7,
14750
14750
  timingSafeEqual
14751
14751
  } from "node:crypto";
14752
- import { dirname as dirname7, isAbsolute as isAbsolute4, join as join10 } from "node:path";
14752
+ import { dirname as dirname8, isAbsolute as isAbsolute4, join as join11 } from "node:path";
14753
14753
 
14754
14754
  // src/service/config.ts
14755
14755
  import { isIP } from "node:net";
@@ -14799,14 +14799,203 @@ function localOrigin(policy, actualPort) {
14799
14799
  return `http://${policy.host === "::1" ? "[::1]" : policy.host}:${actualPort}`;
14800
14800
  }
14801
14801
 
14802
- // src/service/auth.ts
14802
+ // src/service/server-config.ts
14803
+ import { lstatSync as lstatSync8, realpathSync as realpathSync3, statSync } from "node:fs";
14804
+ import { dirname as dirname7, join as join10 } from "node:path";
14805
+
14806
+ // skills/_shared/load-config.mjs
14807
+ function parseEnvFile(content) {
14808
+ const values = {};
14809
+ const lines = content.replaceAll(`\r
14810
+ `, `
14811
+ `).replaceAll("\r", `
14812
+ `).split(`
14813
+ `);
14814
+ for (let index = 0;index < lines.length; index += 1) {
14815
+ const line = lines[index].trim();
14816
+ if (!line || line.startsWith("#"))
14817
+ continue;
14818
+ const separator = line.indexOf("=");
14819
+ if (separator <= 0)
14820
+ throw new Error(`Invalid configuration syntax at line ${index + 1}`);
14821
+ const name = line.slice(0, separator).trim();
14822
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
14823
+ throw new Error(`Invalid configuration name at line ${index + 1}`);
14824
+ }
14825
+ let value = line.slice(separator + 1).trim();
14826
+ const quote = value[0];
14827
+ if (quote === '"' || quote === "'") {
14828
+ if (value.at(-1) !== quote) {
14829
+ throw new Error(`Unterminated quoted configuration value at line ${index + 1}`);
14830
+ }
14831
+ value = value.slice(1, -1);
14832
+ }
14833
+ values[name] = value;
14834
+ }
14835
+ return values;
14836
+ }
14837
+
14838
+ // src/service/server-config.ts
14839
+ var PORT_KEY = "CADENCE_PORT";
14840
+ var TOKEN_KEY = "CADENCE_TOKEN";
14841
+ var CONFIG_BYTES = 64 * 1024;
14803
14842
  var TOKEN_BYTES = 1024;
14804
- function token(value) {
14843
+ function accessToken(value) {
14805
14844
  const result = text(value, TOKEN_BYTES, true);
14806
14845
  if (utf8Bytes(result) < 32 || /[\p{Cc}\p{Cf}]/u.test(result))
14807
14846
  throw new ContractError("invalid");
14808
14847
  return result;
14809
14848
  }
14849
+ class ServerConfigRefusal extends ContractError {
14850
+ path;
14851
+ reason;
14852
+ constructor(path, reason) {
14853
+ super("corrupt-selected");
14854
+ this.path = path;
14855
+ this.reason = reason;
14856
+ }
14857
+ }
14858
+
14859
+ class Unusable extends Error {
14860
+ }
14861
+ var unusable = (reason) => {
14862
+ throw new Unusable(reason);
14863
+ };
14864
+ function syntax(error) {
14865
+ const message = error instanceof Error ? error.message : "";
14866
+ const line = /at line (\d+)$/.exec(message)?.[1];
14867
+ if (!line)
14868
+ return "is not in KEY=value lines";
14869
+ if (message.startsWith("Invalid configuration name"))
14870
+ return `has an invalid name on line ${line} (letters, digits and _ only, no "export")`;
14871
+ if (message.startsWith("Unterminated"))
14872
+ return `has an unterminated quote on line ${line}`;
14873
+ return `has no KEY=value setting on line ${line}`;
14874
+ }
14875
+ function settings(source) {
14876
+ try {
14877
+ parseEnvFile(source);
14878
+ } catch (error) {
14879
+ unusable(`${syntax(error)}, which the research skills refuse too`);
14880
+ }
14881
+ const parts = source.split(/(\r\n|\r|\n)/);
14882
+ const found = {};
14883
+ for (let at = 0;at < parts.length; at += 2) {
14884
+ const line = parts[at].trim();
14885
+ if (!line || line.startsWith("#"))
14886
+ continue;
14887
+ const separator = line.indexOf("=");
14888
+ const key2 = line.slice(0, separator).trim();
14889
+ const name = key2 === PORT_KEY ? "port" : key2 === TOKEN_KEY ? "token" : undefined;
14890
+ if (!name)
14891
+ continue;
14892
+ const previous = found[name];
14893
+ if (previous)
14894
+ unusable(`sets ${key2} twice (lines ${previous.line} and ${at / 2 + 1}); keep one`);
14895
+ let value = line.slice(separator + 1).trim();
14896
+ if (value[0] === '"' || value[0] === "'")
14897
+ value = value.slice(1, -1);
14898
+ found[name] = { at, line: at / 2 + 1, value };
14899
+ }
14900
+ return { parts, ...found };
14901
+ }
14902
+ function decode(source) {
14903
+ const { port, token } = settings(source);
14904
+ if (!port)
14905
+ unusable(`has no ${PORT_KEY} line`);
14906
+ if (!token)
14907
+ unusable(`has no ${TOKEN_KEY} line`);
14908
+ const p = port, t = token;
14909
+ const number = Number(p.value);
14910
+ if (!/^[1-9][0-9]{0,4}$/.test(p.value) || number > 65535)
14911
+ unusable(`has ${PORT_KEY} on line ${p.line} that is not a whole number from 1 to 65535`);
14912
+ try {
14913
+ return { port: number, token: accessToken(t.value) };
14914
+ } catch {
14915
+ return unusable(`has ${TOKEN_KEY} on line ${t.line} shorter than 32 characters or with control characters`);
14916
+ }
14917
+ }
14918
+ var code = (error) => error?.code ?? "error";
14919
+ function locate(path) {
14920
+ let real;
14921
+ try {
14922
+ real = realpathSync3(path);
14923
+ } catch (error) {
14924
+ if (code(error) !== "ENOENT")
14925
+ unusable(`cannot be read (${code(error)})`);
14926
+ let link = false;
14927
+ try {
14928
+ link = lstatSync8(path).isSymbolicLink();
14929
+ } catch {}
14930
+ return unusable(link ? "is a link to a file that does not exist" : "does not exist");
14931
+ }
14932
+ return { real, files: new PrivateFiles(dirname7(real), false) };
14933
+ }
14934
+ function explain(real) {
14935
+ const uid = process.getuid?.();
14936
+ try {
14937
+ const st = lstatSync8(real);
14938
+ if (!st.isFile())
14939
+ return "is not a regular file";
14940
+ if (uid !== undefined && st.uid !== uid)
14941
+ return "is not owned by you";
14942
+ if (uid !== undefined && lstatSync8(dirname7(real)).uid !== uid)
14943
+ return `is in ${dirname7(real)}, which is not owned by you`;
14944
+ if (st.nlink !== 1)
14945
+ return "has more than one hard link";
14946
+ if (st.size > CONFIG_BYTES)
14947
+ return "is larger than 64 KiB";
14948
+ return "changed while Cadence read it; try again";
14949
+ } catch (error) {
14950
+ return `cannot be read (${code(error)})`;
14951
+ }
14952
+ }
14953
+ function load(path) {
14954
+ const { real, files } = locate(path);
14955
+ let snapshot;
14956
+ try {
14957
+ snapshot = files.read(real, CONFIG_BYTES);
14958
+ } catch {
14959
+ return unusable(explain(real));
14960
+ }
14961
+ if (!snapshot)
14962
+ return unusable("does not exist");
14963
+ let source;
14964
+ try {
14965
+ source = decoded(snapshot);
14966
+ } catch {
14967
+ return unusable("is not UTF-8 text");
14968
+ }
14969
+ return { path, real, files, snapshot, source };
14970
+ }
14971
+ function decoded(snapshot) {
14972
+ return new TextDecoder("utf-8", { fatal: true }).decode(snapshot.bytes);
14973
+ }
14974
+ function refusing(path, read2) {
14975
+ try {
14976
+ return read2();
14977
+ } catch (error) {
14978
+ if (error instanceof ServerConfigRefusal)
14979
+ throw error;
14980
+ throw new ServerConfigRefusal(path, error instanceof Unusable ? error.message : `cannot be read (${code(error)})`);
14981
+ }
14982
+ }
14983
+ function readServerConfig(path) {
14984
+ return refusing(path, () => {
14985
+ const file = load(path);
14986
+ return { ...file, config: decode(file.source) };
14987
+ });
14988
+ }
14989
+ function withToken(file, token) {
14990
+ const { parts, token: line } = settings(decoded(file.snapshot));
14991
+ if (!line)
14992
+ throw new ContractError("invalid");
14993
+ const indent = /^\s*/.exec(parts[line.at])?.[0] ?? "";
14994
+ parts[line.at] = `${indent}${TOKEN_KEY}=${accessToken(token)}`;
14995
+ return parts.join("");
14996
+ }
14997
+
14998
+ // src/service/auth.ts
14810
14999
  function sameSecret(candidate, expected) {
14811
15000
  const a = createHash18("sha256").update(candidate).digest();
14812
15001
  const b = createHash18("sha256").update(expected).digest();
@@ -14842,16 +15031,50 @@ function cookieTokens(header, name) {
14842
15031
  return out;
14843
15032
  }
14844
15033
  function sourceConfig(source) {
14845
- if (source.environment !== undefined && source.tokenFile !== undefined)
15034
+ const given = [source.environment, source.tokenFile, source.serverConfig];
15035
+ if (given.filter((v) => v !== undefined).length > 1)
14846
15036
  throw new ContractError("invalid");
14847
15037
  if (source.environment !== undefined)
14848
- token(source.environment);
14849
- if (source.tokenFile !== undefined && (!isAbsolute4(source.tokenFile) || source.tokenFile.includes("\x00")))
15038
+ accessToken(source.environment);
15039
+ if (source.serverConfig !== undefined)
15040
+ accessToken(source.token);
15041
+ const path = source.tokenFile ?? source.serverConfig;
15042
+ if (path !== undefined && (!isAbsolute4(path) || path.includes("\x00")))
14850
15043
  throw new ContractError("invalid");
14851
15044
  return {
14852
15045
  schema: "cadence-service-access-config/1",
14853
- source: source.environment !== undefined ? "environment" : source.tokenFile !== undefined ? "file" : "generated",
14854
- path: source.tokenFile ?? null
15046
+ source: source.environment !== undefined ? "environment" : source.tokenFile !== undefined ? "file" : source.serverConfig !== undefined ? "config" : "generated",
15047
+ path: path ?? null
15048
+ };
15049
+ }
15050
+ function readSource(config) {
15051
+ if (config.source === "file" && config.path)
15052
+ readExternal(config.path);
15053
+ }
15054
+ function selectedMaterial(files, directory, source) {
15055
+ const config = sourceConfig(source);
15056
+ if (config.source !== "config")
15057
+ throw new ContractError("invalid");
15058
+ const marker2 = join11(directory, "access-config.json"), access = join11(directory, "access.json");
15059
+ const markerFile = files.read(marker2), accessFile = files.read(access);
15060
+ if (!markerFile || !accessFile)
15061
+ throw new ContractError("corrupt-selected");
15062
+ const previous = closed(json(markerFile), ["schema", "source", "path"]);
15063
+ literal(previous.schema, "cadence-service-access-config/1");
15064
+ oneOf(previous.source, ["generated", "environment", "file", "config"]);
15065
+ const r = closed(json(accessFile), ["schema", "generation", "token"]);
15066
+ literal(r.schema, "cadence-service-access/1");
15067
+ const generation = integer(r.generation, 1);
15068
+ if (previous.source === "generated" !== (r.token !== null))
15069
+ throw new ContractError("corrupt-selected");
15070
+ return {
15071
+ config,
15072
+ previous,
15073
+ generation,
15074
+ marker: marker2,
15075
+ markerFile,
15076
+ access,
15077
+ accessFile
14855
15078
  };
14856
15079
  }
14857
15080
 
@@ -14869,17 +15092,14 @@ class AccessMaterial {
14869
15092
  listeners = new Set;
14870
15093
  config;
14871
15094
  static validateSource(source) {
14872
- const config = sourceConfig(source);
14873
- if (config.path)
14874
- readExternal(config.path);
15095
+ readSource(sourceConfig(source));
14875
15096
  }
14876
15097
  static initialize(files, directory, source = {}) {
14877
15098
  const config = sourceConfig(source);
14878
- const marker2 = join10(directory, "access-config.json"), access = join10(directory, "access.json");
14879
- if (files.read(marker2) || files.read(access) || files.read(join10(directory, "registration.json")) || files.read(join10(directory, "owner.lock")))
15099
+ const marker2 = join11(directory, "access-config.json"), access = join11(directory, "access.json");
15100
+ if (files.read(marker2) || files.read(access) || files.read(join11(directory, "registration.json")) || files.read(join11(directory, "owner.lock")))
14880
15101
  throw new ContractError("conflict");
14881
- if (config.path)
14882
- readExternal(config.path);
15102
+ readSource(config);
14883
15103
  files.create(marker2, encoded(config));
14884
15104
  files.create(access, encoded({
14885
15105
  schema: "cadence-service-access/1",
@@ -14887,13 +15107,27 @@ class AccessMaterial {
14887
15107
  token: config.source === "generated" ? randomBytes3(32).toString("base64url") : null
14888
15108
  }));
14889
15109
  }
15110
+ static validateReselection(files, directory, source) {
15111
+ selectedMaterial(files, directory, source);
15112
+ }
15113
+ static reselect(files, directory, source) {
15114
+ const m = selectedMaterial(files, directory, source);
15115
+ if (encoded(m.previous) === encoded(m.config))
15116
+ return;
15117
+ files.replace(m.marker, m.markerFile, encoded(m.config));
15118
+ files.replace(m.access, m.accessFile, encoded({
15119
+ schema: "cadence-service-access/1",
15120
+ generation: integer(m.generation + 1, 1),
15121
+ token: null
15122
+ }));
15123
+ }
14890
15124
  constructor(files, directory, source = {}, assertOwner) {
14891
15125
  this.files = files;
14892
15126
  this.directory = directory;
14893
15127
  this.assertOwner = assertOwner;
14894
15128
  this.config = sourceConfig(source);
14895
- const configFile = files.read(join10(directory, "access-config.json"));
14896
- const accessFile = files.read(join10(directory, "access.json"));
15129
+ const configFile = files.read(join11(directory, "access-config.json"));
15130
+ const accessFile = files.read(join11(directory, "access.json"));
14897
15131
  if (!configFile || !accessFile)
14898
15132
  throw new ContractError("corrupt-selected");
14899
15133
  const c = closed(json(configFile), ["schema", "source", "path"]);
@@ -14903,17 +15137,17 @@ class AccessMaterial {
14903
15137
  this.record = {
14904
15138
  schema: literal(r.schema, "cadence-service-access/1"),
14905
15139
  generation: integer(r.generation, 1),
14906
- token: r.token === null ? null : token(r.token)
15140
+ token: r.token === null ? null : accessToken(r.token)
14907
15141
  };
14908
15142
  if (this.config.source === "generated" !== (this.record.token !== null))
14909
15143
  throw new ContractError("corrupt-selected");
14910
- if (this.config.path) {
15144
+ if (this.config.source === "file" && this.config.path) {
14911
15145
  const loaded = readExternal(this.config.path);
14912
15146
  this.external = loaded.files;
14913
15147
  this.externalFile = loaded.file;
14914
15148
  this.secret = loaded.token;
14915
15149
  } else
14916
- this.secret = token(this.record.token ?? source.environment);
15150
+ this.secret = accessToken(this.record.token ?? source.environment ?? source.token);
14917
15151
  this.configFile = configFile;
14918
15152
  this.accessFile = accessFile;
14919
15153
  }
@@ -14953,9 +15187,9 @@ class AccessMaterial {
14953
15187
  assert() {
14954
15188
  if (this.fenced)
14955
15189
  throw new ContractError("quarantine");
14956
- this.files.assert(join10(this.directory, "access-config.json"), this.configFile);
14957
- this.files.assert(join10(this.directory, "access.json"), this.accessFile);
14958
- if (this.config.path)
15190
+ this.files.assert(join11(this.directory, "access-config.json"), this.configFile);
15191
+ this.files.assert(join11(this.directory, "access.json"), this.accessFile);
15192
+ if (this.config.source === "file" && this.config.path)
14959
15193
  this.external?.assert(this.config.path, this.externalFile, TOKEN_BYTES);
14960
15194
  }
14961
15195
  redactionLiterals() {
@@ -14976,19 +15210,27 @@ class AccessMaterial {
14976
15210
  rotate() {
14977
15211
  if (this.config.source === "environment")
14978
15212
  throw new ContractError("unsupported-context");
15213
+ let configured;
15214
+ if (this.config.source === "config" && this.config.path) {
15215
+ configured = readServerConfig(this.config.path);
15216
+ if (!sameSecret(configured.config.token, this.secret))
15217
+ throw new ContractError("conflict");
15218
+ }
14979
15219
  try {
14980
15220
  this.assertRotation();
14981
15221
  const secret = randomBytes3(32).toString("base64url");
14982
15222
  const generation = integer(this.record.generation + 1, 1);
14983
15223
  if (this.config.path && this.external && this.externalFile)
14984
15224
  this.externalFile = this.external.replace(this.config.path, this.externalFile, secret, TOKEN_BYTES);
15225
+ if (configured)
15226
+ configured.files.replace(configured.real, configured.snapshot, withToken(configured, secret), CONFIG_BYTES);
14985
15227
  const record2 = {
14986
15228
  ...this.record,
14987
15229
  generation,
14988
15230
  token: this.config.source === "generated" ? secret : null
14989
15231
  };
14990
15232
  this.assertRotation();
14991
- this.accessFile = this.files.replace(join10(this.directory, "access.json"), this.accessFile, encoded(record2));
15233
+ this.accessFile = this.files.replace(join11(this.directory, "access.json"), this.accessFile, encoded(record2));
14992
15234
  this.assertRotation();
14993
15235
  this.record = record2;
14994
15236
  this.secret = secret;
@@ -15003,7 +15245,7 @@ class AccessMaterial {
15003
15245
  }
15004
15246
  }
15005
15247
  function readExternal(path) {
15006
- const files = new PrivateFiles(dirname7(path));
15248
+ const files = new PrivateFiles(dirname8(path));
15007
15249
  const file = files.read(path, TOKEN_BYTES);
15008
15250
  if (!file)
15009
15251
  throw new ContractError("corrupt-selected");
@@ -15011,7 +15253,7 @@ function readExternal(path) {
15011
15253
  return {
15012
15254
  files,
15013
15255
  file,
15014
- token: token(new TextDecoder("utf-8", { fatal: true }).decode(file.bytes))
15256
+ token: accessToken(new TextDecoder("utf-8", { fatal: true }).decode(file.bytes))
15015
15257
  };
15016
15258
  } catch {
15017
15259
  throw new ContractError("corrupt-selected");
@@ -15322,9 +15564,9 @@ class AuthNetwork {
15322
15564
 
15323
15565
  // src/service/coordination.ts
15324
15566
  import { randomBytes as randomBytes4, randomUUID as randomUUID8 } from "node:crypto";
15325
- import { opendirSync as opendirSync2, realpathSync as realpathSync3 } from "node:fs";
15567
+ import { opendirSync as opendirSync2, realpathSync as realpathSync4 } from "node:fs";
15326
15568
  import { hostname as hostname2 } from "node:os";
15327
- import { dirname as dirname8, isAbsolute as isAbsolute5, join as join11, relative as relative4, sep as sep4 } from "node:path";
15569
+ import { dirname as dirname9, isAbsolute as isAbsolute5, join as join12, relative as relative4, sep as sep4 } from "node:path";
15328
15570
  var registrationKeys = [
15329
15571
  "schema",
15330
15572
  "root",
@@ -15450,17 +15692,17 @@ class ServiceCoordination {
15450
15692
  stateHome;
15451
15693
  constructor(workspace, stateHome) {
15452
15694
  this.store = new TaskStore(resolveWorkspace(workspace).root, stateHome);
15453
- const namespace = dirname8(this.store.privateRoot);
15695
+ const namespace = dirname9(this.store.privateRoot);
15454
15696
  this.files = new PrivateFiles(namespace);
15455
- this.stateHome = dirname8(namespace);
15456
- this.directory = join11(this.store.privateRoot, "service-v1");
15457
- this.registryDirectory = join11(namespace, "service-registry-v1");
15697
+ this.stateHome = dirname9(namespace);
15698
+ this.directory = join12(this.store.privateRoot, "service-v1");
15699
+ this.registryDirectory = join12(namespace, "service-registry-v1");
15458
15700
  }
15459
15701
  path(name) {
15460
- return join11(this.directory, name);
15702
+ return join12(this.directory, name);
15461
15703
  }
15462
15704
  registryPath(name) {
15463
- return join11(this.registryDirectory, name);
15705
+ return join12(this.registryDirectory, name);
15464
15706
  }
15465
15707
  registry() {
15466
15708
  if (!this.files.directory(this.registryDirectory))
@@ -15480,7 +15722,7 @@ class ServiceCoordination {
15480
15722
  const o = closed(json(snapshot, REGISTRY_BYTES), ["schema", "entries"]);
15481
15723
  const entries = list(o.entries, REGISTRY_ENTRIES, decodeReservation);
15482
15724
  for (const [i, row] of entries.entries()) {
15483
- if (row.stateHome !== this.stateHome || realpathSync3(row.root) !== row.root)
15725
+ if (row.stateHome !== this.stateHome || realpathSync4(row.root) !== row.root)
15484
15726
  refused();
15485
15727
  if (entries.slice(0, i).some((other) => rootsOverlap(row.root, other.root) || row.endpoint === other.endpoint))
15486
15728
  refused();
@@ -15508,7 +15750,7 @@ class ServiceCoordination {
15508
15750
  "host-config.json",
15509
15751
  "history-v2"
15510
15752
  ]);
15511
- this.files.directory(join11(this.directory, "history-v2"), false, walks);
15753
+ this.files.directory(join12(this.directory, "history-v2"), false, walks);
15512
15754
  }
15513
15755
  selected() {
15514
15756
  const walks = new DirectoryWalks;
@@ -15668,7 +15910,7 @@ class ServiceCoordination {
15668
15910
  throw error;
15669
15911
  }
15670
15912
  }
15671
- acquire(input, resolvedEndpoint = input.endpoint, initializeNew) {
15913
+ acquire(input, resolvedEndpoint = input.endpoint, initializeNew, reselectExisting) {
15672
15914
  const config = decodeOwnerConfig(input);
15673
15915
  const bound = endpoint(resolvedEndpoint);
15674
15916
  if (!endpointMatches(config.endpoint, bound))
@@ -15683,7 +15925,8 @@ class ServiceCoordination {
15683
15925
  throw new ContractError("quarantine");
15684
15926
  if (this.store.inspectLock())
15685
15927
  throw new ContractError("conflict");
15686
- if (old && old.config.identity !== hash(encoded(config)))
15928
+ const reselect = !!old && old.config.identity !== hash(encoded(config));
15929
+ if (reselect && (!reselectExisting || !old?.hostConfig))
15687
15930
  throw new ContractError("conflict");
15688
15931
  const row = {
15689
15932
  root: this.store.root,
@@ -15700,7 +15943,17 @@ class ServiceCoordination {
15700
15943
  ...registry.value,
15701
15944
  entries: [...registry.value.entries, row]
15702
15945
  }), REGISTRY_BYTES);
15703
- this.initialize(config);
15946
+ if (old?.hostConfig && reselect && reselectExisting) {
15947
+ reselectExisting(old.hostConfig);
15948
+ const saved = this.files.replace(this.path("config.json"), old.config, encoded(config));
15949
+ this.files.replace(this.path("format.json"), old.manifest, encoded({
15950
+ schema: "cadence-service-format/1",
15951
+ root: this.store.root,
15952
+ stateHome: this.stateHome,
15953
+ configIdentity: saved.identity
15954
+ }));
15955
+ } else
15956
+ this.initialize(config);
15704
15957
  if (!old)
15705
15958
  initializeNew?.();
15706
15959
  const checkout = this.store.acquire();
@@ -16148,10 +16401,10 @@ function decodeSocketInput(value) {
16148
16401
  }
16149
16402
 
16150
16403
  // src/service/http.ts
16151
- function refusal(code = "invalid", uncertain = false) {
16404
+ function refusal(code2 = "invalid", uncertain = false) {
16152
16405
  return {
16153
16406
  kind: "error",
16154
- error: publicError(code, uncertain ? "uncertain" : "none")
16407
+ error: publicError(code2, uncertain ? "uncertain" : "none")
16155
16408
  };
16156
16409
  }
16157
16410
  function fixedHeaders(response) {
@@ -16463,7 +16716,7 @@ Content-Length: 0\r
16463
16716
  }
16464
16717
  if (target2 === "/" && req.method === "GET") {
16465
16718
  res.setHeader("Content-Type", "text/plain; charset=utf-8");
16466
- res.end("Cadence: enter with the private link that `cadence token-link --workspace <path> --reveal` prints on this machine (the `cadence server install` service uses ~/cadence by default).\n");
16719
+ res.end("Cadence: enter with the private link that `cadence server install` printed, http://127.0.0.1:<CADENCE_PORT>/?token=<CADENCE_TOKEN> from ~/.pi/agent/cadence/.env, or that `cadence token-link --workspace <path> --reveal` prints on this machine.\n");
16467
16720
  return;
16468
16721
  }
16469
16722
  if (target2.startsWith("/local/v1/admin/")) {
@@ -16544,9 +16797,9 @@ Content-Length: 0\r
16544
16797
  unavailable();
16545
16798
  if (route.operation === "session")
16546
16799
  this.attachment(caller);
16547
- const decoded = decodeHttpOutput(route.operation, value);
16800
+ const decoded2 = decodeHttpOutput(route.operation, value);
16548
16801
  const status = value && typeof value === "object" && "kind" in value && value.kind === "error" && "error" in value ? errorStatus(decodePublicError(value.error).code) : 200;
16549
- send(res, status, decoded);
16802
+ send(res, status, decoded2);
16550
16803
  } catch (e) {
16551
16804
  if (createdAttachment)
16552
16805
  this.detach(createdAttachment);
@@ -16555,9 +16808,9 @@ Content-Length: 0\r
16555
16808
  if (res.headersSent)
16556
16809
  res.destroy();
16557
16810
  else {
16558
- const code = e instanceof ContractError ? e.code : "unavailable";
16811
+ const code2 = e instanceof ContractError ? e.code : "unavailable";
16559
16812
  res.setHeader("Connection", "close");
16560
- send(res, errorStatus(code), refusal(code, dispatched));
16813
+ send(res, errorStatus(code2), refusal(code2, dispatched));
16561
16814
  }
16562
16815
  } finally {
16563
16816
  clearTimeout(timer);
@@ -17033,9 +17286,9 @@ class OwnerModelChoice {
17033
17286
  }
17034
17287
 
17035
17288
  // src/service/model-config.ts
17036
- import { lstatSync as lstatSync8, realpathSync as realpathSync4 } from "node:fs";
17037
- import { dirname as dirname9, isAbsolute as isAbsolute6, join as join12, resolve as resolve3 } from "node:path";
17038
- var CONFIG_BYTES = 1024 * 1024;
17289
+ import { lstatSync as lstatSync9, realpathSync as realpathSync5 } from "node:fs";
17290
+ import { dirname as dirname10, isAbsolute as isAbsolute6, join as join13, resolve as resolve3 } from "node:path";
17291
+ var CONFIG_BYTES2 = 1024 * 1024;
17039
17292
  var ENTRY_LIMIT = 256;
17040
17293
  function refuse2() {
17041
17294
  throw new ContractError("corrupt-selected");
@@ -17092,7 +17345,7 @@ function captureEnvironment(value) {
17092
17345
  if (typeof name !== "string" || !d || !("value" in d) || d.value !== undefined && typeof d.value !== "string")
17093
17346
  refuse2();
17094
17347
  bytes += Buffer.byteLength(name) + Buffer.byteLength(d.value ?? "");
17095
- if (bytes > CONFIG_BYTES)
17348
+ if (bytes > CONFIG_BYTES2)
17096
17349
  throw new ContractError("oversize");
17097
17350
  out[name] = d.value;
17098
17351
  }
@@ -17101,14 +17354,14 @@ function captureEnvironment(value) {
17101
17354
  function piSource(agentDir, home, cwd, env) {
17102
17355
  if (!isAbsolute6(home) || !isAbsolute6(cwd))
17103
17356
  refuse2();
17104
- let selected2 = agentDir ?? env.PI_CODING_AGENT_DIR ?? join12(home, ".pi", "agent");
17357
+ let selected2 = agentDir ?? env.PI_CODING_AGENT_DIR ?? join13(home, ".pi", "agent");
17105
17358
  if (!selected2 || selected2.includes("\x00"))
17106
17359
  refuse2();
17107
17360
  if (selected2 === "~" || selected2.startsWith("~/"))
17108
17361
  selected2 = home + selected2.slice(1);
17109
17362
  selected2 = resolve3(cwd, selected2);
17110
- const directory = join12(realpathSync4(dirname9(selected2)), selected2.slice(dirname9(selected2).length + 1));
17111
- if (!lstatSync8(directory).isDirectory() || lstatSync8(directory).isSymbolicLink())
17363
+ const directory = join13(realpathSync5(dirname10(selected2)), selected2.slice(dirname10(selected2).length + 1));
17364
+ if (!lstatSync9(directory).isDirectory() || lstatSync9(directory).isSymbolicLink())
17112
17365
  refuse2();
17113
17366
  const files = new PrivateFiles(directory, false);
17114
17367
  if (!files.directory(directory))
@@ -17116,8 +17369,8 @@ function piSource(agentDir, home, cwd, env) {
17116
17369
  return {
17117
17370
  directory,
17118
17371
  files,
17119
- authPath: join12(directory, "auth.json"),
17120
- modelsPath: join12(directory, "models.json")
17372
+ authPath: join13(directory, "auth.json"),
17373
+ modelsPath: join13(directory, "models.json")
17121
17374
  };
17122
17375
  }
17123
17376
  function templateNames(value) {
@@ -17396,7 +17649,7 @@ function modelJson(snapshot) {
17396
17649
  } else if (char === '"')
17397
17650
  quoted = true;
17398
17651
  }
17399
- return parseWireJson(normalized, CONFIG_BYTES);
17652
+ return parseWireJson(normalized, CONFIG_BYTES2);
17400
17653
  }
17401
17654
  function readPiSnapshot(source) {
17402
17655
  const { auth, credentials } = readPiAuth(source);
@@ -17404,11 +17657,11 @@ function readPiSnapshot(source) {
17404
17657
  return { auth, models, credentials, providers };
17405
17658
  }
17406
17659
  function readPiAuth(source) {
17407
- const auth = source.files.read(source.authPath, CONFIG_BYTES);
17660
+ const auth = source.files.read(source.authPath, CONFIG_BYTES2);
17408
17661
  if (!auth)
17409
17662
  refuse2();
17410
17663
  const credentials = Object.create(null);
17411
- for (const [provider, input] of Object.entries(data(json(auth, CONFIG_BYTES)))) {
17664
+ for (const [provider, input] of Object.entries(data(json(auth, CONFIG_BYTES2)))) {
17412
17665
  const credential = data(input);
17413
17666
  if (credential.type === "api_key") {
17414
17667
  keys2(credential, ["type", "key", "env"]);
@@ -17430,7 +17683,7 @@ function readPiAuth(source) {
17430
17683
  return { auth, credentials };
17431
17684
  }
17432
17685
  function readPiModels(source) {
17433
- const models = source.files.read(source.modelsPath, CONFIG_BYTES);
17686
+ const models = source.files.read(source.modelsPath, CONFIG_BYTES2);
17434
17687
  const providers = Object.create(null);
17435
17688
  if (models) {
17436
17689
  const root = data(modelJson(models));
@@ -17554,9 +17807,9 @@ function clonePrivate(value) {
17554
17807
  return out;
17555
17808
  };
17556
17809
  const encoded2 = JSON.stringify(visit(value, 0));
17557
- if (Buffer.byteLength(encoded2) > CONFIG_BYTES)
17810
+ if (Buffer.byteLength(encoded2) > CONFIG_BYTES2)
17558
17811
  throw new ContractError("oversize");
17559
- return parseWireJson(encoded2, CONFIG_BYTES);
17812
+ return parseWireJson(encoded2, CONFIG_BYTES2);
17560
17813
  }
17561
17814
 
17562
17815
  // src/service/model-context.ts
@@ -18510,7 +18763,7 @@ class ServiceOwner {
18510
18763
  }
18511
18764
 
18512
18765
  // src/service/runtime.ts
18513
- import { dirname as dirname10, isAbsolute as isAbsolute7, join as join13, resolve as resolve4 } from "node:path";
18766
+ import { dirname as dirname11, isAbsolute as isAbsolute7, join as join14, resolve as resolve4 } from "node:path";
18514
18767
  var STARTUP_INPUT_BYTES = 16 * 1024;
18515
18768
  function absolute(value) {
18516
18769
  const result = text(value, 4096, true);
@@ -18530,10 +18783,11 @@ function decodeHostConfig(value) {
18530
18783
  const tokenSource = oneOf(o.tokenSource, [
18531
18784
  "generated",
18532
18785
  "environment",
18533
- "file"
18786
+ "file",
18787
+ "config"
18534
18788
  ]);
18535
18789
  const tokenFile = o.tokenFile === null ? null : absolute(o.tokenFile);
18536
- if (o.tokenSource === "file" !== (tokenFile !== null))
18790
+ if ((tokenSource === "file" || tokenSource === "config") !== !!tokenFile)
18537
18791
  throw new ContractError("invalid");
18538
18792
  return {
18539
18793
  schema: literal(o.schema, "cadence-service-host/1"),
@@ -18544,6 +18798,15 @@ function decodeHostConfig(value) {
18544
18798
  tokenFile
18545
18799
  };
18546
18800
  }
18801
+ function reselectable(old, next) {
18802
+ const kept = (c) => encoded({
18803
+ ...c,
18804
+ http: { ...c.http, port: 0 },
18805
+ tokenSource: null,
18806
+ tokenFile: null
18807
+ });
18808
+ return next.tokenSource === "config" && kept(old) === kept(next);
18809
+ }
18547
18810
  function readHostConfig(c) {
18548
18811
  const owner = c.configuration();
18549
18812
  if (!owner)
@@ -18607,8 +18870,8 @@ function decodeHostInput(value) {
18607
18870
  class StartupRefusal extends ContractError {
18608
18871
  stage;
18609
18872
  quarantined;
18610
- constructor(code, stage, quarantined = false) {
18611
- super(code);
18873
+ constructor(code2, stage, quarantined = false) {
18874
+ super(code2);
18612
18875
  this.stage = stage;
18613
18876
  this.quarantined = quarantined;
18614
18877
  }
@@ -18629,13 +18892,13 @@ function startupReport(error) {
18629
18892
 
18630
18893
  // src/service/task-actions.ts
18631
18894
  import { randomUUID as randomUUID15 } from "node:crypto";
18632
- import { realpathSync as realpathSync5 } from "node:fs";
18895
+ import { realpathSync as realpathSync6 } from "node:fs";
18633
18896
 
18634
18897
  // src/task-diff.ts
18635
18898
  import { spawnSync as spawnSync2 } from "node:child_process";
18636
18899
  import { createHash as createHash20 } from "node:crypto";
18637
- import { lstatSync as lstatSync9, readdirSync as readdirSync3, readlinkSync } from "node:fs";
18638
- import { join as join14, relative as relative5 } from "node:path";
18900
+ import { lstatSync as lstatSync10, readdirSync as readdirSync3, readlinkSync } from "node:fs";
18901
+ import { join as join15, relative as relative5 } from "node:path";
18639
18902
  var admin = (path) => path === ".cadence/assistant/tasks" || path.startsWith(".cadence/assistant/tasks/");
18640
18903
  function captureChanges(root) {
18641
18904
  const deadline = Date.now() + 3000;
@@ -18676,8 +18939,8 @@ function captureChanges(root) {
18676
18939
  return;
18677
18940
  }
18678
18941
  try {
18679
- const path = join14(root, name);
18680
- const st = lstatSync9(path);
18942
+ const path = join15(root, name);
18943
+ const st = lstatSync10(path);
18681
18944
  add(name, `${st.mode}:${st.size}`);
18682
18945
  if (st.isSymbolicLink()) {
18683
18946
  add(name, readlinkSync(path));
@@ -18812,7 +19075,7 @@ ${data2.toString("utf8")}`);
18812
19075
  return;
18813
19076
  }
18814
19077
  for (const name of names2) {
18815
- const path = join14(dir, name);
19078
+ const path = join15(dir, name);
18816
19079
  const rel = relative5(root, path).split("\\").join("/");
18817
19080
  if (rel === ".git" || admin(rel))
18818
19081
  continue;
@@ -18821,7 +19084,7 @@ ${data2.toString("utf8")}`);
18821
19084
  break;
18822
19085
  }
18823
19086
  try {
18824
- const st = lstatSync9(path);
19087
+ const st = lstatSync10(path);
18825
19088
  if (st.isDirectory())
18826
19089
  walk(path, depth + 1);
18827
19090
  else
@@ -19108,9 +19371,9 @@ class ActionAdmission {
19108
19371
  });
19109
19372
  }
19110
19373
  existing(record2, commandId, requested) {
19111
- const decoded = decodeStoredCommand(record2);
19112
- const receipt = decoded.receipt;
19113
- if (receipt.commandId !== commandId || canonicalJson(decoded.identity) !== canonicalJson(requested))
19374
+ const decoded2 = decodeStoredCommand(record2);
19375
+ const receipt = decoded2.receipt;
19376
+ if (receipt.commandId !== commandId || canonicalJson(decoded2.identity) !== canonicalJson(requested))
19114
19377
  return error(publicError("conflict"));
19115
19378
  return ok(receipt);
19116
19379
  }
@@ -19375,7 +19638,7 @@ class ActionAdmission {
19375
19638
 
19376
19639
  // src/service/graph.ts
19377
19640
  import { createHash as createHash21 } from "node:crypto";
19378
- import { join as join15 } from "node:path";
19641
+ import { join as join16 } from "node:path";
19379
19642
  var hash3 = (text2) => createHash21("sha256").update(text2).digest("hex");
19380
19643
 
19381
19644
  class GraphContext {
@@ -19385,7 +19648,7 @@ class GraphContext {
19385
19648
  identity;
19386
19649
  constructor(root) {
19387
19650
  this.root = root;
19388
- const bytes = readBounded(join15(root, "AGENTS.md"), 32768);
19651
+ const bytes = readBounded(join16(root, "AGENTS.md"), 32768);
19389
19652
  this.hints = bytes ? new TextDecoder("utf-8", { fatal: true }).decode(bytes) : "";
19390
19653
  this.identity = hash3(canonicalJson({
19391
19654
  hints: bytes === undefined ? null : hash3(bytes),
@@ -19522,7 +19785,7 @@ import { randomUUID as randomUUID14 } from "node:crypto";
19522
19785
 
19523
19786
  // src/subagent-process.ts
19524
19787
  import { spawn } from "node:child_process";
19525
- import { join as join16 } from "node:path";
19788
+ import { join as join17 } from "node:path";
19526
19789
  import { StringDecoder } from "node:string_decoder";
19527
19790
  import { fileURLToPath as fileURLToPath3 } from "node:url";
19528
19791
  // src/worker-capture.mjs
@@ -20107,10 +20370,10 @@ class WorkerRecording {
20107
20370
  // src/subagent-process.ts
20108
20371
  function terminateWindowsTree(pid, fallback) {
20109
20372
  const root = process.env.SystemRoot;
20110
- const taskkill = spawn(root ? join16(root, "System32", "taskkill.exe") : "taskkill.exe", ["/F", "/T", "/PID", String(pid)], { stdio: "ignore", windowsHide: true });
20373
+ const taskkill = spawn(root ? join17(root, "System32", "taskkill.exe") : "taskkill.exe", ["/F", "/T", "/PID", String(pid)], { stdio: "ignore", windowsHide: true });
20111
20374
  taskkill.once("error", fallback);
20112
- taskkill.once("close", (code) => {
20113
- if (code !== 0)
20375
+ taskkill.once("close", (code2) => {
20376
+ if (code2 !== 0)
20114
20377
  fallback();
20115
20378
  });
20116
20379
  }
@@ -20260,17 +20523,17 @@ function runProcess(spec) {
20260
20523
  const value = parseCaptureLine(line);
20261
20524
  if (!capture)
20262
20525
  capture = new CaptureState(spec.childRunId ?? decodeCaptureFrame(value).childRunId, spec.expectedRequest);
20263
- const decoded = capture.accept(value, Buffer.byteLength(line) + 1);
20264
- if (decoded.event.kind === "ready") {
20526
+ const decoded2 = capture.accept(value, Buffer.byteLength(line) + 1);
20527
+ if (decoded2.event.kind === "ready") {
20265
20528
  ready2 = true;
20266
20529
  spec.onProgress?.("Agent ready");
20267
20530
  }
20268
- if (decoded.event.kind === "tool-start")
20269
- spec.onProgress?.(clean(`Tool: ${decoded.event.name}`, 128));
20270
- if (decoded.event.kind === "final")
20531
+ if (decoded2.event.kind === "tool-start")
20532
+ spec.onProgress?.(clean(`Tool: ${decoded2.event.name}`, 128));
20533
+ if (decoded2.event.kind === "final")
20271
20534
  final = capture.preview;
20272
20535
  if (lease) {
20273
- spec.recording?.accept(decoded, lease);
20536
+ spec.recording?.accept(decoded2, lease);
20274
20537
  lease = undefined;
20275
20538
  }
20276
20539
  } catch {
@@ -20372,7 +20635,7 @@ function runProcess(spec) {
20372
20635
  child.on("error", () => {
20373
20636
  stopping = "failed";
20374
20637
  });
20375
- child.on("close", (code, signal) => {
20638
+ child.on("close", (code2, signal) => {
20376
20639
  nativeClosed = true;
20377
20640
  clearTimeout(timeout);
20378
20641
  clearTimeout(killTimer);
@@ -20388,7 +20651,7 @@ function runProcess(spec) {
20388
20651
  }
20389
20652
  }
20390
20653
  output += stderrDecoder.end();
20391
- finish(true, code, signal);
20654
+ finish(true, code2, signal);
20392
20655
  };
20393
20656
  if (spec.checkRecording) {
20394
20657
  Promise.all([stdoutWork, stderrWork]).then(async () => {
@@ -21641,9 +21904,9 @@ class TaskActions {
21641
21904
  selection: structuredClone(options.selection),
21642
21905
  ownerSecrets: [...options.ownerSecrets]
21643
21906
  };
21644
- this.cwd = realpathSync5(options.cwd);
21907
+ this.cwd = realpathSync6(options.cwd);
21645
21908
  const config = readHostConfig(options.host.coordination);
21646
- if (!config || realpathSync5(config.cwd) !== this.cwd)
21909
+ if (!config || realpathSync6(config.cwd) !== this.cwd)
21647
21910
  throw new ContractError("unsupported-context");
21648
21911
  this.launchCwd = config.cwd;
21649
21912
  this.checkEnvironment = options.host.checkEnvironment;
@@ -21759,7 +22022,7 @@ class TaskActions {
21759
22022
  if (this.closed || this.options.host.owner.state !== "idle" || this.options.host.writer.quarantined)
21760
22023
  throw new ContractError("quarantine");
21761
22024
  this.options.host.owner.lease.assert();
21762
- if (!this.sources.current() || realpathSync5(this.cwd) !== this.cwd || realpathSync5(this.launchCwd) !== this.cwd || this.options.host.checkEnvironment !== this.checkEnvironment)
22025
+ if (!this.sources.current() || realpathSync6(this.cwd) !== this.cwd || realpathSync6(this.launchCwd) !== this.cwd || this.options.host.checkEnvironment !== this.checkEnvironment)
21763
22026
  throw new ContractError("stale");
21764
22027
  }
21765
22028
  choiceSnapshot() {
@@ -21877,7 +22140,7 @@ class TaskActions {
21877
22140
  const selected2 = taskId ? h.coordination.store.loadSnapshot(taskId) : null;
21878
22141
  if (taskId && !selected2)
21879
22142
  throw new ContractError("stale");
21880
- if (selected2 && realpathSync5(selected2.record.cwd) !== this.cwd)
22143
+ if (selected2 && realpathSync6(selected2.record.cwd) !== this.cwd)
21881
22144
  throw new ContractError("unsupported-context");
21882
22145
  const secrets = this.viewSecrets();
21883
22146
  const roles = this.sources.roles.map(({ role: role2, sha256 }) => ({
@@ -22381,7 +22644,7 @@ class TaskActions {
22381
22644
  this.assertChoice(expected);
22382
22645
  held.assertHeld();
22383
22646
  const actual = h.coordination.store.loadSnapshot(task.id), want = published ?? task;
22384
- if (!actual || actual.identity !== want.identity || actual.record.revision !== want.revision || realpathSync5(actual.record.cwd) !== this.cwd || this.generation !== expected.checkoutGeneration || h.writer.controlRevision(task.id) !== expected.controlRev + 1 || this.scheduler.activeTask(task.id))
22647
+ 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))
22385
22648
  throw new ContractError("stale");
22386
22649
  const changes2 = captureChanges(h.coordination.store.root);
22387
22650
  if (changes2.fingerprint !== expected.observation.identity || changes2.complete !== expected.observation.complete)
@@ -22585,8 +22848,8 @@ function legacy(store, budget, reader) {
22585
22848
  decodeId(entry.name);
22586
22849
  if (created.has(entry.name))
22587
22850
  continue;
22588
- const path = join17(store.tasks, entry.name, "task.json");
22589
- const size = lstatSync10(path).size;
22851
+ const path = join18(store.tasks, entry.name, "task.json");
22852
+ const size = lstatSync11(path).size;
22590
22853
  if (size > 128 * 1024)
22591
22854
  throw new ContractError("corrupt-selected");
22592
22855
  budget.reserve(size);
@@ -22632,12 +22895,19 @@ async function createServiceHost(value, sourceBuild) {
22632
22895
  if (resolveWorkspace(input.config.cwd).root !== c.store.root)
22633
22896
  throw new ContractError("conflict");
22634
22897
  const old = readHostConfig(c);
22635
- if (old && encoded(old) !== encoded(input.config))
22898
+ const reselect = !!old && encoded(old) !== encoded(input.config);
22899
+ if (old && reselect && !reselectable(old, input.config))
22900
+ throw new ContractError("conflict");
22901
+ const configured = input.config.tokenSource === "config" && input.config.tokenFile ? readServerConfig(input.config.tokenFile).config : undefined;
22902
+ if (configured && configured.port !== input.config.http.port)
22636
22903
  throw new ContractError("conflict");
22637
- const source = input.environmentToken !== null ? { environment: input.environmentToken } : input.config.tokenFile ? { tokenFile: input.config.tokenFile } : {};
22904
+ const source = input.environmentToken !== null ? { environment: input.environmentToken } : configured && input.config.tokenFile ? { serverConfig: input.config.tokenFile, token: configured.token } : input.config.tokenFile ? { tokenFile: input.config.tokenFile } : {};
22638
22905
  AccessMaterial.validateSource(source);
22639
22906
  if (old) {
22640
- new AccessMaterial(c.files, c.directory, source);
22907
+ if (reselect)
22908
+ AccessMaterial.validateReselection(c.files, c.directory, source);
22909
+ else
22910
+ new AccessMaterial(c.files, c.directory, source);
22641
22911
  const history = HistoryFiles.open(c);
22642
22912
  if (!history)
22643
22913
  throw new ContractError("corrupt-selected");
@@ -22666,7 +22936,12 @@ async function createServiceHost(value, sourceBuild) {
22666
22936
  }, endpoint2, () => {
22667
22937
  c.files.create(c.path("host-config.json"), encoded(resolved));
22668
22938
  AccessMaterial.initialize(c.files, c.directory, source);
22669
- });
22939
+ }, reselect && old ? (previous) => {
22940
+ if (previous.identity !== hash(encoded(old)))
22941
+ throw new ContractError("conflict");
22942
+ AccessMaterial.reselect(c.files, c.directory, source);
22943
+ c.files.replace(c.path("host-config.json"), previous, encoded(resolved));
22944
+ } : undefined);
22670
22945
  let writer, live;
22671
22946
  owner = new ServiceOwner(lease, {
22672
22947
  quiesce: () => http.quiesce(),