@open-everywhere/provider-slack 0.1.2 → 0.1.4

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 (95) hide show
  1. package/agent/access/slack-attachment-fetch.ts +3 -1
  2. package/agent/app-manifest.ts +48 -10
  3. package/agent/channels/slack-commands.ts +5 -1
  4. package/agent/channels/slack-events.ts +13 -4
  5. package/agent/channels/slack-front.ts +21 -6
  6. package/agent/channels/slack-read.ts +9 -3
  7. package/agent/channels/slack-setup-page.ts +20 -0
  8. package/agent/channels/slack-web-api.ts +34 -7
  9. package/agent/channels/slack.ts +11 -3
  10. package/agent/contributions.ts +1 -0
  11. package/agent/docs-generated.ts +1 -1
  12. package/agent/ops.ts +29 -61
  13. package/agent/register.ts +27 -1
  14. package/agent/setup/claim.ts +343 -0
  15. package/agent/setup/credentials.ts +265 -0
  16. package/agent/setup/eve-credentials.ts +77 -0
  17. package/agent/setup/install-state.ts +248 -0
  18. package/agent/setup/page.ts +579 -0
  19. package/agent/setup/slack-api-setup.ts +176 -0
  20. package/agent/status.ts +81 -0
  21. package/dist/agent/access/slack-attachment-fetch.d.ts.map +1 -1
  22. package/dist/agent/access/slack-attachment-fetch.js +3 -1
  23. package/dist/agent/access/slack-attachment-fetch.js.map +1 -1
  24. package/dist/agent/app-manifest.d.ts +4 -4
  25. package/dist/agent/app-manifest.d.ts.map +1 -1
  26. package/dist/agent/app-manifest.js +48 -10
  27. package/dist/agent/app-manifest.js.map +1 -1
  28. package/dist/agent/channels/slack-commands.d.ts.map +1 -1
  29. package/dist/agent/channels/slack-commands.js +5 -1
  30. package/dist/agent/channels/slack-commands.js.map +1 -1
  31. package/dist/agent/channels/slack-events.d.ts +1 -1
  32. package/dist/agent/channels/slack-events.d.ts.map +1 -1
  33. package/dist/agent/channels/slack-events.js +13 -4
  34. package/dist/agent/channels/slack-events.js.map +1 -1
  35. package/dist/agent/channels/slack-front.d.ts +1 -1
  36. package/dist/agent/channels/slack-front.d.ts.map +1 -1
  37. package/dist/agent/channels/slack-front.js +20 -5
  38. package/dist/agent/channels/slack-front.js.map +1 -1
  39. package/dist/agent/channels/slack-read.d.ts +6 -1
  40. package/dist/agent/channels/slack-read.d.ts.map +1 -1
  41. package/dist/agent/channels/slack-read.js +9 -3
  42. package/dist/agent/channels/slack-read.js.map +1 -1
  43. package/dist/agent/channels/slack-setup-page.d.ts +3 -0
  44. package/dist/agent/channels/slack-setup-page.d.ts.map +1 -0
  45. package/dist/agent/channels/slack-setup-page.js +13 -0
  46. package/dist/agent/channels/slack-setup-page.js.map +1 -0
  47. package/dist/agent/channels/slack-web-api.d.ts +3 -0
  48. package/dist/agent/channels/slack-web-api.d.ts.map +1 -1
  49. package/dist/agent/channels/slack-web-api.js +26 -6
  50. package/dist/agent/channels/slack-web-api.js.map +1 -1
  51. package/dist/agent/channels/slack.d.ts.map +1 -1
  52. package/dist/agent/channels/slack.js +11 -3
  53. package/dist/agent/channels/slack.js.map +1 -1
  54. package/dist/agent/contributions.d.ts +5 -0
  55. package/dist/agent/contributions.d.ts.map +1 -1
  56. package/dist/agent/contributions.js +1 -0
  57. package/dist/agent/contributions.js.map +1 -1
  58. package/dist/agent/docs-generated.js +1 -1
  59. package/dist/agent/docs-generated.js.map +1 -1
  60. package/dist/agent/ops.d.ts +1 -1
  61. package/dist/agent/ops.d.ts.map +1 -1
  62. package/dist/agent/ops.js +29 -47
  63. package/dist/agent/ops.js.map +1 -1
  64. package/dist/agent/register.d.ts.map +1 -1
  65. package/dist/agent/register.js +26 -2
  66. package/dist/agent/register.js.map +1 -1
  67. package/dist/agent/setup/claim.d.ts +73 -0
  68. package/dist/agent/setup/claim.d.ts.map +1 -0
  69. package/dist/agent/setup/claim.js +298 -0
  70. package/dist/agent/setup/claim.js.map +1 -0
  71. package/dist/agent/setup/credentials.d.ts +56 -0
  72. package/dist/agent/setup/credentials.d.ts.map +1 -0
  73. package/dist/agent/setup/credentials.js +214 -0
  74. package/dist/agent/setup/credentials.js.map +1 -0
  75. package/dist/agent/setup/eve-credentials.d.ts +22 -0
  76. package/dist/agent/setup/eve-credentials.d.ts.map +1 -0
  77. package/dist/agent/setup/eve-credentials.js +73 -0
  78. package/dist/agent/setup/eve-credentials.js.map +1 -0
  79. package/dist/agent/setup/install-state.d.ts +139 -0
  80. package/dist/agent/setup/install-state.d.ts.map +1 -0
  81. package/dist/agent/setup/install-state.js +196 -0
  82. package/dist/agent/setup/install-state.js.map +1 -0
  83. package/dist/agent/setup/page.d.ts +29 -0
  84. package/dist/agent/setup/page.d.ts.map +1 -0
  85. package/dist/agent/setup/page.js +460 -0
  86. package/dist/agent/setup/page.js.map +1 -0
  87. package/dist/agent/setup/slack-api-setup.d.ts +62 -0
  88. package/dist/agent/setup/slack-api-setup.d.ts.map +1 -0
  89. package/dist/agent/setup/slack-api-setup.js +138 -0
  90. package/dist/agent/setup/slack-api-setup.js.map +1 -0
  91. package/dist/agent/status.d.ts +4 -0
  92. package/dist/agent/status.d.ts.map +1 -0
  93. package/dist/agent/status.js +79 -0
  94. package/dist/agent/status.js.map +1 -0
  95. package/package.json +36 -3
