@abelxiaoxing/cadence 2.0.15 → 2.0.17
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.
- package/README.md +90 -20
- package/docs/service-management.md +58 -5
- package/package.json +2 -2
- package/src/service/coordination.ts +26 -9
- package/src/service-cli.mjs +1326 -922
- package/src/service-host.mjs +326 -254
- package/web/dist/assets/cadence.css +1 -1
- package/web/dist/assets/cadence.js +11 -11
- package/web/dist/manifest.json +6 -6
package/src/service-cli.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { spawn } from "node:child_process";
|
|
|
6
6
|
import { mkdirSync as mkdirSync6 } from "node:fs";
|
|
7
7
|
import { request } from "node:http";
|
|
8
8
|
import { homedir as homedir6 } from "node:os";
|
|
9
|
-
import { dirname as dirname13, join as
|
|
9
|
+
import { dirname as dirname13, join as join16, resolve as resolve7 } from "node:path";
|
|
10
10
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
11
11
|
|
|
12
12
|
// src/install-config.mjs
|
|
@@ -3233,6 +3233,7 @@ function buildUnit(u) {
|
|
|
3233
3233
|
"KillMode=mixed",
|
|
3234
3234
|
"TimeoutStopSec=90",
|
|
3235
3235
|
"Restart=on-failure",
|
|
3236
|
+
"RestartPreventExitStatus=78",
|
|
3236
3237
|
"RestartSec=10",
|
|
3237
3238
|
"",
|
|
3238
3239
|
"[Install]",
|
|
@@ -3426,7 +3427,7 @@ function autostartStatus(s) {
|
|
|
3426
3427
|
|
|
3427
3428
|
// src/service/coordination.ts
|
|
3428
3429
|
import { randomBytes as randomBytes2, randomUUID as randomUUID3 } from "node:crypto";
|
|
3429
|
-
import { opendirSync
|
|
3430
|
+
import { opendirSync } from "node:fs";
|
|
3430
3431
|
import { hostname as hostname2 } from "node:os";
|
|
3431
3432
|
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join5, relative as relative3, sep as sep4 } from "node:path";
|
|
3432
3433
|
var registrationKeys = [
|
|
@@ -3562,19 +3563,25 @@ function recoveryDigest(registry, selected) {
|
|
|
3562
3563
|
}
|
|
3563
3564
|
|
|
3564
3565
|
class ServiceCoordination {
|
|
3565
|
-
|
|
3566
|
+
project;
|
|
3566
3567
|
files;
|
|
3567
|
-
directory;
|
|
3568
3568
|
registryDirectory;
|
|
3569
3569
|
stateHome;
|
|
3570
3570
|
constructor(workspace, stateHome) {
|
|
3571
|
-
this.
|
|
3572
|
-
|
|
3571
|
+
this.project = workspace === null ? undefined : new TaskStore(resolveWorkspace(workspace).root, stateHome);
|
|
3572
|
+
this.stateHome = this.project ? dirname5(dirname5(this.project.privateRoot)) : canonicalAmbientRoot(stateHome ?? defaultStateHome());
|
|
3573
|
+
const namespace = join5(this.stateHome, "cadence-assistant-v1");
|
|
3573
3574
|
this.files = new PrivateFiles(namespace);
|
|
3574
|
-
this.stateHome = dirname5(namespace);
|
|
3575
|
-
this.directory = join5(this.store.privateRoot, "service-v1");
|
|
3576
3575
|
this.registryDirectory = join5(namespace, "service-registry-v1");
|
|
3577
3576
|
}
|
|
3577
|
+
get store() {
|
|
3578
|
+
if (!this.project)
|
|
3579
|
+
throw new ContractError("forbidden");
|
|
3580
|
+
return this.project;
|
|
3581
|
+
}
|
|
3582
|
+
get directory() {
|
|
3583
|
+
return join5(this.store.privateRoot, "service-v1");
|
|
3584
|
+
}
|
|
3578
3585
|
path(name) {
|
|
3579
3586
|
return join5(this.directory, name);
|
|
3580
3587
|
}
|
|
@@ -3595,7 +3602,7 @@ class ServiceCoordination {
|
|
|
3595
3602
|
const o = closed(json(snapshot, REGISTRY_BYTES), ["schema", "entries"]);
|
|
3596
3603
|
const entries = list(o.entries, REGISTRY_ENTRIES, decodeReservation);
|
|
3597
3604
|
for (const [i, row] of entries.entries()) {
|
|
3598
|
-
if (row.stateHome !== this.stateHome ||
|
|
3605
|
+
if (row.stateHome !== this.stateHome || canonicalAmbientRoot(row.root) !== row.root)
|
|
3599
3606
|
refused();
|
|
3600
3607
|
if (entries.slice(0, i).some((other) => rootsOverlap(row.root, other.root) || row.endpoint === other.endpoint))
|
|
3601
3608
|
refused();
|
|
@@ -3787,6 +3794,7 @@ class ServiceCoordination {
|
|
|
3787
3794
|
}
|
|
3788
3795
|
}
|
|
3789
3796
|
acquire(input, resolvedEndpoint = input.endpoint, initializeNew, reselectExisting) {
|
|
3797
|
+
this.store;
|
|
3790
3798
|
const config = decodeOwnerConfig(input);
|
|
3791
3799
|
const bound = endpoint(resolvedEndpoint);
|
|
3792
3800
|
if (!endpointMatches(config.endpoint, bound))
|
|
@@ -4207,7 +4215,7 @@ class OwnerLease {
|
|
|
4207
4215
|
|
|
4208
4216
|
// src/service/gateway-cli.ts
|
|
4209
4217
|
import { homedir as homedir5 } from "node:os";
|
|
4210
|
-
import { dirname as
|
|
4218
|
+
import { dirname as dirname10, join as join12, resolve as resolve6 } from "node:path";
|
|
4211
4219
|
import { fileURLToPath } from "node:url";
|
|
4212
4220
|
|
|
4213
4221
|
// src/service/config.ts
|
|
@@ -4261,6 +4269,7 @@ function localOrigin(policy, actualPort) {
|
|
|
4261
4269
|
// src/service/gateway-store.ts
|
|
4262
4270
|
import { randomBytes as randomBytes4, randomUUID as randomUUID5 } from "node:crypto";
|
|
4263
4271
|
import { opendirSync as opendirSync2 } from "node:fs";
|
|
4272
|
+
import { hostname as hostname3 } from "node:os";
|
|
4264
4273
|
import { join as join7, resolve as resolve4 } from "node:path";
|
|
4265
4274
|
|
|
4266
4275
|
// src/service/auth.ts
|
|
@@ -4635,6 +4644,8 @@ function decodeGatewayProject(value) {
|
|
|
4635
4644
|
return project;
|
|
4636
4645
|
}
|
|
4637
4646
|
function registration(value) {
|
|
4647
|
+
const hasHost = !!value && typeof value === "object" && Object.hasOwn(value, "host");
|
|
4648
|
+
const hasRecovery = !!value && typeof value === "object" && Object.hasOwn(value, "recovery");
|
|
4638
4649
|
const o = closed(value, [
|
|
4639
4650
|
"schema",
|
|
4640
4651
|
"epoch",
|
|
@@ -4643,8 +4654,22 @@ function registration(value) {
|
|
|
4643
4654
|
"capability",
|
|
4644
4655
|
"selection",
|
|
4645
4656
|
"pid",
|
|
4646
|
-
"phase"
|
|
4657
|
+
"phase",
|
|
4658
|
+
...hasHost ? ["host"] : [],
|
|
4659
|
+
...hasRecovery ? ["recovery"] : []
|
|
4647
4660
|
]);
|
|
4661
|
+
let recovery;
|
|
4662
|
+
if (hasRecovery) {
|
|
4663
|
+
const r = closed(o.recovery, ["receipt", "sha256", "stamp"]);
|
|
4664
|
+
const receipt = text(r.receipt, 128, true);
|
|
4665
|
+
if (!/^recovery-v1\/[a-f0-9]{64}\/(?:0|[1-9][0-9]{0,2})\.json$/.test(receipt))
|
|
4666
|
+
throw new ContractError("corrupt-selected");
|
|
4667
|
+
recovery = {
|
|
4668
|
+
receipt,
|
|
4669
|
+
sha256: decodeHash(r.sha256),
|
|
4670
|
+
stamp: text(r.stamp, 512, true)
|
|
4671
|
+
};
|
|
4672
|
+
}
|
|
4648
4673
|
const endpoint2 = text(o.endpoint, 128, true), capability = text(o.capability, 43, true);
|
|
4649
4674
|
if (!/^http:\/\/(?:127\.0\.0\.1|\[::1\]):[1-9][0-9]{0,4}$/.test(endpoint2) || !/^[A-Za-z0-9_-]{43}$/.test(capability))
|
|
4650
4675
|
throw new ContractError("corrupt-selected");
|
|
@@ -4655,7 +4680,9 @@ function registration(value) {
|
|
|
4655
4680
|
endpoint: endpoint2,
|
|
4656
4681
|
capability,
|
|
4657
4682
|
selection: decodeHash(o.selection),
|
|
4658
|
-
pid: integer(o.pid, 1),
|
|
4683
|
+
pid: integer(o.pid, 1, 2147483647),
|
|
4684
|
+
...hasHost ? { host: text(o.host, 255, true) } : {},
|
|
4685
|
+
...recovery ? { recovery } : {},
|
|
4659
4686
|
phase: oneOf(o.phase, ["alive", "stopping", "stopped", "quarantined"])
|
|
4660
4687
|
};
|
|
4661
4688
|
}
|
|
@@ -4715,6 +4742,8 @@ class GatewayStore {
|
|
|
4715
4742
|
const previous = this.read();
|
|
4716
4743
|
if (previous && previous.phase !== "stopped")
|
|
4717
4744
|
throw new ContractError("quarantine");
|
|
4745
|
+
if (previous?.recovery)
|
|
4746
|
+
this.associatedProjects();
|
|
4718
4747
|
this.lock = this.files.create(this.path("gateway.lock"), encoded({ epoch: randomUUID5() }));
|
|
4719
4748
|
const source = { serverConfig: this.serverConfig, token };
|
|
4720
4749
|
if (!previous)
|
|
@@ -4734,6 +4763,8 @@ class GatewayStore {
|
|
|
4734
4763
|
capability: randomBytes4(32).toString("base64url"),
|
|
4735
4764
|
selection: hash(inputBytes),
|
|
4736
4765
|
pid: process.pid,
|
|
4766
|
+
host: hostname3(),
|
|
4767
|
+
...previous?.recovery ? { recovery: previous.recovery } : {},
|
|
4737
4768
|
phase: "alive"
|
|
4738
4769
|
};
|
|
4739
4770
|
this.selected = this.selected ? this.files.replace(this.path("gateway.json"), this.selected, encoded(this.value)) : this.files.create(this.path("gateway.json"), encoded(this.value));
|
|
@@ -4777,6 +4808,21 @@ class GatewayStore {
|
|
|
4777
4808
|
}
|
|
4778
4809
|
return out;
|
|
4779
4810
|
}
|
|
4811
|
+
associatedProjects() {
|
|
4812
|
+
const projects = this.projects(), recovery = this.read()?.recovery;
|
|
4813
|
+
if (!recovery)
|
|
4814
|
+
return projects;
|
|
4815
|
+
const receipt = this.files.read(this.path(recovery.receipt), 65536);
|
|
4816
|
+
if (!receipt || receipt.identity !== recovery.sha256 || receipt.stamp !== recovery.stamp)
|
|
4817
|
+
throw new ContractError("corrupt-selected");
|
|
4818
|
+
const value = json(receipt, 65536);
|
|
4819
|
+
literal(value.schema, "cadence-workbench-recovery/1");
|
|
4820
|
+
const retired = list(value.retiredProjects, 128, (entry) => {
|
|
4821
|
+
const row = closed(entry, ["id", "stamp"]);
|
|
4822
|
+
return { id: decodeHash(row.id), stamp: text(row.stamp, 512, true) };
|
|
4823
|
+
});
|
|
4824
|
+
return projects.filter((project) => !retired.some((row) => row.id === project.id && row.stamp === this.files.read(this.path(`projects/${project.id}.json`))?.stamp));
|
|
4825
|
+
}
|
|
4780
4826
|
saveProject(project) {
|
|
4781
4827
|
this.assert();
|
|
4782
4828
|
const p = decodeGatewayProject(project), path2 = this.path(`projects/${p.id}.json`), before = this.files.read(path2);
|
|
@@ -4792,827 +4838,285 @@ class GatewayStore {
|
|
|
4792
4838
|
}
|
|
4793
4839
|
}
|
|
4794
4840
|
|
|
4795
|
-
// src/service/
|
|
4796
|
-
import {
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
const
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
"
|
|
4811
|
-
"tokenSource",
|
|
4812
|
-
"tokenFile"
|
|
4813
|
-
]);
|
|
4814
|
-
const tokenSource = oneOf(o.tokenSource, [
|
|
4815
|
-
"generated",
|
|
4816
|
-
"environment",
|
|
4817
|
-
"file",
|
|
4818
|
-
"config"
|
|
4819
|
-
]);
|
|
4820
|
-
const tokenFile = o.tokenFile === null ? null : absolute(o.tokenFile);
|
|
4821
|
-
if ((tokenSource === "file" || tokenSource === "config") !== !!tokenFile)
|
|
4822
|
-
throw new ContractError("invalid");
|
|
4841
|
+
// src/service/recovery-cli.ts
|
|
4842
|
+
import { isatty } from "node:tty";
|
|
4843
|
+
|
|
4844
|
+
// src/history/recovery.ts
|
|
4845
|
+
import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
|
|
4846
|
+
import { lstatSync as lstatSync7, opendirSync as opendirSync4 } from "node:fs";
|
|
4847
|
+
import { join as join9, relative as relative4 } from "node:path";
|
|
4848
|
+
|
|
4849
|
+
// src/service/recovery.ts
|
|
4850
|
+
async function inspectRecovery(coordination, history) {
|
|
4851
|
+
const selected = coordination.recoverySnapshot();
|
|
4852
|
+
const facts = history ? await history.inspect(selected.registration.epoch) : null;
|
|
4853
|
+
if (facts)
|
|
4854
|
+
decodeHash(facts.digest);
|
|
4855
|
+
if (coordination.recoverySnapshot().digest !== selected.digest)
|
|
4856
|
+
throw new ContractError("conflict");
|
|
4823
4857
|
return {
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
tokenFile
|
|
4858
|
+
epoch: selected.registration.epoch,
|
|
4859
|
+
coordinationDigest: selected.digest,
|
|
4860
|
+
history: facts,
|
|
4861
|
+
owner: recoveryOwnerStatus(selected.registration.pid, selected.registration.host),
|
|
4862
|
+
resolution: "local-interactive-only"
|
|
4830
4863
|
};
|
|
4831
4864
|
}
|
|
4832
|
-
function
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
const
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
const
|
|
4840
|
-
if (
|
|
4865
|
+
async function resolveRecovery(coordination, confirmation, history) {
|
|
4866
|
+
if (typeof confirmation.assertLocalAuthority !== "function" || confirmation.acknowledgeEscapedProcesses !== true || confirmation.processes !== "externally-resolved" || confirmation.credentials !== "independently-resolved" || confirmation.storage !== "independently-resolved")
|
|
4867
|
+
throw new ContractError("forbidden");
|
|
4868
|
+
const expected = structuredClone(confirmation.expected);
|
|
4869
|
+
const authority = () => confirmation.assertLocalAuthority(structuredClone(expected));
|
|
4870
|
+
authority();
|
|
4871
|
+
const release = coordination.prepareRecoveryRelease(expected.coordinationDigest, authority);
|
|
4872
|
+
const current = await inspectRecovery(coordination, history);
|
|
4873
|
+
if (JSON.stringify(current) !== JSON.stringify(expected))
|
|
4841
4874
|
throw new ContractError("conflict");
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
try {
|
|
4848
|
-
if (facts.NODE_OPTIONS.present)
|
|
4849
|
-
throw new Error;
|
|
4850
|
-
route = sdkNetworkEnvironment(facts, networkSnapshot(undefined));
|
|
4851
|
-
} catch {
|
|
4852
|
-
throw new ContractError("unsupported-context");
|
|
4853
|
-
}
|
|
4854
|
-
const result = Object.create(null);
|
|
4855
|
-
for (const key of Object.keys(ambient)) {
|
|
4856
|
-
const d = Object.getOwnPropertyDescriptor(ambient, key);
|
|
4857
|
-
if (!d || !("value" in d) || typeof d.value !== "string")
|
|
4858
|
-
throw new ContractError("invalid");
|
|
4859
|
-
if (/^(?:CADENCE_|PI_|ABEL_|TRELLIS_|GIT_)/i.test(key) || networkKeys.some((name) => name === key))
|
|
4860
|
-
continue;
|
|
4861
|
-
result[key] = d.value;
|
|
4862
|
-
}
|
|
4863
|
-
if (route.redactionLiterals.length) {
|
|
4864
|
-
result.HTTP_PROXY = route.env.HTTP_PROXY;
|
|
4865
|
-
result.HTTPS_PROXY = route.env.HTTPS_PROXY;
|
|
4866
|
-
result.NODE_USE_ENV_PROXY = "1";
|
|
4867
|
-
}
|
|
4868
|
-
result.PI_OFFLINE = "1";
|
|
4869
|
-
result.NO_COLOR = "1";
|
|
4870
|
-
if (!sameNetwork(facts, networkSnapshot(ambient)))
|
|
4871
|
-
throw new ContractError("stale");
|
|
4872
|
-
return result;
|
|
4873
|
-
}
|
|
4874
|
-
function selectStart(options, ambient) {
|
|
4875
|
-
const workspace = resolveWorkspace(options.workspace), c = new ServiceCoordination(workspace.root, options.stateHome);
|
|
4876
|
-
const old = readHostConfig(c);
|
|
4877
|
-
const env = Object.getOwnPropertyDescriptor(ambient, "CADENCE_WEB_TOKEN");
|
|
4878
|
-
if (env && (!("value" in env) || typeof env.value !== "string"))
|
|
4879
|
-
throw new ContractError("invalid");
|
|
4880
|
-
let fromFile;
|
|
4881
|
-
if (options.config) {
|
|
4882
|
-
const path2 = resolve5(options.config), f = new PrivateFiles(dirname7(path2));
|
|
4883
|
-
const bytes = f.read(path2);
|
|
4884
|
-
if (!bytes)
|
|
4885
|
-
throw new ContractError("corrupt-selected");
|
|
4886
|
-
fromFile = decodeHostConfig(json(bytes));
|
|
4887
|
-
}
|
|
4888
|
-
const base = old ?? fromFile ?? decodeHostConfig({
|
|
4889
|
-
schema: "cadence-service-host/1",
|
|
4890
|
-
cwd: workspace.cwd,
|
|
4891
|
-
http: {
|
|
4892
|
-
schema: "cadence-service-http/1",
|
|
4893
|
-
host: options.host ?? "127.0.0.1",
|
|
4894
|
-
port: options.port ?? 17423,
|
|
4895
|
-
proxy: null
|
|
4896
|
-
},
|
|
4897
|
-
agentDir: resolve5(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join8(ambient.HOME ?? ambient.USERPROFILE ?? homedir4(), ".pi", "agent")),
|
|
4898
|
-
tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
|
|
4899
|
-
tokenFile: options.tokenFile ? resolve5(options.tokenFile) : null
|
|
4875
|
+
if (current.owner !== "absent" || !current.history)
|
|
4876
|
+
throw new ContractError("quarantine");
|
|
4877
|
+
const fact = await history.append({
|
|
4878
|
+
...confirmation,
|
|
4879
|
+
expected
|
|
4900
4880
|
});
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
if (server) {
|
|
4904
|
-
if (options.tokenFile !== undefined)
|
|
4905
|
-
throw new ContractError("conflict");
|
|
4906
|
-
const { port } = readServerConfig(server).config;
|
|
4907
|
-
selected = decodeHostConfig({
|
|
4908
|
-
...base,
|
|
4909
|
-
http: { ...base.http, port },
|
|
4910
|
-
tokenSource: "config",
|
|
4911
|
-
tokenFile: server
|
|
4912
|
-
});
|
|
4913
|
-
}
|
|
4914
|
-
if (fromFile && encoded(fromFile) !== encoded(selected) || options.port !== undefined && options.port !== selected.http.port || options.host !== undefined && options.host !== selected.http.host || options.agentDir !== undefined && resolve5(options.agentDir) !== selected.agentDir || options.tokenFile !== undefined && resolve5(options.tokenFile) !== selected.tokenFile || env && selected.tokenSource !== "environment" || selected.tokenSource === "environment" && !env || resolveWorkspace(selected.cwd).root !== workspace.root)
|
|
4881
|
+
authority();
|
|
4882
|
+
if (fact.epoch !== expected.epoch || fact.coordinationDigest !== expected.coordinationDigest || fact.historyDigest !== expected.history?.digest)
|
|
4915
4883
|
throw new ContractError("conflict");
|
|
4884
|
+
release(() => history.assertCommitted(fact));
|
|
4916
4885
|
return {
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4886
|
+
status: "resolved",
|
|
4887
|
+
epoch: fact.epoch,
|
|
4888
|
+
recoveryId: fact.recoveryId,
|
|
4889
|
+
committedSeq: fact.committedSeq,
|
|
4890
|
+
notice: "operator-acknowledgment-not-certification"
|
|
4921
4891
|
};
|
|
4922
4892
|
}
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4893
|
+
|
|
4894
|
+
// src/history/files.ts
|
|
4895
|
+
import { createHash as createHash5, randomBytes as randomBytes5, randomUUID as randomUUID6 } from "node:crypto";
|
|
4896
|
+
import {
|
|
4897
|
+
closeSync as closeSync5,
|
|
4898
|
+
constants as constants5,
|
|
4899
|
+
fstatSync as fstatSync4,
|
|
4900
|
+
fsyncSync as fsyncSync3,
|
|
4901
|
+
lstatSync as lstatSync6,
|
|
4902
|
+
opendirSync as opendirSync3,
|
|
4903
|
+
openSync as openSync5,
|
|
4904
|
+
statfsSync
|
|
4905
|
+
} from "node:fs";
|
|
4906
|
+
import { open } from "node:fs/promises";
|
|
4907
|
+
import { dirname as dirname8, join as join8 } from "node:path";
|
|
4908
|
+
|
|
4909
|
+
// src/history/admission.ts
|
|
4910
|
+
function decodeRunAdmission(value) {
|
|
4911
|
+
const o = closed(value, ["schema", "runId", "identity", "graph"]);
|
|
4912
|
+
const result = {
|
|
4913
|
+
schema: literal(o.schema, "cadence-run-admission/2"),
|
|
4914
|
+
runId: decodeId(o.runId),
|
|
4915
|
+
identity: decodeRunIdentity(o.identity),
|
|
4916
|
+
graph: nullable(o.graph, decodeGraph)
|
|
4939
4917
|
};
|
|
4940
|
-
}
|
|
4941
|
-
|
|
4942
|
-
if (!/^(0|[1-9][0-9]{0,4})$/.test(value))
|
|
4918
|
+
const { identity, graph } = result;
|
|
4919
|
+
if (identity.kind === "start" !== (graph !== null))
|
|
4943
4920
|
throw new ContractError("invalid");
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
"package",
|
|
4948
|
-
"pi-agent-directory",
|
|
4949
|
-
"pi-auth",
|
|
4950
|
-
"pi-models",
|
|
4951
|
-
"pi-runtime",
|
|
4952
|
-
"service"
|
|
4953
|
-
];
|
|
4954
|
-
function decodeStartupReport(value) {
|
|
4955
|
-
const o = closed(value, ["status", "error", "stage"]);
|
|
4956
|
-
return {
|
|
4957
|
-
status: oneOf(o.status, ["refused", "quarantined"]),
|
|
4958
|
-
error: oneOf(o.error, ERROR_CODES),
|
|
4959
|
-
stage: oneOf(o.stage, STARTUP_STAGES)
|
|
4960
|
-
};
|
|
4921
|
+
if (graph && (graph.taskId !== identity.context.task?.id || graph.taskKind !== identity.context.task.kind || graph.taskRevision > identity.context.task.revision || graph.revision !== identity.graph?.revision || hash(canonicalJson(graph)) !== identity.graph.sha256 || canonicalJson(graph.assignments.map((a) => a.id)) !== canonicalJson(identity.assignments.map((a) => a.assignmentId)) || graph.assignments.some((a) => canonicalJson(a.model) !== canonicalJson(identity.context.model))))
|
|
4922
|
+
throw new ContractError("conflict");
|
|
4923
|
+
return result;
|
|
4961
4924
|
}
|
|
4962
4925
|
|
|
4963
|
-
// src/
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
let config = serverConfigPath(homedir5()), stateHome = defaultStateHome(), agentDir, policyFile;
|
|
4976
|
-
let foreground = false, reveal = false;
|
|
4977
|
-
const seen = new Set;
|
|
4978
|
-
while (argv.length) {
|
|
4979
|
-
const key = argv.shift();
|
|
4980
|
-
if (seen.has(key))
|
|
4981
|
-
throw new ContractError("invalid");
|
|
4982
|
-
seen.add(key);
|
|
4983
|
-
if (key === "--foreground" && operation === "start")
|
|
4984
|
-
foreground = true;
|
|
4985
|
-
else if (key === "--reveal" && operation === "token-link")
|
|
4986
|
-
reveal = true;
|
|
4987
|
-
else if (argv[0] && ["--server-config", "--state-home", "--agent-dir", "--config"].includes(key)) {
|
|
4988
|
-
const value = resolve6(argv.shift());
|
|
4989
|
-
if (key === "--server-config")
|
|
4990
|
-
config = value;
|
|
4991
|
-
if (key === "--state-home")
|
|
4992
|
-
stateHome = value;
|
|
4993
|
-
if (key === "--agent-dir")
|
|
4994
|
-
agentDir = value;
|
|
4995
|
-
if (key === "--config")
|
|
4996
|
-
policyFile = value;
|
|
4997
|
-
} else
|
|
4998
|
-
throw new ContractError("invalid");
|
|
4999
|
-
}
|
|
5000
|
-
const store = new GatewayStore(config, stateHome);
|
|
5001
|
-
let result;
|
|
5002
|
-
if (operation === "start") {
|
|
5003
|
-
const network = networkSnapshot(process.env), env = hostEnvironment(process.env);
|
|
5004
|
-
if (!seen.has("--server-config"))
|
|
5005
|
-
ensureServerConfig(homedir5());
|
|
5006
|
-
const server = readServerConfig(config).config;
|
|
5007
|
-
const selectedFile = store.files.read(store.path("input.json"));
|
|
5008
|
-
const selected = selectedFile ? decodeGatewayInput(json(selectedFile)) : undefined;
|
|
5009
|
-
let policy = selected?.policy ?? decodeHttpPolicy({
|
|
5010
|
-
schema: "cadence-service-http/1",
|
|
5011
|
-
host: "127.0.0.1",
|
|
5012
|
-
port: server.port,
|
|
5013
|
-
proxy: null
|
|
5014
|
-
});
|
|
5015
|
-
if (policyFile) {
|
|
5016
|
-
const file = new PrivateFiles(dirname8(policyFile)).read(policyFile);
|
|
5017
|
-
if (!file)
|
|
5018
|
-
throw new ContractError("invalid");
|
|
5019
|
-
policy = decodeHttpPolicy(json(file));
|
|
5020
|
-
}
|
|
5021
|
-
const input = decodeGatewayInput({
|
|
5022
|
-
kind: "workbench",
|
|
5023
|
-
serverConfig: config,
|
|
5024
|
-
stateHome: store.stateHome,
|
|
5025
|
-
agentDir: agentDir ?? selected?.agentDir ?? resolve6(process.env.PI_CODING_AGENT_DIR ?? join9(homedir5(), ".pi", "agent")),
|
|
5026
|
-
policy: { ...policy, port: server.port }
|
|
5027
|
-
});
|
|
5028
|
-
const status = await serviceStatus(store);
|
|
5029
|
-
if (status && typeof status === "object" && "status" in status && status.status === "alive") {
|
|
5030
|
-
if (!selected || encoded(selected) !== encoded(input))
|
|
5031
|
-
throw new ContractError("conflict");
|
|
5032
|
-
result = status;
|
|
5033
|
-
} else
|
|
5034
|
-
result = await launchHost(input, store, foreground, hostUrl, dirname8(dirname8(fileURLToPath(hostUrl))), env, network);
|
|
5035
|
-
if (!foreground && result && typeof result === "object" && "status" in result && result.status === "alive") {
|
|
5036
|
-
const reply = closed(await nativeCall(store, "token-link", true), [
|
|
5037
|
-
"link"
|
|
5038
|
-
]);
|
|
5039
|
-
process.stderr.write(`Cadence workbench: ${text(reply.link, 4096, true)}
|
|
5040
|
-
No project or default workspace was created.
|
|
5041
|
-
`);
|
|
5042
|
-
}
|
|
5043
|
-
} else if (operation === "stop")
|
|
5044
|
-
result = await stopService(store);
|
|
5045
|
-
else if (operation === "status" || operation === "diagnose")
|
|
5046
|
-
result = await serviceStatus(store, operation === "diagnose");
|
|
5047
|
-
else {
|
|
5048
|
-
if (operation === "token-link" && !reveal)
|
|
5049
|
-
throw new ContractError("forbidden");
|
|
5050
|
-
result = await nativeCall(store, operation, reveal);
|
|
5051
|
-
}
|
|
5052
|
-
if (result !== null)
|
|
5053
|
-
process.stdout.write(`${JSON.stringify(result)}
|
|
5054
|
-
`);
|
|
5055
|
-
if (result && typeof result === "object" && "status" in result && ["unconfirmed", "unavailable", "quarantined", "refused"].includes(String(result.status)))
|
|
5056
|
-
process.exitCode = 1;
|
|
5057
|
-
}
|
|
5058
|
-
|
|
5059
|
-
// src/service/launchd.ts
|
|
5060
|
-
import { rmSync as rmSync2 } from "node:fs";
|
|
5061
|
-
import { dirname as dirname9, join as join10 } from "node:path";
|
|
5062
|
-
var LABEL = "com.abelxiaoxing.cadence";
|
|
5063
|
-
var MARKER2 = `<?xml version="1.0" encoding="UTF-8"?>
|
|
5064
|
-
<!-- Generated by @abelxiaoxing/cadence`;
|
|
5065
|
-
function plistPath(s) {
|
|
5066
|
-
return join10(s.home, "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
4926
|
+
// src/history/read-io.ts
|
|
4927
|
+
import {
|
|
4928
|
+
closeSync as closeSync4,
|
|
4929
|
+
constants as constants4,
|
|
4930
|
+
fstatSync as fstatSync3,
|
|
4931
|
+
lstatSync as lstatSync5,
|
|
4932
|
+
openSync as openSync4,
|
|
4933
|
+
readSync as readSync3
|
|
4934
|
+
} from "node:fs";
|
|
4935
|
+
import { dirname as dirname7 } from "node:path";
|
|
4936
|
+
function bad() {
|
|
4937
|
+
throw new ContractError("corrupt-selected");
|
|
5067
4938
|
}
|
|
5068
|
-
var
|
|
5069
|
-
function
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
u.serverConfig
|
|
5087
|
-
].map(string),
|
|
5088
|
-
"\t</array>",
|
|
5089
|
-
...u.workspace === null ? ["<!-- Cadence workbench gateway; no project workspace -->"] : [],
|
|
5090
|
-
"\t<key>WorkingDirectory</key>",
|
|
5091
|
-
` <string>${xml(u.workspace ?? dirname9(dirname9(u.cli)))}</string>`,
|
|
5092
|
-
"\t<key>EnvironmentVariables</key>",
|
|
5093
|
-
"\t<dict>",
|
|
5094
|
-
...u.environment.flatMap(([k, v]) => [
|
|
5095
|
-
` <key>${xml(k)}</key>`,
|
|
5096
|
-
string(v)
|
|
5097
|
-
]),
|
|
5098
|
-
"\t</dict>",
|
|
5099
|
-
"\t<key>RunAtLoad</key>",
|
|
5100
|
-
"\t<true/>",
|
|
5101
|
-
"\t<key>ProcessType</key>",
|
|
5102
|
-
"\t<string>Background</string>",
|
|
5103
|
-
"\t<key>StandardOutPath</key>",
|
|
5104
|
-
` <string>${xml(u.log)}</string>`,
|
|
5105
|
-
"\t<key>StandardErrorPath</key>",
|
|
5106
|
-
` <string>${xml(u.log)}</string>`,
|
|
5107
|
-
"</dict>",
|
|
5108
|
-
"</plist>",
|
|
5109
|
-
""
|
|
5110
|
-
].join(`
|
|
5111
|
-
`);
|
|
4939
|
+
var stamp2 = (s) => [s.dev, s.ino, s.size, s.mode, s.uid, s.nlink, s.ctimeMs, s.mtimeMs].join(":");
|
|
4940
|
+
function selected(files, path2, read, walks) {
|
|
4941
|
+
if (!files.files.directory(dirname7(path2), false, walks))
|
|
4942
|
+
bad();
|
|
4943
|
+
const before = lstatSync5(path2);
|
|
4944
|
+
if (!before.isFile() || before.nlink !== 1 || process.platform !== "win32" && (before.mode & 63 || process.getuid && before.uid !== process.getuid()))
|
|
4945
|
+
bad();
|
|
4946
|
+
const fd = openSync4(path2, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
|
|
4947
|
+
try {
|
|
4948
|
+
if (stamp2(fstatSync3(fd)) !== stamp2(before))
|
|
4949
|
+
bad();
|
|
4950
|
+
const result = read(fd, before.size, stamp2(before));
|
|
4951
|
+
if (stamp2(fstatSync3(fd)) !== stamp2(before) || stamp2(lstatSync5(path2)) !== stamp2(before))
|
|
4952
|
+
bad();
|
|
4953
|
+
return result;
|
|
4954
|
+
} finally {
|
|
4955
|
+
closeSync4(fd);
|
|
4956
|
+
}
|
|
5112
4957
|
}
|
|
5113
|
-
function
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
4958
|
+
function assertIncarnation(files, path2, expected) {
|
|
4959
|
+
selected(files, path2, (_fd, _size, current) => {
|
|
4960
|
+
if (current !== expected)
|
|
4961
|
+
bad();
|
|
4962
|
+
});
|
|
5118
4963
|
}
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
4964
|
+
function statSize(files, path2, maximum) {
|
|
4965
|
+
return selected(files, path2, (_fd, size) => {
|
|
4966
|
+
if (size > maximum)
|
|
4967
|
+
bad();
|
|
4968
|
+
return size;
|
|
4969
|
+
});
|
|
5124
4970
|
}
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
return { status: "missing-pi-sdk" };
|
|
5139
|
-
const refused2 = await settle(s, kept);
|
|
5140
|
-
if (refused2)
|
|
5141
|
-
return { ...refused2, unit: path2 };
|
|
5142
|
-
if (loaded(s))
|
|
5143
|
-
s.launchctl(["bootout", target(s)]);
|
|
5144
|
-
write(path2, buildPlist({
|
|
5145
|
-
node: s.node,
|
|
5146
|
-
cli: join10(s.root, "src", "service-cli.mjs"),
|
|
5147
|
-
workspace,
|
|
5148
|
-
serverConfig: serverConfigPath(s.home),
|
|
5149
|
-
environment: serviceEnvironment(s),
|
|
5150
|
-
log: join10(s.home, "Library", "Logs", "cadence.log")
|
|
5151
|
-
}));
|
|
5152
|
-
const result = {
|
|
5153
|
-
unit: path2,
|
|
5154
|
-
workspace,
|
|
5155
|
-
...redirectedFrom ? { redirectedFrom } : {}
|
|
4971
|
+
|
|
4972
|
+
// src/history/types.ts
|
|
4973
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
4974
|
+
var RECORD_BYTES = LIMITS.controlBytes + 64 * 1024;
|
|
4975
|
+
var LEGACY_FORMAT = "cadence-history-store/2";
|
|
4976
|
+
var FORMAT = "cadence-history-store/3";
|
|
4977
|
+
function decodeFormat(value) {
|
|
4978
|
+
const o = closed(value, ["schema", "storeId", "workspaceId", "rootIdentity"]);
|
|
4979
|
+
return {
|
|
4980
|
+
schema: oneOf(o.schema, [FORMAT, LEGACY_FORMAT]),
|
|
4981
|
+
storeId: decodeId(o.storeId),
|
|
4982
|
+
workspaceId: decodeId(o.workspaceId),
|
|
4983
|
+
rootIdentity: decodeHash(o.rootIdentity)
|
|
5156
4984
|
};
|
|
5157
|
-
if (mode === "install")
|
|
5158
|
-
s.launchctl(["enable", target(s)]);
|
|
5159
|
-
else if (disabled(s))
|
|
5160
|
-
return { status: "disabled", ...result };
|
|
5161
|
-
if (workspace !== null && !regularFile(join10(agentDirectory(s), "auth.json")))
|
|
5162
|
-
return { status: "waiting-for-pi-login", ...result };
|
|
5163
|
-
const started = s.launchctl(["bootstrap", domain(s), path2]).status === 0;
|
|
5164
|
-
return { status: started ? "started" : "failed", ...result };
|
|
5165
|
-
}
|
|
5166
|
-
async function stopLaunchAgent(s) {
|
|
5167
|
-
const path2 = plistPath(s);
|
|
5168
|
-
const previous = readGenerated(path2, MARKER2);
|
|
5169
|
-
if (previous === null)
|
|
5170
|
-
return { status: "foreign", unit: path2 };
|
|
5171
|
-
if (previous === undefined)
|
|
5172
|
-
return { status: "absent" };
|
|
5173
|
-
const workspace = plistWorkspace(previous);
|
|
5174
|
-
const refused2 = await settle(s, workspace);
|
|
5175
|
-
return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
|
|
5176
|
-
}
|
|
5177
|
-
async function removeLaunchAgent(s) {
|
|
5178
|
-
const path2 = plistPath(s);
|
|
5179
|
-
const previous = readGenerated(path2, MARKER2);
|
|
5180
|
-
if (previous === null)
|
|
5181
|
-
return { status: "foreign", unit: path2 };
|
|
5182
|
-
if (previous === undefined)
|
|
5183
|
-
return { status: "absent" };
|
|
5184
|
-
const refused2 = await settle(s, plistWorkspace(previous));
|
|
5185
|
-
if (refused2)
|
|
5186
|
-
return { ...refused2, unit: path2 };
|
|
5187
|
-
if (s.uid !== undefined && loaded(s))
|
|
5188
|
-
s.launchctl(["bootout", target(s)]);
|
|
5189
|
-
rmSync2(path2);
|
|
5190
|
-
return { status: "removed", unit: path2 };
|
|
5191
4985
|
}
|
|
5192
|
-
function
|
|
5193
|
-
const
|
|
5194
|
-
const previous = readGenerated(path2, MARKER2);
|
|
5195
|
-
if (previous === null)
|
|
5196
|
-
return { status: "foreign", unit: path2 };
|
|
5197
|
-
if (previous === undefined)
|
|
5198
|
-
return { status: "absent" };
|
|
5199
|
-
const printed = s.uid === undefined ? undefined : s.launchctl(["print", target(s)]);
|
|
4986
|
+
function decodeArtifact(value) {
|
|
4987
|
+
const o = closed(value, ["runId", "artifactId", "part", "bytes", "sha256"]);
|
|
5200
4988
|
return {
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
4989
|
+
runId: decodeId(o.runId),
|
|
4990
|
+
artifactId: decodeId(o.artifactId),
|
|
4991
|
+
part: integer(o.part),
|
|
4992
|
+
bytes: integer(o.bytes, 1, LIMITS.artifactPartBytes),
|
|
4993
|
+
sha256: decodeHash(o.sha256)
|
|
5205
4994
|
};
|
|
5206
4995
|
}
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
const
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
return [
|
|
5228
|
-
`${MARKER3}; \`cadence server install\` and \`restart\` rewrite this file.`,
|
|
5229
|
-
`' Started at login by ${RUN_KEY}\\${RUN_VALUE}; window style 0 hides the console.`,
|
|
5230
|
-
'Set shell = CreateObject("WScript.Shell")',
|
|
5231
|
-
...u.environment.length ? ['Set env = shell.Environment("Process")'] : [],
|
|
5232
|
-
...u.environment.map(([k, v]) => `env(${literal2(k)}) = ${literal2(v)}`),
|
|
5233
|
-
...u.workspace === null ? ["' Cadence workbench gateway; no project workspace"] : [],
|
|
5234
|
-
`shell.CurrentDirectory = ${literal2(u.workspace ?? dirname10(dirname10(u.cli)))}`,
|
|
5235
|
-
`shell.Run ${literal2(command)}, 0, False`,
|
|
5236
|
-
""
|
|
5237
|
-
].join(`\r
|
|
5238
|
-
`);
|
|
5239
|
-
}
|
|
5240
|
-
var utf16 = (text2) => Buffer.concat([Buffer.from([255, 254]), Buffer.from(text2, "utf16le")]);
|
|
5241
|
-
function launcherWorkspace(text2) {
|
|
5242
|
-
if (text2?.includes("' Cadence workbench gateway; no project workspace"))
|
|
5243
|
-
return null;
|
|
5244
|
-
return text2?.match(/^shell\.CurrentDirectory = "((?:[^"]|"")*)"\r?$/m)?.[1].replaceAll('""', '"');
|
|
5245
|
-
}
|
|
5246
|
-
var runEntry = (path2, s) => `"${wscript(s.env)}" "${path2}"`;
|
|
5247
|
-
var present = (s, key) => s.reg(["query", key, "/v", RUN_VALUE]).status === 0;
|
|
5248
|
-
function approvedOff(s) {
|
|
5249
|
-
const r = s.reg(["query", APPROVED_KEY, "/v", RUN_VALUE]);
|
|
5250
|
-
const first = r.status === 0 && /REG_BINARY\s+([0-9A-F]{2})/i.exec(r.stdout);
|
|
5251
|
-
return !!first && (Number.parseInt(first[1], 16) & 1) === 1;
|
|
5252
|
-
}
|
|
5253
|
-
async function configureLoginRun(s, mode, requested) {
|
|
5254
|
-
const path2 = launcherPath(s);
|
|
5255
|
-
const previous = readGenerated(path2, MARKER3);
|
|
5256
|
-
if (previous === null)
|
|
5257
|
-
return { status: "foreign", unit: path2 };
|
|
5258
|
-
if (previous === undefined && present(s, RUN_KEY))
|
|
5259
|
-
return { status: "foreign", unit: `${RUN_KEY}\\${RUN_VALUE}` };
|
|
5260
|
-
if (mode === "restart" && previous === undefined)
|
|
5261
|
-
return { status: "absent" };
|
|
5262
|
-
const sdk = sdkPackages(s.root);
|
|
5263
|
-
const kept = launcherWorkspace(previous);
|
|
5264
|
-
const { workspace, redirectedFrom } = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : kept);
|
|
5265
|
-
if (workspace !== null && !sdk)
|
|
5266
|
-
return { status: "missing-pi-sdk" };
|
|
5267
|
-
const refused2 = await settle(s, kept);
|
|
5268
|
-
if (refused2)
|
|
5269
|
-
return { ...refused2, unit: path2 };
|
|
5270
|
-
const environment = s.env.PI_CODING_AGENT_DIR ? [["PI_CODING_AGENT_DIR", agentDirectory(s)]] : [];
|
|
5271
|
-
write(path2, utf16(buildLauncher({
|
|
5272
|
-
node: s.node,
|
|
5273
|
-
cli: join11(s.root, "src", "service-cli.mjs"),
|
|
5274
|
-
workspace,
|
|
5275
|
-
serverConfig: serverConfigPath(s.home),
|
|
5276
|
-
environment
|
|
5277
|
-
})));
|
|
5278
|
-
const result = {
|
|
5279
|
-
unit: path2,
|
|
5280
|
-
workspace,
|
|
5281
|
-
...redirectedFrom ? { redirectedFrom } : {}
|
|
4996
|
+
function decodeRetainedDetail(value) {
|
|
4997
|
+
const o = closed(value, [
|
|
4998
|
+
"detailId",
|
|
4999
|
+
"origin",
|
|
5000
|
+
"owner",
|
|
5001
|
+
"descriptor",
|
|
5002
|
+
"content",
|
|
5003
|
+
"coverage"
|
|
5004
|
+
]);
|
|
5005
|
+
const origin = closed(o.origin, ["kind", "id"]);
|
|
5006
|
+
const r = {
|
|
5007
|
+
detailId: decodeId(o.detailId),
|
|
5008
|
+
origin: {
|
|
5009
|
+
kind: oneOf(origin.kind, ["worker", "owner"]),
|
|
5010
|
+
id: decodeId(origin.id)
|
|
5011
|
+
},
|
|
5012
|
+
owner: decodeRecordOwner(o.owner),
|
|
5013
|
+
descriptor: nullable(o.descriptor, decodeDetailDescriptor),
|
|
5014
|
+
content: decodeContentRef(o.content),
|
|
5015
|
+
coverage: decodeCoverage(o.coverage)
|
|
5282
5016
|
};
|
|
5283
|
-
if (
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5017
|
+
if (r.origin.kind === "worker" !== (r.descriptor !== null) || r.descriptor && r.content.field !== r.descriptor.field)
|
|
5018
|
+
invalid();
|
|
5019
|
+
return r;
|
|
5020
|
+
}
|
|
5021
|
+
function decodeSavedEvent(value, owner) {
|
|
5022
|
+
const kind = closedTag(value);
|
|
5023
|
+
if (kind === "capture") {
|
|
5024
|
+
const o2 = closed(value, ["kind", "id", "value"]), v = decodeRecordingEvent(o2.value);
|
|
5025
|
+
if (owner || v.event.kind === "stored-chunk" || v.event.kind === "stored-end")
|
|
5026
|
+
invalid();
|
|
5027
|
+
return { kind, id: decodeId(o2.id), value: v };
|
|
5288
5028
|
}
|
|
5289
|
-
if (
|
|
5290
|
-
|
|
5291
|
-
|
|
5029
|
+
if (kind === "owner") {
|
|
5030
|
+
const o2 = closed(value, ["kind", "id", "value"]);
|
|
5031
|
+
if (!owner)
|
|
5032
|
+
invalid();
|
|
5033
|
+
const v = decodeOwnerRecordingInput({
|
|
5034
|
+
link: owner,
|
|
5035
|
+
observedMs: 0,
|
|
5036
|
+
events: [o2.value]
|
|
5037
|
+
}).events[0];
|
|
5038
|
+
if (v.kind === "content-chunk" || v.kind === "content-end")
|
|
5039
|
+
invalid();
|
|
5040
|
+
return { kind, id: decodeId(o2.id), value: v };
|
|
5041
|
+
}
|
|
5042
|
+
if (kind === "chunk") {
|
|
5043
|
+
const o2 = closed(value, [
|
|
5044
|
+
"kind",
|
|
5045
|
+
"detailId",
|
|
5046
|
+
"source",
|
|
5047
|
+
"artifact",
|
|
5048
|
+
"offset",
|
|
5049
|
+
"start",
|
|
5050
|
+
"length"
|
|
5051
|
+
]);
|
|
5052
|
+
const source2 = nullable(o2.source, decodeSourceRange);
|
|
5053
|
+
if (source2 === null !== (owner !== null))
|
|
5054
|
+
invalid();
|
|
5055
|
+
const artifact = decodeArtifact(o2.artifact), offset = integer(o2.offset, 0, LIMITS.detailBytes), start = integer(o2.start, 0, LIMITS.artifactPartBytes), length = integer(o2.length, 1, LIMITS.chunkBytes);
|
|
5056
|
+
if (start + length > artifact.bytes || offset + length > LIMITS.detailBytes)
|
|
5057
|
+
invalid();
|
|
5058
|
+
return {
|
|
5059
|
+
kind,
|
|
5060
|
+
detailId: decodeId(o2.detailId),
|
|
5061
|
+
source: source2,
|
|
5062
|
+
artifact,
|
|
5063
|
+
offset,
|
|
5064
|
+
start,
|
|
5065
|
+
length
|
|
5066
|
+
};
|
|
5067
|
+
}
|
|
5068
|
+
const o = closed(value, ["kind", "detail", "source"]);
|
|
5069
|
+
literal(kind, "end");
|
|
5070
|
+
const detail = decodeRetainedDetail(o.detail), source = nullable(o.source, decodeSourceRange);
|
|
5071
|
+
if (detail.origin.kind === "owner" !== (owner !== null) || source === null !== (owner !== null))
|
|
5072
|
+
invalid();
|
|
5073
|
+
return { kind: "end", detail, source };
|
|
5292
5074
|
}
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
return { status: "foreign", unit: path2 };
|
|
5298
|
-
if (previous === undefined)
|
|
5299
|
-
return { status: "absent" };
|
|
5300
|
-
const workspace = launcherWorkspace(previous);
|
|
5301
|
-
const refused2 = await settle(s, workspace);
|
|
5302
|
-
return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
|
|
5075
|
+
function closedTag(value) {
|
|
5076
|
+
if (!value || typeof value !== "object" || !("kind" in value) || typeof value.kind !== "string")
|
|
5077
|
+
invalid();
|
|
5078
|
+
return value.kind;
|
|
5303
5079
|
}
|
|
5304
|
-
|
|
5305
|
-
const
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5080
|
+
function decodeAdmissionIntent(value) {
|
|
5081
|
+
const o = closed(value, [
|
|
5082
|
+
"commandId",
|
|
5083
|
+
"operationId",
|
|
5084
|
+
"runId",
|
|
5085
|
+
"identity",
|
|
5086
|
+
"createdTaskId"
|
|
5087
|
+
]);
|
|
5088
|
+
return {
|
|
5089
|
+
commandId: decodeId(o.commandId),
|
|
5090
|
+
operationId: decodeId(o.operationId),
|
|
5091
|
+
runId: nullable(o.runId, decodeId),
|
|
5092
|
+
identity: decodeCommandIdentity(o.identity),
|
|
5093
|
+
createdTaskId: nullable(o.createdTaskId, decodeId)
|
|
5094
|
+
};
|
|
5319
5095
|
}
|
|
5320
|
-
|
|
5321
|
-
const
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5096
|
+
function decodeAuthIntent(value) {
|
|
5097
|
+
const o = closed(value, [
|
|
5098
|
+
"operationId",
|
|
5099
|
+
"epoch",
|
|
5100
|
+
"runId",
|
|
5101
|
+
"childRunId",
|
|
5102
|
+
"sourceSelectionId",
|
|
5103
|
+
"validityMs"
|
|
5104
|
+
]);
|
|
5329
5105
|
return {
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5106
|
+
operationId: decodeId(o.operationId),
|
|
5107
|
+
epoch: decodeId(o.epoch),
|
|
5108
|
+
runId: decodeId(o.runId),
|
|
5109
|
+
childRunId: decodeId(o.childRunId),
|
|
5110
|
+
sourceSelectionId: decodeId(o.sourceSelectionId),
|
|
5111
|
+
validityMs: integer(o.validityMs, 1)
|
|
5334
5112
|
};
|
|
5335
5113
|
}
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
import { isatty } from "node:tty";
|
|
5339
|
-
|
|
5340
|
-
// src/history/recovery.ts
|
|
5341
|
-
import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
|
|
5342
|
-
import { lstatSync as lstatSync7, opendirSync as opendirSync4 } from "node:fs";
|
|
5343
|
-
import { join as join13, relative as relative4 } from "node:path";
|
|
5344
|
-
|
|
5345
|
-
// src/service/recovery.ts
|
|
5346
|
-
async function inspectRecovery(coordination, history) {
|
|
5347
|
-
const selected = coordination.recoverySnapshot();
|
|
5348
|
-
const facts = history ? await history.inspect(selected.registration.epoch) : null;
|
|
5349
|
-
if (facts)
|
|
5350
|
-
decodeHash(facts.digest);
|
|
5351
|
-
if (coordination.recoverySnapshot().digest !== selected.digest)
|
|
5352
|
-
throw new ContractError("conflict");
|
|
5114
|
+
function decodeAuthSettlement(value) {
|
|
5115
|
+
const o = closed(value, ["operationId", "outcome", "admissionClosed"]);
|
|
5353
5116
|
return {
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
owner: recoveryOwnerStatus(selected.registration.pid, selected.registration.host),
|
|
5358
|
-
resolution: "local-interactive-only"
|
|
5359
|
-
};
|
|
5360
|
-
}
|
|
5361
|
-
async function resolveRecovery(coordination, confirmation, history) {
|
|
5362
|
-
if (typeof confirmation.assertLocalAuthority !== "function" || confirmation.acknowledgeEscapedProcesses !== true || confirmation.processes !== "externally-resolved" || confirmation.credentials !== "independently-resolved" || confirmation.storage !== "independently-resolved")
|
|
5363
|
-
throw new ContractError("forbidden");
|
|
5364
|
-
const expected = structuredClone(confirmation.expected);
|
|
5365
|
-
const authority = () => confirmation.assertLocalAuthority(structuredClone(expected));
|
|
5366
|
-
authority();
|
|
5367
|
-
const release = coordination.prepareRecoveryRelease(expected.coordinationDigest, authority);
|
|
5368
|
-
const current = await inspectRecovery(coordination, history);
|
|
5369
|
-
if (JSON.stringify(current) !== JSON.stringify(expected))
|
|
5370
|
-
throw new ContractError("conflict");
|
|
5371
|
-
if (current.owner !== "absent" || !current.history)
|
|
5372
|
-
throw new ContractError("quarantine");
|
|
5373
|
-
const fact = await history.append({
|
|
5374
|
-
...confirmation,
|
|
5375
|
-
expected
|
|
5376
|
-
});
|
|
5377
|
-
authority();
|
|
5378
|
-
if (fact.epoch !== expected.epoch || fact.coordinationDigest !== expected.coordinationDigest || fact.historyDigest !== expected.history?.digest)
|
|
5379
|
-
throw new ContractError("conflict");
|
|
5380
|
-
release(() => history.assertCommitted(fact));
|
|
5381
|
-
return {
|
|
5382
|
-
status: "resolved",
|
|
5383
|
-
epoch: fact.epoch,
|
|
5384
|
-
recoveryId: fact.recoveryId,
|
|
5385
|
-
committedSeq: fact.committedSeq,
|
|
5386
|
-
notice: "operator-acknowledgment-not-certification"
|
|
5387
|
-
};
|
|
5388
|
-
}
|
|
5389
|
-
|
|
5390
|
-
// src/history/files.ts
|
|
5391
|
-
import { createHash as createHash5, randomBytes as randomBytes5, randomUUID as randomUUID6 } from "node:crypto";
|
|
5392
|
-
import {
|
|
5393
|
-
closeSync as closeSync5,
|
|
5394
|
-
constants as constants5,
|
|
5395
|
-
fstatSync as fstatSync4,
|
|
5396
|
-
fsyncSync as fsyncSync3,
|
|
5397
|
-
lstatSync as lstatSync6,
|
|
5398
|
-
opendirSync as opendirSync3,
|
|
5399
|
-
openSync as openSync5,
|
|
5400
|
-
statfsSync
|
|
5401
|
-
} from "node:fs";
|
|
5402
|
-
import { open } from "node:fs/promises";
|
|
5403
|
-
import { dirname as dirname12, join as join12 } from "node:path";
|
|
5404
|
-
|
|
5405
|
-
// src/history/admission.ts
|
|
5406
|
-
function decodeRunAdmission(value) {
|
|
5407
|
-
const o = closed(value, ["schema", "runId", "identity", "graph"]);
|
|
5408
|
-
const result = {
|
|
5409
|
-
schema: literal(o.schema, "cadence-run-admission/2"),
|
|
5410
|
-
runId: decodeId(o.runId),
|
|
5411
|
-
identity: decodeRunIdentity(o.identity),
|
|
5412
|
-
graph: nullable(o.graph, decodeGraph)
|
|
5413
|
-
};
|
|
5414
|
-
const { identity, graph } = result;
|
|
5415
|
-
if (identity.kind === "start" !== (graph !== null))
|
|
5416
|
-
throw new ContractError("invalid");
|
|
5417
|
-
if (graph && (graph.taskId !== identity.context.task?.id || graph.taskKind !== identity.context.task.kind || graph.taskRevision > identity.context.task.revision || graph.revision !== identity.graph?.revision || hash(canonicalJson(graph)) !== identity.graph.sha256 || canonicalJson(graph.assignments.map((a) => a.id)) !== canonicalJson(identity.assignments.map((a) => a.assignmentId)) || graph.assignments.some((a) => canonicalJson(a.model) !== canonicalJson(identity.context.model))))
|
|
5418
|
-
throw new ContractError("conflict");
|
|
5419
|
-
return result;
|
|
5420
|
-
}
|
|
5421
|
-
|
|
5422
|
-
// src/history/read-io.ts
|
|
5423
|
-
import {
|
|
5424
|
-
closeSync as closeSync4,
|
|
5425
|
-
constants as constants4,
|
|
5426
|
-
fstatSync as fstatSync3,
|
|
5427
|
-
lstatSync as lstatSync5,
|
|
5428
|
-
openSync as openSync4,
|
|
5429
|
-
readSync as readSync3
|
|
5430
|
-
} from "node:fs";
|
|
5431
|
-
import { dirname as dirname11 } from "node:path";
|
|
5432
|
-
function bad() {
|
|
5433
|
-
throw new ContractError("corrupt-selected");
|
|
5434
|
-
}
|
|
5435
|
-
var stamp2 = (s) => [s.dev, s.ino, s.size, s.mode, s.uid, s.nlink, s.ctimeMs, s.mtimeMs].join(":");
|
|
5436
|
-
function selected(files, path2, read, walks) {
|
|
5437
|
-
if (!files.files.directory(dirname11(path2), false, walks))
|
|
5438
|
-
bad();
|
|
5439
|
-
const before = lstatSync5(path2);
|
|
5440
|
-
if (!before.isFile() || before.nlink !== 1 || process.platform !== "win32" && (before.mode & 63 || process.getuid && before.uid !== process.getuid()))
|
|
5441
|
-
bad();
|
|
5442
|
-
const fd = openSync4(path2, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
|
|
5443
|
-
try {
|
|
5444
|
-
if (stamp2(fstatSync3(fd)) !== stamp2(before))
|
|
5445
|
-
bad();
|
|
5446
|
-
const result = read(fd, before.size, stamp2(before));
|
|
5447
|
-
if (stamp2(fstatSync3(fd)) !== stamp2(before) || stamp2(lstatSync5(path2)) !== stamp2(before))
|
|
5448
|
-
bad();
|
|
5449
|
-
return result;
|
|
5450
|
-
} finally {
|
|
5451
|
-
closeSync4(fd);
|
|
5452
|
-
}
|
|
5453
|
-
}
|
|
5454
|
-
function assertIncarnation(files, path2, expected) {
|
|
5455
|
-
selected(files, path2, (_fd, _size, current) => {
|
|
5456
|
-
if (current !== expected)
|
|
5457
|
-
bad();
|
|
5458
|
-
});
|
|
5459
|
-
}
|
|
5460
|
-
function statSize(files, path2, maximum) {
|
|
5461
|
-
return selected(files, path2, (_fd, size) => {
|
|
5462
|
-
if (size > maximum)
|
|
5463
|
-
bad();
|
|
5464
|
-
return size;
|
|
5465
|
-
});
|
|
5466
|
-
}
|
|
5467
|
-
|
|
5468
|
-
// src/history/types.ts
|
|
5469
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
5470
|
-
var RECORD_BYTES = LIMITS.controlBytes + 64 * 1024;
|
|
5471
|
-
var LEGACY_FORMAT = "cadence-history-store/2";
|
|
5472
|
-
var FORMAT = "cadence-history-store/3";
|
|
5473
|
-
function decodeFormat(value) {
|
|
5474
|
-
const o = closed(value, ["schema", "storeId", "workspaceId", "rootIdentity"]);
|
|
5475
|
-
return {
|
|
5476
|
-
schema: oneOf(o.schema, [FORMAT, LEGACY_FORMAT]),
|
|
5477
|
-
storeId: decodeId(o.storeId),
|
|
5478
|
-
workspaceId: decodeId(o.workspaceId),
|
|
5479
|
-
rootIdentity: decodeHash(o.rootIdentity)
|
|
5480
|
-
};
|
|
5481
|
-
}
|
|
5482
|
-
function decodeArtifact(value) {
|
|
5483
|
-
const o = closed(value, ["runId", "artifactId", "part", "bytes", "sha256"]);
|
|
5484
|
-
return {
|
|
5485
|
-
runId: decodeId(o.runId),
|
|
5486
|
-
artifactId: decodeId(o.artifactId),
|
|
5487
|
-
part: integer(o.part),
|
|
5488
|
-
bytes: integer(o.bytes, 1, LIMITS.artifactPartBytes),
|
|
5489
|
-
sha256: decodeHash(o.sha256)
|
|
5490
|
-
};
|
|
5491
|
-
}
|
|
5492
|
-
function decodeRetainedDetail(value) {
|
|
5493
|
-
const o = closed(value, [
|
|
5494
|
-
"detailId",
|
|
5495
|
-
"origin",
|
|
5496
|
-
"owner",
|
|
5497
|
-
"descriptor",
|
|
5498
|
-
"content",
|
|
5499
|
-
"coverage"
|
|
5500
|
-
]);
|
|
5501
|
-
const origin = closed(o.origin, ["kind", "id"]);
|
|
5502
|
-
const r = {
|
|
5503
|
-
detailId: decodeId(o.detailId),
|
|
5504
|
-
origin: {
|
|
5505
|
-
kind: oneOf(origin.kind, ["worker", "owner"]),
|
|
5506
|
-
id: decodeId(origin.id)
|
|
5507
|
-
},
|
|
5508
|
-
owner: decodeRecordOwner(o.owner),
|
|
5509
|
-
descriptor: nullable(o.descriptor, decodeDetailDescriptor),
|
|
5510
|
-
content: decodeContentRef(o.content),
|
|
5511
|
-
coverage: decodeCoverage(o.coverage)
|
|
5512
|
-
};
|
|
5513
|
-
if (r.origin.kind === "worker" !== (r.descriptor !== null) || r.descriptor && r.content.field !== r.descriptor.field)
|
|
5514
|
-
invalid();
|
|
5515
|
-
return r;
|
|
5516
|
-
}
|
|
5517
|
-
function decodeSavedEvent(value, owner) {
|
|
5518
|
-
const kind = closedTag(value);
|
|
5519
|
-
if (kind === "capture") {
|
|
5520
|
-
const o2 = closed(value, ["kind", "id", "value"]), v = decodeRecordingEvent(o2.value);
|
|
5521
|
-
if (owner || v.event.kind === "stored-chunk" || v.event.kind === "stored-end")
|
|
5522
|
-
invalid();
|
|
5523
|
-
return { kind, id: decodeId(o2.id), value: v };
|
|
5524
|
-
}
|
|
5525
|
-
if (kind === "owner") {
|
|
5526
|
-
const o2 = closed(value, ["kind", "id", "value"]);
|
|
5527
|
-
if (!owner)
|
|
5528
|
-
invalid();
|
|
5529
|
-
const v = decodeOwnerRecordingInput({
|
|
5530
|
-
link: owner,
|
|
5531
|
-
observedMs: 0,
|
|
5532
|
-
events: [o2.value]
|
|
5533
|
-
}).events[0];
|
|
5534
|
-
if (v.kind === "content-chunk" || v.kind === "content-end")
|
|
5535
|
-
invalid();
|
|
5536
|
-
return { kind, id: decodeId(o2.id), value: v };
|
|
5537
|
-
}
|
|
5538
|
-
if (kind === "chunk") {
|
|
5539
|
-
const o2 = closed(value, [
|
|
5540
|
-
"kind",
|
|
5541
|
-
"detailId",
|
|
5542
|
-
"source",
|
|
5543
|
-
"artifact",
|
|
5544
|
-
"offset",
|
|
5545
|
-
"start",
|
|
5546
|
-
"length"
|
|
5547
|
-
]);
|
|
5548
|
-
const source2 = nullable(o2.source, decodeSourceRange);
|
|
5549
|
-
if (source2 === null !== (owner !== null))
|
|
5550
|
-
invalid();
|
|
5551
|
-
const artifact = decodeArtifact(o2.artifact), offset = integer(o2.offset, 0, LIMITS.detailBytes), start = integer(o2.start, 0, LIMITS.artifactPartBytes), length = integer(o2.length, 1, LIMITS.chunkBytes);
|
|
5552
|
-
if (start + length > artifact.bytes || offset + length > LIMITS.detailBytes)
|
|
5553
|
-
invalid();
|
|
5554
|
-
return {
|
|
5555
|
-
kind,
|
|
5556
|
-
detailId: decodeId(o2.detailId),
|
|
5557
|
-
source: source2,
|
|
5558
|
-
artifact,
|
|
5559
|
-
offset,
|
|
5560
|
-
start,
|
|
5561
|
-
length
|
|
5562
|
-
};
|
|
5563
|
-
}
|
|
5564
|
-
const o = closed(value, ["kind", "detail", "source"]);
|
|
5565
|
-
literal(kind, "end");
|
|
5566
|
-
const detail = decodeRetainedDetail(o.detail), source = nullable(o.source, decodeSourceRange);
|
|
5567
|
-
if (detail.origin.kind === "owner" !== (owner !== null) || source === null !== (owner !== null))
|
|
5568
|
-
invalid();
|
|
5569
|
-
return { kind: "end", detail, source };
|
|
5570
|
-
}
|
|
5571
|
-
function closedTag(value) {
|
|
5572
|
-
if (!value || typeof value !== "object" || !("kind" in value) || typeof value.kind !== "string")
|
|
5573
|
-
invalid();
|
|
5574
|
-
return value.kind;
|
|
5575
|
-
}
|
|
5576
|
-
function decodeAdmissionIntent(value) {
|
|
5577
|
-
const o = closed(value, [
|
|
5578
|
-
"commandId",
|
|
5579
|
-
"operationId",
|
|
5580
|
-
"runId",
|
|
5581
|
-
"identity",
|
|
5582
|
-
"createdTaskId"
|
|
5583
|
-
]);
|
|
5584
|
-
return {
|
|
5585
|
-
commandId: decodeId(o.commandId),
|
|
5586
|
-
operationId: decodeId(o.operationId),
|
|
5587
|
-
runId: nullable(o.runId, decodeId),
|
|
5588
|
-
identity: decodeCommandIdentity(o.identity),
|
|
5589
|
-
createdTaskId: nullable(o.createdTaskId, decodeId)
|
|
5590
|
-
};
|
|
5591
|
-
}
|
|
5592
|
-
function decodeAuthIntent(value) {
|
|
5593
|
-
const o = closed(value, [
|
|
5594
|
-
"operationId",
|
|
5595
|
-
"epoch",
|
|
5596
|
-
"runId",
|
|
5597
|
-
"childRunId",
|
|
5598
|
-
"sourceSelectionId",
|
|
5599
|
-
"validityMs"
|
|
5600
|
-
]);
|
|
5601
|
-
return {
|
|
5602
|
-
operationId: decodeId(o.operationId),
|
|
5603
|
-
epoch: decodeId(o.epoch),
|
|
5604
|
-
runId: decodeId(o.runId),
|
|
5605
|
-
childRunId: decodeId(o.childRunId),
|
|
5606
|
-
sourceSelectionId: decodeId(o.sourceSelectionId),
|
|
5607
|
-
validityMs: integer(o.validityMs, 1)
|
|
5608
|
-
};
|
|
5609
|
-
}
|
|
5610
|
-
function decodeAuthSettlement(value) {
|
|
5611
|
-
const o = closed(value, ["operationId", "outcome", "admissionClosed"]);
|
|
5612
|
-
return {
|
|
5613
|
-
operationId: decodeId(o.operationId),
|
|
5614
|
-
outcome: oneOf(o.outcome, ["resolved", "refused", "failed"]),
|
|
5615
|
-
admissionClosed: bool(o.admissionClosed)
|
|
5117
|
+
operationId: decodeId(o.operationId),
|
|
5118
|
+
outcome: oneOf(o.outcome, ["resolved", "refused", "failed"]),
|
|
5119
|
+
admissionClosed: bool(o.admissionClosed)
|
|
5616
5120
|
};
|
|
5617
5121
|
}
|
|
5618
5122
|
function decodeRecoveryBridge(v) {
|
|
@@ -5931,14 +5435,14 @@ class HistoryFiles {
|
|
|
5931
5435
|
this.coordination = coordination;
|
|
5932
5436
|
this.format = format;
|
|
5933
5437
|
this.formatSnapshot = formatSnapshot;
|
|
5934
|
-
this.directory =
|
|
5438
|
+
this.directory = join8(coordination.directory, "history-v2");
|
|
5935
5439
|
this.files = coordination.files;
|
|
5936
5440
|
}
|
|
5937
5441
|
static open(coordination) {
|
|
5938
|
-
const dir =
|
|
5442
|
+
const dir = join8(coordination.directory, "history-v2");
|
|
5939
5443
|
if (!coordination.files.directory(dir))
|
|
5940
5444
|
return;
|
|
5941
|
-
const file = coordination.files.read(
|
|
5445
|
+
const file = coordination.files.read(join8(dir, "format.json"));
|
|
5942
5446
|
if (!file)
|
|
5943
5447
|
corrupt();
|
|
5944
5448
|
const format = decodeFormat(json(file));
|
|
@@ -5954,7 +5458,7 @@ class HistoryFiles {
|
|
|
5954
5458
|
existing.assertWritable();
|
|
5955
5459
|
return existing;
|
|
5956
5460
|
}
|
|
5957
|
-
const dir =
|
|
5461
|
+
const dir = join8(c.directory, "history-v2");
|
|
5958
5462
|
const format = {
|
|
5959
5463
|
schema: FORMAT,
|
|
5960
5464
|
storeId: randomUUID6(),
|
|
@@ -5964,19 +5468,19 @@ class HistoryFiles {
|
|
|
5964
5468
|
c.files.directory(dir, true);
|
|
5965
5469
|
for (const name of ["commands", "ledger", "runs", "indexes"]) {
|
|
5966
5470
|
lease.assert();
|
|
5967
|
-
c.files.directory(
|
|
5471
|
+
c.files.directory(join8(dir, name), true);
|
|
5968
5472
|
}
|
|
5969
5473
|
lease.assert();
|
|
5970
|
-
const keyPath =
|
|
5474
|
+
const keyPath = join8(dir, "read-proof-key.json");
|
|
5971
5475
|
c.files.create(keyPath, encoded({
|
|
5972
5476
|
schema: "cadence-history-read-key/1",
|
|
5973
5477
|
storeId: format.storeId,
|
|
5974
5478
|
key: randomBytes5(32).toString("base64url")
|
|
5975
5479
|
}));
|
|
5976
5480
|
lease.assert();
|
|
5977
|
-
const snapshot = c.files.create(
|
|
5481
|
+
const snapshot = c.files.create(join8(dir, "format.json"), encoded(format));
|
|
5978
5482
|
const result = new HistoryFiles(c, format, snapshot);
|
|
5979
|
-
result.syncParents(
|
|
5483
|
+
result.syncParents(join8(dir, "format.json"));
|
|
5980
5484
|
result.assertWritable();
|
|
5981
5485
|
return result;
|
|
5982
5486
|
}
|
|
@@ -6011,7 +5515,7 @@ class HistoryFiles {
|
|
|
6011
5515
|
throw new ContractError("quarantine");
|
|
6012
5516
|
}
|
|
6013
5517
|
assertFormat() {
|
|
6014
|
-
this.files.assert(
|
|
5518
|
+
this.files.assert(join8(this.directory, "format.json"), this.formatSnapshot);
|
|
6015
5519
|
}
|
|
6016
5520
|
readProofMaterial(budget) {
|
|
6017
5521
|
if (this.format.schema !== FORMAT)
|
|
@@ -6020,7 +5524,7 @@ class HistoryFiles {
|
|
|
6020
5524
|
this.assertReadFormat(budget);
|
|
6021
5525
|
else
|
|
6022
5526
|
this.assertFormat();
|
|
6023
|
-
const selected2 = this.readBudgeted(
|
|
5527
|
+
const selected2 = this.readBudgeted(join8(this.directory, "read-proof-key.json"), 512, budget);
|
|
6024
5528
|
if (!selected2 || this.proofSnapshot && (selected2.identity !== this.proofSnapshot.identity || selected2.stamp !== this.proofSnapshot.stamp))
|
|
6025
5529
|
corrupt();
|
|
6026
5530
|
try {
|
|
@@ -6048,17 +5552,17 @@ class HistoryFiles {
|
|
|
6048
5552
|
return;
|
|
6049
5553
|
}
|
|
6050
5554
|
this.assertFormat();
|
|
6051
|
-
assertIncarnation(this,
|
|
5555
|
+
assertIncarnation(this, join8(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
|
|
6052
5556
|
}
|
|
6053
5557
|
segmentPath(segment) {
|
|
6054
|
-
return
|
|
5558
|
+
return join8(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
|
|
6055
5559
|
}
|
|
6056
5560
|
artifactPath(ref) {
|
|
6057
|
-
return
|
|
5561
|
+
return join8(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
|
|
6058
5562
|
}
|
|
6059
5563
|
admissionPath(runId) {
|
|
6060
5564
|
decodeId(runId);
|
|
6061
|
-
return
|
|
5565
|
+
return join8(this.directory, "runs", runId, "admission.json");
|
|
6062
5566
|
}
|
|
6063
5567
|
writeAdmission(lease, input) {
|
|
6064
5568
|
lease.assert();
|
|
@@ -6084,7 +5588,7 @@ class HistoryFiles {
|
|
|
6084
5588
|
readCommand(commandId, budget) {
|
|
6085
5589
|
decodeId(commandId);
|
|
6086
5590
|
this.assertReadFormat(budget);
|
|
6087
|
-
return this.readBudgeted(
|
|
5591
|
+
return this.readBudgeted(join8(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
|
|
6088
5592
|
}
|
|
6089
5593
|
readAdmission(runId, identity, budget) {
|
|
6090
5594
|
return this.readAdmissionIdentity(runId, hash(canonicalJson(identity)), budget);
|
|
@@ -6105,7 +5609,7 @@ class HistoryFiles {
|
|
|
6105
5609
|
syncParents(file) {
|
|
6106
5610
|
if (process.platform === "win32")
|
|
6107
5611
|
return;
|
|
6108
|
-
for (let path2 =
|
|
5612
|
+
for (let path2 = dirname8(file);; path2 = dirname8(path2)) {
|
|
6109
5613
|
if (!this.files.directory(path2))
|
|
6110
5614
|
corrupt();
|
|
6111
5615
|
const fd = openSync5(path2, constants5.O_RDONLY | constants5.O_DIRECTORY);
|
|
@@ -6116,13 +5620,13 @@ class HistoryFiles {
|
|
|
6116
5620
|
}
|
|
6117
5621
|
if (path2 === this.coordination.directory)
|
|
6118
5622
|
break;
|
|
6119
|
-
if (path2 ===
|
|
5623
|
+
if (path2 === dirname8(path2))
|
|
6120
5624
|
corrupt();
|
|
6121
5625
|
}
|
|
6122
5626
|
}
|
|
6123
5627
|
assertReadFormat(budget) {
|
|
6124
5628
|
budget.reserve(this.formatSnapshot.bytes.length);
|
|
6125
|
-
this.files.assert(
|
|
5629
|
+
this.files.assert(join8(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
|
|
6126
5630
|
}
|
|
6127
5631
|
assertPublished(segment, end, seq) {
|
|
6128
5632
|
const fence = publicationFences.get(this.directory);
|
|
@@ -6160,7 +5664,7 @@ class HistoryFiles {
|
|
|
6160
5664
|
const dir = opendirSync3(path2);
|
|
6161
5665
|
try {
|
|
6162
5666
|
for (let e = dir.readSync();e; e = dir.readSync()) {
|
|
6163
|
-
const p =
|
|
5667
|
+
const p = join8(path2, e.name), st = lstatSync6(p);
|
|
6164
5668
|
if (st.isDirectory())
|
|
6165
5669
|
bytes += walk(p, depth + 1);
|
|
6166
5670
|
else {
|
|
@@ -6195,7 +5699,7 @@ class HistoryFiles {
|
|
|
6195
5699
|
state.bytes = 0;
|
|
6196
5700
|
state.identity = null;
|
|
6197
5701
|
let count = 0;
|
|
6198
|
-
const ledger =
|
|
5702
|
+
const ledger = join8(this.directory, "ledger");
|
|
6199
5703
|
if (!this.files.directory(ledger))
|
|
6200
5704
|
corrupt();
|
|
6201
5705
|
const dir = opendirSync3(ledger);
|
|
@@ -6512,7 +6016,7 @@ class HistoryFiles {
|
|
|
6512
6016
|
checkpoint(lease, state) {
|
|
6513
6017
|
lease.assert();
|
|
6514
6018
|
this.assertWritable();
|
|
6515
|
-
const path2 =
|
|
6019
|
+
const path2 = join8(this.directory, "checkpoint.json"), old = this.files.read(path2);
|
|
6516
6020
|
const body = encoded({
|
|
6517
6021
|
schema: "cadence-history-checkpoint/2",
|
|
6518
6022
|
storeId: this.format.storeId,
|
|
@@ -6939,7 +6443,7 @@ function digestHistory(files, exclude) {
|
|
|
6939
6443
|
dir.closeSync();
|
|
6940
6444
|
}
|
|
6941
6445
|
for (const name of names2.sort()) {
|
|
6942
|
-
const selected2 =
|
|
6446
|
+
const selected2 = join9(path2, name);
|
|
6943
6447
|
if (selected2 === exclude)
|
|
6944
6448
|
continue;
|
|
6945
6449
|
const rel = relative4(files.directory, selected2);
|
|
@@ -7127,19 +6631,260 @@ class HistoryRecovery {
|
|
|
7127
6631
|
}
|
|
7128
6632
|
}
|
|
7129
6633
|
|
|
7130
|
-
// src/service/recovery
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
6634
|
+
// src/service/gateway-recovery.ts
|
|
6635
|
+
import { opendirSync as opendirSync5 } from "node:fs";
|
|
6636
|
+
import { hostname as hostname4 } from "node:os";
|
|
6637
|
+
import { join as join10 } from "node:path";
|
|
6638
|
+
function required(resources, name) {
|
|
6639
|
+
const file = resources.get(name);
|
|
6640
|
+
if (!file)
|
|
6641
|
+
throw new ContractError("corrupt-selected");
|
|
6642
|
+
return file;
|
|
7135
6643
|
}
|
|
7136
|
-
|
|
6644
|
+
function names2(store, path2, maximum) {
|
|
6645
|
+
if (!store.files.directory(path2))
|
|
6646
|
+
return [];
|
|
6647
|
+
const stream = opendirSync5(path2), out = [];
|
|
6648
|
+
try {
|
|
6649
|
+
for (let row = stream.readSync();row; row = stream.readSync()) {
|
|
6650
|
+
if (out.length >= maximum || !row.isFile() && !row.isDirectory())
|
|
6651
|
+
throw new ContractError("corrupt-selected");
|
|
6652
|
+
out.push(row.name);
|
|
6653
|
+
}
|
|
6654
|
+
} finally {
|
|
6655
|
+
stream.closeSync();
|
|
6656
|
+
}
|
|
6657
|
+
return out.sort();
|
|
6658
|
+
}
|
|
6659
|
+
function capture(store) {
|
|
6660
|
+
const out = new Map;
|
|
6661
|
+
const keep = (name) => {
|
|
6662
|
+
const file = store.files.read(store.path(name));
|
|
6663
|
+
if (!file)
|
|
6664
|
+
throw new ContractError("corrupt-selected");
|
|
6665
|
+
out.set(name, file);
|
|
6666
|
+
};
|
|
6667
|
+
for (const name of names2(store, store.directory, 9)) {
|
|
6668
|
+
if ([
|
|
6669
|
+
"gateway.json",
|
|
6670
|
+
"gateway.lock",
|
|
6671
|
+
"input.json",
|
|
6672
|
+
"access-config.json",
|
|
6673
|
+
"access.json"
|
|
6674
|
+
].includes(name))
|
|
6675
|
+
keep(name);
|
|
6676
|
+
else if (name === "projects" || name === "commands") {
|
|
6677
|
+
for (const child of names2(store, store.path(name), name === "projects" ? 128 : 2048)) {
|
|
6678
|
+
if (name === "projects" ? !/^[a-f0-9]{64}\.json$/.test(child) : !/^[a-f0-9-]{36}(?:\.result)?\.json$/.test(child))
|
|
6679
|
+
throw new ContractError("corrupt-selected");
|
|
6680
|
+
keep(`${name}/${child}`);
|
|
6681
|
+
}
|
|
6682
|
+
} else if (name !== "recovery-v1" || !store.files.directory(store.path(name)))
|
|
6683
|
+
throw new ContractError("corrupt-selected");
|
|
6684
|
+
}
|
|
6685
|
+
for (const name of [
|
|
6686
|
+
"gateway.json",
|
|
6687
|
+
"input.json",
|
|
6688
|
+
"access-config.json",
|
|
6689
|
+
"access.json"
|
|
6690
|
+
])
|
|
6691
|
+
if (!out.has(name))
|
|
6692
|
+
throw new ContractError("corrupt-selected");
|
|
6693
|
+
for (const [name, file] of out) {
|
|
6694
|
+
if (!name.startsWith("commands/"))
|
|
6695
|
+
continue;
|
|
6696
|
+
if (name.endsWith(".result.json")) {
|
|
6697
|
+
if (!out.has(name.replace(".result.json", ".json")))
|
|
6698
|
+
throw new ContractError("corrupt-selected");
|
|
6699
|
+
decodeProjectEntry(json(file));
|
|
6700
|
+
} else {
|
|
6701
|
+
decodeId(name.slice("commands/".length, -5));
|
|
6702
|
+
const intent = closed(json(file), ["path", "epoch"]);
|
|
6703
|
+
text(intent.path, 4096, true);
|
|
6704
|
+
decodeId(intent.epoch);
|
|
6705
|
+
}
|
|
6706
|
+
}
|
|
6707
|
+
return out;
|
|
6708
|
+
}
|
|
6709
|
+
function signature(resources) {
|
|
6710
|
+
return hash(encoded([...resources].sort(([a], [b]) => a.localeCompare(b, "en")).map(([name, file]) => [
|
|
6711
|
+
name,
|
|
6712
|
+
file.stamp,
|
|
6713
|
+
["gateway.json", "access.json", "access-config.json"].includes(name) ? null : file.identity
|
|
6714
|
+
])));
|
|
6715
|
+
}
|
|
6716
|
+
function state(store) {
|
|
6717
|
+
const registration2 = store.read();
|
|
6718
|
+
if (!registration2)
|
|
6719
|
+
throw new ContractError("unavailable");
|
|
6720
|
+
const resources = capture(store), lock = resources.get("gateway.lock");
|
|
6721
|
+
if (!lock)
|
|
6722
|
+
throw new ContractError("unavailable");
|
|
6723
|
+
decodeId(closed(json(lock), ["epoch"]).epoch);
|
|
6724
|
+
const input = decodeGatewayInput(json(required(resources, "input.json")));
|
|
6725
|
+
if (input.serverConfig !== store.serverConfig || input.stateHome !== store.stateHome)
|
|
6726
|
+
throw new ContractError("conflict");
|
|
6727
|
+
const projects = store.projects();
|
|
6728
|
+
store.associatedProjects();
|
|
6729
|
+
const incarnation = hash(encoded([lock.identity, lock.stamp]));
|
|
6730
|
+
if (registration2.phase === "stopped" && !registration2.recovery?.receipt.startsWith(`recovery-v1/${incarnation}/`))
|
|
6731
|
+
throw new ContractError("corrupt-selected");
|
|
6732
|
+
const archive = store.path(`recovery-v1/${incarnation}`);
|
|
6733
|
+
const attempts = [];
|
|
6734
|
+
let previousResolver = null;
|
|
6735
|
+
const entries = names2(store, archive, 128);
|
|
6736
|
+
for (let n = 0;n < entries.length; n++) {
|
|
6737
|
+
if (!entries.includes(`${n}.json`))
|
|
6738
|
+
throw new ContractError("corrupt-selected");
|
|
6739
|
+
const file = store.files.read(join10(archive, `${n}.json`), 65536);
|
|
6740
|
+
if (!file)
|
|
6741
|
+
throw new ContractError("corrupt-selected");
|
|
6742
|
+
const row = closed(json(file, 65536), [
|
|
6743
|
+
"schema",
|
|
6744
|
+
"lock",
|
|
6745
|
+
"epoch",
|
|
6746
|
+
"digest",
|
|
6747
|
+
"pid",
|
|
6748
|
+
"host",
|
|
6749
|
+
"legacyHost",
|
|
6750
|
+
"retiredProjects"
|
|
6751
|
+
]);
|
|
6752
|
+
literal(row.schema, "cadence-workbench-recovery/1");
|
|
6753
|
+
literal(row.lock, lock.bytes.toString("utf8"));
|
|
6754
|
+
literal(row.epoch, registration2.epoch);
|
|
6755
|
+
decodeHash(row.digest);
|
|
6756
|
+
if (!Array.isArray(row.retiredProjects) || row.retiredProjects.length > 128)
|
|
6757
|
+
throw new ContractError("corrupt-selected");
|
|
6758
|
+
for (const entry of row.retiredProjects) {
|
|
6759
|
+
const project = closed(entry, ["id", "stamp"]);
|
|
6760
|
+
decodeHash(project.id);
|
|
6761
|
+
text(project.stamp, 512, true);
|
|
6762
|
+
}
|
|
6763
|
+
if (typeof row.legacyHost !== "boolean")
|
|
6764
|
+
throw new ContractError("corrupt-selected");
|
|
6765
|
+
previousResolver = recoveryOwnerStatus(integer(row.pid, 1, 2147483647), text(row.host, 255, true));
|
|
6766
|
+
attempts.push(file);
|
|
6767
|
+
}
|
|
6768
|
+
if (encoded(store.read()) !== encoded(registration2))
|
|
6769
|
+
throw new ContractError("conflict");
|
|
6770
|
+
for (const [name, file] of resources)
|
|
6771
|
+
store.files.assert(store.path(name), file);
|
|
6772
|
+
const localOwner = recoveryOwnerStatus(registration2.pid, registration2.host ?? hostname4());
|
|
6773
|
+
const inspection = {
|
|
6774
|
+
scope: "workbench",
|
|
6775
|
+
epoch: registration2.epoch,
|
|
6776
|
+
coordinationDigest: hash(encoded([
|
|
6777
|
+
signature(resources),
|
|
6778
|
+
attempts.map((a) => [a.identity, a.stamp])
|
|
6779
|
+
])),
|
|
6780
|
+
owner: registration2.host ? localOwner : localOwner === "live" ? "live" : "ambiguous",
|
|
6781
|
+
process: { pid: registration2.pid, host: registration2.host ?? null },
|
|
6782
|
+
legacyHost: !registration2.host,
|
|
6783
|
+
previousResolver,
|
|
6784
|
+
projects: projects.length,
|
|
6785
|
+
openings: [...resources.keys()].filter((name) => name.startsWith("commands/") && !name.endsWith(".result.json")).length,
|
|
6786
|
+
resolution: registration2.host ? "local-interactive-only" : "local-host-confirmation-required"
|
|
6787
|
+
};
|
|
6788
|
+
return {
|
|
6789
|
+
registration: registration2,
|
|
6790
|
+
resources,
|
|
6791
|
+
lock,
|
|
6792
|
+
archive,
|
|
6793
|
+
incarnation,
|
|
6794
|
+
attempts,
|
|
6795
|
+
localOwner,
|
|
6796
|
+
inspection
|
|
6797
|
+
};
|
|
6798
|
+
}
|
|
6799
|
+
function inspectGatewayRecovery(store) {
|
|
6800
|
+
const before = state(store), after = state(store);
|
|
6801
|
+
if (encoded(before.inspection) !== encoded(after.inspection))
|
|
6802
|
+
throw new ContractError("conflict");
|
|
6803
|
+
return before.inspection;
|
|
6804
|
+
}
|
|
6805
|
+
function resolveGatewayRecovery(store, expected, assertAuthority, legacyHostConfirmed = false) {
|
|
6806
|
+
assertAuthority();
|
|
6807
|
+
const selected2 = state(store);
|
|
6808
|
+
if (encoded(selected2.inspection) !== encoded(expected))
|
|
6809
|
+
throw new ContractError("conflict");
|
|
6810
|
+
const absent2 = () => {
|
|
6811
|
+
assertAuthority();
|
|
6812
|
+
if (expected.legacyHost && !legacyHostConfirmed || recoveryOwnerStatus(selected2.registration.pid, selected2.registration.host ?? hostname4()) !== "absent")
|
|
6813
|
+
throw new ContractError("quarantine");
|
|
6814
|
+
};
|
|
6815
|
+
absent2();
|
|
6816
|
+
if (expected.previousResolver !== null && expected.previousResolver !== "absent")
|
|
6817
|
+
throw new ContractError("quarantine");
|
|
6818
|
+
if (selected2.attempts.length >= 128)
|
|
6819
|
+
throw new ContractError("capacity");
|
|
6820
|
+
const receiptPath = join10(selected2.archive, `${selected2.attempts.length}.json`);
|
|
6821
|
+
const receipt = store.files.create(receiptPath, encoded({
|
|
6822
|
+
schema: "cadence-workbench-recovery/1",
|
|
6823
|
+
lock: selected2.lock.bytes.toString("utf8"),
|
|
6824
|
+
epoch: expected.epoch,
|
|
6825
|
+
digest: expected.coordinationDigest,
|
|
6826
|
+
pid: process.pid,
|
|
6827
|
+
host: hostname4(),
|
|
6828
|
+
legacyHost: expected.legacyHost,
|
|
6829
|
+
retiredProjects: [...selected2.resources].filter(([name]) => name.startsWith("projects/")).map(([name, file]) => ({
|
|
6830
|
+
id: name.slice("projects/".length, -5),
|
|
6831
|
+
stamp: file.stamp
|
|
6832
|
+
}))
|
|
6833
|
+
}), 65536);
|
|
6834
|
+
const assert = () => {
|
|
6835
|
+
absent2();
|
|
6836
|
+
store.associatedProjects();
|
|
6837
|
+
const current = capture(store);
|
|
6838
|
+
if (signature(current) !== signature(selected2.resources))
|
|
6839
|
+
throw new ContractError("conflict");
|
|
6840
|
+
for (const [name, file] of selected2.resources)
|
|
6841
|
+
store.files.assert(store.path(name), file);
|
|
6842
|
+
if (names2(store, selected2.archive, 128).length !== selected2.attempts.length + 1)
|
|
6843
|
+
throw new ContractError("conflict");
|
|
6844
|
+
for (const [n, file] of selected2.attempts.entries())
|
|
6845
|
+
store.files.assert(join10(selected2.archive, `${n}.json`), file, 65536);
|
|
6846
|
+
store.files.assert(receiptPath, receipt, 65536);
|
|
6847
|
+
};
|
|
6848
|
+
assert();
|
|
6849
|
+
const registration2 = store.files.replace(store.path("gateway.json"), required(selected2.resources, "gateway.json"), encoded({
|
|
6850
|
+
...selected2.registration,
|
|
6851
|
+
phase: "stopped",
|
|
6852
|
+
recovery: {
|
|
6853
|
+
receipt: `recovery-v1/${selected2.incarnation}/${selected2.attempts.length}.json`,
|
|
6854
|
+
sha256: receipt.identity,
|
|
6855
|
+
stamp: receipt.stamp
|
|
6856
|
+
}
|
|
6857
|
+
}));
|
|
6858
|
+
selected2.resources.set("gateway.json", registration2);
|
|
6859
|
+
assert();
|
|
6860
|
+
store.files.remove(store.path("gateway.lock"), selected2.lock);
|
|
6861
|
+
return {
|
|
6862
|
+
status: "resolved",
|
|
6863
|
+
scope: "workbench",
|
|
6864
|
+
epoch: expected.epoch,
|
|
6865
|
+
notice: "operator-acknowledgment-only-project-fences-preserved"
|
|
6866
|
+
};
|
|
6867
|
+
}
|
|
6868
|
+
|
|
6869
|
+
// src/service/recovery-cli.ts
|
|
6870
|
+
var warning = "PID absence is not proof that descendants stopped or credential writes settled. Inspect/stop old processes externally, independently inspect the selected Pi credential store and SDK locks, and inspect retained history/storage. Cadence does not kill, repair, reset, migrate or certify those resources. Unknown historical outcomes remain unknown. A new Start is required.";
|
|
6871
|
+
function native() {
|
|
6872
|
+
if (!isatty(0) || !isatty(1) || !process.stdin.isTTY || !process.stdout.isTTY)
|
|
6873
|
+
throw new ContractError("forbidden");
|
|
6874
|
+
}
|
|
6875
|
+
async function confirm(expected) {
|
|
7137
6876
|
native();
|
|
7138
6877
|
if (process.stdin.readableLength)
|
|
7139
6878
|
throw new ContractError("forbidden");
|
|
7140
6879
|
const identity = JSON.stringify(expected);
|
|
7141
6880
|
const deadline = performance.now() + LIMITS.grantMs;
|
|
7142
6881
|
const prompts = [
|
|
6882
|
+
...expected.legacyHost ? [
|
|
6883
|
+
[
|
|
6884
|
+
"Legacy registration has no host identity. Independently verify this state originated on THIS host and is not shared/copied from another host. Type local-host",
|
|
6885
|
+
"local-host"
|
|
6886
|
+
]
|
|
6887
|
+
] : [],
|
|
7143
6888
|
[
|
|
7144
6889
|
"Confirm old processes stopped externally; acknowledge escaped-descendant uncertainty. Type processes",
|
|
7145
6890
|
"processes"
|
|
@@ -7153,11 +6898,11 @@ async function confirm(expected) {
|
|
|
7153
6898
|
"storage"
|
|
7154
6899
|
],
|
|
7155
6900
|
[
|
|
7156
|
-
`${expected.scope === "global-registry" ? "Release ONLY the global registry lock; all project fences remain." : "Release ONLY the displayed unchanged service-owned fences."} Type resolve ${expected.epoch}`,
|
|
6901
|
+
`${expected.scope === "global-registry" ? "Release ONLY the global registry lock; all project fences remain." : expected.scope === "workbench" ? "Release ONLY the workbench fence and retire its old project associations; all project fences remain." : "Release ONLY the displayed unchanged service-owned fences."} Type resolve ${expected.epoch}`,
|
|
7157
6902
|
`resolve ${expected.epoch}`
|
|
7158
6903
|
]
|
|
7159
6904
|
];
|
|
7160
|
-
await new Promise((
|
|
6905
|
+
await new Promise((resolve5, reject) => {
|
|
7161
6906
|
let index = 0, line = "", done = false;
|
|
7162
6907
|
const raw = process.stdin.isRaw;
|
|
7163
6908
|
const finish = (ok) => {
|
|
@@ -7173,7 +6918,7 @@ async function confirm(expected) {
|
|
|
7173
6918
|
process.stdout.write(`\x1B[?2004l
|
|
7174
6919
|
`);
|
|
7175
6920
|
if (ok)
|
|
7176
|
-
|
|
6921
|
+
resolve5();
|
|
7177
6922
|
else
|
|
7178
6923
|
reject(new ContractError("forbidden"));
|
|
7179
6924
|
};
|
|
@@ -7213,80 +6958,678 @@ async function confirm(expected) {
|
|
|
7213
6958
|
throw new ContractError("stale");
|
|
7214
6959
|
};
|
|
7215
6960
|
}
|
|
7216
|
-
async function
|
|
7217
|
-
if (operation === "resolve")
|
|
7218
|
-
native();
|
|
7219
|
-
const registryLock = c.registryLockDiagnostic();
|
|
7220
|
-
if (registryLock) {
|
|
7221
|
-
let
|
|
7222
|
-
try {
|
|
7223
|
-
|
|
7224
|
-
} catch {
|
|
7225
|
-
const blocked = {
|
|
7226
|
-
scope: "global-registry",
|
|
7227
|
-
...registryLock,
|
|
7228
|
-
coordination: "unavailable",
|
|
7229
|
-
resolution: "blocked-preserve-state",
|
|
7230
|
-
notice: "Registry state is incomplete, changed or unsafe. Preserve it; no lock or project resource was released."
|
|
7231
|
-
};
|
|
7232
|
-
if (operation === "inspect")
|
|
7233
|
-
return blocked;
|
|
7234
|
-
process.stdout.write(`${JSON.stringify(blocked)}
|
|
7235
|
-
`);
|
|
7236
|
-
throw new ContractError("quarantine");
|
|
7237
|
-
}
|
|
7238
|
-
const
|
|
7239
|
-
...
|
|
7240
|
-
process: {
|
|
7241
|
-
...
|
|
7242
|
-
host: diagnosticText(
|
|
7243
|
-
},
|
|
7244
|
-
notice: `Global registry lock blocks every project in this state home. Recovery preserves its original bytes and all project reservations, owner/checkout locks and history. Inspect project fences separately afterward. ${warning}`
|
|
7245
|
-
};
|
|
7246
|
-
if (operation === "inspect")
|
|
7247
|
-
return
|
|
7248
|
-
process.stdout.write(`${JSON.stringify(
|
|
6961
|
+
async function localRegistryRecovery(c, operation) {
|
|
6962
|
+
if (operation === "resolve")
|
|
6963
|
+
native();
|
|
6964
|
+
const registryLock = c.registryLockDiagnostic();
|
|
6965
|
+
if (registryLock) {
|
|
6966
|
+
let expected;
|
|
6967
|
+
try {
|
|
6968
|
+
expected = c.inspectRegistryRecovery();
|
|
6969
|
+
} catch {
|
|
6970
|
+
const blocked = {
|
|
6971
|
+
scope: "global-registry",
|
|
6972
|
+
...registryLock,
|
|
6973
|
+
coordination: "unavailable",
|
|
6974
|
+
resolution: "blocked-preserve-state",
|
|
6975
|
+
notice: "Registry state is incomplete, changed or unsafe. Preserve it; no lock or project resource was released."
|
|
6976
|
+
};
|
|
6977
|
+
if (operation === "inspect")
|
|
6978
|
+
return blocked;
|
|
6979
|
+
process.stdout.write(`${JSON.stringify(blocked)}
|
|
6980
|
+
`);
|
|
6981
|
+
throw new ContractError("quarantine");
|
|
6982
|
+
}
|
|
6983
|
+
const view = {
|
|
6984
|
+
...expected,
|
|
6985
|
+
process: {
|
|
6986
|
+
...expected.process,
|
|
6987
|
+
host: diagnosticText(expected.process.host, 255)
|
|
6988
|
+
},
|
|
6989
|
+
notice: `Global registry lock blocks every project in this state home. Recovery preserves its original bytes and all project reservations, owner/checkout locks and history. Inspect project fences separately afterward. ${warning}`
|
|
6990
|
+
};
|
|
6991
|
+
if (operation === "inspect")
|
|
6992
|
+
return view;
|
|
6993
|
+
process.stdout.write(`${JSON.stringify(view)}
|
|
6994
|
+
`);
|
|
6995
|
+
if (expected.owner !== "absent" || expected.previousResolver !== null && expected.previousResolver !== "absent")
|
|
6996
|
+
throw new ContractError("quarantine");
|
|
6997
|
+
const assertFresh = await confirm(expected);
|
|
6998
|
+
return c.resolveRegistryRecovery(expected, assertFresh);
|
|
6999
|
+
}
|
|
7000
|
+
return;
|
|
7001
|
+
}
|
|
7002
|
+
async function localRecovery(c, operation) {
|
|
7003
|
+
const registry = await localRegistryRecovery(c, operation);
|
|
7004
|
+
if (registry !== undefined)
|
|
7005
|
+
return registry;
|
|
7006
|
+
const history = new HistoryRecovery(c);
|
|
7007
|
+
const expected = await inspectRecovery(c, history);
|
|
7008
|
+
const registration2 = c.recoverySnapshot().registration;
|
|
7009
|
+
const view = {
|
|
7010
|
+
...expected,
|
|
7011
|
+
process: {
|
|
7012
|
+
pid: registration2.pid,
|
|
7013
|
+
host: diagnosticText(registration2.host, 255)
|
|
7014
|
+
},
|
|
7015
|
+
notice: warning
|
|
7016
|
+
};
|
|
7017
|
+
if (operation === "inspect")
|
|
7018
|
+
return view;
|
|
7019
|
+
process.stdout.write(`${JSON.stringify(view)}
|
|
7020
|
+
`);
|
|
7021
|
+
if (expected.owner !== "absent")
|
|
7022
|
+
throw new ContractError("quarantine");
|
|
7023
|
+
const assertFresh = await confirm(expected);
|
|
7024
|
+
return resolveRecovery(c, {
|
|
7025
|
+
expected,
|
|
7026
|
+
acknowledgeEscapedProcesses: true,
|
|
7027
|
+
processes: "externally-resolved",
|
|
7028
|
+
credentials: "independently-resolved",
|
|
7029
|
+
storage: "independently-resolved",
|
|
7030
|
+
assertLocalAuthority: (choice) => {
|
|
7031
|
+
assertFresh();
|
|
7032
|
+
if (JSON.stringify(choice) !== JSON.stringify(expected))
|
|
7033
|
+
throw new ContractError("stale");
|
|
7034
|
+
}
|
|
7035
|
+
}, history);
|
|
7036
|
+
}
|
|
7037
|
+
async function localGatewayRecovery(store, operation) {
|
|
7038
|
+
const registry = await localRegistryRecovery(new ServiceCoordination(null, store.stateHome), operation);
|
|
7039
|
+
if (registry !== undefined)
|
|
7040
|
+
return registry;
|
|
7041
|
+
let expected;
|
|
7042
|
+
try {
|
|
7043
|
+
const observed = store.inspect();
|
|
7044
|
+
if (observed.status === "absent" || observed.status === "stopped") {
|
|
7045
|
+
if (operation === "resolve")
|
|
7046
|
+
throw new ContractError("unavailable");
|
|
7047
|
+
return { ...observed, scope: "workbench" };
|
|
7048
|
+
}
|
|
7049
|
+
expected = inspectGatewayRecovery(store);
|
|
7050
|
+
} catch (error) {
|
|
7051
|
+
if (operation === "resolve")
|
|
7052
|
+
throw error;
|
|
7053
|
+
return {
|
|
7054
|
+
status: "unavailable",
|
|
7055
|
+
scope: "workbench",
|
|
7056
|
+
resolution: "blocked-preserve-state",
|
|
7057
|
+
error: error instanceof ContractError ? error.code : "unavailable",
|
|
7058
|
+
notice: "Workbench state is incomplete, changed or unsafe. Preserve it; no resource was released."
|
|
7059
|
+
};
|
|
7060
|
+
}
|
|
7061
|
+
const view = {
|
|
7062
|
+
...expected,
|
|
7063
|
+
process: {
|
|
7064
|
+
...expected.process,
|
|
7065
|
+
host: expected.process.host === null ? null : diagnosticText(expected.process.host, 255)
|
|
7066
|
+
},
|
|
7067
|
+
notice: `Recovery releases ONLY the workbench fence and retires its exact old project associations. Project references, reservations, owner/checkout locks, opening receipts and history remain unchanged. Explicit Open must associate a project again; recover affected project owners separately. ${warning}`
|
|
7068
|
+
};
|
|
7069
|
+
if (operation === "inspect")
|
|
7070
|
+
return view;
|
|
7071
|
+
process.stdout.write(`${JSON.stringify(view)}
|
|
7072
|
+
`);
|
|
7073
|
+
if (!expected.legacyHost && expected.owner !== "absent" || expected.owner === "live" || expected.previousResolver !== null && expected.previousResolver !== "absent")
|
|
7074
|
+
throw new ContractError("quarantine");
|
|
7075
|
+
const authority = await confirm(expected);
|
|
7076
|
+
return resolveGatewayRecovery(store, expected, authority, expected.legacyHost);
|
|
7077
|
+
}
|
|
7078
|
+
|
|
7079
|
+
// src/service/runtime.ts
|
|
7080
|
+
import { homedir as homedir4 } from "node:os";
|
|
7081
|
+
import { dirname as dirname9, isAbsolute as isAbsolute6, join as join11, resolve as resolve5 } from "node:path";
|
|
7082
|
+
var STARTUP_INPUT_BYTES = 16 * 1024;
|
|
7083
|
+
function absolute(value) {
|
|
7084
|
+
const result = text(value, 4096, true);
|
|
7085
|
+
if (!isAbsolute6(result))
|
|
7086
|
+
throw new ContractError("invalid");
|
|
7087
|
+
return result;
|
|
7088
|
+
}
|
|
7089
|
+
function decodeHostConfig(value) {
|
|
7090
|
+
const o = closed(value, [
|
|
7091
|
+
"schema",
|
|
7092
|
+
"http",
|
|
7093
|
+
"cwd",
|
|
7094
|
+
"agentDir",
|
|
7095
|
+
"tokenSource",
|
|
7096
|
+
"tokenFile"
|
|
7097
|
+
]);
|
|
7098
|
+
const tokenSource = oneOf(o.tokenSource, [
|
|
7099
|
+
"generated",
|
|
7100
|
+
"environment",
|
|
7101
|
+
"file",
|
|
7102
|
+
"config"
|
|
7103
|
+
]);
|
|
7104
|
+
const tokenFile = o.tokenFile === null ? null : absolute(o.tokenFile);
|
|
7105
|
+
if ((tokenSource === "file" || tokenSource === "config") !== !!tokenFile)
|
|
7106
|
+
throw new ContractError("invalid");
|
|
7107
|
+
return {
|
|
7108
|
+
schema: literal(o.schema, "cadence-service-host/1"),
|
|
7109
|
+
http: decodeHttpPolicy(o.http),
|
|
7110
|
+
cwd: absolute(o.cwd),
|
|
7111
|
+
agentDir: absolute(o.agentDir),
|
|
7112
|
+
tokenSource,
|
|
7113
|
+
tokenFile
|
|
7114
|
+
};
|
|
7115
|
+
}
|
|
7116
|
+
function readHostConfig(c) {
|
|
7117
|
+
const owner = c.configuration();
|
|
7118
|
+
if (!owner)
|
|
7119
|
+
return;
|
|
7120
|
+
const snapshot = c.files.read(c.path("host-config.json"));
|
|
7121
|
+
if (!snapshot)
|
|
7122
|
+
throw new ContractError("corrupt-selected");
|
|
7123
|
+
const config = decodeHostConfig(json(snapshot));
|
|
7124
|
+
if (!config.http.port || owner.endpoint !== localOrigin(config.http, config.http.port) || owner.policyIdentity !== hash(encoded(config)))
|
|
7125
|
+
throw new ContractError("conflict");
|
|
7126
|
+
return config;
|
|
7127
|
+
}
|
|
7128
|
+
function hostEnvironment(ambient) {
|
|
7129
|
+
let route;
|
|
7130
|
+
const facts = networkSnapshot(ambient);
|
|
7131
|
+
try {
|
|
7132
|
+
if (facts.NODE_OPTIONS.present)
|
|
7133
|
+
throw new Error;
|
|
7134
|
+
route = sdkNetworkEnvironment(facts, networkSnapshot(undefined));
|
|
7135
|
+
} catch {
|
|
7136
|
+
throw new ContractError("unsupported-context");
|
|
7137
|
+
}
|
|
7138
|
+
const result = Object.create(null);
|
|
7139
|
+
for (const key of Object.keys(ambient)) {
|
|
7140
|
+
const d = Object.getOwnPropertyDescriptor(ambient, key);
|
|
7141
|
+
if (!d || !("value" in d) || typeof d.value !== "string")
|
|
7142
|
+
throw new ContractError("invalid");
|
|
7143
|
+
if (/^(?:CADENCE_|PI_|ABEL_|TRELLIS_|GIT_)/i.test(key) || networkKeys.some((name) => name === key))
|
|
7144
|
+
continue;
|
|
7145
|
+
result[key] = d.value;
|
|
7146
|
+
}
|
|
7147
|
+
if (route.redactionLiterals.length) {
|
|
7148
|
+
result.HTTP_PROXY = route.env.HTTP_PROXY;
|
|
7149
|
+
result.HTTPS_PROXY = route.env.HTTPS_PROXY;
|
|
7150
|
+
result.NODE_USE_ENV_PROXY = "1";
|
|
7151
|
+
}
|
|
7152
|
+
result.PI_OFFLINE = "1";
|
|
7153
|
+
result.NO_COLOR = "1";
|
|
7154
|
+
if (!sameNetwork(facts, networkSnapshot(ambient)))
|
|
7155
|
+
throw new ContractError("stale");
|
|
7156
|
+
return result;
|
|
7157
|
+
}
|
|
7158
|
+
function selectStart(options, ambient) {
|
|
7159
|
+
const workspace = resolveWorkspace(options.workspace), c = new ServiceCoordination(workspace.root, options.stateHome);
|
|
7160
|
+
const old = readHostConfig(c);
|
|
7161
|
+
const env = Object.getOwnPropertyDescriptor(ambient, "CADENCE_WEB_TOKEN");
|
|
7162
|
+
if (env && (!("value" in env) || typeof env.value !== "string"))
|
|
7163
|
+
throw new ContractError("invalid");
|
|
7164
|
+
let fromFile;
|
|
7165
|
+
if (options.config) {
|
|
7166
|
+
const path2 = resolve5(options.config), f = new PrivateFiles(dirname9(path2));
|
|
7167
|
+
const bytes = f.read(path2);
|
|
7168
|
+
if (!bytes)
|
|
7169
|
+
throw new ContractError("corrupt-selected");
|
|
7170
|
+
fromFile = decodeHostConfig(json(bytes));
|
|
7171
|
+
}
|
|
7172
|
+
const base = old ?? fromFile ?? decodeHostConfig({
|
|
7173
|
+
schema: "cadence-service-host/1",
|
|
7174
|
+
cwd: workspace.cwd,
|
|
7175
|
+
http: {
|
|
7176
|
+
schema: "cadence-service-http/1",
|
|
7177
|
+
host: options.host ?? "127.0.0.1",
|
|
7178
|
+
port: options.port ?? 17423,
|
|
7179
|
+
proxy: null
|
|
7180
|
+
},
|
|
7181
|
+
agentDir: resolve5(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join11(ambient.HOME ?? ambient.USERPROFILE ?? homedir4(), ".pi", "agent")),
|
|
7182
|
+
tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
|
|
7183
|
+
tokenFile: options.tokenFile ? resolve5(options.tokenFile) : null
|
|
7184
|
+
});
|
|
7185
|
+
const server = options.serverConfig !== undefined ? resolve5(options.serverConfig) : old?.tokenSource === "config" ? old.tokenFile : null;
|
|
7186
|
+
let selected2 = base;
|
|
7187
|
+
if (server) {
|
|
7188
|
+
if (options.tokenFile !== undefined)
|
|
7189
|
+
throw new ContractError("conflict");
|
|
7190
|
+
const { port } = readServerConfig(server).config;
|
|
7191
|
+
selected2 = decodeHostConfig({
|
|
7192
|
+
...base,
|
|
7193
|
+
http: { ...base.http, port },
|
|
7194
|
+
tokenSource: "config",
|
|
7195
|
+
tokenFile: server
|
|
7196
|
+
});
|
|
7197
|
+
}
|
|
7198
|
+
if (fromFile && encoded(fromFile) !== encoded(selected2) || options.port !== undefined && options.port !== selected2.http.port || options.host !== undefined && options.host !== selected2.http.host || options.agentDir !== undefined && resolve5(options.agentDir) !== selected2.agentDir || options.tokenFile !== undefined && resolve5(options.tokenFile) !== selected2.tokenFile || env && selected2.tokenSource !== "environment" || selected2.tokenSource === "environment" && !env || resolveWorkspace(selected2.cwd).root !== workspace.root)
|
|
7199
|
+
throw new ContractError("conflict");
|
|
7200
|
+
return {
|
|
7201
|
+
workspace: workspace.root,
|
|
7202
|
+
stateHome: c.stateHome,
|
|
7203
|
+
config: selected2,
|
|
7204
|
+
environmentToken: env?.value ?? null
|
|
7205
|
+
};
|
|
7206
|
+
}
|
|
7207
|
+
function decodeHostInput(value) {
|
|
7208
|
+
const o = closed(value, [
|
|
7209
|
+
"workspace",
|
|
7210
|
+
"stateHome",
|
|
7211
|
+
"config",
|
|
7212
|
+
"environmentToken"
|
|
7213
|
+
]);
|
|
7214
|
+
const config = decodeHostConfig(o.config);
|
|
7215
|
+
const environmentToken = o.environmentToken === null ? null : text(o.environmentToken, 1024, true);
|
|
7216
|
+
if (config.tokenSource === "environment" !== (environmentToken !== null))
|
|
7217
|
+
throw new ContractError("invalid");
|
|
7218
|
+
return {
|
|
7219
|
+
workspace: absolute(o.workspace),
|
|
7220
|
+
stateHome: absolute(o.stateHome),
|
|
7221
|
+
config,
|
|
7222
|
+
environmentToken
|
|
7223
|
+
};
|
|
7224
|
+
}
|
|
7225
|
+
function portValue(value) {
|
|
7226
|
+
if (!/^(0|[1-9][0-9]{0,4})$/.test(value))
|
|
7227
|
+
throw new ContractError("invalid");
|
|
7228
|
+
return integer(Number(value), 0, 65535);
|
|
7229
|
+
}
|
|
7230
|
+
var STARTUP_STAGES = [
|
|
7231
|
+
"package",
|
|
7232
|
+
"pi-agent-directory",
|
|
7233
|
+
"pi-auth",
|
|
7234
|
+
"pi-models",
|
|
7235
|
+
"pi-runtime",
|
|
7236
|
+
"service"
|
|
7237
|
+
];
|
|
7238
|
+
function decodeStartupReport(value) {
|
|
7239
|
+
const o = closed(value, ["status", "error", "stage"]);
|
|
7240
|
+
return {
|
|
7241
|
+
status: oneOf(o.status, ["refused", "quarantined"]),
|
|
7242
|
+
error: oneOf(o.error, ERROR_CODES),
|
|
7243
|
+
stage: oneOf(o.stage, STARTUP_STAGES)
|
|
7244
|
+
};
|
|
7245
|
+
}
|
|
7246
|
+
|
|
7247
|
+
// src/service/gateway-cli.ts
|
|
7248
|
+
async function gatewayMain(argv, hostUrl) {
|
|
7249
|
+
const operation = argv.shift();
|
|
7250
|
+
if (![
|
|
7251
|
+
"start",
|
|
7252
|
+
"status",
|
|
7253
|
+
"diagnose",
|
|
7254
|
+
"stop",
|
|
7255
|
+
"token-link",
|
|
7256
|
+
"token-rotate",
|
|
7257
|
+
"recover"
|
|
7258
|
+
].includes(operation ?? ""))
|
|
7259
|
+
throw new ContractError("invalid");
|
|
7260
|
+
const recovery = operation === "recover" ? argv.shift() : undefined;
|
|
7261
|
+
if (operation === "recover" && recovery !== "inspect" && recovery !== "resolve")
|
|
7262
|
+
throw new ContractError("invalid");
|
|
7263
|
+
let config = serverConfigPath(homedir5()), stateHome = defaultStateHome(), agentDir, policyFile;
|
|
7264
|
+
let foreground = false, reveal = false;
|
|
7265
|
+
const seen = new Set;
|
|
7266
|
+
while (argv.length) {
|
|
7267
|
+
const key = argv.shift();
|
|
7268
|
+
if (seen.has(key))
|
|
7269
|
+
throw new ContractError("invalid");
|
|
7270
|
+
seen.add(key);
|
|
7271
|
+
if (key === "--foreground" && operation === "start")
|
|
7272
|
+
foreground = true;
|
|
7273
|
+
else if (key === "--reveal" && operation === "token-link")
|
|
7274
|
+
reveal = true;
|
|
7275
|
+
else if (argv[0] && (operation === "recover" ? ["--server-config", "--state-home"] : ["--server-config", "--state-home", "--agent-dir", "--config"]).includes(key)) {
|
|
7276
|
+
const value = resolve6(argv.shift());
|
|
7277
|
+
if (key === "--server-config")
|
|
7278
|
+
config = value;
|
|
7279
|
+
if (key === "--state-home")
|
|
7280
|
+
stateHome = value;
|
|
7281
|
+
if (key === "--agent-dir")
|
|
7282
|
+
agentDir = value;
|
|
7283
|
+
if (key === "--config")
|
|
7284
|
+
policyFile = value;
|
|
7285
|
+
} else
|
|
7286
|
+
throw new ContractError("invalid");
|
|
7287
|
+
}
|
|
7288
|
+
const store = new GatewayStore(config, stateHome);
|
|
7289
|
+
let result;
|
|
7290
|
+
if (operation === "start") {
|
|
7291
|
+
const network = networkSnapshot(process.env), env = hostEnvironment(process.env);
|
|
7292
|
+
if (!seen.has("--server-config"))
|
|
7293
|
+
ensureServerConfig(homedir5());
|
|
7294
|
+
const server = readServerConfig(config).config;
|
|
7295
|
+
const selectedFile = store.files.read(store.path("input.json"));
|
|
7296
|
+
const selected2 = selectedFile ? decodeGatewayInput(json(selectedFile)) : undefined;
|
|
7297
|
+
let policy = selected2?.policy ?? decodeHttpPolicy({
|
|
7298
|
+
schema: "cadence-service-http/1",
|
|
7299
|
+
host: "127.0.0.1",
|
|
7300
|
+
port: server.port,
|
|
7301
|
+
proxy: null
|
|
7302
|
+
});
|
|
7303
|
+
if (policyFile) {
|
|
7304
|
+
const file = new PrivateFiles(dirname10(policyFile)).read(policyFile);
|
|
7305
|
+
if (!file)
|
|
7306
|
+
throw new ContractError("invalid");
|
|
7307
|
+
policy = decodeHttpPolicy(json(file));
|
|
7308
|
+
}
|
|
7309
|
+
const input = decodeGatewayInput({
|
|
7310
|
+
kind: "workbench",
|
|
7311
|
+
serverConfig: config,
|
|
7312
|
+
stateHome: store.stateHome,
|
|
7313
|
+
agentDir: agentDir ?? selected2?.agentDir ?? resolve6(process.env.PI_CODING_AGENT_DIR ?? join12(homedir5(), ".pi", "agent")),
|
|
7314
|
+
policy: { ...policy, port: server.port }
|
|
7315
|
+
});
|
|
7316
|
+
const status = await serviceStatus(store);
|
|
7317
|
+
if (status && typeof status === "object" && "status" in status && status.status === "alive") {
|
|
7318
|
+
if (!selected2 || encoded(selected2) !== encoded(input))
|
|
7319
|
+
throw new ContractError("conflict");
|
|
7320
|
+
result = status;
|
|
7321
|
+
} else
|
|
7322
|
+
result = await launchHost(input, store, foreground, hostUrl, dirname10(dirname10(fileURLToPath(hostUrl))), env, network);
|
|
7323
|
+
if (!foreground && result && typeof result === "object" && "status" in result && result.status === "alive") {
|
|
7324
|
+
const reply = closed(await nativeCall(store, "token-link", true), [
|
|
7325
|
+
"link"
|
|
7326
|
+
]);
|
|
7327
|
+
process.stderr.write(`Cadence workbench: ${text(reply.link, 4096, true)}
|
|
7328
|
+
No project or default workspace was created.
|
|
7329
|
+
`);
|
|
7330
|
+
}
|
|
7331
|
+
} else if (operation === "recover" && (recovery === "inspect" || recovery === "resolve"))
|
|
7332
|
+
result = await localGatewayRecovery(store, recovery);
|
|
7333
|
+
else if (operation === "stop")
|
|
7334
|
+
result = await stopService(store);
|
|
7335
|
+
else if (operation === "status" || operation === "diagnose")
|
|
7336
|
+
result = await serviceStatus(store, operation === "diagnose");
|
|
7337
|
+
else {
|
|
7338
|
+
if (operation === "token-link" && !reveal)
|
|
7339
|
+
throw new ContractError("forbidden");
|
|
7340
|
+
result = await nativeCall(store, operation, reveal);
|
|
7341
|
+
}
|
|
7342
|
+
if (result !== null)
|
|
7343
|
+
process.stdout.write(`${JSON.stringify(result)}
|
|
7344
|
+
`);
|
|
7345
|
+
if (result && typeof result === "object" && "status" in result && ["unconfirmed", "unavailable", "quarantined", "refused"].includes(String(result.status)))
|
|
7346
|
+
process.exitCode = managementExitCode(result, foreground);
|
|
7347
|
+
if (result && typeof result === "object" && "status" in result && ["unavailable", "quarantined", "refused", "unconfirmed"].includes(String(result.status)))
|
|
7348
|
+
process.stderr.write(`Preserve state; inspect locally: cadence workbench recover inspect --server-config ${JSON.stringify(config)} --state-home ${JSON.stringify(store.stateHome)}. Eligible recovery requires recover resolve in a local terminal; it never starts work.
|
|
7349
|
+
`);
|
|
7350
|
+
}
|
|
7351
|
+
|
|
7352
|
+
// src/service/launchd.ts
|
|
7353
|
+
import { rmSync as rmSync2 } from "node:fs";
|
|
7354
|
+
import { dirname as dirname11, join as join13 } from "node:path";
|
|
7355
|
+
var LABEL = "com.abelxiaoxing.cadence";
|
|
7356
|
+
var MARKER2 = `<?xml version="1.0" encoding="UTF-8"?>
|
|
7357
|
+
<!-- Generated by @abelxiaoxing/cadence`;
|
|
7358
|
+
function plistPath(s) {
|
|
7359
|
+
return join13(s.home, "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
7360
|
+
}
|
|
7361
|
+
var xml = (value) => checked(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
7362
|
+
function buildPlist(u) {
|
|
7363
|
+
const string = (value) => ` <string>${xml(value)}</string>`;
|
|
7364
|
+
return [
|
|
7365
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
7366
|
+
"<!-- Generated by @abelxiaoxing/cadence; `cadence server install` and `restart` rewrite this file. -->",
|
|
7367
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
7368
|
+
'<plist version="1.0">',
|
|
7369
|
+
"<dict>",
|
|
7370
|
+
"\t<key>Label</key>",
|
|
7371
|
+
` <string>${LABEL}</string>`,
|
|
7372
|
+
"\t<key>ProgramArguments</key>",
|
|
7373
|
+
"\t<array>",
|
|
7374
|
+
...[
|
|
7375
|
+
u.node,
|
|
7376
|
+
u.cli,
|
|
7377
|
+
...u.workspace === null ? ["workbench", "start", "--foreground"] : ["start", "--foreground", "--workspace", u.workspace],
|
|
7378
|
+
"--server-config",
|
|
7379
|
+
u.serverConfig
|
|
7380
|
+
].map(string),
|
|
7381
|
+
"\t</array>",
|
|
7382
|
+
...u.workspace === null ? ["<!-- Cadence workbench gateway; no project workspace -->"] : [],
|
|
7383
|
+
"\t<key>WorkingDirectory</key>",
|
|
7384
|
+
` <string>${xml(u.workspace ?? dirname11(dirname11(u.cli)))}</string>`,
|
|
7385
|
+
"\t<key>EnvironmentVariables</key>",
|
|
7386
|
+
"\t<dict>",
|
|
7387
|
+
...u.environment.flatMap(([k, v]) => [
|
|
7388
|
+
` <key>${xml(k)}</key>`,
|
|
7389
|
+
string(v)
|
|
7390
|
+
]),
|
|
7391
|
+
"\t</dict>",
|
|
7392
|
+
"\t<key>RunAtLoad</key>",
|
|
7393
|
+
"\t<true/>",
|
|
7394
|
+
"\t<key>ProcessType</key>",
|
|
7395
|
+
"\t<string>Background</string>",
|
|
7396
|
+
"\t<key>StandardOutPath</key>",
|
|
7397
|
+
` <string>${xml(u.log)}</string>`,
|
|
7398
|
+
"\t<key>StandardErrorPath</key>",
|
|
7399
|
+
` <string>${xml(u.log)}</string>`,
|
|
7400
|
+
"</dict>",
|
|
7401
|
+
"</plist>",
|
|
7402
|
+
""
|
|
7403
|
+
].join(`
|
|
7404
|
+
`);
|
|
7405
|
+
}
|
|
7406
|
+
function plistWorkspace(text2) {
|
|
7407
|
+
if (text2?.includes("<!-- Cadence workbench gateway; no project workspace -->"))
|
|
7408
|
+
return null;
|
|
7409
|
+
const m = text2?.match(/^\t<key>WorkingDirectory<\/key>\n\t<string>([^<]*)<\/string>$/m);
|
|
7410
|
+
return m?.[1].replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
7411
|
+
}
|
|
7412
|
+
var domain = (s) => `gui/${s.uid}`;
|
|
7413
|
+
var target = (s) => `${domain(s)}/${LABEL}`;
|
|
7414
|
+
var loaded = (s) => s.launchctl(["print", target(s)]).status === 0;
|
|
7415
|
+
function disabled(s) {
|
|
7416
|
+
return new RegExp(`"${LABEL.replaceAll(".", "\\.")}" => (true|disabled)`).test(s.launchctl(["print-disabled", domain(s)]).stdout);
|
|
7417
|
+
}
|
|
7418
|
+
async function configureLaunchAgent(s, mode, requested) {
|
|
7419
|
+
if (s.uid === undefined)
|
|
7420
|
+
return { status: "unavailable" };
|
|
7421
|
+
const path2 = plistPath(s);
|
|
7422
|
+
const previous = readGenerated(path2, MARKER2);
|
|
7423
|
+
if (previous === null)
|
|
7424
|
+
return { status: "foreign", unit: path2 };
|
|
7425
|
+
if (mode === "restart" && previous === undefined)
|
|
7426
|
+
return { status: "absent" };
|
|
7427
|
+
const sdk = sdkPackages(s.root);
|
|
7428
|
+
const kept = plistWorkspace(previous);
|
|
7429
|
+
const { workspace, redirectedFrom } = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : kept);
|
|
7430
|
+
if (workspace !== null && !sdk)
|
|
7431
|
+
return { status: "missing-pi-sdk" };
|
|
7432
|
+
const refused2 = await settle(s, kept);
|
|
7433
|
+
if (refused2)
|
|
7434
|
+
return { ...refused2, unit: path2 };
|
|
7435
|
+
if (loaded(s))
|
|
7436
|
+
s.launchctl(["bootout", target(s)]);
|
|
7437
|
+
write(path2, buildPlist({
|
|
7438
|
+
node: s.node,
|
|
7439
|
+
cli: join13(s.root, "src", "service-cli.mjs"),
|
|
7440
|
+
workspace,
|
|
7441
|
+
serverConfig: serverConfigPath(s.home),
|
|
7442
|
+
environment: serviceEnvironment(s),
|
|
7443
|
+
log: join13(s.home, "Library", "Logs", "cadence.log")
|
|
7444
|
+
}));
|
|
7445
|
+
const result = {
|
|
7446
|
+
unit: path2,
|
|
7447
|
+
workspace,
|
|
7448
|
+
...redirectedFrom ? { redirectedFrom } : {}
|
|
7449
|
+
};
|
|
7450
|
+
if (mode === "install")
|
|
7451
|
+
s.launchctl(["enable", target(s)]);
|
|
7452
|
+
else if (disabled(s))
|
|
7453
|
+
return { status: "disabled", ...result };
|
|
7454
|
+
if (workspace !== null && !regularFile(join13(agentDirectory(s), "auth.json")))
|
|
7455
|
+
return { status: "waiting-for-pi-login", ...result };
|
|
7456
|
+
const started = s.launchctl(["bootstrap", domain(s), path2]).status === 0;
|
|
7457
|
+
return { status: started ? "started" : "failed", ...result };
|
|
7458
|
+
}
|
|
7459
|
+
async function stopLaunchAgent(s) {
|
|
7460
|
+
const path2 = plistPath(s);
|
|
7461
|
+
const previous = readGenerated(path2, MARKER2);
|
|
7462
|
+
if (previous === null)
|
|
7463
|
+
return { status: "foreign", unit: path2 };
|
|
7464
|
+
if (previous === undefined)
|
|
7465
|
+
return { status: "absent" };
|
|
7466
|
+
const workspace = plistWorkspace(previous);
|
|
7467
|
+
const refused2 = await settle(s, workspace);
|
|
7468
|
+
return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
|
|
7469
|
+
}
|
|
7470
|
+
async function removeLaunchAgent(s) {
|
|
7471
|
+
const path2 = plistPath(s);
|
|
7472
|
+
const previous = readGenerated(path2, MARKER2);
|
|
7473
|
+
if (previous === null)
|
|
7474
|
+
return { status: "foreign", unit: path2 };
|
|
7475
|
+
if (previous === undefined)
|
|
7476
|
+
return { status: "absent" };
|
|
7477
|
+
const refused2 = await settle(s, plistWorkspace(previous));
|
|
7478
|
+
if (refused2)
|
|
7479
|
+
return { ...refused2, unit: path2 };
|
|
7480
|
+
if (s.uid !== undefined && loaded(s))
|
|
7481
|
+
s.launchctl(["bootout", target(s)]);
|
|
7482
|
+
rmSync2(path2);
|
|
7483
|
+
return { status: "removed", unit: path2 };
|
|
7484
|
+
}
|
|
7485
|
+
function launchAgentStatus(s) {
|
|
7486
|
+
const path2 = plistPath(s);
|
|
7487
|
+
const previous = readGenerated(path2, MARKER2);
|
|
7488
|
+
if (previous === null)
|
|
7489
|
+
return { status: "foreign", unit: path2 };
|
|
7490
|
+
if (previous === undefined)
|
|
7491
|
+
return { status: "absent" };
|
|
7492
|
+
const printed = s.uid === undefined ? undefined : s.launchctl(["print", target(s)]);
|
|
7493
|
+
return {
|
|
7494
|
+
status: printed?.status !== 0 ? "unloaded" : /^\s*state = running$/m.test(printed.stdout) ? "active" : "inactive",
|
|
7495
|
+
enabled: s.uid !== undefined && disabled(s) ? "disabled" : "enabled",
|
|
7496
|
+
unit: path2,
|
|
7497
|
+
workspace: plistWorkspace(previous)
|
|
7498
|
+
};
|
|
7499
|
+
}
|
|
7500
|
+
|
|
7501
|
+
// src/service/login-run.ts
|
|
7502
|
+
import { rmSync as rmSync3 } from "node:fs";
|
|
7503
|
+
import { dirname as dirname12, isAbsolute as isAbsolute7, join as join14 } from "node:path";
|
|
7504
|
+
var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
7505
|
+
var APPROVED_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run";
|
|
7506
|
+
var RUN_VALUE = "Cadence";
|
|
7507
|
+
var MARKER3 = "' Generated by @abelxiaoxing/cadence";
|
|
7508
|
+
function launcherPath(s) {
|
|
7509
|
+
const local = s.env.LOCALAPPDATA;
|
|
7510
|
+
return join14(local && isAbsolute7(local) ? local : join14(s.home, "AppData", "Local"), "cadence-server", "cadence.vbs");
|
|
7511
|
+
}
|
|
7512
|
+
var literal2 = (value) => `"${checked(value).replaceAll('"', '""')}"`;
|
|
7513
|
+
function argument2(value) {
|
|
7514
|
+
if (checked(value).includes('"'))
|
|
7515
|
+
throw new ContractError("invalid");
|
|
7516
|
+
return `"${value}"`;
|
|
7517
|
+
}
|
|
7518
|
+
function buildLauncher(u) {
|
|
7519
|
+
const command = `${argument2(u.node)} ${argument2(u.cli)} ${u.workspace === null ? "workbench start" : `start --workspace ${argument2(u.workspace)}`} --server-config ${argument2(u.serverConfig)}`;
|
|
7520
|
+
return [
|
|
7521
|
+
`${MARKER3}; \`cadence server install\` and \`restart\` rewrite this file.`,
|
|
7522
|
+
`' Started at login by ${RUN_KEY}\\${RUN_VALUE}; window style 0 hides the console.`,
|
|
7523
|
+
'Set shell = CreateObject("WScript.Shell")',
|
|
7524
|
+
...u.environment.length ? ['Set env = shell.Environment("Process")'] : [],
|
|
7525
|
+
...u.environment.map(([k, v]) => `env(${literal2(k)}) = ${literal2(v)}`),
|
|
7526
|
+
...u.workspace === null ? ["' Cadence workbench gateway; no project workspace"] : [],
|
|
7527
|
+
`shell.CurrentDirectory = ${literal2(u.workspace ?? dirname12(dirname12(u.cli)))}`,
|
|
7528
|
+
`shell.Run ${literal2(command)}, 0, False`,
|
|
7529
|
+
""
|
|
7530
|
+
].join(`\r
|
|
7249
7531
|
`);
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7532
|
+
}
|
|
7533
|
+
var utf16 = (text2) => Buffer.concat([Buffer.from([255, 254]), Buffer.from(text2, "utf16le")]);
|
|
7534
|
+
function launcherWorkspace(text2) {
|
|
7535
|
+
if (text2?.includes("' Cadence workbench gateway; no project workspace"))
|
|
7536
|
+
return null;
|
|
7537
|
+
return text2?.match(/^shell\.CurrentDirectory = "((?:[^"]|"")*)"\r?$/m)?.[1].replaceAll('""', '"');
|
|
7538
|
+
}
|
|
7539
|
+
var runEntry = (path2, s) => `"${wscript(s.env)}" "${path2}"`;
|
|
7540
|
+
var present = (s, key) => s.reg(["query", key, "/v", RUN_VALUE]).status === 0;
|
|
7541
|
+
function approvedOff(s) {
|
|
7542
|
+
const r = s.reg(["query", APPROVED_KEY, "/v", RUN_VALUE]);
|
|
7543
|
+
const first = r.status === 0 && /REG_BINARY\s+([0-9A-F]{2})/i.exec(r.stdout);
|
|
7544
|
+
return !!first && (Number.parseInt(first[1], 16) & 1) === 1;
|
|
7545
|
+
}
|
|
7546
|
+
async function configureLoginRun(s, mode, requested) {
|
|
7547
|
+
const path2 = launcherPath(s);
|
|
7548
|
+
const previous = readGenerated(path2, MARKER3);
|
|
7549
|
+
if (previous === null)
|
|
7550
|
+
return { status: "foreign", unit: path2 };
|
|
7551
|
+
if (previous === undefined && present(s, RUN_KEY))
|
|
7552
|
+
return { status: "foreign", unit: `${RUN_KEY}\\${RUN_VALUE}` };
|
|
7553
|
+
if (mode === "restart" && previous === undefined)
|
|
7554
|
+
return { status: "absent" };
|
|
7555
|
+
const sdk = sdkPackages(s.root);
|
|
7556
|
+
const kept = launcherWorkspace(previous);
|
|
7557
|
+
const { workspace, redirectedFrom } = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : kept);
|
|
7558
|
+
if (workspace !== null && !sdk)
|
|
7559
|
+
return { status: "missing-pi-sdk" };
|
|
7560
|
+
const refused2 = await settle(s, kept);
|
|
7561
|
+
if (refused2)
|
|
7562
|
+
return { ...refused2, unit: path2 };
|
|
7563
|
+
const environment = s.env.PI_CODING_AGENT_DIR ? [["PI_CODING_AGENT_DIR", agentDirectory(s)]] : [];
|
|
7564
|
+
write(path2, utf16(buildLauncher({
|
|
7565
|
+
node: s.node,
|
|
7566
|
+
cli: join14(s.root, "src", "service-cli.mjs"),
|
|
7567
|
+
workspace,
|
|
7568
|
+
serverConfig: serverConfigPath(s.home),
|
|
7569
|
+
environment
|
|
7570
|
+
})));
|
|
7571
|
+
const result = {
|
|
7572
|
+
unit: path2,
|
|
7573
|
+
workspace,
|
|
7574
|
+
...redirectedFrom ? { redirectedFrom } : {}
|
|
7575
|
+
};
|
|
7576
|
+
if (mode === "install") {
|
|
7577
|
+
const entry = ["/v", RUN_VALUE, "/t", "REG_SZ", "/d", runEntry(path2, s)];
|
|
7578
|
+
if (s.reg(["add", RUN_KEY, ...entry, "/f"]).status !== 0)
|
|
7579
|
+
return { status: "failed", ...result };
|
|
7580
|
+
s.reg(["delete", APPROVED_KEY, "/v", RUN_VALUE, "/f"]);
|
|
7254
7581
|
}
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
}
|
|
7264
|
-
|
|
7582
|
+
if (workspace !== null && !regularFile(join14(agentDirectory(s), "auth.json")))
|
|
7583
|
+
return { status: "waiting-for-pi-login", ...result };
|
|
7584
|
+
return { status: s.launch(path2) ? "started" : "failed", ...result };
|
|
7585
|
+
}
|
|
7586
|
+
async function stopLoginRun(s) {
|
|
7587
|
+
const path2 = launcherPath(s);
|
|
7588
|
+
const previous = readGenerated(path2, MARKER3);
|
|
7589
|
+
if (previous === null)
|
|
7590
|
+
return { status: "foreign", unit: path2 };
|
|
7591
|
+
if (previous === undefined)
|
|
7592
|
+
return { status: "absent" };
|
|
7593
|
+
const workspace = launcherWorkspace(previous);
|
|
7594
|
+
const refused2 = await settle(s, workspace);
|
|
7595
|
+
return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
|
|
7596
|
+
}
|
|
7597
|
+
async function removeLoginRun(s) {
|
|
7598
|
+
const path2 = launcherPath(s);
|
|
7599
|
+
const previous = readGenerated(path2, MARKER3);
|
|
7600
|
+
if (previous === null)
|
|
7601
|
+
return { status: "foreign", unit: path2 };
|
|
7602
|
+
if (previous === undefined)
|
|
7603
|
+
return { status: "absent" };
|
|
7604
|
+
const refused2 = await settle(s, launcherWorkspace(previous));
|
|
7605
|
+
if (refused2)
|
|
7606
|
+
return { ...refused2, unit: path2 };
|
|
7607
|
+
for (const key of [RUN_KEY, APPROVED_KEY])
|
|
7608
|
+
if (present(s, key))
|
|
7609
|
+
s.reg(["delete", key, "/v", RUN_VALUE, "/f"]);
|
|
7610
|
+
rmSync3(path2);
|
|
7611
|
+
return { status: "removed", unit: path2 };
|
|
7612
|
+
}
|
|
7613
|
+
async function loginRunStatus(s) {
|
|
7614
|
+
const path2 = launcherPath(s);
|
|
7615
|
+
const previous = readGenerated(path2, MARKER3);
|
|
7616
|
+
if (previous === null)
|
|
7617
|
+
return { status: "foreign", unit: path2 };
|
|
7618
|
+
if (previous === undefined)
|
|
7619
|
+
return { status: "absent" };
|
|
7620
|
+
const workspace = launcherWorkspace(previous);
|
|
7621
|
+
const owner = workspace !== undefined ? await s.ownerStatus(workspace, serverConfigPath(s.home)) : "unavailable";
|
|
7622
|
+
return {
|
|
7623
|
+
status: owner === "alive" ? "active" : "inactive",
|
|
7624
|
+
enabled: present(s, RUN_KEY) && !approvedOff(s) ? "enabled" : "disabled",
|
|
7625
|
+
unit: path2,
|
|
7626
|
+
workspace
|
|
7265
7627
|
};
|
|
7266
|
-
if (operation === "inspect")
|
|
7267
|
-
return view;
|
|
7268
|
-
process.stdout.write(`${JSON.stringify(view)}
|
|
7269
|
-
`);
|
|
7270
|
-
if (expected.owner !== "absent")
|
|
7271
|
-
throw new ContractError("quarantine");
|
|
7272
|
-
const assertFresh = await confirm(expected);
|
|
7273
|
-
return resolveRecovery(c, {
|
|
7274
|
-
expected,
|
|
7275
|
-
acknowledgeEscapedProcesses: true,
|
|
7276
|
-
processes: "externally-resolved",
|
|
7277
|
-
credentials: "independently-resolved",
|
|
7278
|
-
storage: "independently-resolved",
|
|
7279
|
-
assertLocalAuthority: (choice) => {
|
|
7280
|
-
assertFresh();
|
|
7281
|
-
if (JSON.stringify(choice) !== JSON.stringify(expected))
|
|
7282
|
-
throw new ContractError("stale");
|
|
7283
|
-
}
|
|
7284
|
-
}, history);
|
|
7285
7628
|
}
|
|
7286
7629
|
|
|
7287
7630
|
// src/service/system-service.ts
|
|
7288
|
-
import { existsSync as existsSync2, realpathSync as
|
|
7289
|
-
import { isAbsolute as isAbsolute8, join as
|
|
7631
|
+
import { existsSync as existsSync2, realpathSync as realpathSync4, rmSync as rmSync4 } from "node:fs";
|
|
7632
|
+
import { isAbsolute as isAbsolute8, join as join15 } from "node:path";
|
|
7290
7633
|
var MARKER4 = "# Generated by @abelxiaoxing/cadence";
|
|
7291
7634
|
function boundary(s) {
|
|
7292
7635
|
if (s.platform !== "linux" || s.scope !== "system")
|
|
@@ -7328,8 +7671,8 @@ function sameStateEnvironment(s, previous) {
|
|
|
7328
7671
|
return false;
|
|
7329
7672
|
values.set(key, value.slice(at + 1).replaceAll("%%", "%"));
|
|
7330
7673
|
}
|
|
7331
|
-
const
|
|
7332
|
-
return values.get("HOME") === s.home && values.get("XDG_STATE_HOME") === (
|
|
7674
|
+
const state2 = s.env.XDG_STATE_HOME;
|
|
7675
|
+
return values.get("HOME") === s.home && values.get("XDG_STATE_HOME") === (state2 && isAbsolute8(state2) ? state2 : undefined);
|
|
7333
7676
|
} catch {
|
|
7334
7677
|
return false;
|
|
7335
7678
|
}
|
|
@@ -7367,12 +7710,12 @@ async function configureSystemService(s, mode, requested) {
|
|
|
7367
7710
|
const selected2 = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : unitWorkspace(previous), false);
|
|
7368
7711
|
if (selected2.workspace !== null && !sdk)
|
|
7369
7712
|
return { status: "missing-pi-sdk", scope: "system" };
|
|
7370
|
-
const selectedReal = selected2.workspace && existsSync2(selected2.workspace) ?
|
|
7713
|
+
const selectedReal = selected2.workspace && existsSync2(selected2.workspace) ? realpathSync4(selected2.workspace) : undefined;
|
|
7371
7714
|
const agentDir = agentDirectory(s);
|
|
7372
7715
|
const content = buildUnit({
|
|
7373
7716
|
system: true,
|
|
7374
7717
|
node: s.node,
|
|
7375
|
-
cli:
|
|
7718
|
+
cli: join15(s.root, "src", "service-cli.mjs"),
|
|
7376
7719
|
workspace: selected2.workspace,
|
|
7377
7720
|
serverConfig: serviceConfigPath(s),
|
|
7378
7721
|
agentDir,
|
|
@@ -7404,7 +7747,7 @@ async function configureSystemService(s, mode, requested) {
|
|
|
7404
7747
|
return { status: "foreign", ...result };
|
|
7405
7748
|
if (selectedReal && selected2.workspace) {
|
|
7406
7749
|
try {
|
|
7407
|
-
if (
|
|
7750
|
+
if (realpathSync4(selected2.workspace) !== selectedReal)
|
|
7408
7751
|
return { status: "workspace-changed", ...result };
|
|
7409
7752
|
} catch {
|
|
7410
7753
|
return { status: "workspace-changed", ...result };
|
|
@@ -7421,7 +7764,7 @@ async function configureSystemService(s, mode, requested) {
|
|
|
7421
7764
|
return { status: "failed", ...result, ...selected2 };
|
|
7422
7765
|
if (mode === "install" && s.systemctl(["enable", s.unit]).status !== 0)
|
|
7423
7766
|
return { status: "failed", ...result, ...selected2 };
|
|
7424
|
-
if (selected2.workspace !== null && !regularFile(
|
|
7767
|
+
if (selected2.workspace !== null && !regularFile(join15(agentDir, "auth.json")))
|
|
7425
7768
|
return { status: "waiting-for-pi-login", ...result, ...selected2 };
|
|
7426
7769
|
s.systemctl(["reset-failed", s.unit]);
|
|
7427
7770
|
return {
|
|
@@ -7583,7 +7926,12 @@ async function stopService(c) {
|
|
|
7583
7926
|
}
|
|
7584
7927
|
const deadline = performance.now() + LIMITS.stopWaitMs;
|
|
7585
7928
|
while (performance.now() < deadline) {
|
|
7586
|
-
|
|
7929
|
+
let observed2;
|
|
7930
|
+
try {
|
|
7931
|
+
observed2 = c.inspect();
|
|
7932
|
+
} catch {
|
|
7933
|
+
observed2 = { status: "unavailable" };
|
|
7934
|
+
}
|
|
7587
7935
|
if ("epoch" in observed2 && observed2.epoch === epoch && observed2.status === "stopped")
|
|
7588
7936
|
return { status: "stopped", epoch };
|
|
7589
7937
|
if (observed2.status === "quarantined")
|
|
@@ -7597,6 +7945,25 @@ async function stopService(c) {
|
|
|
7597
7945
|
function failedStatus(result) {
|
|
7598
7946
|
return !!(result && typeof result === "object" && ("status" in result) && ["unconfirmed", "unavailable", "quarantined", "refused"].includes(String(result.status)));
|
|
7599
7947
|
}
|
|
7948
|
+
function managementExitCode(result, foreground = false) {
|
|
7949
|
+
if (foreground) {
|
|
7950
|
+
try {
|
|
7951
|
+
decodeStartupReport(result);
|
|
7952
|
+
return 78;
|
|
7953
|
+
} catch {}
|
|
7954
|
+
}
|
|
7955
|
+
return 1;
|
|
7956
|
+
}
|
|
7957
|
+
function observedServerResult(result, service) {
|
|
7958
|
+
return {
|
|
7959
|
+
...result,
|
|
7960
|
+
service,
|
|
7961
|
+
...result.status === "started" && service !== "alive" ? {
|
|
7962
|
+
managerStatus: result.status,
|
|
7963
|
+
status: service === "quarantined" ? "quarantined" : "unconfirmed"
|
|
7964
|
+
} : {}
|
|
7965
|
+
};
|
|
7966
|
+
}
|
|
7600
7967
|
function serverConfigHint(error) {
|
|
7601
7968
|
return `Cadence changed nothing: ${error.path} ${error.reason}.
|
|
7602
7969
|
Fix it and run the command again; the file holds the WebUI port and token as ${PORT_KEY}=<1-65535> and ${TOKEN_KEY}=<at least 32 characters>, one line each.
|
|
@@ -7626,10 +7993,10 @@ function startupGuidance(result, input) {
|
|
|
7626
7993
|
return `Pi's agent directory ${agent} must be an existing real directory (not a symlink) owned by you.
|
|
7627
7994
|
`;
|
|
7628
7995
|
case "pi-auth":
|
|
7629
|
-
return `${
|
|
7996
|
+
return `${join16(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
|
|
7630
7997
|
`;
|
|
7631
7998
|
case "pi-models":
|
|
7632
|
-
return `${
|
|
7999
|
+
return `${join16(agent, "models.json")} must be your own single-link regular file using only fields Cadence's pinned Pi SDK supports; a field only newer Pi understands is refused.
|
|
7633
8000
|
`;
|
|
7634
8001
|
case "pi-runtime":
|
|
7635
8002
|
return `Loading Pi's SDK model runtime failed after the owner registered (optional SDK packages missing, or sources the SDK refuses); it stays quarantined.
|
|
@@ -7759,6 +8126,8 @@ async function launchHost(input, c, foreground, hostUrl, cwd, env, network) {
|
|
|
7759
8126
|
process.stdout.write(`${JSON.stringify(result)}
|
|
7760
8127
|
`);
|
|
7761
8128
|
await closure;
|
|
8129
|
+
if (failedStatus(result))
|
|
8130
|
+
process.exitCode = managementExitCode(result, true);
|
|
7762
8131
|
return null;
|
|
7763
8132
|
}
|
|
7764
8133
|
return result;
|
|
@@ -7845,7 +8214,7 @@ async function serverAction(s, action, requested, linger) {
|
|
|
7845
8214
|
}
|
|
7846
8215
|
}
|
|
7847
8216
|
function serverGuidance(s, action, result, link) {
|
|
7848
|
-
const cli = piUserInstall(s) ? `node ${JSON.stringify(
|
|
8217
|
+
const cli = piUserInstall(s) ? `node ${JSON.stringify(join16(s.root, "src", "service-cli.mjs"))}` : "cadence";
|
|
7849
8218
|
const note = result.redirectedFrom ? `Cadence workspace: ${result.workspace} (${result.redirectedFrom} contains Cadence's private state)
|
|
7850
8219
|
` : "";
|
|
7851
8220
|
return note + serverStatusGuidance(s, action, result, cli, link);
|
|
@@ -7877,6 +8246,11 @@ function managerDetail(s) {
|
|
|
7877
8246
|
}
|
|
7878
8247
|
function serverStatusGuidance(s, action, result, cli, link) {
|
|
7879
8248
|
const workspace = JSON.stringify(result.workspace ?? "");
|
|
8249
|
+
if (result.workspace === null && ["quarantined", "unconfirmed", "unavailable"].includes(result.status))
|
|
8250
|
+
return `Cadence workbench is not ready (${result.status}); no successful startup or Stop is inferred.
|
|
8251
|
+
Inspect locally: ${cli} workbench recover inspect --server-config ${JSON.stringify(serviceConfigPath(s))}
|
|
8252
|
+
Use the definition's same account and state home; eligible recovery requires recover resolve in a local terminal. Preserve locks and history.
|
|
8253
|
+
`;
|
|
7880
8254
|
if (result.workspace === null && result.status === "started")
|
|
7881
8255
|
return `Cadence unified workbench: ${link ?? result.endpoint ?? result.service ?? "unconfirmed"}
|
|
7882
8256
|
No default workspace was created. Projects are opened explicitly.
|
|
@@ -7907,7 +8281,7 @@ ${manage}
|
|
|
7907
8281
|
${linger}`;
|
|
7908
8282
|
}
|
|
7909
8283
|
case "waiting-for-pi-login":
|
|
7910
|
-
return `Cadence is registered for ${result.workspace}; it starts once Pi has ${
|
|
8284
|
+
return `Cadence is registered for ${result.workspace}; it starts once Pi has ${join16(agentDirectory(s), "auth.json")}.
|
|
7911
8285
|
Log in to Pi, then: ${cli} server restart${flags}
|
|
7912
8286
|
${config}
|
|
7913
8287
|
${lingerGuidance(result.linger)}`;
|
|
@@ -7953,7 +8327,10 @@ ${lingerGuidance(result.linger)}`;
|
|
|
7953
8327
|
`;
|
|
7954
8328
|
case "unconfirmed":
|
|
7955
8329
|
case "quarantined":
|
|
7956
|
-
return `Cadence
|
|
8330
|
+
return `Cadence owner reports ${result.status}; successful startup or orderly Stop is not confirmed.
|
|
8331
|
+
Inspect: ${cli} diagnose --workspace ${workspace}
|
|
8332
|
+
Local recovery: ${cli} recover inspect --workspace ${workspace}
|
|
8333
|
+
Preserve uncertain state; recovery requires separate fresh terminal confirmation.
|
|
7957
8334
|
`;
|
|
7958
8335
|
case "failed":
|
|
7959
8336
|
return `Could not register or start Cadence's service; ${managerDetail(s)}.
|
|
@@ -8057,9 +8434,26 @@ async function serverMain(argv, hostUrl) {
|
|
|
8057
8434
|
}
|
|
8058
8435
|
let link;
|
|
8059
8436
|
if (result.status === "started" && result.workspace !== undefined) {
|
|
8060
|
-
|
|
8437
|
+
const workspace = result.workspace;
|
|
8438
|
+
const observation = await observeStarted(workspace, serviceConfigPath(s));
|
|
8439
|
+
result = {
|
|
8440
|
+
...observedServerResult(result, observation.service),
|
|
8441
|
+
...observation
|
|
8442
|
+
};
|
|
8061
8443
|
if (result.service === "alive")
|
|
8062
|
-
link = await privateLink(
|
|
8444
|
+
link = await privateLink(workspace, undefined, serviceConfigPath(s));
|
|
8445
|
+
}
|
|
8446
|
+
if (action === "status" && result.workspace !== undefined && ["active", "activating", "failed"].includes(result.status)) {
|
|
8447
|
+
const target2 = result.workspace === null ? new GatewayStore(serviceConfigPath(s)) : new ServiceCoordination(result.workspace);
|
|
8448
|
+
const observed2 = await serviceStatus(target2);
|
|
8449
|
+
if (observed2 && typeof observed2 === "object" && "status" in observed2) {
|
|
8450
|
+
const service = String(observed2.status);
|
|
8451
|
+
result = {
|
|
8452
|
+
...result,
|
|
8453
|
+
service,
|
|
8454
|
+
...service === "quarantined" ? { managerStatus: result.status, status: service } : {}
|
|
8455
|
+
};
|
|
8456
|
+
}
|
|
8063
8457
|
}
|
|
8064
8458
|
process.stderr.write(serverGuidance(s, action, result, link));
|
|
8065
8459
|
process.stdout.write(`${JSON.stringify(result)}
|
|
@@ -8132,6 +8526,8 @@ async function projectMain(argv, hostUrl) {
|
|
|
8132
8526
|
process.exitCode = 1;
|
|
8133
8527
|
}
|
|
8134
8528
|
async function managementMain(argv, hostUrl) {
|
|
8529
|
+
const foregroundStart = argv.includes("--foreground") && (argv[0] === "start" || argv[0] === "workbench" && argv[1] === "start");
|
|
8530
|
+
const recoveryCommand = argv[0] === "recover" || argv[0] === "workbench" && argv[1] === "recover";
|
|
8135
8531
|
try {
|
|
8136
8532
|
if (!process.versions.node || process.versions.bun)
|
|
8137
8533
|
throw new ContractError("unsupported-context");
|
|
@@ -8146,15 +8542,16 @@ async function managementMain(argv, hostUrl) {
|
|
|
8146
8542
|
"diagnose",
|
|
8147
8543
|
"stop",
|
|
8148
8544
|
"token-link",
|
|
8149
|
-
"token-rotate"
|
|
8545
|
+
"token-rotate",
|
|
8546
|
+
"recover"
|
|
8150
8547
|
].includes(command ?? "") && !argv.includes("--workspace")) {
|
|
8151
8548
|
await gatewayMain([command, ...argv], hostUrl);
|
|
8152
8549
|
return;
|
|
8153
8550
|
}
|
|
8154
8551
|
if (command === "--help" && !argv.length) {
|
|
8155
8552
|
process.stdout.write(`cadence start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--workspace PATH] [--state-home PATH]
|
|
8156
|
-
Without --workspace these commands address the unified workbench
|
|
8157
|
-
cadence workbench start|status|diagnose|stop|token-link --reveal|token-rotate [--server-config PATH] [--state-home PATH]
|
|
8553
|
+
Without --workspace these commands (including recover) address the unified workbench, never cwd; no default project directory is created. Project recovery requires explicit --workspace PATH. Recovery checks the account-wide registry lock first.
|
|
8554
|
+
cadence workbench start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--server-config PATH] [--state-home PATH]
|
|
8158
8555
|
cadence server install [--workspace PATH] [--no-linger]|status|restart|stop|uninstall
|
|
8159
8556
|
Linux server options: --system (root, no linger) or --user; --name NAME selects an independent service; install also accepts --port PORT (required for a new named service). Root defaults to --system. No automatic sudo or profile migration.
|
|
8160
8557
|
cadence project open [PATH] [--create] [--agent-dir PATH] [--state-home PATH]
|
|
@@ -8258,11 +8655,18 @@ Recovery resolve requires this local native terminal, exact unchanged dead-owner
|
|
|
8258
8655
|
process.stdout.write(`${JSON.stringify(result)}
|
|
8259
8656
|
`);
|
|
8260
8657
|
if (failedStatus(result))
|
|
8261
|
-
process.exitCode =
|
|
8262
|
-
} catch {
|
|
8263
|
-
process.stderr.write(
|
|
8658
|
+
process.exitCode = managementExitCode(result, foreground);
|
|
8659
|
+
} catch (error) {
|
|
8660
|
+
process.stderr.write(`${JSON.stringify({
|
|
8661
|
+
error: "service-refused",
|
|
8662
|
+
...recoveryCommand ? {
|
|
8663
|
+
code: error instanceof ContractError ? error.code : "unavailable",
|
|
8664
|
+
stage: "recovery",
|
|
8665
|
+
notice: "Use workbench recover for account/workbench state or explicit --workspace for an existing project; preserve state on refusal."
|
|
8666
|
+
} : {}
|
|
8667
|
+
})}
|
|
8264
8668
|
`);
|
|
8265
|
-
process.exitCode = 1;
|
|
8669
|
+
process.exitCode = foregroundStart && (error instanceof ContractError || error instanceof ServerConfigRefusal) ? 78 : 1;
|
|
8266
8670
|
}
|
|
8267
8671
|
}
|
|
8268
8672
|
|