Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions deploy/helm/coordinator/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ apiVersion: v2
name: coordinator
description: Read-only ephemeral native-session inspector with explicit legacy storage retention
type: application
version: 0.3.0-beta.9
appVersion: 0.3.0-beta.9
version: 0.3.0-beta.10
appVersion: 0.3.0-beta.10
2 changes: 1 addition & 1 deletion deploy/helm/coordinator/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Coordinator inspector chart 0.3.0-beta.9
# Coordinator inspector chart 0.3.0-beta.10

This chart deploys the private, ephemeral native-session inspector: one Deployment and internal ClusterIP Service named `<release>-inspector`, plus only explicitly retained legacy PVC manifests. It creates no database, migrations, task queue, native agents, PVC mounts, ingress, namespace, NetworkPolicy or provider CRDs. See the [root README](../../../README.md) for configuration, native host setup, metrics and verification.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@samuelho-dev/coordinator",
"version": "0.3.0-beta.9",
"version": "0.3.0-beta.10",
"type": "module",
"description": "Read-only native agent session inspection across self-hosted machines",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion scripts/deployment-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
Never pass raw credentials as arguments.
`;
const fail = (message: string) => Effect.fail(new HttpError({ status: 400, message }));
const program = Command.make("deployment-smoke", {

Check warning on line 21 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / npm-package

`effect/cli/Command#make` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 21 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/cli/Command#make` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 21 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/cli/Command#make` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)
config: Flag.String("config").pipe(Flag.optional),

Check warning on line 22 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / npm-package

`effect/cli/Flag#optional` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 22 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / npm-package

`effect/cli/Flag#String` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 22 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/cli/Flag#optional` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 22 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/cli/Flag#String` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 22 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/cli/Flag#optional` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 22 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/cli/Flag#String` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)
sourceSha: Flag.String("source-sha").pipe(Flag.optional),

Check warning on line 23 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / npm-package

`effect/cli/Flag#optional` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 23 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / npm-package

`effect/cli/Flag#String` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 23 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/cli/Flag#optional` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 23 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/cli/Flag#String` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 23 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/cli/Flag#optional` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 23 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/cli/Flag#String` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)
safeTarget: Flag.String("safe-target").pipe(Flag.optional),

Check warning on line 24 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / npm-package

`effect/cli/Flag#optional` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 24 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / npm-package

