@noodleseed/one 0.153.0 → 0.154.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/dist/commands/app-purge-reconciliation-args.d.ts +28 -0
- package/dist/commands/app-purge-reconciliation-args.d.ts.map +1 -0
- package/dist/commands/app-purge-reconciliation-args.js +124 -0
- package/dist/commands/app-purge-reconciliation-args.js.map +1 -0
- package/dist/commands/app-purge-reconciliation-files.d.ts +8 -0
- package/dist/commands/app-purge-reconciliation-files.d.ts.map +1 -0
- package/dist/commands/app-purge-reconciliation-files.js +109 -0
- package/dist/commands/app-purge-reconciliation-files.js.map +1 -0
- package/dist/commands/app-purge-reconciliation-ops.d.ts +3 -0
- package/dist/commands/app-purge-reconciliation-ops.d.ts.map +1 -0
- package/dist/commands/app-purge-reconciliation-ops.js +190 -0
- package/dist/commands/app-purge-reconciliation-ops.js.map +1 -0
- package/dist/commands/billing-ops.d.ts.map +1 -1
- package/dist/commands/billing-ops.js +7 -0
- package/dist/commands/billing-ops.js.map +1 -1
- package/dist/commands/billing-org-transfer-files.d.ts +12 -0
- package/dist/commands/billing-org-transfer-files.d.ts.map +1 -0
- package/dist/commands/billing-org-transfer-files.js +112 -0
- package/dist/commands/billing-org-transfer-files.js.map +1 -0
- package/dist/commands/billing-org-transfer-ops.d.ts +4 -0
- package/dist/commands/billing-org-transfer-ops.d.ts.map +1 -0
- package/dist/commands/billing-org-transfer-ops.js +534 -0
- package/dist/commands/billing-org-transfer-ops.js.map +1 -0
- package/dist/commands/service-ops.d.ts.map +1 -1
- package/dist/commands/service-ops.js +4 -1
- package/dist/commands/service-ops.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-account.js +96 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-billing.js +116 -1
- package/node_modules/@noodle-borg/control-plane/dist/app-purge-reconciliation-port.d.ts +25 -0
- package/node_modules/@noodle-borg/control-plane/dist/app-purge-reconciliation-port.js +10 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +1 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/control-plane/package.json +3 -1
- package/node_modules/@noodle-borg/service/dist/routes/app-purge-reconciliation.js +72 -0
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +15 -2
- package/node_modules/@noodle-borg/service/dist/service.js +27 -1
- package/node_modules/@noodle-borg/service/dist/store/in-memory.js +4 -8
- package/node_modules/@noodle-borg/service/dist/store/json-file.js +6 -3
- package/node_modules/@noodle-borg/service/dist/store/records.js +20 -0
- package/node_modules/@noodle-borg/service/dist/store.d.ts +3 -2
- package/node_modules/@noodle-borg/service/package.json +1 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/app-purge-reconciliation.d.ts +177 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/app-purge-reconciliation.js +189 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/assistant.d.ts +1 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/billing-org-transfer.d.ts +629 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/billing-org-transfer.js +259 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.d.ts +21 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.js +23 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +2 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +2 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/platform-account-reset.d.ts +4 -4
- package/node_modules/@noodleseed/assistant/package.json +1 -1
- package/package.json +2 -2
|
@@ -23,6 +23,7 @@ import { isAuthServerPath } from './oauth/paths.js';
|
|
|
23
23
|
import { createIntentTargetResolver, createObservabilityDispatcher, createObservabilityStores, } from './observability-runtime.js';
|
|
24
24
|
import { handleAccessUpdate } from './routes/access.js';
|
|
25
25
|
import { dispatchAlertRoutes } from './routes/alerts-dispatch.js';
|
|
26
|
+
import { handleAppPurgeReconciliationRoute, } from './routes/app-purge-reconciliation.js';
|
|
26
27
|
import { handleAppArchive, handleAppRestore } from './routes/archive.js';
|
|
27
28
|
import { handleAssetPreflight } from './routes/asset-control-plane.js';
|
|
28
29
|
import { dispatchAssistantRoutes } from './routes/assistant-dispatch.js';
|
|
@@ -233,6 +234,26 @@ export function createServiceHandler(registry, options = {}) {
|
|
|
233
234
|
...(advanced ? { modules: capabilityReport.modules } : {}),
|
|
234
235
|
});
|
|
235
236
|
}
|
|
237
|
+
const appPurgeReconciliationAction = url.pathname === '/v1/service/app-purge-reconciliation/preview'
|
|
238
|
+
? 'preview'
|
|
239
|
+
: url.pathname === '/v1/service/app-purge-reconciliation/apply'
|
|
240
|
+
? 'apply'
|
|
241
|
+
: undefined;
|
|
242
|
+
if (req.method === 'POST' && appPurgeReconciliationAction !== undefined) {
|
|
243
|
+
applySecurityHeaders(res, tls);
|
|
244
|
+
if (enforceHttps(req, res, tls))
|
|
245
|
+
return;
|
|
246
|
+
void handleAppPurgeReconciliationRoute(req, res, {
|
|
247
|
+
gate,
|
|
248
|
+
...(options.appPurgeReconciliationOperator === undefined
|
|
249
|
+
? {}
|
|
250
|
+
: { operator: options.appPurgeReconciliationOperator }),
|
|
251
|
+
releaseSha: buildInfo.gitSha,
|
|
252
|
+
maxBody,
|
|
253
|
+
...(options.clock === undefined ? {} : { clock: options.clock }),
|
|
254
|
+
}, appPurgeReconciliationAction);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
236
257
|
if (servicePrincipalDispatcher?.(req, res, url))
|
|
237
258
|
return;
|
|
238
259
|
if (dispatchAssistantRoutes(req, res, url, {
|
|
@@ -316,7 +337,12 @@ export function createServiceHandler(registry, options = {}) {
|
|
|
316
337
|
applySecurityHeaders(res, tls);
|
|
317
338
|
if (enforceHttps(req, res, tls))
|
|
318
339
|
return;
|
|
319
|
-
|
|
340
|
+
const scopes = url.searchParams.getAll('scope');
|
|
341
|
+
if (scopes.length > 1 || (scopes.length === 1 && scopes[0] !== 'identity')) {
|
|
342
|
+
sendJson(res, 400, { error: 'invalid whoami scope' });
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
handleWhoami(req, res, gate, controlPlane, options, scopes[0] === 'identity' ? 'identity' : undefined).catch(() => {
|
|
320
346
|
if (!res.headersSent)
|
|
321
347
|
sendJson(res, 500, { error: 'internal error' });
|
|
322
348
|
});
|
|
@@ -3,7 +3,7 @@ import { assertSameAppPackageSnapshot, sanitizeDeployRecordAppPackageSnapshot, }
|
|
|
3
3
|
import { assertCustomerAuthRestorePrecondition, assertUniqueActiveCustomerAuthAudienceBindings, findActiveCustomerAuthAudienceConflict, } from '../customer-auth-audience-binding.js';
|
|
4
4
|
import { assertDeploymentActivationUnlocked, assertDeploymentAppendUnlocked, } from '../deployment-lock.js';
|
|
5
5
|
import { defaultActiveRecord, sameDeploymentScope, sameTenantRecord, } from '../deployment-versioning.js';
|
|
6
|
-
import { appArchivedAt, deploymentSummary, matchesDeploymentFilter, paginateAppSummaries, planAppArchive, planAppRestore, preserveDeploymentOwnerState, resolveActiveAccessUpdate, resolveProductionEnvironment, summarizeApps, summarizeEnvs, validateDeploymentListFilter, withoutArchiveStamp, } from './records.js';
|
|
6
|
+
import { appArchivedAt, deploymentSummary, matchesDeploymentFilter, paginateAppSummaries, planAppArchive, planAppRestore, planArchivedAppSweep, preserveDeploymentOwnerState, resolveActiveAccessUpdate, resolveProductionEnvironment, summarizeApps, summarizeEnvs, validateDeploymentListFilter, withoutArchiveStamp, } from './records.js';
|
|
7
7
|
import { validateSlug, validateTenantRef } from './validate.js';
|
|
8
8
|
/** In-memory store: keeps records in a Map. Used by tests and as an explicit, non-persistent option. */
|
|
9
9
|
export class InMemoryArtifactStore {
|
|
@@ -165,13 +165,9 @@ export class InMemoryArtifactStore {
|
|
|
165
165
|
return Promise.resolve(plan.result);
|
|
166
166
|
}
|
|
167
167
|
sweepArchived(before) {
|
|
168
|
-
const deleted = [];
|
|
169
|
-
for (const
|
|
170
|
-
|
|
171
|
-
deleted.push(record);
|
|
172
|
-
this.#records.delete(id);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
168
|
+
const deleted = planArchivedAppSweep([...this.#records.values()], before);
|
|
169
|
+
for (const record of deleted)
|
|
170
|
+
this.#records.delete(record.deploymentId);
|
|
175
171
|
return Promise.resolve(deleted);
|
|
176
172
|
}
|
|
177
173
|
async listApps(org, opts = {}) {
|
|
@@ -6,7 +6,7 @@ import { assertSameAppPackageSnapshot, sanitizeDeployRecordAppPackageSnapshot, }
|
|
|
6
6
|
import { assertCustomerAuthRestorePrecondition, assertUniqueActiveCustomerAuthAudienceBindings, findActiveCustomerAuthAudienceConflict, } from '../customer-auth-audience-binding.js';
|
|
7
7
|
import { assertDeploymentActivationUnlocked, assertDeploymentAppendUnlocked, } from '../deployment-lock.js';
|
|
8
8
|
import { defaultActiveRecord, sameDeploymentScope, sameTenantRecord, } from '../deployment-versioning.js';
|
|
9
|
-
import { appArchivedAt, deploymentSummary, matchesDeploymentFilter, paginateAppSummaries, planAppArchive, planAppRestore, preserveDeploymentOwnerState, resolveActiveAccessUpdate, resolveProductionEnvironment, summarizeApps, summarizeEnvs, validateDeploymentListFilter, withoutArchiveStamp, } from './records.js';
|
|
9
|
+
import { appArchivedAt, deploymentSummary, matchesDeploymentFilter, paginateAppSummaries, planAppArchive, planAppRestore, planArchivedAppSweep, preserveDeploymentOwnerState, resolveActiveAccessUpdate, resolveProductionEnvironment, summarizeApps, summarizeEnvs, validateDeploymentListFilter, withoutArchiveStamp, } from './records.js';
|
|
10
10
|
import { DEPLOYMENT_ID_PATTERN, validateSlug, validateTenantRef } from './validate.js';
|
|
11
11
|
/**
|
|
12
12
|
* JSON-file store: one file per deployment at `<dataDir>/deployments/<deploymentId>.json`, written atomically
|
|
@@ -228,8 +228,11 @@ export class JsonFileArtifactStore {
|
|
|
228
228
|
await Promise.all(plan.clear.map((record) => this.#writeRecord(withoutArchiveStamp(record))));
|
|
229
229
|
return plan.result;
|
|
230
230
|
}
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
sweepArchived(before) {
|
|
232
|
+
return this.#serializeLifecycle(() => this.#sweepArchivedUnlocked(before));
|
|
233
|
+
}
|
|
234
|
+
async #sweepArchivedUnlocked(before) {
|
|
235
|
+
const deleted = planArchivedAppSweep(await this.loadAll(), before);
|
|
233
236
|
await Promise.all(deleted.map((record) => rm(join(this.#dir, `${record.deploymentId}.json`), { force: true })));
|
|
234
237
|
return deleted;
|
|
235
238
|
}
|
|
@@ -101,6 +101,26 @@ export function planAppRestore(records) {
|
|
|
101
101
|
const clear = records.filter((record) => record.archivedAt !== undefined);
|
|
102
102
|
return { result: { restoredDeployments: clear.length }, clear };
|
|
103
103
|
}
|
|
104
|
+
/** Plan a retention sweep: only delete an app when every deployment is archived before the cutoff. */
|
|
105
|
+
export function planArchivedAppSweep(records, before) {
|
|
106
|
+
const apps = new Map();
|
|
107
|
+
for (const record of records) {
|
|
108
|
+
let byApp = apps.get(record.orgSlug);
|
|
109
|
+
if (byApp === undefined) {
|
|
110
|
+
byApp = new Map();
|
|
111
|
+
apps.set(record.orgSlug, byApp);
|
|
112
|
+
}
|
|
113
|
+
const bucket = byApp.get(record.appSlug);
|
|
114
|
+
if (bucket === undefined)
|
|
115
|
+
byApp.set(record.appSlug, [record]);
|
|
116
|
+
else
|
|
117
|
+
bucket.push(record);
|
|
118
|
+
}
|
|
119
|
+
return [...apps.values()].flatMap((byApp) => [...byApp.values()].flatMap((appRecords) => appRecords.length > 0 &&
|
|
120
|
+
appRecords.every((record) => record.archivedAt !== undefined && record.archivedAt < before)
|
|
121
|
+
? appRecords
|
|
122
|
+
: []));
|
|
123
|
+
}
|
|
104
124
|
/** Strip the archive stamp from a record (exact-optional safe). */
|
|
105
125
|
export function withoutArchiveStamp(record) {
|
|
106
126
|
const { archivedAt: _cleared, ...rest } = record;
|
|
@@ -318,8 +318,9 @@ export interface ArtifactStore {
|
|
|
318
318
|
* App soft delete (ADR 0117). `getAppArchivedAt` reports the app's archived stamp (defined iff the
|
|
319
319
|
* app has records and every record is stamped). `archiveApp` stamps every record for the app (all
|
|
320
320
|
* environments/versions); `restoreApp` clears the stamps; both resolve `undefined` for an app with
|
|
321
|
-
* no records. `sweepArchived` hard-deletes
|
|
322
|
-
*
|
|
321
|
+
* no records. `sweepArchived` hard-deletes a complete app only when every one of its records is archived
|
|
322
|
+
* strictly before `before`, returning every record from each deleted app so the caller can reclaim
|
|
323
|
+
* associated config and emit audit events.
|
|
323
324
|
*/
|
|
324
325
|
getAppArchivedAt(org: string, app: string): Promise<string | undefined>;
|
|
325
326
|
archiveApp(org: string, app: string, at: string): Promise<AppArchiveResult | undefined>;
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
41
41
|
"@noodle-borg/admission-limits": "0.0.0",
|
|
42
|
-
"@noodle-borg/agent-kit": "0.
|
|
42
|
+
"@noodle-borg/agent-kit": "0.93.0",
|
|
43
43
|
"@noodle-borg/app-package": "0.0.0",
|
|
44
44
|
"@noodle-borg/assistant-gateway": "0.0.0",
|
|
45
45
|
"@noodle-borg/auth": "0.0.0",
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const APP_PURGE_RECONCILIATION_SCHEMA_VERSION: 1;
|
|
3
|
+
export declare const APP_PURGE_RECONCILIATION_MAX_CANDIDATES = 100;
|
|
4
|
+
export declare const APP_PURGE_RECONCILIATION_PREVIEW_TTL_MS: number;
|
|
5
|
+
export declare const AppPurgeReconciliationEnvironmentSchema: z.ZodObject<{
|
|
6
|
+
name: z.ZodString;
|
|
7
|
+
isProduction: z.ZodBoolean;
|
|
8
|
+
createdAt: z.ZodISODateTime;
|
|
9
|
+
}, z.core.$strict>;
|
|
10
|
+
export type AppPurgeReconciliationEnvironmentV1 = z.infer<typeof AppPurgeReconciliationEnvironmentSchema>;
|
|
11
|
+
export declare const AppPurgeReconciliationCandidateSchema: z.ZodObject<{
|
|
12
|
+
org: z.ZodString;
|
|
13
|
+
app: z.ZodString;
|
|
14
|
+
anchorCreatedAt: z.ZodISODateTime;
|
|
15
|
+
purgeAuditId: z.ZodString;
|
|
16
|
+
environments: z.ZodArray<z.ZodObject<{
|
|
17
|
+
name: z.ZodString;
|
|
18
|
+
isProduction: z.ZodBoolean;
|
|
19
|
+
createdAt: z.ZodISODateTime;
|
|
20
|
+
}, z.core.$strict>>;
|
|
21
|
+
}, z.core.$strict>;
|
|
22
|
+
export type AppPurgeReconciliationCandidateV1 = z.infer<typeof AppPurgeReconciliationCandidateSchema>;
|
|
23
|
+
export declare const AppPurgeReconciliationPreviewRequestSchema: z.ZodObject<{
|
|
24
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
25
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
26
|
+
}, z.core.$strict>;
|
|
27
|
+
export type AppPurgeReconciliationPreviewRequestV1 = z.infer<typeof AppPurgeReconciliationPreviewRequestSchema>;
|
|
28
|
+
export declare const AppPurgeReconciliationPreviewArtifactSchema: z.ZodObject<{
|
|
29
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
30
|
+
releaseSha: z.ZodString;
|
|
31
|
+
createdAt: z.ZodISODateTime;
|
|
32
|
+
expiresAt: z.ZodISODateTime;
|
|
33
|
+
candidateCount: z.ZodNumber;
|
|
34
|
+
truncated: z.ZodBoolean;
|
|
35
|
+
candidates: z.ZodArray<z.ZodObject<{
|
|
36
|
+
org: z.ZodString;
|
|
37
|
+
app: z.ZodString;
|
|
38
|
+
anchorCreatedAt: z.ZodISODateTime;
|
|
39
|
+
purgeAuditId: z.ZodString;
|
|
40
|
+
environments: z.ZodArray<z.ZodObject<{
|
|
41
|
+
name: z.ZodString;
|
|
42
|
+
isProduction: z.ZodBoolean;
|
|
43
|
+
createdAt: z.ZodISODateTime;
|
|
44
|
+
}, z.core.$strict>>;
|
|
45
|
+
}, z.core.$strict>>;
|
|
46
|
+
checksum: z.ZodString;
|
|
47
|
+
}, z.core.$strict>;
|
|
48
|
+
export type AppPurgeReconciliationPreviewArtifactV1 = z.infer<typeof AppPurgeReconciliationPreviewArtifactSchema>;
|
|
49
|
+
type ReadonlyDeep<Value> = Value extends readonly (infer Item)[] ? readonly ReadonlyDeep<Item>[] : Value extends object ? {
|
|
50
|
+
readonly [Key in keyof Value]: ReadonlyDeep<Value[Key]>;
|
|
51
|
+
} : Value;
|
|
52
|
+
export type AppPurgeReconciliationUnsignedPreviewArtifactV1 = Omit<ReadonlyDeep<AppPurgeReconciliationPreviewArtifactV1>, 'checksum'>;
|
|
53
|
+
export declare const AppPurgeReconciliationPreviewResponseSchema: z.ZodObject<{
|
|
54
|
+
ok: z.ZodLiteral<true>;
|
|
55
|
+
artifact: z.ZodObject<{
|
|
56
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
57
|
+
releaseSha: z.ZodString;
|
|
58
|
+
createdAt: z.ZodISODateTime;
|
|
59
|
+
expiresAt: z.ZodISODateTime;
|
|
60
|
+
candidateCount: z.ZodNumber;
|
|
61
|
+
truncated: z.ZodBoolean;
|
|
62
|
+
candidates: z.ZodArray<z.ZodObject<{
|
|
63
|
+
org: z.ZodString;
|
|
64
|
+
app: z.ZodString;
|
|
65
|
+
anchorCreatedAt: z.ZodISODateTime;
|
|
66
|
+
purgeAuditId: z.ZodString;
|
|
67
|
+
environments: z.ZodArray<z.ZodObject<{
|
|
68
|
+
name: z.ZodString;
|
|
69
|
+
isProduction: z.ZodBoolean;
|
|
70
|
+
createdAt: z.ZodISODateTime;
|
|
71
|
+
}, z.core.$strict>>;
|
|
72
|
+
}, z.core.$strict>>;
|
|
73
|
+
checksum: z.ZodString;
|
|
74
|
+
}, z.core.$strict>;
|
|
75
|
+
}, z.core.$strict>;
|
|
76
|
+
export type AppPurgeReconciliationPreviewResponseV1 = z.infer<typeof AppPurgeReconciliationPreviewResponseSchema>;
|
|
77
|
+
export declare const AppPurgeReconciliationApplyRequestSchema: z.ZodObject<{
|
|
78
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
79
|
+
preview: z.ZodObject<{
|
|
80
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
81
|
+
releaseSha: z.ZodString;
|
|
82
|
+
createdAt: z.ZodISODateTime;
|
|
83
|
+
expiresAt: z.ZodISODateTime;
|
|
84
|
+
candidateCount: z.ZodNumber;
|
|
85
|
+
truncated: z.ZodBoolean;
|
|
86
|
+
candidates: z.ZodArray<z.ZodObject<{
|
|
87
|
+
org: z.ZodString;
|
|
88
|
+
app: z.ZodString;
|
|
89
|
+
anchorCreatedAt: z.ZodISODateTime;
|
|
90
|
+
purgeAuditId: z.ZodString;
|
|
91
|
+
environments: z.ZodArray<z.ZodObject<{
|
|
92
|
+
name: z.ZodString;
|
|
93
|
+
isProduction: z.ZodBoolean;
|
|
94
|
+
createdAt: z.ZodISODateTime;
|
|
95
|
+
}, z.core.$strict>>;
|
|
96
|
+
}, z.core.$strict>>;
|
|
97
|
+
checksum: z.ZodString;
|
|
98
|
+
}, z.core.$strict>;
|
|
99
|
+
releaseSha: z.ZodString;
|
|
100
|
+
approvalReference: z.ZodString;
|
|
101
|
+
recoveryCheckpoint: z.ZodString;
|
|
102
|
+
reason: z.ZodString;
|
|
103
|
+
idempotencyKey: z.ZodString;
|
|
104
|
+
confirmed: z.ZodLiteral<true>;
|
|
105
|
+
}, z.core.$strict>;
|
|
106
|
+
export type AppPurgeReconciliationApplyRequestV1 = z.infer<typeof AppPurgeReconciliationApplyRequestSchema>;
|
|
107
|
+
export declare const AppPurgeReconciliationApplyResultSchema: z.ZodObject<{
|
|
108
|
+
operationId: z.ZodString;
|
|
109
|
+
previewChecksum: z.ZodString;
|
|
110
|
+
releaseSha: z.ZodString;
|
|
111
|
+
candidateCount: z.ZodNumber;
|
|
112
|
+
deletedCount: z.ZodNumber;
|
|
113
|
+
appliedAt: z.ZodISODateTime;
|
|
114
|
+
}, z.core.$strict>;
|
|
115
|
+
export type AppPurgeReconciliationApplyResultV1 = z.infer<typeof AppPurgeReconciliationApplyResultSchema>;
|
|
116
|
+
export declare const AppPurgeReconciliationApplyResponseSchema: z.ZodObject<{
|
|
117
|
+
ok: z.ZodLiteral<true>;
|
|
118
|
+
replayed: z.ZodBoolean;
|
|
119
|
+
result: z.ZodObject<{
|
|
120
|
+
operationId: z.ZodString;
|
|
121
|
+
previewChecksum: z.ZodString;
|
|
122
|
+
releaseSha: z.ZodString;
|
|
123
|
+
candidateCount: z.ZodNumber;
|
|
124
|
+
deletedCount: z.ZodNumber;
|
|
125
|
+
appliedAt: z.ZodISODateTime;
|
|
126
|
+
}, z.core.$strict>;
|
|
127
|
+
}, z.core.$strict>;
|
|
128
|
+
export type AppPurgeReconciliationApplyResponseV1 = z.infer<typeof AppPurgeReconciliationApplyResponseSchema>;
|
|
129
|
+
/** Client reader strips additive fields at every response layer while service output stays strict. */
|
|
130
|
+
export declare const AppPurgeReconciliationClientResponseSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
131
|
+
ok: z.ZodLiteral<true>;
|
|
132
|
+
artifact: z.ZodObject<{
|
|
133
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
134
|
+
releaseSha: z.ZodString;
|
|
135
|
+
createdAt: z.ZodISODateTime;
|
|
136
|
+
expiresAt: z.ZodISODateTime;
|
|
137
|
+
candidateCount: z.ZodNumber;
|
|
138
|
+
truncated: z.ZodBoolean;
|
|
139
|
+
candidates: z.ZodArray<z.ZodObject<{
|
|
140
|
+
org: z.ZodString;
|
|
141
|
+
app: z.ZodString;
|
|
142
|
+
anchorCreatedAt: z.ZodISODateTime;
|
|
143
|
+
purgeAuditId: z.ZodString;
|
|
144
|
+
environments: z.ZodArray<z.ZodObject<{
|
|
145
|
+
name: z.ZodString;
|
|
146
|
+
isProduction: z.ZodBoolean;
|
|
147
|
+
createdAt: z.ZodISODateTime;
|
|
148
|
+
}, z.core.$strip>>;
|
|
149
|
+
}, z.core.$strip>>;
|
|
150
|
+
checksum: z.ZodString;
|
|
151
|
+
}, z.core.$strip>;
|
|
152
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
153
|
+
ok: z.ZodLiteral<true>;
|
|
154
|
+
replayed: z.ZodBoolean;
|
|
155
|
+
result: z.ZodObject<{
|
|
156
|
+
operationId: z.ZodString;
|
|
157
|
+
previewChecksum: z.ZodString;
|
|
158
|
+
releaseSha: z.ZodString;
|
|
159
|
+
candidateCount: z.ZodNumber;
|
|
160
|
+
deletedCount: z.ZodNumber;
|
|
161
|
+
appliedAt: z.ZodISODateTime;
|
|
162
|
+
}, z.core.$strip>;
|
|
163
|
+
}, z.core.$strip>]>;
|
|
164
|
+
export type AppPurgeReconciliationClientResponseV1 = z.infer<typeof AppPurgeReconciliationClientResponseSchema>;
|
|
165
|
+
export declare const AppPurgeReconciliationErrorCodeSchema: z.ZodEnum<{
|
|
166
|
+
app_purge_preview_expired: "app_purge_preview_expired";
|
|
167
|
+
app_purge_release_mismatch: "app_purge_release_mismatch";
|
|
168
|
+
app_purge_preview_mismatch: "app_purge_preview_mismatch";
|
|
169
|
+
app_purge_candidate_drift: "app_purge_candidate_drift";
|
|
170
|
+
app_purge_idempotency_conflict: "app_purge_idempotency_conflict";
|
|
171
|
+
app_purge_reconciliation_unavailable: "app_purge_reconciliation_unavailable";
|
|
172
|
+
}>;
|
|
173
|
+
export type AppPurgeReconciliationErrorCodeV1 = z.infer<typeof AppPurgeReconciliationErrorCodeSchema>;
|
|
174
|
+
/** Exact unsigned bytes hashed for a preview checksum. */
|
|
175
|
+
export declare function appPurgeReconciliationChecksumPayload(unsignedArtifact: AppPurgeReconciliationUnsignedPreviewArtifactV1): string;
|
|
176
|
+
export {};
|
|
177
|
+
//# sourceMappingURL=app-purge-reconciliation.d.ts.map
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
3
|
+
const slug = z.string().regex(SLUG_PATTERN);
|
|
4
|
+
const timestamp = z.iso.datetime({ offset: true });
|
|
5
|
+
const releaseSha = z.string().regex(/^[0-9a-f]{40}$/);
|
|
6
|
+
const checksum = z.string().regex(/^sha256:[0-9a-f]{64}$/);
|
|
7
|
+
export const APP_PURGE_RECONCILIATION_SCHEMA_VERSION = 1;
|
|
8
|
+
export const APP_PURGE_RECONCILIATION_MAX_CANDIDATES = 100;
|
|
9
|
+
export const APP_PURGE_RECONCILIATION_PREVIEW_TTL_MS = 15 * 60 * 1000;
|
|
10
|
+
export const AppPurgeReconciliationEnvironmentSchema = z.strictObject({
|
|
11
|
+
name: slug,
|
|
12
|
+
isProduction: z.boolean(),
|
|
13
|
+
createdAt: timestamp,
|
|
14
|
+
});
|
|
15
|
+
export const AppPurgeReconciliationCandidateSchema = z.strictObject({
|
|
16
|
+
org: slug,
|
|
17
|
+
app: slug,
|
|
18
|
+
anchorCreatedAt: timestamp,
|
|
19
|
+
purgeAuditId: z.string().uuid(),
|
|
20
|
+
environments: z.array(AppPurgeReconciliationEnvironmentSchema),
|
|
21
|
+
});
|
|
22
|
+
export const AppPurgeReconciliationPreviewRequestSchema = z.strictObject({
|
|
23
|
+
schemaVersion: z.literal(APP_PURGE_RECONCILIATION_SCHEMA_VERSION),
|
|
24
|
+
limit: z.number().int().min(1).max(APP_PURGE_RECONCILIATION_MAX_CANDIDATES).optional(),
|
|
25
|
+
});
|
|
26
|
+
function checkArtifactInvariants(artifact, context) {
|
|
27
|
+
if (Date.parse(artifact.expiresAt) - Date.parse(artifact.createdAt) !==
|
|
28
|
+
APP_PURGE_RECONCILIATION_PREVIEW_TTL_MS) {
|
|
29
|
+
context.addIssue({
|
|
30
|
+
code: 'custom',
|
|
31
|
+
path: ['expiresAt'],
|
|
32
|
+
message: 'expiresAt must be exactly 15 minutes after createdAt',
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (artifact.candidateCount !== artifact.candidates.length) {
|
|
36
|
+
context.addIssue({
|
|
37
|
+
code: 'custom',
|
|
38
|
+
path: ['candidateCount'],
|
|
39
|
+
message: 'candidateCount must equal candidates.length',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (artifact.candidateCount === 0 && artifact.truncated) {
|
|
43
|
+
context.addIssue({
|
|
44
|
+
code: 'custom',
|
|
45
|
+
path: ['truncated'],
|
|
46
|
+
message: 'a zero-candidate artifact cannot be truncated',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
let previousCandidateKey;
|
|
50
|
+
for (const [candidateIndex, candidate] of artifact.candidates.entries()) {
|
|
51
|
+
const candidateKey = `${candidate.org}\u0000${candidate.app}`;
|
|
52
|
+
if (previousCandidateKey !== undefined && previousCandidateKey >= candidateKey) {
|
|
53
|
+
context.addIssue({
|
|
54
|
+
code: 'custom',
|
|
55
|
+
path: ['candidates', candidateIndex],
|
|
56
|
+
message: 'candidates must be unique and lexically ordered by org and app',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
previousCandidateKey = candidateKey;
|
|
60
|
+
let previousEnvironmentName;
|
|
61
|
+
for (const [environmentIndex, environment] of candidate.environments.entries()) {
|
|
62
|
+
if (previousEnvironmentName !== undefined && previousEnvironmentName >= environment.name) {
|
|
63
|
+
context.addIssue({
|
|
64
|
+
code: 'custom',
|
|
65
|
+
path: ['candidates', candidateIndex, 'environments', environmentIndex],
|
|
66
|
+
message: 'environments must be unique and lexically ordered by name',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
previousEnvironmentName = environment.name;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const previewArtifactShape = {
|
|
74
|
+
schemaVersion: z.literal(APP_PURGE_RECONCILIATION_SCHEMA_VERSION),
|
|
75
|
+
releaseSha,
|
|
76
|
+
createdAt: timestamp,
|
|
77
|
+
expiresAt: timestamp,
|
|
78
|
+
candidateCount: z.number().int().min(0).max(APP_PURGE_RECONCILIATION_MAX_CANDIDATES),
|
|
79
|
+
truncated: z.boolean(),
|
|
80
|
+
candidates: z.array(AppPurgeReconciliationCandidateSchema),
|
|
81
|
+
checksum,
|
|
82
|
+
};
|
|
83
|
+
export const AppPurgeReconciliationPreviewArtifactSchema = z
|
|
84
|
+
.strictObject(previewArtifactShape)
|
|
85
|
+
.superRefine(checkArtifactInvariants);
|
|
86
|
+
export const AppPurgeReconciliationPreviewResponseSchema = z.strictObject({
|
|
87
|
+
ok: z.literal(true),
|
|
88
|
+
artifact: AppPurgeReconciliationPreviewArtifactSchema,
|
|
89
|
+
});
|
|
90
|
+
export const AppPurgeReconciliationApplyRequestSchema = z.strictObject({
|
|
91
|
+
schemaVersion: z.literal(APP_PURGE_RECONCILIATION_SCHEMA_VERSION),
|
|
92
|
+
preview: AppPurgeReconciliationPreviewArtifactSchema,
|
|
93
|
+
releaseSha,
|
|
94
|
+
approvalReference: z.string().trim().min(1).max(256),
|
|
95
|
+
recoveryCheckpoint: z.string().trim().min(1).max(256),
|
|
96
|
+
reason: z.string().trim().min(1).max(500),
|
|
97
|
+
idempotencyKey: z.string().min(8).max(256),
|
|
98
|
+
confirmed: z.literal(true),
|
|
99
|
+
});
|
|
100
|
+
export const AppPurgeReconciliationApplyResultSchema = z.strictObject({
|
|
101
|
+
operationId: z.string().uuid(),
|
|
102
|
+
previewChecksum: checksum,
|
|
103
|
+
releaseSha,
|
|
104
|
+
candidateCount: z.number().int().min(0).max(APP_PURGE_RECONCILIATION_MAX_CANDIDATES),
|
|
105
|
+
deletedCount: z.number().int().min(0).max(APP_PURGE_RECONCILIATION_MAX_CANDIDATES),
|
|
106
|
+
appliedAt: timestamp,
|
|
107
|
+
});
|
|
108
|
+
export const AppPurgeReconciliationApplyResponseSchema = z.strictObject({
|
|
109
|
+
ok: z.literal(true),
|
|
110
|
+
replayed: z.boolean(),
|
|
111
|
+
result: AppPurgeReconciliationApplyResultSchema,
|
|
112
|
+
});
|
|
113
|
+
const ClientEnvironmentSchema = z.object({
|
|
114
|
+
name: slug,
|
|
115
|
+
isProduction: z.boolean(),
|
|
116
|
+
createdAt: timestamp,
|
|
117
|
+
});
|
|
118
|
+
const ClientCandidateSchema = z.object({
|
|
119
|
+
org: slug,
|
|
120
|
+
app: slug,
|
|
121
|
+
anchorCreatedAt: timestamp,
|
|
122
|
+
purgeAuditId: z.string().uuid(),
|
|
123
|
+
environments: z.array(ClientEnvironmentSchema),
|
|
124
|
+
});
|
|
125
|
+
const ClientPreviewArtifactSchema = z
|
|
126
|
+
.object({
|
|
127
|
+
schemaVersion: z.literal(APP_PURGE_RECONCILIATION_SCHEMA_VERSION),
|
|
128
|
+
releaseSha,
|
|
129
|
+
createdAt: timestamp,
|
|
130
|
+
expiresAt: timestamp,
|
|
131
|
+
candidateCount: z.number().int().min(0).max(APP_PURGE_RECONCILIATION_MAX_CANDIDATES),
|
|
132
|
+
truncated: z.boolean(),
|
|
133
|
+
candidates: z.array(ClientCandidateSchema),
|
|
134
|
+
checksum,
|
|
135
|
+
})
|
|
136
|
+
.superRefine(checkArtifactInvariants);
|
|
137
|
+
const ClientPreviewResponseSchema = z.object({
|
|
138
|
+
ok: z.literal(true),
|
|
139
|
+
artifact: ClientPreviewArtifactSchema,
|
|
140
|
+
});
|
|
141
|
+
const ClientApplyResultSchema = z.object({
|
|
142
|
+
operationId: z.string().uuid(),
|
|
143
|
+
previewChecksum: checksum,
|
|
144
|
+
releaseSha,
|
|
145
|
+
candidateCount: z.number().int().min(0).max(APP_PURGE_RECONCILIATION_MAX_CANDIDATES),
|
|
146
|
+
deletedCount: z.number().int().min(0).max(APP_PURGE_RECONCILIATION_MAX_CANDIDATES),
|
|
147
|
+
appliedAt: timestamp,
|
|
148
|
+
});
|
|
149
|
+
const ClientApplyResponseSchema = z.object({
|
|
150
|
+
ok: z.literal(true),
|
|
151
|
+
replayed: z.boolean(),
|
|
152
|
+
result: ClientApplyResultSchema,
|
|
153
|
+
});
|
|
154
|
+
/** Client reader strips additive fields at every response layer while service output stays strict. */
|
|
155
|
+
export const AppPurgeReconciliationClientResponseSchema = z.union([
|
|
156
|
+
ClientPreviewResponseSchema,
|
|
157
|
+
ClientApplyResponseSchema,
|
|
158
|
+
]);
|
|
159
|
+
export const AppPurgeReconciliationErrorCodeSchema = z.enum([
|
|
160
|
+
'app_purge_preview_expired',
|
|
161
|
+
'app_purge_release_mismatch',
|
|
162
|
+
'app_purge_preview_mismatch',
|
|
163
|
+
'app_purge_candidate_drift',
|
|
164
|
+
'app_purge_idempotency_conflict',
|
|
165
|
+
'app_purge_reconciliation_unavailable',
|
|
166
|
+
]);
|
|
167
|
+
/** Exact unsigned bytes hashed for a preview checksum. */
|
|
168
|
+
export function appPurgeReconciliationChecksumPayload(unsignedArtifact) {
|
|
169
|
+
return JSON.stringify({
|
|
170
|
+
schemaVersion: unsignedArtifact.schemaVersion,
|
|
171
|
+
releaseSha: unsignedArtifact.releaseSha,
|
|
172
|
+
createdAt: unsignedArtifact.createdAt,
|
|
173
|
+
expiresAt: unsignedArtifact.expiresAt,
|
|
174
|
+
candidateCount: unsignedArtifact.candidateCount,
|
|
175
|
+
truncated: unsignedArtifact.truncated,
|
|
176
|
+
candidates: unsignedArtifact.candidates.map((candidate) => ({
|
|
177
|
+
org: candidate.org,
|
|
178
|
+
app: candidate.app,
|
|
179
|
+
anchorCreatedAt: candidate.anchorCreatedAt,
|
|
180
|
+
purgeAuditId: candidate.purgeAuditId,
|
|
181
|
+
environments: candidate.environments.map((environment) => ({
|
|
182
|
+
name: environment.name,
|
|
183
|
+
isProduction: environment.isProduction,
|
|
184
|
+
createdAt: environment.createdAt,
|
|
185
|
+
})),
|
|
186
|
+
})),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=app-purge-reconciliation.js.map
|
|
@@ -204,11 +204,11 @@ export declare const assistantWireEventSchema: z.ZodUnion<readonly [z.ZodObject<
|
|
|
204
204
|
}, z.core.$strip>, z.ZodObject<{
|
|
205
205
|
event: z.ZodLiteral<string>;
|
|
206
206
|
data: z.ZodObject<{
|
|
207
|
+
result: z.ZodJSONSchema;
|
|
207
208
|
tool: z.ZodString;
|
|
208
209
|
id: z.ZodString;
|
|
209
210
|
resourceUri: z.ZodString;
|
|
210
211
|
title: z.ZodOptional<z.ZodString>;
|
|
211
|
-
result: z.ZodJSONSchema;
|
|
212
212
|
arguments: z.ZodOptional<z.ZodJSONSchema>;
|
|
213
213
|
resourceMeta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
214
214
|
allowedOpenDomains: z.ZodOptional<z.ZodArray<z.ZodURL>>;
|