@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.
Files changed (55) hide show
  1. package/dist/commands/app-purge-reconciliation-args.d.ts +28 -0
  2. package/dist/commands/app-purge-reconciliation-args.d.ts.map +1 -0
  3. package/dist/commands/app-purge-reconciliation-args.js +124 -0
  4. package/dist/commands/app-purge-reconciliation-args.js.map +1 -0
  5. package/dist/commands/app-purge-reconciliation-files.d.ts +8 -0
  6. package/dist/commands/app-purge-reconciliation-files.d.ts.map +1 -0
  7. package/dist/commands/app-purge-reconciliation-files.js +109 -0
  8. package/dist/commands/app-purge-reconciliation-files.js.map +1 -0
  9. package/dist/commands/app-purge-reconciliation-ops.d.ts +3 -0
  10. package/dist/commands/app-purge-reconciliation-ops.d.ts.map +1 -0
  11. package/dist/commands/app-purge-reconciliation-ops.js +190 -0
  12. package/dist/commands/app-purge-reconciliation-ops.js.map +1 -0
  13. package/dist/commands/billing-ops.d.ts.map +1 -1
  14. package/dist/commands/billing-ops.js +7 -0
  15. package/dist/commands/billing-ops.js.map +1 -1
  16. package/dist/commands/billing-org-transfer-files.d.ts +12 -0
  17. package/dist/commands/billing-org-transfer-files.d.ts.map +1 -0
  18. package/dist/commands/billing-org-transfer-files.js +112 -0
  19. package/dist/commands/billing-org-transfer-files.js.map +1 -0
  20. package/dist/commands/billing-org-transfer-ops.d.ts +4 -0
  21. package/dist/commands/billing-org-transfer-ops.d.ts.map +1 -0
  22. package/dist/commands/billing-org-transfer-ops.js +534 -0
  23. package/dist/commands/billing-org-transfer-ops.js.map +1 -0
  24. package/dist/commands/service-ops.d.ts.map +1 -1
  25. package/dist/commands/service-ops.js +4 -1
  26. package/dist/commands/service-ops.js.map +1 -1
  27. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  28. package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
  29. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-account.js +96 -0
  30. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-billing.js +116 -1
  31. package/node_modules/@noodle-borg/control-plane/dist/app-purge-reconciliation-port.d.ts +25 -0
  32. package/node_modules/@noodle-borg/control-plane/dist/app-purge-reconciliation-port.js +10 -0
  33. package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +1 -0
  34. package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
  35. package/node_modules/@noodle-borg/control-plane/package.json +3 -1
  36. package/node_modules/@noodle-borg/service/dist/routes/app-purge-reconciliation.js +72 -0
  37. package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +15 -2
  38. package/node_modules/@noodle-borg/service/dist/service.js +27 -1
  39. package/node_modules/@noodle-borg/service/dist/store/in-memory.js +4 -8
  40. package/node_modules/@noodle-borg/service/dist/store/json-file.js +6 -3
  41. package/node_modules/@noodle-borg/service/dist/store/records.js +20 -0
  42. package/node_modules/@noodle-borg/service/dist/store.d.ts +3 -2
  43. package/node_modules/@noodle-borg/service/package.json +1 -1
  44. package/node_modules/@noodle-borg/wire-contracts/dist/app-purge-reconciliation.d.ts +177 -0
  45. package/node_modules/@noodle-borg/wire-contracts/dist/app-purge-reconciliation.js +189 -0
  46. package/node_modules/@noodle-borg/wire-contracts/dist/assistant.d.ts +1 -1
  47. package/node_modules/@noodle-borg/wire-contracts/dist/billing-org-transfer.d.ts +629 -0
  48. package/node_modules/@noodle-borg/wire-contracts/dist/billing-org-transfer.js +259 -0
  49. package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.d.ts +21 -0
  50. package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.js +23 -0
  51. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +2 -0
  52. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +2 -0
  53. package/node_modules/@noodle-borg/wire-contracts/dist/platform-account-reset.d.ts +4 -4
  54. package/node_modules/@noodleseed/assistant/package.json +1 -1
  55. 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
- handleWhoami(req, res, gate, controlPlane, options).catch(() => {
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 [id, record] of this.#records) {
170
- if (record.archivedAt !== undefined && record.archivedAt < before) {
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
- async sweepArchived(before) {
232
- const deleted = (await this.loadAll()).filter((record) => record.archivedAt !== undefined && record.archivedAt < before);
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 records archived strictly before `before` and returns
322
- * them so the caller can reclaim associated config and emit audit events.
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.92.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>>;