Skip to content
Source
Sign inSign up

RuntimePolicy.test.ts

190 lines6.8 KB

1import { assert, it } from "@effect/vitest";
2import {
3 type ManagedPolicySummary,
4 type ModelSelection,
5 type OrchestrationV2AppThread,
6 ProjectId,
7 ProviderInstanceId,
8 type RuntimeMode,
9 type ServerProvider,
10 ThreadId,
11} from "@t3tools/contracts";
12import * as DateTime from "effect/DateTime";
13import * as Effect from "effect/Effect";
14import * as Layer from "effect/Layer";
15import * as Stream from "effect/Stream";
16​
17import type { ProviderInstance } from "@t3tools/provider-core/server/driver";
18import { acpPermissionDisposition } from "@t3tools/provider-acp/server/clientPolicy";
19import * as ServerSettings from "../serverSettings.ts";
20import * as ProviderInstanceRegistry from "../provider/ProviderInstanceRegistry.ts";
21import * as ProjectStore from "./ProjectStore.ts";
22import * as RuntimePolicy from "./RuntimePolicy.ts";
23​
24const projectId = ProjectId.make("project:runtime-policy");
25const providerInstanceId = ProviderInstanceId.make("codex");
26const modelSelection = {
27 instanceId: providerInstanceId,
28 model: "gpt-5.5",
29} satisfies ModelSelection;
30​
31function makeThread(input: {
32 readonly now: DateTime.Utc;
33 readonly worktreePath: string | null;
34 readonly runtimeMode?: RuntimeMode;
35}): OrchestrationV2AppThread {
36 const threadId = ThreadId.make("thread:runtime-policy");
37 return {
38 createdBy: "user",
39 creationSource: "web",
40 id: threadId,
41 projectId,
42 title: "Runtime policy",
43 providerInstanceId,
44 modelSelection,
45 runtimeMode: input.runtimeMode ?? "full-access",
46 interactionMode: "default",
47 branch: null,
48 worktreePath: input.worktreePath,
49 activeProviderThreadId: null,
50 lineage: {
51 parentThreadId: null,
52 relationshipToParent: null,
53 rootThreadId: threadId,
54 },
55 forkedFrom: null,
56 createdAt: input.now,
57 updatedAt: input.now,
58 archivedAt: null,
59 settledOverride: null,
60 settledAt: null,
61 lastVisitedAt: null,
62 deletedAt: null,
63 };
65​
66const grokInstanceId = ProviderInstanceId.make("grok");
67const antigravityInstanceId = ProviderInstanceId.make("antigravity");
68const supportedRuntimeModesByInstance = new Map<ProviderInstanceId, ReadonlyArray<RuntimeMode>>([
69 [grokInstanceId, ["approval-required", "auto", "full-access"]],
70 [antigravityInstanceId, ["approval-required", "auto-accept-edits", "full-access"]],
71]);
72const providerInstanceFor = (instanceId: ProviderInstanceId) =>
73 ({
74 snapshot: {
75 getSnapshot: Effect.succeed({
76 supportedRuntimeModes: supportedRuntimeModesByInstance.get(instanceId),
77 } as ServerProvider),
78 },
79 }) as ProviderInstance;
80​
81const layerWithPolicy = (policy?: ManagedPolicySummary) =>
82 RuntimePolicy.layerFromProjectStore.pipe(
83 Layer.provide(ServerSettings.layerTest({}, policy)),
84 Layer.provide(
85 Layer.succeed(ProviderInstanceRegistry.ProviderInstanceRegistry, {
86 getInstance: (instanceId) => Effect.succeed(providerInstanceFor(instanceId)),
87 listInstances: Effect.succeed([]),
88 listUnavailable: Effect.succeed([]),
89 streamChanges: Stream.empty,
90 subscribeChanges: Effect.never,
91 }),
92 ),
93 Layer.provide(
94 Layer.mock(ProjectStore.ProjectStoreV2)({
95 get: () =>
96 Effect.succeedSome({
97 projectId,
98 title: "Project",
99 workspaceRoot: "/project-root",
100 defaultModelSelection: null,
101 defaultThreadEnvMode: null,
102 hasCustomTitle: false,
103 autoPull: false,
104 faviconPath: null,
105 projectIcon: null,
106 scripts: [],
107 pinnedAt: null,
108 archivedAt: null,
109 createdAt: "2026-06-21T00:00:00.000Z",
110 updatedAt: "2026-06-21T00:00:00.000Z",
111 deletedAt: null,
112 }),
113 }),
114 ),
115 );
116​
117const layerTest = layerWithPolicy();
118​
119it.layer(layerTest)("RuntimePolicyV2", (it) => {
120 it.effect("asks for Antigravity command approval when a stored Auto thread falls back", () =>
121 Effect.gen(function* () {
122 const policy = yield* RuntimePolicy.RuntimePolicyV2;
123 const now = yield* DateTime.now;
124 const resolve = (runtimeMode: RuntimeMode) =>
125 policy.resolve({
126 thread: makeThread({ now, worktreePath: null, runtimeMode }),
127 modelSelection: { instanceId: antigravityInstanceId, model: "test-model" },
128 });
129 const request = {
130 sessionId: "session-1",
131 toolCall: { toolCallId: "command-1", title: "Run command", kind: "execute" as const },
132 options: [{ optionId: "allow", name: "Allow", kind: "allow_once" as const }],
133 };
134 const auto = yield* resolve("auto");
135 assert.equal(auto.runtimeMode, "approval-required");
136 assert.equal(acpPermissionDisposition(auto, request), "ask");
137 assert.equal(acpPermissionDisposition(yield* resolve("auto-accept-edits"), request), "ask");
138 assert.equal(acpPermissionDisposition(yield* resolve("full-access"), request), "allow");
139 }),
140 );
141​
142 it.effect("runs a mode the provider does not offer in Supervised", () =>
143 Effect.gen(function* () {
144 const policy = yield* RuntimePolicy.RuntimePolicyV2;
145 const now = yield* DateTime.now;
146 const modeFor = (instanceId: ProviderInstanceId, runtimeMode: RuntimeMode) =>
147 policy
148 .resolve({
149 thread: makeThread({ now, worktreePath: null, runtimeMode }),
150 modelSelection: { instanceId, model: "test-model" },
151 })
152 .pipe(Effect.map((resolved) => resolved.runtimeMode));
153​
154 assert.equal(yield* modeFor(grokInstanceId, "auto-accept-edits"), "approval-required");
155 assert.equal(yield* modeFor(grokInstanceId, "auto"), "auto");
156 assert.equal(yield* modeFor(grokInstanceId, "full-access"), "full-access");
157 assert.equal(yield* modeFor(providerInstanceId, "auto-accept-edits"), "auto-accept-edits");
158 }),
159 );
160});
161​
162it.layer(
163 layerWithPolicy({
164 organization: "Example Corp",
165 lockedSettings: [],
166 limits: { maxRuntimeMode: "auto-accept-edits" },
167 }),
168)("RuntimePolicyV2 under a runtime mode ceiling", (it) => {
169 it.effect("refuses to start a turn above the ceiling, such as one resumed after a restart", () =>
170 Effect.gen(function* () {
171 const policy = yield* RuntimePolicy.RuntimePolicyV2;
172 const now = yield* DateTime.now;
173 const resolve = (runtimeMode: RuntimeMode) =>
174 policy.resolve({
175 thread: makeThread({ now, worktreePath: "/work", runtimeMode }),
176 modelSelection,
177 });
178​
179 const refused = yield* resolve("full-access").pipe(Effect.flip);
180 assert.equal(refused._tag, "RuntimePolicyAboveCeilingError");
181 assert.equal(
182 refused.message,
183 "Example Corp limits threads to Auto-accept edits. Switch this thread's mode to continue.",
184 );
185 const allowed = yield* resolve("auto-accept-edits");
186 assert.equal(allowed.runtimeMode, "auto-accept-edits");
187 assert.equal(allowed.maxRuntimeMode, "auto-accept-edits");
188 }),
189 );
190});