@noodleseed/one 0.168.0 → 0.169.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 (38) hide show
  1. package/dist/commands/deployment-delete-ops.d.ts +12 -0
  2. package/dist/commands/deployment-delete-ops.d.ts.map +1 -0
  3. package/dist/commands/deployment-delete-ops.js +230 -0
  4. package/dist/commands/deployment-delete-ops.js.map +1 -0
  5. package/dist/commands/deployment-delete-recovery.d.ts +35 -0
  6. package/dist/commands/deployment-delete-recovery.d.ts.map +1 -0
  7. package/dist/commands/deployment-delete-recovery.js +99 -0
  8. package/dist/commands/deployment-delete-recovery.js.map +1 -0
  9. package/dist/commands/deployments-ops.d.ts +2 -1
  10. package/dist/commands/deployments-ops.d.ts.map +1 -1
  11. package/dist/commands/deployments-ops.js +5 -1
  12. package/dist/commands/deployments-ops.js.map +1 -1
  13. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-tenant-resources.js +27 -1
  14. package/node_modules/@noodle-borg/control-plane/dist/delete-deployment.d.ts +44 -0
  15. package/node_modules/@noodle-borg/control-plane/dist/delete-deployment.js +105 -0
  16. package/node_modules/@noodle-borg/control-plane/dist/deployment-deletion-contracts.d.ts +20 -0
  17. package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +2 -0
  18. package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
  19. package/node_modules/@noodle-borg/service/dist/deployment-deletion.js +40 -0
  20. package/node_modules/@noodle-borg/service/dist/registry-compile.js +6 -0
  21. package/node_modules/@noodle-borg/service/dist/registry-deletion.js +75 -0
  22. package/node_modules/@noodle-borg/service/dist/registry-state.js +4 -2
  23. package/node_modules/@noodle-borg/service/dist/registry-targets.js +4 -0
  24. package/node_modules/@noodle-borg/service/dist/registry.d.ts +2 -0
  25. package/node_modules/@noodle-borg/service/dist/registry.js +7 -23
  26. package/node_modules/@noodle-borg/service/dist/routes/deploy-dispatch.js +15 -1
  27. package/node_modules/@noodle-borg/service/dist/routes/deployment-delete.js +53 -0
  28. package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +2 -1
  29. package/node_modules/@noodle-borg/service/dist/store/in-memory.js +8 -0
  30. package/node_modules/@noodle-borg/service/dist/store/json-file-deletion.js +64 -0
  31. package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +2 -1
  32. package/node_modules/@noodle-borg/service/dist/store/json-file.js +23 -5
  33. package/node_modules/@noodle-borg/service/dist/store.d.ts +5 -1
  34. package/node_modules/@noodle-borg/wire-contracts/dist/deployment-deletion.d.ts +29 -0
  35. package/node_modules/@noodle-borg/wire-contracts/dist/deployment-deletion.js +33 -0
  36. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +1 -0
  37. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +1 -0
  38. package/package.json +1 -1
@@ -5,9 +5,11 @@ import { normalizeServerVersion } from '@noodle-borg/module';
5
5
  import { assertSameAppPackageSnapshot, sanitizeDeployRecordAppPackageSnapshot, } from '../app-package-snapshot.js';
6
6
  import { assertCustomerAuthRestorePrecondition, assertUniqueActiveCustomerAuthAudienceBindings, findActiveCustomerAuthAudienceConflict, requiresCustomerAuthProjection, } from '../customer-auth-audience-binding.js';
7
7
  import { matchesDeploymentActivation } from '../deployment-activation-precondition.js';
8
+ import { planDeploymentDeletion } from '../deployment-deletion.js';
8
9
  import { assertDeploymentActivationUnlocked, assertDeploymentAppendUnlocked, } from '../deployment-lock.js';
9
10
  import { assertDeploymentAppendPolicy, assertDeploymentAppendVersion, } from '../deployment-record-version.js';
10
11
  import { defaultActiveRecord, sameDeploymentScope, sameTenantRecord, } from '../deployment-versioning.js';
12
+ import { commitDeletedDeploymentIds, readDeletedDeploymentIds, serializeFileLifecycle, } from './json-file-deletion.js';
11
13
  import { appArchivedAt, deploymentSummary, matchesDeploymentFilter, paginateAppSummaries, planAppArchive, planAppRestore, planArchivedAppSweep, preserveDeploymentOwnerState, resolveActiveAccessUpdate, resolveProductionEnvironment, summarizeApps, summarizeEnvs, validateDeploymentListFilter, withoutArchiveStamp, } from './records.js';
12
14
  import { DEPLOYMENT_ID_PATTERN, validateSlug, validateTenantRef } from './validate.js';
