Skip to content

Commit 29a5a34

Browse files
committed
feat(prime): verify managed distributions
Closes #193
1 parent 22e8c2b commit 29a5a34

15 files changed

Lines changed: 3124 additions & 8 deletions

apps/server/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
"@ff-labs/fff-node": "0.9.4",
3131
"@opencode-ai/sdk": "^1.3.15",
3232
"@pierre/diffs": "catalog:",
33+
"@sigstore/bundle": "4.0.0",
34+
"@sigstore/core": "3.2.1",
35+
"@sigstore/tuf": "4.0.0",
36+
"@sigstore/verify": "3.0.0",
3337
"effect": "catalog:",
3438
"msgpackr-extract": "3.0.4",
3539
"node-pty": "^1.1.0",

apps/server/src/provider/Drivers/PrimeAgentDriver.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { PrimeAgentSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts";
1+
import {
2+
PrimeAgentSettings,
3+
ProviderDriverKind,
4+
type ServerProvider,
5+
type ServerProviderDistribution,
6+
} from "@t3tools/contracts";
27
import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
38
import { resolveCommandPath } from "@t3tools/shared/shell";
49
import * as Crypto from "effect/Crypto";
@@ -32,6 +37,12 @@ import { readPrimeAgentBackends, readPrimeAgentCapacity } from "../primeAgentBac
3237
const PRIME_AGENT_TURN_END_CAPACITY_FRESH_MS = 60_000;
3338
import { makePrimeAgentDaemonAdapter } from "../prime/PrimeAgentDaemonAdapter.ts";
3439
import { negotiatePrimeAgentBackend } from "../prime/PrimeAgentBackendSelection.ts";
40+
import { locatePrimeAgentPublicPackage } from "../prime/PrimeAgentDaemonBridge.ts";
41+
import {
42+
inspectPrimeAgentDistribution,
43+
makeLatestPrimePublicationLoader,
44+
makePrimeDistributionNetworkDependencies,
45+
} from "../prime/PrimeAgentDistributionVerifier.ts";
3546
import { makePrimeAgentDaemonManager } from "../prime/PrimeAgentDaemonManager.ts";
3647
import {
3748
defaultProviderContinuationIdentity,
@@ -137,6 +148,67 @@ export const PrimeAgentDriver: ProviderDriver<PrimeAgentSettings, PrimeAgentDriv
137148
continuationGroupKey: continuationIdentity.continuationKey,
138149
});
139150
const effectiveConfig = { ...config, enabled } satisfies PrimeAgentSettings;
151+
const loadLatestVerifiedPublication = makeLatestPrimePublicationLoader(
152+
makePrimeDistributionNetworkDependencies({
153+
tufCachePath: path.join(serverConfig.stateDir, "sigstore-tuf"),
154+
}),
155+
);
156+
const inspectDistribution = (
157+
snapshot: ServerProvider,
158+
enableUpdateChecks: boolean | undefined,
159+
): Effect.Effect<ServerProviderDistribution> => {
160+
if (!snapshot.enabled || !snapshot.installed) {
161+
return Effect.succeed({
162+
classification: "stock-or-custom" as const,
163+
channel: null,
164+
buildId: null,
165+
sequence: null,
166+
latestBuildId: null,
167+
latestSequence: null,
168+
updateAvailable: false,
169+
checkedAt: snapshot.checkedAt,
170+
message: "This Prime installation is maintained manually.",
171+
});
172+
}
173+
return Effect.gen(function* () {
174+
const executablePath = path.resolve(
175+
yield* resolveCommandPath(effectiveConfig.binaryPath || "prime-agent", {
176+
env: processEnv,
177+
}),
178+
);
179+
const publicPackage = yield* locatePrimeAgentPublicPackage(executablePath);
180+
return yield* Effect.promise(() =>
181+
inspectPrimeAgentDistribution(
182+
{
183+
stateDir: serverConfig.stateDir,
184+
instanceId,
185+
packageRoot: publicPackage.packageRoot,
186+
platform: hostPlatform,
187+
checkedAt: snapshot.checkedAt,
188+
...(enableUpdateChecks === undefined ? {} : { enableUpdateChecks }),
189+
},
190+
{ loadLatestVerifiedPublication },
191+
),
192+
);
193+
}).pipe(
194+
Effect.catchCause(() =>
195+
Effect.succeed({
196+
classification: "stock-or-custom" as const,
197+
channel: null,
198+
buildId: null,
199+
sequence: null,
200+
latestBuildId: null,
201+
latestSequence: null,
202+
updateAvailable: false,
203+
checkedAt: snapshot.checkedAt,
204+
message:
205+
"Pylon could not inspect the selected Prime distribution; Prime remains ready and manually maintained.",
206+
}),
207+
),
208+
Effect.provideService(FileSystem.FileSystem, fileSystem),
209+
Effect.provideService(Path.Path, path),
210+
);
211+
};
140212
const maintenanceCapabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(UPDATE, {
141213
binaryPath: effectiveConfig.binaryPath,
142214
env: processEnv,
@@ -286,6 +358,7 @@ export const PrimeAgentDriver: ProviderDriver<PrimeAgentSettings, PrimeAgentDriv
286358
snapshot: currentSnapshot,
287359
maintenanceCapabilities,
288360
enableProviderUpdateChecks: settings.enableProviderUpdateChecks,
361+
distribution: inspectDistribution(currentSnapshot, settings.enableProviderUpdateChecks),
289362
publishSnapshot,
290363
httpClient,
291364
}),

