@heychunky/core 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/admin/apps.d.ts +50 -0
  2. package/dist/admin/apps.d.ts.map +1 -1
  3. package/dist/admin/apps.js +69 -2
  4. package/dist/admin/apps.js.map +1 -1
  5. package/dist/admin/index.d.ts +2 -0
  6. package/dist/admin/index.d.ts.map +1 -1
  7. package/dist/admin/index.js +2 -0
  8. package/dist/admin/index.js.map +1 -1
  9. package/dist/admin/migrations.d.ts +19 -0
  10. package/dist/admin/migrations.d.ts.map +1 -1
  11. package/dist/admin/migrations.js +27 -0
  12. package/dist/admin/migrations.js.map +1 -1
  13. package/dist/admin/orgs.d.ts +142 -0
  14. package/dist/admin/orgs.d.ts.map +1 -0
  15. package/dist/admin/orgs.js +166 -0
  16. package/dist/admin/orgs.js.map +1 -0
  17. package/dist/admin/provision.d.ts +10 -0
  18. package/dist/admin/provision.d.ts.map +1 -1
  19. package/dist/admin/provision.js +1 -1
  20. package/dist/admin/provision.js.map +1 -1
  21. package/dist/admin/register.d.ts +98 -0
  22. package/dist/admin/register.d.ts.map +1 -0
  23. package/dist/admin/register.js +191 -0
  24. package/dist/admin/register.js.map +1 -0
  25. package/dist/admin/run.d.ts.map +1 -1
  26. package/dist/admin/run.js +14 -1
  27. package/dist/admin/run.js.map +1 -1
  28. package/dist/admin/services/github.d.ts.map +1 -1
  29. package/dist/admin/services/github.js +15 -1
  30. package/dist/admin/services/github.js.map +1 -1
  31. package/dist/admin/services/vercel.d.ts +2 -0
  32. package/dist/admin/services/vercel.d.ts.map +1 -1
  33. package/dist/admin/services/vercel.js.map +1 -1
  34. package/dist/admin/users.d.ts +34 -0
  35. package/dist/admin/users.d.ts.map +1 -1
  36. package/dist/admin/users.js +45 -3
  37. package/dist/admin/users.js.map +1 -1
  38. package/dist/registry.d.ts +40 -0
  39. package/dist/registry.d.ts.map +1 -1
  40. package/dist/registry.js +27 -0
  41. package/dist/registry.js.map +1 -1
  42. package/dist/services/db.d.ts +16 -0
  43. package/dist/services/db.d.ts.map +1 -1
  44. package/dist/services/db.js +27 -22
  45. package/dist/services/db.js.map +1 -1
  46. package/package.json +3 -2
  47. package/register/0001-baseline/01-invite-requests.sql +11 -0
  48. package/register/0001-baseline/02-invite-requests-email-key.sql +1 -0
  49. package/register/0001-baseline/03-login-codes.sql +12 -0
  50. package/register/0001-baseline/04-login-codes-email-idx.sql +1 -0
  51. package/register/0001-baseline/05-notifications.sql +11 -0
  52. package/register/0001-baseline/06-notifications-unread-idx.sql +1 -0
  53. package/register/0001-baseline/07-users.sql +13 -0
  54. package/register/0001-baseline/08-users-email-key.sql +1 -0
  55. package/register/0001-baseline/09-users-one-superadmin.sql +1 -0
  56. package/register/0001-baseline/10-users-username-unique.sql +1 -0
  57. package/register/0001-baseline/11-api-tokens.sql +13 -0
  58. package/register/0001-baseline/12-apps.sql +14 -0
  59. package/register/0001-baseline/13-apps-name-key.sql +1 -0
  60. package/register/0001-baseline/14-apps-owner-idx.sql +1 -0
  61. package/register/0001-baseline/15-contexts.sql +9 -0
  62. package/register/0001-baseline/16-credit-ledger.sql +10 -0
  63. package/register/0001-baseline/17-credit-ledger-user.sql +1 -0
  64. package/register/0001-baseline/18-runs.sql +16 -0
  65. package/register/0001-baseline/19-sessions.sql +10 -0
  66. package/register/0001-baseline/20-sessions-user-idx.sql +1 -0
  67. package/register/0001-baseline/21-app-chunks.sql +7 -0
  68. package/register/0001-baseline/22-context-members.sql +10 -0
  69. package/register/0002-orgs/01-orgs.sql +14 -0
  70. package/register/0002-orgs/02-orgs-name-key.sql +1 -0
  71. package/register/0002-orgs/03-org-members.sql +10 -0
  72. package/register/0002-orgs/04-org-members-user-idx.sql +1 -0
  73. package/register/0002-orgs/05-apps-org-id.sql +1 -0
  74. package/register/0002-orgs/06-apps-org-idx.sql +1 -0
  75. package/register/0002-orgs/07-apps-org-name-key.sql +1 -0
  76. package/register/0002-orgs/08-credit-ledger-org-id.sql +1 -0
  77. package/register/0002-orgs/09-credit-ledger-org-idx.sql +1 -0
  78. package/register/0002-orgs/10-api-tokens-org-id.sql +1 -0
  79. package/register/0002-orgs/11-api-tokens-created-by.sql +1 -0
  80. package/register/0002-orgs/12-backfill-personal-orgs.sql +15 -0
  81. package/register/0002-orgs/13-backfill-org-members.sql +12 -0
  82. package/register/0002-orgs/14-backfill-apps-org.sql +14 -0
  83. package/register/0002-orgs/15-backfill-credit-ledger-org.sql +11 -0
  84. package/register/0002-orgs/16-backfill-api-tokens-org.sql +12 -0
  85. package/register/README.md +73 -0
