@caelo-cms/provisioning 0.11.4 → 0.11.6

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/agent-db.js CHANGED
@@ -6614,6 +6614,8 @@ var LIST_RESERVED_NAMES = new Set([
6614
6614
  "site_text",
6615
6615
  "recurse"
6616
6616
  ]);
6617
+ var LIST_RESERVED_PREFIXES = ["is_", "has_", "count_"];
6618
+ var LIST_RESERVED_NAMES_CLAUSE = `reserved: ${[...LIST_RESERVED_NAMES].join(", ")}, ${LIST_RESERVED_PREFIXES.map((p) => `${p}*`).join(", ")}`;
6617
6619
  var listNameSchema = string2().regex(/^[a-z][a-z0-9_]{0,63}$/, "must be snake_case (lowercase letters, digits, underscores)");
6618
6620
  var fieldLabel = string2().min(1).max(128);
6619
6621
  var fieldExtSchema = record(string2().regex(/^[a-z0-9-]+$/), unknown());
@@ -6769,6 +6771,22 @@ var listItemInputSchema = lazy(() => object({
6769
6771
  query: record(string2(), unknown()).optional()
6770
6772
  }).strict());
6771
6773
 
6774
+ // ../shared/src/lists/page-type.ts
6775
+ var pageTypeDefSchema = object({
6776
+ slug: string2().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/),
6777
+ name: string2().min(1).max(200),
6778
+ purpose: string2().min(1).max(1000),
6779
+ collection: boolean2(),
6780
+ templates: array(string2().uuid()).min(1).max(64),
6781
+ defaultTemplate: string2().uuid(),
6782
+ properties: array(fieldDefSchema).max(32).default([]),
6783
+ vocabularies: array(object({
6784
+ list: string2().uuid(),
6785
+ cardinality: _enum(["one", "many"]),
6786
+ required: boolean2().optional()
6787
+ }).strict()).max(16).default([])
6788
+ }).strict();
6789
+
6772
6790
  // ../shared/src/lists/pagination.ts
6773
6791
  var PAGINATION_FOLLOW_UPS = ["index", "noindex"];
