@pithy-sh/core 0.7.0 → 0.7.2

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/controlPlane/capability.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,UAAU,EAAuC,MAAM,0BAA0B,CAAC;AAChG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAc,MAAM,kBAAkB,CAAC;AAGvE,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAUnF,OAAO,EACL,wBAAwB,EAIzB,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,MAAM,MAAM,mBAAmB,GAAG,uBAAuB,CAAC;AAE1D,uEAAuE;AACvE,MAAM,WAAW,sBAAuB,SAAQ,UAAU,CAAC,eAAe,EAAE,kBAAkB,EAAE,cAAc,CAAC;IAC7G,gCAAgC;IAChC,kBAAkB,EAAE,kBAAkB,CAAC;CACxC;AAED,qGAAqG;AACrG,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,IAAI,sBAAsB,CAErG;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,sBAAsB,CAwJtF;AAED,4GAA4G;AAC5G,OAAO,EAAE,wBAAwB,EAAE,CAAC"}
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../../src/controlPlane/capability.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,UAAU,EAAuC,MAAM,0BAA0B,CAAC;AAChG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAc,MAAM,kBAAkB,CAAC;AAGvE,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAWnF,OAAO,EACL,wBAAwB,EAIzB,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,MAAM,MAAM,mBAAmB,GAAG,uBAAuB,CAAC;AAE1D,uEAAuE;AACvE,MAAM,WAAW,sBAAuB,SAAQ,UAAU,CAAC,eAAe,EAAE,kBAAkB,EAAE,cAAc,CAAC;IAC7G,gCAAgC;IAChC,kBAAkB,EAAE,kBAAkB,CAAC;CACxC;AAED,qGAAqG;AACrG,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,IAAI,sBAAsB,CAErG;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,sBAAsB,CAyJtF;AAED,4GAA4G;AAC5G,OAAO,EAAE,wBAAwB,EAAE,CAAC"}
@@ -10,6 +10,7 @@ import { capabilityHealthSources } from "./discovery/health.js";
10
10
  import { createControlPlaneVerifier } from "./http/guard.js";
11
11
  import { controlPlaneRouteDescriptors, registerControlPlaneRoutes } from "./http/routes.js";
12
12
  import { controlplane_0001_init } from "./migrations/0001_init.js";
13
+ import { controlplane_0002_management_origin } from "./migrations/0002_management_origin.js";
13
14
  import { d1ReplayGuard } from "./replay/d1Guard.js";
14
15
  import { CONTROL_PLANE_KV_BINDING, controlPlaneKvNamespaces, kvReplayGuard } from "./replay/kvGuard.js";
15
16
  //#region src/controlPlane/capability.ts