@@ -19,6 +19,9 @@ import type { User } from "./users.ts";
19
19
  export interface App {
20
20
  id: string;
21
21
  name: string;
22
+ /** The tenancy boundary. Null only for an owner who has claimed no username. */
23
+ org_id: string | null;
24
+ /** Who created it. Provenance, not tenancy — see orgs.ts. */
22
25
  owner_id: string;
23
26
  org: string | null;
24
27
  repo: string | null;
@@ -70,6 +73,53 @@ export declare function assertCanCreate(owner: string, register: Register): Prom
70
73
  * what a first, partial run could not, rather than making a second row.
71
74
  */
72
75
  export declare function recordApp(app: AppRecord, register: Register): Promise<App>;
76
+ /**
77
+ * What a run found at the providers, in the shape the register stores.
78
+ *
79
+ * A step's detail is `name · extra` — `carmelcity/carmelcity.ai · private`,
80
+ * `carmelcity-ai`, `Carmel City Main` — so the name is everything before the
81
+ * separator. Three callers were each doing that split themselves; a fourth
82
+ * spelling it a fourth way is how the register ends up holding a repository
83
+ * called `carmelcity/carmelcity.ai ·`.
84
+ */
85
+ export declare function detailsFromRun(steps: readonly StepResult[]): Pick<AppRecord, "repo" | "vercelProject" | "neonProject">;
86
+ /** Refused because there is nothing of this app anywhere to adopt. */
87
+ export declare class NothingToAdoptError extends Error {
88
+ constructor(name: string);
89
+ }
90
+ /**
91
+ * Record an app the factory did not create.
92
+ *
93
+ * The register was only ever written at the end of a successful provision run,
94
+ * which is right for an app Chunky made and useless for one that predates it.
95
+ * An app can exist — repository, database, deployment, all live for months —
96
+ * and be invisible to every command that reads the register.
97
+ *
98
+ * This is deliberately not `--adopt`, which means *create the missing pieces
99
+ * on a live app*. This creates nothing, ever: it takes a walk that already
100
+ * happened in plan mode and writes down what that walk found.
101
+ *
102
+ * Refusing when nothing exists is the guard that matters. Recording an app
103
+ * that is not there would put a row in the register pointing at no repository
104
+ * and no database, and every later command would believe it.
105
+ */
106
+ export declare function adoptApp(steps: readonly StepResult[], app: {
107
+ name: string;
108
+ owner: string;
109
+ org?: string | undefined;
110
+ /**
111
+ * A Neon project the walk cannot find by name.
112
+ *
113
+ * `matchByName` generates the forms a provider plausibly used, and a
114
+ * project somebody named by hand years ago is often none of them —
115
+ * `carmelcity.ai` never reaches `Carmel City Main`. No lookup fixes that,
116
+ * because the two strings share no rule. So it is stated rather than
117
+ * guessed, and the caller is expected to have checked it is real: a
118
+ * register pointing at a database that does not exist is worse than one
119
+ * admitting it does not know.
120
+ */
121
+ neonProject?: string | undefined;
122
+ }, register: Register): Promise<App>;
73
123
  /** Apps an owner has, or every app when none is named. */
74
124
  /**
75
125
  * One app, if this operator may have it.
@@ -1 +1 @@
1
- {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/admin/apps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,WAAW,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,WAAW,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;CAClC;AAED,sEAAsE;AACtE,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBACX,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAKzC;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,UAAU,EAAE,GAAG,SAAS,CAAC;CACxC;AAED,2EAA2E;AAC3E,wBAAsB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAc5F;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBtF;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAiBhF;AAED,0DAA0D;AAC1D;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,MAAM,CAC1B,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAAC,EAC/B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC,CAQ1B;AAED,wBAAsB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAU5F"}
1
+ {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/admin/apps.ts"],"names":[],"mappings":"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"}
@@ -66,9 +66,23 @@ export async function assertCanCreate(owner, register) {
66
66
  */
67
67
  export async function recordApp(app, register) {
68
68
  const conn = register;
69
- const { rows } = await conn.write(`insert into apps (name, owner_id, org, repo, vercel_project, neon_project, provisioned)
70
- values ($1, $2, $3, $4, $5, $6, $7)
69
+ const { rows } = await conn.write(
70
+ // `org_id` is derived rather than passed, and derived the same way the
71
+ // backfill derived it: through the owner's username to their personal
72
+ // organisation. A caller that had to supply it is a caller that can supply
73
+ // the wrong one, and the invariant `heychunky register verify` asserts —
74
+ // every app is in an organisation — would then hold only until the next
75
+ // app was made.
76
+ //
77
+ // Kept on conflict, never overwritten. Re-running provisioning fills in
78
+ // what a partial run could not; it must not move an app between tenants.
79
+ `insert into apps (name, owner_id, org_id, org, repo, vercel_project, neon_project, provisioned)
80
+ values ($1, $2,
81
+ (select o.id from orgs o join users u on lower(o.name) = lower(u.username)
82
+ where u.id = $2 and o.kind = 'personal'),
83
+ $3, $4, $5, $6, $7)
71
84
  on conflict (lower(name)) do update set
85
+ org_id = coalesce(apps.org_id, excluded.org_id),
72
86
  org = coalesce(excluded.org, apps.org),
73
87
  repo = coalesce(excluded.repo, apps.repo),
74
88
  vercel_project = coalesce(excluded.vercel_project, apps.vercel_project),
@@ -79,6 +93,59 @@ export async function recordApp(app, register) {
79
93
  app.provisioned ? JSON.stringify(app.provisioned) : null]);
80
94
  return rows[0];
81
95
  }
96
+ /**
97
+ * What a run found at the providers, in the shape the register stores.
98
+ *
99
+ * A step's detail is `name · extra` — `carmelcity/carmelcity.ai · private`,
100
+ * `carmelcity-ai`, `Carmel City Main` — so the name is everything before the
101
+ * separator. Three callers were each doing that split themselves; a fourth
102
+ * spelling it a fourth way is how the register ends up holding a repository
103
+ * called `carmelcity/carmelcity.ai ·`.
104
+ */
105
+ export function detailsFromRun(steps) {
106
+ const detail = (step) => steps.find((s) => s.step === step && (s.state === "exists" || s.state === "created"))
107
+ ?.detail?.split(" · ")[0];
108
+ return {
109
+ repo: detail("github repo"),
110
+ vercelProject: detail("vercel project"),
111
+ neonProject: detail("neon project"),
112
+ };
113
+ }
114
+ /** Refused because there is nothing of this app anywhere to adopt. */
115
+ export class NothingToAdoptError extends Error {
116
+ constructor(name) {
117
+ super(`Nothing of ${name} exists at any provider. Use \`heychunky new\` to make it.`);
118
+ this.name = "NothingToAdoptError";
119
+ }
120
+ }
121
+ /**
122
+ * Record an app the factory did not create.
123
+ *
124
+ * The register was only ever written at the end of a successful provision run,
125
+ * which is right for an app Chunky made and useless for one that predates it.
126
+ * An app can exist — repository, database, deployment, all live for months —
127
+ * and be invisible to every command that reads the register.
128
+ *
129
+ * This is deliberately not `--adopt`, which means *create the missing pieces
130
+ * on a live app*. This creates nothing, ever: it takes a walk that already
131
+ * happened in plan mode and writes down what that walk found.
132
+ *
133
+ * Refusing when nothing exists is the guard that matters. Recording an app
134
+ * that is not there would put a row in the register pointing at no repository
135
+ * and no database, and every later command would believe it.
136
+ */
137
+ export async function adoptApp(steps, app, register) {
138
+ if (!steps.some((s) => s.state === "exists"))
139
+ throw new NothingToAdoptError(app.name);
140
+ const found = detailsFromRun(steps);
141
+ return recordApp({
142
+ name: app.name,
143
+ owner: app.owner,
144
+ org: app.org,
145
+ ...found,
146
+ neonProject: app.neonProject ?? found.neonProject,
147
+ }, register);
148
+ }
82
149
  /** Apps an owner has, or every app when none is named. */
83
150
  /**
84
151
  * One app, if this operator may have it.
@@ -1 +1 @@
1
- {"version":3,"file":"apps.js","sourceRoot":"","sources":["../../src/admin/apps.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"apps.js","sourceRoot":"","sources":["../../src/admin/apps.ts"],"names":[],"mappings":"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"}
@@ -8,11 +8,13 @@ export * from "./secrets/infisical.ts";
8
8
  export * from "./syncenv.ts";
9
9
  export * from "./master.ts";
10
10
  export * from "./apps.ts";
11
+ export * from "./orgs.ts";
11
12
  export * from "./users.ts";
12
13
  export * from "./run.ts";
13
14
  export * from "./provision.ts";
14
15
  export * from "./decommission.ts";
15
16
  export * from "./migrations.ts";
17
+ export * from "./register.ts";
16
18
  export * from "./promote.ts";
17
19
  export * from "./runs.ts";
18
20
  export * from "./entrypoints.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAG5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAG5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
@@ -8,11 +8,13 @@ export * from "./secrets/infisical.js";
8
8
  export * from "./syncenv.js";
9
9
  export * from "./master.js";
10
10
  export * from "./apps.js";
11
+ export * from "./orgs.js";
11
12
  export * from "./users.js";
12
13
  export * from "./run.js";
13
14
  export * from "./provision.js";
14
15
  export * from "./decommission.js";
15
16
  export * from "./migrations.js";
17
+ export * from "./register.js";
16
18
  export * from "./promote.js";
17
19
  export * from "./runs.js";
18
20
  export * from "./entrypoints.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAE5B,+CAA+C;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAE5B,+CAA+C;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
@@ -95,4 +95,23 @@ export declare function pending(all: Migration[], have: Set<string>): Migration[
95
95
  * failed, and running them anyway turns one clear error into several.
96
96
  */
97
97
  export declare function apply(db: Ledger, migrations: Migration[], watch?: (m: Migration) => void): Promise<number>;
98
+ /**
99
+ * Record statements as applied without running them.
100
+ *
101
+ * For a database that predates its ledger. The register is the case this was
102
+ * written for — `apps` and `users` were typed into psql long before anything
103
+ * wrote them down, so the first migration file describing them is a record of
104
+ * what is already there rather than work to do. Running it would fail on every
105
+ * `create table`, and failing is not the answer: the database is *correct*, it
106
+ * simply has no receipt.
107
+ *
108
+ * Deliberately separate from `apply`, and deliberately not a flag on it. One
109
+ * function that either runs SQL or does not, depending on an argument, is one
110
+ * a caller can get backwards — and getting this one backwards means a schema
111
+ * change silently skipped, discovered later as a missing column in production.
112
+ *
113
+ * Only ever right when somebody has looked at the database and confirmed the
114
+ * statements are already true of it. There is no way to check that from here.
115
+ */
116
+ export declare function adopt(db: Ledger, migrations: Migration[]): Promise<string[]>;
98
117
  //# sourceMappingURL=migrations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/admin/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,kEAAkE;AAClE,eAAO,MAAM,cAAc,eAAe,CAAC;AAE3C,oEAAoE;AACpE,eAAO,MAAM,MAAM,0BAA0B,CAAC;AAE9C,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACxB,sEAAsE;IACtE,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb;AAED,8DAA8D;AAC9D,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI3E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAIlD;AAED,oEAAoE;AACpE,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK5D;AAED;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAS9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,CAExE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,KAAK,CACzB,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,SAAS,EAAE,EACvB,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,GAC7B,OAAO,CAAC,MAAM,CAAC,CAUjB"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/admin/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,kEAAkE;AAClE,eAAO,MAAM,cAAc,eAAe,CAAC;AAE3C,oEAAoE;AACpE,eAAO,MAAM,MAAM,0BAA0B,CAAC;AAE9C,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACxB,sEAAsE;IACtE,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb;AAED,8DAA8D;AAC9D,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI3E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAIlD;AAED,oEAAoE;AACpE,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK5D;AAED;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAS9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,CAExE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,KAAK,CACzB,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,SAAS,EAAE,EACvB,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,GAC7B,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQlF"}
@@ -90,4 +90,31 @@ export async function apply(db, migrations, watch) {
90
90
  }
91
91
  return ran;
92
92
  }
