1import * as GCP from "@/GCP";
2import { GcpEnvironment } from "@/GCP/Environment";
3import * as Test from "@/Test/Alchemy";
4import * as aiplatform from "@distilled.cloud/gcp/aiplatform_v1";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17// Policies govern an existing Vertex AI Agent
18// (`projects/{p}/locations/{l}/agents/{a}`), which the v1 API cannot create.
19// Set GCP_TEST_AGENT to that agent's resource name to run the lifecycle.
20const runLifecycle = !process.env.FAST && !!process.env.GCP_TEST_AGENT;
21
22const waitUntilGone = (name: string) =>
23 aiplatform.getProjectsLocationsSemanticGovernancePolicies({ name }).pipe(
24 Effect.as("found" as const),
25 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
26 Effect.repeat({
27 schedule: Schedule.spaced("2 seconds"),
28 until: (status) => status === "gone",
29 times: 10,
30 }),
31 );
32
33test.provider(
34 "getProjectsLocationsSemanticGovernancePolicies on a missing policy fails with a typed tag",
35 (stack) =>
36 Effect.gen(function* () {
37 const { project } = yield* GcpEnvironment.current;
38 yield* stack.destroy();
39
40 const error = yield* Effect.flip(
41 aiplatform.getProjectsLocationsSemanticGovernancePolicies({
42 name: `projects/${project}/locations/us-central1/semanticGovernancePolicies/alchemy-sgp-missing`,
43 }),
44 );
45 expect(error._tag).toEqual("NotFound");
46
47 yield* stack.destroy();
48 }).pipe(logLevel),
49 {
50 tags: ["provider:gcp", "provider:gcp:aiplatform", "live"],
51 timeout: 90_000,
52 },
53);
54
55test.provider.skipIf(!runLifecycle)(
56 "create, update, and delete a semantic governance policy",
57 (stack) =>
58 Effect.gen(function* () {
59 const { project } = yield* GcpEnvironment.current;
60 const agent =
61 process.env.GCP_TEST_AGENT ??
62 `projects/${project}/locations/us-central1/reasoningEngines/missing-agent`;
63 yield* stack.destroy();
64
65 const created = yield* stack.deploy(
66 Effect.gen(function* () {
67 return yield* GCP.AIPlatform.SemanticGovernancePolicy("Safety", {
68 location: "us-central1",
69 displayName: "safety",
70 description: "no pii",
71 agent,
72 naturalLanguageConstraint: "Never share customer PII.",
73 });
74 }),
75 );
76
77 expect(created.name).toContain("/semanticGovernancePolicies/");
78 expect(created.naturalLanguageConstraint).toEqual(
79 "Never share customer PII.",
80 );
81
82 const fetched =
83 yield* aiplatform.getProjectsLocationsSemanticGovernancePolicies({
84 name: created.name,
85 });
86 expect(fetched.name).toEqual(created.name);
87 expect(fetched.description).toContain("alchemy-id=");
88
89 const updated = yield* stack.deploy(
90 Effect.gen(function* () {
91 return yield* GCP.AIPlatform.SemanticGovernancePolicy("Safety", {
92 location: "us-central1",
93 semanticGovernancePolicyId: created.semanticGovernancePolicyId,
94 displayName: "safety-v2",
95 description: "no pii v2",
96 agent,
97 naturalLanguageConstraint: "Never share secrets or PII.",
98 });
99 }),
100 );
101 expect(updated.name).toEqual(created.name);
102 expect(updated.naturalLanguageConstraint).toEqual(
103 "Never share secrets or PII.",
104 );
105
106 yield* stack.destroy();
107 const gone = yield* waitUntilGone(created.name);
108 expect(gone).toEqual("gone");
109 }).pipe(logLevel),
110 {
111 tags: ["provider:gcp", "provider:gcp:aiplatform", "live"],
112 timeout: 120_000,
113 },
114);