13
15
  /**
@@ -19,11 +21,23 @@ import { DEPLOYMENT_ID_PATTERN, validateSlug, validateTenantRef } from './valida
19
21
  export class JsonFileArtifactStore {
20
22
  #dir;
21
23
  #environmentMetadataDir;
22
- #lifecycleTail = Promise.resolve();
24
+ #deletionMetadataDir;
23
25
  constructor(dataDir) {
24
26
  this.#dir = join(dataDir, 'deployments');
27
+ this.#deletionMetadataDir = join(dataDir, 'deployment-metadata');
25
28
  this.#environmentMetadataDir = join(dataDir, 'environment-metadata');
26
29
  }
30
+ deleteDeployments(ref, selection) {
31
+ return this.#serializeLifecycle(async () => {
32
+ const result = planDeploymentDeletion(await this.loadAll(), ref, selection);
33
+ if (!result.ok)
34
+ return result;
35
+ await commitDeletedDeploymentIds(this.#deletionMetadataDir, result.deleted.map((record) => record.deploymentId));
36
+ // Logical deletion has committed. Failed physical cleanup remains hidden by the journal on restart.
37
+ await Promise.all(result.deleted.map((record) => rm(join(this.#dir, `${record.deploymentId}.json`), { force: true }).catch(() => undefined)));
38
+ return result;
39
+ });
40
+ }
27
41
  append(record, precondition) {
28
42
  return this.#serializeLifecycle(() => this.#appendUnlocked(record, precondition));
29
43
  }
@@ -35,6 +49,9 @@ export class JsonFileArtifactStore {
35
49
  if (!DEPLOYMENT_ID_PATTERN.test(record.deploymentId)) {
36
50
  throw new Error(`invalid deploymentId for persistence: "${record.deploymentId}"`);
37
51
  }
52
+ if ((await readDeletedDeploymentIds(this.#deletionMetadataDir)).has(record.deploymentId)) {
53
+ throw new Error('Deployment ID has been deleted');
54
+ }
38
55
  const records = await this.loadAll();
39
56
  assertDeploymentAppendVersion(records, record);
40
57
  assertDeploymentAppendPolicy(records, record, precondition);
@@ -63,6 +80,8 @@ export class JsonFileArtifactStore {
63
80
  // An invalid id can never be a file we wrote; treat as absent (also closes off any traversal).
64
81
  if (!DEPLOYMENT_ID_PATTERN.test(deploymentId))
65
82
  return undefined;
83
+ if ((await readDeletedDeploymentIds(this.#deletionMetadataDir)).has(deploymentId))
84
+ return undefined;
66
85
  try {
67
86
  const text = await readFile(join(this.#dir, `${deploymentId}.json`), 'utf8');
68
87
  return sanitizeDeployRecordAppPackageSnapshot(JSON.parse(text));
@@ -172,9 +191,7 @@ export class JsonFileArtifactStore {
172
191
  });
173
192
  }
174
193
  #serializeLifecycle(operation) {
175
- const result = this.#lifecycleTail.then(() => operation(), () => operation());
176
- this.#lifecycleTail = result.then(() => undefined, () => undefined);
177
- return result;
194
+ return serializeFileLifecycle(this.#dir, operation);
178
195
  }
179
196
  async loadAll() {
180
197
  let names;
@@ -199,7 +216,8 @@ export class JsonFileArtifactStore {
199
216
  // other servers recover. (Visibility of skipped files lands with structured logging, Slice 27.)
200
217
  }
201
218
  }
202
- return records;
219
+ const deleted = await readDeletedDeploymentIds(this.#deletionMetadataDir);
220
+ return records.filter((record) => !deleted.has(record.deploymentId));
203
221
  }
204
222
  async listDeployments(filter) {
205
223
  const safe = validateDeploymentListFilter(filter);
@@ -1,5 +1,6 @@
1
1
  import type { ArtifactCustomerAuthRouting, HostedPackagedAsset } from '@noodle-borg/compiler';
2
- import type { OrganizationStore } from '@noodle-borg/control-plane/portable';
2
+ import type { DeploymentDeleteSelection, OrganizationStore, DeploymentDeleteResult as PortableDeploymentDeleteResult } from '@noodle-borg/control-plane/portable';
3
+ export type { DeploymentDeleteSelection } from '@noodle-borg/control-plane/portable';
3
4
  import type { OrgMembershipSource } from '@noodle-borg/module';
4
5
  import type { SealedSecret } from '@noodle-borg/runtime';
5
6
  import type { AccessMode } from '@noodle-borg/transport-http';
@@ -76,6 +77,7 @@ export interface DeployRecord {
76
77
  /** Immutable rendered package bytes bound to this exact deployment, when the manifest has a guide. */
77
78
  readonly appPackageSnapshot?: AppPackageSnapshotV1;
78
79
  }
