@usine/executor 0.0.1 → 0.1.0
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 +54 -2
- package/dist/child-command.d.ts +18 -0
- package/dist/child-command.d.ts.map +1 -0
- package/dist/child-command.js +47 -0
- package/dist/child-command.js.map +1 -0
- package/dist/exe-dev/channel.d.ts +8 -0
- package/dist/exe-dev/channel.d.ts.map +1 -0
- package/dist/exe-dev/channel.js +45 -0
- package/dist/exe-dev/channel.js.map +1 -0
- package/dist/exe-dev/commands.d.ts +22 -0
- package/dist/exe-dev/commands.d.ts.map +1 -0
- package/dist/exe-dev/commands.js +67 -0
- package/dist/exe-dev/commands.js.map +1 -0
- package/dist/exe-dev/grants.d.ts +9 -0
- package/dist/exe-dev/grants.d.ts.map +1 -0
- package/dist/exe-dev/grants.js +88 -0
- package/dist/exe-dev/grants.js.map +1 -0
- package/dist/exe-dev/index.d.ts +8 -0
- package/dist/exe-dev/index.d.ts.map +1 -0
- package/dist/exe-dev/index.js +239 -0
- package/dist/exe-dev/index.js.map +1 -0
- package/dist/exe-dev/model.d.ts +58 -0
- package/dist/exe-dev/model.d.ts.map +1 -0
- package/dist/exe-dev/model.js +33 -0
- package/dist/exe-dev/model.js.map +1 -0
- package/dist/exe-dev/preflight.d.ts +9 -0
- package/dist/exe-dev/preflight.d.ts.map +1 -0
- package/dist/exe-dev/preflight.js +82 -0
- package/dist/exe-dev/preflight.js.map +1 -0
- package/dist/exe-dev/shell.d.ts +12 -0
- package/dist/exe-dev/shell.d.ts.map +1 -0
- package/dist/exe-dev/shell.js +31 -0
- package/dist/exe-dev/shell.js.map +1 -0
- package/dist/exe-dev/sweep.d.ts +12 -0
- package/dist/exe-dev/sweep.d.ts.map +1 -0
- package/dist/exe-dev/sweep.js +119 -0
- package/dist/exe-dev/sweep.js.map +1 -0
- package/dist/index.d.ts +80 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +81 -7
- package/dist/index.js.map +1 -1
- package/dist/invalid-frame.d.ts +17 -0
- package/dist/invalid-frame.d.ts.map +1 -0
- package/dist/invalid-frame.js +110 -0
- package/dist/invalid-frame.js.map +1 -0
- package/dist/keyed-lock.d.ts +11 -0
- package/dist/keyed-lock.d.ts.map +1 -0
- package/dist/keyed-lock.js +21 -0
- package/dist/keyed-lock.js.map +1 -0
- package/dist/local/index.d.ts +15 -0
- package/dist/local/index.d.ts.map +1 -0
- package/dist/local/index.js +302 -0
- package/dist/local/index.js.map +1 -0
- package/dist/provision.d.ts +33 -0
- package/dist/provision.d.ts.map +1 -0
- package/dist/provision.js +76 -0
- package/dist/provision.js.map +1 -0
- package/dist/run-host-session.d.ts +22 -0
- package/dist/run-host-session.d.ts.map +1 -0
- package/dist/run-host-session.js +228 -0
- package/dist/run-host-session.js.map +1 -0
- package/package.json +19 -3
package/README.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# @usine/executor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Worker execution for [usine](https://github.com/basaltbytes/usine). The platform-free package root defines five operations: preflight, prepare, launch, cancel, and cleanup. It also owns the credential provider, Worker capability errors, and adapter-neutral request types.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Local Worker
|
|
6
|
+
|
|
7
|
+
The Node adapter at `@usine/executor/local` does the following for each Attempt:
|
|
8
|
+
|
|
9
|
+
- checks Git and the installed run-host protocol without changing worker state;
|
|
10
|
+
- updates a per-repository bare cache, then creates a detached worktree at the resolved revision;
|
|
11
|
+
- mints one repository credential and passes it only through the child process environment;
|
|
12
|
+
- exchanges protocol-v3 envelopes with `@usine/run-host` over newline-delimited standard input and output, including immutable payload and agent context plus normalized session events;
|
|
13
|
+
- interrupts cooperatively before a hard kill, then removes the worktree and wipes the credential.
|
|
14
|
+
|
|
15
|
+
`LocalExecutorOptions.runHost` can override the child command for another compatible host. The adapter otherwise launches the executable shipped by `@usine/run-host` and rejects a protocol-version mismatch during preflight.
|
|
16
|
+
|
|
17
|
+
The local layer requires Effect `FileSystem`, `Path`, and `ChildProcessSpawner` services plus a `CredentialProvider`. Everything is 0.x, so APIs may change. Supply `effect` at the version pinned by the usine workspace.
|
|
18
|
+
|
|
19
|
+
## exe.dev Worker
|
|
20
|
+
|
|
21
|
+
`@usine/executor/exe-dev` runs the same contract on a persistent exe.dev VM. The configured Worker name must match the VM name. The VM image must already contain Git, Node.js 22 or later, the compatible `usine-run-host` executable, and every coding-agent login used by the instance.
|
|
22
|
+
|
|
23
|
+
Preflight changes no remote state. It reads VM metadata through `ssh exe.dev ls --json`, checks Git and Node.js over SSH, compares the run-host protocol version, runs the configured harness login checks, and asks exe.dev to test each native GitHub integration.
|
|
24
|
+
|
|
25
|
+
Prepare attaches the repository's integration to the VM with a bounded `--for` grant. It then fetches a bare cached clone from `github.int.exe.xyz` and creates a detached worktree for the Attempt. Launch keeps one SSH process open and carries protocol-v3 frames on that process's standard input and output. Cleanup removes the worktree, prunes its cache registration, and detaches the integration after the last concurrent Attempt using it finishes.
|
|
26
|
+
|
|
27
|
+
The first prepare on each VM after orchestrator startup sweeps abandoned Attempt worktrees and prunes cached worktree registrations left by a crash. Each sweep removal is isolated and logged: an immovable leftover never blocks its siblings or later prepares, and the sweep runs once per process regardless of partial failures. One-shot lobby and SSH commands carry a ten-minute timeout, and the SSH client sends keepalives so a dead link fails the operation instead of parking it. Cancellation also bounds interrupt persistence and delivery before escalating to a hard kill, so a parked store or remote process cannot stall shutdown; before removing a workspace, cleanup reads the run host's recorded pid on the VM and confirms the process is dead, because killing the local SSH client alone says nothing about the remote side.
|
|
28
|
+
|
|
29
|
+
Concurrent Attempts against different repositories leave their integrations attached to the VM simultaneously; each grant stays time-boxed, and the refcounted release detaches an integration only after its last Attempt finishes. A failed detach is logged and left to lapse with its time-box.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { Repository } from "@usine/core"
|
|
33
|
+
import {
|
|
34
|
+
GitHubIntegration,
|
|
35
|
+
HarnessLoginCheck,
|
|
36
|
+
IntegrationName,
|
|
37
|
+
layer,
|
|
38
|
+
} from "@usine/executor/exe-dev"
|
|
39
|
+
|
|
40
|
+
const executorLayer = layer({
|
|
41
|
+
githubIntegrations: [
|
|
42
|
+
new GitHubIntegration({
|
|
43
|
+
repository: Repository.Name.make("acme/widget"),
|
|
44
|
+
name: IntegrationName.make("usine-acme-widget"),
|
|
45
|
+
}),
|
|
46
|
+
],
|
|
47
|
+
harnessLoginChecks: [
|
|
48
|
+
new HarnessLoginCheck({
|
|
49
|
+
harness: "codex",
|
|
50
|
+
command: "codex",
|
|
51
|
+
args: ["login", "status"],
|
|
52
|
+
}),
|
|
53
|
+
],
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The native integration holds the GitHub credential at exe.dev's network edge. No GitHub token enters the RunSpec, the Attempt channel, the local SSH environment, or the VM. Git pushes from this Worker use `exe-dev-github-integration[bot]`; agent-authored PRs and comments should carry the Agent signature used by the run.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { ChildProcess, type ChildProcessSpawner } from "effect/unstable/process";
|
|
3
|
+
import { ExecutorOperationError, type ExecutorOperation } from "./index.ts";
|
|
4
|
+
export interface CommandResult {
|
|
5
|
+
readonly exitCode: number;
|
|
6
|
+
readonly stderr: string;
|
|
7
|
+
readonly stdout: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The named variables are the only ones a child may inherit; everything else
|
|
11
|
+
* in the orchestrator environment — the App private key above all — stays out.
|
|
12
|
+
*/
|
|
13
|
+
export declare const allowlistedEnvironment: (names: ReadonlyArray<string>) => Effect.Effect<Record<string, string>>;
|
|
14
|
+
/** One bounded child process, output collected whole; the exit code is the caller's. */
|
|
15
|
+
export declare const collectCommand: (spawner: ChildProcessSpawner.ChildProcessSpawner["Service"], operation: ExecutorOperation, command: string, args: ReadonlyArray<string>, options: ChildProcess.CommandOptions) => Effect.Effect<CommandResult, ExecutorOperationError>;
|
|
16
|
+
/** Turns a collected result into stdout, logging and failing on a non-zero exit. */
|
|
17
|
+
export declare const requireSuccess: (operation: "cancel" | "cleanup" | "launch" | "preflight" | "prepare", host: string, command: string, args: readonly string[], result: CommandResult) => Effect.Effect<string, ExecutorOperationError, never>;
|
|
18
|
+
//# sourceMappingURL=child-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"child-command.d.ts","sourceRoot":"","sources":["../src/child-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,MAAM,EAAyB,MAAM,QAAQ,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACjF,OAAO,EAAE,sBAAsB,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE5E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAKD;;;GAGG;AACH,eAAO,MAAM,sBAAsB,UAC1B,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASpC,CAAC;AAEJ,wFAAwF;AACxF,eAAO,MAAM,cAAc,YAChB,mBAAmB,CAAC,mBAAmB,CAAC,SAAS,CAAC,aAChD,iBAAiB,WACnB,MAAM,QACT,aAAa,CAAC,MAAM,CAAC,WAClB,YAAY,CAAC,cAAc,KACnC,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,sBAAsB,CA+BnD,CAAC;AAEJ,oFAAoF;AACpF,eAAO,MAAM,cAAc,+MAsBzB,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Array as Arr, Config, Effect, Fiber, Option, Stream } from "effect";
|
|
2
|
+
import { ChildProcess } from "effect/unstable/process";
|
|
3
|
+
import { ExecutorOperationError } from "./index.js";
|
|
4
|
+
/** Every adapter command is bounded: a hung clone or SSH hop must stay cancellable. */
|
|
5
|
+
const COMMAND_TIMEOUT = "10 minutes";
|
|
6
|
+
/**
|
|
7
|
+
* The named variables are the only ones a child may inherit; everything else
|
|
8
|
+
* in the orchestrator environment — the App private key above all — stays out.
|
|
9
|
+
*/
|
|
10
|
+
export const allowlistedEnvironment = (names) => Effect.forEach(names, (name) => Effect.map(Config.option(Config.string(name)), (value) => Option.map(value, (found) => [name, found]))).pipe(Effect.map((entries) => Object.fromEntries(Arr.getSomes(entries))),
|
|
11
|
+
// An unreadable process environment at layer build is startup breakage.
|
|
12
|
+
Effect.orDie);
|
|
13
|
+
/** One bounded child process, output collected whole; the exit code is the caller's. */
|
|
14
|
+
export const collectCommand = (spawner, operation, command, args, options) => Effect.scoped(Effect.gen(function* () {
|
|
15
|
+
const handle = yield* spawner.spawn(ChildProcess.make(command, args, options));
|
|
16
|
+
const stdoutFiber = yield* handle.stdout.pipe(Stream.decodeText(), Stream.mkString, Effect.forkChild);
|
|
17
|
+
const stderrFiber = yield* handle.stderr.pipe(Stream.decodeText(), Stream.mkString, Effect.forkChild);
|
|
18
|
+
const exitCode = yield* handle.exitCode;
|
|
19
|
+
const [stdout, stderr] = yield* Effect.all([
|
|
20
|
+
Fiber.join(stdoutFiber),
|
|
21
|
+
Fiber.join(stderrFiber),
|
|
22
|
+
]);
|
|
23
|
+
return { exitCode: Number(exitCode), stderr, stdout };
|
|
24
|
+
})).pipe(Effect.timeout(COMMAND_TIMEOUT), Effect.mapError((cause) => new ExecutorOperationError({
|
|
25
|
+
operation,
|
|
26
|
+
message: `${command} could not be executed`,
|
|
27
|
+
cause,
|
|
28
|
+
})));
|
|
29
|
+
/** Turns a collected result into stdout, logging and failing on a non-zero exit. */
|
|
30
|
+
export const requireSuccess = Effect.fn("Executor.requireSuccess")(function* (operation, host, command, args, result) {
|
|
31
|
+
if (result.exitCode !== 0) {
|
|
32
|
+
yield* Effect.logWarning("Executor command exited non-zero", {
|
|
33
|
+
operation,
|
|
34
|
+
host,
|
|
35
|
+
command,
|
|
36
|
+
args: args.join(" "),
|
|
37
|
+
exitCode: String(result.exitCode),
|
|
38
|
+
});
|
|
39
|
+
return yield* new ExecutorOperationError({
|
|
40
|
+
operation,
|
|
41
|
+
message: `${command} exited with code ${String(result.exitCode)}`,
|
|
42
|
+
cause: new Error(result.stderr.trim() || `${command} exited without an error message`),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return result.stdout;
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=child-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"child-command.js","sourceRoot":"","sources":["../src/child-command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { ChildProcessSpawner } from "effect/unstable/process";
|
|
3
|
+
import type { RunHostCommand } from "../index.ts";
|
|
4
|
+
import { type RunHostSession } from "../run-host-session.ts";
|
|
5
|
+
import type { ExeDevCommands } from "./commands.ts";
|
|
6
|
+
import type { PreparedStates } from "./model.ts";
|
|
7
|
+
export declare const attemptChannelFor: (commands: ExeDevCommands, preparedStates: PreparedStates, runHost: RunHostCommand, interruptGrace: Parameters<typeof Effect.timeoutOption>[1]) => Effect.Effect<RunHostSession, never, ChildProcessSpawner.ChildProcessSpawner>;
|
|
8
|
+
//# sourceMappingURL=channel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/exe-dev/channel.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAe,MAAM,QAAQ,CAAC;AAC7C,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACnG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD,eAAO,MAAM,iBAAiB,EAAE,CAC9B,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,cAAc,EAC9B,OAAO,EAAE,cAAc,EACvB,cAAc,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,KACvD,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,KAAK,EAAE,mBAAmB,CAAC,mBAAmB,CAwD/E,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { AttemptFailure } from "@usine/core";
|
|
2
|
+
import { Effect, Ref, Stream } from "effect";
|
|
3
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
4
|
+
import { runHostSessionFor } from "../run-host-session.js";
|
|
5
|
+
import { runHostShellCommand } from "./shell.js";
|
|
6
|
+
export const attemptChannelFor = Effect.fn("ExeDevExecutor.attemptChannelFor")(function* (commands, preparedStates, runHost, interruptGrace) {
|
|
7
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
8
|
+
const spawn = Effect.fn("ExeDevExecutor.spawnRunHost")(function* (prepared, input) {
|
|
9
|
+
const attemptId = prepared.spec.attemptId;
|
|
10
|
+
const state = (yield* Ref.get(preparedStates)).get(attemptId);
|
|
11
|
+
if (state === undefined) {
|
|
12
|
+
return yield* new AttemptFailure.InfraFailure({
|
|
13
|
+
message: `Attempt ${attemptId} has no prepared exe.dev workspace`,
|
|
14
|
+
cause: new Error("prepared Attempt state missing"),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
yield* Effect.annotateCurrentSpan({
|
|
18
|
+
attemptId,
|
|
19
|
+
worker: prepared.worker,
|
|
20
|
+
host: state.vm.sshDest,
|
|
21
|
+
});
|
|
22
|
+
yield* Effect.logInfo("Launching the run host over SSH", {
|
|
23
|
+
attemptId,
|
|
24
|
+
worker: prepared.worker,
|
|
25
|
+
host: state.vm.sshDest,
|
|
26
|
+
worktree: prepared.worktree,
|
|
27
|
+
});
|
|
28
|
+
const handle = yield* spawner.spawn(ChildProcess.make(commands.ssh.command, [
|
|
29
|
+
...commands.ssh.args,
|
|
30
|
+
state.vm.sshDest,
|
|
31
|
+
"--",
|
|
32
|
+
runHostShellCommand(prepared.worktree, runHost.command, runHost.args),
|
|
33
|
+
], {
|
|
34
|
+
env: commands.environment,
|
|
35
|
+
extendEnv: false,
|
|
36
|
+
stdin: { stream: Stream.fromQueue(input), endOnDone: true },
|
|
37
|
+
}));
|
|
38
|
+
// Identity scrub is safe here because this process never holds a secret
|
|
39
|
+
// the run host could echo: credentials are edge-injected on the VM by the
|
|
40
|
+
// exe.dev integration. Revisit if any credential ever crosses this seam.
|
|
41
|
+
return { handle, host: state.vm.sshDest, scrub: (text) => text };
|
|
42
|
+
});
|
|
43
|
+
return yield* runHostSessionFor({ interruptGrace, spawn });
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../../src/exe-dev/channel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAE5E,OAAO,EAAE,iBAAiB,EAA0C,MAAM,wBAAwB,CAAC;AAGnG,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,CAAC,MAAM,iBAAiB,GAKuD,MAAM,CAAC,EAAE,CAC5F,kCAAkC,CACnC,CAAC,QAAQ,CAAC,EACT,QAAwB,EACxB,cAA8B,EAC9B,OAAuB,EACvB,cAA0D;IAE1D,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,mBAAmB,CAAC,mBAAmB,CAAC;IAE/D,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,6BAA6B,CAAC,CAAC,QAAQ,CAAC,EAC9D,QAAyB,EACzB,KAAmB;QAEnB,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC;QAC1C,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC9D,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,KAAK,CAAC,CAAC,IAAI,cAAc,CAAC,YAAY,CAAC;gBAC5C,OAAO,EAAE,WAAW,SAAS,oCAAoC;gBACjE,KAAK,EAAE,IAAI,KAAK,CAAC,gCAAgC,CAAC;aACnD,CAAC,CAAC;QACL,CAAC;QACD,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC;YAChC,SAAS;YACT,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,OAAO;SACvB,CAAC,CAAC;QACH,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,iCAAiC,EAAE;YACvD,SAAS;YACT,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,OAAO;YACtB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;SAC5B,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CACjC,YAAY,CAAC,IAAI,CACf,QAAQ,CAAC,GAAG,CAAC,OAAO,EACpB;YACE,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI;YACpB,KAAK,CAAC,EAAE,CAAC,OAAO;YAChB,IAAI;YACJ,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC;SACtE,EACD;YACE,GAAG,EAAE,QAAQ,CAAC,WAAW;YACzB,SAAS,EAAE,KAAK;YAChB,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE;SAC5D,CACF,CACF,CAAC;QACF,wEAAwE;QACxE,0EAA0E;QAC1E,yEAAyE;QACzE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,EAAE,CAAC;IACnF,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC,CAAC,iBAAiB,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;AAC7D,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { ChildProcessSpawner } from "effect/unstable/process";
|
|
3
|
+
import { type CommandResult } from "../child-command.ts";
|
|
4
|
+
import type { ExecutorOperation, ExecutorOperationError, RunHostCommand } from "../index.ts";
|
|
5
|
+
import type { SshDestination } from "./model.ts";
|
|
6
|
+
export interface ExeDevCommands {
|
|
7
|
+
readonly environment: Record<string, string>;
|
|
8
|
+
readonly ssh: RunHostCommand;
|
|
9
|
+
readonly lobby: (operation: ExecutorOperation, args: ReadonlyArray<string>) => Effect.Effect<string, ExecutorOperationError>;
|
|
10
|
+
readonly lobbyJson: (operation: ExecutorOperation, args: ReadonlyArray<string>) => Effect.Effect<string, ExecutorOperationError>;
|
|
11
|
+
readonly remoteRaw: (operation: ExecutorOperation, sshDest: SshDestination, command: string, args: ReadonlyArray<string>) => Effect.Effect<CommandResult, ExecutorOperationError>;
|
|
12
|
+
readonly remote: (operation: ExecutorOperation, sshDest: SshDestination, command: string, args: ReadonlyArray<string>) => Effect.Effect<string, ExecutorOperationError>;
|
|
13
|
+
}
|
|
14
|
+
export declare const commandsFor: (configuredSsh?: RunHostCommand | undefined) => Effect.Effect<{
|
|
15
|
+
environment: Record<string, string>;
|
|
16
|
+
lobby: (operation: ExecutorOperation, args: ReadonlyArray<string>) => Effect.Effect<string, ExecutorOperationError>;
|
|
17
|
+
lobbyJson: (operation: ExecutorOperation, args: ReadonlyArray<string>) => Effect.Effect<string, ExecutorOperationError>;
|
|
18
|
+
remote: (operation: ExecutorOperation, sshDest: SshDestination, command: string, args: ReadonlyArray<string>) => Effect.Effect<string, ExecutorOperationError>;
|
|
19
|
+
remoteRaw: (operation: ExecutorOperation, sshDest: SshDestination, command: string, args: ReadonlyArray<string>) => Effect.Effect<CommandResult, ExecutorOperationError>;
|
|
20
|
+
ssh: RunHostCommand;
|
|
21
|
+
}, never, ChildProcessSpawner.ChildProcessSpawner>;
|
|
22
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/exe-dev/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,CACd,SAAS,EAAE,iBAAiB,EAC5B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,KACxB,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IACnD,QAAQ,CAAC,SAAS,EAAE,CAClB,SAAS,EAAE,iBAAiB,EAC5B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,KACxB,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IACnD,QAAQ,CAAC,SAAS,EAAE,CAClB,SAAS,EAAE,iBAAiB,EAC5B,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,KACxB,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,sBAAsB,CAAC,CAAC;IAC1D,QAAQ,CAAC,MAAM,EAAE,CACf,SAAS,EAAE,iBAAiB,EAC5B,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,KACxB,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;CACpD;AAmBD,eAAO,MAAM,WAAW;;uBAuCT,iBAAiB,QACtB,aAAa,CAAC,MAAM,CAAC,KAC1B,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC;2BAInC,iBAAiB,QACtB,aAAa,CAAC,MAAM,CAAC,KAC1B,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC;wBAgBnC,iBAAiB,WACnB,cAAc,WACd,MAAM,QACT,aAAa,CAAC,MAAM,CAAC,KAC1B,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC;2BAjBnC,iBAAiB,WACnB,cAAc,WACd,MAAM,QACT,aAAa,CAAC,MAAM,CAAC,KAC1B,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,sBAAsB,CAAC;;kDA6BvD,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { ChildProcessSpawner } from "effect/unstable/process";
|
|
3
|
+
import { allowlistedEnvironment, collectCommand, requireSuccess, } from "../child-command.js";
|
|
4
|
+
import { shellCommand } from "./shell.js";
|
|
5
|
+
/**
|
|
6
|
+
* The local SSH process receives only what SSH itself needs. In particular,
|
|
7
|
+
* GitHub and factory credentials from the orchestrator environment never
|
|
8
|
+
* cross into either the SSH client or the VM.
|
|
9
|
+
*/
|
|
10
|
+
const SSH_ENVIRONMENT_ALLOWLIST = [
|
|
11
|
+
"HOME",
|
|
12
|
+
"LANG",
|
|
13
|
+
"LC_ALL",
|
|
14
|
+
"PATH",
|
|
15
|
+
"SHELL",
|
|
16
|
+
"SSH_AGENT_PID",
|
|
17
|
+
"SSH_AUTH_SOCK",
|
|
18
|
+
"TMPDIR",
|
|
19
|
+
"USER",
|
|
20
|
+
];
|
|
21
|
+
export const commandsFor = Effect.fn("ExeDevExecutor.commandsFor")(function* (configuredSsh) {
|
|
22
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
23
|
+
const environment = yield* allowlistedEnvironment(SSH_ENVIRONMENT_ALLOWLIST);
|
|
24
|
+
const ssh = configuredSsh ?? {
|
|
25
|
+
command: "ssh",
|
|
26
|
+
// The keepalive pair turns a dead TCP link into a non-zero SSH exit
|
|
27
|
+
// instead of a silent forever-park: a lost VM must fail the operation.
|
|
28
|
+
args: ["-o", "BatchMode=yes", "-o", "ServerAliveInterval=30", "-o", "ServerAliveCountMax=4"],
|
|
29
|
+
};
|
|
30
|
+
const executeRaw = Effect.fn("ExeDevExecutor.executeRaw")(function* (operation, host, command, args) {
|
|
31
|
+
yield* Effect.annotateCurrentSpan({ host, operation });
|
|
32
|
+
return yield* collectCommand(spawner, operation, command, args, {
|
|
33
|
+
env: environment,
|
|
34
|
+
extendEnv: false,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
const executeChecked = Effect.fn("ExeDevExecutor.executeChecked")(function* (operation, host, command, args) {
|
|
38
|
+
const result = yield* executeRaw(operation, host, command, args);
|
|
39
|
+
return yield* requireSuccess(operation, host, command, args, result);
|
|
40
|
+
});
|
|
41
|
+
// Lobby arguments ride unquoted through OpenSSH's argv join. Every value
|
|
42
|
+
// passed here must be a brand-validated token (Worker.Name,
|
|
43
|
+
// IntegrationName, CredentialGrantMinutes); never pass free-form text.
|
|
44
|
+
const lobby = (operation, args) => executeChecked(operation, "exe.dev", ssh.command, [...ssh.args, "exe.dev", ...args]);
|
|
45
|
+
const lobbyJson = (operation, args) => lobby(operation, [...args, "--json"]);
|
|
46
|
+
const remoteRaw = (operation, sshDest, command, args) => executeRaw(operation, sshDest, ssh.command, [
|
|
47
|
+
...ssh.args,
|
|
48
|
+
sshDest,
|
|
49
|
+
"--",
|
|
50
|
+
shellCommand(command, args),
|
|
51
|
+
]);
|
|
52
|
+
const remote = (operation, sshDest, command, args) => executeChecked(operation, sshDest, ssh.command, [
|
|
53
|
+
...ssh.args,
|
|
54
|
+
sshDest,
|
|
55
|
+
"--",
|
|
56
|
+
shellCommand(command, args),
|
|
57
|
+
]);
|
|
58
|
+
return {
|
|
59
|
+
environment,
|
|
60
|
+
lobby,
|
|
61
|
+
lobbyJson,
|
|
62
|
+
remote,
|
|
63
|
+
remoteRaw,
|
|
64
|
+
ssh,
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/exe-dev/commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { CredentialUnavailableError } from "../index.ts";
|
|
3
|
+
import type { ExeDevCommands } from "./commands.ts";
|
|
4
|
+
import { type ExeDevExecutorOptions } from "./model.ts";
|
|
5
|
+
export declare const credentialGrantsFor: (options: ExeDevExecutorOptions, commands: ExeDevCommands) => Effect.Effect<{
|
|
6
|
+
acquire: (worker: string & import("effect/Brand").Brand<"WorkerName">, repository: string & import("effect/Brand").Brand<"RepoName">, integration: string & import("effect/Brand").Brand<"ExeDevIntegrationName">) => Effect.Effect<void, CredentialUnavailableError, never>;
|
|
7
|
+
release: (worker: string & import("effect/Brand").Brand<"WorkerName">, integration: string & import("effect/Brand").Brand<"ExeDevIntegrationName">) => Effect.Effect<void, import("../index.ts").ExecutorOperationError, never>;
|
|
8
|
+
}, never, never>;
|
|
9
|
+
//# sourceMappingURL=grants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../../src/exe-dev/grants.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAO,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,YAAY,CAAC;AAKpB,eAAO,MAAM,mBAAmB;;;gBA6G9B,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Repository, Worker } from "@usine/core";
|
|
2
|
+
import { Effect, Ref } from "effect";
|
|
3
|
+
import { CredentialUnavailableError } from "../index.js";
|
|
4
|
+
import { keyedLockFor } from "../keyed-lock.js";
|
|
5
|
+
import { CredentialGrantMinutes, } from "./model.js";
|
|
6
|
+
const grantKey = (worker, integration) => `${worker} ${integration}`;
|
|
7
|
+
export const credentialGrantsFor = Effect.fn("ExeDevExecutor.credentialGrantsFor")(function* (options, commands) {
|
|
8
|
+
const locks = yield* keyedLockFor();
|
|
9
|
+
const counts = yield* Ref.make(new Map());
|
|
10
|
+
const grantMinutes = options.credentialGrantMinutes ?? CredentialGrantMinutes.make(65);
|
|
11
|
+
const acquire = Effect.fn("ExeDevExecutor.acquireGrant")(function* (worker, repository, integration) {
|
|
12
|
+
const key = grantKey(worker, integration);
|
|
13
|
+
yield* locks.withLock(key)(Effect.gen(function* () {
|
|
14
|
+
const current = (yield* Ref.get(counts)).get(key) ?? 0;
|
|
15
|
+
yield* commands
|
|
16
|
+
.lobby("prepare", [
|
|
17
|
+
"integrations",
|
|
18
|
+
"attach",
|
|
19
|
+
integration,
|
|
20
|
+
`vm:${worker}`,
|
|
21
|
+
"--for",
|
|
22
|
+
`${grantMinutes}m`,
|
|
23
|
+
])
|
|
24
|
+
.pipe(Effect.mapError((cause) => new CredentialUnavailableError({
|
|
25
|
+
repository,
|
|
26
|
+
message: `Could not attach exe.dev GitHub integration ${integration}`,
|
|
27
|
+
cause,
|
|
28
|
+
})));
|
|
29
|
+
yield* Effect.logInfo("Attached or refreshed time-boxed exe.dev GitHub integration", {
|
|
30
|
+
worker,
|
|
31
|
+
repository,
|
|
32
|
+
integration,
|
|
33
|
+
grantMinutes,
|
|
34
|
+
activeGrants: current + 1,
|
|
35
|
+
});
|
|
36
|
+
yield* Ref.update(counts, (currentCounts) => {
|
|
37
|
+
const next = new Map(currentCounts);
|
|
38
|
+
next.set(key, current + 1);
|
|
39
|
+
return next;
|
|
40
|
+
});
|
|
41
|
+
}));
|
|
42
|
+
});
|
|
43
|
+
const release = Effect.fn("ExeDevExecutor.releaseGrant")(function* (worker, integration) {
|
|
44
|
+
const key = grantKey(worker, integration);
|
|
45
|
+
yield* locks.withLock(key)(Effect.gen(function* () {
|
|
46
|
+
const current = (yield* Ref.get(counts)).get(key) ?? 0;
|
|
47
|
+
if (current === 0) {
|
|
48
|
+
yield* Effect.logDebug("Credential cleanup was already complete", {
|
|
49
|
+
worker,
|
|
50
|
+
integration,
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (current > 1) {
|
|
55
|
+
yield* Ref.update(counts, (currentCounts) => {
|
|
56
|
+
const next = new Map(currentCounts);
|
|
57
|
+
next.set(key, current - 1);
|
|
58
|
+
return next;
|
|
59
|
+
});
|
|
60
|
+
yield* Effect.logDebug("Kept the shared exe.dev GitHub integration", {
|
|
61
|
+
worker,
|
|
62
|
+
integration,
|
|
63
|
+
activeGrants: current - 1,
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
yield* commands
|
|
68
|
+
.lobby("cleanup", ["integrations", "detach", integration, `vm:${worker}`])
|
|
69
|
+
.pipe(Effect.tapError((error) => Effect.logWarning("Could not detach the exe.dev GitHub integration", {
|
|
70
|
+
worker,
|
|
71
|
+
integration,
|
|
72
|
+
grantMinutes,
|
|
73
|
+
lapse: `the grant stays attached until its ${grantMinutes}m time-box expires`,
|
|
74
|
+
message: error.message,
|
|
75
|
+
})), Effect.ensuring(Ref.update(counts, (currentCounts) => {
|
|
76
|
+
const next = new Map(currentCounts);
|
|
77
|
+
next.delete(key);
|
|
78
|
+
return next;
|
|
79
|
+
})));
|
|
80
|
+
yield* Effect.logInfo("Detached exe.dev GitHub integration", {
|
|
81
|
+
worker,
|
|
82
|
+
integration,
|
|
83
|
+
});
|
|
84
|
+
}));
|
|
85
|
+
});
|
|
86
|
+
return { acquire, release };
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=grants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grants.js","sourceRoot":"","sources":["../../src/exe-dev/grants.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Layer } from "effect";
|
|
2
|
+
import { ChildProcessSpawner } from "effect/unstable/process";
|
|
3
|
+
import { Executor } from "../index.ts";
|
|
4
|
+
import { CredentialGrantMinutes, type ExeDevExecutorOptions, GitHubIntegration, HarnessLoginCheck, IntegrationName } from "./model.ts";
|
|
5
|
+
export { CredentialGrantMinutes, type ExeDevExecutorOptions, GitHubIntegration, HarnessLoginCheck, IntegrationName, };
|
|
6
|
+
/** Node-only exe.dev adapter; the package root remains platform-free. */
|
|
7
|
+
export declare const layer: (options: ExeDevExecutorOptions) => Layer.Layer<Executor, never, ChildProcessSpawner.ChildProcessSpawner>;
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/exe-dev/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAU,KAAK,EAAuB,MAAM,QAAQ,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAGL,QAAQ,EAIT,MAAM,aAAa,CAAC;AAMrB,OAAO,EACL,sBAAsB,EACtB,KAAK,qBAAqB,EAC1B,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EAEhB,MAAM,YAAY,CAAC;AAKpB,OAAO,EACL,sBAAsB,EACtB,KAAK,qBAAqB,EAC1B,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GAChB,CAAC;AAwRF,yEAAyE;AACzE,eAAO,MAAM,KAAK,YACP,qBAAqB,KAC7B,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,mBAAmB,CAAC,mBAAmB,CAClB,CAAC"}
|