93
+ /**
94
+ * Record statements as applied without running them.
95
+ *
96
+ * For a database that predates its ledger. The register is the case this was
97
+ * written for — `apps` and `users` were typed into psql long before anything
98
+ * wrote them down, so the first migration file describing them is a record of
99
+ * what is already there rather than work to do. Running it would fail on every
100
+ * `create table`, and failing is not the answer: the database is *correct*, it
101
+ * simply has no receipt.
102
+ *
103
+ * Deliberately separate from `apply`, and deliberately not a flag on it. One
104
+ * function that either runs SQL or does not, depending on an argument, is one
105
+ * a caller can get backwards — and getting this one backwards means a schema
106
+ * change silently skipped, discovered later as a missing column in production.
107
+ *
108
+ * Only ever right when somebody has looked at the database and confirmed the
109
+ * statements are already true of it. There is no way to check that from here.
110
+ */
111
+ export async function adopt(db, migrations) {
112
+ await ensureLedger(db);
113
+ const recorded = [];
114
+ for (const m of migrations) {
115
+ await db.write(`insert into ${LEDGER} (id) values ($1) on conflict (id) do nothing`, [m.id]);
116
+ recorded.push(m.id);
117
+ }
118
+ return recorded;
119
+ }
93
120
  //# sourceMappingURL=migrations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/admin/migrations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/admin/migrations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * The tenancy boundary.
