@pithy-sh/core 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access/access.d.ts +172 -0
- package/dist/access/access.d.ts.map +1 -0
- package/dist/access/access.js +147 -0
- package/dist/access/acting.d.ts +49 -0
- package/dist/access/acting.d.ts.map +1 -0
- package/dist/access/acting.js +21 -0
- package/dist/access/policyCheck.d.ts +33 -0
- package/dist/access/policyCheck.d.ts.map +1 -0
- package/dist/access/policyCheck.js +48 -0
- package/dist/access/usage.d.ts +83 -0
- package/dist/access/usage.d.ts.map +1 -0
- package/dist/access/usage.js +104 -0
- package/dist/access/visibility.d.ts +66 -0
- package/dist/access/visibility.d.ts.map +1 -0
- package/dist/access/visibility.js +86 -0
- package/dist/audit/auditEvent.d.ts +5 -0
- package/dist/audit/auditEvent.d.ts.map +1 -1
- package/dist/audit/auditEvent.js +2 -0
- package/dist/capability/bindings.d.ts +47 -2
- package/dist/capability/bindings.d.ts.map +1 -1
- package/dist/capability/bindings.js +117 -7
- package/dist/capability/capability.d.ts +98 -28
- package/dist/capability/capability.d.ts.map +1 -1
- package/dist/capability/manifest.d.ts +2 -1
- package/dist/capability/manifest.d.ts.map +1 -1
- package/dist/capability/manifest.js +1 -1
- package/dist/capability/settings.d.ts +86 -2
- package/dist/capability/settings.d.ts.map +1 -1
- package/dist/capability/settings.js +80 -1
- package/dist/cli/commandCatalog.generated.d.ts +184 -22
- package/dist/cli/commandCatalog.generated.d.ts.map +1 -1
- package/dist/cli/commandCatalog.generated.js +196 -22
- package/dist/cli/commandFacts.d.ts +1 -1
- package/dist/cli/commandFacts.d.ts.map +1 -1
- package/dist/cli/commandFacts.js +1 -0
- package/dist/controlPlane/capability.d.ts.map +1 -1
- package/dist/controlPlane/capability.js +12 -2
- package/dist/controlPlane/context.d.ts +5 -0
- package/dist/controlPlane/context.d.ts.map +1 -1
- package/dist/controlPlane/context.js +3 -1
- package/dist/controlPlane/data/connection.d.ts +47 -0
- package/dist/controlPlane/data/connection.d.ts.map +1 -1
- package/dist/controlPlane/data/connection.js +55 -1
- package/dist/controlPlane/http/cors.d.ts +4 -4
- package/dist/controlPlane/http/cors.js +4 -4
- package/dist/controlPlane/http/guard.d.ts +11 -0
- package/dist/controlPlane/http/guard.d.ts.map +1 -1
- package/dist/controlPlane/http/guard.js +17 -1
- package/dist/controlPlane/http/handlers.d.ts +60 -0
- package/dist/controlPlane/http/handlers.d.ts.map +1 -1
- package/dist/controlPlane/http/handlers.js +31 -2
- package/dist/controlPlane/http/readiness.d.ts +33 -0
- package/dist/controlPlane/http/readiness.d.ts.map +1 -0
- package/dist/controlPlane/http/readiness.js +39 -0
- package/dist/controlPlane/http/responses.d.ts +147 -0
- package/dist/controlPlane/http/responses.d.ts.map +1 -1
- package/dist/controlPlane/http/responses.js +61 -4
- package/dist/controlPlane/http/routes.d.ts +1 -0
- package/dist/controlPlane/http/routes.d.ts.map +1 -1
- package/dist/controlPlane/http/routes.js +17 -3
- package/dist/controlPlane/http/verify.d.ts.map +1 -1
- package/dist/controlPlane/http/verify.js +2 -1
- package/dist/controlPlane/migrationState/migrationState.d.ts +102 -0
- package/dist/controlPlane/migrationState/migrationState.d.ts.map +1 -0
- package/dist/controlPlane/migrationState/migrationState.js +142 -0
- package/dist/controlPlane/migrations/0003_connection_principal.d.ts +21 -0
- package/dist/controlPlane/migrations/0003_connection_principal.d.ts.map +1 -0
- package/dist/controlPlane/migrations/0003_connection_principal.js +33 -0
- package/dist/controlPlane/scope/scope.d.ts +13 -2
- package/dist/controlPlane/scope/scope.d.ts.map +1 -1
- package/dist/controlPlane/scope/scope.js +14 -3
- package/dist/createBackend.d.ts.map +1 -1
- package/dist/createBackend.js +7 -2
- package/dist/createEntrypoint.d.ts.map +1 -1
- package/dist/createEntrypoint.js +5 -3
- package/dist/entitlement/allowance.d.ts +25 -0
- package/dist/entitlement/allowance.d.ts.map +1 -0
- package/dist/entitlement/allowance.js +80 -0
- package/dist/entitlement/entitlement.d.ts +41 -0
- package/dist/entitlement/entitlement.d.ts.map +1 -1
- package/dist/entitlement/meter.d.ts +57 -0
- package/dist/entitlement/meter.d.ts.map +1 -0
- package/dist/entitlement/meter.js +13 -0
- package/dist/error/http.d.ts +144 -0
- package/dist/error/http.d.ts.map +1 -1
- package/dist/error/payload.d.ts +288 -0
- package/dist/error/payload.d.ts.map +1 -1
- package/dist/error/payload.js +100 -4
- package/dist/http/authContext.d.ts +7 -0
- package/dist/http/authContext.d.ts.map +1 -1
- package/dist/http/authContext.js +8 -1
- package/dist/http/scopeGates.d.ts +20 -0
- package/dist/http/scopeGates.d.ts.map +1 -0
- package/dist/http/scopeGates.js +36 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/logger/record.d.ts +84 -0
- package/dist/logger/record.d.ts.map +1 -1
- package/dist/migrations/capabilitySets.d.ts +23 -0
- package/dist/migrations/capabilitySets.d.ts.map +1 -0
- package/dist/migrations/capabilitySets.js +74 -0
- package/dist/migrations/registry.d.ts +26 -0
- package/dist/migrations/registry.d.ts.map +1 -1
- package/dist/migrations/registry.js +38 -7
- package/dist/migrations/state.d.ts +60 -0
- package/dist/migrations/state.d.ts.map +1 -0
- package/dist/migrations/state.js +46 -0
- package/dist/naming/feature.d.ts +27 -5
- package/dist/naming/feature.d.ts.map +1 -1
- package/dist/naming/feature.js +36 -6
- package/dist/naming/limits.d.ts +47 -3
- package/dist/naming/limits.d.ts.map +1 -1
- package/dist/naming/limits.js +47 -3
- package/dist/naming/provisionScope.d.ts +6 -0
- package/dist/naming/provisionScope.d.ts.map +1 -1
- package/dist/naming/provisionScope.js +8 -3
- package/dist/naming/resourceNames.d.ts +21 -2
- package/dist/naming/resourceNames.d.ts.map +1 -1
- package/dist/naming/resourceNames.js +21 -3
- package/dist/seed/writeD1.d.ts +7 -1
- package/dist/seed/writeD1.d.ts.map +1 -1
- package/dist/seed/writeD1.js +9 -3
- package/dist/seed/writeKv.d.ts +2 -0
- package/dist/seed/writeKv.d.ts.map +1 -1
- package/dist/seed/writeKv.js +6 -2
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/worker/serviceLoopback.d.ts +36 -0
- package/dist/worker/serviceLoopback.d.ts.map +1 -0
- package/dist/worker/serviceLoopback.js +74 -0
- package/dist/workflow/faults.d.ts +12 -1
- package/dist/workflow/faults.d.ts.map +1 -1
- package/dist/workflow/faults.js +22 -15
- package/package.json +2 -2
- package/src/access/access.ts +298 -0
- package/src/access/acting.ts +59 -0
- package/src/access/policyCheck.ts +77 -0
- package/src/access/usage.ts +141 -0
- package/src/access/visibility.ts +109 -0
- package/src/audit/auditEvent.ts +4 -0
- package/src/capability/bindings.ts +159 -13
- package/src/capability/capability.ts +99 -29
- package/src/capability/settings.ts +146 -2
- package/src/cli/commandCatalog.generated.ts +212 -22
- package/src/cli/commandFacts.ts +1 -0
- package/src/controlPlane/capability.ts +25 -1
- package/src/controlPlane/context.ts +4 -0
- package/src/controlPlane/data/connection.ts +90 -0
- package/src/controlPlane/http/cors.ts +4 -4
- package/src/controlPlane/http/guard.ts +21 -0
- package/src/controlPlane/http/handlers.ts +41 -0
- package/src/controlPlane/http/readiness.ts +50 -0
- package/src/controlPlane/http/responses.ts +140 -2
- package/src/controlPlane/http/routes.ts +25 -1
- package/src/controlPlane/http/verify.ts +2 -0
- package/src/controlPlane/migrationState/migrationState.ts +200 -0
- package/src/controlPlane/migrations/0003_connection_principal.ts +37 -0
- package/src/controlPlane/scope/scope.ts +14 -2
- package/src/createBackend.ts +24 -6
- package/src/createEntrypoint.ts +9 -3
- package/src/entitlement/allowance.ts +85 -0
- package/src/entitlement/entitlement.ts +38 -0
- package/src/entitlement/meter.ts +69 -0
- package/src/error/payload.ts +211 -4
- package/src/http/authContext.ts +13 -1
- package/src/http/scopeGates.ts +34 -0
- package/src/index.ts +1 -0
- package/src/migrations/capabilitySets.ts +87 -0
- package/src/migrations/registry.ts +46 -2
- package/src/migrations/state.ts +94 -0
- package/src/naming/feature.ts +46 -7
- package/src/naming/limits.ts +35 -3
- package/src/naming/provisionScope.ts +19 -1
- package/src/naming/resourceNames.ts +52 -5
- package/src/seed/writeD1.ts +13 -4
- package/src/seed/writeKv.ts +6 -2
- package/src/version.generated.ts +1 -1
- package/src/worker/serviceLoopback.ts +105 -0
- package/src/workflow/faults.ts +51 -24
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { InternalError } from "../error/pithyError";
|
|
5
6
|
import type { HostEnvReport } from "../workflow/hostEnv";
|
|
6
7
|
import { hostEnvProviderSentence } from "../workflow/hostEnv";
|
|
7
8
|
|
|
@@ -115,8 +116,8 @@ export interface SettingsCheckContext {
|
|
|
115
116
|
*
|
|
116
117
|
* Closed for the reason `CapabilityHealth` is: handing a capability a raw Cloudflare client would make
|
|
117
118
|
* every capability a place a network call can be invented, and would put `@pithy-sh/cloudflare` in the
|
|
118
|
-
* dependency list of packages that need none of it.
|
|
119
|
-
*
|
|
119
|
+
* dependency list of packages that need none of it. A question lands here once a capability actually needs
|
|
120
|
+
* it, and not before: six cover what the checks ask.
|
|
120
121
|
*
|
|
121
122
|
* Every method may throw. A throw is the account failing to answer, and the runner reports that
|
|
122
123
|
* capability's account tier as unchecked rather than as a pass.
|
|
@@ -141,6 +142,47 @@ export interface SettingsAccountReader {
|
|
|
141
142
|
* created it with, so the check looks where the value went.
|
|
142
143
|
*/
|
|
143
144
|
storeEntry(name: string): Promise<boolean>;
|
|
145
|
+
/**
|
|
146
|
+
* The ids of the rows in one of the kit's own tables whose column holds one of `values`, in the app
|
|
147
|
+
* database a Worker binds as `DB` in one deployed environment (#727).
|
|
148
|
+
*
|
|
149
|
+
* Asked by media and storage after a project narrows `visibility.allowed`: narrowing never rewrites a
|
|
150
|
+
* record, so the records left outside the policy have to be *named*, and only the deployed database
|
|
151
|
+
* holds them. Closed in its own way — the table must be a `pithy_` one and the column an identifier,
|
|
152
|
+
* both refused otherwise, and the values are bound, never interpolated — so it reads a fact about a
|
|
153
|
+
* capability's own rows and cannot become a query language.
|
|
154
|
+
*/
|
|
155
|
+
appRows(request: AppRowsRequest): Promise<readonly string[]>;
|
|
156
|
+
/**
|
|
157
|
+
* Whether that environment's **D1 vault** holds a `json` secret that still carries a top-level `field` —
|
|
158
|
+
* asked of its manager Worker, which opens the value and answers a bit, never the value (#728). `false`
|
|
159
|
+
* for a secret that is not stored, since it carries nothing.
|
|
160
|
+
*
|
|
161
|
+
* Asked by media of `media-storage-credentials`, which held a supplied Cloudflare token until the runtime
|
|
162
|
+
* token replaced it: the upgrade is finished only once the bundle has been rewritten without it.
|
|
163
|
+
*/
|
|
164
|
+
vaultSecretField(request: { name: string; environment: string; field: string }): Promise<boolean>;
|
|
165
|
+
/**
|
|
166
|
+
* Whether the account holds a Cloudflare API token of exactly this name (#728). The name is the token's,
|
|
167
|
+
* composed with the namer that minted it — never a pattern, so a check names one credential it can see.
|
|
168
|
+
*
|
|
169
|
+
* Asked by secrets of the manager token the runtime token superseded, which nothing revokes for you.
|
|
170
|
+
*/
|
|
171
|
+
accountToken(name: string): Promise<boolean>;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** What {@link SettingsAccountReader.appRows} is asked. */
|
|
175
|
+
export interface AppRowsRequest {
|
|
176
|
+
/** The deployed environment whose app database is read. `dev` is refused: it has no deployed database. */
|
|
177
|
+
readonly environment: string;
|
|
178
|
+
/** The Worker's `apps/` directory — whose `wrangler.jsonc` names the database for that environment. */
|
|
179
|
+
readonly workerDirectory: string;
|
|
180
|
+
/** The kit table, as it is named in SQL — `pithy_media_assets`. Must carry the `pithy_` prefix. */
|
|
181
|
+
readonly table: string;
|
|
182
|
+
/** The column compared, as it is named in SQL — `visibility`. */
|
|
183
|
+
readonly column: string;
|
|
184
|
+
/** The values that make a row worth naming. */
|
|
185
|
+
readonly values: readonly string[];
|
|
144
186
|
}
|
|
145
187
|
|
|
146
188
|
/** What the account tier is told: everything the local tier gets, plus the account it may ask. */
|
|
@@ -149,6 +191,56 @@ export interface SettingsAccountContext extends SettingsCheckContext {
|
|
|
149
191
|
account: SettingsAccountReader;
|
|
150
192
|
}
|
|
151
193
|
|
|
194
|
+
/** One environment an account check could not answer for, and why. */
|
|
195
|
+
export interface UnansweredEnvironment {
|
|
196
|
+
/** The environment id. */
|
|
197
|
+
readonly environment: string;
|
|
198
|
+
/** What the account, or the project, said instead of an answer. */
|
|
199
|
+
readonly cause: unknown;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* An account check that answered for some environments and not for others.
|
|
204
|
+
*
|
|
205
|
+
* A check that asks per environment must not let one unanswerable environment cost the others their
|
|
206
|
+
* findings: staging stood up before prod is the ordinary bring-up, and a check that threw on prod's missing
|
|
207
|
+
* database would throw away what it had already found in staging. So it collects what it can, and throws
|
|
208
|
+
* this at the end carrying those findings. The runner reports them as findings and the tier as unchecked,
|
|
209
|
+
* both — a fault and an unanswered question are two facts, and neither hides the other.
|
|
210
|
+
*/
|
|
211
|
+
export class SettingsAccountIncomplete extends InternalError {
|
|
212
|
+
/** What the environments that did answer were found to have wrong. */
|
|
213
|
+
readonly findings: readonly SettingsFinding[];
|
|
214
|
+
/** The environments that did not answer, in the order they were asked. */
|
|
215
|
+
readonly unanswered: readonly string[];
|
|
216
|
+
|
|
217
|
+
constructor(findings: readonly SettingsFinding[], unanswered: readonly UnansweredEnvironment[]) {
|
|
218
|
+
const reasons = unanswered.map(({ environment, cause }) => `${environment}: ${describeCause(cause)}`).join("; ");
|
|
219
|
+
super(
|
|
220
|
+
{
|
|
221
|
+
message: `The account check could not answer for ${unanswered.map(({ environment }) => environment).join(", ")}.`,
|
|
222
|
+
detail: reasons,
|
|
223
|
+
},
|
|
224
|
+
{ cause: unanswered[0]?.cause },
|
|
225
|
+
);
|
|
226
|
+
this.findings = findings;
|
|
227
|
+
this.unanswered = unanswered.map(({ environment }) => environment);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** A cause, as one line. */
|
|
232
|
+
function describeCause(cause: unknown): string {
|
|
233
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The findings an account check reached before it threw — none for an ordinary throw, and what it had
|
|
238
|
+
* collected for a {@link SettingsAccountIncomplete}.
|
|
239
|
+
*/
|
|
240
|
+
export function settingsFindingsBefore(error: unknown): readonly SettingsFinding[] {
|
|
241
|
+
return error instanceof SettingsAccountIncomplete ? error.findings : [];
|
|
242
|
+
}
|
|
243
|
+
|
|
152
244
|
/**
|
|
153
245
|
* A capability's settings check, as it hangs off the capability.
|
|
154
246
|
*
|
|
@@ -163,6 +255,58 @@ export interface CapabilitySettings {
|
|
|
163
255
|
account?(context: SettingsAccountContext): Promise<SettingsFinding[]>;
|
|
164
256
|
}
|
|
165
257
|
|
|
258
|
+
/**
|
|
259
|
+
* **Two or more checks on one capability, as the one `settings` it declares (#728).** Media names records a
|
|
260
|
+
* narrowed visibility no longer admits and a bundle still holding a superseded token: two questions, one
|
|
261
|
+
* capability, one seam.
|
|
262
|
+
*
|
|
263
|
+
* Each tier runs every check's, in declaration order. A check that could not answer for some environments
|
|
264
|
+
* costs those environments and nothing else: the others' findings are kept, and the run ends in one
|
|
265
|
+
* {@link SettingsAccountIncomplete} carrying every finding and every unanswered environment. A check that
|
|
266
|
+
* throws outright is unanswered for every environment the context declares.
|
|
267
|
+
*/
|
|
268
|
+
export function allSettings(...checks: readonly CapabilitySettings[]): CapabilitySettings {
|
|
269
|
+
const accounts = checks.flatMap((check) => (check.account ? [check.account.bind(check)] : []));
|
|
270
|
+
return {
|
|
271
|
+
async local(context) {
|
|
272
|
+
const findings: SettingsFinding[] = [];
|
|
273
|
+
for (const check of checks) findings.push(...(await check.local(context)));
|
|
274
|
+
return findings;
|
|
275
|
+
},
|
|
276
|
+
...(accounts.length === 0
|
|
277
|
+
? {}
|
|
278
|
+
: {
|
|
279
|
+
async account(context) {
|
|
280
|
+
const findings: SettingsFinding[] = [];
|
|
281
|
+
const unanswered: UnansweredEnvironment[] = [];
|
|
282
|
+
for (const account of accounts) {
|
|
283
|
+
try {
|
|
284
|
+
findings.push(...(await account(context)));
|
|
285
|
+
} catch (cause) {
|
|
286
|
+
findings.push(...settingsFindingsBefore(cause));
|
|
287
|
+
if (cause instanceof SettingsAccountIncomplete) {
|
|
288
|
+
for (const environment of cause.unanswered) {
|
|
289
|
+
if (!unanswered.some((entry) => entry.environment === environment)) {
|
|
290
|
+
unanswered.push({ environment, cause: cause.cause ?? cause });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
// With no environment to name, there is nothing the throw could be reported against.
|
|
296
|
+
if (context.environments.length === 0) throw cause;
|
|
297
|
+
for (const { name } of context.environments) {
|
|
298
|
+
if (!unanswered.some((entry) => entry.environment === name))
|
|
299
|
+
unanswered.push({ environment: name, cause });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (unanswered.length > 0) throw new SettingsAccountIncomplete(findings, unanswered);
|
|
304
|
+
return findings;
|
|
305
|
+
},
|
|
306
|
+
}),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
166
310
|
/**
|
|
167
311
|
* Turn a host env's own parse report into findings — the bridge that makes "one schema, two readers" real.
|
|
168
312
|
*
|
|
@@ -161,6 +161,41 @@ export const COMMANDS = {
|
|
|
161
161
|
},
|
|
162
162
|
"groups": []
|
|
163
163
|
},
|
|
164
|
+
"completion": {
|
|
165
|
+
"positionals": [
|
|
166
|
+
{
|
|
167
|
+
"name": "shell",
|
|
168
|
+
"required": false,
|
|
169
|
+
"variadic": false
|
|
170
|
+
}
|
|
171
|
+
],
|
|
172
|
+
"flags": {
|
|
173
|
+
"--remove": {
|
|
174
|
+
"type": "boolean",
|
|
175
|
+
"required": false,
|
|
176
|
+
"repeatable": false
|
|
177
|
+
},
|
|
178
|
+
"--status": {
|
|
179
|
+
"type": "boolean",
|
|
180
|
+
"required": false,
|
|
181
|
+
"repeatable": false
|
|
182
|
+
},
|
|
183
|
+
"--json": {
|
|
184
|
+
"type": "boolean",
|
|
185
|
+
"required": false,
|
|
186
|
+
"repeatable": false
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
"groups": [
|
|
190
|
+
{
|
|
191
|
+
"kind": "atMostOne",
|
|
192
|
+
"flags": [
|
|
193
|
+
"--remove",
|
|
194
|
+
"--status"
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
},
|
|
164
199
|
"contract": {
|
|
165
200
|
"positionals": [],
|
|
166
201
|
"flags": {
|
|
@@ -251,6 +286,16 @@ export const COMMANDS = {
|
|
|
251
286
|
"type": "string",
|
|
252
287
|
"required": false,
|
|
253
288
|
"repeatable": false
|
|
289
|
+
},
|
|
290
|
+
"--purpose": {
|
|
291
|
+
"type": "string",
|
|
292
|
+
"required": false,
|
|
293
|
+
"repeatable": false
|
|
294
|
+
},
|
|
295
|
+
"--kind": {
|
|
296
|
+
"type": "string",
|
|
297
|
+
"required": false,
|
|
298
|
+
"repeatable": false
|
|
254
299
|
}
|
|
255
300
|
},
|
|
256
301
|
"groups": [
|
|
@@ -278,13 +323,22 @@ export const COMMANDS = {
|
|
|
278
323
|
],
|
|
279
324
|
"any": false
|
|
280
325
|
},
|
|
326
|
+
{
|
|
327
|
+
"kind": "requires",
|
|
328
|
+
"flag": "--kind",
|
|
329
|
+
"needs": [
|
|
330
|
+
"--public-key"
|
|
331
|
+
],
|
|
332
|
+
"any": false
|
|
333
|
+
},
|
|
281
334
|
{
|
|
282
335
|
"kind": "requires",
|
|
283
336
|
"flag": "--update",
|
|
284
337
|
"needs": [
|
|
285
338
|
"--worker-url",
|
|
286
339
|
"--scope",
|
|
287
|
-
"--public-key"
|
|
340
|
+
"--public-key",
|
|
341
|
+
"--purpose"
|
|
288
342
|
],
|
|
289
343
|
"any": true
|
|
290
344
|
}
|
|
@@ -1042,11 +1096,6 @@ export const COMMANDS = {
|
|
|
1042
1096
|
"required": false,
|
|
1043
1097
|
"repeatable": false
|
|
1044
1098
|
},
|
|
1045
|
-
"--api-token": {
|
|
1046
|
-
"type": "string",
|
|
1047
|
-
"required": false,
|
|
1048
|
-
"repeatable": false
|
|
1049
|
-
},
|
|
1050
1099
|
"--r2-access-key-id": {
|
|
1051
1100
|
"type": "string",
|
|
1052
1101
|
"required": false,
|
|
@@ -1057,11 +1106,6 @@ export const COMMANDS = {
|
|
|
1057
1106
|
"required": false,
|
|
1058
1107
|
"repeatable": false
|
|
1059
1108
|
},
|
|
1060
|
-
"--r2-api-token": {
|
|
1061
|
-
"type": "string",
|
|
1062
|
-
"required": false,
|
|
1063
|
-
"repeatable": false
|
|
1064
|
-
},
|
|
1065
1109
|
"--json": {
|
|
1066
1110
|
"type": "boolean",
|
|
1067
1111
|
"required": false,
|
|
@@ -1447,6 +1491,28 @@ export const COMMANDS = {
|
|
|
1447
1491
|
},
|
|
1448
1492
|
"groups": []
|
|
1449
1493
|
},
|
|
1494
|
+
"secrets tables": {
|
|
1495
|
+
"positionals": [],
|
|
1496
|
+
"flags": {
|
|
1497
|
+
"--env": {
|
|
1498
|
+
"type": "string",
|
|
1499
|
+
"required": false,
|
|
1500
|
+
"repeatable": false,
|
|
1501
|
+
"default": "dev"
|
|
1502
|
+
},
|
|
1503
|
+
"--worker": {
|
|
1504
|
+
"type": "string",
|
|
1505
|
+
"required": false,
|
|
1506
|
+
"repeatable": false
|
|
1507
|
+
},
|
|
1508
|
+
"--json": {
|
|
1509
|
+
"type": "boolean",
|
|
1510
|
+
"required": false,
|
|
1511
|
+
"repeatable": false
|
|
1512
|
+
}
|
|
1513
|
+
},
|
|
1514
|
+
"groups": []
|
|
1515
|
+
},
|
|
1450
1516
|
"secrets update": {
|
|
1451
1517
|
"positionals": [
|
|
1452
1518
|
{
|
|
@@ -1547,6 +1613,27 @@ export const COMMANDS = {
|
|
|
1547
1613
|
},
|
|
1548
1614
|
"groups": []
|
|
1549
1615
|
},
|
|
1616
|
+
"setup": {
|
|
1617
|
+
"positionals": [],
|
|
1618
|
+
"flags": {
|
|
1619
|
+
"--skip-install": {
|
|
1620
|
+
"type": "boolean",
|
|
1621
|
+
"required": false,
|
|
1622
|
+
"repeatable": false
|
|
1623
|
+
},
|
|
1624
|
+
"--dry-run": {
|
|
1625
|
+
"type": "boolean",
|
|
1626
|
+
"required": false,
|
|
1627
|
+
"repeatable": false
|
|
1628
|
+
},
|
|
1629
|
+
"--json": {
|
|
1630
|
+
"type": "boolean",
|
|
1631
|
+
"required": false,
|
|
1632
|
+
"repeatable": false
|
|
1633
|
+
}
|
|
1634
|
+
},
|
|
1635
|
+
"groups": []
|
|
1636
|
+
},
|
|
1550
1637
|
"storage deprovision": {
|
|
1551
1638
|
"positionals": [],
|
|
1552
1639
|
"flags": {
|
|
@@ -1586,11 +1673,6 @@ export const COMMANDS = {
|
|
|
1586
1673
|
"required": false,
|
|
1587
1674
|
"repeatable": false
|
|
1588
1675
|
},
|
|
1589
|
-
"--api-token": {
|
|
1590
|
-
"type": "string",
|
|
1591
|
-
"required": false,
|
|
1592
|
-
"repeatable": false
|
|
1593
|
-
},
|
|
1594
1676
|
"--r2-access-key-id": {
|
|
1595
1677
|
"type": "string",
|
|
1596
1678
|
"required": false,
|
|
@@ -1986,6 +2068,34 @@ export const COMMANDS = {
|
|
|
1986
2068
|
},
|
|
1987
2069
|
"groups": []
|
|
1988
2070
|
},
|
|
2071
|
+
"token forget": {
|
|
2072
|
+
"positionals": [
|
|
2073
|
+
{
|
|
2074
|
+
"name": "profile",
|
|
2075
|
+
"required": true,
|
|
2076
|
+
"variadic": false
|
|
2077
|
+
}
|
|
2078
|
+
],
|
|
2079
|
+
"flags": {
|
|
2080
|
+
"--env": {
|
|
2081
|
+
"type": "string",
|
|
2082
|
+
"required": false,
|
|
2083
|
+
"repeatable": false,
|
|
2084
|
+
"default": "dev"
|
|
2085
|
+
},
|
|
2086
|
+
"--force": {
|
|
2087
|
+
"type": "boolean",
|
|
2088
|
+
"required": false,
|
|
2089
|
+
"repeatable": false
|
|
2090
|
+
},
|
|
2091
|
+
"--json": {
|
|
2092
|
+
"type": "boolean",
|
|
2093
|
+
"required": false,
|
|
2094
|
+
"repeatable": false
|
|
2095
|
+
}
|
|
2096
|
+
},
|
|
2097
|
+
"groups": []
|
|
2098
|
+
},
|
|
1989
2099
|
"token list": {
|
|
1990
2100
|
"positionals": [],
|
|
1991
2101
|
"flags": {
|
|
@@ -2046,7 +2156,12 @@ export const COMMANDS = {
|
|
|
2046
2156
|
"vectorize:write",
|
|
2047
2157
|
"ai:read",
|
|
2048
2158
|
"zone:read",
|
|
2049
|
-
"routes:write"
|
|
2159
|
+
"routes:write",
|
|
2160
|
+
"analytics:read",
|
|
2161
|
+
"images:write",
|
|
2162
|
+
"stream:write",
|
|
2163
|
+
"queues:write",
|
|
2164
|
+
"artifacts:write"
|
|
2050
2165
|
]
|
|
2051
2166
|
},
|
|
2052
2167
|
"--json": {
|
|
@@ -2123,7 +2238,12 @@ export const COMMANDS = {
|
|
|
2123
2238
|
"vectorize:write",
|
|
2124
2239
|
"ai:read",
|
|
2125
2240
|
"zone:read",
|
|
2126
|
-
"routes:write"
|
|
2241
|
+
"routes:write",
|
|
2242
|
+
"analytics:read",
|
|
2243
|
+
"images:write",
|
|
2244
|
+
"stream:write",
|
|
2245
|
+
"queues:write",
|
|
2246
|
+
"artifacts:write"
|
|
2127
2247
|
]
|
|
2128
2248
|
},
|
|
2129
2249
|
"--keep-previous": {
|
|
@@ -2416,6 +2536,32 @@ export const COMMANDS = {
|
|
|
2416
2536
|
},
|
|
2417
2537
|
"groups": []
|
|
2418
2538
|
},
|
|
2539
|
+
"worker bind": {
|
|
2540
|
+
"positionals": [],
|
|
2541
|
+
"flags": {
|
|
2542
|
+
"--from": {
|
|
2543
|
+
"type": "string",
|
|
2544
|
+
"required": false,
|
|
2545
|
+
"repeatable": false
|
|
2546
|
+
},
|
|
2547
|
+
"--to": {
|
|
2548
|
+
"type": "string",
|
|
2549
|
+
"required": false,
|
|
2550
|
+
"repeatable": false
|
|
2551
|
+
},
|
|
2552
|
+
"--binding": {
|
|
2553
|
+
"type": "string",
|
|
2554
|
+
"required": false,
|
|
2555
|
+
"repeatable": false
|
|
2556
|
+
},
|
|
2557
|
+
"--json": {
|
|
2558
|
+
"type": "boolean",
|
|
2559
|
+
"required": false,
|
|
2560
|
+
"repeatable": false
|
|
2561
|
+
}
|
|
2562
|
+
},
|
|
2563
|
+
"groups": []
|
|
2564
|
+
},
|
|
2419
2565
|
"worker list": {
|
|
2420
2566
|
"positionals": [],
|
|
2421
2567
|
"flags": {
|
|
@@ -2447,17 +2593,27 @@ export const COMMANDS = {
|
|
|
2447
2593
|
"worker rename": {
|
|
2448
2594
|
"positionals": [
|
|
2449
2595
|
{
|
|
2450
|
-
"name": "
|
|
2451
|
-
"required":
|
|
2596
|
+
"name": "old",
|
|
2597
|
+
"required": false,
|
|
2452
2598
|
"variadic": false
|
|
2453
2599
|
},
|
|
2454
2600
|
{
|
|
2455
|
-
"name": "
|
|
2456
|
-
"required":
|
|
2601
|
+
"name": "new",
|
|
2602
|
+
"required": false,
|
|
2457
2603
|
"variadic": false
|
|
2458
2604
|
}
|
|
2459
2605
|
],
|
|
2460
2606
|
"flags": {
|
|
2607
|
+
"--from": {
|
|
2608
|
+
"type": "string",
|
|
2609
|
+
"required": false,
|
|
2610
|
+
"repeatable": false
|
|
2611
|
+
},
|
|
2612
|
+
"--to": {
|
|
2613
|
+
"type": "string",
|
|
2614
|
+
"required": false,
|
|
2615
|
+
"repeatable": false
|
|
2616
|
+
},
|
|
2461
2617
|
"--force": {
|
|
2462
2618
|
"type": "boolean",
|
|
2463
2619
|
"required": false,
|
|
@@ -2496,6 +2652,40 @@ export const COMMANDS = {
|
|
|
2496
2652
|
}
|
|
2497
2653
|
},
|
|
2498
2654
|
"groups": []
|
|
2655
|
+
},
|
|
2656
|
+
"worker unbind": {
|
|
2657
|
+
"positionals": [],
|
|
2658
|
+
"flags": {
|
|
2659
|
+
"--from": {
|
|
2660
|
+
"type": "string",
|
|
2661
|
+
"required": false,
|
|
2662
|
+
"repeatable": false
|
|
2663
|
+
},
|
|
2664
|
+
"--to": {
|
|
2665
|
+
"type": "string",
|
|
2666
|
+
"required": false,
|
|
2667
|
+
"repeatable": false
|
|
2668
|
+
},
|
|
2669
|
+
"--binding": {
|
|
2670
|
+
"type": "string",
|
|
2671
|
+
"required": false,
|
|
2672
|
+
"repeatable": false
|
|
2673
|
+
},
|
|
2674
|
+
"--json": {
|
|
2675
|
+
"type": "boolean",
|
|
2676
|
+
"required": false,
|
|
2677
|
+
"repeatable": false
|
|
2678
|
+
}
|
|
2679
|
+
},
|
|
2680
|
+
"groups": [
|
|
2681
|
+
{
|
|
2682
|
+
"kind": "exactlyOne",
|
|
2683
|
+
"flags": [
|
|
2684
|
+
"--to",
|
|
2685
|
+
"--binding"
|
|
2686
|
+
]
|
|
2687
|
+
}
|
|
2688
|
+
]
|
|
2499
2689
|
}
|
|
2500
2690
|
} as const;
|
|
2501
2691
|
|
package/src/cli/commandFacts.ts
CHANGED
|
@@ -13,11 +13,18 @@ import { ControlPlaneConfig, type ControlPlaneConfigInput } from "./config/confi
|
|
|
13
13
|
import { ControlPlaneConnection } from "./data/connection";
|
|
14
14
|
import { CONTROL_PLANE_CONNECTIONS_TABLE, type ControlPlaneDatabase, controlPlaneTables } from "./data/tables";
|
|
15
15
|
import type { CapabilityDeclaration } from "./discovery/adminRoute";
|
|
16
|
-
import { type CapabilityHealthSource, capabilityHealthSources } from "./discovery/health";
|
|
16
|
+
import { type CapabilityHealthSource, capabilityHealthSources, defineCapabilityHealth } from "./discovery/health";
|
|
17
17
|
import { createControlPlaneVerifier } from "./http/guard";
|
|
18
18
|
import { controlPlaneRouteDescriptors, registerControlPlaneRoutes } from "./http/routes";
|
|
19
|
+
import {
|
|
20
|
+
type ComposedMigrationDatabase,
|
|
21
|
+
composeMigrationDatabases,
|
|
22
|
+
MIGRATION_HEALTH_KEYS,
|
|
23
|
+
readMigrationHealth,
|
|
24
|
+
} from "./migrationState/migrationState";
|
|
19
25
|
import { controlplane_0001_init } from "./migrations/0001_init";
|
|
20
26
|
import { controlplane_0002_management_origin } from "./migrations/0002_management_origin";
|
|
27
|
+
import { controlplane_0003_connection_principal } from "./migrations/0003_connection_principal";
|
|
21
28
|
import { d1ReplayGuard } from "./replay/d1Guard";
|
|
22
29
|
import type { ReplayGuard } from "./replay/guard";
|
|
23
30
|
import {
|
|
@@ -79,6 +86,7 @@ export function controlplane(options: ControlPlaneOptions = {}): ControlPlaneCap
|
|
|
79
86
|
const migrations: Record<string, Migration> = {
|
|
80
87
|
"0001_init": controlplane_0001_init,
|
|
81
88
|
"0002_management_origin": controlplane_0002_management_origin,
|
|
89
|
+
"0003_connection_principal": controlplane_0003_connection_principal,
|
|
82
90
|
};
|
|
83
91
|
const requiredBindings: BindingSpecInput[] = [
|
|
84
92
|
// The app database — the connections table lives here, beside auth's and audit's. Not the secrets
|
|
@@ -132,6 +140,12 @@ export function controlplane(options: ControlPlaneOptions = {}): ControlPlaneCap
|
|
|
132
140
|
// assembly, because an adopter is never offered such a scope and the number could never be granted.
|
|
133
141
|
let health: ReadonlyMap<string, CapabilityHealthSource> = new Map();
|
|
134
142
|
|
|
143
|
+
// Every database this Worker binds and what this build declares for it (#729), composed by the same hook
|
|
144
|
+
// through `collectMigrationSets` — the function `pithy migrate` uses, so the route and `pithy doctor`
|
|
145
|
+
// compose the same way. The inputs differ on a D1 two Workers share: the CLI merges every co-bound
|
|
146
|
+
// Worker's sets and this Worker holds only its own, so `pending` and `undeclared` can differ there.
|
|
147
|
+
let migrationDatabases: readonly ComposedMigrationDatabase[] = [];
|
|
148
|
+
|
|
135
149
|
const capability = defineCapability({
|
|
136
150
|
name: "controlplane",
|
|
137
151
|
// `@pithy-sh/core`'s version, stamped by `scripts/stampVersions.ts` — a Worker cannot read its own
|
|
@@ -164,6 +178,14 @@ export function controlplane(options: ControlPlaneOptions = {}): ControlPlaneCap
|
|
|
164
178
|
// The seam describes itself too. Built from the same resolved `basePath` the routes mount on, so a
|
|
165
179
|
// moved mount point is reported rather than becoming a lie a client believes.
|
|
166
180
|
adminRoutes: controlPlaneRouteDescriptors(config.basePath),
|
|
181
|
+
// Two counts on the seam's own manifest entry, so a client sees *this environment is behind* on the read
|
|
182
|
+
// it already makes and calls the route only to see what. Behind `migrations:read`, which the route above
|
|
183
|
+
// requires — a connection without it gets no number rather than a zero. A database that cannot be read
|
|
184
|
+
// fails the producer, and the seam reports the summary unavailable rather than a partial total.
|
|
185
|
+
health: defineCapabilityHealth({
|
|
186
|
+
keys: MIGRATION_HEALTH_KEYS,
|
|
187
|
+
read: (c) => readMigrationHealth(c.env as Record<string, unknown>, migrationDatabases),
|
|
188
|
+
}),
|
|
167
189
|
compose: ({ capabilities }) => {
|
|
168
190
|
// Every capability, including those exposing nothing. A client rendering a capability it cannot
|
|
169
191
|
// act on is useful; a client that cannot tell "no admin surface" from "not installed" is not.
|
|
@@ -186,6 +208,7 @@ export function controlplane(options: ControlPlaneOptions = {}): ControlPlaneCap
|
|
|
186
208
|
config: { ...(cap.manifestConfig?.values ?? {}) },
|
|
187
209
|
}));
|
|
188
210
|
health = capabilityHealthSources(capabilities);
|
|
211
|
+
migrationDatabases = composeMigrationDatabases(capabilities);
|
|
189
212
|
},
|
|
190
213
|
middleware: [
|
|
191
214
|
(app) => {
|
|
@@ -228,6 +251,7 @@ export function controlplane(options: ControlPlaneOptions = {}): ControlPlaneCap
|
|
|
228
251
|
database,
|
|
229
252
|
composedCapabilities: () => composed,
|
|
230
253
|
composedHealth: () => health,
|
|
254
|
+
composedMigrations: () => migrationDatabases,
|
|
231
255
|
now,
|
|
232
256
|
});
|
|
233
257
|
},
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { ControlPlaneActorKind } from "./data/connection";
|
|
5
6
|
import { ControlPlaneScope } from "./scope/scope";
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -56,6 +57,9 @@ export const ControlPlaneContext = z
|
|
|
56
57
|
tokenId: z
|
|
57
58
|
.string()
|
|
58
59
|
.describe("The token's `jti`, already claimed in the replay set. Recorded to tie one audit event to one call."),
|
|
60
|
+
actorKind: ControlPlaneActorKind.nullable().describe(
|
|
61
|
+
"What kind of client the connection is for — `agent`, `dashboard` or `script` — read from the adopter's own row, never from the token. The gate stamps it onto every control-plane audit row this call writes. Null for a connection registered before the kind was recorded (#721).",
|
|
62
|
+
),
|
|
59
63
|
})
|
|
60
64
|
.describe(
|
|
61
65
|
"The verified control-plane caller for one request. Never an AuthContext: a management client is not a user of the adopter's app.",
|