@@ -0,0 +1,214 @@
1
+ // WHERE THE SLACK BUNDLE'S TWO SECRETS COME FROM (OPE-2231) — the signing secret that verifies
2
+ // Slack's requests, and the bot token that signs ours.
3
+ //
4
+ // Before this module there was one answer: `process.env.SLACK_SIGNING_SECRET` and
5
+ // `process.env.SLACK_BOT_TOKEN`, read inline at each call site. A `/setup` run now captures both
6
+ // into the credential store, so there are two homes and an order is needed.
7
+ //
8
+ // 🔴 THE ORDER IS ENV-FIRST, AND IT IS THE OPPOSITE OF `getOperatorSecret`'s. Core's operator-secret
9
+ // resolver puts the db store first and treats env as a fallback on its way out (OPE-408: "db-over-env
10
+ // is the DEFAULT home for secrets"). This module inverts that, deliberately, on the OPE-2231 ruling
11
+ // ("env as fallback (env wins if set, for existing installs)"). The reason is the migration, not a
12
+ // disagreement about where secrets belong:
13
+ //
14
+ // • EVERY deployment that exists today holds these two in env and works. This change must not be
15
+ // able to alter which Slack app a working deployment speaks as. Store-first could: a `/setup` run
16
+ // somebody started and abandoned, or a second workspace's install, would silently take over a
17
+ // live deployment's Slack identity at the next cold start, with a 401 loop as the symptom and
18
+ // nothing in the logs saying a different secret had been picked.
19
+ // • Env-first cannot do that. A deployment with the env vars set resolves to exactly the value it
20
+ // resolved to before this commit, every time. It reads the store AT MOST ONCE PER NAME PER
21
+ // PROCESS — only to decide whether to warn that the env var is shadowing something — and never
22
+ // again, so the per-request hot path for an existing install costs the same property read it
23
+ // always did.
24
+ // • A fresh hosted install sets neither env var (the deploy button asks for one value, the
25
+ // credential encryption key), so it reads the store, which is where `/setup` put them.
26
+ //
27
+ // The cost, stated plainly: an operator who runs `/setup` on a deployment that still has the env vars
28
+ // set will find the stored credentials ignored. {@link slackCredentialSourceNotice} is what makes that
29
+ // visible instead of mysterious — the page says it before the run, and the resolver logs it once.
30
+ //
31
+ // MEMOISED PER PROCESS WITH A SHORT TTL, store reads only. The signature check runs on every inbound
32
+ // Slack request and the bot token on every outbound call, so a vault round trip per request is not
33
+ // acceptable.
34
+ //
35
+ // 🔴 THE TTL IS NOT A TIDINESS CHOICE — A LIFETIME MEMO WAS A CORRECTNESS BUG. An earlier draft cached
36
+ // a store hit for the process's lifetime, on the same reasoning core's `self-secrets.ts` gives for its
37
+ // own memo. That reasoning does not transfer, because `self-secrets` holds values this deployment
38
+ // minted for ITSELF and only rotation changes: nothing re-mints them behind a warm process. These two
39
+ // DO change behind one — `reopen_slack_setup` followed by a second `/setup` run replaces both, by
40
+ // design. With a lifetime memo every warm instance kept serving the OLD signing secret and the OLD
41
+ // bot token after that second install, so the deployment verified the previous app's signature and
42
+ // posted with the previous app's token until every instance happened to recycle: a 401 loop that
43
+ // healed at different times on different instances, with nothing in the logs naming the cause.
44
+ //
45
+ // A TTL bounds that to {@link HIT_TTL_MS} without giving up the per-request cost, which is what the
46
+ // memo was for. It is deliberately not a cache invalidated by the reopen path: a reopen runs in ONE
47
+ // process and these memos live in all of them, so nothing a reopen does can reach them. Only time can. An env hit is not memoised because it is already a
48
+ // property read. A MISS is remembered only BRIEFLY ({@link MISS_TTL_MS}) — long enough that an
49
+ // unauthenticated POST to the public Slack route cannot drive one database query per request, short
50
+ // enough that a process which booted before `/setup` finished still picks the credentials up without a
51
+ // restart.
52
+ import { encryptedKvVault } from "@open-everywhere/core/agent/adapter-api.js";
53
+ import { SLACK_BOT_TOKEN_REF, SLACK_SIGNING_SECRET_REF } from "./install-state.js";
54
+ /** The env var holding the app's request-signing secret — the pre-OPE-2231 home, still first. */
55
+ const SLACK_SIGNING_SECRET_ENV = "SLACK_SIGNING_SECRET";
56
+ /** The env var holding the workspace bot token — the pre-OPE-2231 home, still first. */
57
+ const SLACK_BOT_TOKEN_ENV = "SLACK_BOT_TOKEN";
58
+ /**
59
+ * How long a resolved STORE value is reused. Bounds how long a warm instance can keep serving the
60
+ * credentials a `reopen_slack_setup` + second `/setup` run replaced — see the header. Thirty seconds
61
+ * keeps the per-request cost at zero in the steady state while making a reinstall take effect on its
62
+ * own, which a lifetime memo never did.
63
+ */
64
+ const HIT_TTL_MS = 30_000;
65
+ /** A resolved store value and when it stops being trusted. */
66
+ const memo = new Map();
67
+ /**
68
+ * How long a store MISS is remembered. A miss cannot be cached for the process's life — a process
69
+ * that boots while `/setup` is still running has to pick the credentials up without a restart — but it
70
+ * must not be re-asked on every request either.
71
+ *
72
+ * 🔴 WHY A MISS NEEDS CACHING AT ALL. The signature check runs through this resolver on every inbound
73
+ * request to the PUBLIC Slack webhook route. On a deployment with neither an env var nor a stored value
74
+ * — which is every deployment for the whole window `/setup` is open — an uncached miss meant one
75
+ * `stored_credential` SELECT per request, before the 401. Anyone who knew the host could drive one
76
+ * database query per request by POSTing garbage. Thirty seconds bounds that at two queries a minute
77
+ * while keeping "a mid-`/setup` process picks them up without a restart" true, one TTL later.
78
+ */
79
+ const MISS_TTL_MS = 30_000;
80
+ /** When each ref's miss stops being trusted, epoch ms. Absent ⇒ no remembered miss. */
81
+ const missUntil = new Map();
82
+ /** Test seam: forget memoised store resolutions, hits and misses alike. */
83
+ export function resetSlackCredentialMemo() {
84
+ memo.clear();
85
+ missUntil.clear();
86
+ }
87
+ const noticed = new Set();
88
+ /** The names whose env-shadows-store CHECK has run in this process, whether or not it warned. See the
89
+ * resolver: keying the one-shot off `noticed` alone made the no-stored-value case re-read the vault
90
+ * on every single request. */
91
+ const shadowChecked = new Set();
92
+ /** Test seam: forget which names have already logged the shadowing notice, and which have been
93
+ * checked at all. */
94
+ export function resetSlackCredentialNotices() {
95
+ noticed.clear();
96
+ shadowChecked.clear();
97
+ }
98
+ /**
99
+ * The sentence an operator needs when the env var is set AND the store holds a value for the same
100
+ * secret: the env var is winning and the stored value is inert. `undefined` when there is nothing to
101
+ * say. The `/setup` page renders this before a run so the operator learns it BEFORE capturing
102
+ * credentials the deployment will ignore, and {@link resolveSlackSecret} logs it once per name.
103
+ */
104
+ function slackCredentialSourceNotice(envName) {
105
+ return (`[open-everywhere:slack-setup] ${envName} is set in the environment, so this deployment uses the environment value and ` +
106
+ `IGNORES the one in the credential store. That is the documented order (OPE-2231): env wins, so landing this change cannot ` +
107
+ `alter which Slack app an existing deployment speaks as. To use the stored value instead, unset ${envName} and redeploy.`);
108
+ }
109
+ /**
110
+ * Resolve one Slack secret: env if set, else the credential store, else `undefined`.
111
+ *
112
+ * `undefined` is a real answer, not an error — it means this deployment has not been through `/setup`
113
+ * and holds no env var either. Each caller fails in its own way (the front route 401s; an outbound
114
+ * call throws the message eve's primitive always threw), which is what keeps this function free of
115
+ * any opinion about what a missing credential should do.
116
+ */
117
+ export async function resolveSlackSecret(envName, fromEnv, ref, deps = {}) {
118
+ // 🔴 THE ENV VALUE IS PASSED IN, NOT READ HERE, and that is a gate's requirement rather than a
119
+ // style choice. `lint:env-docs` resolves `env[SOME_CONST]` through the const so every variable the
120
+ // runtime reads can be named in `scripts/env-read-allowlist.ts`; a read through a PARAMETER
121
+ // (`env[envName]`) resolves to nothing, so the gate cannot tell which variable it reaches and
122
+ // refuses the site. The two public helpers below each do their own read with a module-level const
123
+ // as the key, which keeps both names visible to the allowlist — and leaves this function with no
124
+ // environment access at all.
125
+ // An injected vault means a test driving a specific store state — bypass the memo entirely, so a
126
+ // test can rewrite a row and see the effect without reaching for a reset.
127
+ const vault = deps.vault;
128
+ const now = (deps.now ?? Date.now)();
129
+ // The memo applies on the live path, and on an injected vault only when a test opts in — see
130
+ // `deps.memo`.
131
+ const memoApplies = vault === undefined || deps.memo === true;
132
+ const cached = memoApplies ? memo.get(ref) : undefined;
133
+ const memoised = cached !== undefined && cached.until > now ? cached.value : undefined;
134
+ if (fromEnv.length > 0) {
135
+ // Only say the env is shadowing a stored value when one is ACTUALLY stored — the notice is for
136
+ // the operator who ran `/setup` and is about to wonder why nothing changed, not for every
137
+ // deployment that has always used env.
138
+ //
139
+ // 🔴 THE CHECK IS MARKED DONE WHETHER OR NOT IT WARNED, and that is the whole reason this is a
140
+ // separate set from `noticed`. Keying the guard off "have I warned?" meant a deployment with the
141
+ // env var set and NOTHING stored — the common case, every pre-OPE-2231 install — never recorded
142
+ // that it had looked, so it re-read the vault on EVERY resolution: one store round trip per
143
+ // inbound Slack request, for an answer that was always going to be the env value. One read per
144
+ // name per process, then never again.
145
+ if (!shadowChecked.has(envName)) {
146
+ shadowChecked.add(envName);
147
+ const stored = memoised ?? (await readStored(ref, vault)); // best-effort; a store failure says nothing
148
+ if (stored !== undefined) {
149
+ noticed.add(envName);
150
+ console.warn(slackCredentialSourceNotice(envName));
151
+ }
152
+ }
153
+ return { value: fromEnv, source: "env" };
154
+ }
155
+ if (memoised !== undefined)
156
+ return { value: memoised, source: "store" };
157
+ // A remembered MISS, still inside its TTL — answer without touching the store. Skipped entirely for
158
+ // an injected vault (a test driving a specific store state).
159
+ if (memoApplies) {
160
+ const until = missUntil.get(ref);
161
+ if (until !== undefined && until > now)
162
+ return undefined;
163
+ }
164
+ const stored = await readStored(ref, vault);
165
+ if (stored === undefined) {
166
+ if (memoApplies)
167
+ missUntil.set(ref, now + MISS_TTL_MS);
168
+ return undefined;
169
+ }
170
+ if (memoApplies) {
171
+ memo.set(ref, { value: stored, until: now + HIT_TTL_MS });
172
+ missUntil.delete(ref);
173
+ }
174
+ return { value: stored, source: "store" };
175
+ }
176
+ /** Read one ref out of the vault, fail-soft to `undefined`. A disarmed credential key, an
177
+ * unreachable store and an absent row are one answer here on purpose: all three mean "the store has
178
+ * nothing for me", and the caller's next step is the same for each. */
179
+ async function readStored(ref, injected) {
180
+ try {
181
+ const value = await (injected ?? encryptedKvVault()).read(ref);
182
+ return value === null || value.length === 0 ? undefined : value;
183
+ }
184
+ catch {
185
+ return undefined;
186
+ }
187
+ }
188
+ /** The app's request-signing secret, or `undefined` when this deployment holds none. */
189
+ export async function slackSigningSecret(deps = {}) {
190
+ const env = deps.env ?? process.env;
191
+ const fromEnv = (env[SLACK_SIGNING_SECRET_ENV] ?? "").trim();
192
+ return (await resolveSlackSecret(SLACK_SIGNING_SECRET_ENV, fromEnv, SLACK_SIGNING_SECRET_REF, deps))?.value;
193
+ }
194
+ /** The workspace bot token, or `undefined` when this deployment holds none. */
195
+ export async function slackBotToken(deps = {}) {
196
+ const env = deps.env ?? process.env;
197
+ const fromEnv = (env[SLACK_BOT_TOKEN_ENV] ?? "").trim();
198
+ return (await resolveSlackSecret(SLACK_BOT_TOKEN_ENV, fromEnv, SLACK_BOT_TOKEN_REF, deps))?.value;
199
+ }
200
+ /**
201
+ * Is `name`'s env var set, for the ONE caller that needs to ask without resolving — the `/setup`
202
+ * page's "this env var will shadow what you are about to store" notice.
203
+ *
204
+ * Unrolled rather than looped over the two names for the same reason {@link resolveSlackSecret} takes
205
+ * its env value as a parameter: the gate resolves a const key and nothing else.
206
+ */
207
+ export function slackSecretEnvPresent(deps = {}) {
208
+ const env = deps.env ?? process.env;
209
+ return {
210
+ signingSecret: (env[SLACK_SIGNING_SECRET_ENV] ?? "").trim().length > 0,
211
+ botToken: (env[SLACK_BOT_TOKEN_ENV] ?? "").trim().length > 0,
212
+ };
213
+ }
214
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../../agent/setup/credentials.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,uDAAuD;AACvD,EAAE;AACF,kFAAkF;AAClF,iGAAiG;AACjG,4EAA4E;AAC5E,EAAE;AACF,qGAAqG;AACrG,sGAAsG;AACtG,oGAAoG;AACpG,mGAAmG;AACnG,2CAA2C;AAC3C,EAAE;AACF,mGAAmG;AACnG,sGAAsG;AACtG,kGAAkG;AAClG,kGAAkG;AAClG,qEAAqE;AACrE,oGAAoG;AACpG,+FAA+F;AAC/F,mGAAmG;AACnG,iGAAiG;AACjG,kBAAkB;AAClB,6FAA6F;AAC7F,2FAA2F;AAC3F,EAAE;AACF,sGAAsG;AACtG,uGAAuG;AACvG,kGAAkG;AAClG,EAAE;AACF,qGAAqG;AACrG,mGAAmG;AACnG,cAAc;AACd,EAAE;AACF,uGAAuG;AACvG,uGAAuG;AACvG,kGAAkG;AAClG,sGAAsG;AACtG,kGAAkG;AAClG,mGAAmG;AACnG,mGAAmG;AACnG,iGAAiG;AACjG,+FAA+F;AAC/F,EAAE;AACF,oGAAoG;AACpG,oGAAoG;AACpG,0JAA0J;AAC1J,+FAA+F;AAC/F,oGAAoG;AACpG,uGAAuG;AACvG,WAAW;AAEX,OAAO,EAAE,gBAAgB,EAAyB,MAAM,4CAA4C,CAAC;AACrG,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAEnF,iGAAiG;AACjG,MAAM,wBAAwB,GAAG,sBAAsB,CAAC;AACxD,wFAAwF;AACxF,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AA8B9C;;;;;GAKG;AACH,MAAM,UAAU,GAAG,MAAM,CAAC;AAE1B,8DAA8D;AAC9D,MAAM,IAAI,GAAG,IAAI,GAAG,EAA8D,CAAC;AAEnF;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,GAAG,MAAM,CAAC;AAE3B,uFAAuF;AACvF,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;AAE5C,2EAA2E;AAC3E,MAAM,UAAU,wBAAwB;IACtC,IAAI,CAAC,KAAK,EAAE,CAAC;IACb,SAAS,CAAC,KAAK,EAAE,CAAC;AACpB,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;AAElC;;+BAE+B;AAC/B,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;AAExC;sBACsB;AACtB,MAAM,UAAU,2BAA2B;IACzC,OAAO,CAAC,KAAK,EAAE,CAAC;IAChB,aAAa,CAAC,KAAK,EAAE,CAAC;AACxB,CAAC;AAED;;;;;GAKG;AACH,SAAS,2BAA2B,CAAC,OAAe;IAClD,OAAO,CACL,iCAAiC,OAAO,gFAAgF;QACxH,4HAA4H;QAC5H,kGAAkG,OAAO,gBAAgB,CAC1H,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAe,EACf,OAAe,EACf,GAAW,EACX,IAAI,GAAwB,EAAE;IAE9B,+FAA+F;IAC/F,mGAAmG;IACnG,4FAA4F;IAC5F,8FAA8F;IAC9F,kGAAkG;IAClG,iGAAiG;IACjG,6BAA6B;IAE7B,iGAAiG;IACjG,0EAA0E;IAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACzB,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACrC,6FAA6F;IAC7F,eAAe;IACf,MAAM,WAAW,GAAG,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;IAC9D,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACvD,MAAM,QAAQ,GAAG,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAEvF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,+FAA+F;QAC/F,0FAA0F;QAC1F,uCAAuC;QACvC,EAAE;QACF,+FAA+F;QAC/F,iGAAiG;QACjG,gGAAgG;QAChG,4FAA4F;QAC5F,+FAA+F;QAC/F,sCAAsC;QACtC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAChC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC3B,MAAM,MAAM,GAAG,QAAQ,IAAI,CAAC,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,4CAA4C;YACvG,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBACrB,OAAO,CAAC,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC3C,CAAC;IAED,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAExE,oGAAoG;IACpG,6DAA6D;IAC7D,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,GAAG,GAAG;YAAE,OAAO,SAAS,CAAC;IAC3D,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC5C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,WAAW;YAAE,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,WAAW,CAAC,CAAC;QACvD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,WAAW,EAAE,CAAC;QAChB,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,UAAU,EAAE,CAAC,CAAC;QAC1D,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC5C,CAAC;AAED;;wEAEwE;AACxE,KAAK,UAAU,UAAU,CAAC,GAAW,EAAE,QAAsC;IAC3E,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,IAAI,gBAAgB,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/D,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,wFAAwF;AACxF,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAI,GAAwB,EAAE;IACrE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,wBAAwB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7D,OAAO,CAAC,MAAM,kBAAkB,CAAC,wBAAwB,EAAE,OAAO,EAAE,wBAAwB,EAAE,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC;AAC9G,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAI,GAAwB,EAAE;IAChE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxD,OAAO,CAAC,MAAM,kBAAkB,CAAC,mBAAmB,EAAE,OAAO,EAAE,mBAAmB,EAAE,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC;AACpG,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAI,GAAwB,EAAE;IAClE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,OAAO;QACL,aAAa,EAAE,CAAC,GAAG,CAAC,wBAAwB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;QACtE,QAAQ,EAAE,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;KAC7D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Eve's inbound verifier. THROWS to reject — eve catches, logs, and answers 401, so a thrown error
3
+ * here is the refusal. It must never return a verdict as a value.
4
+ *
5
+ * The message names no secret and no header value: a verification failure is one of three things to
6
+ * an attacker (no secret configured, bad signature, stale timestamp) and telling them which is free
7
+ * information. The three are distinguished in the thrown text only to the extent an OPERATOR reading
8
+ * their own logs needs, which is why it names the CONDITION and never the input.
9
+ */
10
+ export declare function verifySlackInboundForEve(request: Request, body: string): Promise<void>;
11
+ /**
12
+ * Eve's outbound bot-token provider. Throws eve's OWN message when none resolves, byte for byte, so a
13
+ * misconfigured deployment produces the error every runbook and every existing test already names.
14
+ */
15
+ export declare function slackBotTokenForEve(): Promise<string>;
16
+ /** The `credentials` block for `slackChannel(...)`. One object, so the two halves cannot be wired
17
+ * independently and leave eve resolving one of them from the environment. */
18
+ export declare const SLACK_EVE_CREDENTIALS: {
19
+ readonly webhookVerifier: typeof verifySlackInboundForEve;
20
+ readonly botToken: typeof slackBotTokenForEve;
21
+ };
22
+ //# sourceMappingURL=eve-credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eve-credentials.d.ts","sourceRoot":"","sources":["../../../agent/setup/eve-credentials.ts"],"names":[],"mappings":"AAiCA;;;;;;;;GAQG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB5F;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAI3D;AAED;8EAC8E;AAC9E,eAAO,MAAM,qBAAqB;aAChC,eAAe;aACf,QAAQ;CACA,CAAC"}
@@ -0,0 +1,73 @@
1
+ // WHAT EVE IS TOLD ABOUT THIS DEPLOYMENT'S SLACK CREDENTIALS (OPE-2231).
2
+ //
3
+ // 🔴 WHY THIS FILE HAD TO EXIST, AND THE DEFECT IT CLOSES. OE's front route is not the only thing
4
+ // that verifies a Slack signature. It verifies, then FORWARDS to eve's own `slackChannel` POST
5
+ // handler, and eve verifies AGAIN — `verifyInbound` in eve's `slackChannel.js`:
6
+ //
7
+ // signingSecret: credentials?.signingSecret ?? (credentials?.webhookVerifier ? undefined
8
+ // : process.env.SLACK_SIGNING_SECRET)
9
+ //
10
+ // `channels/slack.ts` passed NO `credentials`, so eve resolved the secret from `process.env` and
11
+ // nowhere else. Moving OE's own read to the credential store therefore produced a deployment where
12
+ // OE's half of the verification passed and EVE's half 401'd: a `/setup`-configured deployment with no
13
+ // env vars — the exact case the page exists to produce — would have acked nothing, woken nothing, and
14
+ // said only `unauthorized`. Eve's outbound path had the same shape: `buildSlackBinding({ botToken:
15
+ // credentials?.botToken })` falls back to `resolveSlackBotToken`, which throws
16
+ // `SLACK_BOT_TOKEN is required.`
17
+ //
18
+ // So the migration is only complete when EVE resolves through the same seam OE does. That is what
19
+ // these two arms are. Both are FUNCTIONS, not values, because the resolution is async and because a
20
+ // value read at module load would pin whatever was true at boot — before `/setup` ran.
21
+ //
22
+ // `webhookVerifier` rather than `signingSecret`: eve takes `signingSecret` as a plain string, which
23
+ // means reading it once at module load. Supplying the verifier instead also makes eve SKIP its
24
+ // `process.env.SLACK_SIGNING_SECRET` fallback entirely (see the expression above), so there is no path
25
+ // left on which eve and OE can disagree about which secret this request is held to.
26
+ import { slackSignatureMatches, SLACK_SIGNATURE_HEADER, SLACK_TIMESTAMP_HEADER } from "../channels/slack-signature.js";
27
+ import { slackBotToken, slackSigningSecret } from "./credentials.js";
28
+ /** How far a request's signed timestamp may be from now. Slack's own recommendation, and the same
29
+ * bound OE's front route applies — the two verifications must not differ on skew either. */
30
+ const MAX_SKEW_SECONDS = 60 * 5;
31
+ /**
32
+ * Eve's inbound verifier. THROWS to reject — eve catches, logs, and answers 401, so a thrown error
33
+ * here is the refusal. It must never return a verdict as a value.
34
+ *
35
+ * The message names no secret and no header value: a verification failure is one of three things to
36
+ * an attacker (no secret configured, bad signature, stale timestamp) and telling them which is free
37
+ * information. The three are distinguished in the thrown text only to the extent an OPERATOR reading
38
+ * their own logs needs, which is why it names the CONDITION and never the input.
39
+ */
40
+ export async function verifySlackInboundForEve(request, body) {
41
+ const secret = await slackSigningSecret();
42
+ if (!secret) {
43
+ throw new Error("SLACK_SIGNING_SECRET is not set and no signing secret is stored — this deployment cannot verify a Slack request. " +
44
+ "Run /setup, or set SLACK_SIGNING_SECRET.");
45
+ }
46
+ const timestamp = request.headers.get(SLACK_TIMESTAMP_HEADER);
47
+ const signature = request.headers.get(SLACK_SIGNATURE_HEADER);
48
+ if (!timestamp || !signature)
49
+ throw new Error("slack inbound: the request carries no signature headers.");
50
+ // The timestamp is INSIDE the signed basestring, which is what makes a skew check meaningful here.
51
+ const skew = Math.abs(Date.now() / 1000 - Number(timestamp));
52
+ if (!Number.isFinite(skew) || skew > MAX_SKEW_SECONDS)
53
+ throw new Error("slack inbound: the signed timestamp is outside the accepted window.");
54
+ if (!slackSignatureMatches(secret, timestamp, body, signature))
55
+ throw new Error("slack inbound: the signature does not match.");
56
+ }
57
+ /**
58
+ * Eve's outbound bot-token provider. Throws eve's OWN message when none resolves, byte for byte, so a
59
+ * misconfigured deployment produces the error every runbook and every existing test already names.
60
+ */
61
+ export async function slackBotTokenForEve() {
62
+ const token = await slackBotToken();
63
+ if (!token)
64
+ throw new Error("SLACK_BOT_TOKEN is required.");
65
+ return token;
66
+ }
67
+ /** The `credentials` block for `slackChannel(...)`. One object, so the two halves cannot be wired
68
+ * independently and leave eve resolving one of them from the environment. */
69
+ export const SLACK_EVE_CREDENTIALS = {
70
+ webhookVerifier: verifySlackInboundForEve,
71
+ botToken: slackBotTokenForEve,
72
+ };
73
+ //# sourceMappingURL=eve-credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eve-credentials.js","sourceRoot":"","sources":["../../../agent/setup/eve-credentials.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,EAAE;AACF,kGAAkG;AAClG,+FAA+F;AAC/F,gFAAgF;AAChF,EAAE;AACF,6FAA6F;AAC7F,oHAAoH;AACpH,EAAE;AACF,iGAAiG;AACjG,mGAAmG;AACnG,sGAAsG;AACtG,sGAAsG;AACtG,mGAAmG;AACnG,+EAA+E;AAC/E,iCAAiC;AACjC,EAAE;AACF,kGAAkG;AAClG,oGAAoG;AACpG,uFAAuF;AACvF,EAAE;AACF,oGAAoG;AACpG,+FAA+F;AAC/F,uGAAuG;AACvG,oFAAoF;AAEpF,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACvH,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAErE;6FAC6F;AAC7F,MAAM,gBAAgB,GAAG,EAAE,GAAG,CAAC,CAAC;AAEhC;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,OAAgB,EAAE,IAAY;IAC3E,MAAM,MAAM,GAAG,MAAM,kBAAkB,EAAE,CAAC;IAC1C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,mHAAmH;YACjH,0CAA0C,CAC7C,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IAC9D,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAE1G,mGAAmG;IACnG,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;IAC7D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,gBAAgB;QAAE,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;IAE9I,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;AAClI,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC;IACpC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAC5D,OAAO,KAAK,CAAC;AACf,CAAC;AAED;8EAC8E;AAC9E,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,eAAe,EAAE,wBAAwB;IACzC,QAAQ,EAAE,mBAAmB;CACrB,CAAC"}
@@ -0,0 +1,139 @@
1
+ import { type EncryptedKvVault } from "@open-everywhere/core/agent/adapter-api.js";
2
+ /** The ref holding the app's OAuth client secret. SECRET — never rendered, never logged. */
3
+ export declare const SLACK_CLIENT_SECRET_REF = "slack-app:client_secret";
4
+ /** The ref holding the app's request-signing secret. SECRET. */
5
+ export declare const SLACK_SIGNING_SECRET_REF = "slack-app:signing_secret";
6
+ /** The ref holding the workspace bot token (`xoxb-…`). SECRET. */
7
+ export declare const SLACK_BOT_TOKEN_REF = "slack-app:bot_token";
8
+ /**
9
+ * The ref holding a PENDING app's signing secret, between the create and the OAuth callback.
10
+ *
11
+ * 🔴 WHY A SECOND REF AND NOT JUST {@link SLACK_SIGNING_SECRET_REF}. That ref is LIVE — it is what
12
+ * verifies every inbound Slack request. Writing the new app's secret straight into it at the create
13
+ * step would swap the running deployment's signing key before the install existed, so an ABANDONED
14
+ * run (wrong workspace, expired token, closed tab) left an installed deployment unable to verify its
15
+ * own workspace's requests: every cold start from then on 401s, staggered by the per-process memo, with
16
+ * nothing in the logs saying a different secret had been picked. The callback promotes this to the live
17
+ * ref immediately before it records the install, so the live secret changes only when a run COMPLETES.
18
+ */
19
+ export declare const SLACK_PENDING_SIGNING_SECRET_REF = "slack-app:pending_signing_secret";
20
+ /** The ref holding the HMAC key for the claim cookie. SECRET, and minted here — see claim.ts. */
21
+ export declare const SLACK_COOKIE_KEY_REF = "slack-app:cookie_key";
22
+ /** The ref holding the one-time setup code a human reads out of the deployment's logs. SECRET. */
23
+ export declare const SLACK_SETUP_CODE_REF = "slack-app:setup_code";
24
+ /** The ref holding {@link SlackInstallState} as JSON. The ONLY ref here with no secret in it. */
25
+ export declare const SLACK_INSTALL_REF = "slack-app:install";
26
+ /** The ref holding the PENDING app record (`agent/setup/page.ts` writes and spends it). Declared here
27
+ * so one module owns the ref vocabulary — `reopenSlackSetup` has to clear it, and a ref it could not
28
+ * name is a ref it would leave behind. */
29
+ export declare const SLACK_PENDING_REF = "slack-app:pending";
30
+ /**
31
+ * What a completed `/setup` run recorded. IDS AND TIMESTAMPS ONLY — see the header on why.
32
+ *
33
+ * `v` guards a future shape change: an unreadable or wrong-version record reads as NO install, which
34
+ * fails in the safe direction (the page reopens; it does not grant anything).
35
+ */
36
+ export interface SlackInstallState {
37
+ readonly v: 1;
38
+ /** The Slack app id (`A…`) `apps.manifest.create` returned. */
39
+ readonly appId: string;
40
+ /** The app's OAuth client id. Public by Slack's own design (it rides the authorize URL). */
41
+ readonly clientId: string;
42
+ /** The workspace (team) id (`T…`) the OAuth install landed in. */
43
+ readonly teamId: string;
44
+ /** The workspace's display name, when `oauth.v2.access` reported one. */
45
+ readonly teamName?: string;
46
+ /** The installing member's id (`U…`) — `authed_user.id` on the OAuth response. */
47
+ readonly installerUserId: string;
48
+ /** ISO-8601 instant the install completed. */
49
+ readonly installedAt: string;
50
+ }
51
+ /** Injectable seams for tests — the live default is the real encrypted KV vault. */
52
+ export interface SlackInstallStateDeps {
53
+ readonly vault?: EncryptedKvVault;
54
+ }
55
+ /**
56
+ * The recorded Slack install, or `undefined` when this deployment has none.
57
+ *
58
+ * 🔴 THIS IS THE SEAM OPE-2230's STATUS ENDPOINT READS, and the only one it should: it returns the
59
+ * whole non-secret truth about whether Slack is installed and where, and it can return nothing
60
+ * else. Call it; do not read `slack-app:install` directly, or the status endpoint and the claim lock
61
+ * end up with two readings of one record.
62
+ *
63
+ * FAIL-SOFT TO "NOT INSTALLED". An unreachable vault, a disarmed credential key, a malformed row or
64
+ * a record of an unknown version all answer `undefined`. For a STATUS READER that is the safe
65
+ * direction and the honest one: it reports Slack as not installed, which is true as far as this
66
+ * process can tell, and it cannot report an install that is not there.
67
+ *
68
+ * 🔴 THE CLAIM LOCK MUST NOT USE THIS FUNCTION. For the lock, the same collapse is a hole — a store
69
+ * fault would reopen `/setup`. The lock reads {@link readInstallRecord} instead, which tells the
70
+ * three cases apart. See its doc comment.
71
+ */
72
+ export declare function slackInstallState(deps?: SlackInstallStateDeps): Promise<SlackInstallState | undefined>;
73
+ /**
74
+ * What a read of the install record actually found — THREE outcomes, not two.
75
+ *
76
+ * 🔴 WHY THE LOCK NEEDS THIS AND THE STATUS SEAM DOES NOT. {@link slackInstallState} collapses every
77
+ * unreadable case into `undefined`, which is right for a status endpoint: "no install I can see" is
78
+ * the honest answer whether the store is unreachable or the row is absent, and it cannot report an
79
+ * install that is not there. For the LOCK that collapse is a hole. The lock asks the opposite
80
+ * question — "may this page serve?" — and `undefined` meaning both "nothing stored" and "I could not
81
+ * tell" makes a store fault, a malformed row or a row from a future version REOPEN `/setup`. The
82
+ * setup code is in the deployment's old log lines from before the install, so a reopen is a window
83
+ * for whoever read them.
84
+ *
85
+ * So the lock distinguishes: `absent` is the ONLY outcome that opens the page. `unreadable` covers a
86
+ * throw, a non-JSON row, and a row whose `v` this build does not know — all of which mean "something
87
+ * IS stored and I cannot read it", which is never a reason to grant. The cost of that is a setup page
88
+ * that is briefly unavailable during a store outage, recoverable by a retry or the control call; the
89
+ * cost of the other direction is not recoverable at all.
90
+ */
91
+ export type InstallRecordRead = {
92
+ readonly kind: "present";
93
+ readonly state: SlackInstallState;
94
+ } | {
95
+ readonly kind: "absent";
96
+ readonly state: undefined;
97
+ } | {
98
+ readonly kind: "unreadable";
99
+ readonly state: undefined;
100
+ };
101
+ /** Read the install record, reporting WHICH of the three outcomes happened. The lock reads this. */
102
+ export declare function readInstallRecord(deps?: SlackInstallStateDeps): Promise<InstallRecordRead>;
103
+ /** Parse + validate a stored record. Exported for the suite; `undefined` for anything unreadable. */
104
+ export declare function parseInstallState(raw: string): SlackInstallState | undefined;
105
+ /**
106
+ * Record a completed install. THE LAST WRITE OF A SUCCESSFUL `/setup` RUN — it is what closes the
107
+ * page, so every secret the install captured must already be stored when this is called.
108
+ *
109
+ * Throws whatever the vault throws (a disarmed credential key, an unreachable store). The caller
110
+ * must NOT swallow it: a run that stored the credentials but failed to record the install has to
111
+ * say so, because the page is still open and a second run would mint a second Slack app.
112
+ */
113
+ export declare function recordSlackInstall(state: SlackInstallState, deps?: SlackInstallStateDeps): Promise<void>;
114
+ /**
115
+ * REOPEN the setup page: destroy the install record, keeping every stored credential.
116
+ *
117
+ * WHY THE LIVE CREDENTIALS STAY. A reopen is "let me run setup again", not "forget this workspace".
118
+ * The running deployment is still serving Slack with the stored bot token and signing secret while the
119
+ * admin walks the page, and a reopen that deleted them would take the agent off Slack the moment it
120
+ * was called. A COMPLETED second run replaces them, at its callback; an abandoned one leaves the
121
+ * working install untouched, which is true because the create step stages the new signing secret under
122
+ * {@link SLACK_PENDING_SIGNING_SECRET_REF} rather than writing the live ref.
123
+ *
124
+ * What DOES go, besides the record: the setup code (so the reopened page needs a fresh one — see
125
+ * below) and any stale pending-app material.
126
+ *
127
+ * THROWS on a failure to rotate the setup code or the claim cookie key, and leaves the install record
128
+ * in place when it does — see the body. A reopen that could not rotate must not open the page.
129
+ *
130
+ * NO REOPEN HISTORY SURVIVES THIS, deliberately. The record IS the lock, so reopening means
131
+ * destroying it, and a field recording "reopened at" would have to live on a record that no longer
132
+ * exists. The audit row is where the history lives (`slack.setup_reopened`, audit/action-registry.ts)
133
+ * — a hash-chained row naming the admin who called it, which is a better home for that fact than a
134
+ * field on a row the next install overwrites.
135
+ *
136
+ * Returns true iff a record went (false when the page was already open) — idempotent either way.
137
+ */
138
+ export declare function reopenSlackSetup(deps?: SlackInstallStateDeps): Promise<boolean>;
139
+ //# sourceMappingURL=install-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-state.d.ts","sourceRoot":"","sources":["../../../agent/setup/install-state.ts"],"names":[],"mappings":"AA2BA,OAAO,EAA6C,KAAK,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAE9H,4FAA4F;AAC5F,eAAO,MAAM,uBAAuB,4BAA4C,CAAC;AACjF,gEAAgE;AAChE,eAAO,MAAM,wBAAwB,6BAA6C,CAAC;AACnF,kEAAkE;AAClE,eAAO,MAAM,mBAAmB,wBAAwC,CAAC;AACzE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,qCAAqD,CAAC;AACnG,iGAAiG;AACjG,eAAO,MAAM,oBAAoB,yBAAyC,CAAC;AAC3E,kGAAkG;AAClG,eAAO,MAAM,oBAAoB,yBAAyC,CAAC;AAC3E,iGAAiG;AACjG,eAAO,MAAM,iBAAiB,sBAAsC,CAAC;AACrE;;2CAE2C;AAC3C,eAAO,MAAM,iBAAiB,sBAAsC,CAAC;AAErE;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC;IACd,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,kFAAkF;IAClF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,8CAA8C;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,oFAAoF;AACpF,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACnC;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,GAAE,qBAA0B,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAEhH;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAA;CAAE,GAC/D;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GACtD;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,CAAC;AAE/D,oGAAoG;AACpG,wBAAsB,iBAAiB,CAAC,IAAI,GAAE,qBAA0B,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAYpG;AAED,qGAAqG;AACrG,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CA2B5E;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,iBAAiB,EAAE,IAAI,GAAE,qBAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAElH;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,GAAE,qBAA0B,GAAG,OAAO,CAAC,OAAO,CAAC,CAiCzF"}