3
+ *
4
+ * An **organisation** is what owns things. A **user** is a person who builds.
5
+ * Not the users of anybody's app — Carmel's citizens are a Carmel City concept
6
+ * and stay in Carmel's database; these are the people who make apps here.
7
+ *
8
+ * `owner_id` on `apps` was the boundary until now, and a person is the wrong
9
+ * shape for one: it cannot be shared, it cannot be handed over, and it cannot
10
+ * hold a plan two people are both paying against. `org_id` is the boundary and
11
+ * `owner_id` survives beside it as provenance — who created this.
12
+ *
13
+ * **An organisation's name is the publish scope.** The curated shelf keeps bare
14
+ * names because somebody chose them; everyone else publishes under a scope, and
15
+ * `@carmelcity/auth` needs no second entity to explain what `carmelcity` is. It
16
+ * is also the URL segment a signed-in session is scoped by, which is why the
17
+ * database insists it is a slug rather than a title: one string, three jobs,
18
+ * and nothing anywhere has to remember which spelling it is holding.
19
+ *
20
+ * **The organisation is not the GitHub organisation — it has one.** Carmel's
21
+ * organisation is `carmelcity` here and `carmelcity` on GitHub today, and
22
+ * nothing should be built on those staying equal; `github_org`, `vercel_team`
23
+ * and `vault_folder` are where a provider's own name for the same tenant goes.
24
+ *
25
+ * **One level.** No spaces or workspaces inside an organisation until something
26
+ * needs one.
27
+ *
28
+ * Two kinds of role, and they live in different places on purpose. `superadmin`
29
+ * is a factory role and stays on the user, where a partial unique index permits
30
+ * exactly one of them. `admin` and `publisher` are organisation roles and live
31
+ * on the membership. One column for both would let an organisation's own admin
32
+ * inherit the factory.
33
+ */
34
+ import type { Register } from "./master.ts";
35
+ import type { PlanKey } from "./users.ts";
36
+ /** What somebody may do inside one organisation. Never a factory role. */
37
+ export declare const ORG_ROLES: readonly ["admin", "publisher"];
38
+ export type OrgRole = (typeof ORG_ROLES)[number];
39
+ /**
40
+ * Whether an organisation is a person or a group.
41
+ *
42
+ * `personal` is made for you when you claim a username and is never made any
43
+ * other way, which is what keeps a name meaning one thing across the People
44
+ * page, the shelf and the URL.
45
+ */
46
+ export declare const ORG_KINDS: readonly ["personal", "team"];
47
+ export type OrgKind = (typeof ORG_KINDS)[number];
48
+ /**
49
+ * The shape a name may take.
50
+ *
51
+ * The same rule as a chunk's scope and a username, because they are the same
52
+ * string. Enforced by a check constraint as well as here — a rule that lives
53
+ * only in TypeScript is a rule the next caller writes around.
54
+ */
55
+ export declare const ORG_NAME: RegExp;
56
+ export interface Org {
57
+ id: string;
58
+ /** The publish scope and the URL segment. Unique without case. */
59
+ name: string;
60
+ kind: OrgKind;
61
+ /** What this tenant may spend. The plan moved here from the user. */
62
+ plan: PlanKey;
63
+ /** A provider's own name for the same tenant. Null until one is linked. */
64
+ github_org: string | null;
65
+ vercel_team: string | null;
66
+ vault_folder: string | null;
67
+ created_at: string;
68
+ }
69
+ /** An organisation with what the person asking may do in it. */
70
+ export interface Membership extends Org {
71
+ role: OrgRole;
72
+ }
73
+ /** Refused because the name belongs to an organisation that is not personal. */
74
+ export declare class OrgNameTakenError extends Error {
75
+ constructor(name: string);
76
+ }
77
+ /**
78
+ * Give a user the organisation that matches their username.
79
+ *
80
+ * Runs in the same moment as claiming the name, because a name that is yours on
81
+ * the People page and somebody else's on the shelf would be worse than having
82
+ * no names at all. Safe to run again: if the membership insert fails the name is
83
+ * still theirs and the organisation is still there, which is the right way round
84
+ * — a person without their scope yet is inconvenienced, a person whose scope
85
+ * belongs to somebody else is a bug you cannot undo.
86
+ *
87
+ * Three statements rather than one because the client sends one statement per
88
+ * request. The insert is the check for the same reason `claimUsername`'s is: two
89
+ * people reaching for a name in the same second is a race only the unique index
90
+ * can settle.
91
+ *
92
+ * The read between the two writes is not ceremony. `on conflict do nothing` is
93
+ * silent about *why* it did nothing, and the one case that matters is a team
94
+ * organisation already holding this name — adding the caller to that as an admin
95
+ * would hand them somebody else's tenant. So it is looked at, and refused.
96
+ */
97
+ export declare function claimPersonalOrg(userId: string, username: string, register: Register): Promise<Org>;
98
+ /** The organisations this person belongs to, personal one first. */
99
+ export declare function orgsFor(userId: string, register: Register): Promise<Membership[]>;
100
+ /**
101
+ * Whether this person may publish under this name.
102
+ *
103
+ * Membership is the whole answer — both roles publish, which is what makes
104
+ * `publisher` worth having as the one that cannot also invite people.
105
+ */
106
+ export declare function mayPublish(userId: string, org: string, register: Register): Promise<boolean>;
107
+ /**
108
+ * What must be true of the register once organisations are backfilled.
109
+ *
110
+ * Written as data, and as violations rather than assertions, for one reason:
111
+ * **a wrong backfill does not fail loudly.** It sits looking fine until the day
112
+ * reads move onto `org_id`, and then it is one customer seeing another's apps.
113
+ * Nothing about that shows up in a test — the schema is right either way — so
114
+ * the proof has to be a question asked of the live rows.
115
+ *
116
+ * Every check counts rows that should not exist. Zero is the only passing
117
+ * answer, which means a check that is wrong tends to fail rather than pass, and
118
+ * a table that is empty passes honestly rather than vacuously hiding a bug.
119
+ *
120
+ * Exported so the CLI can print the SQL beside the count. A verification
121
+ * somebody has to take on trust is not one.
122
+ */
123
+ export declare const ORG_CHECKS: readonly {
124
+ check: string;
125
+ sql: string;
126
+ }[];
127
+ export interface CheckResult {
128
+ check: string;
129
+ sql: string;
130
+ /** Rows that should not exist. Anything but zero is a finding. */
131
+ found: number;
132
+ ok: boolean;
133
+ }
134
+ /**
135
+ * Ask the register all of them.
136
+ *
137
+ * Read-only, and safe to run before the migration as well as after — a check
138
+ * against a table that does not exist yet is a failure worth seeing rather than
139
+ * one to swallow, so nothing here catches.
140
+ */
141
+ export declare function verifyOrgs(register: Register): Promise<CheckResult[]>;
142
+ //# sourceMappingURL=orgs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orgs.d.ts","sourceRoot":"","sources":["../../src/admin/orgs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,0EAA0E;AAC1E,eAAO,MAAM,SAAS,iCAAkC,CAAC;AACzD,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,+BAAgC,CAAC;AACvD,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,QAAyB,CAAC;AAE/C,MAAM,WAAW,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,qEAAqE;IACrE,IAAI,EAAE,OAAO,CAAC;IACd,2EAA2E;IAC3E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,gEAAgE;AAChE,MAAM,WAAW,UAAW,SAAQ,GAAG;IACrC,IAAI,EAAE,OAAO,CAAC;CACf;AAED,gFAAgF;AAChF,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,IAAI,EAAE,MAAM;CAIzB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,GAAG,CAAC,CAwBd;AAED,oEAAoE;AACpE,wBAAsB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAQvF;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAqD/D,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,OAAO,CAAC;CACb;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAQ3E"}
@@ -0,0 +1,166 @@
1
+ /** What somebody may do inside one organisation. Never a factory role. */
2
+ export const ORG_ROLES = ["admin", "publisher"];
3
+ /**
4
+ * Whether an organisation is a person or a group.
5
+ *
6
+ * `personal` is made for you when you claim a username and is never made any
7
+ * other way, which is what keeps a name meaning one thing across the People
8
+ * page, the shelf and the URL.
9
+ */
10
+ export const ORG_KINDS = ["personal", "team"];
11
+ /**
12
+ * The shape a name may take.
13
+ *
14
+ * The same rule as a chunk's scope and a username, because they are the same
15
+ * string. Enforced by a check constraint as well as here — a rule that lives
16
+ * only in TypeScript is a rule the next caller writes around.
17
+ */
18
+ export const ORG_NAME = /^[a-z0-9][a-z0-9-]*$/;
19
+ /** Refused because the name belongs to an organisation that is not personal. */
20
+ export class OrgNameTakenError extends Error {
21
+ constructor(name) {
22
+ super(`"${name}" is a team organisation, so it cannot also be somebody's personal one.`);
23
+ this.name = "OrgNameTakenError";
24
+ }
25
+ }
26
+ /**
27
+ * Give a user the organisation that matches their username.
28
+ *
29
+ * Runs in the same moment as claiming the name, because a name that is yours on
30
+ * the People page and somebody else's on the shelf would be worse than having
31
+ * no names at all. Safe to run again: if the membership insert fails the name is
32
+ * still theirs and the organisation is still there, which is the right way round
33
+ * — a person without their scope yet is inconvenienced, a person whose scope
34
+ * belongs to somebody else is a bug you cannot undo.
35
+ *
36
+ * Three statements rather than one because the client sends one statement per
37
+ * request. The insert is the check for the same reason `claimUsername`'s is: two
38
+ * people reaching for a name in the same second is a race only the unique index
39
+ * can settle.
40
+ *
41
+ * The read between the two writes is not ceremony. `on conflict do nothing` is
42
+ * silent about *why* it did nothing, and the one case that matters is a team
43
+ * organisation already holding this name — adding the caller to that as an admin
44
+ * would hand them somebody else's tenant. So it is looked at, and refused.
45
+ */
46
+ export async function claimPersonalOrg(userId, username, register) {
47
+ if (!ORG_NAME.test(username))
48
+ throw new Error(`"${username}" is not a name an organisation can have.`);
49
+ await register.write(`insert into orgs (name, kind, plan)
50
+ select $1, 'personal', coalesce(u.plan, 'free') from users u where u.id = $2
51
+ on conflict (lower(name)) do nothing`, [username, userId]);
52
+ const { rows } = await register.query("select * from orgs where lower(name) = lower($1)", [username]);
53
+ const org = rows[0];
54
+ if (!org)
55
+ throw new Error(`No user ${userId}, so "${username}" belongs to nobody.`);
56
+ if (org.kind !== "personal")
57
+ throw new OrgNameTakenError(username);
58
+ await register.write(`insert into org_members (org_id, user_id, role) values ($1, $2, 'admin')
59
+ on conflict (org_id, user_id) do nothing`, [org.id, userId]);
60
+ return org;
61
+ }
62
+ /** The organisations this person belongs to, personal one first. */
63
+ export async function orgsFor(userId, register) {
64
+ const { rows } = await register.query(`select o.*, m.role from orgs o join org_members m on m.org_id = o.id
65
+ where m.user_id = $1
66
+ order by (o.kind = 'personal') desc, o.name`, [userId]);
67
+ return rows;
68
+ }
69
+ /**
70
+ * Whether this person may publish under this name.
71
+ *
72
+ * Membership is the whole answer — both roles publish, which is what makes
73
+ * `publisher` worth having as the one that cannot also invite people.
74
+ */
75
+ export async function mayPublish(userId, org, register) {
76
+ const { rows } = await register.query(`select 1 from org_members m join orgs o on o.id = m.org_id
77
+ where m.user_id = $1 and lower(o.name) = lower($2)`, [userId, org]);
78
+ return rows.length > 0;
79
+ }
80
+ /**
81
+ * What must be true of the register once organisations are backfilled.
82
+ *
83
+ * Written as data, and as violations rather than assertions, for one reason:
84
+ * **a wrong backfill does not fail loudly.** It sits looking fine until the day
85
+ * reads move onto `org_id`, and then it is one customer seeing another's apps.
86
+ * Nothing about that shows up in a test — the schema is right either way — so
87
+ * the proof has to be a question asked of the live rows.
88
+ *
89
+ * Every check counts rows that should not exist. Zero is the only passing
90
+ * answer, which means a check that is wrong tends to fail rather than pass, and
91
+ * a table that is empty passes honestly rather than vacuously hiding a bug.
92
+ *
93
+ * Exported so the CLI can print the SQL beside the count. A verification
94
+ * somebody has to take on trust is not one.
95
+ */
96
+ export const ORG_CHECKS = [
97
+ {
98
+ check: "users with a username and no personal organisation",
99
+ sql: `select count(*) as n from users u where u.username is not null
100
+ and not exists (select 1 from orgs o
101
+ where lower(o.name) = lower(u.username) and o.kind = 'personal')`,
102
+ },
103
+ {
104
+ check: "personal organisations whose name is nobody's username",
105
+ sql: `select count(*) as n from orgs o where o.kind = 'personal'
106
+ and not exists (select 1 from users u where lower(u.username) = lower(o.name))`,
107
+ },
108
+ {
109
+ check: "personal organisations without exactly one admin member",
110
+ sql: `select count(*) as n from orgs o where o.kind = 'personal'
111
+ and (select count(*) from org_members m
112
+ where m.org_id = o.id and m.role = 'admin') <> 1`,
113
+ },
114
+ {
115
+ check: "members of an organisation they are not in the register of",
116
+ sql: `select count(*) as n from org_members m
117
+ where not exists (select 1 from users u where u.id = m.user_id)`,
118
+ },
119
+ {
120
+ check: "apps in no organisation",
121
+ sql: "select count(*) as n from apps where org_id is null",
122
+ },
123
+ {
124
+ check: "apps whose organisation is not their owner's personal one",
125
+ sql: `select count(*) as n from apps a
126
+ join users u on u.id = a.owner_id
127
+ where a.org_id is not null
128
+ and a.org_id is distinct from
129
+ (select o.id from orgs o
130
+ where lower(o.name) = lower(u.username) and o.kind = 'personal')`,
131
+ },
132
+ {
133
+ check: "apps whose owner has claimed no username, so can have no organisation",
134
+ sql: `select count(*) as n from apps a join users u on u.id = a.owner_id
135
+ where u.username is null`,
136
+ },
137
+ {
138
+ check: "credit entries in no organisation",
139
+ sql: "select count(*) as n from credit_ledger where org_id is null",
140
+ },
141
+ {
142
+ check: "api tokens in no organisation",
143
+ sql: "select count(*) as n from api_tokens where org_id is null",
144
+ },
145
+ {
146
+ check: "api tokens with no recorded creator",
147
+ sql: "select count(*) as n from api_tokens where created_by is null",
148
+ },
149
+ ];
150
+ /**
151
+ * Ask the register all of them.
152
+ *
153
+ * Read-only, and safe to run before the migration as well as after — a check
154
+ * against a table that does not exist yet is a failure worth seeing rather than
155
+ * one to swallow, so nothing here catches.
156
+ */
157
+ export async function verifyOrgs(register) {
158
+ const out = [];
159
+ for (const { check, sql } of ORG_CHECKS) {
160
+ const { rows } = await register.query(sql);
161
+ const found = Number(rows[0]?.["n"] ?? 0);
162
+ out.push({ check, sql, found, ok: found === 0 });
163
+ }
164
+ return out;
165
+ }
166
+ //# sourceMappingURL=orgs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orgs.js","sourceRoot":"","sources":["../../src/admin/orgs.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,16 @@ export interface AppSpec {
18
18
  * a domain of your own is a decision taken later, on top of one that works.
19
19
  */
20
20
  domain?: boolean | string;
21
+ /**
22
+ * The Neon project, when it is not named after the app.
23
+ *
24
+ * `findProject` matches exactly, and a project somebody named by hand years
25
+ * ago rarely matches the app: `carmelcity.ai` never reaches `Carmel City
26
+ * Main`. No rule connects those two strings, so for an app the register
27
+ * already knows, the register says which project it is rather than the walk
28
+ * guessing and reporting a live database as missing.
29
+ */
30
+ neonProject?: string;
21
31
  /**
22
32
  * What kind of app it is, which decides what the repository starts as.
23
33
  *