@supacloud/compiler 0.59.0 → 0.60.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
@@ -86,7 +86,8 @@ attempts. The default policy requires `requireRouteEffects`,
86
86
  ## Unified Database Contracts
87
87
 
88
88
  Database contracts are protocol-aware: configure only the database sources the
89
- application uses (`postgrestTypes`, `modelSchema`, or `graphql`). The generated
89
+ application uses (`postgrestTypes`, `modelSchema`, `graphql`, `rpcRegistry` or
90
+ `rpcCatalog`). The generated
90
91
  `database.manifest.json` is a TypeBox-validated, fingerprinted build boundary;
91
92
  consumers must reject it when its schema or migration fingerprint is stale.
92
93
 
@@ -99,6 +100,8 @@ type entry point while preserving PostgREST, ZModel and GraphQL native types:
99
100
  "outDir": "generated",
100
101
  "postgrestTypes": "database.types.ts",
101
102
  "modelSchema": "zenstack/generated/schema.ts",
103
+ "rpcRegistry": "db/rpc-registry.json",
104
+ "rpcCatalog": "db/contracts/rpc-catalog.json",
102
105
  "role": "authenticated",
103
106
  "graphql": {
104
107
  "schema": "graphql/schema.graphql",
@@ -114,11 +117,14 @@ snapshot and a role-scoped `pg_graphql` schema snapshot. The generated barrel
114
117
  exports `Database`, `QueryData`, ZModel schema types and GraphQL contracts.
115
118
  Consumers need `@supabase/supabase-js` and their ZenStack dependencies.
116
119
 
117
- `--check` detects drift in supplied snapshots, local ZModel imports, migration
118
- content/order and generated artifacts without writing. This is an offline check,
119
- not proof of live database parity or authorization. Refresh snapshots from the
120
- intended database and role before running it. Keep runtime decoders at untrusted
121
- boundaries; protocol result shapes are not interchangeable.
120
+ `--check` detects drift in supplied snapshots, local ZModel imports, RPC
121
+ registry/catalog inputs, migration content/order and generated artifacts without
122
+ writing. This is an offline check, not proof of live database parity or
123
+ authorization. Refresh snapshots from the intended database and role before
124
+ running it. Keep runtime decoders at untrusted boundaries; protocol result shapes
125
+ are not interchangeable. When `databaseGovernance.rpcBoundary` is configured,
126
+ the compiler also fails if a named Command RPC capability has no registry/catalog
127
+ source or explicit historical entry.
122
128
 
123
129
  Architecture references: Micronaut for compile-time AOP, NestJS for feature modules,
124
130
  and Angular for DI semantics. Compiled DI requires constructor injection and generated scope factories.
package/dist/cli.js CHANGED
@@ -2097,6 +2097,47 @@ function allAspects(module) {
2097
2097
  ...(module.jobs ?? []).flatMap((job) => job.aspects ?? [])
2098
2098
  ];
2099
2099
  }
2100
+ function uniqueStages(paths) {
2101
+ return [...new Set(paths.flat())];
2102
+ }
2103
+ function commandStagePaths(command, aspects) {
2104
+ const prefix = ["commandExecutor", "authorize"];
2105
+ if (command.branches?.length) {
2106
+ const branchPaths = command.branches.map((branch) => [
2107
+ ...prefix,
2108
+ `rpc:${branch.rpc}`,
2109
+ ...aspects,
2110
+ "handler"
2111
+ ]);
2112
+ if (command.branchesFallback === "command") {
2113
+ branchPaths.push([
2114
+ ...prefix,
2115
+ ...command.idempotency === "required" ? ["idempotency"] : [],
2116
+ ...command.transaction === "required" ? ["transaction"] : [],
2117
+ ...aspects,
2118
+ "handler",
2119
+ ...command.audit ? ["audit"] : []
2120
+ ]);
2121
+ }
2122
+ return branchPaths;
2123
+ }
2124
+ return [[
2125
+ ...prefix,
2126
+ ...command.rpc ? [`rpc:${command.rpc}`] : [
2127
+ ...command.idempotency === "required" ? ["idempotency"] : [],
2128
+ ...command.transaction === "required" ? ["transaction"] : []
2129
+ ],
2130
+ ...aspects,
2131
+ "handler",
2132
+ ...command.audit && !command.rpc ? ["audit"] : []
2133
+ ]];
2134
+ }
2135
+ function planStages(paths, branches) {
2136
+ const stages = uniqueStages(paths);
2137
+ if (branches && !stages.includes("rpc:selected-branch"))
2138
+ stages.splice(2, 0, "rpc:selected-branch");
2139
+ return paths.length > 1 ? { stages, stagePaths: paths } : { stages };
2140
+ }
2100
2141
  function createExecutionPlans(graph) {
2101
2142
  const aspects = (boundary, refs = []) => refs.map((ref, index) => `${boundary}.aspect[${index}]:${ref.name}`);
2102
2143
  return graph.modules.flatMap((module) => [
@@ -2106,46 +2147,34 @@ function createExecutionPlans(graph) {
2106
2147
  const command = module.commands.find((item) => item.className === commandRef || item.name === commandRef);
2107
2148
  const boundCommand = route.command === undefined ? undefined : command;
2108
2149
  const path = `${controller.path}/${route.path}`.replace(/\/+/g, "/");
2150
+ const routeAspects = [
2151
+ ...aspects(`module:${module.name}`, module.aspects),
2152
+ ...aspects("route", route.aspects),
2153
+ ...aspects("command", boundCommand?.aspects)
2154
+ ];
2155
+ const stagePaths = boundCommand === undefined ? undefined : commandStagePaths(boundCommand, routeAspects);
2156
+ const plannedStages = stagePaths === undefined ? { stages: [...routeAspects, "handler"] } : planStages(stagePaths, Boolean(boundCommand?.branches?.length));
2109
2157
  return {
2110
2158
  module: module.name,
2111
2159
  kind: "route",
2112
2160
  name: `${route.method} ${path.length > 1 ? path.replace(/\/+$/, "") : path}`,
2113
2161
  ...command ? { command: command.name } : {},
2114
- stages: [
2115
- ...boundCommand ? [
2116
- "commandExecutor",
2117
- "authorize",
2118
- ...boundCommand.branches?.length ? ["rpc:selected-branch"] : boundCommand.rpc ? [`rpc:${boundCommand.rpc}`] : [
2119
- ...boundCommand.idempotency === "required" ? ["idempotency"] : [],
2120
- ...boundCommand.transaction === "required" ? ["transaction"] : []
2121
- ]
2122
- ] : [],
2123
- ...aspects(`module:${module.name}`, module.aspects),
2124
- ...aspects("route", route.aspects),
2125
- ...aspects("command", boundCommand?.aspects),
2126
- "handler",
2127
- ...boundCommand?.audit && !boundCommand.rpc && !boundCommand.branches?.length ? ["audit"] : []
2128
- ]
2162
+ ...plannedStages
2129
2163
  };
2130
2164
  })),
2131
- ...module.commands.map((command) => ({
2132
- module: module.name,
2133
- kind: "command",
2134
- name: command.name,
2135
- command: command.name,
2136
- stages: [
2137
- "commandExecutor",
2138
- "authorize",
2139
- ...command.branches?.length ? ["rpc:selected-branch"] : command.rpc ? [`rpc:${command.rpc}`] : [
2140
- ...command.idempotency === "required" ? ["idempotency"] : [],
2141
- ...command.transaction === "required" ? ["transaction"] : []
2142
- ],
2165
+ ...module.commands.map((command) => {
2166
+ const stagePaths = commandStagePaths(command, [
2143
2167
  ...aspects(`module:${module.name}`, module.aspects),
2144
- ...aspects("command", command.aspects),
2145
- "handler",
2146
- ...command.audit && !command.rpc && !command.branches?.length ? ["audit"] : []
2147
- ]
2148
- })),
2168
+ ...aspects("command", command.aspects)
2169
+ ]);
2170
+ return {
2171
+ module: module.name,
2172
+ kind: "command",
2173
+ name: command.name,
2174
+ command: command.name,
2175
+ ...planStages(stagePaths, Boolean(command.branches?.length))
2176
+ };
2177
+ }),
2149
2178
  ...(module.jobs ?? []).map((job) => ({
2150
2179
  module: module.name,
2151
2180
  kind: "job",
@@ -2480,26 +2509,29 @@ function renderApplication(graph, options) {
2480
2509
  }
2481
2510
  };
2482
2511
  }));
