@catalyst-cloud/cli 0.16.12 → 0.16.14
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/CHANGELOG.md +8 -0
- package/dist/onboard-next.js +4 -2
- package/dist/onboard-runner.js +32 -11
- package/dist/onboard.js +1 -0
- package/package.json +1 -1
- package/skills/catalyst-github/SKILL.md +1 -1
- package/skills/catalyst-linear/SKILL.md +1 -1
- package/skills/catalyst-onboard/SKILL.md +1 -1
- package/skills/run-this-project/SKILL.md +1 -1
- package/skills/unstick/SKILL.md +1 -1
- package/skills/what-needs-me/SKILL.md +1 -1
- package/skills/what-this-repo-needs/SKILL.md +1 -1
- package/skills/whats-happening/SKILL.md +1 -1
- package/vendor/self-host/darwin-thoughts-custody/manifest.json +2 -2
- package/vendor/self-host/darwin-thoughts-custody/verifier.mjs +80 -16
- package/vendor/self-host/provenance.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.16.14
|
|
4
|
+
|
|
5
|
+
Setup uses the updated macOS folder checks and verifies the required release image before starting work. Re-run setup to update this computer.
|
|
6
|
+
|
|
7
|
+
## 0.16.13
|
|
8
|
+
|
|
9
|
+
Setup on macOS now verifies private file access when Docker changes a folder’s displayed owner in either direction. Signed access verification remains required. Re-run setup to update this computer.
|
|
10
|
+
|
|
3
11
|
## 0.16.12
|
|
4
12
|
|
|
5
13
|
Setup on macOS now handles changing folder ownership during the local storage check. Signed file-access verification remains required. Re-run setup to update this computer.
|
package/dist/onboard-next.js
CHANGED
|
@@ -427,13 +427,15 @@ export function onboardReasonText(step, context = {}) {
|
|
|
427
427
|
const commands = nativeEgressInstallCommands();
|
|
428
428
|
return "Native Linux needs a root-managed session egress producer before the runner can take work. Its elevated installation creates the isolated session network and private-range and host-isolation nftables rules. Review and authorize this separately; setup has not run it. It requires systemd, python3, Docker and nftables. " + (commands ? "Install the verified bundled artifacts, then run the same setup command:\n" + commands : "Setup could not verify its bundled artifacts. Reinstall the CLI before authorizing installation.");
|
|
429
429
|
}
|
|
430
|
-
if (reason === "runner_images_unavailable") {
|
|
430
|
+
if (reason === "runner_images_unavailable" || reason === "runner_supervisor_image_unavailable") {
|
|
431
431
|
const image = step.evidence?.image;
|
|
432
432
|
const named = typeof image === "string" &&
|
|
433
433
|
/^[a-z0-9][a-z0-9._:/-]{0,255}@sha256:[0-9a-f]{64}$/.test(image)
|
|
434
434
|
? ` ${image}`
|
|
435
435
|
: "";
|
|
436
|
-
return
|
|
436
|
+
return reason === "runner_supervisor_image_unavailable"
|
|
437
|
+
? `Setup could not verify or download the supervisor image required by this CLI release${named}. It has not started new work. Check network access and run the same setup command again.`
|
|
438
|
+
: `This machine does not have the Catalyst image${named}, and its pull did not complete. Check network access and run the same setup command to retry the anonymous pull.`;
|
|
437
439
|
}
|
|
438
440
|
if (reason === "runner_org_key_missing") {
|
|
439
441
|
const where = context.baseUrl
|
package/dist/onboard-runner.js
CHANGED
|
@@ -20,13 +20,13 @@ import { onboardStateRoot, } from "./onboard.js";
|
|
|
20
20
|
/** The Compose project name the vendored file declares. */
|
|
21
21
|
export const RUNNER_PROJECT = "catalyst-host";
|
|
22
22
|
export const RUNNER_SESSION_NETWORK = "catalyst-session-v1";
|
|
23
|
-
/**
|
|
24
|
-
*
|
|
23
|
+
/** Supervisor from ee729b12 (Host images 38138827750); runner from
|
|
24
|
+
* 91a82214 (Runner image 38133282600). The watchdog retains its reviewed pin.
|
|
25
25
|
* Explicit environment values override these pins; a saved runner image is preserved. */
|
|
26
26
|
export const RUNNER_HOST_IMAGES = {
|
|
27
|
-
supervisor: "ghcr.io/coalesce-labs/catalyst-supervisor@sha256:
|
|
27
|
+
supervisor: "ghcr.io/coalesce-labs/catalyst-supervisor@sha256:3331ebc4204ae3975a4efb2c870c941f9c0f5ab435735e91dc9977941c91e9a8",
|
|
28
28
|
watchdog: "ghcr.io/coalesce-labs/catalyst-deadline-watchdog@sha256:a5999dbf229ac432b83c2ed3fe43fa251366baceb73af2f57590890a6a758f0d",
|
|
29
|
-
runner: "ghcr.io/coalesce-labs/catalyst-runner@sha256:
|
|
29
|
+
runner: "ghcr.io/coalesce-labs/catalyst-runner@sha256:9db2bf2c5580e2a1459fd076fa32c9af34456cc639ec3ab92b57b6ad98da67fb",
|
|
30
30
|
};
|
|
31
31
|
const IMAGE_REF = /^[a-z0-9][a-z0-9._:/-]{0,255}@sha256:[0-9a-f]{64}$/;
|
|
32
32
|
const HOST_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$/;
|
|
@@ -267,15 +267,32 @@ export function dockerRunnerEngine(deps = {}) {
|
|
|
267
267
|
return "unverified";
|
|
268
268
|
}
|
|
269
269
|
}
|
|
270
|
-
return { dir, hostName, baseUrl: normalizeBaseUrl(baseUrl) };
|
|
270
|
+
return { dir, hostName, baseUrl: normalizeBaseUrl(baseUrl), supervisorImage: config.Image };
|
|
271
271
|
}
|
|
272
272
|
catch {
|
|
273
273
|
return "unverified";
|
|
274
274
|
}
|
|
275
275
|
},
|
|
276
276
|
async imageArch(ref, signal) {
|
|
277
|
-
|
|
278
|
-
|
|
277
|
+
if (!IMAGE_REF.test(ref))
|
|
278
|
+
return null;
|
|
279
|
+
const read = await docker(["image", "inspect", ref], { signal });
|
|
280
|
+
if (read.code !== 0)
|
|
281
|
+
return null;
|
|
282
|
+
try {
|
|
283
|
+
const rows = JSON.parse(read.stdout);
|
|
284
|
+
if (!Array.isArray(rows) || rows.length !== 1)
|
|
285
|
+
return null;
|
|
286
|
+
const row = object(rows[0]);
|
|
287
|
+
if (typeof row?.Id !== "string" || !/^sha256:[0-9a-f]{64}$/.test(row.Id) ||
|
|
288
|
+
row.Os !== "linux" || typeof row.Architecture !== "string" || !row.Architecture ||
|
|
289
|
+
!Array.isArray(row.RepoDigests) || !row.RepoDigests.includes(ref))
|
|
290
|
+
return null;
|
|
291
|
+
return row.Architecture;
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
279
296
|
},
|
|
280
297
|
async pull(ref, signal) {
|
|
281
298
|
const host = await endpoint(signal);
|
|
@@ -711,7 +728,8 @@ export function onboardRunnerAdapter(input = {}) {
|
|
|
711
728
|
if (!info.vm && !(await engine.nativeEgressStatus?.(ctx.now().getTime(), signal)))
|
|
712
729
|
return waiting("runner_native_egress_setup_required", selected);
|
|
713
730
|
return { account, baseUrl: normalizeBaseUrl(cfg.baseUrl), teamKey, teamId, cloud, dir, info, images,
|
|
714
|
-
...(installation && installation !== "missing" ? { installedName: installation.hostName
|
|
731
|
+
...(installation && installation !== "missing" ? { installedName: installation.hostName,
|
|
732
|
+
installedSupervisorImage: installation.supervisorImage } : {}) };
|
|
715
733
|
}
|
|
716
734
|
async function desiredEnv(p, ctx, joinToken) {
|
|
717
735
|
const saved = readEnvFile(join(p.dir, ".env")) ?? {};
|
|
@@ -852,8 +870,9 @@ export function onboardRunnerAdapter(input = {}) {
|
|
|
852
870
|
catch {
|
|
853
871
|
/* A missing or unreadable compose needs an explicitly authorized repair. */
|
|
854
872
|
}
|
|
855
|
-
const changed = !saved || !composeMatches || Object.entries(want).some(([key, value]) => saved[key] !== value)
|
|
856
|
-
|
|
873
|
+
const changed = !saved || !composeMatches || Object.entries(want).some(([key, value]) => saved[key] !== value) ||
|
|
874
|
+
(p.installedSupervisorImage !== undefined && p.installedSupervisorImage !== p.images.supervisor);
|
|
875
|
+
const hostEvidence = { ...selected, hostName: name, supervisorImage: p.images.supervisor };
|
|
857
876
|
const pending = () => mayAct ? { state: "pending" } : waiting("runner_needs_runner_flag", hostEvidence);
|
|
858
877
|
if (!running || changed)
|
|
859
878
|
return afterAct ? waiting("runner_compose_not_running", hostEvidence) : pending();
|
|
@@ -983,7 +1002,9 @@ export function onboardRunnerAdapter(input = {}) {
|
|
|
983
1002
|
};
|
|
984
1003
|
const imagesRefused = await prepareRunnerImages(engine, Object.values(p.images), p.info.arch, signal);
|
|
985
1004
|
if (imagesRefused)
|
|
986
|
-
return waiting(imagesRefused.reason
|
|
1005
|
+
return waiting(imagesRefused.reason === "runner_images_unavailable" && imagesRefused.image === p.images.supervisor
|
|
1006
|
+
? "runner_supervisor_image_unavailable" : imagesRefused.reason, { ...selected, supervisorImage: p.images.supervisor,
|
|
1007
|
+
...(imagesRefused.image ? { image: imagesRefused.image } : {}) });
|
|
987
1008
|
if (p.info.vm) {
|
|
988
1009
|
const capability = await engine.vmCapabilities(p.dir, p.images.supervisor, p.baseUrl, signal);
|
|
989
1010
|
if (capability !== "ready")
|
package/dist/onboard.js
CHANGED
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Catalyst's GitHub: show a ticket's pull request with its checks, reviews and review threads, say whether it is mergeable under this repository's policy, and explain what a PR accumulates as the ticket moves (the branch, the draft, the rewrite, the force-pushes, the labels, the queue). Use when someone asks "show me the PR", "what are the checks saying", "why hasn't it merged", "what does the queue need", "what does that label mean", or wants the branch and merge conventions Catalyst follows.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.14 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Catalyst's GitHub
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Catalyst's view of Linear on the customer's own cloud account. Reads a ticket with its comments, relations, labels, linked pull requests and agent sessions inline, from the cloud API by default, with local replica reads available as an explicit opt-in, always naming the source; searches tickets, PRs, projects and initiatives; writes comments, card moves, labels and new tickets through the account's agent proxy as the app actor, with every route, stage id, label id and marker read from the account's contract. Knows what a ticket accumulates as Catalyst works it (phase-outcome comments, the document per phase, the agent session, the labels, the single blocks relation, the bookkeeping marker, the eyes acknowledgement). Use when a person says "show me the ticket", "what did Catalyst write on it", "comment on it", "move it", "label it" or "file a ticket". Not for raising a decision for a human (what-needs-me) and not for pull requests (catalyst-github).
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.14 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Catalyst Linear
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Set up Catalyst Cloud and say whether setup is working. Walks a person from nothing to their first ticket running, one step at a time: the coding account, the project, the integrations and connected accounts, each repository's settings file, then one real ticket moving. Also logs this machine in, reads the readiness verdict with who can fix each failure, and checks the optional local replica. Use when someone says "set me up", "onboard me", "I just signed up", "get me started", "what do I do first", "log me in", "which account is this machine on", "am I set up", "what is missing", "why does nothing happen" or "is the replica running", when a login expired, or when a skill script exits 2 saying this machine is not connected. Does every step a key can do through the catalyst CLI, hands over the exact page for the steps only a browser can do, and never claims a step it did not watch succeed.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.14 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Onboard me
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Own one Catalyst Cloud project end to end until it closes. Use when the person says "run this project for me", "own this until it ships", "keep this moving", or hands you a project id or a set of tickets to drive. Subscribes to the account's event stream for the scope through the catalyst CLI, reacts to each change in the same turn, makes tickets ready and moves them to dispatch, parks what should stop, chases stalls, escalates inward, and keeps one status summary current. Writes to Linear as the app actor; never polls.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.14 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Run this project
|
|
10
10
|
|
package/skills/unstick/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Get a stuck Catalyst Cloud ticket moving again. Use when the person asks "why is this parked and can you release it?", "unpark this", "get things flowing again", "the outage is over, retry what failed", or hands you a ticket that is not moving. Reads why nothing runs and what holds the ticket through the catalyst CLI, decides whether the recorded cause is fixed, previews the release, releases every governor holding the ticket the right way (or one failure class across a team), and raises an ask only for what a person has to do. Never releases a cause it cannot show changed without saying so.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.14 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Unstick
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
The human's decision inbox on Catalyst Cloud, and the one way an agent raises a decision on their behalf. Use when the person asks "what needs me?", "what am I blocking?", or when active work is gated on a choice only they can make. Lists open asks ranked by how much open work each one holds, files an ask through the cloud's ask route with the account's own template, and records the answer so the held work releases. Never answers as the human, never duplicates an open ask.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.14 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# What needs me
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
What environment variable names does this repository need, and where does each one come from? Scans the repository offline (no login, no network) and lists the names in three groups (build/test, deploy-only, bindings), each with where it was found, what uses it, and where a local value would come from. Never reads a value or prints one. Also checks the environment variable table in `.catalyst/catalyst.toml` offline. Use when someone asks "what does catalyst.env.json mean", "what env vars does this repo need", "why does the container need this", or before reviewing or writing the declaration.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.14 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# What this repository needs
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Use for "what's happening?", "what machines do we have?", "how much capacity do we have?", "where are we?", "why is that stuck?", "what closed?", "what's next?", "how does this work?", "why did it do that?", "how does it prioritise?" and "what does this setting do, where is it set?" in Catalyst Cloud, or asks for something to be done rather than known. Reads the contract via catalyst CLI. Read-only: route work/decisions, never answer as the human.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.14 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# What's happening
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"inputs": {
|
|
5
5
|
"packages/host-isolation/src/darwin-thoughts-producer.ts": "038fb63d8121fb1665ec31869f45604aedd96dd7d9b15a415d578fea9a0ad507",
|
|
6
6
|
"packages/protocol/src/darwin-thoughts-custody.ts": "3515efc22b0a3cfb512ff1acaf649c6eafb978b894c724fabb01c325aea51075",
|
|
7
|
-
"packages/host-isolation/src/darwin-thoughts-custody.ts": "
|
|
7
|
+
"packages/host-isolation/src/darwin-thoughts-custody.ts": "8165e058f6bed990fe35ce30685db0f4634746c3b22d51f81a9c77cd9745735f",
|
|
8
8
|
"deploy/self-host/darwin-thoughts-custody/producer-entry.ts": "27c8ff591c8dc4d1a4c65dd154157df5191ead1fb0af673bfd7099d5226d37bc",
|
|
9
9
|
"deploy/self-host/darwin-thoughts-custody/verifier-entry.ts": "80bc1e72943e43bc33d76ba205bd79751f58ca34d622e35f7e50e3b661f65464",
|
|
10
10
|
"deploy/self-host/darwin-thoughts-custody/generate.ts": "bfe1bef175fa8243be0558e1ddee6ddbcfbd622922e3a5df850e8621f18e7b26",
|
|
@@ -13,6 +13,6 @@
|
|
|
13
13
|
"deploy/self-host/darwin-thoughts-custody/service.plist.in": "66e9a2c175d2500b1f7130e7c04a5ed07f22bc7425661777f514fa3f2a816496"
|
|
14
14
|
},
|
|
15
15
|
"artifactSha256": "0e0f1bbdac23813e8ee57b6eb4ae3dfd24c3f1c51ebe0c3d119a1448734ea05e",
|
|
16
|
-
"verifierSha256": "
|
|
16
|
+
"verifierSha256": "322b838917b9f7e1d9b7d9705a75143697ca093553bf0dcca7e7f5d4a88bbdc0",
|
|
17
17
|
"verifierDeclarationsSha256": "624428ced2eb18b045b761b49e20b7f52c0fc6c3132689571103ece7b4a1083f"
|
|
18
18
|
}
|
|
@@ -514,14 +514,22 @@ function ancestors(paths, allowMissing = false) {
|
|
|
514
514
|
}
|
|
515
515
|
return Array.from(observed, ([path, stat]) => ({ path, stat }));
|
|
516
516
|
}
|
|
517
|
-
function checkAncestors(expected) {
|
|
517
|
+
function checkAncestors(expected, projection) {
|
|
518
518
|
for (const ancestor of expected) {
|
|
519
519
|
const current = fs.lstatSync(ancestor.path, { bigint: true });
|
|
520
520
|
if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryAuthority(current, ancestor.stat)) {
|
|
521
|
+
if (projection?.paths.has(ancestor.path) && current.isDirectory() && !current.isSymbolicLink() && isBootstrapGuestOwnerProjection(ancestor.stat, current, projection.staged)) {
|
|
522
|
+
projection.authenticateIfPresent();
|
|
523
|
+
ancestor.stat = current;
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
521
526
|
throw failure("ancestor_identity");
|
|
522
527
|
}
|
|
523
528
|
}
|
|
524
529
|
}
|
|
530
|
+
function isBootstrapGuestOwnerProjection(before, current, stagedFile) {
|
|
531
|
+
return isInitialGuestOwnerProjection(before, current, stagedFile) || before.dev === current.dev && before.ino === current.ino && before.mode === current.mode && before.uid === 10001n && before.gid === 10001n && current.uid === 0n && current.gid === 0n && process.getuid?.() === 10001 && process.geteuid?.() === 10001 && process.getgid?.() === 10001 && stagedFile.uid === 10001n && stagedFile.gid === 10001n;
|
|
532
|
+
}
|
|
525
533
|
function isInitialGuestOwnerProjection(before, current, stagedFile) {
|
|
526
534
|
return before.dev === current.dev && before.ino === current.ino && before.mode === current.mode && before.uid === 0n && before.gid === 0n && current.uid === 10001n && current.gid === 10001n && process.getuid?.() === 10001 && process.geteuid?.() === 10001 && process.getgid?.() === 10001 && stagedFile.uid === 10001n && stagedFile.gid === 10001n;
|
|
527
535
|
}
|
|
@@ -538,7 +546,7 @@ function receiveStagingChannel(path, before, stagedFile, observedAncestors) {
|
|
|
538
546
|
channel.stat = current;
|
|
539
547
|
return current;
|
|
540
548
|
}
|
|
541
|
-
function receiveWaitingResponseChannel(path, before, stagedFile, observedAncestors, responsePath, bootstrapRoots) {
|
|
549
|
+
function receiveWaitingResponseChannel(path, before, stagedFile, observedAncestors, responsePath, bootstrapRoots, authenticatePresentResponse, bootstrapProjection) {
|
|
542
550
|
const maxPasses = bootstrapRoots ? 4 : 2;
|
|
543
551
|
for (let pass = 0;pass < maxPasses; pass += 1) {
|
|
544
552
|
const current = directory(path, true);
|
|
@@ -555,8 +563,9 @@ function receiveWaitingResponseChannel(path, before, stagedFile, observedAncesto
|
|
|
555
563
|
});
|
|
556
564
|
}
|
|
557
565
|
const changed = projections.filter((projection) => !sameDirectoryAuthority(projection.before, projection.current));
|
|
566
|
+
const receiveProjection = bootstrapRoots ? isBootstrapGuestOwnerProjection : isInitialGuestOwnerProjection;
|
|
558
567
|
for (const projection of changed) {
|
|
559
|
-
if (!
|
|
568
|
+
if (!receiveProjection(projection.before, projection.current, stagedFile))
|
|
560
569
|
throw failure("ancestor_identity");
|
|
561
570
|
const captured = observedAncestors.find((ancestor) => ancestor.path === projection.path);
|
|
562
571
|
if (!captured || !sameDirectoryAuthority(captured.stat, projection.before))
|
|
@@ -565,6 +574,10 @@ function receiveWaitingResponseChannel(path, before, stagedFile, observedAncesto
|
|
|
565
574
|
const requireAbsentResponse = () => {
|
|
566
575
|
try {
|
|
567
576
|
fs.lstatSync(responsePath);
|
|
577
|
+
if (bootstrapRoots && authenticatePresentResponse) {
|
|
578
|
+
authenticatePresentResponse();
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
568
581
|
throw failure("ancestor_identity");
|
|
569
582
|
} catch (error) {
|
|
570
583
|
if (!isMissing(error))
|
|
@@ -573,13 +586,13 @@ function receiveWaitingResponseChannel(path, before, stagedFile, observedAncesto
|
|
|
573
586
|
};
|
|
574
587
|
if (changed.length > 0)
|
|
575
588
|
requireAbsentResponse();
|
|
576
|
-
checkAncestors(observedAncestors.filter((ancestor) => !projections.some((projection) => projection.path === ancestor.path)));
|
|
589
|
+
checkAncestors(observedAncestors.filter((ancestor) => !projections.some((projection) => projection.path === ancestor.path)), bootstrapProjection);
|
|
577
590
|
let resample = false;
|
|
578
591
|
for (const projection of projections) {
|
|
579
592
|
const next = directory(projection.path, projection.privateChannel);
|
|
580
593
|
if (sameDirectoryAuthority(projection.current, next))
|
|
581
594
|
continue;
|
|
582
|
-
if (!
|
|
595
|
+
if (!receiveProjection(projection.current, next, stagedFile))
|
|
583
596
|
throw failure("ancestor_identity");
|
|
584
597
|
resample = true;
|
|
585
598
|
}
|
|
@@ -709,6 +722,54 @@ async function exchange(options, bootstrap) {
|
|
|
709
722
|
const stagingPath = join(options.requestDir, `.${request.nonce}.tmp`);
|
|
710
723
|
const responsePath = join(options.responseDir, `${request.nonce}.json`);
|
|
711
724
|
let owned;
|
|
725
|
+
const projectedPaths = new Set;
|
|
726
|
+
for (const [root, leaf] of creationPaths) {
|
|
727
|
+
if (!root || !leaf)
|
|
728
|
+
throw failure("bootstrap_path");
|
|
729
|
+
let path = root;
|
|
730
|
+
projectedPaths.add(path);
|
|
731
|
+
for (const component of leaf.slice(root.length + 1).split("/")) {
|
|
732
|
+
path = join(path, component);
|
|
733
|
+
projectedPaths.add(path);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
const bootstrapResponseOwner = () => {
|
|
737
|
+
const stat = fs.lstatSync(responsePath, { bigint: true });
|
|
738
|
+
if (!(stat.uid === 0n && stat.gid === 0n || stat.uid === 10001n && stat.gid === 10001n) || process.getuid?.() !== 10001 || process.geteuid?.() !== 10001 || process.getgid?.() !== 10001 || owned?.uid !== 10001n || owned.gid !== 10001n)
|
|
739
|
+
throw failure("file_owner");
|
|
740
|
+
return { uid: Number(stat.uid), gid: Number(stat.gid) };
|
|
741
|
+
};
|
|
742
|
+
const authenticatePresentResponse = () => {
|
|
743
|
+
const bytes = readDarwinThoughtsCustodyFile(responsePath, bootstrapResponseOwner());
|
|
744
|
+
const expectation = {
|
|
745
|
+
authority,
|
|
746
|
+
request,
|
|
747
|
+
nowMs: now(),
|
|
748
|
+
engineCheckout: currentIdentity(options.checkoutPath, targets.checkoutSource),
|
|
749
|
+
engineLock: currentIdentity(options.lockPath, targets.lockSource)
|
|
750
|
+
};
|
|
751
|
+
if (gitPresent(gitPath))
|
|
752
|
+
expectation.engineGit = currentIdentity(gitPath, join(targets.checkoutSource, ".git"));
|
|
753
|
+
verifyDarwinThoughtsCustodyResponse(bytes, expectation);
|
|
754
|
+
};
|
|
755
|
+
const projection = () => {
|
|
756
|
+
if (!bootstrap || !owned)
|
|
757
|
+
return;
|
|
758
|
+
return {
|
|
759
|
+
paths: projectedPaths,
|
|
760
|
+
staged: owned,
|
|
761
|
+
authenticateIfPresent() {
|
|
762
|
+
try {
|
|
763
|
+
fs.lstatSync(responsePath);
|
|
764
|
+
} catch (error) {
|
|
765
|
+
if (isMissing(error))
|
|
766
|
+
return;
|
|
767
|
+
throw error;
|
|
768
|
+
}
|
|
769
|
+
authenticatePresentResponse();
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
};
|
|
712
773
|
let published = false;
|
|
713
774
|
try {
|
|
714
775
|
const fd = guarded(() => fs.openSync(stagingPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK, 384));
|
|
@@ -745,8 +806,14 @@ async function exchange(options, bootstrap) {
|
|
|
745
806
|
if (!Number.isSafeInteger(nowMs) || nowMs < previousNow || nowMs >= options.deadlineMs)
|
|
746
807
|
throw failure("deadline");
|
|
747
808
|
previousNow = nowMs;
|
|
748
|
-
responses = receiveWaitingResponseChannel(options.responseDir, responses, owned, aliasAncestors, responsePath, bootstrap ? authority : undefined);
|
|
749
|
-
checkAncestors(aliasAncestors);
|
|
809
|
+
responses = receiveWaitingResponseChannel(options.responseDir, responses, owned, aliasAncestors, responsePath, bootstrap ? authority : undefined, bootstrap ? authenticatePresentResponse : undefined, projection());
|
|
810
|
+
checkAncestors(aliasAncestors, projection());
|
|
811
|
+
if (bootstrap) {
|
|
812
|
+
const receivedRequest = aliasAncestors.find((ancestor) => ancestor.path === options.requestDir);
|
|
813
|
+
if (!receivedRequest)
|
|
814
|
+
throw failure("ancestor_identity");
|
|
815
|
+
requests = receivedRequest.stat;
|
|
816
|
+
}
|
|
750
817
|
for (const [path, expected] of [
|
|
751
818
|
[options.requestDir, requests],
|
|
752
819
|
[options.responseDir, responses]
|
|
@@ -769,10 +836,7 @@ async function exchange(options, bootstrap) {
|
|
|
769
836
|
const receivedAncestors = bootstrap ? guarded(() => ancestors(creationPaths.map((paths) => paths[1]))) : [];
|
|
770
837
|
const receivedGitPresent = options.gitPath !== undefined || gitPresent(gitPath);
|
|
771
838
|
const receivedGitAncestors = receivedGitPresent ? guarded(() => ancestors([gitPath])) : [];
|
|
772
|
-
const bytes = readDarwinThoughtsCustodyFile(responsePath, {
|
|
773
|
-
uid: Number(responses.uid),
|
|
774
|
-
gid: Number(responses.gid)
|
|
775
|
-
});
|
|
839
|
+
const bytes = readDarwinThoughtsCustodyFile(responsePath, bootstrap && process.getuid?.() === 10001 && process.geteuid?.() === 10001 && process.getgid?.() === 10001 && owned.uid === 10001n && owned.gid === 10001n ? bootstrapResponseOwner() : { uid: Number(responses.uid), gid: Number(responses.gid) });
|
|
776
840
|
const expectation = {
|
|
777
841
|
authority,
|
|
778
842
|
request,
|
|
@@ -782,16 +846,16 @@ async function exchange(options, bootstrap) {
|
|
|
782
846
|
};
|
|
783
847
|
if (receivedGitPresent)
|
|
784
848
|
expectation.engineGit = currentIdentity(gitPath, join(targets.checkoutSource, ".git"));
|
|
785
|
-
checkAncestors(aliasAncestors);
|
|
786
|
-
checkAncestors(receivedGitAncestors);
|
|
849
|
+
checkAncestors(aliasAncestors, projection());
|
|
850
|
+
checkAncestors(receivedGitAncestors, projection());
|
|
787
851
|
expectation.nowMs = now();
|
|
788
852
|
if (!Number.isSafeInteger(expectation.nowMs) || expectation.nowMs < previousNow || expectation.nowMs >= options.deadlineMs)
|
|
789
853
|
throw failure("deadline");
|
|
790
854
|
const proof = verifyDarwinThoughtsCustodyResponse(bytes, expectation);
|
|
791
|
-
checkAncestors(receivedGitAncestors);
|
|
855
|
+
checkAncestors(receivedGitAncestors, projection());
|
|
792
856
|
if (bootstrap) {
|
|
793
|
-
checkAncestors(receivedAncestors);
|
|
794
|
-
checkAncestors(aliasAncestors);
|
|
857
|
+
checkAncestors(receivedAncestors, projection());
|
|
858
|
+
checkAncestors(aliasAncestors, projection());
|
|
795
859
|
checkBootstrapDirectories(creationPaths, false);
|
|
796
860
|
}
|
|
797
861
|
const finalNow = now();
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"repository": "coalesce-labs/catalyst-cloud",
|
|
3
|
-
"commit": "
|
|
3
|
+
"commit": "ee729b12ea821310c48de7bbc753a59d60cc45d6",
|
|
4
4
|
"sha256": {
|
|
5
5
|
"deploy/self-host/compose.yaml": "0ffb7671c7ea49d8490d6e97093ada0afcb31da3b00092a8768150b090cca0bc",
|
|
6
6
|
"deploy/self-host/compose.darwin-thoughts.yaml": "6269d66d6f487175ab8560d0648e65c8a0eccb86f69a8dc24daf41d8ae1dcf02",
|
|
7
7
|
"deploy/self-host/darwin-thoughts-custody/producer.mjs": "0e0f1bbdac23813e8ee57b6eb4ae3dfd24c3f1c51ebe0c3d119a1448734ea05e",
|
|
8
|
-
"deploy/self-host/darwin-thoughts-custody/verifier.mjs": "
|
|
8
|
+
"deploy/self-host/darwin-thoughts-custody/verifier.mjs": "322b838917b9f7e1d9b7d9705a75143697ca093553bf0dcca7e7f5d4a88bbdc0",
|
|
9
9
|
"deploy/self-host/darwin-thoughts-custody/verifier.d.mts": "624428ced2eb18b045b761b49e20b7f52c0fc6c3132689571103ece7b4a1083f",
|
|
10
10
|
"deploy/self-host/darwin-thoughts-custody/watchdog.mjs": "6b5c7204aacd296c85a216d4f1b3adbe2084024fe37200ec1d24dfc2dec2e12d",
|
|
11
11
|
"deploy/self-host/darwin-thoughts-custody/watchdog-deadline.mjs": "a642e6d8968f56f635056d3b258e5c4ad186c638a273c26b4528e85b7d2a6346",
|
|
12
12
|
"deploy/self-host/darwin-thoughts-custody/service.plist.in": "66e9a2c175d2500b1f7130e7c04a5ed07f22bc7425661777f514fa3f2a816496",
|
|
13
|
-
"deploy/self-host/darwin-thoughts-custody/manifest.json": "
|
|
13
|
+
"deploy/self-host/darwin-thoughts-custody/manifest.json": "b2525e457dcd82326a71a873df95f6845d8d3231bf2afc20119a6034ddb98852",
|
|
14
14
|
"deploy/self-host/linux-session-egress/session-egress.py": "a20cedfa1e883127ec461e4b7f9588d379fe1633b8244ecd703c81f589d3f142",
|
|
15
15
|
"deploy/self-host/linux-session-egress/catalyst-session-egress.service": "703c150ab23ec87aea47cfc394f63e061b66c4b8c1454a88a9be879c161ce2a5",
|
|
16
16
|
"deploy/self-host/linux-session-egress/catalyst-session-egress-attest.service": "94fa75ea0616f6a1172fc20ca637a5727c36456357e4a977d5bdd65f2fad2e8d",
|