apps/server/src/provider/Layers/PrimeAgentProvider.test.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import * as NodeServices from "@effect/platform-node/NodeServices";
22
import { describe, expect, it } from "@effect/vitest";
3-
import { PrimeAgentSettings, ProviderDriverKind, ProviderInstanceId } from "@t3tools/contracts";
3+
import {
4+
PrimeAgentSettings,
5+
ProviderDriverKind,
6+
ProviderInstanceId,
7+
type ServerProvider,
8+
} from "@t3tools/contracts";
49
import * as Cause from "effect/Cause";
510
import * as Deferred from "effect/Deferred";
611
import * as Effect from "effect/Effect";
@@ -24,6 +29,7 @@ import {
2429
sharedUsageReadKey,
2530
} from "../sharedUsageReadCache.ts";
2631
import {
32+
applyPrimeAgentDistribution,
2733
buildInitialPrimeAgentProviderSnapshot,
2834
checkPrimeAgentProviderStatus,
2935
parsePrimeAgentModelDiscoveryOutput,
@@ -98,6 +104,45 @@ function mockPrimeAgentSpawner(input: {
98104
);
99105
}
100106

107+
describe("PrimeAgentProvider distribution", () => {
108+
it("keeps runtime readiness independent and maps only signed build identity to advisory", () => {
109+
const snapshot: ServerProvider = {
110+
instanceId: ProviderInstanceId.make("primeAgent"),
111+
driver: ProviderDriverKind.make("primeAgent"),
112+
enabled: true,
113+
installed: true,
114+
version: "0.8.1",
115+
status: "ready",
116+
auth: { status: "authenticated" },
117+
checkedAt: "2026-08-20T12:00:00.000Z",
118+
models: [],
119+
slashCommands: [],
120+
skills: [],
121+
};
122+
const result = applyPrimeAgentDistribution(snapshot, {
123+
classification: "pylon-managed",
124+
channel: "preview",
125+
buildId: "pylon-build-g0123456789ab-r1",
126+
sequence: 9,
127+
latestBuildId: "pylon-build-gabcdef012345-r1",
128+
latestSequence: 10,
129+
updateAvailable: true,
130+
checkedAt: "2026-08-20T12:00:00.000Z",
131+
message: "A signed preview build is available.",
132+
});
133+
134+
expect(result.status).toBe("ready");
135+
expect(result.version).toBe("0.8.1");
136+
expect(result.versionAdvisory).toMatchObject({
137+
status: "behind_latest",
138+
currentVersion: "pylon-build-g0123456789ab-r1",
139+
latestVersion: "pylon-build-gabcdef012345-r1",
140+
canUpdate: false,
141+
updateCommand: null,
142+
});
143+
});
144+
});
145+
101146
describe("PrimeAgentProvider models", () => {
102147
it("always publishes the synthetic default model before unique custom models", () => {
103148
expect(

apps/server/src/provider/Layers/PrimeAgentProvider.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
type ModelCapabilities,
33
type PrimeAgentSettings,
44
type ServerProvider,
5+
type ServerProviderDistribution,
56
type ServerProviderModel,
67
type ServerProviderBackend,
78
} from "@t3tools/contracts";
@@ -534,20 +535,48 @@ ${versionOutput.stderr}`);
534535
});
535536
});
536537

538+
export function applyPrimeAgentDistribution(
539+
snapshot: ServerProvider,
540+
distribution: ServerProviderDistribution,
541+
): ServerProvider {
542+
const versionAdvisory =
543+
distribution.classification === "pylon-managed"
544+
? {
545+
status: distribution.updateAvailable ? ("behind_latest" as const) : ("current" as const),
546+
currentVersion: distribution.buildId,
547+
latestVersion: distribution.latestBuildId,
548+
updateCommand: null,
549+
canUpdate: false,
550+
checkedAt: distribution.checkedAt,
551+
message: distribution.updateAvailable ? distribution.message : null,
552+
}
553+
: snapshot.versionAdvisory;
554+
return {
555+
...snapshot,
556+
distribution,
557+
...(versionAdvisory ? { versionAdvisory } : {}),
558+
};
559+
}
560+
537561
export const enrichPrimeAgentSnapshot = (input: {
538562
readonly snapshot: ServerProvider;
539563
readonly maintenanceCapabilities: ProviderMaintenanceCapabilities;
540564
readonly enableProviderUpdateChecks?: boolean;
565+
readonly distribution: Effect.Effect<ServerProviderDistribution>;
541566
readonly publishSnapshot: (snapshot: ServerProvider) => Effect.Effect<void>;
542567
readonly httpClient: HttpClient.HttpClient;
543568
}): Effect.Effect<void> =>
544-
enrichProviderSnapshotWithVersionAdvisory(input.snapshot, input.maintenanceCapabilities, {
545-
enableProviderUpdateChecks: input.enableProviderUpdateChecks,
569+
Effect.gen(function* () {
570+
const snapshot = yield* enrichProviderSnapshotWithVersionAdvisory(
571+
input.snapshot,
572+
input.maintenanceCapabilities,
573+
{ enableProviderUpdateChecks: input.enableProviderUpdateChecks },
574+
).pipe(Effect.provideService(HttpClient.HttpClient, input.httpClient));
575+
const distribution = yield* input.distribution;
576+
yield* input.publishSnapshot(applyPrimeAgentDistribution(snapshot, distribution));
546577
}).pipe(
547-
Effect.provideService(HttpClient.HttpClient, input.httpClient),
548-
Effect.flatMap(input.publishSnapshot),
549578
Effect.catchCause((cause) =>
550-
Effect.logWarning("Prime Agent version advisory enrichment failed", { cause }),
579+
Effect.logWarning("Prime Agent distribution advisory enrichment failed", { cause }),
551580
),
552581
Effect.asVoid,
553582
);

0 commit comments

Comments
 (0)