@pithy-sh/support 0.2.6 → 0.2.8

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.
@@ -1 +1 @@
1
- {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAGnE,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,0CAA0C,CAAC;AAC7F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAE3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAItE,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,kBAAkB,EAGxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAO9E,OAAO,EAAkB,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,+FAA+F;AAC/F,MAAM,MAAM,cAAc,GAAG,kBAAkB,GAAG;IAChD,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,8BAA8B;IAC9B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,EAAE,oBAAoB,CAAC;IAC/B;;;;;OAKG;IACH,YAAY,EAAE,eAAe,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IACrD;;;;;;;;OAQG;IACH,IAAI,EAAE,SAAS,CAAC;CACjB;AA4ED;;;;;GAKG;AACH,MAAM,WAAW,iBACf,SAAQ,UAAU,CAAC,eAAe,EAAE,kBAAkB,EAAE,SAAS,EAAE,OAAO,gBAAgB,CAAC;IAC3F,mCAAmC;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,wGAAwG;IACxG,UAAU,EAAE,iBAAiB,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,iBAAiB,CAmGvE;AAED,2GAA2G;AAC3G,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,IAAI,iBAAiB,CAE3F"}
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAGnE,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,0CAA0C,CAAC;AAC7F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAE3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAItE,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,kBAAkB,EAGxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAO9E,OAAO,EAAkB,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,+FAA+F;AAC/F,MAAM,MAAM,cAAc,GAAG,kBAAkB,GAAG;IAChD,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,8BAA8B;IAC9B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,EAAE,oBAAoB,CAAC;IAC/B;;;;;OAKG;IACH,YAAY,EAAE,eAAe,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IACrD;;;;;;;;OAQG;IACH,IAAI,EAAE,SAAS,CAAC;CACjB;AA4ED;;;;;GAKG;AACH,MAAM,WAAW,iBACf,SAAQ,UAAU,CAAC,eAAe,EAAE,kBAAkB,EAAE,SAAS,EAAE,OAAO,gBAAgB,CAAC;IAC3F,mCAAmC;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,wGAAwG;IACxG,UAAU,EAAE,iBAAiB,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,iBAAiB,CAsGvE;AAED,2GAA2G;AAC3G,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,IAAI,iBAAiB,CAE3F"}
@@ -12,7 +12,7 @@ import { registerSupportRoutes } from "./http/routes.js";
12
12
  import { createSupportEmailHandler } from "./inbound/handler.js";
13
13
  import { support_0001_threads } from "./migrations/0001_threads.js";
14
14
  import { supportExampleSeed } from "./seeds/example.js";
15
- import { PACKAGE_VERSION } from "./version.generated.js";
15
+ import { PACKAGE_NAME, PACKAGE_VERSION } from "./version.generated.js";
16
16
  import { noopEmit } from "@pithy-sh/core/src/audit/recorder";
17
17
  import { defineCapability } from "@pithy-sh/core/src/capability/capability";
18
18
  import { workflowBindings } from "@pithy-sh/core/src/workflow/bindings";
@@ -141,6 +141,7 @@ function support(options = {}) {
141
141
  const capability = defineCapability({
142
142
  name: "support",
143
143
  version: PACKAGE_VERSION,
144
+ package: PACKAGE_NAME,
144
145
  dependsOn: ["secrets"],
145
146
  secretRegistry: supportSecretsRegistry,
146
147
  inapplicableSecrets: inapplicableAttachmentSecrets(resolved),
@@ -1,7 +1,7 @@
1
1
  // SPDX-FileCopyrightText: 2026 Pithy
2
2
  // SPDX-License-Identifier: MIT
3
3
  import type { DeclaredEnvironments } from "@pithy-sh/core/src/naming/environment";
4
- import { type ManagedEnvironment } from "@pithy-sh/secrets/src/scope";
4
+ import { type DeprovisionTarget, type ManagedEnvironment } from "@pithy-sh/secrets/src/scope";
5
5
  /**
6
6
  * The provisioning orchestration for the support capability — the live counterpart to
7
7
  * `pithy add support`'s config wiring.
@@ -131,9 +131,17 @@ export interface SupportProvisionResult {
131
131
  export declare function provisionSupport(provisioner: SupportProvisioner, environments: DeclaredEnvironments | readonly string[]): Promise<SupportProvisionResult>;
132
132
  /** The teardown seam — the inverse of {@link SupportProvisioner}. Every step idempotent. */
133
133
  export interface SupportDeprovisioner {
134
+ /**
135
+ * Whether the env's classification worker is deployed. Read-only. What a teardown asks of every *other*
136
+ * environment before it removes the bucket or the rule they share (#591).
137
+ */
138
+ hasWorker(env: ManagedEnvironment): Promise<boolean>;
134
139
  /** Delete the env's classification worker. Idempotent. */
135
140
  deleteWorker(env: ManagedEnvironment): Promise<void>;
136
- /** Remove the inbound routing rule, so mail stops being delivered here. Idempotent. */
141
+ /**
142
+ * Remove the inbound routing rule, so mail stops being delivered here. Idempotent. Called only when the teardown
143
+ * was asked to remove it (`removeRouting`).
144
+ */
137
145
  removeRoutingRule(): Promise<{
138
146
  removed: boolean;
139
147
  }>;
@@ -145,20 +153,32 @@ export interface SupportDeprovisioner {
145
153
  }
146
154
  /** Teardown options. By default the bucket is **kept**: it holds correspondence, not cache. */
147
155
  export interface SupportDeprovisionOptions {
148
- /** Also delete the R2 bucket and its contents. Off by default. */
156
+ /** Also delete the R2 bucket and its contents. Off by default, and only with the last environment. */
149
157
  deleteStorage?: boolean;
158
+ /** Also remove the inbound routing rule. Off by default, and only with the last environment. */
159
+ removeRouting?: boolean;
160
+ }
161
+ /** What a support teardown did. */
162
+ export interface SupportDeprovisionResult {
163
+ /** The one environment torn down. */
164
+ env: ManagedEnvironment;
165
+ /** Whether this run removed the inbound routing rule. */
166
+ routingRuleRemoved: boolean;
150
167
  }
151
168
  /**
152
- * Tear down the support infrastructure, reversing {@link provisionSupport}.
169
+ * Tear down **one named environment's** support infrastructure, reversing {@link provisionSupport} for it.
153
170
  *
154
- * The routing rule goes **first**, and that ordering is the whole point: stop new mail arriving before
155
- * removing the workers that would have handled it, or messages land in a Worker with no classification
156
- * host during the teardown. Stored correspondence is preserved unless explicitly requested — losing an
157
- * adopter's support history to a teardown flag would be unrecoverable.
171
+ * In order, and the order is the contract:
158
172
  *
159
- * `environments` is the project's declaration from the root `pithy.config.ts` (#241). Every declared
160
- * environment is provisioned; an environment this skipped would be one the project deploys to with no
161
- * resources behind it — the silence the closed `ManagedEnvironment` enum used to produce.
173
+ * 1. Resolve the target (`@pithy-sh/secrets`' `deprovisionTarget`) — refused with nothing read when none was
174
+ * named, or one the project does not declare. This used to walk every declared environment, so a run meant
175
+ * for staging took production's classification worker with it (#591).
176
+ * 2. With `removeRouting` or `deleteStorage`: refuse while any other declared environment still runs a
177
+ * classification worker (`assertSharedLeavesLast`). The rule and the bucket are one per project — the bucket
178
+ * is every environment's support history — so they go with the last environment, never a staging teardown.
179
+ * 3. Remove the routing rule, when asked, **first**: stop new mail arriving before removing the worker that would
180
+ * have handled it.
181
+ * 4. Delete the environment's worker, then — only when asked — the bucket and everything in it.
162
182
  */
163
- export declare function deprovisionSupport(deprovisioner: SupportDeprovisioner, environments: DeclaredEnvironments | readonly string[], options?: SupportDeprovisionOptions): Promise<void>;
183
+ export declare function deprovisionSupport(deprovisioner: SupportDeprovisioner, target: DeprovisionTarget, options?: SupportDeprovisionOptions): Promise<SupportDeprovisionResult>;
164
184
  //# sourceMappingURL=provisionSupport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"provisionSupport.d.ts","sourceRoot":"","sources":["../../src/provision/provisionSupport.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAIlF,OAAO,EAAE,KAAK,kBAAkB,EAAuB,MAAM,6BAA6B,CAAC;AAG3F;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,GAAG,MAAM,CAElF;AAED,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B;;;;;OAKG;IACH,YAAY,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAChF,sEAAsE;IACtE,YAAY,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD;;;;;;;;OAQG;IACH,iBAAiB,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC5F;;;OAGG;IACH,iBAAiB,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACtE;AAED,kCAAkC;AAClC,MAAM,WAAW,sBAAsB;IACrC,uDAAuD;IACvD,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/D,iEAAiE;IACjE,YAAY,EAAE,kBAAkB,EAAE,CAAC;IACnC,4FAA4F;IAC5F,MAAM,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,kBAAkB,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC/E,iFAAiF;IACjF,OAAO,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;CACjD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,kBAAkB,EAC/B,YAAY,EAAE,oBAAoB,GAAG,SAAS,MAAM,EAAE,GACrD,OAAO,CAAC,sBAAsB,CAAC,CAiBjC;AAED,4FAA4F;AAC5F,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,YAAY,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,uFAAuF;IACvF,iBAAiB,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACnD;;;OAGG;IACH,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,+FAA+F;AAC/F,MAAM,WAAW,yBAAyB;IACxC,kEAAkE;IAClE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACtC,aAAa,EAAE,oBAAoB,EACnC,YAAY,EAAE,oBAAoB,GAAG,SAAS,MAAM,EAAE,EACtD,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,IAAI,CAAC,CAMf"}
1
+ {"version":3,"file":"provisionSupport.d.ts","sourceRoot":"","sources":["../../src/provision/provisionSupport.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@ import { SUPPORT_CAPABILITY } from "../workflows/specs.js";
4
4
  import { GLOBAL_SCOPE } from "@pithy-sh/core/src/naming/environment";
5
5
  import { resourceName } from "@pithy-sh/core/src/naming/resource";
6
6
  import { resourceNames } from "@pithy-sh/core/src/naming/resourceNames";
7
- import { managedEnvironments } from "@pithy-sh/secrets/src/scope";
7
+ import { assertSharedLeavesLast, deprovisionTarget, managedEnvironments } from "@pithy-sh/secrets/src/scope";
8
8
  //#region src/provision/provisionSupport.ts
9
9
  /**
10
10
  * The provisioning orchestration for the support capability — the live counterpart to
@@ -99,21 +99,36 @@ async function provisionSupport(provisioner, environments) {
99
99
  };
100
100
  }
101
101
  /**
102
- * Tear down the support infrastructure, reversing {@link provisionSupport}.
102
+ * Tear down **one named environment's** support infrastructure, reversing {@link provisionSupport} for it.
103
103
  *
104
- * The routing rule goes **first**, and that ordering is the whole point: stop new mail arriving before
105
- * removing the workers that would have handled it, or messages land in a Worker with no classification
106
- * host during the teardown. Stored correspondence is preserved unless explicitly requested — losing an
107
- * adopter's support history to a teardown flag would be unrecoverable.
104
+ * In order, and the order is the contract:
108
105
  *
109
- * `environments` is the project's declaration from the root `pithy.config.ts` (#241). Every declared
110
- * environment is provisioned; an environment this skipped would be one the project deploys to with no
111
- * resources behind it — the silence the closed `ManagedEnvironment` enum used to produce.
106
+ * 1. Resolve the target (`@pithy-sh/secrets`' `deprovisionTarget`) — refused with nothing read when none was
107
+ * named, or one the project does not declare. This used to walk every declared environment, so a run meant
108
+ * for staging took production's classification worker with it (#591).
109
+ * 2. With `removeRouting` or `deleteStorage`: refuse while any other declared environment still runs a
110
+ * classification worker (`assertSharedLeavesLast`). The rule and the bucket are one per project — the bucket
111
+ * is every environment's support history — so they go with the last environment, never a staging teardown.
112
+ * 3. Remove the routing rule, when asked, **first**: stop new mail arriving before removing the worker that would
113
+ * have handled it.
114
+ * 4. Delete the environment's worker, then — only when asked — the bucket and everything in it.
112
115
  */
113
- async function deprovisionSupport(deprovisioner, environments, options = {}) {
114
- await deprovisioner.removeRoutingRule();
115
- for (const env of managedEnvironments(environments)) await deprovisioner.deleteWorker(env);
116
+ async function deprovisionSupport(deprovisioner, target, options = {}) {
117
+ const env = deprovisionTarget(target);
118
+ await assertSharedLeavesLast(env, target.declared, (other) => deprovisioner.hasWorker(other), [...options.deleteStorage ? [{
119
+ what: "the support bucket",
120
+ flag: "--storage"
121
+ }] : [], ...options.removeRouting ? [{
122
+ what: "the inbound routing rule",
123
+ flag: "--routing-zone"
124
+ }] : []]);
125
+ const routingRuleRemoved = options.removeRouting ? (await deprovisioner.removeRoutingRule()).removed : false;
126
+ await deprovisioner.deleteWorker(env);
116
127
  if (options.deleteStorage) await deprovisioner.deleteBucket();
128
+ return {
129
+ env,
130
+ routingRuleRemoved
131
+ };
117
132
  }
118
133
  //#endregion
119
134
  export { deprovisionSupport, provisionSupport, supportRoutingRuleName, supportWorkerName };
@@ -3,5 +3,5 @@
3
3
  /** This package's npm name — the join key against a release feed. */
4
4
  export declare const PACKAGE_NAME = "@pithy-sh/support";
5
5
  /** This package's version, stamped from its own package.json at generation time. */
6
- export declare const PACKAGE_VERSION = "0.2.6";
6
+ export declare const PACKAGE_VERSION = "0.2.8";
7
7
  //# sourceMappingURL=version.generated.d.ts.map
@@ -4,6 +4,6 @@
4
4
  /** This package's npm name — the join key against a release feed. */
5
5
  const PACKAGE_NAME = "@pithy-sh/support";
6
6
  /** This package's version, stamped from its own package.json at generation time. */
7
- const PACKAGE_VERSION = "0.2.6";
7
+ const PACKAGE_VERSION = "0.2.8";
8
8
  //#endregion
9
9
  export { PACKAGE_NAME, PACKAGE_VERSION };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pithy-sh/support",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -40,12 +40,12 @@
40
40
  "tldts": "^7.4.10"
41
41
  },
42
42
  "peerDependencies": {
43
- "@pithy-sh/auth": "^0.6.1",
44
- "@pithy-sh/core": "^0.6.1",
45
- "@pithy-sh/email": "^0.3.3",
46
- "@pithy-sh/payments": "^0.3.3",
47
- "@pithy-sh/secrets": "^0.2.3",
48
- "@pithy-sh/storage": "^0.2.3",
43
+ "@pithy-sh/auth": "^0.6.3",
44
+ "@pithy-sh/core": "^0.7.3",
45
+ "@pithy-sh/email": "^0.3.7",
46
+ "@pithy-sh/payments": "^0.3.5",
47
+ "@pithy-sh/secrets": "^0.2.6",
48
+ "@pithy-sh/storage": "^0.2.6",
49
49
  "hono": "^4.13.2",
50
50
  "kysely": "^0.29.0",
51
51
  "zod": "^4.4.0"
package/src/capability.ts CHANGED
@@ -28,7 +28,7 @@ import { support_0001_threads } from "./migrations/0001_threads";
28
28
  import { resolveReplies, type SupportReplySnippets } from "./reply/snippets";
29
29
  import { inapplicableAttachmentSecrets, SUPPORT_BUCKET_BINDING, supportSecretsRegistry } from "./secret/registry";
30
30
  import { supportExampleSeed } from "./seeds/example";
31
- import { PACKAGE_VERSION } from "./version.generated";
31
+ import { PACKAGE_NAME, PACKAGE_VERSION } from "./version.generated";
32
32
  import { supportWorkflows } from "./workflows/specs";
33
33
 
34
34
  /**
@@ -235,9 +235,12 @@ export function support(options: SupportOptions = {}): SupportCapability {
235
235
 
236
236
  const capability = defineCapability({
237
237
  name: "support",
238
- // The package version this capability ships at, stamped by `scripts/stampVersions.ts` — a Worker
239
- // cannot read its own package.json. Reported per capability by the control-plane manifest.
238
+ // The package this capability ships in and the version it ships at, both stamped by
239
+ // `scripts/stampVersions.ts` — a Worker cannot read its own package.json. Reported per capability by
240
+ // the control-plane manifest, and reported together: a release feed is keyed by package name, so the
241
+ // version alone leaves a client guessing the key (#626).
240
242
  version: PACKAGE_VERSION,
243
+ package: PACKAGE_NAME,
241
244
  // Attachment presigning reads an R2 credential bundle through @pithy-sh/secrets.
242
245
  dependsOn: ["secrets"],
243
246
  secretRegistry: supportSecretsRegistry,
@@ -5,7 +5,13 @@ import type { DeclaredEnvironments } from "@pithy-sh/core/src/naming/environment
5
5
  import { GLOBAL_SCOPE } from "@pithy-sh/core/src/naming/environment";
6
6
  import { resourceName } from "@pithy-sh/core/src/naming/resource";
7
7
  import { resourceNames } from "@pithy-sh/core/src/naming/resourceNames";
8
- import { type ManagedEnvironment, managedEnvironments } from "@pithy-sh/secrets/src/scope";
8
+ import {
9
+ assertSharedLeavesLast,
10
+ type DeprovisionTarget,
11
+ deprovisionTarget,
12
+ type ManagedEnvironment,
13
+ managedEnvironments,
14
+ } from "@pithy-sh/secrets/src/scope";
9
15
  import { SUPPORT_CAPABILITY } from "../workflows/specs";
10
16
 
11
17
  /**
@@ -146,9 +152,17 @@ export async function provisionSupport(
146
152
 
147
153
  /** The teardown seam — the inverse of {@link SupportProvisioner}. Every step idempotent. */
148
154
  export interface SupportDeprovisioner {
155
+ /**
156
+ * Whether the env's classification worker is deployed. Read-only. What a teardown asks of every *other*
157
+ * environment before it removes the bucket or the rule they share (#591).
158
+ */
159
+ hasWorker(env: ManagedEnvironment): Promise<boolean>;
149
160
  /** Delete the env's classification worker. Idempotent. */
150
161
  deleteWorker(env: ManagedEnvironment): Promise<void>;
151
- /** Remove the inbound routing rule, so mail stops being delivered here. Idempotent. */
162
+ /**
163
+ * Remove the inbound routing rule, so mail stops being delivered here. Idempotent. Called only when the teardown
164
+ * was asked to remove it (`removeRouting`).
165
+ */
152
166
  removeRoutingRule(): Promise<{ removed: boolean }>;
153
167
  /**
154
168
  * Delete the R2 bucket **and everything in it** — every attachment and every raw message an adopter's
@@ -159,30 +173,47 @@ export interface SupportDeprovisioner {
159
173
 
160
174
  /** Teardown options. By default the bucket is **kept**: it holds correspondence, not cache. */
161
175
  export interface SupportDeprovisionOptions {
162
- /** Also delete the R2 bucket and its contents. Off by default. */
176
+ /** Also delete the R2 bucket and its contents. Off by default, and only with the last environment. */
163
177
  deleteStorage?: boolean;
178
+ /** Also remove the inbound routing rule. Off by default, and only with the last environment. */
179
+ removeRouting?: boolean;
180
+ }
181
+
182
+ /** What a support teardown did. */
183
+ export interface SupportDeprovisionResult {
184
+ /** The one environment torn down. */
185
+ env: ManagedEnvironment;
186
+ /** Whether this run removed the inbound routing rule. */
187
+ routingRuleRemoved: boolean;
164
188
  }
165
189
 
166
190
  /**
167
- * Tear down the support infrastructure, reversing {@link provisionSupport}.
191
+ * Tear down **one named environment's** support infrastructure, reversing {@link provisionSupport} for it.
168
192
  *
169
- * The routing rule goes **first**, and that ordering is the whole point: stop new mail arriving before
170
- * removing the workers that would have handled it, or messages land in a Worker with no classification
171
- * host during the teardown. Stored correspondence is preserved unless explicitly requested — losing an
172
- * adopter's support history to a teardown flag would be unrecoverable.
193
+ * In order, and the order is the contract:
173
194
  *
174
- * `environments` is the project's declaration from the root `pithy.config.ts` (#241). Every declared
175
- * environment is provisioned; an environment this skipped would be one the project deploys to with no
176
- * resources behind it — the silence the closed `ManagedEnvironment` enum used to produce.
195
+ * 1. Resolve the target (`@pithy-sh/secrets`' `deprovisionTarget`) — refused with nothing read when none was
196
+ * named, or one the project does not declare. This used to walk every declared environment, so a run meant
197
+ * for staging took production's classification worker with it (#591).
198
+ * 2. With `removeRouting` or `deleteStorage`: refuse while any other declared environment still runs a
199
+ * classification worker (`assertSharedLeavesLast`). The rule and the bucket are one per project — the bucket
200
+ * is every environment's support history — so they go with the last environment, never a staging teardown.
201
+ * 3. Remove the routing rule, when asked, **first**: stop new mail arriving before removing the worker that would
202
+ * have handled it.
203
+ * 4. Delete the environment's worker, then — only when asked — the bucket and everything in it.
177
204
  */
178
205
  export async function deprovisionSupport(
179
206
  deprovisioner: SupportDeprovisioner,
180
- environments: DeclaredEnvironments | readonly string[],
207
+ target: DeprovisionTarget,
181
208
  options: SupportDeprovisionOptions = {},
182
- ): Promise<void> {
183
- await deprovisioner.removeRoutingRule();
184
- for (const env of managedEnvironments(environments)) {
185
- await deprovisioner.deleteWorker(env);
186
- }
209
+ ): Promise<SupportDeprovisionResult> {
210
+ const env = deprovisionTarget(target);
211
+ await assertSharedLeavesLast(env, target.declared, (other) => deprovisioner.hasWorker(other), [
212
+ ...(options.deleteStorage ? [{ what: "the support bucket", flag: "--storage" }] : []),
213
+ ...(options.removeRouting ? [{ what: "the inbound routing rule", flag: "--routing-zone" }] : []),
214
+ ]);
215
+ const routingRuleRemoved = options.removeRouting ? (await deprovisioner.removeRoutingRule()).removed : false;
216
+ await deprovisioner.deleteWorker(env);
187
217
  if (options.deleteStorage) await deprovisioner.deleteBucket();
218
+ return { env, routingRuleRemoved };
188
219
  }
@@ -13,4 +13,4 @@
13
13
  export const PACKAGE_NAME = "@pithy-sh/support";
14
14
 
15
15
  /** This package's version, stamped from its own package.json at generation time. */
16
- export const PACKAGE_VERSION = "0.2.6";
16
+ export const PACKAGE_VERSION = "0.2.8";