@@ -49,7 +50,10 @@ function isControlPlaneCapability(capability) {
49
50
  */
50
51
  function controlplane(options = {}) {
51
52
  const config = ControlPlaneConfig.parse(options);
52
- const migrations = { "0001_init": controlplane_0001_init };
53
+ const migrations = {
54
+ "0001_init": controlplane_0001_init,
55
+ "0002_management_origin": controlplane_0002_management_origin
56
+ };
53
57
  const requiredBindings = [{
54
58
  type: "d1",
55
59
  name: "DB"
@@ -51,6 +51,7 @@ export declare const ControlPlaneConnection: z.ZodObject<{
51
51
  issuer: z.ZodURL;
52
52
  workerUrl: z.ZodURL;
53
53
  basePath: z.ZodDefault<z.ZodString>;
54
+ managementOrigin: z.ZodDefault<z.ZodNullable<z.ZodURL>>;
54
55
  scopes: z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodArray<z.ZodString>>;
55
56
  keys: z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodObject<{
56
57
  keyId: z.ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/data/connection.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;GASG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB;;;;iBAgBoF,CAAC;AAClH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEjE;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;iBAoBoF,CAAC;AAC/G,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAE3D;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4ChC,CAAC;AACJ,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC7E,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/data/connection.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;GASG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB;;;;iBAgBoF,CAAC;AAClH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEjE;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;iBAoBoF,CAAC;AAC/G,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAE3D;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmDhC,CAAC;AACJ,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC7E,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
@@ -48,6 +48,7 @@ const ControlPlaneConnection = z.object({
48
48
  issuer: z.url().describe("The exact `iss` this connection accepts. Verified on every call, and effectively permanent — changing the management client's origin is a migration across every registered connection, not a DNS change."),
49
49
  workerUrl: z.url().describe("This environment's deployed Worker URL, captured at connect. The address the management client calls; re-pointed by `pithy dashboard connect --update` when a custom domain or a rename moves it."),
50
50
  basePath: z.string().min(1).default("/control-plane").describe("Where this Worker mounts the control-plane seam, captured at connect from its resolved config. Stored beside `workerUrl` because the two together fully determine the manifest address — and this is the one part of it a client cannot discover, since it *is* the manifest's own address. Defaulted rather than required so a connection registered before the column existed still parses, reading as the default it was necessarily using."),
51
+ managementOrigin: z.url().nullable().default(null).describe("Where the management client that registered this connection answers — the origin the CLI calls for a verify, a rotation or a disconnect. Stored because it is not derivable: `issuer` is what that client *signs as*, and nothing says a dashboard's API answers at its own `iss`. Null for a connection registered before this column, which reads as the issuer rather than as the hosted dashboard — an older row is still a row about somebody's own deployment. Nothing on the Worker's side reads it; verification cares about `issuer` alone."),
51
52
  scopes: sqliteJson(z.array(ControlPlaneScope)).describe("The operations this connection was granted, stored and enforced on the adopter's side. Enforced only by the caller, a scope would not be a limit at all."),
52
53
  keys: sqliteJson(z.array(RegisteredKey)).describe("Every key this connection may sign with, current and superseded. One JSON column rather than a child table, so a rotation is one atomic UPDATE and verification is one read."),
53
54
  createdAt: SQLiteDate.describe("When the connection was registered. Ms-epoch in SQLite, a Date in TypeScript."),
@@ -0,0 +1,23 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { Migration } from "kysely/migration";
4
+ /**
5
+ * Where the management client that registered a connection answers — `#614`.
6
+ *
7
+ * **Appended, not folded into `0001`.** The seam has shipped and adopters' databases hold rows written
8
+ * by it; a column added to the initial migration is history rewritten under a ledger that already
9
+ * records `0001_init` as applied. So this is a second migration, and `0001` stays what it was.
10
+ *
11
+ * **Nullable, with no default.** Every other address on the row has an obvious value when it is absent
12
+ * — a seam is mounted somewhere, so `basePath` defaults. An origin does not: a row written before this
13
+ * column knows only what its dashboard *signs* as, and defaulting it to the hosted origin would assert
14
+ * that every existing self-hosted connection belongs to `app.pithy.sh`. That is the assertion that
15
+ * caused #614 in the first place. Null means "not recorded", the CLI falls back to the issuer, and the
16
+ * next `connect` writes the real answer.
17
+ *
18
+ * **The Worker never reads it.** Verification matches a token's `iss` against `issuer`; this column
19
+ * exists for the operator's side, so that `status --verify`, `rotate` and `disconnect` call the
20
+ * dashboard the connection was registered against rather than whichever one a flag's default names.
21
+ */
22
+ export declare const controlplane_0002_management_origin: Migration;
23
+ //# sourceMappingURL=0002_management_origin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0002_management_origin.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/migrations/0002_management_origin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mCAAmC,EAAE,SAWjD,CAAC"}
@@ -0,0 +1,31 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ //#region src/controlPlane/migrations/0002_management_origin.ts
4
+ /**
5
+ * Where the management client that registered a connection answers — `#614`.
6
+ *
7
+ * **Appended, not folded into `0001`.** The seam has shipped and adopters' databases hold rows written
8
+ * by it; a column added to the initial migration is history rewritten under a ledger that already
9
+ * records `0001_init` as applied. So this is a second migration, and `0001` stays what it was.
10
+ *
11
+ * **Nullable, with no default.** Every other address on the row has an obvious value when it is absent
12
+ * — a seam is mounted somewhere, so `basePath` defaults. An origin does not: a row written before this
13
+ * column knows only what its dashboard *signs* as, and defaulting it to the hosted origin would assert
14
+ * that every existing self-hosted connection belongs to `app.pithy.sh`. That is the assertion that
15
+ * caused #614 in the first place. Null means "not recorded", the CLI falls back to the issuer, and the
16
+ * next `connect` writes the real answer.
17
+ *
18
+ * **The Worker never reads it.** Verification matches a token's `iss` against `issuer`; this column
19
+ * exists for the operator's side, so that `status --verify`, `rotate` and `disconnect` call the
20
+ * dashboard the connection was registered against rather than whichever one a flag's default names.
21
+ */
22
+ const controlplane_0002_management_origin = {
23
+ up: async (db) => {
24
+ await db.schema.alterTable("pithyControlplaneConnections").addColumn("managementOrigin", "text").execute();
25
+ },
26
+ down: async (db) => {
27
+ await db.schema.alterTable("pithyControlplaneConnections").dropColumn("managementOrigin").execute();
28
+ }
29
+ };
30
+ //#endregion
31
+ export { controlplane_0002_management_origin };
@@ -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/core";
5
5
  /** This package's version, stamped from its own package.json at generation time. */
6
- export declare const PACKAGE_VERSION = "0.7.0";
6
+ export declare const PACKAGE_VERSION = "0.7.2";
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/core";
6
6
  /** This package's version, stamped from its own package.json at generation time. */
7
- const PACKAGE_VERSION = "0.7.0";
7
+ const PACKAGE_VERSION = "0.7.2";
8
8
  //#endregion
9
9
  export { PACKAGE_NAME, PACKAGE_VERSION };
@@ -65,6 +65,20 @@ export declare const WORKER_VAR = "WORKER";
65
65
  * silently absent in every scaffolded project.
66
66
  */
67
67
  export declare const VERSION_METADATA_BINDING = "CF_VERSION_METADATA";
68
+ /**
69
+ * The service binding a Worker reaches **itself** through (#616).
70
+ *
71
+ * **A Worker cannot fetch its own hostname.** The subrequest leaves the isolate, loops back through the
72
+ * edge into the Worker it came from, and hangs until Cloudflare answers 522 — on a deployment where the
73
+ * same route answers `401` in 1.4s from outside. So a project that administers itself dispatches inside
74
+ * the runtime instead: `env.SELF.fetch(request)` reaches the same script with no edge hop.
75
+ *
76
+ * Like the three vars above, it is a name that has to be agreed rather than derived — the binding
77
+ * `pithy provision` writes into every stanza it generates for a project that declares
78
+ * `administersItself`, and the one an app reads off `env`. It lives here so neither side, in this
79
+ * repository or in a management client built on it, spells it a second way.
80
+ */
81
+ export declare const SELF_BINDING = "SELF";
68
82
  /** Where a Worker is running, as it can state about itself. Every field is `null` when unstamped. */
69
83
  export interface WorkerIdentity {
70
84
  /** The owning project, or `null` when the Worker carries no `PROJECT` var. */
@@ -1 +1 @@
1
- {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/worker/identity.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,qFAAqF;AACrF,eAAO,MAAM,WAAW,YAAY,CAAC;AAErC,2FAA2F;AAC3F,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAEvD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,WAAW,CAAC;AAEnC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,wBAAwB,CAAC;AAE9D,qGAAqG;AACrG,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,wEAAwE;IACxE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,2EAA2E;IAC3E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;;;;;;OAQG;IACH,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,qBAAqB;IACpC,yGAAyG;IACzG,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAgBD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAW3D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,qBAAqB,CAazE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAEzD"}
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/worker/identity.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,qFAAqF;AACrF,eAAO,MAAM,WAAW,YAAY,CAAC;AAErC,2FAA2F;AAC3F,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAEvD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,WAAW,CAAC;AAEnC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,wBAAwB,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC,qGAAqG;AACrG,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,wEAAwE;IACxE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,2EAA2E;IAC3E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;;;;;;OAQG;IACH,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,qBAAqB;IACpC,yGAAyG;IACzG,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAgBD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAW3D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,qBAAqB,CAazE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAEzD"}
@@ -66,6 +66,20 @@ const WORKER_VAR = "WORKER";
66
66
  * silently absent in every scaffolded project.
67
67
  */
68
68
  const VERSION_METADATA_BINDING = "CF_VERSION_METADATA";
69
+ /**
70
+ * The service binding a Worker reaches **itself** through (#616).
71
+ *
72
+ * **A Worker cannot fetch its own hostname.** The subrequest leaves the isolate, loops back through the
73
+ * edge into the Worker it came from, and hangs until Cloudflare answers 522 — on a deployment where the
74
+ * same route answers `401` in 1.4s from outside. So a project that administers itself dispatches inside
75
+ * the runtime instead: `env.SELF.fetch(request)` reaches the same script with no edge hop.
76
+ *
77
+ * Like the three vars above, it is a name that has to be agreed rather than derived — the binding
78
+ * `pithy provision` writes into every stanza it generates for a project that declares
79
+ * `administersItself`, and the one an app reads off `env`. It lives here so neither side, in this
80
+ * repository or in a management client built on it, spells it a second way.
81
+ */
82
+ const SELF_BINDING = "SELF";
69
83
  /** One binding field, or `null` for anything that is not a non-empty string. */
70
84
  function carried(meta, field) {
71
85
  const value = meta[field];
@@ -161,4 +175,4 @@ function workerVersion(env) {
161
175
  return workerVersionMetadata(env).id;
162
176
  }
163
177
  //#endregion
164
- export { ENVIRONMENT_VAR, PROJECT_VAR, VERSION_METADATA_BINDING, WORKER_ORIGIN_VAR, WORKER_VAR, workerIdentity, workerVersion, workerVersionMetadata };
178
+ export { ENVIRONMENT_VAR, PROJECT_VAR, SELF_BINDING, VERSION_METADATA_BINDING, WORKER_ORIGIN_VAR, WORKER_VAR, workerIdentity, workerVersion, workerVersionMetadata };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pithy-sh/core",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,6 +17,7 @@ import { type CapabilityHealthSource, capabilityHealthSources } from "./discover
17
17
  import { createControlPlaneVerifier } from "./http/guard";
18
18
  import { controlPlaneRouteDescriptors, registerControlPlaneRoutes } from "./http/routes";
19
19
  import { controlplane_0001_init } from "./migrations/0001_init";
20
+ import { controlplane_0002_management_origin } from "./migrations/0002_management_origin";
20
21
  import { d1ReplayGuard } from "./replay/d1Guard";
21
22
  import type { ReplayGuard } from "./replay/guard";
22
23
  import {
@@ -77,6 +78,7 @@ export function controlplane(options: ControlPlaneOptions = {}): ControlPlaneCap
77
78
 
78
79
  const migrations: Record<string, Migration> = {
79
80
  "0001_init": controlplane_0001_init,
81
+ "0002_management_origin": controlplane_0002_management_origin,
80
82
  };
81
83
  const requiredBindings: BindingSpecInput[] = [
82
84
  // The app database — the connections table lives here, beside auth's and audit's. Not the secrets
@@ -105,6 +105,13 @@ export const ControlPlaneConnection = z
105
105
  .describe(
106
106
  "Where this Worker mounts the control-plane seam, captured at connect from its resolved config. Stored beside `workerUrl` because the two together fully determine the manifest address — and this is the one part of it a client cannot discover, since it *is* the manifest's own address. Defaulted rather than required so a connection registered before the column existed still parses, reading as the default it was necessarily using.",
107
107
  ),
108
+ managementOrigin: z
109
+ .url()
110
+ .nullable()
111
+ .default(null)
112
+ .describe(
113
+ "Where the management client that registered this connection answers — the origin the CLI calls for a verify, a rotation or a disconnect. Stored because it is not derivable: `issuer` is what that client *signs as*, and nothing says a dashboard's API answers at its own `iss`. Null for a connection registered before this column, which reads as the issuer rather than as the hosted dashboard — an older row is still a row about somebody's own deployment. Nothing on the Worker's side reads it; verification cares about `issuer` alone.",
114
+ ),
108
115
  scopes: sqliteJson(z.array(ControlPlaneScope)).describe(
109
116
  "The operations this connection was granted, stored and enforced on the adopter's side. Enforced only by the caller, a scope would not be a limit at all.",
110
117
  ),
@@ -0,0 +1,36 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { Kysely } from "kysely";
5
+ import type { Migration } from "kysely/migration";
6
+
7
+ /**
8
+ * Where the management client that registered a connection answers — `#614`.
9
+ *
10
+ * **Appended, not folded into `0001`.** The seam has shipped and adopters' databases hold rows written
11
+ * by it; a column added to the initial migration is history rewritten under a ledger that already
12
+ * records `0001_init` as applied. So this is a second migration, and `0001` stays what it was.
13
+ *
14
+ * **Nullable, with no default.** Every other address on the row has an obvious value when it is absent
15
+ * — a seam is mounted somewhere, so `basePath` defaults. An origin does not: a row written before this
16
+ * column knows only what its dashboard *signs* as, and defaulting it to the hosted origin would assert
17
+ * that every existing self-hosted connection belongs to `app.pithy.sh`. That is the assertion that
18
+ * caused #614 in the first place. Null means "not recorded", the CLI falls back to the issuer, and the
19
+ * next `connect` writes the real answer.
20
+ *
21
+ * **The Worker never reads it.** Verification matches a token's `iss` against `issuer`; this column
22
+ * exists for the operator's side, so that `status --verify`, `rotate` and `disconnect` call the
23
+ * dashboard the connection was registered against rather than whichever one a flag's default names.
24
+ */
25
+ export const controlplane_0002_management_origin: Migration = {
26
+ up: async (db: Kysely<unknown>): Promise<void> => {
27
+ await db.schema.alterTable("pithyControlplaneConnections").addColumn("managementOrigin", "text").execute();
28
+ },
29
+
30
+ down: async (db: Kysely<unknown>): Promise<void> => {
31
+ // SQLite has dropped columns since 3.35 and D1 carries a newer engine, so the twelve-step rebuild is
32
+ // not needed here — and a rebuild would be the riskier `down`, since it rewrites every row of a table
33
+ // holding live credentials to remove a column nothing reads.
34
+ await db.schema.alterTable("pithyControlplaneConnections").dropColumn("managementOrigin").execute();
35
+ },
36
+ };
@@ -13,4 +13,4 @@
13
13
  export const PACKAGE_NAME = "@pithy-sh/core";
14
14
 
15
15
  /** This package's version, stamped from its own package.json at generation time. */
16
- export const PACKAGE_VERSION = "0.7.0";
16
+ export const PACKAGE_VERSION = "0.7.2";
@@ -72,6 +72,21 @@ export const WORKER_VAR = "WORKER";
72
72
  */
73
73
  export const VERSION_METADATA_BINDING = "CF_VERSION_METADATA";
74
74
 
75
+ /**
76
+ * The service binding a Worker reaches **itself** through (#616).
77
+ *
78
+ * **A Worker cannot fetch its own hostname.** The subrequest leaves the isolate, loops back through the
79
+ * edge into the Worker it came from, and hangs until Cloudflare answers 522 — on a deployment where the
80
+ * same route answers `401` in 1.4s from outside. So a project that administers itself dispatches inside
81
+ * the runtime instead: `env.SELF.fetch(request)` reaches the same script with no edge hop.
82
+ *
83
+ * Like the three vars above, it is a name that has to be agreed rather than derived — the binding
84
+ * `pithy provision` writes into every stanza it generates for a project that declares
85
+ * `administersItself`, and the one an app reads off `env`. It lives here so neither side, in this
86
+ * repository or in a management client built on it, spells it a second way.
87
+ */
88
+ export const SELF_BINDING = "SELF";
89
+
75
90
  /** Where a Worker is running, as it can state about itself. Every field is `null` when unstamped. */
76
91
  export interface WorkerIdentity {
77
92
  /** The owning project, or `null` when the Worker carries no `PROJECT` var. */