@checkstack/gitops-backend 0.1.1 → 0.2.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/CHANGELOG.md +49 -0
- package/drizzle/0001_wandering_leech.sql +1 -0
- package/drizzle/0002_far_lady_vermin.sql +1 -0
- package/drizzle/meta/0001_snapshot.json +317 -0
- package/drizzle/meta/0002_snapshot.json +324 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +2 -2
- package/src/index.ts +3 -0
- package/src/kind-registry.test.ts +96 -0
- package/src/kind-registry.ts +67 -3
- package/src/router.ts +62 -5
- package/src/schema.ts +6 -2
- package/src/secret-resolver.test.ts +279 -40
- package/src/secret-resolver.ts +194 -27
- package/src/sync/reconciler-delete.test.ts +4 -0
- package/src/sync/reconciler.ts +63 -14
- package/src/sync/sort-entities.ts +2 -0
- package/src/sync/sync-worker.ts +14 -4
|
@@ -8,6 +8,20 @@
|
|
|
8
8
|
"when": 1776797758378,
|
|
9
9
|
"tag": "0000_tense_stryfe",
|
|
10
10
|
"breakpoints": true
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"idx": 1,
|
|
14
|
+
"version": "7",
|
|
15
|
+
"when": 1776929187262,
|
|
16
|
+
"tag": "0001_wandering_leech",
|
|
17
|
+
"breakpoints": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"idx": 2,
|
|
21
|
+
"version": "7",
|
|
22
|
+
"when": 1776931984135,
|
|
23
|
+
"tag": "0002_far_lady_vermin",
|
|
24
|
+
"breakpoints": true
|
|
11
25
|
}
|
|
12
26
|
]
|
|
13
27
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/gitops-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"checkstack": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@checkstack/backend-api": "0.12.0",
|
|
17
|
-
"@checkstack/gitops-common": "0.1.
|
|
17
|
+
"@checkstack/gitops-common": "0.1.1",
|
|
18
18
|
"@checkstack/common": "0.6.5",
|
|
19
19
|
"@checkstack/command-backend": "0.1.19",
|
|
20
20
|
"@checkstack/queue-api": "0.2.13",
|
package/src/index.ts
CHANGED
|
@@ -207,6 +207,7 @@ describe("EntityKindRegistry", () => {
|
|
|
207
207
|
const sys = described[0];
|
|
208
208
|
expect(sys.apiVersion).toBe(CHECKSTACK_API_VERSION);
|
|
209
209
|
expect(sys.kind).toBe("System");
|
|
210
|
+
expect(sys.metadataSchema).toBeDefined();
|
|
210
211
|
expect(sys.specSchema).toBeDefined();
|
|
211
212
|
expect(sys.extensions).toHaveLength(0);
|
|
212
213
|
|
|
@@ -259,4 +260,99 @@ describe("EntityKindRegistry", () => {
|
|
|
259
260
|
expect(described).toHaveLength(0);
|
|
260
261
|
});
|
|
261
262
|
});
|
|
263
|
+
|
|
264
|
+
describe("registerSpecSchemaDocumentation", () => {
|
|
265
|
+
it("allows registering documentation for different field paths and multiple entries per path", () => {
|
|
266
|
+
const registry = createEntityKindRegistry();
|
|
267
|
+
|
|
268
|
+
registry.registerKind({
|
|
269
|
+
apiVersion: CHECKSTACK_API_VERSION,
|
|
270
|
+
kind: "Healthcheck",
|
|
271
|
+
specSchema: z.object({ config: z.unknown(), collectors: z.array(z.unknown()) }),
|
|
272
|
+
reconcile: async () => ({ entityId: "test-id" }),
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Register two strategies for the 'config' field
|
|
276
|
+
registry.registerSpecSchemaDocumentation({
|
|
277
|
+
apiVersion: CHECKSTACK_API_VERSION,
|
|
278
|
+
kind: "Healthcheck",
|
|
279
|
+
fieldPath: "config",
|
|
280
|
+
variantId: "http-strat",
|
|
281
|
+
label: "HTTP Strategy",
|
|
282
|
+
description: "Configure HTTP health check",
|
|
283
|
+
schema: z.object({ url: z.string() }),
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
registry.registerSpecSchemaDocumentation({
|
|
287
|
+
apiVersion: CHECKSTACK_API_VERSION,
|
|
288
|
+
kind: "Healthcheck",
|
|
289
|
+
fieldPath: "config",
|
|
290
|
+
variantId: "dns-strat",
|
|
291
|
+
label: "DNS Strategy",
|
|
292
|
+
schema: z.object({ hostname: z.string() }), // no description
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Register a collector for 'collectors[].config'
|
|
296
|
+
registry.registerSpecSchemaDocumentation({
|
|
297
|
+
apiVersion: CHECKSTACK_API_VERSION,
|
|
298
|
+
kind: "Healthcheck",
|
|
299
|
+
fieldPath: "collectors[].config",
|
|
300
|
+
label: "Ping Collector",
|
|
301
|
+
description: "Ping something",
|
|
302
|
+
schema: z.object({ count: z.number() }),
|
|
303
|
+
conditions: [{
|
|
304
|
+
fieldPath: "config",
|
|
305
|
+
variantIds: ["http-strat", "dns-strat"],
|
|
306
|
+
}],
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const described = registry.describeKinds();
|
|
310
|
+
expect(described).toHaveLength(1);
|
|
311
|
+
|
|
312
|
+
const docs = described[0].specSchemaDocumentation;
|
|
313
|
+
expect(docs).toHaveLength(3);
|
|
314
|
+
|
|
315
|
+
const configDocs = docs.filter(d => d.fieldPath === "config");
|
|
316
|
+
expect(configDocs).toHaveLength(2);
|
|
317
|
+
expect(configDocs[0].label).toBe("HTTP Strategy");
|
|
318
|
+
expect(configDocs[0].variantId).toBe("http-strat");
|
|
319
|
+
expect(configDocs[0].description).toBe("Configure HTTP health check");
|
|
320
|
+
expect(configDocs[1].label).toBe("DNS Strategy");
|
|
321
|
+
expect(configDocs[1].variantId).toBe("dns-strat");
|
|
322
|
+
expect(configDocs[1].description).toBeUndefined();
|
|
323
|
+
|
|
324
|
+
const collectorDocs = docs.filter(d => d.fieldPath === "collectors[].config");
|
|
325
|
+
expect(collectorDocs).toHaveLength(1);
|
|
326
|
+
expect(collectorDocs[0].label).toBe("Ping Collector");
|
|
327
|
+
expect(collectorDocs[0].conditions).toBeDefined();
|
|
328
|
+
expect(collectorDocs[0].conditions?.[0].variantIds).toEqual(["http-strat", "dns-strat"]);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("allows registering docs before the kind itself is registered", () => {
|
|
332
|
+
const registry = createEntityKindRegistry();
|
|
333
|
+
|
|
334
|
+
registry.registerSpecSchemaDocumentation({
|
|
335
|
+
apiVersion: CHECKSTACK_API_VERSION,
|
|
336
|
+
kind: "Healthcheck",
|
|
337
|
+
fieldPath: "config",
|
|
338
|
+
label: "HTTP Strategy",
|
|
339
|
+
schema: z.object({ url: z.string() }),
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Base kind is missing, so describeKinds should skip it
|
|
343
|
+
expect(registry.describeKinds()).toHaveLength(0);
|
|
344
|
+
|
|
345
|
+
registry.registerKind({
|
|
346
|
+
apiVersion: CHECKSTACK_API_VERSION,
|
|
347
|
+
kind: "Healthcheck",
|
|
348
|
+
specSchema: z.object({ config: z.unknown() }),
|
|
349
|
+
reconcile: async () => ({ entityId: "test-id" }),
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Now it should be included
|
|
353
|
+
const described = registry.describeKinds();
|
|
354
|
+
expect(described).toHaveLength(1);
|
|
355
|
+
expect(described[0].specSchemaDocumentation).toHaveLength(1);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
262
358
|
});
|
package/src/kind-registry.ts
CHANGED
|
@@ -4,12 +4,15 @@ import type {
|
|
|
4
4
|
EntityKindDefinition,
|
|
5
5
|
EntityKindExtensionDefinition,
|
|
6
6
|
EntityKindRegistry,
|
|
7
|
+
SpecSchemaDocumentation,
|
|
7
8
|
} from "@checkstack/gitops-common";
|
|
9
|
+
import { entityMetadataSchema } from "@checkstack/gitops-common";
|
|
8
10
|
|
|
9
11
|
/** Internal storage for a registered kind with its extensions. */
|
|
10
12
|
interface RegisteredKind {
|
|
11
13
|
definition: EntityKindDefinition<unknown> | undefined;
|
|
12
14
|
extensions: Map<string, EntityKindExtensionDefinition<unknown>>;
|
|
15
|
+
specSchemaDocumentation: SpecSchemaDocumentation[];
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
/** Composite key for looking up kinds by apiVersion + kind. */
|
|
@@ -46,11 +49,23 @@ export function createEntityKindRegistry() {
|
|
|
46
49
|
describeKinds: () => Array<{
|
|
47
50
|
apiVersion: string;
|
|
48
51
|
kind: string;
|
|
52
|
+
metadataSchema: Record<string, unknown>;
|
|
49
53
|
specSchema: Record<string, unknown>;
|
|
50
54
|
extensions: Array<{
|
|
51
55
|
namespace: string;
|
|
52
56
|
specSchema: Record<string, unknown>;
|
|
53
57
|
}>;
|
|
58
|
+
specSchemaDocumentation: Array<{
|
|
59
|
+
fieldPath: string;
|
|
60
|
+
variantId?: string;
|
|
61
|
+
label: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
specSchema: Record<string, unknown>;
|
|
64
|
+
conditions?: Array<{
|
|
65
|
+
fieldPath: string;
|
|
66
|
+
variantIds: string[];
|
|
67
|
+
}>;
|
|
68
|
+
}>;
|
|
54
69
|
}>;
|
|
55
70
|
} = {
|
|
56
71
|
registerKind<TSpec>(definition: EntityKindDefinition<TSpec>) {
|
|
@@ -70,6 +85,7 @@ export function createEntityKindRegistry() {
|
|
|
70
85
|
kinds.set(key, {
|
|
71
86
|
definition: definition as EntityKindDefinition<unknown>,
|
|
72
87
|
extensions: new Map(),
|
|
88
|
+
specSchemaDocumentation: [],
|
|
73
89
|
});
|
|
74
90
|
}
|
|
75
91
|
},
|
|
@@ -91,6 +107,7 @@ export function createEntityKindRegistry() {
|
|
|
91
107
|
definition as EntityKindExtensionDefinition<unknown>,
|
|
92
108
|
],
|
|
93
109
|
]),
|
|
110
|
+
specSchemaDocumentation: [],
|
|
94
111
|
});
|
|
95
112
|
return;
|
|
96
113
|
}
|
|
@@ -107,6 +124,30 @@ export function createEntityKindRegistry() {
|
|
|
107
124
|
);
|
|
108
125
|
},
|
|
109
126
|
|
|
127
|
+
registerSpecSchemaDocumentation(params) {
|
|
128
|
+
const key = kindKey(params);
|
|
129
|
+
let registered = kinds.get(key);
|
|
130
|
+
|
|
131
|
+
if (!registered) {
|
|
132
|
+
// Allow registering docs before the kind itself
|
|
133
|
+
registered = {
|
|
134
|
+
definition: undefined,
|
|
135
|
+
extensions: new Map(),
|
|
136
|
+
specSchemaDocumentation: [],
|
|
137
|
+
};
|
|
138
|
+
kinds.set(key, registered);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
registered.specSchemaDocumentation.push({
|
|
142
|
+
fieldPath: params.fieldPath,
|
|
143
|
+
variantId: params.variantId,
|
|
144
|
+
label: params.label,
|
|
145
|
+
description: params.description,
|
|
146
|
+
schema: params.schema,
|
|
147
|
+
conditions: params.conditions,
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
|
|
110
151
|
getKind(params) {
|
|
111
152
|
return kinds.get(kindKey(params))?.definition;
|
|
112
153
|
},
|
|
@@ -149,20 +190,30 @@ export function createEntityKindRegistry() {
|
|
|
149
190
|
const result: Array<{
|
|
150
191
|
apiVersion: string;
|
|
151
192
|
kind: string;
|
|
193
|
+
metadataSchema: Record<string, unknown>;
|
|
152
194
|
specSchema: Record<string, unknown>;
|
|
153
195
|
extensions: Array<{
|
|
154
196
|
namespace: string;
|
|
155
197
|
specSchema: Record<string, unknown>;
|
|
156
198
|
}>;
|
|
199
|
+
specSchemaDocumentation: Array<{
|
|
200
|
+
fieldPath: string;
|
|
201
|
+
variantId?: string;
|
|
202
|
+
label: string;
|
|
203
|
+
description?: string;
|
|
204
|
+
specSchema: Record<string, unknown>;
|
|
205
|
+
conditions?: Array<{
|
|
206
|
+
fieldPath: string;
|
|
207
|
+
variantIds: string[];
|
|
208
|
+
}>;
|
|
209
|
+
}>;
|
|
157
210
|
}> = [];
|
|
158
211
|
|
|
159
212
|
for (const registered of kinds.values()) {
|
|
160
213
|
if (!registered.definition) continue;
|
|
161
214
|
|
|
162
215
|
const def = registered.definition;
|
|
163
|
-
const baseSchema = toJsonSchema(
|
|
164
|
-
def.specSchema as z.ZodTypeAny,
|
|
165
|
-
);
|
|
216
|
+
const baseSchema = toJsonSchema(def.specSchema as z.ZodTypeAny);
|
|
166
217
|
|
|
167
218
|
const extensions = [...registered.extensions.entries()].map(
|
|
168
219
|
([namespace, ext]) => ({
|
|
@@ -171,11 +222,24 @@ export function createEntityKindRegistry() {
|
|
|
171
222
|
}),
|
|
172
223
|
);
|
|
173
224
|
|
|
225
|
+
const specSchemaDocumentation = registered.specSchemaDocumentation.map(
|
|
226
|
+
(doc) => ({
|
|
227
|
+
fieldPath: doc.fieldPath,
|
|
228
|
+
variantId: doc.variantId,
|
|
229
|
+
label: doc.label,
|
|
230
|
+
description: doc.description,
|
|
231
|
+
specSchema: toJsonSchema(doc.schema),
|
|
232
|
+
conditions: doc.conditions,
|
|
233
|
+
}),
|
|
234
|
+
);
|
|
235
|
+
|
|
174
236
|
result.push({
|
|
175
237
|
apiVersion: def.apiVersion,
|
|
176
238
|
kind: def.kind,
|
|
239
|
+
metadataSchema: toJsonSchema(entityMetadataSchema),
|
|
177
240
|
specSchema: baseSchema,
|
|
178
241
|
extensions,
|
|
242
|
+
specSchemaDocumentation,
|
|
179
243
|
});
|
|
180
244
|
}
|
|
181
245
|
|
package/src/router.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { implement, ORPCError } from "@orpc/server";
|
|
2
|
+
import { z } from "zod";
|
|
2
3
|
import { autoAuthMiddleware, type RpcContext } from "@checkstack/backend-api";
|
|
3
4
|
import { encrypt, decrypt } from "@checkstack/backend-api";
|
|
4
5
|
import { gitopsContract } from "@checkstack/gitops-common";
|
|
5
6
|
import type { SafeDatabase } from "@checkstack/backend-api";
|
|
6
7
|
import type { QueueManager } from "@checkstack/queue-api";
|
|
7
8
|
import type { InternalEntityKindRegistry } from "./kind-registry";
|
|
8
|
-
import { triggerSyncForProvider } from "./sync/sync-worker";
|
|
9
|
+
import { triggerSyncForProvider, scheduleSyncForProvider, cancelSyncForProvider } from "./sync/sync-worker";
|
|
9
10
|
import * as schema from "./schema";
|
|
10
|
-
import { eq, and } from "drizzle-orm";
|
|
11
|
+
import { eq, and, sql } from "drizzle-orm";
|
|
11
12
|
import { v4 as uuidv4 } from "uuid";
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -47,12 +48,15 @@ export const createGitOpsRouter = ({
|
|
|
47
48
|
.select()
|
|
48
49
|
.from(schema.provenance)
|
|
49
50
|
.where(and(...conditions));
|
|
51
|
+
const row = result[0];
|
|
50
52
|
// eslint-disable-next-line unicorn/no-null
|
|
51
|
-
return
|
|
53
|
+
return row ? { ...row, warnings: row.warnings ?? [] } : null;
|
|
52
54
|
});
|
|
53
55
|
|
|
54
56
|
const listProvenance = os.listProvenance.handler(async ({ input }) => {
|
|
55
|
-
const
|
|
57
|
+
const rawRows = await db.select().from(schema.provenance);
|
|
58
|
+
// Normalize: ensure warnings is always a string[] (Drizzle may return null for pre-migration rows)
|
|
59
|
+
const rows = rawRows.map((row) => ({ ...row, warnings: row.warnings ?? [] }));
|
|
56
60
|
if (!input) return rows;
|
|
57
61
|
|
|
58
62
|
return rows.filter((row) => {
|
|
@@ -82,6 +86,7 @@ export const createGitOpsRouter = ({
|
|
|
82
86
|
|
|
83
87
|
const createProvider = os.createProvider.handler(async ({ input }) => {
|
|
84
88
|
const id = uuidv4();
|
|
89
|
+
const syncInterval = input.syncInterval ?? 300;
|
|
85
90
|
await db.insert(schema.providers).values({
|
|
86
91
|
id,
|
|
87
92
|
type: input.type,
|
|
@@ -89,9 +94,17 @@ export const createGitOpsRouter = ({
|
|
|
89
94
|
pathPattern: input.pathPattern,
|
|
90
95
|
baseUrl: input.baseUrl ?? null, // eslint-disable-line unicorn/no-null
|
|
91
96
|
authToken: input.authToken ? encrypt(input.authToken) : null, // eslint-disable-line unicorn/no-null
|
|
92
|
-
syncInterval
|
|
97
|
+
syncInterval,
|
|
93
98
|
deletionPolicy: input.deletionPolicy ?? "orphan",
|
|
94
99
|
});
|
|
100
|
+
|
|
101
|
+
// Schedule recurring sync for the new provider
|
|
102
|
+
await scheduleSyncForProvider({
|
|
103
|
+
queueManager,
|
|
104
|
+
providerId: id,
|
|
105
|
+
syncIntervalSeconds: syncInterval,
|
|
106
|
+
});
|
|
107
|
+
|
|
95
108
|
return { id };
|
|
96
109
|
});
|
|
97
110
|
|
|
@@ -127,6 +140,15 @@ export const createGitOpsRouter = ({
|
|
|
127
140
|
.set(updates)
|
|
128
141
|
.where(eq(schema.providers.id, input.id));
|
|
129
142
|
|
|
143
|
+
// If syncInterval changed, reschedule the recurring job
|
|
144
|
+
if (input.data.syncInterval !== undefined) {
|
|
145
|
+
await scheduleSyncForProvider({
|
|
146
|
+
queueManager,
|
|
147
|
+
providerId: input.id,
|
|
148
|
+
syncIntervalSeconds: input.data.syncInterval,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
130
152
|
return { success: true };
|
|
131
153
|
});
|
|
132
154
|
|
|
@@ -145,6 +167,12 @@ export const createGitOpsRouter = ({
|
|
|
145
167
|
// Provenance entries are cascade-deleted via FK constraint
|
|
146
168
|
await db.delete(schema.providers).where(eq(schema.providers.id, input.id));
|
|
147
169
|
|
|
170
|
+
// Cancel the recurring sync job
|
|
171
|
+
await cancelSyncForProvider({
|
|
172
|
+
queueManager,
|
|
173
|
+
providerId: input.id,
|
|
174
|
+
});
|
|
175
|
+
|
|
148
176
|
return { success: true };
|
|
149
177
|
});
|
|
150
178
|
|
|
@@ -212,6 +240,8 @@ export const createGitOpsRouter = ({
|
|
|
212
240
|
// eslint-disable-next-line unicorn/no-useless-undefined
|
|
213
241
|
return undefined;
|
|
214
242
|
},
|
|
243
|
+
resolveSecretsBySchema: async <T>(params: { value: T; schema: z.ZodTypeAny }): Promise<{ resolved: T; warnings: string[] }> =>
|
|
244
|
+
({ resolved: params.value, warnings: [] }),
|
|
215
245
|
},
|
|
216
246
|
});
|
|
217
247
|
} catch (deleteError) {
|
|
@@ -303,6 +333,16 @@ export const createGitOpsRouter = ({
|
|
|
303
333
|
.set({ encryptedValue, updatedAt: new Date() })
|
|
304
334
|
.where(eq(schema.secrets.id, input.id));
|
|
305
335
|
|
|
336
|
+
// Invalidate provenance for all entities referencing this secret
|
|
337
|
+
// so the next sync cycle re-reconciles them with the updated value
|
|
338
|
+
const secretName = existing[0].name;
|
|
339
|
+
await db
|
|
340
|
+
.update(schema.provenance)
|
|
341
|
+
.set({ lastSyncHash: "" })
|
|
342
|
+
.where(
|
|
343
|
+
sql`${secretName} = ANY(${schema.provenance.secretRefs})`,
|
|
344
|
+
);
|
|
345
|
+
|
|
306
346
|
return { success: true };
|
|
307
347
|
});
|
|
308
348
|
|
|
@@ -328,6 +368,22 @@ export const createGitOpsRouter = ({
|
|
|
328
368
|
return { value: decrypt(secret.encryptedValue) };
|
|
329
369
|
});
|
|
330
370
|
|
|
371
|
+
const getSecretUsage = os.getSecretUsage.handler(async ({ input }) => {
|
|
372
|
+
const rows = await db
|
|
373
|
+
.select({
|
|
374
|
+
kind: schema.provenance.kind,
|
|
375
|
+
entityName: schema.provenance.entityName,
|
|
376
|
+
repository: schema.provenance.repository,
|
|
377
|
+
filePath: schema.provenance.filePath,
|
|
378
|
+
})
|
|
379
|
+
.from(schema.provenance)
|
|
380
|
+
.where(
|
|
381
|
+
sql`${input.secretName} = ANY(${schema.provenance.secretRefs})`,
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
return rows;
|
|
385
|
+
});
|
|
386
|
+
|
|
331
387
|
// ─── Kind Registry ────────────────────────────────────────────────────
|
|
332
388
|
|
|
333
389
|
const listKinds = os.listKinds.handler(async () => {
|
|
@@ -351,6 +407,7 @@ export const createGitOpsRouter = ({
|
|
|
351
407
|
rotateSecret,
|
|
352
408
|
deleteSecret,
|
|
353
409
|
resolveSecret,
|
|
410
|
+
getSecretUsage,
|
|
354
411
|
listKinds,
|
|
355
412
|
});
|
|
356
413
|
};
|
package/src/schema.ts
CHANGED
|
@@ -57,16 +57,20 @@ export const provenance = pgTable("provenance", {
|
|
|
57
57
|
repository: text("repository").notNull(),
|
|
58
58
|
filePath: text("file_path").notNull(),
|
|
59
59
|
lastSyncHash: text("last_sync_hash").notNull(),
|
|
60
|
+
/** Secret names referenced via ${{ secrets.NAME }} in this entity's spec. */
|
|
61
|
+
secretRefs: text("secret_refs").array().default([]),
|
|
60
62
|
status: provenanceStatusEnum("status").notNull().default("synced"),
|
|
61
63
|
errorMessage: text("error_message"),
|
|
64
|
+
/** Warnings about unresolved secret templates in non-secret fields. */
|
|
65
|
+
warnings: text("warnings").array().default([]),
|
|
62
66
|
lastSyncedAt: timestamp("last_synced_at").defaultNow().notNull(),
|
|
63
67
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
64
68
|
});
|
|
65
69
|
|
|
66
|
-
/** Secret store for
|
|
70
|
+
/** Secret store for ${{ secrets.NAME }} template values in YAML descriptors. */
|
|
67
71
|
export const secrets = pgTable("secrets", {
|
|
68
72
|
id: text("id").primaryKey(),
|
|
69
|
-
/** Unique name referenced
|
|
73
|
+
/** Unique name referenced via ${{ secrets.NAME }} in descriptors. */
|
|
70
74
|
name: text("name").notNull().unique(),
|
|
71
75
|
/** AES-256-GCM encrypted value (format: iv:authTag:ciphertext). */
|
|
72
76
|
encryptedValue: text("encrypted_value").notNull(),
|