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