`effect/cli/Flag#String` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 24 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/cli/Flag#String` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 24 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/cli/Flag#optional` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 24 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/cli/Flag#String` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)
}, (flags) => Effect.gen(function*() {
const configFile = Option.getOrUndefined(flags.config), sourceSha = Option.getOrUndefined(flags.sourceSha);
if (sourceSha === undefined || !/^[a-f0-9]{40}$/.test(sourceSha)) return yield* fail("Full source SHA required");
Expand All @@ -31,9 +31,9 @@
if (endpoint.protocol !== "https:" || endpoint.pathname !== "/") return yield* fail("HTTPS inspector authority required");
const token = yield* readConnectorToken(config);
const mcp = yield* Mcp;
const crypto = yield* Crypto.Crypto;

Check warning on line 34 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / npm-package

`effect/Crypto#Crypto` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 34 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/Crypto#Crypto` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)
const invalidToken = `invalid-${yield* crypto.randomUUIDv4()}`;
const client = HttpClient.withScope(yield* HttpClient.HttpClient);

Check warning on line 36 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / npm-package

`effect/http/HttpClient#HttpClient` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 36 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / npm-package

`effect/http/HttpClient#withScope` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 36 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/http/HttpClient#HttpClient` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)

Check warning on line 36 in scripts/deployment-smoke.ts

View workflow job for this annotation

GitHub Actions / supplementary

`effect/http/HttpClient#withScope` is an unstable API. Breaking changes may happen between versions. effect(unstableApiUsage)
const probe = (path: string, credential?: string) => Effect.gen(function*() {
const headers = {
"content-type": "application/json",
Expand Down Expand Up @@ -67,7 +67,7 @@
let found = false;
for (let page = 0; page < 10; page++) {
const result = yield* mcp.call(config.inspectorUrl, token, InspectionTools.list, {
machineId: ref.machineId, harness: ref.harness, limit: 100, ...(cursor === undefined ? {} : { cursor }),
machineId: ref.machineId, limit: 100, ...(cursor === undefined ? {} : { cursor }),
});
if (result.sessions.some((presence) => sessionRefKey(presence.ref) === sessionRefKey(ref))) { found = true; break; }
if (result.nextCursor === null) break;
Expand Down
65 changes: 42 additions & 23 deletions scripts/smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const verify = {
};
const CoordinatorListMessage = Schema.Struct({
role: Schema.Literal("custom"), customType: Schema.Literal("coordinator-list"), content: Schema.String,
details: Schema.Struct({ sessions: Schema.Array(SessionPresence) }),
details: Schema.Struct({ machines: InspectionTools.list.output.fields.machines, sessions: InspectionTools.list.output.fields.sessions }),
});
const NativeStartupMessage = Schema.fromJsonString(Schema.Union([
Schema.Struct({ id: Schema.Literal("coordinator-startup"), type: Schema.Literal("response"), command: Schema.Literal("get_state"), success: Schema.Boolean }),
Expand Down Expand Up @@ -53,11 +53,13 @@ const program = Effect.gen(function*() {
const token = yield* crypto.randomUUIDv4(), siblingToken = yield* crypto.randomUUIDv4(), outsiderToken = yield* crypto.randomUUIDv4();
const machineId = `smoke-${yield* crypto.randomUUIDv4()}`;
const principal = (id: string, owner: string, credential: string) => crypto.digest("SHA-256", new TextEncoder().encode(credential)).pipe(Effect.map((digest) => ({ id, owner, tokenHash: Hex.encode(digest) })));
const inspector = yield* Inspector.make({ host: "127.0.0.1", port: 0, principals: [
yield* principal("local", "smoke-owner", token),
yield* principal("sibling", "smoke-owner", siblingToken),
yield* principal("outsider", "other-owner", outsiderToken),
] });
const inspector = yield* Inspector.make({
host: "127.0.0.1", port: 0, principals: [
yield* principal("local", "smoke-owner", token),
yield* principal("sibling", "smoke-owner", siblingToken),
yield* principal("outsider", "other-owner", outsiderToken),
]
});
const server = Context.get(yield* Layer.build(serverLayer("127.0.0.1", 0)), HttpServer.HttpServer);
let blockedStartup: {
readonly machineId: string;
Expand Down Expand Up @@ -116,8 +118,9 @@ const program = Effect.gen(function*() {
}).pipe(Effect.forkScoped);
const waitForSession = (harness: "pi" | "omp" | "hermes", sessionId?: string, installationId = machineId) => Effect.gen(function*() {
for (let attempt = 0; attempt < 300; attempt++) {
const result = yield* mcp.call(inspectorUrl, token, InspectionTools.list, { machineId: installationId, harness, limit: 100 });
const found = result.sessions.find((session) => sessionId === undefined ? session.ref.nativeSessionId !== presence.ref.nativeSessionId : session.ref.nativeSessionId === sessionId);
const result = yield* mcp.call(inspectorUrl, token, InspectionTools.list, { machineId: installationId, limit: 100 });
const found = result.sessions.find((session) => session.ref.harness === harness &&
(sessionId === undefined ? session.ref.nativeSessionId !== presence.ref.nativeSessionId : session.ref.nativeSessionId === sessionId));
if (found !== undefined) return found;
yield* Effect.sleep(100);
}
Expand Down Expand Up @@ -293,9 +296,9 @@ const program = Effect.gen(function*() {
})), Effect.forkScoped);
const hermes = yield* waitForSession("hermes", hermesId, hermesMachineId);
yield* verify.equal(hermes.activity?.state, "idle", "Turn end retains Hermes identity as idle");
yield* verifyWorkspace(hermes, repositoryRoot, "main");
yield* verifySessionResources(hermes, "shared");
const hermesUsage = yield* mcp.call(inspectorUrl, token, InspectionTools.inspect, { ref: hermes.ref });
yield* verifyWorkspace(hermesUsage.presence, repositoryRoot, "main");
yield* verifySessionResources(hermesUsage.presence, "shared");
yield* verify.equal(hermesUsage.presence.agent?.profile, "synthetic-profile");
yield* verify.equal(hermesUsage.presence.model?.provider, "synthetic-provider");
yield* verify.equal(hermesUsage.presence.model?.id, "synthetic-model");
Expand All @@ -320,8 +323,8 @@ const program = Effect.gen(function*() {
yield* Effect.sleep(25);
yield* hermesHook("on_session_end");
for (let attempt = 0; attempt < 300; attempt++) {
const current = yield* mcp.call(inspectorUrl, token, InspectionTools.list, { machineId: hermesMachineId, harness: "hermes", limit: 100 });
const updated = current.sessions.find((session) => session.ref.nativeSessionId === hermesId);
const current = yield* mcp.call(inspectorUrl, token, InspectionTools.list, { machineId: hermesMachineId, limit: 100 });
const updated = current.sessions.find((session) => session.ref.harness === "hermes" && session.ref.nativeSessionId === hermesId);
if (updated?.lastActivityAt !== undefined && updated.lastActivityAt !== hermes.lastActivityAt) break;
yield* verify.ok(attempt < 299, "Idle presence refresh deadline expired");
yield* Effect.sleep(100);
Expand All @@ -344,8 +347,8 @@ const program = Effect.gen(function*() {
yield* hermesHook("post_api_request", { usage: { input_tokens: 999 }, response: sensitive });
yield* hermesHook("on_session_end");
for (let attempt = 0; attempt < 300; attempt++) {
const current = yield* mcp.call(inspectorUrl, token, InspectionTools.list, { machineId: hermesMachineId, harness: "hermes", limit: 100 });
if (!current.sessions.some((session) => session.ref.nativeSessionId === hermesId)) break;
const current = yield* mcp.call(inspectorUrl, token, InspectionTools.list, { machineId: hermesMachineId, limit: 100 });
if (!current.sessions.some((session) => session.ref.harness === "hermes" && session.ref.nativeSessionId === hermesId)) break;
yield* verify.ok(attempt < 299, "Finalized Hermes identity must disappear and late callbacks cannot resurrect it");
yield* Effect.sleep(100);
}
Expand Down Expand Up @@ -485,12 +488,23 @@ const program = Effect.gen(function*() {
yield* verify.ok(result.details.sessions.some((entry) => entry.ref.machineId === hermesMachineId && entry.ref.harness === harness));
yield* verify.ok(result.details.sessions.some((entry) => entry.ref.machineId === otherMachineId));
const content = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(CoordinatorListMessage.fields.details))(result.content);
yield* verify.deepEqual(content.sessions, result.details.sessions, "Displayed JSON must preserve every listed session");
yield* verify.deepEqual(content, result.details, "Displayed JSON must preserve compact machine and session records");
const displayedNative = content.sessions.find((entry) => entry.ref.machineId === hermesMachineId && entry.ref.harness === harness);
if (displayedNative === undefined) return yield* verify.fail("Displayed native session missing");
yield* verifyWorkspace(displayedNative);
yield* verifySessionResources(displayedNative, harness === "omp" ? "shared" : "owned");
yield* verify.deepEqual(displayedNative.terminal, terminal, "Displayed native list must retain source terminal identity");
const displayedMachine = content.machines.find((entry) => entry.machineId === displayedNative.ref.machineId && entry.scopeId === displayedNative.machineScopeId);
if (!displayedMachine?.resources || !displayedNative.resources) return yield* verify.fail("Native list must include machine and process summaries");
yield* verify.equal(displayedNative.workspace?.cwd, worktreeRoot);
yield* verify.deepEqual(displayedNative.workspace?.project, project);
yield* verify.equal(displayedNative.workspace?.git?.state, "available");
if (displayedNative.workspace?.git?.state === "available") {
yield* verify.equal(displayedNative.workspace.git.head, gitHead);
yield* verify.equal(displayedNative.workspace.git.worktreeRoot, worktreeRoot);
yield* verify.equal(displayedNative.workspace.git.commonDir, join(repositoryRoot, ".git"));
}
yield* verify.equal(displayedNative.resources.attribution, harness === "omp" ? "shared" : "owned");
yield* verify.equal(displayedNative.resources.machineScopeId, displayedMachine.scopeId);
yield* verify.equal(displayedNative.resources.rssBytes.shared, harness === "omp" ? true : undefined);
yield* verify.deepEqual(displayedNative.terminal, terminal ? { name: terminal.name } : undefined, "Native list must retain terminal name without diagnostic version");
yield* verify.ok(!result.content.includes(token));
yield* Deferred.succeed(listVerified, undefined);
return;
Expand All @@ -517,11 +531,13 @@ const program = Effect.gen(function*() {
yield* verify.equal(yield* Deferred.isDone(startup.finished), false, `${harness} startup waited for the inspector response`);
blockedStartup = undefined;
yield* Deferred.succeed(startup.resume, undefined);
const native = yield* Effect.raceFirst(waitForSession(harness, undefined, hermesMachineId), Effect.gen(function*() {
const nativeList = yield* Effect.raceFirst(waitForSession(harness, undefined, hermesMachineId), Effect.gen(function*() {
const code = yield* child.exitCode;
yield* Fiber.join(stderr);
return yield* Effect.die(`Disposable ${harness} exited ${code} before presence; stderr (last 4096 characters): ${JSON.stringify(nativeStderr)}`);
}));
const inspected = yield* mcp.call(inspectorUrl, token, InspectionTools.inspect, { ref: nativeList.ref });
const native = inspected.presence;
yield* verify.deepEqual(native.terminal, terminal, "Native presence must preserve inherited terminal identity");
yield* verify.deepEqual(native.programStatus, { state: "idle" }, "Native startup has a known idle status");
yield* verifyWorkspace(native);
Expand All @@ -544,7 +560,7 @@ const program = Effect.gen(function*() {
yield* Deferred.succeed(runList, undefined);
yield* Effect.raceFirst(Deferred.await(listVerified), Fiber.join(stdout).pipe(Effect.andThen(verify.fail("Native RPC ended before list verification")))).pipe(Effect.timeout("30 seconds"));
yield* verify.equal(detailRequests, beforeList, "The list command must not sample per-session details");
const inspected = yield* mcp.call(inspectorUrl, token, InspectionTools.inspect, { ref: native.ref });
yield* verify.equal(inspected.presence.ref.nativeSessionId, nativeList.ref.nativeSessionId);
yield* verify.equal(inspected.presence.ref.harness, harness);
yield* verifyWorkspace(inspected.presence);
yield* verify.deepEqual(inspected.presence.terminal, terminal, "Inspection must preserve source terminal identity");
Expand Down Expand Up @@ -596,7 +612,8 @@ const program = Effect.gen(function*() {
yield* guard(JSON.stringify(rawListing) === JSON.stringify(listing), "Raw native list JSON must preserve complete structured details");
yield* guard(listing.sessions.some((entry) => entry.ref.machineId === ref.machineId && entry.ref.harness === "pi" && entry.ref.nativeSessionId === ref.nativeSessionId), "Native reader must discover the independent disposable Pi CLI");
const listedNative = listing.sessions.find((entry) => entry.ref.nativeSessionId === ref.nativeSessionId && entry.ref.machineId === ref.machineId && entry.ref.harness === ref.harness);
yield* guard(listedNative?.workspace?.cwd === ${JSON.stringify(worktreeRoot)} && listedNative.workspace.git?.state === "available" && listedNative.workspace.git.worktreeRoot === ${JSON.stringify(worktreeRoot)} && listedNative.workspace.git.head === ${JSON.stringify(gitHead)}, "Native list JSON must retain real linked-worktree association");
yield* guard(listedNative?.workspace?.cwd === ${JSON.stringify(worktreeRoot)} && listedNative.workspace.git?.state === "available" && listedNative.workspace.git.head === ${JSON.stringify(gitHead)}, "Native list JSON must retain real linked-worktree association");
yield* guard(listedNative?.workspace?.git?.state === "available" && listedNative.workspace.git.worktreeRoot === ${JSON.stringify(worktreeRoot)} && listedNative.workspace.git.commonDir === ${JSON.stringify(join(repositoryRoot, ".git"))}, "Native list JSON must retain shared Git identity and distinct worktree root");
yield* guard(listedNative?.workspace?.project?.root === ${JSON.stringify(project.root)} && listedNative.workspace.project.id === "smoke-project", "Native list JSON must retain explicit project scope");
yield* guard(listing.sessions.some((entry) => entry.ref.machineId === otherMachineId), "Native owner-key reader must discover separate installation");
const inspected = yield* Effect.promise(() => inspect.execute("native-smoke-inspect", { ref }));
Expand Down Expand Up @@ -637,8 +654,10 @@ const program = Effect.gen(function*() {
}
yield* otherConnector.kill();
yield* otherConnector.exitCode;
yield* Console.log(JSON.stringify({ scenario: "ephemeral-inspector", status: "ok", authenticatedHttpMcp: true, outboundConnector: true,
ownerIsolation: true, sharedOwnerMultipleInstallations: true, sameInstallationNativeIdentity: true, groupedMetadata: true, terminalIdentity: terminal !== undefined, localMachineTelemetry: nativeHarnesses, realGitWorktree: nativeHarnesses, explicitProjectScope: true, selfHostBootstrap: true, authenticatedClientSetup: true, onDemandOsResources: true, fixtureNativeCounters: "unavailable, not fabricated", nativeHarnesses, nativePrograms: nativeResults }));
yield* Console.log(JSON.stringify({
scenario: "ephemeral-inspector", status: "ok", authenticatedHttpMcp: true, outboundConnector: true,
ownerIsolation: true, sharedOwnerMultipleInstallations: true, sameInstallationNativeIdentity: true, groupedMetadata: true, terminalIdentity: terminal !== undefined, localMachineTelemetry: nativeHarnesses, realGitWorktree: nativeHarnesses, explicitProjectScope: true, selfHostBootstrap: true, authenticatedClientSetup: true, onDemandOsResources: true, fixtureNativeCounters: "unavailable, not fabricated", nativeHarnesses, nativePrograms: nativeResults
}));
});

NodeRuntime.runMain(program.pipe(Effect.scoped, Effect.timeout("120 seconds"), Effect.provide(Layer.mergeAll(NodeServices.layer, Host.layer, NodeHttpClient.layerUndici, Mcp.layer))));
Loading
Loading