2483
- const commands = module.commands.map((command) => ({
2484
- name: command.name,
2485
- className: command.className,
2486
- executor: "commandExecutor",
2487
- adapter: command.rpc ?? null,
2488
- permission: command.permission ?? null,
2489
- audit: command.audit ?? null,
2490
- transaction: command.transaction,
2491
- idempotency: command.idempotency,
2492
- branches: command.branches ?? [],
2493
- branchesFallback: command.branchesFallback ?? "reject",
2494
- ...command.tenantScope === undefined ? {} : { tenantScope: command.tenantScope },
2495
- requiredCapabilities: {
2496
- permission: true,
2497
- audit: command.branches?.length ? command.branches.some((branch) => Boolean(branch.audit)) : Boolean(command.audit),
2498
- idempotency: command.branches?.length ? command.branches.some((branch) => branch.idempotency === "required") : command.idempotency === "required",
2499
- transaction: command.branches?.length ? command.branches.some((branch) => branch.transaction === "required") : command.transaction === "required"
2500
- },
2501
- aspects: (command.aspects ?? []).map((aspect) => aspect.name)
2502
- })).sort((left, right) => left.name.localeCompare(right.name));
2512
+ const commands = module.commands.map((command) => {
2513
+ const fallbackCommand = command.branchesFallback === "command";
2514
+ return {
2515
+ name: command.name,
2516
+ className: command.className,
2517
+ executor: "commandExecutor",
2518
+ adapter: command.rpc ?? null,
2519
+ permission: command.permission ?? null,
2520
+ audit: command.audit ?? null,
2521
+ transaction: command.transaction,
2522
+ idempotency: command.idempotency,
2523
+ branches: command.branches ?? [],
2524
+ branchesFallback: command.branchesFallback ?? "reject",
2525
+ ...command.tenantScope === undefined ? {} : { tenantScope: command.tenantScope },
2526
+ requiredCapabilities: {
2527
+ permission: true,
2528
+ audit: Boolean(command.audit) && fallbackCommand || (command.branches?.length ? command.branches.some((branch) => Boolean(branch.audit)) : Boolean(command.audit)),
2529
+ idempotency: command.idempotency === "required" && fallbackCommand || (command.branches?.length ? command.branches.some((branch) => branch.idempotency === "required") : command.idempotency === "required"),
2530
+ transaction: command.transaction === "required" && fallbackCommand || (command.branches?.length ? command.branches.some((branch) => branch.transaction === "required") : command.transaction === "required")
2531
+ },
2532
+ aspects: (command.aspects ?? []).map((aspect) => aspect.name)
2533
+ };
2534
+ }).sort((left, right) => left.name.localeCompare(right.name));
2503
2535
  const jobs = (module.jobs ?? []).map((job) => ({
2504
2536
  name: job.name,
2505
2537
  className: job.className,
@@ -15823,7 +15855,8 @@ var init_execution_snapshot = __esm(() => {
15823
15855
  kind,
15824
15856
  name: text,
15825
15857
  command: Optional(text),
15826
- stages: _Array_(text)
15858
+ stages: _Array_(text),
15859
+ stagePaths: Optional(_Array_(_Array_(text)))
15827
15860
  }, closed)),
15828
15861
  commandAliases: _Array_(_Object_({ module: text, name: text, alias: text }, closed)),
15829
15862
  contexts: _Array_(_Object_({
@@ -15992,7 +16025,8 @@ function buildTimeline(events, matchedPlans) {
15992
16025
  attempts: [],
15993
16026
  omittedAttempts: 0
15994
16027
  },
15995
- attempts: new Map
16028
+ attempts: new Map,
16029
+ ...plan === undefined ? {} : { plan }
15996
16030
  };
15997
16031
  groups.set(key, group);
15998
16032
  }
@@ -16020,13 +16054,16 @@ function buildTimeline(events, matchedPlans) {
16020
16054
  const declared = group.entry.declaredStages;
16021
16055
  const attempts = [...group.attempts.values()].sort((left, right) => Number(hasFailure(right)) - Number(hasFailure(left)) || left.attempt - right.attempt).slice(0, EXECUTION_CONTEXT_LIMITS.timelineAttempts).sort((left, right) => left.attempt - right.attempt).map((attempt) => {
16022
16056
  const stages = [...attempt.stages].sort((left, right) => Number(right.phase === "failed") - Number(left.phase === "failed") || left.index - right.index).slice(0, EXECUTION_CONTEXT_LIMITS.timelineStages).sort((left, right) => left.index - right.index);
16023
- const terminal = declared[declared.length - 1];
16057
+ const paths = group.plan?.stagePaths && group.plan.stagePaths.length > 0 ? group.plan.stagePaths : [declared];
16058
+ const bestPath = [...paths].sort((left, right) => right.filter((stage) => attempt.observed.has(stage)).length - left.filter((stage) => attempt.observed.has(stage)).length)[0] ?? declared;
16059
+ const terminal = bestPath[bestPath.length - 1];
16060
+ const complete = terminal !== undefined && attempt.stages.some((stage) => stage.stage === terminal && stage.phase === "succeeded");
16024
16061
  return {
16025
16062
  attempt: attempt.attempt,
16026
16063
  stages,
16027
16064
  failed: hasFailure(attempt),
16028
- complete: terminal !== undefined && attempt.stages.some((stage) => stage.stage === terminal && stage.phase === "succeeded"),
16029
- missingStages: declared.filter((stage) => !attempt.observed.has(stage)),
16065
+ complete,
16066
+ missingStages: bestPath.filter((stage) => !attempt.observed.has(stage)),
16030
16067
  unexpectedStages: [],
16031
16068
  traceIds: [...attempt.traceIds].sort(),
16032
16069
  omittedStages: attempt.stages.length - stages.length
@@ -18553,7 +18590,7 @@ function importPath(out, path) {
18553
18590
  function parseDatabaseContractsOptions(value, directory) {
18554
18591
  if (!value || typeof value !== "object" || Array.isArray(value))
18555
18592
  throw new TypeError("Expected database contracts configuration");
18556
- const allowed = ["rootDir", "outDir", "postgrestTypes", "modelSchema", "role", "graphql", "migrations"];
18593
+ const allowed = ["rootDir", "outDir", "postgrestTypes", "modelSchema", "role", "graphql", "rpcRegistry", "rpcCatalog", "migrations"];
18557
18594
  if (Object.keys(value).some((name) => !allowed.includes(name)))
18558
18595
  throw new TypeError("Unknown database contracts option");
18559
18596
  const field = (name) => {
@@ -18569,6 +18606,8 @@ function parseDatabaseContractsOptions(value, directory) {
18569
18606
  };
18570
18607
  const postgrestTypes = optionalPath("postgrestTypes");
18571
18608
  const modelSchema = optionalPath("modelSchema");
18609
+ const rpcRegistry = optionalPath("rpcRegistry");
18610
+ const rpcCatalog = optionalPath("rpcCatalog");
18572
18611
  const graphqlPresent = Object.hasOwn(value, "graphql");
18573
18612
  if (!postgrestTypes && !modelSchema && !graphqlPresent) {
18574
18613
  throw new TypeError("Database contracts require postgrestTypes, modelSchema, or graphql");
@@ -18590,6 +18629,8 @@ function parseDatabaseContractsOptions(value, directory) {
18590
18629
  ...modelSchema ? { modelSchema: resolve25(directory, modelSchema) } : {},
18591
18630
  role: field("role"),
18592
18631
  ...graphql ? { graphql } : {},
18632
+ ...rpcRegistry ? { rpcRegistry: resolve25(directory, rpcRegistry) } : {},
18633
+ ...rpcCatalog ? { rpcCatalog: resolve25(directory, rpcCatalog) } : {},
18593
18634
  migrations: migrations.map((file) => resolve25(directory, file))
18594
18635
  };
18595
18636
  }
@@ -18665,6 +18706,10 @@ async function generateDatabaseContracts(options, check = false) {
18665
18706
  }
18666
18707
  if (options.graphql)
18667
18708
  await addInput(resolve25(rootDir, options.graphql.schema));
18709
+ if (options.rpcRegistry)
18710
+ await addInput(options.rpcRegistry);
18711
+ if (options.rpcCatalog)
18712
+ await addInput(options.rpcCatalog);
18668
18713
  if (new Set(options.migrations.map((path) => resolve25(rootDir, path))).size !== options.migrations.length) {
18669
18714
  throw new Error("Duplicate migration in database contracts configuration");
18670
18715
  }
@@ -23516,11 +23561,13 @@ function assertDatabaseGovernance(value) {
23516
23561
  if (!value || typeof value !== "object" || Array.isArray(value))
23517
23562
  throw new Error("Invalid databaseGovernance configuration.");
23518
23563
  const entry = value;
23519
- if (entry["mode"] !== "off" && entry["mode"] !== "check" && entry["mode"] !== "enforce" || Object.keys(entry).some((key) => !["mode", "projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv", "schemas"].includes(key)) || ["projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv"].some((key) => entry[key] !== undefined && (typeof entry[key] !== "string" || !entry[key].trim())) || entry["databaseUrlEnv"] !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(String(entry["databaseUrlEnv"])) || entry["schemas"] !== undefined && (!Array.isArray(entry["schemas"]) || !entry["schemas"].length || !entry["schemas"].every((schema) => typeof schema === "string" && schema.trim()))) {
23564
+ const rpcBoundary = entry["rpcBoundary"];
23565
+ const validRpcBoundary = rpcBoundary === undefined || rpcBoundary !== null && typeof rpcBoundary === "object" && !Array.isArray(rpcBoundary) && Object.keys(rpcBoundary).every((key) => ["registry", "catalog", "legacy"].includes(key)) && Object.values(rpcBoundary).length > 0 && Object.values(rpcBoundary).every((path) => typeof path === "string" && path.trim() !== "" && !path.includes("\\") && !path.startsWith("/"));
23566
+ if (entry["mode"] !== "off" && entry["mode"] !== "check" && entry["mode"] !== "enforce" || Object.keys(entry).some((key) => !["mode", "projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv", "schemas", "rpcBoundary"].includes(key)) || ["projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv"].some((key) => entry[key] !== undefined && (typeof entry[key] !== "string" || !entry[key].trim())) || entry["databaseUrlEnv"] !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(String(entry["databaseUrlEnv"])) || entry["schemas"] !== undefined && (!Array.isArray(entry["schemas"]) || !entry["schemas"].length || !entry["schemas"].every((schema) => typeof schema === "string" && schema.trim())) || !validRpcBoundary) {
23520
23567
  throw new Error("Invalid databaseGovernance mode, paths, connection or schemas.");
23521
23568
  }
23522
23569
  }
23523
- async function checkDatabaseGovernance(rootDir, value) {
23570
+ async function checkDatabaseGovernance(rootDir, value, rpcCapabilities = []) {
23524
23571
  if (value === undefined)
23525
23572
  return [];
23526
23573
  const failure = () => [{
@@ -23537,7 +23584,7 @@ async function checkDatabaseGovernance(rootDir, value) {
23537
23584
  const output = await new Promise((accept, reject) => {
23538
23585
  const child = execFile(process.versions.bun ? process.execPath : "bun", ["--no-env-file", worker], { timeout: 30000, killSignal: "SIGKILL", maxBuffer: 1024 * 1024 }, (error, stdout) => error ? reject(error) : accept(stdout));
23539
23586
  child.stdin?.on("error", reject);
23540
- child.stdin?.end(JSON.stringify(options));
23587
+ child.stdin?.end(JSON.stringify({ options, rpcCapabilities }));
23541
23588
  });
23542
23589
  const report = JSON.parse(output);
23543
23590
  if (!report || typeof report !== "object" || !("diagnostics" in report) || !Array.isArray(report.diagnostics))
@@ -23583,7 +23630,7 @@ async function compileProject(options) {
23583
23630
  ...validateEffectPolicies(graph, options.effect),
23584
23631
  ...validateJobPolicies(graph)
23585
23632
  ];
23586
- const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance);
23633
+ const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance, Object.keys(options.commandCapabilities?.rpc ?? {}));
23587
23634
  diagnostics.push(...databaseDiagnostics);
23588
23635
  if (databaseDiagnostics.length)
23589
23636
  return { diagnostics, graph, written: [] };
@@ -23653,7 +23700,7 @@ async function checkProject(options) {
23653
23700
  ...validateEffectPolicies(graph, options.effect),
23654
23701
  ...validateJobPolicies(graph)
23655
23702
  ];
23656
- const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance);
23703
+ const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance, Object.keys(options.commandCapabilities?.rpc ?? {}));
23657
23704
  diagnostics.push(...databaseDiagnostics);
23658
23705
  if (databaseDiagnostics.length) {
23659
23706
  return { diagnostics, graph, upToDate: false, mismatches: ["Database governance verification failed."] };
@@ -26513,7 +26560,7 @@ import { resolve as resolve19 } from "node:path";
26513
26560
  var MIGRATION_APP_VERSION = "0.39.1";
26514
26561
 
26515
26562
  // src/migration-elysia-version.ts
26516
- var MIGRATION_ELYSIA_VERSION = "0.42.0";
26563
+ var MIGRATION_ELYSIA_VERSION = "0.42.1";
26517
26564
 
26518
26565
  // src/migration-policy.ts
26519
26566
  var cachedCompilerVersion;
@@ -11,6 +11,10 @@ export interface DatabaseContractsOptions {
11
11
  role: string;
12
12
  /** Role-scoped pg_graphql snapshot exported by graphql-schema. Omit when GraphQL is unused. */
13
13
  graphql?: GraphqlOptions;
14
+ /** Registry emitted by application or database code generation. Tracked for contract freshness. */
15
+ rpcRegistry?: string;
16
+ /** Generated or exported RPC catalog. Tracked for contract freshness. */
17
+ rpcCatalog?: string;
14
18
  /** Ordered complete migration history. */
15
19
  migrations: readonly string[];
16
20
  }
@@ -1,4 +1,4 @@
1
1
  import type { DatabaseGovernanceOptions, Diagnostic } from "./types";
2
- export declare function enforceDatabaseGovernance(options: DatabaseGovernanceOptions): Promise<{
2
+ export declare function enforceDatabaseGovernance(options: DatabaseGovernanceOptions, rpcCapabilities?: readonly string[]): Promise<{
3
3
  diagnostics: Diagnostic[];
4
4
  }>;
@@ -3,6 +3,7 @@ import { readFile, realpath } from "node:fs/promises";
3
3
  import { isAbsolute, relative, resolve, sep } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import {
6
+ checkRpcBoundary,
6
7
  lintModule,
7
8
  readCatalog,
8
9
  reconcileModule
@@ -34,13 +35,31 @@ function governanceDiagnostic(message, code = "database-governance") {
34
35
  suggestion: "Fix the database source contract, module manifest or live catalog before compiling."
35
36
  };
36
37
  }
37
- async function enforceDatabaseGovernance(options) {
38
+ async function enforceDatabaseGovernance(options, rpcCapabilities = []) {
38
39
  const projectRoot = await realpath(options.projectRoot ?? ".");
39
40
  const sourceReport = await databaseSources(projectRoot, "check");
40
41
  const diagnostics = sourceReport.findings.map((finding) => governanceDiagnostic(`${finding.file}: ${finding.message}`, "database-source-contract"));
41
42
  if (!sourceReport.ok && diagnostics.length === 0) {
42
43
  diagnostics.push(governanceDiagnostic("Database source contract check failed without findings."));
43
44
  }
45
+ if (diagnostics.length > 0)
46
+ return { diagnostics };
47
+ if (options.rpcBoundary) {
48
+ try {
49
+ const readJson = async (path) => path === undefined ? undefined : JSON.parse(await readFile(await withinRoot(projectRoot, path), "utf8"));
50
+ const report = checkRpcBoundary({
51
+ capabilities: rpcCapabilities,
52
+ registry: await readJson(options.rpcBoundary.registry),
53
+ catalog: await readJson(options.rpcBoundary.catalog),
54
+ legacy: await readJson(options.rpcBoundary.legacy)
55
+ });
56
+ if (!report.ok) {
57
+ diagnostics.push(governanceDiagnostic(`RPC capabilities have no registry/catalog source or historical entry: ${report.missing.join(", ")}`, "database-rpc-boundary"));
58
+ }
59
+ } catch {
60
+ diagnostics.push(governanceDiagnostic("RPC boundary artifacts could not be read or parsed.", "database-rpc-boundary"));
61
+ }
62
+ }
44
63
  if (diagnostics.length > 0)
45
64
  return { diagnostics };
46
65
  if (options.mode === "check")
@@ -107,11 +126,13 @@ function assertDatabaseGovernance(value) {
107
126
  if (!value || typeof value !== "object" || Array.isArray(value))
108
127
  throw new Error("Invalid databaseGovernance configuration.");
109
128
  const entry = value;
110
- if (entry["mode"] !== "off" && entry["mode"] !== "check" && entry["mode"] !== "enforce" || Object.keys(entry).some((key) => !["mode", "projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv", "schemas"].includes(key)) || ["projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv"].some((key) => entry[key] !== undefined && (typeof entry[key] !== "string" || !entry[key].trim())) || entry["databaseUrlEnv"] !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(String(entry["databaseUrlEnv"])) || entry["schemas"] !== undefined && (!Array.isArray(entry["schemas"]) || !entry["schemas"].length || !entry["schemas"].every((schema) => typeof schema === "string" && schema.trim()))) {
129
+ const rpcBoundary = entry["rpcBoundary"];
130
+ const validRpcBoundary = rpcBoundary === undefined || rpcBoundary !== null && typeof rpcBoundary === "object" && !Array.isArray(rpcBoundary) && Object.keys(rpcBoundary).every((key) => ["registry", "catalog", "legacy"].includes(key)) && Object.values(rpcBoundary).length > 0 && Object.values(rpcBoundary).every((path) => typeof path === "string" && path.trim() !== "" && !path.includes("\\") && !path.startsWith("/"));
131
+ if (entry["mode"] !== "off" && entry["mode"] !== "check" && entry["mode"] !== "enforce" || Object.keys(entry).some((key) => !["mode", "projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv", "schemas", "rpcBoundary"].includes(key)) || ["projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv"].some((key) => entry[key] !== undefined && (typeof entry[key] !== "string" || !entry[key].trim())) || entry["databaseUrlEnv"] !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(String(entry["databaseUrlEnv"])) || entry["schemas"] !== undefined && (!Array.isArray(entry["schemas"]) || !entry["schemas"].length || !entry["schemas"].every((schema) => typeof schema === "string" && schema.trim())) || !validRpcBoundary) {
111
132
  throw new Error("Invalid databaseGovernance mode, paths, connection or schemas.");
112
133
  }
113
134
  }
114
- async function checkDatabaseGovernance(rootDir, value) {
135
+ async function checkDatabaseGovernance(rootDir, value, rpcCapabilities = []) {
115
136
  if (value === undefined)
116
137
  return [];
117
138
  const failure = () => [{
@@ -128,7 +149,7 @@ async function checkDatabaseGovernance(rootDir, value) {
128
149
  const output = await new Promise((accept, reject) => {
129
150
  const child = execFile(process.versions.bun ? process.execPath : "bun", ["--no-env-file", worker], { timeout: 30000, killSignal: "SIGKILL", maxBuffer: 1024 * 1024 }, (error, stdout) => error ? reject(error) : accept(stdout));
130
151
  child.stdin?.on("error", reject);
131
- child.stdin?.end(JSON.stringify(options));
152
+ child.stdin?.end(JSON.stringify({ options, rpcCapabilities }));
132
153
  });
133
154
  const report = JSON.parse(output);
134
155
  if (!report || typeof report !== "object" || !("diagnostics" in report) || !Array.isArray(report.diagnostics))
@@ -147,9 +168,11 @@ async function checkDatabaseGovernance(rootDir, value) {
147
168
 
148
169
  // src/database-governance-worker.ts
149
170
  try {
150
- const options = JSON.parse(await Bun.stdin.text());
171
+ const payload = JSON.parse(await Bun.stdin.text());
172
+ const options = payload && typeof payload === "object" && !Array.isArray(payload) && "options" in payload ? payload.options : payload;
173
+ const rpcCapabilities = payload && typeof payload === "object" && !Array.isArray(payload) && "rpcCapabilities" in payload && Array.isArray(payload.rpcCapabilities) ? payload.rpcCapabilities : [];
151
174
  assertDatabaseGovernance(options);
152
- process.stdout.write(JSON.stringify(await enforceDatabaseGovernance(options)));
175
+ process.stdout.write(JSON.stringify(await enforceDatabaseGovernance(options, rpcCapabilities)));
153
176
  } catch {
154
177
  process.stdout.write(JSON.stringify({ diagnostics: [{
155
178
  severity: "error",
@@ -1,4 +1,4 @@
1
1
  import type { DatabaseGovernanceOptions, Diagnostic } from "./types";
2
2
  export declare function assertDatabaseGovernance(value: unknown): asserts value is DatabaseGovernanceOptions;
3
3
  /** A fresh, bounded process avoids stale module imports during incremental compilation. */
4
- export declare function checkDatabaseGovernance(rootDir: string, value: DatabaseGovernanceOptions | undefined): Promise<Diagnostic[]>;
4
+ export declare function checkDatabaseGovernance(rootDir: string, value: DatabaseGovernanceOptions | undefined, rpcCapabilities?: readonly string[]): Promise<Diagnostic[]>;
@@ -8,6 +8,7 @@ declare const SnapshotSchema: Type.TObject<{
8
8
  name: Type.TString;
9
9
  command: Type.TOptional<Type.TString>;
10
10
  stages: Type.TArray<Type.TString>;
11
+ stagePaths: Type.TOptional<Type.TArray<Type.TArray<Type.TString>>>;
11
12
  }>>;
12
13
  commandAliases: Type.TArray<Type.TObject<{
13
14
  module: Type.TString;
package/dist/index.js CHANGED
@@ -2096,6 +2096,47 @@ function allAspects(module) {
2096
2096
  ...(module.jobs ?? []).flatMap((job) => job.aspects ?? [])
2097
2097
  ];
2098
2098
  }
2099
+ function uniqueStages(paths) {
2100
+ return [...new Set(paths.flat())];
2101
+ }
2102
+ function commandStagePaths(command, aspects) {
2103
+ const prefix = ["commandExecutor", "authorize"];
2104
+ if (command.branches?.length) {
2105
+ const branchPaths = command.branches.map((branch) => [
2106
+ ...prefix,
2107
+ `rpc:${branch.rpc}`,
2108
+ ...aspects,
2109
+ "handler"
2110
+ ]);
2111
+ if (command.branchesFallback === "command") {
2112
+ branchPaths.push([
2113
+ ...prefix,
2114
+ ...command.idempotency === "required" ? ["idempotency"] : [],
2115
+ ...command.transaction === "required" ? ["transaction"] : [],
2116
+ ...aspects,
2117
+ "handler",
2118
+ ...command.audit ? ["audit"] : []
2119
+ ]);
2120
+ }
2121
+ return branchPaths;
2122
+ }
2123
+ return [[
2124
+ ...prefix,
2125
+ ...command.rpc ? [`rpc:${command.rpc}`] : [
2126
+ ...command.idempotency === "required" ? ["idempotency"] : [],
2127
+ ...command.transaction === "required" ? ["transaction"] : []
2128
+ ],
2129
+ ...aspects,
2130
+ "handler",
2131
+ ...command.audit && !command.rpc ? ["audit"] : []
2132
+ ]];
2133
+ }
2134
+ function planStages(paths, branches) {
2135
+ const stages = uniqueStages(paths);
2136
+ if (branches && !stages.includes("rpc:selected-branch"))
2137
+ stages.splice(2, 0, "rpc:selected-branch");
2138
+ return paths.length > 1 ? { stages, stagePaths: paths } : { stages };
2139
+ }
2099
2140
  function createExecutionPlans(graph) {
2100
2141
  const aspects = (boundary, refs = []) => refs.map((ref, index) => `${boundary}.aspect[${index}]:${ref.name}`);
2101
2142
  return graph.modules.flatMap((module) => [
@@ -2105,46 +2146,34 @@ function createExecutionPlans(graph) {
2105
2146
  const command = module.commands.find((item) => item.className === commandRef || item.name === commandRef);
2106
2147
  const boundCommand = route.command === undefined ? undefined : command;
2107
2148
  const path = `${controller.path}/${route.path}`.replace(/\/+/g, "/");
2149
+ const routeAspects = [
2150
+ ...aspects(`module:${module.name}`, module.aspects),
2151
+ ...aspects("route", route.aspects),
2152
+ ...aspects("command", boundCommand?.aspects)
2153
+ ];
2154
+ const stagePaths = boundCommand === undefined ? undefined : commandStagePaths(boundCommand, routeAspects);
2155
+ const plannedStages = stagePaths === undefined ? { stages: [...routeAspects, "handler"] } : planStages(stagePaths, Boolean(boundCommand?.branches?.length));
2108
2156
  return {
2109
2157
  module: module.name,
2110
2158
  kind: "route",
2111
2159
  name: `${route.method} ${path.length > 1 ? path.replace(/\/+$/, "") : path}`,
2112
2160
  ...command ? { command: command.name } : {},
2113
- stages: [
2114
- ...boundCommand ? [
2115
- "commandExecutor",
2116
- "authorize",
2117
- ...boundCommand.branches?.length ? ["rpc:selected-branch"] : boundCommand.rpc ? [`rpc:${boundCommand.rpc}`] : [
2118
- ...boundCommand.idempotency === "required" ? ["idempotency"] : [],
2119
- ...boundCommand.transaction === "required" ? ["transaction"] : []
2120
- ]
2121
- ] : [],
2122
- ...aspects(`module:${module.name}`, module.aspects),
2123
- ...aspects("route", route.aspects),
2124
- ...aspects("command", boundCommand?.aspects),
2125
- "handler",
2126
- ...boundCommand?.audit && !boundCommand.rpc && !boundCommand.branches?.length ? ["audit"] : []
2127
- ]
2161
+ ...plannedStages
2128
2162
  };
2129
2163
  })),
2130
- ...module.commands.map((command) => ({
2131
- module: module.name,
2132
- kind: "command",
2133
- name: command.name,
2134
- command: command.name,
2135
- stages: [
2136
- "commandExecutor",
2137
- "authorize",
2138
- ...command.branches?.length ? ["rpc:selected-branch"] : command.rpc ? [`rpc:${command.rpc}`] : [
2139
- ...command.idempotency === "required" ? ["idempotency"] : [],
2140
- ...command.transaction === "required" ? ["transaction"] : []
2141
- ],
2164
+ ...module.commands.map((command) => {
2165
+ const stagePaths = commandStagePaths(command, [
2142
2166
  ...aspects(`module:${module.name}`, module.aspects),
2143
- ...aspects("command", command.aspects),
2144
- "handler",
2145
- ...command.audit && !command.rpc && !command.branches?.length ? ["audit"] : []
2146
- ]
2147
- })),
2167
+ ...aspects("command", command.aspects)
2168
+ ]);
2169
+ return {
2170
+ module: module.name,
2171
+ kind: "command",
2172
+ name: command.name,
2173
+ command: command.name,
2174
+ ...planStages(stagePaths, Boolean(command.branches?.length))
2175
+ };
2176
+ }),
2148
2177
  ...(module.jobs ?? []).map((job) => ({
2149
2178
  module: module.name,
2150
2179
  kind: "job",
@@ -2479,26 +2508,29 @@ function renderApplication(graph, options) {
2479
2508
  }
2480
2509
  };
2481
2510
  }));
2482
- const commands = module.commands.map((command) => ({
2483
- name: command.name,
2484
- className: command.className,
2485
- executor: "commandExecutor",
2486
- adapter: command.rpc ?? null,
2487
- permission: command.permission ?? null,
2488
- audit: command.audit ?? null,
2489
- transaction: command.transaction,
2490
- idempotency: command.idempotency,
2491
- branches: command.branches ?? [],
2492
- branchesFallback: command.branchesFallback ?? "reject",
2493
- ...command.tenantScope === undefined ? {} : { tenantScope: command.tenantScope },
2494
- requiredCapabilities: {
2495
- permission: true,
2496
- audit: command.branches?.length ? command.branches.some((branch) => Boolean(branch.audit)) : Boolean(command.audit),
2497
- idempotency: command.branches?.length ? command.branches.some((branch) => branch.idempotency === "required") : command.idempotency === "required",
2498
- transaction: command.branches?.length ? command.branches.some((branch) => branch.transaction === "required") : command.transaction === "required"
2499
- },
2500
- aspects: (command.aspects ?? []).map((aspect) => aspect.name)
2501
- })).sort((left, right) => left.name.localeCompare(right.name));
2511
+ const commands = module.commands.map((command) => {
2512
+ const fallbackCommand = command.branchesFallback === "command";
2513
+ return {
2514
+ name: command.name,
2515
+ className: command.className,
2516
+ executor: "commandExecutor",
2517
+ adapter: command.rpc ?? null,
2518
+ permission: command.permission ?? null,
2519
+ audit: command.audit ?? null,
2520
+ transaction: command.transaction,
2521
+ idempotency: command.idempotency,
2522
+ branches: command.branches ?? [],
2523
+ branchesFallback: command.branchesFallback ?? "reject",
2524
+ ...command.tenantScope === undefined ? {} : { tenantScope: command.tenantScope },
2525
+ requiredCapabilities: {
2526
+ permission: true,
2527
+ audit: Boolean(command.audit) && fallbackCommand || (command.branches?.length ? command.branches.some((branch) => Boolean(branch.audit)) : Boolean(command.audit)),
2528
+ idempotency: command.idempotency === "required" && fallbackCommand || (command.branches?.length ? command.branches.some((branch) => branch.idempotency === "required") : command.idempotency === "required"),
2529
+ transaction: command.transaction === "required" && fallbackCommand || (command.branches?.length ? command.branches.some((branch) => branch.transaction === "required") : command.transaction === "required")
2530
+ },
2531
+ aspects: (command.aspects ?? []).map((aspect) => aspect.name)
2532
+ };
2533
+ }).sort((left, right) => left.name.localeCompare(right.name));
2502
2534
  const jobs = (module.jobs ?? []).map((job) => ({
2503
2535
  name: job.name,
2504
2536
  className: job.className,
@@ -16410,7 +16442,8 @@ var init_execution_snapshot = __esm(() => {
16410
16442
  kind: kind2,
16411
16443
  name: text,
16412
16444
  command: Optional(text),
16413
- stages: _Array_(text)
16445
+ stages: _Array_(text),
16446
+ stagePaths: Optional(_Array_(_Array_(text)))
16414
16447
  }, closed3)),
16415
16448
  commandAliases: _Array_(_Object_({ module: text, name: text, alias: text }, closed3)),
16416
16449
  contexts: _Array_(_Object_({
@@ -18129,7 +18162,8 @@ function buildTimeline(events, matchedPlans) {
18129
18162
  attempts: [],
18130
18163
  omittedAttempts: 0
18131
18164
  },
18132
- attempts: new Map
18165
+ attempts: new Map,
18166
+ ...plan === undefined ? {} : { plan }
18133
18167
  };
18134
18168
  groups.set(key, group);
18135
18169
  }
@@ -18157,13 +18191,16 @@ function buildTimeline(events, matchedPlans) {
18157
18191
  const declared = group.entry.declaredStages;
18158
18192
  const attempts = [...group.attempts.values()].sort((left, right) => Number(hasFailure(right)) - Number(hasFailure(left)) || left.attempt - right.attempt).slice(0, EXECUTION_CONTEXT_LIMITS.timelineAttempts).sort((left, right) => left.attempt - right.attempt).map((attempt) => {
18159
18193
  const stages = [...attempt.stages].sort((left, right) => Number(right.phase === "failed") - Number(left.phase === "failed") || left.index - right.index).slice(0, EXECUTION_CONTEXT_LIMITS.timelineStages).sort((left, right) => left.index - right.index);
18160
- const terminal = declared[declared.length - 1];
18194
+ const paths = group.plan?.stagePaths && group.plan.stagePaths.length > 0 ? group.plan.stagePaths : [declared];
18195
+ const bestPath = [...paths].sort((left, right) => right.filter((stage) => attempt.observed.has(stage)).length - left.filter((stage) => attempt.observed.has(stage)).length)[0] ?? declared;
18196
+ const terminal = bestPath[bestPath.length - 1];
18197
+ const complete = terminal !== undefined && attempt.stages.some((stage) => stage.stage === terminal && stage.phase === "succeeded");
18161
18198
  return {
18162
18199
  attempt: attempt.attempt,
18163
18200
  stages,
18164
18201
  failed: hasFailure(attempt),
18165
- complete: terminal !== undefined && attempt.stages.some((stage) => stage.stage === terminal && stage.phase === "succeeded"),
18166
- missingStages: declared.filter((stage) => !attempt.observed.has(stage)),
18202
+ complete,
18203
+ missingStages: bestPath.filter((stage) => !attempt.observed.has(stage)),
18167
18204
  unexpectedStages: [],
18168
18205
  traceIds: [...attempt.traceIds].sort(),
18169
18206
  omittedStages: attempt.stages.length - stages.length
@@ -18585,7 +18622,7 @@ function importPath(out, path) {
18585
18622
  function parseDatabaseContractsOptions(value, directory) {
18586
18623
  if (!value || typeof value !== "object" || Array.isArray(value))
18587
18624
  throw new TypeError("Expected database contracts configuration");
18588
- const allowed = ["rootDir", "outDir", "postgrestTypes", "modelSchema", "role", "graphql", "migrations"];
18625
+ const allowed = ["rootDir", "outDir", "postgrestTypes", "modelSchema", "role", "graphql", "rpcRegistry", "rpcCatalog", "migrations"];
18589
18626
  if (Object.keys(value).some((name) => !allowed.includes(name)))
18590
18627
  throw new TypeError("Unknown database contracts option");
18591
18628
  const field = (name) => {
@@ -18601,6 +18638,8 @@ function parseDatabaseContractsOptions(value, directory) {
18601
18638
  };
18602
18639
  const postgrestTypes = optionalPath("postgrestTypes");
18603
18640
  const modelSchema = optionalPath("modelSchema");
18641
+ const rpcRegistry = optionalPath("rpcRegistry");
18642
+ const rpcCatalog = optionalPath("rpcCatalog");
18604
18643
  const graphqlPresent = Object.hasOwn(value, "graphql");
18605
18644
  if (!postgrestTypes && !modelSchema && !graphqlPresent) {
18606
18645
  throw new TypeError("Database contracts require postgrestTypes, modelSchema, or graphql");
@@ -18622,6 +18661,8 @@ function parseDatabaseContractsOptions(value, directory) {
18622
18661
  ...modelSchema ? { modelSchema: resolve24(directory, modelSchema) } : {},
18623
18662
  role: field("role"),
18624
18663
  ...graphql ? { graphql } : {},
18664
+ ...rpcRegistry ? { rpcRegistry: resolve24(directory, rpcRegistry) } : {},
18665
+ ...rpcCatalog ? { rpcCatalog: resolve24(directory, rpcCatalog) } : {},
18625
18666
  migrations: migrations.map((file) => resolve24(directory, file))
18626
18667
  };
18627
18668
  }
@@ -18697,6 +18738,10 @@ async function generateDatabaseContracts(options, check = false) {
18697
18738
  }
18698
18739
  if (options.graphql)
18699
18740
  await addInput(resolve24(rootDir, options.graphql.schema));
18741
+ if (options.rpcRegistry)
18742
+ await addInput(options.rpcRegistry);
18743
+ if (options.rpcCatalog)
18744
+ await addInput(options.rpcCatalog);
18700
18745
  if (new Set(options.migrations.map((path) => resolve24(rootDir, path))).size !== options.migrations.length) {
18701
18746
  throw new Error("Duplicate migration in database contracts configuration");
18702
18747
  }
@@ -23477,11 +23522,13 @@ function assertDatabaseGovernance(value) {
23477
23522
  if (!value || typeof value !== "object" || Array.isArray(value))
23478
23523
  throw new Error("Invalid databaseGovernance configuration.");
23479
23524
  const entry = value;
23480
- if (entry["mode"] !== "off" && entry["mode"] !== "check" && entry["mode"] !== "enforce" || Object.keys(entry).some((key) => !["mode", "projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv", "schemas"].includes(key)) || ["projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv"].some((key) => entry[key] !== undefined && (typeof entry[key] !== "string" || !entry[key].trim())) || entry["databaseUrlEnv"] !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(String(entry["databaseUrlEnv"])) || entry["schemas"] !== undefined && (!Array.isArray(entry["schemas"]) || !entry["schemas"].length || !entry["schemas"].every((schema) => typeof schema === "string" && schema.trim()))) {
23525
+ const rpcBoundary = entry["rpcBoundary"];
23526
+ const validRpcBoundary = rpcBoundary === undefined || rpcBoundary !== null && typeof rpcBoundary === "object" && !Array.isArray(rpcBoundary) && Object.keys(rpcBoundary).every((key) => ["registry", "catalog", "legacy"].includes(key)) && Object.values(rpcBoundary).length > 0 && Object.values(rpcBoundary).every((path) => typeof path === "string" && path.trim() !== "" && !path.includes("\\") && !path.startsWith("/"));
23527
+ if (entry["mode"] !== "off" && entry["mode"] !== "check" && entry["mode"] !== "enforce" || Object.keys(entry).some((key) => !["mode", "projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv", "schemas", "rpcBoundary"].includes(key)) || ["projectRoot", "moduleFile", "databaseUrl", "databaseUrlEnv"].some((key) => entry[key] !== undefined && (typeof entry[key] !== "string" || !entry[key].trim())) || entry["databaseUrlEnv"] !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(String(entry["databaseUrlEnv"])) || entry["schemas"] !== undefined && (!Array.isArray(entry["schemas"]) || !entry["schemas"].length || !entry["schemas"].every((schema) => typeof schema === "string" && schema.trim())) || !validRpcBoundary) {
23481
23528
  throw new Error("Invalid databaseGovernance mode, paths, connection or schemas.");
23482
23529
  }
23483
23530
  }
23484
- async function checkDatabaseGovernance(rootDir, value) {
23531
+ async function checkDatabaseGovernance(rootDir, value, rpcCapabilities = []) {
23485
23532
  if (value === undefined)
23486
23533
  return [];
23487
23534
  const failure = () => [{
@@ -23498,7 +23545,7 @@ async function checkDatabaseGovernance(rootDir, value) {
23498
23545
  const output = await new Promise((accept, reject) => {
23499
23546
  const child = execFile(process.versions.bun ? process.execPath : "bun", ["--no-env-file", worker], { timeout: 30000, killSignal: "SIGKILL", maxBuffer: 1024 * 1024 }, (error, stdout) => error ? reject(error) : accept(stdout));
23500
23547
  child.stdin?.on("error", reject);
23501
- child.stdin?.end(JSON.stringify(options));
23548
+ child.stdin?.end(JSON.stringify({ options, rpcCapabilities }));
23502
23549
  });
23503
23550
  const report = JSON.parse(output);
23504
23551
  if (!report || typeof report !== "object" || !("diagnostics" in report) || !Array.isArray(report.diagnostics))
@@ -23544,7 +23591,7 @@ async function compileProject(options) {
23544
23591
  ...validateEffectPolicies(graph, options.effect),
23545
23592
  ...validateJobPolicies(graph)
23546
23593
  ];
23547
- const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance);
23594
+ const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance, Object.keys(options.commandCapabilities?.rpc ?? {}));
23548
23595
  diagnostics.push(...databaseDiagnostics);
23549
23596
  if (databaseDiagnostics.length)
23550
23597
  return { diagnostics, graph, written: [] };
@@ -23614,7 +23661,7 @@ async function checkProject(options) {
23614
23661
  ...validateEffectPolicies(graph, options.effect),
23615
23662
  ...validateJobPolicies(graph)
23616
23663
  ];
23617
- const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance);
23664
+ const databaseDiagnostics = await checkDatabaseGovernance(options.rootDir, options.databaseGovernance, Object.keys(options.commandCapabilities?.rpc ?? {}));
23618
23665
  diagnostics.push(...databaseDiagnostics);
23619
23666
  if (databaseDiagnostics.length) {
23620
23667
  return { diagnostics, graph, upToDate: false, mismatches: ["Database governance verification failed."] };
@@ -25765,7 +25812,7 @@ import { resolve as resolve17 } from "node:path";
25765
25812
  var MIGRATION_APP_VERSION = "0.39.1";
25766
25813
 
25767
25814
  // src/migration-elysia-version.ts
25768
- var MIGRATION_ELYSIA_VERSION = "0.42.0";
25815
+ var MIGRATION_ELYSIA_VERSION = "0.42.1";
25769
25816
 
25770
25817
  // src/migration-policy.ts
25771
25818
  var cachedCompilerVersion;
package/dist/inspect.d.ts CHANGED
@@ -7,6 +7,8 @@ export interface ExecutionPlan {
7
7
  command?: string;
8
8
  /** Standard governance contract. Audit success follows the handler; custom executors may short-circuit. */
9
9
  stages: string[];
10
+ /** Alternative stage sequences for commands with conditional execution paths. */
11
+ stagePaths?: string[][];
10
12
  }
11
13
  export interface ContextPack {
12
14
  version: 1;
@@ -1 +1 @@
1
- export declare const MIGRATION_ELYSIA_VERSION = "0.42.0";
1
+ export declare const MIGRATION_ELYSIA_VERSION = "0.42.1";
package/dist/types.d.ts CHANGED
@@ -775,6 +775,15 @@ export interface DatabaseGovernanceOptions {
775
775
  databaseUrlEnv?: string;
776
776
  /** Schemas included in the live Catalog query. */
777
777
  schemas?: string[];
778
+ /** Optional offline gate connecting named RPC capabilities to maintained artifacts. */
779
+ rpcBoundary?: {
780
+ /** JSON registry produced by application or database code generation. */
781
+ registry?: string;
782
+ /** Generated or exported RPC catalog JSON. */
783
+ catalog?: string;
784
+ /** JSON list/object containing explicitly historical RPC capability names. */
785
+ legacy?: string;
786
+ };
778
787
  }
779
788
  export interface ModuleBoundaryRule {
780
789
  /** Source module tag pattern or tag (for example, 'type:ui', 'scope:case', or '*'). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/compiler",
3
- "version": "0.59.0",
3
+ "version": "0.60.0",
4
4
  "description": "Static compiler for @supacloud/app metadata: builds the application graph from AST, validates it, and generates reflection-free factory code",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -57,7 +57,7 @@
57
57
  "directory": "packages/compiler"
58
58
  },
59
59
  "dependencies": {
60
- "@supacloud/db": "0.23.1",
60
+ "@supacloud/db": "0.24.0",
61
61
  "@supacloud/delivery": "0.26.0",
62
62
  "typebox": "1.4.3",
63
63
  "@graphql-codegen/core": "^6.2.2",
@@ -68,7 +68,7 @@
68
68
  "typescript": "7.1.0-beta"
69
69
  },
70
70
  "overrides": {
71
- "@supacloud/contracts": "0.16.0",
71
+ "@supacloud/contracts": "0.16.1",
72
72
  "@graphql-tools/utils": "^12.0.4",
73
73
  "@whatwg-node/promise-helpers": "2.0.0",
74
74
  "deepmerge-ts": "8.0.2",