80
+ export type DeploymentDeleteResult = PortableDeploymentDeleteResult<DeployRecord>;
79
81
  export interface DeploymentLockMetadata {
80
82
  /** ISO-8601 time the version pointer was locked. */
81
83
  readonly lockedAt: string;
@@ -302,6 +304,8 @@ export interface ControlPlaneStore extends OrganizationStore {
302
304
  * an earlier deployment without changing the tenant-facing URL.
303
305
  */
304
306
  export interface ArtifactStore {
307
+ /** Atomically remove exact deployment history or an inventory-guarded whole version. */
308
+ deleteDeployments(ref: TenantRef, selection: DeploymentDeleteSelection): Promise<DeploymentDeleteResult>;
305
309
  append(record: DeployRecord, precondition?: DeploymentPolicyPrecondition): Promise<void>;
306
310
  loadAll(): Promise<readonly DeployRecord[]>;
307
311
  /**
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+ /** Bind destructive version deletion to the complete inventory the operator confirmed. */
3
+ export declare const deploymentVersionDeleteRequestSchema: z.ZodObject<{
4
+ expectedDeploymentIds: z.ZodArray<z.ZodString>;
5
+ }, z.core.$strict>;
6
+ export type DeploymentVersionDeleteRequest = z.infer<typeof deploymentVersionDeleteRequestSchema>;
7
+ export declare const deploymentDeleteResponseSchema: z.ZodObject<{
8
+ target: z.ZodObject<{
9
+ org: z.ZodString;
10
+ app: z.ZodString;
11
+ env: z.ZodString;
12
+ }, z.core.$strict>;
13
+ ok: z.ZodLiteral<true>;
14
+ deletedDeploymentIds: z.ZodArray<z.ZodString>;
15
+ auditRecorded: z.ZodBoolean;
16
+ }, z.core.$strict>;
17
+ /** Additive response reader for clients that can outlive a service revision. */
18
+ export declare const deploymentDeleteClientResponseSchema: z.ZodObject<{
19
+ target: z.ZodObject<{
20
+ org: z.ZodString;
21
+ app: z.ZodString;
22
+ env: z.ZodString;
23
+ }, z.core.$strip>;
24
+ ok: z.ZodLiteral<true>;
25
+ deletedDeploymentIds: z.ZodArray<z.ZodString>;
26
+ auditRecorded: z.ZodBoolean;
27
+ }, z.core.$strip>;
28
+ export type DeploymentDeleteResponse = z.infer<typeof deploymentDeleteResponseSchema>;
29
+ //# sourceMappingURL=deployment-deletion.d.ts.map
@@ -0,0 +1,33 @@
1
+ import { z } from 'zod';
2
+ const deploymentId = z
3
+ .string()
4
+ .min(1)
5
+ .max(256)
6
+ .regex(/^[a-z0-9-]+$/);
7
+ const ids = z
8
+ .array(deploymentId)
9
+ .min(1)
10
+ .max(10000)
11
+ .refine((values) => new Set(values).size === values.length, 'deployment IDs must be unique');
12
+ const target = {
13
+ org: z.string().min(1),
14
+ app: z.string().min(1),
15
+ env: z.string().min(1),
16
+ };
17
+ /** Bind destructive version deletion to the complete inventory the operator confirmed. */
18
+ export const deploymentVersionDeleteRequestSchema = z.strictObject({ expectedDeploymentIds: ids });
19
+ const result = {
20
+ ok: z.literal(true),
21
+ deletedDeploymentIds: ids,
22
+ auditRecorded: z.boolean(),
23
+ };
24
+ export const deploymentDeleteResponseSchema = z.strictObject({
25
+ ...result,
26
+ target: z.strictObject(target),
27
+ });
28
+ /** Additive response reader for clients that can outlive a service revision. */
29
+ export const deploymentDeleteClientResponseSchema = z.object({
30
+ ...result,
31
+ target: z.object(target),
32
+ });
33
+ //# sourceMappingURL=deployment-deletion.js.map
@@ -389,6 +389,7 @@ export * from './application-activity.js';
389
389
  export * from './application-connections.js';
390
390
  export * from './application-onboarding.js';
391
391
  export * from './billing-catalog.js';
392
+ export * from './deployment-deletion.js';
392
393
  export * from './operation-coordination.js';
393
394
  export { MIXED_CUSTOMER_AUTH_FEATURE_VERSION, type ServiceInfoClientResponse, type ServiceInfoResponse, serviceInfoClientResponseSchema, serviceInfoResponseSchema, } from './service-info.js';
394
395
  //# sourceMappingURL=index.d.ts.map
@@ -228,6 +228,7 @@ export * from './application-activity.js';
228
228
  export * from './application-connections.js';
229
229
  export * from './application-onboarding.js';
230
230
  export * from './billing-catalog.js';
231
+ export * from './deployment-deletion.js';
231
232
  export * from './operation-coordination.js';
232
233
  export { MIXED_CUSTOMER_AUTH_FEATURE_VERSION, serviceInfoClientResponseSchema, serviceInfoResponseSchema, } from './service-info.js';
233
234
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noodleseed/one",
3
- "version": "0.168.0",
3
+ "version": "0.169.0",
4
4
  "private": false,
5
5
  "description": "Noodle CLI by Noodle Seed — author, run, and deploy declarative MCP servers. Embedding the assistant in your own web app is @noodleseed/assistant.",
6
6
  "license": "Apache-2.0",