6774
6792
  var listPaginationSchema = object({
@@ -6827,7 +6845,7 @@ var noCorruptCss = (value, ctx) => {
6827
6845
  var moduleHtml = string2().max(MODULE_HTML_MAX, `html exceeds ${MODULE_HTML_MAX} bytes`).superRefine(noCorruptMarkup("html"));
6828
6846
  var CDATA_OPEN = "<![CDATA[";
6829
6847
  var noCdata = (value) => !value.includes(CDATA_OPEN);
6830
- var cdataMessage = (field) => `${field} contains an XML CDATA marker (${CDATA_OPEN}) \u2014 module ${field} is stored and served ` + `verbatim, so CDATA wrappers reach the browser unparsed and break rendering. ` + `Remove the wrapper and resend plain ${field === "css" ? "CSS" : "JavaScript"}.`;
6848
+ var cdataMessage = (field) => `${field} contains an XML CDATA marker (${CDATA_OPEN}) \u2014 module ${field} is stored and served ` + `verbatim, so CDATA wrappers reach the browser unparsed and break rendering. ` + `Remove the wrapper: pass plain ${field === "css" ? "CSS" : "JavaScript"}.`;
6831
6849
  var moduleCss = string2().max(MODULE_CSS_MAX, `css exceeds ${MODULE_CSS_MAX} bytes`).refine(noCdata, cdataMessage("css")).superRefine(noCorruptCss);
6832
6850
  var moduleJs = string2().max(MODULE_JS_MAX, `js exceeds ${MODULE_JS_MAX} bytes`).refine(noCdata, cdataMessage("js")).superRefine(noJsonEncodedCode("js"));
6833
6851
  var MODULE_FIELD_PRIMITIVE_KINDS = [
@@ -7026,10 +7044,13 @@ var moduleUpdateSchema = object({
7026
7044
  fields: moduleFieldsArray.optional()
7027
7045
  }).strict();
7028
7046
  var pageTypeSlugSchema = string2().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/, "page type slug: lowercase letters, digits, hyphens");
7047
+ var pageTypeDistinctFromSchema = array(pageTypeSlugSchema).max(16);
7048
+ var newPageTypeSchema = pageTypeDefSchema.omit({ templates: true, defaultTemplate: true }).extend({ distinctFrom: pageTypeDistinctFromSchema.optional() }).strict();
7029
7049
  var templateCreateSchema = object({
7030
7050
  slug: slugSchema,
7031
7051
  displayName: displayNameSchema,
7032
7052
  pageType: pageTypeSlugSchema.optional(),
7053
+ newPageType: newPageTypeSchema.optional(),
7033
7054
  html: string2().max(TEMPLATE_HTML_MAX, `html exceeds ${TEMPLATE_HTML_MAX} bytes`).superRefine(noCorruptMarkup("html")),
7034
7055
  css: string2().max(TEMPLATE_CSS_MAX, `css exceeds ${TEMPLATE_CSS_MAX} bytes`).default(""),
7035
7056
  layoutId: string2().uuid().optional(),
@@ -7366,7 +7387,8 @@ var chatCreateSessionInput = object({
7366
7387
  templateId: string2().uuid().optional(),
7367
7388
  subagentRole: string2().min(1).max(120).optional(),
7368
7389
  parentChatSessionId: string2().uuid().nullable().optional(),
7369
- isolation: _enum(["experiment", "migration"]).optional()
7390
+ isolation: _enum(["experiment", "migration"]).optional(),
7391
+ agentClient: string2().min(1).max(200).optional()
7370
7392
  }).strict();
7371
7393
  var CHAT_IMAGE_MIMES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
7372
7394
  var CHAT_MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;
@@ -7457,6 +7479,7 @@ var createTemplateToolInput = object({
7457
7479
  css: sourceTextArg(string2().max(2000000), "css").default(""),
7458
7480
  layoutId: string2().uuid().optional(),
7459
7481
  pageType: pageTypeSlugSchema.optional(),
7482
+ newPageType: newPageTypeSchema.optional(),
7460
7483
  blocks: array(object({
7461
7484
  name: string2().min(1).max(80),
7462
7485
  displayName: string2().min(1).max(200),
@@ -7484,17 +7507,15 @@ var createLayoutToolInput = object({
7484
7507
  }).strict()).min(1).max(20)
7485
7508
  }).strict();
7486
7509
  var setSiteDefaultsToolInput = object({
7487
- defaultLayoutSlug: slugInputSchema.optional(),
7488
- defaultTemplateSlug: slugInputSchema.optional()
7489
- }).strict().refine((v) => v.defaultLayoutSlug !== undefined || v.defaultTemplateSlug !== undefined, {
7490
- message: "must provide at least one of defaultLayoutSlug, defaultTemplateSlug"
7491
- });
7510
+ defaultLayoutSlug: slugInputSchema
7511
+ }).strict();
7492
7512
  var duplicatePageToolInput = object({
7493
7513
  sourcePageId: string2().uuid(),
7494
7514
  newSlug: slugInputSchema,
7495
7515
  newTitle: string2().min(1).max(256).optional(),
7496
7516
  newShortTitle: pageShortTitleSchema.optional(),
7497
7517
  targetTemplateId: string2().uuid().optional(),
7518
+ pageType: pageTypeSlugSchema.optional(),
7498
7519
  newParentPageId: string2().uuid().nullable().optional()
7499
7520
  }).strict();
7500
7521
  var repointPageTemplateToolInput = object({
@@ -7628,6 +7649,7 @@ var bootstrapSiteScaffoldToolInput = object({
7628
7649
  })).max(8).optional(),
7629
7650
  templateSlug: string2().min(1).max(120).optional(),
7630
7651
  templateDisplayName: string2().min(1).max(256).optional(),
7652
+ templatePageType: newPageTypeSchema.optional(),
7631
7653
  setAsDefaults: boolean2().optional()
7632
7654
  }).strict();
7633
7655
  var revertChatChangesToolInput = object({
@@ -7964,6 +7986,98 @@ var pageModuleContentSetManySchema = object({
7964
7986
  }).strict();
7965
7987
  // ../shared/src/cap-failures.ts
7966
7988
  var counters = new Map;
7989
+ // ../shared/src/change-journal.ts
7990
+ var JOURNAL_KINDS = [
7991
+ "page",
7992
+ "page_terms",
7993
+ "page_seo",
7994
+ "page_layout",
7995
+ "page_module_content",
7996
+ "module",
7997
+ "template",
7998
+ "template_blocks",
7999
+ "layout",
8000
+ "layout_blocks",
8001
+ "layout_module",
8002
+ "content_instance",
8003
+ "list",
8004
+ "list_type",
8005
+ "page_type",
8006
+ "theme",
8007
+ "media",
8008
+ "media_variant",
8009
+ "media_crop",
8010
+ "font",
8011
+ "redirect",
8012
+ "domain",
8013
+ "site_defaults",
8014
+ "site_settings",
8015
+ "site_memory",
8016
+ "skill",
8017
+ "plugin",
8018
+ "plugin_installation",
8019
+ "plugin_grant",
8020
+ "plugin_file",
8021
+ "plugin_row",
8022
+ "plugin_storage",
8023
+ "snapshot"
8024
+ ];
8025
+ var KIND_SET = new Set(JOURNAL_KINDS);
8026
+ var row = (kind, key, pair, extra = {}) => ({ kind, mode: "row", key, pair, ...extra });
8027
+ var snapshot = (kind, key, extra = {}) => ({ kind, mode: "snapshot", key, pair: ["id"], ...extra });
8028
+ var JOURNALED_TABLES = {
8029
+ pages: row("page", "id", ["id"], { branch: "chat_branch_id" }),
8030
+ page_terms: row("page_terms", "page_id", ["page_id", "list_id", "item_id"]),
8031
+ pages_seo: row("page_seo", "page_id", ["page_id"]),
8032
+ page_modules: row("page_layout", "page_id", ["page_id", "block_name", "position"]),
8033
+ page_module_content: row("page_module_content", "id", ["id"], { parent: "page_id" }),
8034
+ modules: row("module", "id", ["id"], { branch: "chat_branch_id" }),
8035
+ templates: row("template", "id", ["id"], { branch: "chat_branch_id" }),
8036
+ template_blocks: row("template_blocks", "template_id", ["template_id", "name"]),
8037
+ layouts: row("layout", "id", ["id"], { branch: "chat_branch_id" }),
8038
+ layout_blocks: row("layout_blocks", "layout_id", ["layout_id", "name"]),
8039
+ layout_modules: row("layout_module", "layout_id", ["layout_id", "block_name", "position"]),
8040
+ content_instances: row("content_instance", "id", ["id"], {
8041
+ branch: "chat_branch_id",
8042
+ parent: "module_id"
8043
+ }),
8044
+ lists: row("list", "id", ["id"], { branch: "chat_branch_id" }),
8045
+ list_types: row("list_type", "id", ["id"], { branch: "chat_branch_id" }),
8046
+ page_types: row("page_type", "id", ["id"], { branch: "chat_branch_id" }),
8047
+ themes: row("theme", "id", ["id"]),
8048
+ media_assets: row("media", "id", ["id"]),
8049
+ media_variants: row("media_variant", "asset_id", ["asset_id", "variant"]),
8050
+ media_crops: row("media_crop", "id", ["id"], { parent: "asset_id" }),
8051
+ font_assets: row("font", "id", ["id"]),
8052
+ redirects: row("redirect", "id", ["id"]),
8053
+ domains: row("domain", "id", ["id"]),
8054
+ site_defaults: row("site_defaults", "id", ["id"]),
8055
+ site_settings: row("site_settings", "id", ["id"]),
8056
+ site_ai_memory: row("site_memory", "id", ["id"]),
8057
+ skills: row("skill", "id", ["id"]),
8058
+ plugins: row("plugin", "id", ["id"]),
8059
+ plugin_installation_versions: row("plugin_installation", "id", ["id"], { parent: "plugin_id" }),
8060
+ plugin_capability_grants: row("plugin_grant", "id", ["id"], { parent: "plugin_id" }),
8061
+ plugin_private_files: row("plugin_file", "id", ["plugin_id", "id"], { plugin: "plugin_id" }),
8062
+ site_snapshots: { kind: "snapshot", mode: "header", key: "id", pair: ["id"] },
8063
+ page_snapshots: snapshot("page", "page_id"),
8064
+ page_layout_snapshots: snapshot("page_layout", "page_id"),
8065
+ page_module_content_snapshots: snapshot("page_module_content", "page_module_content_id", {
8066
+ parent: "page_id"
8067
+ }),
8068
+ module_snapshots: snapshot("module", "module_id"),
8069
+ template_snapshots: snapshot("template", "template_id"),
8070
+ layout_module_snapshots: snapshot("layout_module", "layout_id"),
8071
+ content_instance_snapshots: snapshot("content_instance", "content_instance_id"),
8072
+ list_snapshots: snapshot("list", "list_id"),
8073
+ list_type_snapshots: snapshot("list_type", "list_type_id"),
8074
+ page_type_snapshots: snapshot("page_type", "page_type_id"),
8075
+ theme_snapshots: snapshot("theme", "theme_id"),
8076
+ plugin_row_snapshots: snapshot("plugin_row", "row_id", {
8077
+ parent: "table_name",
8078
+ plugin: "plugin_id"
8079
+ })
8080
+ };
7967
8081
  // ../shared/src/css-scope-scan.ts
7968
8082
  var GROUPING_AT_RULES = new Set(["media", "supports", "layer", "container", "document"]);
7969
8083
  // ../shared/src/interactions.ts
@@ -8026,6 +8140,13 @@ var RASTER_IMAGE_MIMES = new Set([
8026
8140
  ]);
8027
8141
  var mediaUrlPattern = new RegExp(`${MEDIA_URL_PREFIX}/([a-z0-9][a-z0-9-]{0,63})(?:/([a-z][a-z0-9-]{0,63}))?`, "g");
8028
8142
  var sha256Schema = string2().regex(/^[0-9a-f]{64}$/, "must be hex sha256");
8143
+ var publishedDigestSchema = object({
8144
+ contentSha256: sha256Schema,
8145
+ size: number2().int().nonnegative(),
8146
+ gzipSha256: sha256Schema,
8147
+ gzipSize: number2().int().nonnegative(),
8148
+ profile: string2().min(1).max(64)
8149
+ }).strict();
8029
8150
  var mediaVisibilitySchema = _enum(["library", "reference"]);
8030
8151
  var mediaUploadInputSchema = object({
8031
8152
  sha256: sha256Schema,
@@ -8049,7 +8170,8 @@ var mediaUploadInputSchema = object({
8049
8170
  width: number2().int().positive().nullable(),
8050
8171
  height: number2().int().positive().nullable(),
8051
8172
  sizeBytes: number2().int().positive(),
8052
- storageKey: string2().min(1)
8173
+ storageKey: string2().min(1),
8174
+ digest: publishedDigestSchema
8053
8175
  })).min(1)
8054
8176
  }).strict();
8055
8177
  var mediaListInputSchema = object({
@@ -9058,20 +9180,6 @@ var NESTED_KINDS = new Set(["module", "module-list"]);
9058
9180
  // ../shared/src/lists/address.ts
9059
9181
  var SEG = "[a-z0-9_-]+";
9060
9182
  var ADDRESS_RE = new RegExp(`^(?:list/(${SEG})/item/(${SEG})/(${SEG})` + `|instance/(${SEG})/(${SEG})/item/(${SEG})/(${SEG})` + `|page/(${SEG})/props/(${SEG})` + `|list_type/(${SEG})/node/(${SEG})/(${SEG}))$`);
9061
- // ../shared/src/lists/page-type.ts
9062
- var pageTypeDefSchema = object({
9063
- slug: string2().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/),
9064
- name: string2().min(1).max(200),
9065
- purpose: string2().min(1).max(1000),
9066
- templates: array(string2().uuid()).min(1).max(64),
9067
- defaultTemplate: string2().uuid(),
9068
- properties: array(fieldDefSchema).max(32).default([]),
9069
- vocabularies: array(object({
9070
- list: string2().uuid(),
9071
- cardinality: _enum(["one", "many"]),
9072
- required: boolean2().optional()
9073
- }).strict()).max(16).default([])
9074
- }).strict();
9075
9183
  // ../shared/src/lists/resolve.ts
9076
9184
  var SEG2 = "[a-z0-9_-]+";
9077
9185
  var LIST_VALUE_RE = new RegExp(`^list/(${SEG2})/item/(${SEG2})/(${SEG2})$`);
@@ -9087,7 +9195,8 @@ var LIVE_TOPICS = [
9087
9195
  "credentials",
9088
9196
  "quality",
9089
9197
  "crawl",
9090
- "deploys"
9198
+ "deploys",
9199
+ "draft"
9091
9200
  ];
9092
9201
  var TOPIC_SET = new Set(LIVE_TOPICS);
9093
9202
  // ../shared/src/markup-structure.ts
@@ -9122,8 +9231,43 @@ var pageLogEntrySchema = object({
9122
9231
  // ../shared/src/phase-timer.ts
9123
9232
  var phaseTimingSchema = object({
9124
9233
  phase: string2().min(1),
9125
- ms: number2().int().nonnegative()
9234
+ ms: number2().int().nonnegative(),
9235
+ peakRssMb: number2().int().nonnegative().optional(),
9236
+ cpuMs: number2().int().nonnegative().optional()
9126
9237
  });
9238
+ var RSS_SAMPLE_MS = 25;
9239
+ var MIB = 1024 * 1024;
9240
+ function rssNow() {
9241
+ return typeof process !== "undefined" && typeof process.memoryUsage === "function" ? process.memoryUsage().rss : 0;
9242
+ }
9243
+ var sampler = (() => {
9244
+ const listeners = new Set;
9245
+ let timer = null;
9246
+ return {
9247
+ add(fn) {
9248
+ listeners.add(fn);
9249
+ if (timer === null) {
9250
+ timer = setInterval(() => {
9251
+ const rss = rssNow();
9252
+ for (const l of listeners)
9253
+ l(rss);
9254
+ }, RSS_SAMPLE_MS);
9255
+ timer.unref?.();
9256
+ }
9257
+ },
9258
+ remove(fn) {
9259
+ listeners.delete(fn);
9260
+ if (listeners.size === 0 && timer !== null) {
9261
+ clearInterval(timer);
9262
+ timer = null;
9263
+ }
9264
+ }
9265
+ };
9266
+ })();
9267
+ // ../shared/src/pool-budget.ts
9268
+ var DB_POOL_MAX_ENV = "CAELO_DB_POOL_MAX";
9269
+ var DB_PUBLIC_POOL_MAX_ENV = "CAELO_DB_PUBLIC_POOL_MAX";
9270
+ var MIN_POOL_MAX = 2;
9127
9271
  // ../shared/src/proposal-status.ts
9128
9272
  var PROPOSAL_STATUSES = ["pending", "applied", "rejected", "superseded"];
9129
9273
  var proposalStatus = _enum(PROPOSAL_STATUSES);
@@ -18665,7 +18809,8 @@ function extractPgFields(error) {
18665
18809
  let current = error;
18666
18810
  for (let depth = 0;depth < 5 && typeof current === "object" && current !== null; depth++) {
18667
18811
  const e = current;
18668
- const code = typeof e.code === "string" ? e.code : typeof e.errno === "string" ? e.errno : undefined;
18812
+ const sqlstate = typeof e.errno === "string" && /^[0-9A-Z]{5}$/.test(e.errno) ? e.errno : undefined;
18813
+ const code = sqlstate ?? (typeof e.code === "string" ? e.code : undefined);
18669
18814
  const constraint = typeof e.constraint === "string" ? e.constraint : undefined;
18670
18815
  const table = typeof e.table === "string" ? e.table : undefined;
18671
18816
  const column = typeof e.column === "string" ? e.column : undefined;
@@ -18737,6 +18882,71 @@ function defineOperation(def) {
18737
18882
  };
18738
18883
  }
18739
18884
 
18885
+ // ../query-api/src/pg-failure.ts
18886
+ function parseKey(detail) {
18887
+ if (!detail)
18888
+ return null;
18889
+ const start = detail.indexOf("Key (");
18890
+ if (start === -1)
18891
+ return null;
18892
+ const sep = detail.indexOf(")=(", start + 5);
18893
+ const end = detail.lastIndexOf(")");
18894
+ if (sep === -1 || end <= sep + 2)
18895
+ return null;
18896
+ const columns = detail.slice(start + 5, sep);
18897
+ if (columns.includes(")"))
18898
+ return null;
18899
+ return { columns, values: detail.slice(sep + 3, end) };
18900
+ }
18901
+ function keyPhrase(key) {
18902
+ return key.columns.includes(",") ? `the combination \`${key.columns}\` = (${key.values})` : `\`${key.columns}\` "${key.values}"`;
18903
+ }
18904
+ function describePgFailure(fields) {
18905
+ const key = parseKey(fields.detail);
18906
+ switch (fields.code) {
18907
+ case "23505":
18908
+ return {
18909
+ message: key ? `${keyPhrase(key)} already exists \u2014 choose a different value` : "a row with the same unique value already exists \u2014 choose a different value",
18910
+ internal: false
18911
+ };
18912
+ case "23503":
18913
+ if (fields.detail?.includes("still referenced")) {
18914
+ return {
18915
+ message: "the row is still referenced by other content \u2014 remove those references first",
18916
+ internal: false
18917
+ };
18918
+ }
18919
+ return {
18920
+ message: key ? `${keyPhrase(key)} names a row that does not exist or was deleted \u2014 use an id from the matching list tool` : "a referenced row does not exist or was deleted \u2014 use an id from the matching list tool",
18921
+ internal: false
18922
+ };
18923
+ case "23502":
18924
+ return {
18925
+ message: fields.column ? `\`${fields.column}\` is required but was empty` : "a required value was empty",
18926
+ internal: false
18927
+ };
18928
+ case "23514":
18929
+ return { message: "a value is outside the allowed range or set", internal: false };
18930
+ case "22P02":
18931
+ return {
18932
+ message: "a value has the wrong format (e.g. an id that is not a UUID) \u2014 copy ids exactly",
18933
+ internal: false
18934
+ };
18935
+ case "22001":
18936
+ return { message: "a text value is longer than allowed \u2014 shorten it", internal: false };
18937
+ case "22003":
18938
+ return { message: "a number is out of range", internal: false };
18939
+ default:
18940
+ return {
18941
+ message: "a database error the call cannot fix \u2014 a defect in Caelo, reported automatically",
18942
+ internal: true
18943
+ };
18944
+ }
18945
+ }
18946
+ function rlsDenialMessage(operation, access) {
18947
+ return `permission denied: ${operation} may not ${access} that row as the current actor ` + "(it is not visible on this chat's branch, or belongs to another actor or plugin). " + "Check the ids against the matching list tool; if they are right, the change needs a human with the permission.";
18948
+ }
18949
+
18740
18950
  // ../query-api/src/plugin-schema-provision.ts
18741
18951
  import { createHash } from "crypto";
18742
18952
  var DROPPED_COLUMNS_SETTING = "caelo.plugin_dropped_columns";
@@ -18763,6 +18973,9 @@ async function provisionPluginSchemaInTx(tx, caller, opts) {
18763
18973
  return { schemaName, droppedColumns: [] };
18764
18974
  await tx.execute(sql.raw(opts.sql));
18765
18975
  const dropped = await tx.execute(sql.raw(`SELECT coalesce(current_setting('${DROPPED_COLUMNS_SETTING}', true), '') AS v`));
18976
+ if (opts.journal === true) {
18977
+ await tx.execute(sql`SELECT caelo_journal_attach_plugin_schema(${schemaName}, ${opts.pluginId}::uuid)`);
18978
+ }
18766
18979
  await tx.execute(sql`
18767
18980
  INSERT INTO plugin_schema_provisions (schema_name, sql_digest)
18768
18981
  VALUES (${schemaName}, ${digest})
@@ -18778,18 +18991,25 @@ async function provisionPluginSchemaInTx(tx, caller, opts) {
18778
18991
  // ../query-api/src/pool-guard.ts
18779
18992
  import { AsyncLocalStorage } from "async_hooks";
18780
18993
  var POOL_ACQUIRE_TIMEOUT_MS = 1e4;
18781
- var NESTED_CHECKOUTS = [
18782
- { holder: "plugins.render_hook", issue: 1220 }
18783
- ];
18784
18994
  var LONG_TRANSACTIONS = [
18785
18995
  {
18786
18996
  holder: "static-generator.build",
18787
18997
  reason: "a static site build runs in one transaction for as long as the build takes, idle while plugins render and files are written."
18788
18998
  }
18789
18999
  ];
18790
- var NESTED_CHECKOUT_ALLOWLIST = new Map(NESTED_CHECKOUTS.map((entry) => [entry.holder, entry]));
18791
19000
  var LONG_TRANSACTION_ALLOWLIST = new Map(LONG_TRANSACTIONS.map((entry) => [entry.holder, entry]));
18792
19001
 
19002
+ class SerialQueue {
19003
+ #tail = Promise.resolve();
19004
+ run(fn) {
19005
+ const next = this.#tail.then(fn, fn);
19006
+ this.#tail = next.catch(() => {
19007
+ return;
19008
+ });
19009
+ return next;
19010
+ }
19011
+ }
19012
+
18793
19013
  class NestedPoolCheckoutError extends Error {
18794
19014
  pool;
18795
19015
  holder;
@@ -18815,24 +19035,55 @@ class PoolAcquireTimeoutError extends Error {
18815
19035
  this.timeoutMs = timeoutMs;
18816
19036
  }
18817
19037
  }
18818
- var allowances = new AsyncLocalStorage;
19038
+ var joining = new AsyncLocalStorage;
19039
+ function newMarker(holder, root) {
19040
+ return {
19041
+ holder,
19042
+ active: true,
19043
+ tx: null,
19044
+ identity: null,
19045
+ identityOpName: null,
19046
+ root,
19047
+ joined: new SerialQueue,
19048
+ afterEnd: []
19049
+ };
19050
+ }
19051
+
18819
19052
  class PoolGuard {
18820
19053
  #stores = {
18821
19054
  admin: new AsyncLocalStorage,
18822
19055
  public: new AsyncLocalStorage
18823
19056
  };
18824
- enter(pool, holder) {
18825
- const allowedBy = this.assertMayCheckout(pool, holder);
18826
- return { holder, active: true, allowedBy };
19057
+ plan(pool, holder) {
19058
+ const outer = this.held(pool);
19059
+ if (outer === null)
19060
+ return { kind: "own", marker: newMarker(holder, null) };
19061
+ this.assertMayCheckout(pool, holder);
19062
+ return { kind: "join", outer, marker: newMarker(holder, outer.root ?? outer) };
19063
+ }
19064
+ held(pool) {
19065
+ const outer = this.#stores[pool].getStore();
19066
+ return outer?.active ? outer : null;
18827
19067
  }
18828
19068
  assertMayCheckout(pool, requester) {
18829
- const outer = this.#stores[pool].getStore();
18830
- if (outer === undefined || !outer.active)
18831
- return null;
18832
- const allowedBy = outer.allowedBy ?? (NESTED_CHECKOUT_ALLOWLIST.has(outer.holder) ? outer.holder : null) ?? allowances.getStore() ?? null;
18833
- if (allowedBy === null)
19069
+ const outer = this.held(pool);
19070
+ if (outer === null || joining.getStore() === true)
19071
+ return;
19072
+ throw new NestedPoolCheckoutError(pool, outer.holder, requester);
19073
+ }
19074
+ assertNoHeldTransaction(pool, requester) {
19075
+ const outer = this.held(pool);
19076
+ if (outer !== null)
18834
19077
  throw new NestedPoolCheckoutError(pool, outer.holder, requester);
18835
- return allowedBy;
19078
+ }
19079
+ async deferUntilEnd(pool, work) {
19080
+ const outer = this.held(pool);
19081
+ if (outer === null) {
19082
+ await work();
19083
+ return false;
19084
+ }
19085
+ (outer.root ?? outer).afterEnd.push(work);
19086
+ return true;
18836
19087
  }
18837
19088
  async run(pool, marker, fn) {
18838
19089
  try {
@@ -18870,6 +19121,15 @@ function collectPostCommitEffects(tx) {
18870
19121
  pendingByTx.set(tx, pending);
18871
19122
  return pending;
18872
19123
  }
19124
+ function mergeSavepointEffects(effects, parentTx) {
19125
+ if (effects.length === 0)
19126
+ return;
19127
+ const pending = pendingByTx.get(parentTx);
19128
+ if (pending === undefined) {
19129
+ throw new Error(`afterCommit(${effects[0]?.label}): the transaction this call joined collects no post-commit effects`);
19130
+ }
19131
+ pending.push(...effects);
19132
+ }
18873
19133
  async function runPostCommitEffects(effects) {
18874
19134
  const failures = [];
18875
19135
  for (const effect of effects) {
@@ -18894,6 +19154,10 @@ function notifyOperationCommitted(operationName) {
18894
19154
  }
18895
19155
  }
18896
19156
  }
19157
+ var commitNotice = null;
19158
+ function commitNoticeOf(operationName) {
19159
+ return commitNotice === null ? null : commitNotice(operationName);
19160
+ }
18897
19161
 
18898
19162
  // ../query-api/src/adapter.ts
18899
19163
  var SQL3 = globalThis.Bun?.SQL;
@@ -18932,13 +19196,21 @@ class AdminDatabaseUnavailableError extends Error {
18932
19196
  function resolvePoolMax(explicit) {
18933
19197
  if (explicit !== undefined)
18934
19198
  return validatePoolMax(explicit, "poolMax option");
18935
- const raw = process.env.CAELO_DB_POOL_MAX;
19199
+ const raw = process.env[DB_POOL_MAX_ENV];
18936
19200
  if (raw === undefined || raw === "")
18937
19201
  return;
18938
- return validatePoolMax(Number(raw), "CAELO_DB_POOL_MAX");
19202
+ return validatePoolMax(Number(raw), DB_POOL_MAX_ENV);
19203
+ }
19204
+ function resolvePublicPoolMax(explicitPublic, explicitAll) {
19205
+ if (explicitPublic !== undefined)
19206
+ return validatePoolMax(explicitPublic, "publicPoolMax option");
19207
+ const raw = process.env[DB_PUBLIC_POOL_MAX_ENV];
19208
+ if (raw !== undefined && raw !== "")
19209
+ return validatePoolMax(Number(raw), DB_PUBLIC_POOL_MAX_ENV);
19210
+ return resolvePoolMax(explicitAll);
18939
19211
  }
18940
19212
  function validatePoolMax(value, source) {
18941
- if (!Number.isInteger(value) || value < 2) {
19213
+ if (!Number.isInteger(value) || value < MIN_POOL_MAX) {
18942
19214
  throw new Error(`${source} must be an integer >= 2 (got ${value}); ` + `1 self-deadlocks the pool. Leave it unset for the Bun default of 10.`);
18943
19215
  }
18944
19216
  return value;
@@ -18962,12 +19234,18 @@ function postCommitFailureMessage(failures) {
18962
19234
  const list = failures.map((f) => `${f.effect.label} (${f.message})`).join("; ");
18963
19235
  return `The change is saved, but its follow-up in the running server failed: ${list}. Do not repeat the change. Restart the admin server so the running state is rebuilt from the saved data; the failure is in the audit log.`;
18964
19236
  }
19237
+ var LISTEN_CONNECTIONS_PER_INSTANCE = 1;
18965
19238
 
18966
19239
  class DatabaseAdapter {
18967
19240
  #admin;
18968
19241
  #public;
18969
19242
  #adminRaw;
18970
19243
  #publicRaw;
19244
+ #adminUrl;
19245
+ #publicUrl;
19246
+ #listeners = new Map;
19247
+ #listenLost = new Map;
19248
+ #listenersClosing = false;
18971
19249
  #skipVerify;
18972
19250
  #roles;
18973
19251
  #acquireTimeoutMs;
@@ -18983,10 +19261,13 @@ class DatabaseAdapter {
18983
19261
  }
18984
19262
  assertPoolSafeBun(globalThis.Bun?.version ?? "0");
18985
19263
  const poolMax = resolvePoolMax(config.poolMax);
19264
+ const publicPoolMax = resolvePublicPoolMax(config.publicPoolMax, config.poolMax);
18986
19265
  const idleTimeout = config.poolIdleTimeoutSeconds ?? POOL_IDLE_TIMEOUT_SECONDS;
18987
- const open = (url) => new SQL3(url, poolMax === undefined ? { idleTimeout } : { max: poolMax, idleTimeout });
18988
- this.#adminRaw = config.adminDatabaseUrl === null ? null : open(config.adminDatabaseUrl);
18989
- this.#publicRaw = open(config.publicDatabaseUrl);
19266
+ const open = (url, max) => new SQL3(url, max === undefined ? { idleTimeout } : { max, idleTimeout });
19267
+ this.#adminRaw = config.adminDatabaseUrl === null ? null : open(config.adminDatabaseUrl, poolMax);
19268
+ this.#publicRaw = open(config.publicDatabaseUrl, publicPoolMax);
19269
+ this.#adminUrl = config.adminDatabaseUrl;
19270
+ this.#publicUrl = config.publicDatabaseUrl;
18990
19271
  this.#onStatement = config.onStatement ?? null;
18991
19272
  const observe = this.#onStatement;
18992
19273
  this.#drizzleOptions = observe ? { logger: { logQuery: (query) => observe(query) } } : {};
@@ -18999,22 +19280,64 @@ class DatabaseAdapter {
18999
19280
  const raw = pool === "admin" ? this.#adminRaw : this.#publicRaw;
19000
19281
  if (raw === null)
19001
19282
  throw new AdminDatabaseUnavailableError(holder);
19002
- const marker = this.#guard.enter(pool, holder);
19283
+ const plan = this.#guard.plan(pool, holder);
19284
+ if (plan.kind === "join")
19285
+ return this.#joinHeld(pool, plan.outer, plan.marker, fn);
19286
+ const marker = plan.marker;
19003
19287
  const reserved = await this.#reserve(pool, raw, holder);
19004
19288
  try {
19005
19289
  const db = drizzle({ client: reserved, ...this.#drizzleOptions });
19006
19290
  this.#onStatement?.("BEGIN");
19007
19291
  const out = await db.transaction((tx) => this.#guard.run(pool, marker, async () => {
19292
+ marker.tx = tx;
19008
19293
  if (exemptFromSessionTimeouts(holder))
19009
19294
  await liftSessionTimeouts(tx);
19010
- return fn(tx);
19295
+ return fn(tx, false);
19011
19296
  }));
19012
19297
  this.#onStatement?.("COMMIT");
19013
19298
  return out;
19014
19299
  } finally {
19015
19300
  reserved.release();
19301
+ await this.#runAfterEnd(marker);
19302
+ }
19303
+ }
19304
+ #joinHeld(pool, outer, marker, fn) {
19305
+ return outer.joined.run(async () => {
19306
+ const outerTx = outer.tx;
19307
+ if (outerTx === null || !outer.active) {
19308
+ throw new NestedPoolCheckoutError(pool, outer.holder, marker.holder);
19309
+ }
19310
+ let effects = [];
19311
+ const out = await outerTx.transaction((savepoint) => this.#guard.run(pool, marker, async () => {
19312
+ marker.tx = savepoint;
19313
+ effects = collectPostCommitEffects(savepoint);
19314
+ return fn(savepoint, true);
19315
+ }));
19316
+ mergeSavepointEffects(effects, outerTx);
19317
+ if (outer.identity !== null) {
19318
+ await this.#setIdentity(outerTx, outer.identity, outer.identityOpName);
19319
+ }
19320
+ return out;
19321
+ });
19322
+ }
19323
+ async#runAfterEnd(marker) {
19324
+ for (const work of marker.afterEnd.splice(0)) {
19325
+ try {
19326
+ await work();
19327
+ } catch (thrown) {
19328
+ console.error("[query-api] work deferred until a transaction ended failed", {
19329
+ holder: marker.holder,
19330
+ error: thrown instanceof Error ? thrown.message : String(thrown)
19331
+ });
19332
+ }
19016
19333
  }
19017
19334
  }
19335
+ deferUntilHeldTransactionEnds(work) {
19336
+ return this.#guard.deferUntilEnd("admin", work);
19337
+ }
19338
+ holdsTransaction(pool) {
19339
+ return this.#guard.held(pool) !== null;
19340
+ }
19018
19341
  async#reserve(pool, raw, requester) {
19019
19342
  const deadline = new AbortController;
19020
19343
  const timer = setTimeout(() => deadline.abort(), this.#acquireTimeoutMs);
@@ -19129,10 +19452,13 @@ class DatabaseAdapter {
19129
19452
  let postCommit = [];
19130
19453
  let result;
19131
19454
  let handlerError = null;
19455
+ let joinedHolder = false;
19132
19456
  try {
19133
- result = await this.#transaction(pool, op.name, async (tx) => {
19134
- postCommit = collectPostCommitEffects(tx);
19135
- await this.#setIdentity(tx, ctx);
19457
+ result = await this.#transaction(pool, op.name, async (tx, joined) => {
19458
+ joinedHolder = joined;
19459
+ if (!joined)
19460
+ postCommit = collectPostCommitEffects(tx);
19461
+ await this.#setIdentity(tx, ctx, op.name);
19136
19462
  if (op.database === "cms_admin") {
19137
19463
  const denied = await firstInvisibleBranch(tx, namedBranches(ctx, validatedInput));
19138
19464
  if (denied !== null)
@@ -19142,7 +19468,12 @@ class DatabaseAdapter {
19142
19468
  if (!out.ok)
19143
19469
  handlerError = out;
19144
19470
  if (out.ok && op.database === "cms_admin") {
19145
- publishAfterCommit = await this.#projectionDirty(tx);
19471
+ if (!joined)
19472
+ publishAfterCommit = await this.#projectionDirty(tx);
19473
+ const notice = commitNoticeOf(op.name);
19474
+ if (notice !== null) {
19475
+ await tx.execute(sql`SELECT pg_notify(${notice.channel}, ${notice.payload})`);
19476
+ }
19146
19477
  }
19147
19478
  return out;
19148
19479
  });
@@ -19152,23 +19483,11 @@ class DatabaseAdapter {
19152
19483
  if (thrown instanceof OperationAbortError) {
19153
19484
  return err(thrown.queryError);
19154
19485
  }
19155
- if (isRlsDenial(thrown)) {
19156
- return err({
19157
- kind: "RLSDenied",
19158
- operation: op.name,
19159
- detail: thrown.message ?? "RLS policy denied the write"
19160
- });
19161
- }
19162
19486
  const pgDetail = extractPgFields(thrown);
19163
19487
  if (pgDetail?.code === COMMIT_ROLLED_BACK && handlerError !== null) {
19164
19488
  return handlerError;
19165
19489
  }
19166
- return err({
19167
- kind: "HandlerError",
19168
- operation: op.name,
19169
- message: thrown instanceof Error ? thrown.message : String(thrown),
19170
- ...pgDetail ? { pgDetail } : {}
19171
- });
19490
+ return err(thrownToQueryError(op.name, thrown, "write"));
19172
19491
  }
19173
19492
  const effectFailures = await this.#settlePostCommit(op.name, ctx, postCommit);
19174
19493
  let publishFailure = null;
@@ -19179,7 +19498,7 @@ class DatabaseAdapter {
19179
19498
  publishFailure = `The change is saved, but the public site does not serve it yet: publishing it failed (${thrown instanceof Error ? thrown.message : String(thrown)}). Do not repeat the change; it is published automatically within seconds once the public database answers.`;
19180
19499
  }
19181
19500
  }
19182
- if (result.ok)
19501
+ if (result.ok && !joinedHolder)
19183
19502
  notifyOperationCommitted(op.name);
19184
19503
  if (effectFailures.length > 0) {
19185
19504
  const effects = postCommitFailureMessage(effectFailures);
@@ -19213,22 +19532,77 @@ class DatabaseAdapter {
19213
19532
  throw thrown;
19214
19533
  if (thrown instanceof OperationAbortError)
19215
19534
  return err(thrown.queryError);
19216
- const pgDetail = extractPgFields(thrown);
19217
- return err({
19218
- kind: "HandlerError",
19219
- operation: op.name,
19220
- message: thrown instanceof Error ? thrown.message : String(thrown),
19221
- ...pgDetail ? { pgDetail } : {}
19222
- });
19535
+ return err(thrownToQueryError(op.name, thrown, "write"));
19223
19536
  }
19224
19537
  }
19225
- async#setIdentity(tx, ctx) {
19538
+ async#setIdentity(tx, ctx, opName = null) {
19226
19539
  await tx.execute(sql`SELECT
19227
19540
  set_config('caelo.actor_id', ${ctx.actorId}, true),
19228
19541
  set_config('caelo.actor_kind', ${ctx.actorKind}, true),
19229
19542
  set_config('caelo.plugin_id', ${ctx.pluginId ?? ""}, true),
19230
19543
  set_config('caelo.chat_branch_id', ${ctx.chatBranchId ?? ""}, true),
19231
- set_config('caelo.chat_task_id', ${ctx.chatTaskId ?? ""}, true)`);
19544
+ set_config('caelo.chat_task_id', ${ctx.chatTaskId ?? ""}, true),
19545
+ set_config('caelo.op_name', ${opName ?? ""}, true)`);
19546
+ this.#recordIdentity(tx, ctx, opName);
19547
+ }
19548
+ async#setPublicIdentity(tx, ctx) {
19549
+ await tx.execute(sql`SELECT
19550
+ set_config('caelo.actor_id', ${ctx.actorId}, true),
19551
+ set_config('caelo.actor_kind', ${ctx.actorKind}, true),
19552
+ set_config('caelo.plugin_id', ${ctx.pluginId ?? ""}, true)`);
19553
+ this.#recordIdentity(tx, ctx, null);
19554
+ }
19555
+ #recordIdentity(tx, ctx, opName) {
19556
+ for (const pool of ["admin", "public"]) {
19557
+ const marker = this.#guard.held(pool);
19558
+ if (marker !== null && marker.tx === tx) {
19559
+ marker.identity = ctx;
19560
+ marker.identityOpName = opName;
19561
+ }
19562
+ }
19563
+ }
19564
+ async listen(pool, channel, callbacks) {
19565
+ const url = pool === "admin" ? this.#adminUrl : this.#publicUrl;
19566
+ if (url === null)
19567
+ throw new AdminDatabaseUnavailableError(`listen(${channel})`);
19568
+ let lost = this.#listenLost.get(pool);
19569
+ if (!lost) {
19570
+ lost = new Set;
19571
+ this.#listenLost.set(pool, lost);
19572
+ }
19573
+ const lostSet = lost;
19574
+ let conn = this.#listeners.get(pool);
19575
+ if (!conn) {
19576
+ conn = new SQL3(url, {
19577
+ max: LISTEN_CONNECTIONS_PER_INSTANCE,
19578
+ onclose: (cause) => {
19579
+ if (this.#listenersClosing)
19580
+ return;
19581
+ for (const onLost of lostSet) {
19582
+ try {
19583
+ onLost(cause);
19584
+ } catch (thrown) {
19585
+ console.error(`[query-api] a LISTEN onLost callback failed`, thrown);
19586
+ }
19587
+ }
19588
+ }
19589
+ });
19590
+ this.#listeners.set(pool, conn);
19591
+ }
19592
+ const onLost = callbacks.onLost;
19593
+ lostSet.add(onLost);
19594
+ const handle = await conn.listen(channel, callbacks.onNotify, callbacks.onListen);
19595
+ return {
19596
+ unlisten: async () => {
19597
+ lostSet.delete(onLost);
19598
+ await handle.unlisten();
19599
+ }
19600
+ };
19601
+ }
19602
+ async notify(channel, payload) {
19603
+ await this.#transaction("admin", "notify", async (tx) => {
19604
+ await tx.execute(sql`SELECT pg_notify(${channel}, ${payload})`);
19605
+ });
19232
19606
  }
19233
19607
  async#runReadOperation(op, ctx, validatedInput) {
19234
19608
  try {
@@ -19244,20 +19618,7 @@ class DatabaseAdapter {
19244
19618
  throw thrown;
19245
19619
  if (thrown instanceof OperationAbortError)
19246
19620
  return err(thrown.queryError);
19247
- if (isRlsDenial(thrown)) {
19248
- return err({
19249
- kind: "RLSDenied",
19250
- operation: op.name,
19251
- detail: thrown.message ?? "RLS policy denied the read"
19252
- });
19253
- }
19254
- const pgDetail = extractPgFields(thrown);
19255
- return err({
19256
- kind: "HandlerError",
19257
- operation: op.name,
19258
- message: thrown instanceof Error ? thrown.message : String(thrown),
19259
- ...pgDetail ? { pgDetail } : {}
19260
- });
19621
+ return err(thrownToQueryError(op.name, thrown, "read"));
19261
19622
  }
19262
19623
  }
19263
19624
  readScope(ctx, holder = "reads.read") {
@@ -19306,7 +19667,14 @@ class DatabaseAdapter {
19306
19667
  return failures;
19307
19668
  }
19308
19669
  async close() {
19309
- await Promise.all([this.#adminRaw?.end(), this.#publicRaw.end()]);
19670
+ this.#listenersClosing = true;
19671
+ const listeners = [...this.#listeners.values()];
19672
+ this.#listeners.clear();
19673
+ await Promise.all([
19674
+ this.#adminRaw?.end(),
19675
+ this.#publicRaw.end(),
19676
+ ...listeners.map((l) => l.close())
19677
+ ]);
19310
19678
  }
19311
19679
  async withAdminTransaction(ctx, fn, options = {}) {
19312
19680
  const holder = options.holder ?? "withAdminTransaction";
@@ -19316,15 +19684,17 @@ class DatabaseAdapter {
19316
19684
  await this.verifyRoles();
19317
19685
  let publishAfterCommit = false;
19318
19686
  let postCommit = [];
19319
- const out = await this.#transaction("admin", holder, async (tx) => {
19320
- postCommit = collectPostCommitEffects(tx);
19321
- await this.#setIdentity(tx, ctx);
19687
+ const out = await this.#transaction("admin", holder, async (tx, joined) => {
19688
+ if (!joined)
19689
+ postCommit = collectPostCommitEffects(tx);
19690
+ await this.#setIdentity(tx, ctx, options.holder ?? null);
19322
19691
  const denied = await firstInvisibleBranch(tx, namedBranches(ctx, undefined));
19323
19692
  if (denied !== null) {
19324
19693
  throw new OperationAbortError(branchNotFound("withAdminTransaction", denied));
19325
19694
  }
19326
19695
  const result = await fn(tx);
19327
- publishAfterCommit = await this.#projectionDirty(tx);
19696
+ if (!joined)
19697
+ publishAfterCommit = await this.#projectionDirty(tx);
19328
19698
  return result;
19329
19699
  });
19330
19700
  const effectFailures = await this.#settlePostCommit(holder, ctx, postCommit);
@@ -19340,11 +19710,10 @@ class DatabaseAdapter {
19340
19710
  if (!this.#skipVerify)
19341
19711
  await this.verifyRoles();
19342
19712
  let postCommit = [];
19343
- const out = await this.#transaction("public", holder, async (tx) => {
19344
- postCommit = collectPostCommitEffects(tx);
19345
- await tx.execute(sql`SELECT set_config('caelo.actor_id', ${ctx.actorId}, true)`);
19346
- await tx.execute(sql`SELECT set_config('caelo.actor_kind', ${ctx.actorKind}, true)`);
19347
- await tx.execute(sql`SELECT set_config('caelo.plugin_id', ${ctx.pluginId ?? ""}, true)`);
19713
+ const out = await this.#transaction("public", holder, async (tx, joined) => {
19714
+ if (!joined)
19715
+ postCommit = collectPostCommitEffects(tx);
19716
+ await this.#setPublicIdentity(tx, ctx);
19348
19717
  return await fn(tx);
19349
19718
  });
19350
19719
  const effectFailures = await this.#settlePostCommit(holder, ctx, postCommit);
@@ -19355,11 +19724,11 @@ class DatabaseAdapter {
19355
19724
  rawAdmin() {
19356
19725
  if (this.#adminRaw === null)
19357
19726
  throw new AdminDatabaseUnavailableError("rawAdmin");
19358
- this.#guard.assertMayCheckout("admin", "rawAdmin()");
19727
+ this.#guard.assertNoHeldTransaction("admin", "rawAdmin()");
19359
19728
  return this.#adminRaw;
19360
19729
  }
19361
19730
  rawPublic() {
19362
- this.#guard.assertMayCheckout("public", "rawPublic()");
19731
+ this.#guard.assertNoHeldTransaction("public", "rawPublic()");
19363
19732
  return this.#publicRaw;
19364
19733
  }
19365
19734
  async provisionPluginPublicSchema(opts) {
@@ -19386,7 +19755,10 @@ class DatabaseAdapter {
19386
19755
  if (!this.#skipVerify)
19387
19756
  await this.verifyRoles();
19388
19757
  await this.#transaction("admin", "provisionPluginAdminSchema", async (tx) => {
19389
- const outcome = await provisionPluginSchemaInTx(tx, "provisionPluginAdminSchema", opts);
19758
+ const outcome = await provisionPluginSchemaInTx(tx, "provisionPluginAdminSchema", {
19759
+ ...opts,
19760
+ journal: true
19761
+ });
19390
19762
  if (outcome.droppedColumns.length > 0) {
19391
19763
  await auditDroppedColumns(tx, "cms_admin", opts.pluginId, outcome);
19392
19764
  }
@@ -19419,11 +19791,11 @@ class DatabaseAdapter {
19419
19791
  }
19420
19792
  get admin() {
19421
19793
  const db = this.#adminDb("admin pool");
19422
- this.#guard.assertMayCheckout("admin", "adapter.admin");
19794
+ this.#guard.assertNoHeldTransaction("admin", "adapter.admin");
19423
19795
  return db;
19424
19796
  }
19425
19797
  get public() {
19426
- this.#guard.assertMayCheckout("public", "adapter.public");
19798
+ this.#guard.assertNoHeldTransaction("public", "adapter.public");
19427
19799
  return this.#public;
19428
19800
  }
19429
19801
  }
@@ -19437,6 +19809,39 @@ async function auditDroppedColumns(tx, database, pluginId, outcome) {
19437
19809
  resultSummary: `${database}.${outcome.schemaName}: dropped ${outcome.droppedColumns.join(", ")}`
19438
19810
  });
19439
19811
  }
19812
+ function thrownToQueryError(operation, thrown, access) {
19813
+ const raw = thrown instanceof Error ? thrown.message : String(thrown);
19814
+ if (isRlsDenial(thrown)) {
19815
+ console.warn(`[query-api] ${operation}: RLS denied the ${access}: ${raw}`);
19816
+ return { kind: "RLSDenied", operation, detail: rlsDenialMessage(operation, access) };
19817
+ }
19818
+ const pgDetail = extractPgFields(thrown);
19819
+ if (pgDetail?.code) {
19820
+ const described = describePgFailure(pgDetail);
19821
+ if (described.internal)
19822
+ console.error(`[query-api] ${operation} failed: ${raw}`, pgDetail);
19823
+ return {
19824
+ kind: "HandlerError",
19825
+ operation,
19826
+ message: described.message,
19827
+ pgDetail,
19828
+ ...described.internal ? { internal: true } : {}
19829
+ };
19830
+ }
19831
+ return {
19832
+ kind: "HandlerError",
19833
+ operation,
19834
+ message: raw,
19835
+ internal: true,
19836
+ ...pgDetail ? { pgDetail } : {}
19837
+ };
19838
+ }
19839
+ // ../query-api/src/change-journal.ts
19840
+ function textArray(values) {
19841
+ return sql`ARRAY(SELECT jsonb_array_elements_text((${JSON.stringify(values)}::text)::jsonb))`;
19842
+ }
19843
+ var JOURNAL_RETENTION_MS = 24 * 60 * 60 * 1000;
19844
+ var JOURNAL_STALLED_CONSUMER_MS = 7 * 24 * 60 * 60 * 1000;
19440
19845
  // ../query-api/src/execute.ts
19441
19846
  var allowAllRateLimiter = {
19442
19847
  async check() {
@@ -19472,8 +19877,15 @@ async function execute(registry, adapter, ctx, name, rawInput, options = {}) {
19472
19877
  adapter,
19473
19878
  ...options.signal ? { signal: options.signal } : {}
19474
19879
  });
19475
- if (result.ok)
19880
+ if (result.ok) {
19476
19881
  notifyOperationCommitted(op.name);
19882
+ const notice = commitNoticeOf(op.name);
19883
+ if (notice !== null) {
19884
+ await adapter.notify(notice.channel, notice.payload).catch((thrown) => {
19885
+ console.error(`[query-api] could not notify other instances of ${op.name}`, thrown);
19886
+ });
19887
+ }
19888
+ }
19477
19889
  return result;
19478
19890
  }
19479
19891
  return adapter.runOperation(op, ctx, parsed.data);
@@ -19537,6 +19949,7 @@ var claimOutput = object({
19537
19949
  });
19538
19950
  var jobClaimOp = defineOperation({
19539
19951
  name: "platform_updates.job_claim",
19952
+ readScope: "request",
19540
19953
  actorScope: ["system"],
19541
19954
  database: "cms_admin",
19542
19955
  input: object({
@@ -19637,6 +20050,7 @@ var jobClaimOp = defineOperation({
19637
20050
  });
19638
20051
  var jobHandoverOp = defineOperation({
19639
20052
  name: "platform_updates.job_handover",
20053
+ readScope: "request",
19640
20054
  actorScope: ["system"],
19641
20055
  database: "cms_admin",
19642
20056
  input: object({
@@ -19667,6 +20081,7 @@ var jobHandoverOp = defineOperation({
19667
20081
  });
19668
20082
  var jobEventOp = defineOperation({
19669
20083
  name: "platform_updates.job_event",
20084
+ readScope: "request",
19670
20085
  actorScope: ["system"],
19671
20086
  database: "cms_admin",
19672
20087
  input: object({
@@ -19687,6 +20102,7 @@ var jobEventOp = defineOperation({
19687
20102
  var digest2 = string2().regex(/^sha256:[0-9a-f]{64}$/);
19688
20103
  var jobRecordOp = defineOperation({
19689
20104
  name: "platform_updates.job_record",
20105
+ readScope: "request",
19690
20106
  actorScope: ["system"],
19691
20107
  database: "cms_admin",
19692
20108
  input: object({
@@ -19722,6 +20138,7 @@ var jobRecordOp = defineOperation({
19722
20138
  });
19723
20139
  var jobFinishOp = defineOperation({
19724
20140
  name: "platform_updates.job_finish",
20141
+ readScope: "request",
19725
20142
  actorScope: ["system"],
19726
20143
  database: "cms_admin",
19727
20144
  input: object({
@@ -19733,7 +20150,7 @@ var jobFinishOp = defineOperation({
19733
20150
  }).strict(),
19734
20151
  output: object({ finished: boolean2() }),
19735
20152
  handler: async (ctx, input, tx) => {
19736
- const codes = input.codes.length > 0 ? sql`ARRAY[${sql.join(input.codes.map((c) => sql`${c}`), sql`, `)}]::text[]` : sql`'{}'::text[]`;
20153
+ const codes = textArray(input.codes);
19737
20154
  const rows = await tx.execute(sql`
19738
20155
  UPDATE platform_update_requests
19739
20156
  SET status = ${input.status}, status_reason = ${input.reason},
@@ -19763,6 +20180,7 @@ var jobFinishOp = defineOperation({
19763
20180
  });
19764
20181
  var agentHeartbeatOp = defineOperation({
19765
20182
  name: "platform_updates.agent_heartbeat",
20183
+ readScope: "request",
19766
20184
  actorScope: ["system"],
19767
20185
  database: "cms_admin",
19768
20186
  input: object({