@odla-ai/chapter 0.46.0 → 0.47.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/README.md CHANGED
@@ -1511,8 +1511,10 @@ These bite silently — a smoke test won't catch them:
1511
1511
  - **Bootstrap the first admin according to the selected auth source.** Chapter
1512
1512
  mode defaults to Clerk claims: set the first operator's Clerk
1513
1513
  `public_metadata.role` to `admin`. If that operator also needs the
1514
- read-only cross-site tier, add their lowercase email to `superAdmins` in odla
1515
- Studio. Hub mode defaults to table auth: add the first operator's lowercase
1514
+ super-admin tier, prefer `auth: { superAdminSource: "odla" }` and grant their
1515
+ app-scoped role on Database → Users in odla Studio. Existing sites retain the
1516
+ legacy `superAdmins` table until they opt in. Hub mode defaults to table auth:
1517
+ add the first operator's lowercase
1516
1518
  email to `admins` in Studio. Neither allowlist is ever written by a worker
1517
1519
  route or provisioning seed; that is deliberate, so the running app cannot
1518
1520
  grant itself admin. Confirm the result by signing in, not merely by inspecting
@@ -458,7 +458,7 @@ interface ChapterScheduling {
458
458
  *
459
459
  * - `"claim"` reads a role ladder from a JWT claim (the member-site model:
460
460
  * `provisional → member → admin`). Admin is the last (highest) rung; the
461
- * read-only `superAdmins` tier sits above it.
461
+ * Studio-managed super-admin tier sits above it.
462
462
  * - `"table"` gates on the deny-all `admins` allowlist (the hub model):
463
463
  * a binary "email is an admin or isn't", set only in odla Studio.
464
464
  *
@@ -470,10 +470,13 @@ interface ChapterAuth {
470
470
  /** Role ladder low→high, for `source: "claim"`. The last entry is admin.
471
471
  * Default `["provisional", "member", "admin"]`. */
472
472
  ladder?: readonly string[];
473
- /** Provision the read-only `superAdmins` tier table — the only tier that may
474
- * create/modify admins, and (like every namespace) deny-all + written only in
475
- * odla Studio. Default: `true` for `"claim"`, `false` for `"table"`. */
473
+ /** Enable the Studio-managed super-admin tier — the only tier that may
474
+ * create/modify admins. Default: `true` for `"claim"`, `false` for `"table"`. */
476
475
  superAdmins?: boolean;
476
+ /** Authority behind the super-admin tier. `"table"` preserves the legacy
477
+ * app allowlist; `"odla"` reads protected `$users.role`. Default `"table"`
478
+ * for backward compatibility. */
479
+ superAdminSource?: "table" | "odla";
477
480
  }
478
481
  /** The fully-resolved auth policy (defaults applied) carried on the {@link Chapter}. */
479
482
  interface ResolvedAuth {
@@ -483,6 +486,7 @@ interface ResolvedAuth {
483
486
  /** The highest ladder rung (last entry) — the "admin" gate. */
484
487
  adminRole: string;
485
488
  superAdmins: boolean;
489
+ superAdminSource: "none" | "table" | "odla";
486
490
  }
487
491
  /** The application status pipeline. Which statuses exist, and the subsets a site
488
492
  * allows a call to be booked from / an application approved from. Defaults to
package/dist/index.cjs CHANGED
@@ -1015,7 +1015,7 @@ function chapterDb(mode, auth, includeNetworkNotes = false, application = resolv
1015
1015
  }
1016
1016
  }
1017
1017
  if (auth.source === "table") entities.admins = admins;
1018
- if (auth.superAdmins) entities.superAdmins = superAdmins;
1018
+ if (auth.superAdminSource === "table") entities.superAdmins = superAdmins;
1019
1019
  if (includeNetworkNotes) entities.networkNotes = networkNotes;
1020
1020
  if (includeBuilderLibrary) {
1021
1021
  entities.builderLibraryControls = builderLibraryControls;
@@ -1198,7 +1198,12 @@ function resolveAuth(mode, auth) {
1198
1198
  }
1199
1199
  const adminRole = ladder[ladder.length - 1];
1200
1200
  const superAdmins2 = a.superAdmins ?? source === "claim";
1201
- return { source, claim, ladder, adminRole, superAdmins: superAdmins2 };
1201
+ const configuredSuperSource = a.superAdminSource ?? "table";
1202
+ if (configuredSuperSource !== "table" && configuredSuperSource !== "odla") {
1203
+ throw new Error(`defineChapter.auth.superAdminSource: must be "table" or "odla" \u2014 got ${JSON.stringify(a.superAdminSource)}`);
1204
+ }
1205
+ const superAdminSource = superAdmins2 ? configuredSuperSource : "none";
1206
+ return { source, claim, ladder, adminRole, superAdmins: superAdmins2, superAdminSource };
1202
1207
  }
1203
1208
  function roleFromClaim(payload, auth) {
1204
1209
  const raw2 = payload[auth.claim];