@runa-ai/runa-cli 0.7.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/{build-HQMSVN6N.js → build-P2A6345N.js} +2 -2
  2. package/dist/{check-PCSQPYDM.js → check-4TZHNOZU.js} +4 -4
  3. package/dist/{chunk-DRSUEMAK.js → chunk-B7C7CLW2.js} +2 -5
  4. package/dist/{chunk-B3POLMII.js → chunk-BQ336L5T.js} +1 -1
  5. package/dist/{chunk-6FAU4IGR.js → chunk-ELXXQIGW.js} +4 -1
  6. package/dist/{chunk-FWMGC5FP.js → chunk-EXR4J2JT.js} +289 -16
  7. package/dist/{chunk-AO554K3G.js → chunk-GKBE7EIE.js} +1 -1
  8. package/dist/{chunk-JMJP4A47.js → chunk-GT5DMS5R.js} +20 -2
  9. package/dist/{chunk-3JO6YP3T.js → chunk-IEKYTCYA.js} +1 -1
  10. package/dist/{chunk-WPMR7RQ4.js → chunk-IWVXI5O4.js} +2 -2
  11. package/dist/chunk-KUH3G522.js +72 -0
  12. package/dist/{chunk-VSH3IXDQ.js → chunk-MAFJAA2P.js} +1 -1
  13. package/dist/{chunk-CCKG5R4Y.js → chunk-MILCC3B6.js} +1 -1
  14. package/dist/{chunk-5NKWR4FF.js → chunk-OERS32LW.js} +1 -1
  15. package/dist/{chunk-2QX7T24B.js → chunk-QKGL6Q2S.js} +1 -1
  16. package/dist/{chunk-OBYZDT2E.js → chunk-URWDB7YL.js} +15 -78
  17. package/dist/{chunk-CKRLVEIO.js → chunk-WGRVAGSR.js} +16 -11
  18. package/dist/chunk-YRNQEJQW.js +9043 -0
  19. package/dist/chunk-ZWDWFMOX.js +1514 -0
  20. package/dist/{ci-Z4525QW6.js → ci-S5KSBECX.js} +1226 -1207
  21. package/dist/{cli-SVXOSMW6.js → cli-TJZCAMB2.js} +30 -30
  22. package/dist/commands/ci/commands/ci-prod-db-operations.d.ts +12 -17
  23. package/dist/commands/ci/commands/ci-prod-utils.d.ts +7 -0
  24. package/dist/commands/ci/commands/ci-resolvers.d.ts +1 -2
  25. package/dist/commands/ci/commands/layer4-discovery.d.ts +2 -0
  26. package/dist/commands/ci/machine/actors/db/production-preview.d.ts +4 -3
  27. package/dist/commands/ci/machine/actors/db/sync-schema.d.ts +5 -1
  28. package/dist/commands/ci/machine/actors/setup/pr-common.d.ts +1 -1
  29. package/dist/commands/ci/machine/actors/test/capabilities.d.ts +2 -13
  30. package/dist/commands/ci/machine/actors/test/index.d.ts +1 -0
  31. package/dist/commands/ci/machine/actors/test/layer-content.d.ts +11 -0
  32. package/dist/commands/ci/machine/commands/ci-pr-internal-profile.d.ts +7 -0
  33. package/dist/commands/ci/machine/commands/ci-step-registry.d.ts +25 -0
  34. package/dist/commands/ci/machine/commands/step-telemetry.d.ts +1 -2
  35. package/dist/commands/ci/machine/contract.d.ts +9 -1
  36. package/dist/commands/ci/machine/guards.d.ts +19 -10
  37. package/dist/commands/ci/machine/helpers.d.ts +1 -1
  38. package/dist/commands/ci/machine/machine-execution-helpers.d.ts +5 -2
  39. package/dist/commands/ci/machine/machine.d.ts +34 -32
  40. package/dist/commands/ci/machine/selectors.d.ts +6 -0
  41. package/dist/commands/ci/machine/types.d.ts +3 -1
  42. package/dist/commands/ci/utils/ci-logging.d.ts +16 -0
  43. package/dist/commands/ci/utils/rls-verification.d.ts +3 -2
  44. package/dist/commands/db/apply/actors/pg-schema-diff-actors.d.ts +1 -0
  45. package/dist/commands/db/apply/actors/seed-actors.d.ts +1 -0
  46. package/dist/commands/db/apply/contract.d.ts +232 -0
  47. package/dist/commands/db/apply/helpers/fresh-db-handler.d.ts +3 -1
  48. package/dist/commands/db/apply/helpers/hazard-handler.d.ts +19 -8
  49. package/dist/commands/db/apply/helpers/index.d.ts +5 -2
  50. package/dist/commands/db/apply/helpers/no-change-plan.d.ts +2 -0
  51. package/dist/commands/db/apply/helpers/plan-ast-sql-helpers.d.ts +19 -0
  52. package/dist/commands/db/apply/helpers/plan-ast.d.ts +1 -2
  53. package/dist/commands/db/apply/helpers/plan-check-filter.d.ts +11 -14
  54. package/dist/commands/db/apply/helpers/plan-validator.d.ts +34 -0
  55. package/dist/commands/db/apply/helpers/planner-artifact.d.ts +65 -0
  56. package/dist/commands/db/apply/helpers/retry-logic.d.ts +5 -0
  57. package/dist/commands/db/apply/machine.d.ts +100 -14
  58. package/dist/commands/db/commands/db-apply-error.d.ts +6 -1
  59. package/dist/commands/db/commands/db-apply.d.ts +5 -0
  60. package/dist/commands/db/commands/db-plan.d.ts +3 -0
  61. package/dist/commands/db/commands/db-preview-profile.d.ts +23 -0
  62. package/dist/commands/db/commands/db-preview.d.ts +3 -0
  63. package/dist/commands/db/sync/actors.d.ts +1 -0
  64. package/dist/commands/db/sync/contract.d.ts +16 -0
  65. package/dist/commands/db/sync/guardrail-orchestrator.d.ts +15 -0
  66. package/dist/commands/db/sync/guardrail-reporting.d.ts +12 -0
  67. package/dist/commands/db/sync/index.d.ts +4 -0
  68. package/dist/commands/db/sync/machine.d.ts +18 -13
  69. package/dist/commands/db/sync/schema-guardrail-config-test-support.d.ts +15 -0
  70. package/dist/commands/db/sync/schema-guardrail-config.d.ts +11 -0
  71. package/dist/commands/db/sync/schema-guardrail-ddl-order.d.ts +36 -0
  72. package/dist/commands/db/sync/schema-guardrail-graph-guidance.d.ts +15 -0
  73. package/dist/commands/db/sync/schema-guardrail-graph-metadata.d.ts +41 -0
  74. package/dist/commands/db/sync/schema-guardrail-graph-nodes.d.ts +61 -0
  75. package/dist/commands/db/sync/schema-guardrail-graph-sql-helpers.d.ts +31 -0
  76. package/dist/commands/db/sync/schema-guardrail-graph-types.d.ts +56 -0
  77. package/dist/commands/db/sync/schema-guardrail-graph.d.ts +20 -0
  78. package/dist/commands/db/sync/schema-guardrail-local-blockers.d.ts +7 -0
  79. package/dist/commands/db/sync/schema-guardrail-phases.d.ts +26 -0
  80. package/dist/commands/db/sync/schema-guardrail-production-check.d.ts +23 -0
  81. package/dist/commands/db/sync/schema-guardrail-rewrite.d.ts +46 -0
  82. package/dist/commands/db/sync/schema-guardrail-runtime.d.ts +5 -0
  83. package/dist/commands/db/sync/schema-guardrail-semantic-warnings.d.ts +9 -0
  84. package/dist/commands/db/sync/schema-guardrail-types.d.ts +243 -0
  85. package/dist/commands/db/sync/schema-guardrail.d.ts +10 -0
  86. package/dist/commands/db/utils/declarative-dependency-sql-utils.d.ts +1 -1
  87. package/dist/commands/db/utils/duplicate-function-ownership.d.ts +61 -0
  88. package/dist/commands/db/utils/plan-size-guard.d.ts +16 -0
  89. package/dist/commands/db/utils/policy-cross-schema-refs.d.ts +12 -0
  90. package/dist/commands/db/utils/preflight-checks/duplicate-function-ownership-checks.d.ts +4 -0
  91. package/dist/commands/db/utils/sql-table-extractor.d.ts +6 -0
  92. package/dist/commands/test/commands/layer4-prereqs.d.ts +15 -0
  93. package/dist/{config-loader-GT3HAQ7U.js → config-loader-N5ODNMD5.js} +2 -2
  94. package/dist/db-D2OLJDYW.js +12757 -0
  95. package/dist/{dev-MLRKIP7F.js → dev-LGSMDFJN.js} +7 -6
  96. package/dist/{doctor-ROSWSMLH.js → doctor-GYX73IEW.js} +4 -4
  97. package/dist/{env-WNHJVLOT.js → env-KYR6Q7WO.js} +15 -10
  98. package/dist/{env-files-HRNUGZ5O.js → env-files-ONBC47I6.js} +3 -3
  99. package/dist/{hotfix-Z5EGVSMH.js → hotfix-RJIAPLAM.js} +4 -4
  100. package/dist/index.js +3 -3
  101. package/dist/{init-35JLDFHI.js → init-2O6ODG5Z.js} +2 -2
  102. package/dist/{inject-test-attrs-XN4I2AOR.js → inject-test-attrs-F5A346UV.js} +3 -3
  103. package/dist/{manifest-EGCAZ4TK.js → manifest-CI4BRWEB.js} +2 -2
  104. package/dist/{observability-CJA5UFIC.js → observability-WNSCJ5FV.js} +2 -2
  105. package/dist/pg-schema-diff-helpers-7377FS2D.js +7 -0
  106. package/dist/{sdk-XK6HQU7S.js → sdk-BTIVPEE5.js} +1 -1
  107. package/dist/{test-V4KQL574.js → test-QCPN6Z47.js} +74 -46
  108. package/dist/{upgrade-7L4JIE4K.js → upgrade-QZKEI3NJ.js} +2 -2
  109. package/dist/utils/db-url-utils.d.ts +4 -77
  110. package/dist/{vuln-check-D575VXIQ.js → vuln-check-5NUTETPW.js} +1 -1
  111. package/dist/{vuln-checker-QV6XODTJ.js → vuln-checker-UV342N66.js} +1 -1
  112. package/dist/{watch-AL4LCBRM.js → watch-RFVCEQLH.js} +3 -3
  113. package/dist/{workflow-UZIZ2JUS.js → workflow-UOG6ZZMH.js} +3 -3
  114. package/package.json +3 -3
  115. package/dist/chunk-4XHZQRRK.js +0 -215
  116. package/dist/db-S4V4ETDR.js +0 -17556
@@ -1,25 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
- import { normalizeDatabaseUrlForDdl, parseBoolish, enhanceConnectionError, detectAppSchemas, formatSchemasForSql } from './chunk-4XHZQRRK.js';
4
- import { isPathContained } from './chunk-DRSUEMAK.js';
3
+ import { normalizeDatabaseUrlForDdl, parseBoolish, enhanceConnectionError, isIdempotentRoleHazard, detectAppSchemas, formatSchemasForSql, getDbPlanArtifactPath, runDbApply } from './chunk-YRNQEJQW.js';
4
+ import './chunk-WGRVAGSR.js';
5
+ import './chunk-ZWDWFMOX.js';
6
+ import './chunk-UHDAYPHH.js';
7
+ import './chunk-Y5ANTCKE.js';
8
+ import './chunk-IWVXI5O4.js';
9
+ import './chunk-GKBE7EIE.js';
10
+ import './chunk-B7C7CLW2.js';
5
11
  import './chunk-QDF7QXBL.js';
6
12
  import { getSnapshotStateName, getSnapshotStatePaths, isSnapshotComplete } from './chunk-XVNDDHAF.js';
7
- import { writeEnvLocal, startAppBackground, waitForAppReady, executePrSetupBase, createErrorOutput } from './chunk-FWMGC5FP.js';
13
+ import { createInitialSummary, resolveMode, appendGithubStepSummary, buildCiProdApplyStepSummaryMarkdown, setSummaryErrorFromUnknown, writeEnvLocal, startAppBackground, waitForAppReady, executePrSetupBase, createErrorOutput, requireCiAutoApprove, resolveProdApplyInputs, parseIntOr, classifyCiProdApplyError, addGithubMask } from './chunk-EXR4J2JT.js';
14
+ import './chunk-URWDB7YL.js';
8
15
  import { parsePostgresUrl, buildPsqlArgs, buildPsqlEnv, psqlSyncQuery } from './chunk-A6A7JIRD.js';
9
- import { ensureRunaTmpDir, runLogged } from './chunk-6FAU4IGR.js';
16
+ import { ensureRunaTmpDir, runLogged } from './chunk-ELXXQIGW.js';
10
17
  import { createMachineStateChangeLogger } from './chunk-5FT3F36G.js';
11
18
  import { getSafeEnv, getFilteredEnv, redactSecrets } from './chunk-II7VYQEM.js';
12
19
  import { init_constants, init_local_supabase, detectLocalSupabasePorts } from './chunk-QSEF4T3Y.js';
13
20
  import { emitJsonSuccess } from './chunk-KE6QJBZG.js';
14
21
  import './chunk-WJXC4MVY.js';
15
22
  import { setOutputFormat } from './chunk-HKUWEGUX.js';
16
- import { detectEnvironment } from './chunk-JMJP4A47.js';
17
- import { init_esm_shims, __require } from './chunk-VRXHCR5K.js';
23
+ import './chunk-OERS32LW.js';
24
+ import './chunk-GT5DMS5R.js';
25
+ import { init_esm_shims } from './chunk-VRXHCR5K.js';
18
26
  import { Command } from 'commander';
19
27
  import { spawnSync, spawn, execFileSync } from 'child_process';
20
- import { mkdir, writeFile, appendFile, readFile } from 'fs/promises';
28
+ import { mkdir, writeFile, readFile } from 'fs/promises';
21
29
  import path4, { join } from 'path';
22
- import { CommandOutcomeSchema, createCLILogger, CLIError, syncFromProduction, formatDuration, GITHUB_API, getClassificationForProfile, isTimeoutLikeMessage, buildCommandOutcomeSummary, deriveCommandExitMode, getStatusIcon, detectDatabasePackage, DATABASE_PACKAGE_CANDIDATES } from '@runa-ai/runa';
30
+ import { CommandOutcomeSchema, createCLILogger, CLIError, syncFromProduction, GITHUB_API, getClassificationForProfile, formatDuration, buildCommandOutcomeSummary, deriveCommandExitMode, getStatusIcon, DATABASE_PACKAGE_CANDIDATES, detectDatabasePackage } from '@runa-ai/runa';
23
31
  import { z } from 'zod';
24
32
  import { existsSync, createWriteStream, readFileSync, readdirSync, statSync, promises, lstatSync } from 'fs';
25
33
  import { resolve4 } from 'dns/promises';
@@ -28,6 +36,7 @@ import { fromPromise, setup, assign, createActor } from 'xstate';
28
36
  import { randomUUID, createHash } from 'crypto';
29
37
  import { execa } from 'execa';
30
38
  import postgres from 'postgres';
39
+ import { resolveLayer4DiscoverySync } from '@runa-ai/runa/test-config';
31
40
  import { glob } from 'glob';
32
41
 
33
42
  createRequire(import.meta.url);
@@ -37,28 +46,6 @@ init_esm_shims();
37
46
 
38
47
  // src/commands/ci/commands/ci-checks.ts
39
48
  init_esm_shims();
40
-
41
- // src/commands/ci/utils/github.ts
42
- init_esm_shims();
43
- async function appendGithubStepSummary(markdown) {
44
- const file = process.env.GITHUB_STEP_SUMMARY;
45
- if (!file) return;
46
- await appendFile(file, `${markdown.trimEnd()}
47
-
48
- `, "utf-8");
49
- }
50
- function addGithubMask(value) {
51
- if (!value) return;
52
- console.log(`::add-mask::${value}`);
53
- }
54
- z.object({
55
- action: z.string().optional(),
56
- pull_request: z.object({
57
- number: z.number().int()
58
- })
59
- }).passthrough();
60
-
61
- // src/commands/ci/commands/ci-checks.ts
62
49
  async function runTool(params) {
63
50
  const startTime = Date.now();
64
51
  return new Promise((resolve) => {
@@ -316,29 +303,30 @@ async function loadCiConfig(params) {
316
303
 
317
304
  // src/commands/ci/utils/ci-logging.ts
318
305
  init_esm_shims();
306
+ var write = (msg) => process.stderr.write(msg + "\n");
319
307
  function logSection(title) {
320
- console.log("");
321
- console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
322
- console.log(title);
323
- console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
324
- console.log("");
308
+ write("");
309
+ write("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
310
+ write(title);
311
+ write("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
312
+ write("");
325
313
  }
326
314
  function logKeyValueTable(params) {
327
315
  logSection(params.title);
328
316
  for (const [k, v] of params.rows) {
329
- console.log(`${k}: ${v}`);
317
+ write(`${k}: ${v}`);
330
318
  }
331
319
  }
332
320
  function logPlan(steps) {
333
321
  logSection("Plan");
334
322
  for (const step of steps) {
335
- console.log(`- ${step.id}: ${step.description}`);
323
+ write(`- ${step.id}: ${step.description}`);
336
324
  }
337
325
  }
338
326
  function logNextActions(items) {
339
327
  logSection("Next");
340
328
  for (const item of items) {
341
- console.log(`- ${item}`);
329
+ write(`- ${item}`);
342
330
  }
343
331
  }
344
332
 
@@ -506,7 +494,7 @@ async function githubRequest(params) {
506
494
  url.searchParams.set(k, String(v));
507
495
  }
508
496
  const startTime = Date.now();
509
- console.log(`[DEBUG] GitHub API: ${params.method} ${params.path} (starting)`);
497
+ process.stderr.write(`[DEBUG] GitHub API: ${params.method} ${params.path} (starting)`);
510
498
  const controller = new AbortController();
511
499
  const timeoutId = setTimeout(() => controller.abort(), GITHUB_API_TIMEOUT_MS);
512
500
  try {
@@ -525,7 +513,7 @@ async function githubRequest(params) {
525
513
  const duration = Date.now() - startTime;
526
514
  if (!res.ok) {
527
515
  const text = await res.text().catch(() => "");
528
- console.error(
516
+ process.stderr.write(
529
517
  `[DEBUG] GitHub API: ${params.method} ${params.path} failed (${res.status}) after ${duration}ms`
530
518
  );
531
519
  throw new CLIError(
@@ -536,7 +524,7 @@ async function githubRequest(params) {
536
524
  20
537
525
  );
538
526
  }
539
- console.log(
527
+ process.stderr.write(
540
528
  `[DEBUG] GitHub API: ${params.method} ${params.path} succeeded (${res.status}) in ${duration}ms`
541
529
  );
542
530
  if (res.status === 204) return null;
@@ -546,7 +534,7 @@ async function githubRequest(params) {
546
534
  clearTimeout(timeoutId);
547
535
  const duration = Date.now() - startTime;
548
536
  if (error instanceof Error && error.name === "AbortError") {
549
- console.error(
537
+ process.stderr.write(
550
538
  `[DEBUG] GitHub API: ${params.method} ${params.path} timed out after ${duration}ms`
551
539
  );
552
540
  throw new CLIError(
@@ -557,7 +545,7 @@ async function githubRequest(params) {
557
545
  21
558
546
  );
559
547
  }
560
- console.error(
548
+ process.stderr.write(
561
549
  `[DEBUG] GitHub API: ${params.method} ${params.path} error after ${duration}ms: ${error instanceof Error ? error.message : String(error)}`
562
550
  );
563
551
  throw error;
@@ -684,17 +672,6 @@ async function detectStack(repoRoot, tmpDir, productionDbUrlAdmin) {
684
672
  });
685
673
  return String(res.stdout ?? "").trim();
686
674
  }
687
- function runGitDiffQuiet(repoRoot, diffPaths) {
688
- const result = spawnSync("git", ["diff", "--quiet", "origin/main", "HEAD", "--", ...diffPaths], {
689
- cwd: repoRoot,
690
- stdio: "ignore",
691
- timeout: 3e4
692
- });
693
- if (result.error || result.signal === "SIGTERM") {
694
- return null;
695
- }
696
- return result.status !== 0;
697
- }
698
675
  function getSqlSchemaDiffPaths(repoRoot) {
699
676
  const declarativeSqlPath = path4.join(repoRoot, "supabase/schemas/declarative");
700
677
  const idempotentSqlPath = path4.join(repoRoot, "supabase/schemas/idempotent");
@@ -711,6 +688,38 @@ function getLegacySchemaDiffPath(repoRoot) {
711
688
  const schemaPath = path4.join(detected, "src", "schema");
712
689
  return existsSync(path4.join(repoRoot, schemaPath)) ? `${schemaPath}/` : null;
713
690
  }
691
+ function resolveSchemaDiffTarget(repoRoot) {
692
+ const sqlDiffPaths = getSqlSchemaDiffPaths(repoRoot);
693
+ if (sqlDiffPaths.length > 0) {
694
+ return {
695
+ label: "git diff schema (supabase/schemas)",
696
+ paths: sqlDiffPaths
697
+ };
698
+ }
699
+ const legacySchemaPath = getLegacySchemaDiffPath(repoRoot);
700
+ if (legacySchemaPath) {
701
+ return {
702
+ label: "git diff schema (legacy drizzle schema)",
703
+ paths: [legacySchemaPath]
704
+ };
705
+ }
706
+ for (const candidate of DATABASE_PACKAGE_CANDIDATES) {
707
+ const candidatePath = path4.join("packages", candidate, "src", "schema");
708
+ if (existsSync(path4.join(repoRoot, candidatePath))) {
709
+ return {
710
+ label: `git diff schema (packages/${candidate})`,
711
+ paths: [`${candidatePath}/`]
712
+ };
713
+ }
714
+ }
715
+ if (existsSync(path4.join(repoRoot, "src", "schema"))) {
716
+ return {
717
+ label: "git diff schema (src/schema)",
718
+ paths: ["src/schema/"]
719
+ };
720
+ }
721
+ return null;
722
+ }
714
723
  function checkIfInitialDeployment(repoRoot, productionDbUrl) {
715
724
  const schemasDir = path4.join(repoRoot, "supabase", "schemas", "declarative");
716
725
  let schemas;
@@ -777,71 +786,32 @@ function checkIfInitialDeployment(repoRoot, productionDbUrl) {
777
786
  };
778
787
  }
779
788
  }
780
- function hasSchemaChanges(repoRoot) {
781
- const sqlDiffPaths = getSqlSchemaDiffPaths(repoRoot);
782
- if (sqlDiffPaths.length > 0) {
783
- return runGitDiffQuiet(repoRoot, sqlDiffPaths);
784
- }
785
- const legacySchemaPath = getLegacySchemaDiffPath(repoRoot);
786
- if (legacySchemaPath) {
787
- return runGitDiffQuiet(repoRoot, [legacySchemaPath]);
789
+ async function collectSchemaChangeSummary(repoRoot, tmpDir) {
790
+ const target = resolveSchemaDiffTarget(repoRoot);
791
+ if (!target) {
792
+ return {
793
+ hasChanges: null,
794
+ diffLogPath: null,
795
+ targetLabel: null,
796
+ diffPaths: []
797
+ };
788
798
  }
789
- return null;
790
- }
791
- async function showSchemaDiff(repoRoot, tmpDir) {
792
799
  const diffLog = path4.join(tmpDir, "schema-diff.log");
793
- const safeEnv = getSafeEnv();
794
- const declarativeSqlPath = path4.join(repoRoot, "supabase/schemas/declarative");
795
- if (existsSync(declarativeSqlPath)) {
796
- await runLogged({
797
- cwd: repoRoot,
798
- env: safeEnv,
799
- label: "git diff schema (supabase/schemas/declarative)",
800
- command: "git",
801
- args: ["diff", "origin/main", "HEAD", "--", "supabase/schemas/declarative/"],
802
- logFile: diffLog
803
- });
804
- return;
805
- }
806
- const detected = detectDatabasePackage(repoRoot);
807
- if (detected) {
808
- const schemaPath = path4.join(detected, "src", "schema");
809
- if (existsSync(path4.join(repoRoot, schemaPath))) {
810
- await runLogged({
811
- cwd: repoRoot,
812
- env: safeEnv,
813
- label: `git diff schema (${detected})`,
814
- command: "git",
815
- args: ["diff", "origin/main", "HEAD", "--", `${schemaPath}/`],
816
- logFile: diffLog
817
- });
818
- return;
819
- }
820
- }
821
- for (const candidate of DATABASE_PACKAGE_CANDIDATES) {
822
- const candidatePath = path4.join("packages", candidate, "src", "schema");
823
- if (existsSync(path4.join(repoRoot, candidatePath))) {
824
- await runLogged({
825
- cwd: repoRoot,
826
- env: safeEnv,
827
- label: `git diff schema (packages/${candidate})`,
828
- command: "git",
829
- args: ["diff", "origin/main", "HEAD", "--", `${candidatePath}/`],
830
- logFile: diffLog
831
- });
832
- return;
833
- }
834
- }
835
- if (existsSync(path4.join(repoRoot, "src", "schema"))) {
836
- await runLogged({
837
- cwd: repoRoot,
838
- env: safeEnv,
839
- label: "git diff schema (src/schema)",
840
- command: "git",
841
- args: ["diff", "origin/main", "HEAD", "--", "src/schema/"],
842
- logFile: diffLog
843
- });
844
- }
800
+ const diffResult = await runLogged({
801
+ cwd: repoRoot,
802
+ env: getSafeEnv(),
803
+ label: target.label,
804
+ command: "git",
805
+ args: ["diff", "origin/main", "HEAD", "--", ...target.paths],
806
+ logFile: diffLog
807
+ });
808
+ const rawDiff = String(diffResult.stdout ?? "");
809
+ return {
810
+ hasChanges: rawDiff.trim().length > 0,
811
+ diffLogPath: diffLog,
812
+ targetLabel: target.label,
813
+ diffPaths: target.paths
814
+ };
845
815
  }
846
816
  async function detectRisks(repoRoot, tmpDir, timeoutMs) {
847
817
  const logFile = path4.join(tmpDir, "db-risks.log");
@@ -859,8 +829,8 @@ async function detectRisks(repoRoot, tmpDir, timeoutMs) {
859
829
  } catch (error) {
860
830
  let logContent = "";
861
831
  try {
862
- const { readFileSync: readFileSync5 } = await import('fs');
863
- logContent = readFileSync5(logFile, "utf-8");
832
+ const { readFileSync: readFileSync4 } = await import('fs');
833
+ logContent = readFileSync4(logFile, "utf-8");
864
834
  } catch {
865
835
  }
866
836
  const isInitialDeployment = logContent.includes("No common ancestor") || logContent.includes("INITIAL DEPLOYMENT");
@@ -909,15 +879,12 @@ async function snapshotRestoreLatest(repoRoot, tmpDir, productionDbUrlAdmin, tim
909
879
  timeoutMs
910
880
  });
911
881
  }
912
- function parseApplyLog(logContent) {
913
- const hazards = [];
882
+ function summarizePlanSql(sql) {
914
883
  let statements = 0;
915
884
  const changes = [];
916
- let retryAttempts;
917
- let retryWaitMs;
918
- const createMatches = logContent.match(/CREATE\s+(TABLE|INDEX|POLICY|FUNCTION|TRIGGER)/gi);
919
- const alterMatches = logContent.match(/ALTER\s+(TABLE|COLUMN|POLICY)/gi);
920
- const dropMatches = logContent.match(/DROP\s+(TABLE|INDEX|POLICY|FUNCTION|TRIGGER)/gi);
885
+ const createMatches = sql.match(/CREATE\s+(TABLE|INDEX|POLICY|FUNCTION|TRIGGER)/gi);
886
+ const alterMatches = sql.match(/ALTER\s+(TABLE|COLUMN|POLICY)/gi);
887
+ const dropMatches = sql.match(/DROP\s+(TABLE|INDEX|POLICY|FUNCTION|TRIGGER)/gi);
921
888
  const creates = createMatches?.length ?? 0;
922
889
  const alters = alterMatches?.length ?? 0;
923
890
  const drops = dropMatches?.length ?? 0;
@@ -925,87 +892,123 @@ function parseApplyLog(logContent) {
925
892
  if (creates > 0) changes.push(`${creates} CREATE`);
926
893
  if (alters > 0) changes.push(`${alters} ALTER`);
927
894
  if (drops > 0) changes.push(`${drops} DROP`);
928
- const hazardSet = /* @__PURE__ */ new Set();
929
- const hazardMatches = logContent.matchAll(/-- Hazard (\w+): (.+)/g);
930
- for (const match of hazardMatches) {
931
- hazardSet.add(`${match[1]}: ${match[2]}`);
932
- }
933
- hazards.push(...hazardSet);
934
- const retryMatch = logContent.match(/Retry (\d+)\/(\d+) after (\d+)ms/g);
935
- if (retryMatch) {
936
- retryAttempts = retryMatch.length;
937
- const waitMatches = logContent.matchAll(/after (\d+)ms/g);
938
- retryWaitMs = 0;
939
- for (const m of waitMatches) {
940
- retryWaitMs += Number.parseInt(m[1], 10);
941
- }
895
+ if (!sql.trim()) {
896
+ return { statements: 0, summary: "No schema changes" };
942
897
  }
943
- const metricsMatch = logContent.match(/retries=(\d+)/);
944
- if (metricsMatch) {
945
- retryAttempts = Number.parseInt(metricsMatch[1], 10);
898
+ const summary = changes.length > 0 ? changes.join(", ") : "Schema applied";
899
+ return { statements, summary };
900
+ }
901
+ function summarizeApplyResult(result) {
902
+ const sql = result.filteredPlanSql ?? result.planSql ?? "";
903
+ const summaryFromSql = summarizePlanSql(sql);
904
+ const statements = result.planSummary?.effectiveStatements ?? summaryFromSql.statements;
905
+ if (!sql.trim() || statements === 0 || result.planner?.hasChanges === false) {
906
+ return {
907
+ statements: 0,
908
+ summary: "No schema changes"
909
+ };
946
910
  }
947
- if (logContent.toLowerCase().includes("no schema changes detected")) {
948
- return { statements: 0, hazards: [], summary: "No schema changes", retryAttempts, retryWaitMs };
911
+ if (summaryFromSql.summary !== "Schema applied") {
912
+ return {
913
+ statements,
914
+ summary: summaryFromSql.summary
915
+ };
949
916
  }
950
- const summary = changes.length > 0 ? changes.join(", ") : "Schema applied";
951
- return { statements, hazards, summary, retryAttempts, retryWaitMs };
917
+ return {
918
+ statements,
919
+ summary: `${statements} structural statement${statements === 1 ? "" : "s"} applied`
920
+ };
952
921
  }
953
- async function applyProductionSchema(repoRoot, tmpDir, productionDbUrlAdmin, productionDbUrl, options) {
954
- const args = [
955
- "exec",
956
- "runa",
957
- "db",
958
- "apply",
959
- "production",
960
- "--auto-approve",
961
- "--no-seed",
962
- "--verbose"
963
- ];
964
- if (options?.allowDataLoss) {
965
- args.push("--allow-data-loss");
922
+ async function withTemporaryEnv(overrides, run) {
923
+ const previous = /* @__PURE__ */ new Map();
924
+ for (const [key, value] of Object.entries(overrides)) {
925
+ previous.set(key, process.env[key]);
926
+ if (value === void 0) {
927
+ delete process.env[key];
928
+ } else {
929
+ process.env[key] = value;
930
+ }
931
+ }
932
+ try {
933
+ return await run();
934
+ } finally {
935
+ for (const [key, value] of previous.entries()) {
936
+ if (value === void 0) {
937
+ delete process.env[key];
938
+ } else {
939
+ process.env[key] = value;
940
+ }
941
+ }
966
942
  }
967
- if (options?.confirmAuthzUpdate) {
968
- args.push("--confirm-authz-update");
943
+ }
944
+ async function withTimeout(promise, timeoutMs, label) {
945
+ if (!timeoutMs || timeoutMs <= 0) {
946
+ return promise;
969
947
  }
970
- if (options?.maxLockWaitMs !== void 0) {
971
- args.push("--max-lock-wait-ms", String(options.maxLockWaitMs));
948
+ let timer;
949
+ try {
950
+ return await Promise.race([
951
+ promise,
952
+ new Promise((_, reject) => {
953
+ timer = setTimeout(() => {
954
+ reject(new Error(`${label} timed out after ${timeoutMs}ms`));
955
+ }, timeoutMs);
956
+ timer.unref?.();
957
+ })
958
+ ]);
959
+ } finally {
960
+ if (timer) {
961
+ clearTimeout(timer);
962
+ }
972
963
  }
973
- const logPath = path4.join(tmpDir, "db-apply-production.log");
964
+ }
965
+ async function applyProductionSchema(repoRoot, tmpDir, productionDbUrlAdmin, productionDbUrl, options) {
966
+ const logPath = path4.join(tmpDir, "db-apply-production.json");
974
967
  const startTime = Date.now();
975
- await runLogged({
976
- cwd: repoRoot,
977
- env: {
968
+ const plannerArtifactPath = getDbPlanArtifactPath({
969
+ repoRoot,
970
+ environment: "production",
971
+ previewProfile: "full"
972
+ });
973
+ const result = await withTemporaryEnv(
974
+ {
978
975
  ...getFilteredEnv(),
979
- // PRD Naming Convention:
980
- // - GH_DATABASE_URL_ADMIN = postgres role (DDL capable, for pg-schema-diff)
981
- // - GH_DATABASE_URL = drizzle_app role (app runtime)
982
976
  GH_DATABASE_URL_ADMIN: productionDbUrlAdmin,
983
977
  GH_DATABASE_URL: productionDbUrl || productionDbUrlAdmin
984
978
  },
985
- label: "db apply production",
986
- command: "pnpm",
987
- args,
988
- logFile: logPath,
989
- timeoutMs: options?.timeoutMs
990
- });
991
- const totalMs = Date.now() - startTime;
992
- let logContent = "";
993
- try {
994
- const { readFileSync: readFileSync5 } = await import('fs');
995
- logContent = readFileSync5(logPath, "utf-8");
996
- } catch {
979
+ () => withTimeout(
980
+ runDbApply("production", {
981
+ verbose: true,
982
+ seed: false,
983
+ autoApprove: true,
984
+ allowDataLoss: options?.allowDataLoss,
985
+ confirmAuthzUpdate: options?.confirmAuthzUpdate,
986
+ maxLockWaitMs: options?.maxLockWaitMs,
987
+ commandLabel: `runa db apply production --from-plan ${path4.relative(repoRoot, plannerArtifactPath)}`,
988
+ fromPlan: plannerArtifactPath
989
+ }),
990
+ options?.timeoutMs,
991
+ "db apply production"
992
+ )
993
+ );
994
+ await writeFile(logPath, `${JSON.stringify(result, null, 2)}
995
+ `, "utf-8");
996
+ if (result.error) {
997
+ throw new Error(result.error);
997
998
  }
998
- const parsed = parseApplyLog(logContent);
999
+ const totalMs = Date.now() - startTime;
1000
+ const parsed = summarizeApplyResult(result);
999
1001
  return {
1000
1002
  statementsExecuted: parsed.statements,
1001
- hazards: parsed.hazards,
1003
+ hazards: result.hazards,
1002
1004
  changeSummary: parsed.summary,
1003
1005
  logPath,
1004
1006
  metrics: {
1005
- totalMs,
1006
- retryAttempts: parsed.retryAttempts,
1007
- retryWaitMs: parsed.retryWaitMs
1008
- }
1007
+ totalMs: result.metrics?.totalMs ?? totalMs,
1008
+ retryAttempts: result.metrics?.retryAttempts,
1009
+ retryWaitMs: result.metrics?.retryWaitMs
1010
+ },
1011
+ planner: result.planner
1009
1012
  };
1010
1013
  }
1011
1014
  async function auditRecord(repoRoot, tmpDir, productionDbUrlAdmin, params) {
@@ -1019,30 +1022,17 @@ async function auditRecord(repoRoot, tmpDir, productionDbUrlAdmin, params) {
1019
1022
  logFile: path4.join(tmpDir, "git-commit-message.log")
1020
1023
  });
1021
1024
  const commitMsg = String(msg.stdout ?? "").trim();
1022
- const schemaPaths = [];
1023
- const declarativeSqlPath = path4.join(repoRoot, "supabase/schemas/declarative");
1024
- if (existsSync(declarativeSqlPath)) {
1025
- schemaPaths.push("supabase/schemas/declarative/");
1026
- } else {
1027
- const detected = detectDatabasePackage(repoRoot);
1028
- if (detected) {
1029
- schemaPaths.push(`${detected}/src/schema/`);
1030
- } else {
1031
- for (const candidate of DATABASE_PACKAGE_CANDIDATES) {
1032
- schemaPaths.push(`packages/${candidate}/src/schema/`);
1033
- }
1034
- schemaPaths.push("src/schema/");
1035
- }
1036
- }
1037
- const diff = await runLogged({
1038
- cwd: repoRoot,
1039
- env: safeEnv,
1040
- label: "git diff (schema)",
1041
- command: "git",
1042
- args: ["diff", "origin/main", "HEAD", "--", ...schemaPaths],
1043
- logFile: path4.join(tmpDir, "git-schema-diff.log")
1044
- });
1045
- const diffRaw = String(diff.stdout ?? "");
1025
+ const target = resolveSchemaDiffTarget(repoRoot);
1026
+ const diffRaw = target ? String(
1027
+ (await runLogged({
1028
+ cwd: repoRoot,
1029
+ env: safeEnv,
1030
+ label: target.label,
1031
+ command: "git",
1032
+ args: ["diff", "origin/main", "HEAD", "--", ...target.paths],
1033
+ logFile: path4.join(tmpDir, "git-schema-diff.log")
1034
+ })).stdout ?? ""
1035
+ ) : "";
1046
1036
  const diffLimited = diffRaw.length > 1e4 ? diffRaw.slice(0, 1e4) : diffRaw;
1047
1037
  await runLogged({
1048
1038
  cwd: repoRoot,
@@ -1102,6 +1092,24 @@ async function notifyDeployment(repoRoot, tmpDir, params) {
1102
1092
  }
1103
1093
 
1104
1094
  // src/commands/ci/commands/ci-prod-github.ts
1095
+ function formatPlannerSource(result) {
1096
+ const planner = result.planner;
1097
+ if (!planner) return null;
1098
+ if (planner.source === "artifact") {
1099
+ return "Reused checked full preview artifact";
1100
+ }
1101
+ if (planner.reuseAttempted) {
1102
+ return "Inline planning after checked artifact fallback";
1103
+ }
1104
+ return "Inline planning";
1105
+ }
1106
+ function formatPlannerFallback(result) {
1107
+ const planner = result.planner;
1108
+ if (!planner?.reuseAttempted || planner.source === "artifact" || !planner.reuseReason) {
1109
+ return null;
1110
+ }
1111
+ return planner.reuseMessage ? `${planner.reuseReason}: ${planner.reuseMessage}` : planner.reuseReason;
1112
+ }
1105
1113
  async function maybeNotifyExternal(params) {
1106
1114
  if (params.skipNotify) return;
1107
1115
  const runaAppUrl = process.env.RUNA_APP_URL?.trim() || "";
@@ -1142,6 +1150,17 @@ async function maybeAddGithubLabelAndComment(params) {
1142
1150
  if (result.statementsExecuted > 0) {
1143
1151
  changeSummaryLines.push(`- **Statements executed**: ${result.statementsExecuted}`);
1144
1152
  }
1153
+ const plannerSource = formatPlannerSource(result);
1154
+ if (plannerSource) {
1155
+ changeSummaryLines.push(`- **Plan source**: ${plannerSource}`);
1156
+ }
1157
+ const plannerFallback = formatPlannerFallback(result);
1158
+ if (plannerFallback) {
1159
+ changeSummaryLines.push(`- **Plan reuse fallback**: ${plannerFallback}`);
1160
+ }
1161
+ if (result.planner?.hasChanges === false) {
1162
+ changeSummaryLines.push("- **Plan result**: No schema changes");
1163
+ }
1145
1164
  if (result.hazards.length > 0) {
1146
1165
  const hazardList = result.hazards.map((h) => ` - ${h}`).join("\n");
1147
1166
  changeSummaryLines.push(`- **Hazards**:
@@ -1194,135 +1213,6 @@ async function maybePostFailureComment(params) {
1194
1213
  }
1195
1214
  }
1196
1215
 
1197
- // src/commands/ci/commands/ci-prod-utils.ts
1198
- init_esm_shims();
1199
- function requireEnv(name) {
1200
- const v = process.env[name];
1201
- if (v && v.trim().length > 0) return v.trim();
1202
- throw new CLIError(
1203
- `Missing required environment variable: ${name}`,
1204
- "CI_INPUT_MISSING",
1205
- [`Set ${name} in GitHub Actions secrets/env`, "If unsure, run: runa check"],
1206
- void 0,
1207
- 10
1208
- );
1209
- }
1210
- function resolveRepoKind() {
1211
- const env = detectEnvironment(process.cwd());
1212
- const result = env === "runa-repo" ? "monorepo" : env === "pj-repo" ? "pj-repo" : "unknown";
1213
- if (process.env.RUNA_DEBUG === "true") {
1214
- console.error("[DEBUG:resolveRepoKind]", {
1215
- cwd: process.cwd(),
1216
- detectedEnv: env,
1217
- result
1218
- });
1219
- }
1220
- return result;
1221
- }
1222
- function createInitialSummary(params) {
1223
- return {
1224
- version: "1.0",
1225
- mode: params.mode,
1226
- command: "ci prod-apply",
1227
- status: "failure",
1228
- startedAt: params.startedAt.toISOString(),
1229
- endedAt: params.startedAt.toISOString(),
1230
- durationMs: 0,
1231
- repoKind: resolveRepoKind(),
1232
- detected: {},
1233
- diagnostics: {},
1234
- steps: {},
1235
- layers: {},
1236
- errors: []
1237
- };
1238
- }
1239
- function requireCiAutoApprove(params) {
1240
- if (params.mode !== "github-actions") return;
1241
- if (params.autoApprove === true) return;
1242
- throw new CLIError(
1243
- "Missing required flag: --auto-approve (required in CI mode)",
1244
- "CI_AUTO_APPROVE_REQUIRED",
1245
- ["Re-run with: runa ci prod-apply --auto-approve", "Keep CI non-interactive and deterministic"],
1246
- void 0,
1247
- 10
1248
- );
1249
- }
1250
- function resolveProdApplyInputs() {
1251
- const productionDatabaseUrlAdmin = requireEnv("GH_DATABASE_URL_ADMIN");
1252
- const productionDatabaseUrl = requireEnv("GH_DATABASE_URL");
1253
- addGithubMask(productionDatabaseUrlAdmin);
1254
- addGithubMask(productionDatabaseUrl);
1255
- return {
1256
- productionDatabaseUrlAdmin,
1257
- productionDatabaseUrl,
1258
- githubSha: process.env.GITHUB_SHA ?? "unknown",
1259
- githubActor: process.env.GITHUB_ACTOR ?? "unknown",
1260
- githubRepository: process.env.GITHUB_REPOSITORY ?? "unknown"
1261
- };
1262
- }
1263
- function setSummaryErrorFromUnknown(summary, error) {
1264
- if (error instanceof CLIError) {
1265
- summary.errors.push({
1266
- code: error.code ?? "CI_ERROR",
1267
- message: error.message,
1268
- step: "ci prod-apply",
1269
- details: error.cause instanceof Error ? error.cause.message : void 0
1270
- });
1271
- return;
1272
- }
1273
- if (error instanceof Error) {
1274
- summary.errors.push({ code: "CI_ERROR", message: error.message, step: "ci prod-apply" });
1275
- return;
1276
- }
1277
- summary.errors.push({ code: "CI_ERROR", message: String(error), step: "ci prod-apply" });
1278
- }
1279
- function buildCiProdApplyStepSummaryMarkdown(params) {
1280
- const { summary } = params;
1281
- const lines = [];
1282
- const statusEmoji = summary.status === "success" ? "\u2705" : "\u274C";
1283
- const duration = formatDuration(summary.durationMs);
1284
- lines.push(
1285
- `## ${statusEmoji} Production Deploy ${summary.status === "success" ? "Completed" : "Failed"}`
1286
- );
1287
- lines.push("");
1288
- lines.push(`**Duration**: ${duration}`);
1289
- lines.push("");
1290
- if (summary.dbOutcome) {
1291
- lines.push(`**Exit mode**: \`${summary.dbOutcome.exitMode}\``);
1292
- const failedPhase = summary.dbOutcome.phases.find(
1293
- (phase) => phase.status === "failed" || phase.status === "timeout"
1294
- );
1295
- if (failedPhase) {
1296
- lines.push(`**Failed phase**: \`${failedPhase.id}\``);
1297
- }
1298
- if (summary.dbOutcome.summary.warnings > 0) {
1299
- lines.push(`**Warnings**: ${summary.dbOutcome.summary.warnings}`);
1300
- }
1301
- lines.push("");
1302
- }
1303
- if (summary.errors.length > 0) {
1304
- lines.push("### \u274C Errors");
1305
- lines.push("");
1306
- for (const e of summary.errors) {
1307
- lines.push(`- **${e.code}**: ${e.message}`);
1308
- }
1309
- lines.push("");
1310
- }
1311
- lines.push("<details>");
1312
- lines.push("<summary>\u{1F4CB} Technical Details</summary>");
1313
- lines.push("");
1314
- lines.push(`- Command: \`${summary.command}\``);
1315
- lines.push(`- Mode: \`${summary.mode}\``);
1316
- lines.push(`- Summary: \`${params.summaryPath}\``);
1317
- if (summary.dbOutcome) {
1318
- lines.push(`- DB exit mode: \`${summary.dbOutcome.exitMode}\``);
1319
- }
1320
- lines.push("");
1321
- lines.push("</details>");
1322
- lines.push("");
1323
- return lines.join("\n");
1324
- }
1325
-
1326
1216
  // src/commands/ci/commands/ci-prod-workflow.ts
1327
1217
  init_esm_shims();
1328
1218
 
@@ -1523,8 +1413,8 @@ var CiProdApplyWorkflow = class {
1523
1413
  id: "schema-diff",
1524
1414
  description: "Show schema diff",
1525
1415
  run: async (ctx) => {
1526
- await showSchemaDiff(ctx.repoRoot, ctx.tmpDir);
1527
- ctx.hasSchemaChanges = hasSchemaChanges(ctx.repoRoot);
1416
+ ctx.schemaChangeSummary = await collectSchemaChangeSummary(ctx.repoRoot, ctx.tmpDir);
1417
+ ctx.hasSchemaChanges = ctx.schemaChangeSummary.hasChanges;
1528
1418
  if (ctx.hasSchemaChanges === false) {
1529
1419
  ctx.logger.info("No schema changes detected \u2014 schema apply steps will be skipped");
1530
1420
  } else if (ctx.hasSchemaChanges === null) {
@@ -1621,6 +1511,17 @@ var CiProdApplyWorkflow = class {
1621
1511
  timeoutMs: ctx.options.applyTimeoutMs
1622
1512
  }
1623
1513
  );
1514
+ if (ctx.schemaApplyResult.planner) {
1515
+ ctx.summary.detected.dbPlanSource = ctx.schemaApplyResult.planner.source;
1516
+ ctx.summary.detected.dbPlanArtifactPath = ctx.schemaApplyResult.planner.path;
1517
+ ctx.summary.detected.dbPlanReuseAttempted = ctx.schemaApplyResult.planner.reuseAttempted;
1518
+ ctx.summary.detected.dbPlanReuseReason = ctx.schemaApplyResult.planner.reuseReason;
1519
+ ctx.summary.detected.dbPlanReuseMessage = ctx.schemaApplyResult.planner.reuseMessage;
1520
+ ctx.summary.detected.dbPlanHasChanges = ctx.schemaApplyResult.planner.hasChanges;
1521
+ ctx.summary.detected.dbSourceFingerprint = ctx.schemaApplyResult.planner.sourceFingerprint;
1522
+ ctx.summary.detected.dbTargetFingerprint = ctx.schemaApplyResult.planner.targetFingerprint;
1523
+ ctx.summary.detected.dbTargetFingerprintMatched = ctx.schemaApplyResult.planner.targetFingerprintMatched;
1524
+ }
1624
1525
  } catch (error) {
1625
1526
  ctx.logger.error("Schema apply failed; attempting rollback from snapshot");
1626
1527
  await snapshotRestoreLatest(
@@ -1712,18 +1613,18 @@ var CiProdApplyWorkflow = class {
1712
1613
  console.log(`${prefix} done (${duration}ms)`);
1713
1614
  } catch (error) {
1714
1615
  const message = error instanceof Error ? error.message : String(error);
1715
- const timeoutLike = isTimeoutLikeMessage(message);
1616
+ const classified = classifyCiProdApplyError(error);
1716
1617
  phases.push({
1717
1618
  id: step.id,
1718
1619
  label: step.description,
1719
- status: timeoutLike ? "timeout" : "failed",
1620
+ status: classified.retryable ? "timeout" : "failed",
1720
1621
  startedAt: new Date(startedAt).toISOString(),
1721
1622
  endedAt: (/* @__PURE__ */ new Date()).toISOString(),
1722
1623
  durationMs: Date.now() - startedAt,
1723
1624
  error: {
1724
- code: timeoutLike ? "PHASE_TIMEOUT" : "CI_PROD_APPLY_FAILED",
1625
+ code: classified.code === "CI_ERROR" ? "CI_PROD_APPLY_FAILED" : classified.code,
1725
1626
  message,
1726
- retryable: timeoutLike,
1627
+ retryable: classified.retryable,
1727
1628
  phase: step.id
1728
1629
  }
1729
1630
  });
@@ -1756,22 +1657,6 @@ var CiProdApplyWorkflow = class {
1756
1657
  }
1757
1658
  };
1758
1659
 
1759
- // src/commands/ci/commands/ci-resolvers.ts
1760
- init_esm_shims();
1761
-
1762
- // src/commands/ci/utils/config-readers.ts
1763
- init_esm_shims();
1764
-
1765
- // src/commands/ci/commands/ci-resolvers.ts
1766
- function resolveMode(modeRaw) {
1767
- if (modeRaw === "github-actions" || modeRaw === "local") return modeRaw;
1768
- return process.env.GITHUB_ACTIONS === "true" ? "github-actions" : "local";
1769
- }
1770
- function parseIntOr(value, fallback) {
1771
- const n = Number.parseInt(String(value ?? ""), 10);
1772
- return Number.isNaN(n) ? fallback : n;
1773
- }
1774
-
1775
1660
  // src/commands/ci/utils/workflow-idempotency.ts
1776
1661
  init_esm_shims();
1777
1662
  var PROD_DEPLOY_LOCK_ID = 88889;
@@ -1878,7 +1763,7 @@ function guardProductionDeployment(dbUrl, commit) {
1878
1763
  };
1879
1764
  }
1880
1765
  if (!idempotencyResult.checkSuccessful && idempotencyResult.error) {
1881
- console.warn(
1766
+ process.stderr.write(
1882
1767
  `[WARN] Idempotency check failed: ${idempotencyResult.error}. Proceeding with deployment.`
1883
1768
  );
1884
1769
  }
@@ -2029,7 +1914,7 @@ var ciProdApplyCommand = new Command("prod-apply").description("Apply production
2029
1914
  console.log(`status: ${summary.status}`);
2030
1915
  console.log(`summary: ${path4.relative(repoRoot, summaryPath)}`);
2031
1916
  logNextActions([
2032
- "Inspect .runa/tmp logs (db-apply-production.log, snapshot-*.log)",
1917
+ "Inspect .runa/tmp artifacts (db-apply-production.json, snapshot-*.log)",
2033
1918
  "If rollback succeeded, database should be restored to pre-deploy snapshot"
2034
1919
  ]);
2035
1920
  await appendGithubStepSummary(
@@ -4170,6 +4055,7 @@ function buildSkipResult(hasUrl) {
4170
4055
  return {
4171
4056
  preview: {
4172
4057
  executed: false,
4058
+ profile: null,
4173
4059
  planSql: null,
4174
4060
  hazards: [],
4175
4061
  hasChanges: false,
@@ -4256,41 +4142,6 @@ function extractSqlFromSchemaChanges(fullOutput) {
4256
4142
  }
4257
4143
  return null;
4258
4144
  }
4259
- function getIdempotentRoleNames(repoRoot) {
4260
- const idempotentDir = path4.join(repoRoot, "supabase", "schemas", "idempotent");
4261
- const roles = [];
4262
- try {
4263
- if (!existsSync(idempotentDir)) return [];
4264
- const files = readdirSync(idempotentDir).filter((f) => f.endsWith(".sql"));
4265
- for (const file of files) {
4266
- const content = readFileSync(path4.join(idempotentDir, file), "utf-8");
4267
- const roleMatches = content.matchAll(/CREATE\s+ROLE\s+(\w+)\s+WITH/gi);
4268
- for (const match of roleMatches) {
4269
- if (match[1]) roles.push(match[1].toLowerCase());
4270
- }
4271
- const existsMatches = content.matchAll(/rolname\s*=\s*'(\w+)'/gi);
4272
- for (const match of existsMatches) {
4273
- if (match[1] && !roles.includes(match[1].toLowerCase())) {
4274
- roles.push(match[1].toLowerCase());
4275
- }
4276
- }
4277
- }
4278
- } catch {
4279
- }
4280
- return [...new Set(roles)];
4281
- }
4282
- function isIdempotentRoleHazard(hazardType, hazardMessage, causingSql, idempotentRoles) {
4283
- if (hazardType.toUpperCase() !== "AUTHZ_UPDATE") return false;
4284
- if (idempotentRoles.length === 0) return false;
4285
- const sql = (causingSql || "").trim().toLowerCase();
4286
- const isGrantRevoke = /^\s*(?:grant|revoke)\b/i.test(sql);
4287
- if (sql && !isGrantRevoke) return false;
4288
- const textToCheck = sql || hazardMessage.toLowerCase();
4289
- for (const role of idempotentRoles) {
4290
- if (textToCheck.includes(role)) return true;
4291
- }
4292
- return false;
4293
- }
4294
4145
  function getHazardSeverity(type) {
4295
4146
  switch (type.toUpperCase()) {
4296
4147
  case "DELETES_DATA":
@@ -4332,20 +4183,26 @@ function createHazardDetail(type, message, causingSql) {
4332
4183
  causingSql
4333
4184
  };
4334
4185
  }
4335
- function collectCommentHazards(lines, idempotentRoles) {
4186
+ function collectCommentHazards(lines, schemasDir) {
4336
4187
  const hazards = [];
4337
4188
  for (let i = 0; i < lines.length; i++) {
4338
4189
  const parsed = parseHazardLine(lines[i] ?? "");
4339
4190
  if (!parsed) continue;
4340
4191
  const causingSql = findCausingSqlLine(lines, i);
4341
- if (isIdempotentRoleHazard(parsed.type, parsed.message, causingSql, idempotentRoles)) {
4192
+ const registryHazard = {
4193
+ type: parsed.type,
4194
+ message: parsed.message,
4195
+ fullMatch: lines[i] ?? "",
4196
+ causingSql
4197
+ };
4198
+ if (isIdempotentRoleHazard(registryHazard, schemasDir)) {
4342
4199
  continue;
4343
4200
  }
4344
4201
  hazards.push(createHazardDetail(parsed.type, parsed.message, causingSql));
4345
4202
  }
4346
4203
  return hazards;
4347
4204
  }
4348
- function appendEmojiHazards(fullOutput, idempotentRoles, hazards) {
4205
+ function appendEmojiHazards(fullOutput, schemasDir, hazards) {
4349
4206
  const hazardPrefixes = ["\u{1F534}", "\u{1F7E0}", "\u{1F7E1}", "\u26A0"];
4350
4207
  for (const line of fullOutput.split("\n")) {
4351
4208
  const trimmed = line.trim();
@@ -4356,7 +4213,12 @@ function appendEmojiHazards(fullOutput, idempotentRoles, hazards) {
4356
4213
  if (!match?.[1] || !match[2]) continue;
4357
4214
  const type = match[1].toUpperCase();
4358
4215
  const message = match[2].trim();
4359
- if (isIdempotentRoleHazard(type, message, void 0, idempotentRoles)) {
4216
+ const registryHazard = {
4217
+ type,
4218
+ message,
4219
+ fullMatch: trimmed
4220
+ };
4221
+ if (isIdempotentRoleHazard(registryHazard, schemasDir)) {
4360
4222
  continue;
4361
4223
  }
4362
4224
  const isDuplicate = hazards.some(
@@ -4369,9 +4231,9 @@ function appendEmojiHazards(fullOutput, idempotentRoles, hazards) {
4369
4231
  }
4370
4232
  function extractHazardsWithContext(fullOutput, repoRoot) {
4371
4233
  const lines = fullOutput.split("\n");
4372
- const idempotentRoles = repoRoot ? getIdempotentRoleNames(repoRoot) : [];
4373
- const hazards = collectCommentHazards(lines, idempotentRoles);
4374
- appendEmojiHazards(fullOutput, idempotentRoles, hazards);
4234
+ const schemasDir = repoRoot ? path4.join(repoRoot, "supabase", "schemas", "declarative") : void 0;
4235
+ const hazards = collectCommentHazards(lines, schemasDir);
4236
+ appendEmojiHazards(fullOutput, schemasDir, hazards);
4375
4237
  return hazards;
4376
4238
  }
4377
4239
  function stripAnsi(text) {
@@ -4388,6 +4250,7 @@ var PreviewOutputSchema = z.object({
4388
4250
  filteredPlanSql: z.string().optional(),
4389
4251
  hazards: z.array(z.string()),
4390
4252
  checkOnly: z.boolean().optional(),
4253
+ previewProfile: z.enum(["compare-only", "full"]).optional(),
4391
4254
  metrics: z.object({
4392
4255
  totalMs: z.number(),
4393
4256
  idempotentMs: z.number().optional(),
@@ -4411,6 +4274,7 @@ function tryParseStructuredOutput(stdout) {
4411
4274
  hazards: data.hazards,
4412
4275
  hasChanges,
4413
4276
  checkOnly: data.checkOnly ?? false,
4277
+ previewProfile: data.previewProfile ?? null,
4414
4278
  metrics: data.metrics
4415
4279
  };
4416
4280
  } catch {
@@ -4526,6 +4390,7 @@ function buildErrorResult(error, databaseUrl) {
4526
4390
  return {
4527
4391
  preview: {
4528
4392
  executed: true,
4393
+ profile: null,
4529
4394
  planSql: null,
4530
4395
  hazards: [],
4531
4396
  hasChanges: false,
@@ -4541,6 +4406,7 @@ function buildPreviewFromOutput(stdout, _stderr, fullOutput, repoRoot, productio
4541
4406
  hazards: structured.hazards,
4542
4407
  hazardDetails: void 0,
4543
4408
  hasChanges: structured.hasChanges,
4409
+ profile: structured.previewProfile,
4544
4410
  error: null
4545
4411
  };
4546
4412
  }
@@ -4560,6 +4426,7 @@ function buildPreviewFromOutput(stdout, _stderr, fullOutput, repoRoot, productio
4560
4426
  hazards,
4561
4427
  hazardDetails: hazardDetails.length > 0 ? hazardDetails : void 0,
4562
4428
  hasChanges,
4429
+ profile: null,
4563
4430
  error: enhanceConnectionError(rawError, productionUrl)
4564
4431
  };
4565
4432
  }
@@ -4573,6 +4440,7 @@ function buildSuccessPreviewResult(exitCode, stdout, stderr, fullOutput, repoRoo
4573
4440
  hazards: preview.hazards,
4574
4441
  hazardDetails: preview.hazardDetails,
4575
4442
  hasChanges: preview.hasChanges,
4443
+ profile: preview.profile ?? null,
4576
4444
  error: isSuccess ? null : preview.error
4577
4445
  }
4578
4446
  };
@@ -4586,9 +4454,11 @@ var productionPreviewActor = fromPromise(
4586
4454
  }
4587
4455
  const logFile = path4.join(tmpDir, "ci-production-preview.log");
4588
4456
  const timeoutMs = resolveProductionPreviewTimeoutMs();
4589
- console.log("\u25B6 production preview (dry-run): runa db apply production --check --compare-only");
4590
4457
  console.log(
4591
- "\u25B6 production preview details: pg-schema-diff plan + hazard extraction (compare-only)"
4458
+ "\u25B6 production preview (dry-run): runa db preview production --profile compare-only"
4459
+ );
4460
+ console.log(
4461
+ "\u25B6 production preview details: db preview compare-only profile (plan + hazard extraction)"
4592
4462
  );
4593
4463
  console.log(`\u25B6 production preview timeout: ${formatDurationMs(timeoutMs)}`);
4594
4464
  const heartbeat = startHeartbeat("production preview");
@@ -4598,7 +4468,7 @@ var productionPreviewActor = fromPromise(
4598
4468
  try {
4599
4469
  const child = execa(
4600
4470
  "pnpm",
4601
- ["exec", "runa", "db", "apply", "production", "--check", "--compare-only"],
4471
+ ["exec", "runa", "db", "preview", "production", "--profile", "compare-only"],
4602
4472
  {
4603
4473
  cwd: repoRoot,
4604
4474
  env: {
@@ -4785,7 +4655,6 @@ function findBaseRef(repoRoot) {
4785
4655
  stdio: ["pipe", "pipe", "pipe"]
4786
4656
  });
4787
4657
  if (checkOriginMain.status === 0) {
4788
- console.log("[DEBUG] getSchemaGitDiff: Using origin/main as base");
4789
4658
  return "origin/main";
4790
4659
  }
4791
4660
  const baseRef = process.env.GITHUB_BASE_REF;
@@ -4796,9 +4665,6 @@ function findBaseRef(repoRoot) {
4796
4665
  stdio: ["pipe", "pipe", "pipe"]
4797
4666
  });
4798
4667
  if (checkBaseRef.status === 0) {
4799
- console.log(
4800
- `[DEBUG] getSchemaGitDiff: Using origin/${baseRef} as base (from GITHUB_BASE_REF)`
4801
- );
4802
4668
  return `origin/${baseRef}`;
4803
4669
  }
4804
4670
  }
@@ -4808,112 +4674,64 @@ function findBaseRef(repoRoot) {
4808
4674
  stdio: ["pipe", "pipe", "pipe"]
4809
4675
  });
4810
4676
  if (mergeBaseResult.status === 0 && mergeBaseResult.stdout?.trim()) {
4811
- console.log("[DEBUG] getSchemaGitDiff: Using merge-base as base");
4812
4677
  return mergeBaseResult.stdout.trim();
4813
4678
  }
4814
- console.log("[DEBUG] getSchemaGitDiff: No valid base reference found");
4815
4679
  return null;
4816
4680
  }
4681
+ function extractSchemaGitDiffSnapshot(diffOutput) {
4682
+ const filesChanged = [];
4683
+ const seenFiles = /* @__PURE__ */ new Set();
4684
+ for (const match of diffOutput.matchAll(/^diff --git a\/(.+?) b\/(.+)$/gm)) {
4685
+ const filePath = match[2] ?? match[1];
4686
+ if (!filePath || seenFiles.has(filePath)) {
4687
+ continue;
4688
+ }
4689
+ seenFiles.add(filePath);
4690
+ filesChanged.push(filePath);
4691
+ }
4692
+ if (filesChanged.length === 0) {
4693
+ return null;
4694
+ }
4695
+ let linesAdded = 0;
4696
+ let linesDeleted = 0;
4697
+ for (const line of diffOutput.split("\n")) {
4698
+ if (line.startsWith("+") && !line.startsWith("+++")) {
4699
+ linesAdded += 1;
4700
+ continue;
4701
+ }
4702
+ if (line.startsWith("-") && !line.startsWith("---")) {
4703
+ linesDeleted += 1;
4704
+ }
4705
+ }
4706
+ const maxLines = 100;
4707
+ const diffLines = diffOutput.split("\n");
4708
+ const diffSummary = diffLines.length > maxLines ? `${diffLines.slice(0, maxLines).join("\n")}
4709
+ ... (${diffLines.length - maxLines} more lines)` : diffOutput;
4710
+ return {
4711
+ filesChanged,
4712
+ linesAdded,
4713
+ linesDeleted,
4714
+ diffSummary
4715
+ };
4716
+ }
4817
4717
  function getSchemaGitDiff(repoRoot) {
4818
4718
  try {
4819
4719
  const baseRef = findBaseRef(repoRoot);
4820
4720
  if (!baseRef) {
4821
- console.log("[DEBUG] getSchemaGitDiff: Cannot determine base reference, skipping diff");
4822
- return null;
4823
- }
4824
- const filesResult = spawnSync(
4825
- "git",
4826
- ["diff", "--name-only", `${baseRef}...HEAD`, "--", "supabase/schemas/"],
4827
- { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4828
- );
4829
- if (filesResult.status !== 0) {
4830
- console.log(
4831
- `[DEBUG] getSchemaGitDiff: git diff failed: ${filesResult.stderr || "unknown error"}`
4832
- );
4833
4721
  return null;
4834
4722
  }
4835
- const filesChanged = (filesResult.stdout || "").split("\n").filter((f) => f.trim().length > 0);
4836
- console.log(`[DEBUG] getSchemaGitDiff: Found ${filesChanged.length} changed schema file(s)`);
4837
- if (filesChanged.length === 0) {
4838
- return null;
4839
- }
4840
- const statResult = spawnSync(
4841
- "git",
4842
- ["diff", "--stat", `${baseRef}...HEAD`, "--", "supabase/schemas/"],
4843
- { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4844
- );
4845
- const statOutput = statResult.stdout || "";
4846
- const statMatch = statOutput.match(/(\d+) insertions?\(\+\)/);
4847
- const delMatch = statOutput.match(/(\d+) deletions?\(-\)/);
4848
- const linesAdded = statMatch ? Number.parseInt(statMatch[1], 10) : 0;
4849
- const linesDeleted = delMatch ? Number.parseInt(delMatch[1], 10) : 0;
4850
4723
  const diffResult = spawnSync(
4851
4724
  "git",
4852
4725
  ["diff", "--no-color", `${baseRef}...HEAD`, "--", "supabase/schemas/"],
4853
4726
  { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4854
4727
  );
4855
- const fullDiff = diffResult.stdout || "";
4856
- const maxLines = 100;
4857
- const diffLines = fullDiff.split("\n");
4858
- const diffSummary = diffLines.length > maxLines ? `${diffLines.slice(0, maxLines).join("\n")}
4859
- ... (${diffLines.length - maxLines} more lines)` : fullDiff;
4860
- return {
4861
- filesChanged,
4862
- linesAdded,
4863
- linesDeleted,
4864
- diffSummary
4865
- };
4866
- } catch (error) {
4867
- console.log(
4868
- `[DEBUG] getSchemaGitDiff: Exception: ${error instanceof Error ? error.message : String(error)}`
4869
- );
4870
- return null;
4871
- }
4872
- }
4873
- function extractRolesFromSql(content) {
4874
- const roles = [];
4875
- const roleMatches = content.matchAll(/CREATE\s+ROLE\s+(\w+)\s+WITH/gi);
4876
- for (const match of roleMatches) {
4877
- if (match[1]) roles.push(match[1].toLowerCase());
4878
- }
4879
- const existsMatches = content.matchAll(/rolname\s*=\s*'(\w+)'/gi);
4880
- for (const match of existsMatches) {
4881
- if (match[1] && !roles.includes(match[1].toLowerCase())) {
4882
- roles.push(match[1].toLowerCase());
4883
- }
4884
- }
4885
- return roles;
4886
- }
4887
- function getIdempotentRoleNames2(repoRoot) {
4888
- const idempotentDir = path4.join(repoRoot, "supabase", "schemas", "idempotent");
4889
- const roles = [];
4890
- try {
4891
- const fs2 = __require("fs");
4892
- if (!fs2.existsSync(idempotentDir)) return [];
4893
- const files = fs2.readdirSync(idempotentDir).filter((f) => f.endsWith(".sql"));
4894
- for (const file of files) {
4895
- const filePath = path4.join(idempotentDir, file);
4896
- if (!isPathContained(idempotentDir, filePath)) {
4897
- continue;
4898
- }
4899
- const content = fs2.readFileSync(filePath, "utf-8");
4900
- roles.push(...extractRolesFromSql(content));
4728
+ if (diffResult.status !== 0) {
4729
+ return null;
4901
4730
  }
4731
+ return extractSchemaGitDiffSnapshot(diffResult.stdout || "");
4902
4732
  } catch {
4733
+ return null;
4903
4734
  }
4904
- return [...new Set(roles)];
4905
- }
4906
- function isIdempotentRoleHazard2(hazardType, hazardMessage, idempotentRoles, causingSql) {
4907
- if (hazardType !== "AUTHZ_UPDATE") return false;
4908
- if (idempotentRoles.length === 0) return false;
4909
- const sql = ("").trim().toLowerCase();
4910
- const isGrantRevoke = /^\s*(?:grant|revoke)\b/i.test(sql);
4911
- if (sql && !isGrantRevoke) return false;
4912
- const textToCheck = sql || hazardMessage.toLowerCase();
4913
- for (const role of idempotentRoles) {
4914
- if (textToCheck.includes(role)) return true;
4915
- }
4916
- return false;
4917
4735
  }
4918
4736
  function createEmptySchemaChangeStats() {
4919
4737
  return {
@@ -4934,13 +4752,18 @@ function resolveSqlInput(sqlInput, logPath) {
4934
4752
  return null;
4935
4753
  }
4936
4754
  }
4937
- function parseHazards(sql, idempotentRoles) {
4755
+ function parseHazards(sql, schemasDir) {
4938
4756
  const hazards = [];
4939
4757
  const hazardMatches = sql.matchAll(/-- Hazard (\w+): (.+)/g);
4940
4758
  for (const match of hazardMatches) {
4941
4759
  const hazardType = match[1];
4942
4760
  const hazardMessage = match[2];
4943
- if (isIdempotentRoleHazard2(hazardType, hazardMessage, idempotentRoles)) {
4761
+ const registryHazard = {
4762
+ type: hazardType,
4763
+ message: hazardMessage,
4764
+ fullMatch: match[0]
4765
+ };
4766
+ if (isIdempotentRoleHazard(registryHazard, schemasDir)) {
4944
4767
  continue;
4945
4768
  }
4946
4769
  hazards.push(`${hazardType}: ${hazardMessage}`);
@@ -4960,8 +4783,8 @@ function parseSchemaChangeStats(sqlInput, logPath, repoRoot) {
4960
4783
  stats.creates.other = countMatches(sql, /CREATE (TRIGGER|TYPE|SEQUENCE|VIEW|SCHEMA)/gi);
4961
4784
  stats.alters = countMatches(sql, /ALTER (TABLE|COLUMN|INDEX|POLICY|FUNCTION)/gi);
4962
4785
  stats.drops = countMatches(sql, /DROP (TABLE|COLUMN|INDEX|POLICY|FUNCTION|TRIGGER)/gi);
4963
- const idempotentRoles = repoRoot ? getIdempotentRoleNames2(repoRoot) : [];
4964
- stats.hazards = parseHazards(sql, idempotentRoles);
4786
+ const schemasDir = repoRoot ? path4.join(repoRoot, "supabase", "schemas", "declarative") : void 0;
4787
+ stats.hazards = parseHazards(sql, schemasDir);
4965
4788
  return stats;
4966
4789
  }
4967
4790
  function isCheckSummaryOutput(output) {
@@ -5235,9 +5058,9 @@ function detectSupabaseContainers() {
5235
5058
  async function checkSupabasePortConflicts(repoRoot) {
5236
5059
  let dbPort = 54322;
5237
5060
  try {
5238
- const { readFileSync: readFileSync5 } = await import('fs');
5061
+ const { readFileSync: readFileSync4 } = await import('fs');
5239
5062
  const configPath = path4.join(repoRoot, "supabase", "config.toml");
5240
- const content = readFileSync5(configPath, "utf-8");
5063
+ const content = readFileSync4(configPath, "utf-8");
5241
5064
  const match = /\[db\][^[]*?port\s*=\s*(\d+)/s.exec(content);
5242
5065
  if (match?.[1]) dbPort = Number.parseInt(match[1], 10);
5243
5066
  } catch {
@@ -5481,7 +5304,7 @@ function shouldSkipSchemaPostCheck(context) {
5481
5304
  return context.mode === "ci-pr-local" && context.executionEnv === "github-actions";
5482
5305
  }
5483
5306
  function shouldReuseCiReferenceStats(context) {
5484
- return context.mode !== "ci-local" && context.schemaApplied && context.schemaDrift !== null && context.schemaDrift?.hasDrift === false;
5307
+ return context.mode !== "ci-local" && context.schemaApplied && context.schemaDrift !== null && context.schemaDrift?.checkExecuted === true && context.schemaDrift?.hasDrift === false;
5485
5308
  }
5486
5309
  function mergeLayerResults(coreResults, e2eResults) {
5487
5310
  return { ...coreResults, ...e2eResults };
@@ -5617,6 +5440,11 @@ init_esm_shims();
5617
5440
 
5618
5441
  // src/commands/ci/commands/ci-pr-capabilities.ts
5619
5442
  init_esm_shims();
5443
+
5444
+ // src/commands/ci/commands/layer4-discovery.ts
5445
+ init_esm_shims();
5446
+
5447
+ // src/commands/ci/commands/ci-pr-capabilities.ts
5620
5448
  async function pathExists(p) {
5621
5449
  return promises.access(p).then(() => true).catch(() => false);
5622
5450
  }
@@ -5654,116 +5482,96 @@ async function queryScalar(params) {
5654
5482
  async function detectDbCapabilities(params) {
5655
5483
  const caps = /* @__PURE__ */ new Set();
5656
5484
  const diagnostics = {};
5657
- const isPostgresRaw = await queryScalar({
5485
+ const batchedSql = `
5486
+ SELECT json_build_object(
5487
+ 'isPostgres', (SELECT version() LIKE 'PostgreSQL%'),
5488
+ 'tableCount', (
5489
+ SELECT COUNT(*)::int
5490
+ FROM information_schema.tables
5491
+ WHERE table_type = 'BASE TABLE'
5492
+ AND table_schema NOT LIKE 'pg_%'
5493
+ AND table_schema NOT IN ('information_schema')
5494
+ ),
5495
+ 'rlsCount', (
5496
+ SELECT COUNT(*)::int
5497
+ FROM pg_class c
5498
+ JOIN pg_namespace n ON n.oid = c.relnamespace
5499
+ WHERE c.relkind IN ('r', 'p')
5500
+ AND n.nspname NOT LIKE 'pg_%'
5501
+ AND n.nspname NOT IN ('information_schema')
5502
+ AND c.relrowsecurity
5503
+ ),
5504
+ 'canSelectAny', (
5505
+ SELECT COALESCE(bool_or(has_table_privilege(current_user, tbl, 'select')), false)
5506
+ FROM (
5507
+ SELECT quote_ident(table_schema) || '.' || quote_ident(table_name) AS tbl
5508
+ FROM information_schema.tables
5509
+ WHERE table_type = 'BASE TABLE'
5510
+ AND table_schema NOT LIKE 'pg_%'
5511
+ AND table_schema NOT IN ('information_schema')
5512
+ LIMIT 50 -- cap sampling to prevent slow scans on large databases
5513
+ ) candidates
5514
+ ),
5515
+ 'authenticatedExists', (
5516
+ SELECT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticated')
5517
+ ),
5518
+ 'isMember', (
5519
+ SELECT CASE
5520
+ WHEN EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticated')
5521
+ THEN pg_has_role(current_user, 'authenticated', 'member')
5522
+ ELSE false
5523
+ END
5524
+ )
5525
+ )::text;
5526
+ `.trim();
5527
+ const raw = await queryScalar({
5658
5528
  repoRoot: params.repoRoot,
5659
5529
  tmpDir: params.tmpDir,
5660
5530
  databaseUrl: params.databaseUrlApp,
5661
- sql: "SELECT version() LIKE 'PostgreSQL%';",
5662
- logName: "cap-is-postgres"
5531
+ sql: batchedSql,
5532
+ logName: "cap-db-batch"
5663
5533
  });
5664
- const isPostgres = parseBoolish(isPostgresRaw);
5665
- if (isPostgres) {
5534
+ let parsed;
5535
+ try {
5536
+ parsed = JSON.parse(raw);
5537
+ } catch {
5538
+ diagnostics["db.postgres"] = `Failed to parse batched query result (raw=${raw.substring(0, 200)})`;
5539
+ return { capabilities: caps, diagnostics };
5540
+ }
5541
+ if (parsed.isPostgres) {
5666
5542
  caps.add("db.postgres");
5667
5543
  diagnostics["db.postgres"] = "PostgreSQL detected via SELECT version()";
5668
5544
  } else {
5669
- diagnostics["db.postgres"] = `Not PostgreSQL (raw=${isPostgresRaw})`;
5545
+ diagnostics["db.postgres"] = `Not PostgreSQL`;
5670
5546
  return { capabilities: caps, diagnostics };
5671
5547
  }
5672
- const tablesRaw = await queryScalar({
5673
- repoRoot: params.repoRoot,
5674
- tmpDir: params.tmpDir,
5675
- databaseUrl: params.databaseUrlApp,
5676
- sql: `
5677
- SELECT COUNT(*)::int
5678
- FROM information_schema.tables
5679
- WHERE table_type='BASE TABLE'
5680
- AND table_schema NOT LIKE 'pg_%'
5681
- AND table_schema NOT IN ('information_schema');
5682
- `.trim(),
5683
- logName: "cap-table-count"
5684
- });
5685
- const tableCount = Number.parseInt(tablesRaw, 10);
5686
- if (Number.isFinite(tableCount) && tableCount > 0) {
5548
+ if (Number.isFinite(parsed.tableCount) && parsed.tableCount > 0) {
5687
5549
  caps.add("db.schemaApplied");
5688
- diagnostics["db.schemaApplied"] = `Non-system base tables detected: ${tableCount}`;
5550
+ diagnostics["db.schemaApplied"] = `Non-system base tables detected: ${parsed.tableCount}`;
5689
5551
  } else {
5690
- diagnostics["db.schemaApplied"] = `No non-system base tables detected (raw=${tablesRaw})`;
5552
+ diagnostics["db.schemaApplied"] = `No non-system base tables detected (count=${parsed.tableCount})`;
5691
5553
  }
5692
- const rlsCountRaw = await queryScalar({
5693
- repoRoot: params.repoRoot,
5694
- tmpDir: params.tmpDir,
5695
- databaseUrl: params.databaseUrlApp,
5696
- sql: `
5697
- SELECT COUNT(*)::int
5698
- FROM pg_class c
5699
- JOIN pg_namespace n ON n.oid=c.relnamespace
5700
- WHERE c.relkind IN ('r','p')
5701
- AND n.nspname NOT LIKE 'pg_%'
5702
- AND n.nspname NOT IN ('information_schema')
5703
- AND c.relrowsecurity;
5704
- `.trim(),
5705
- logName: "cap-rls-count"
5706
- });
5707
- const rlsCount = Number.parseInt(rlsCountRaw, 10);
5708
- if (Number.isFinite(rlsCount) && rlsCount > 0) {
5554
+ if (Number.isFinite(parsed.rlsCount) && parsed.rlsCount > 0) {
5709
5555
  caps.add("db.rlsEnabled");
5710
- diagnostics["db.rlsEnabled"] = `RLS-enabled tables detected: ${rlsCount}`;
5556
+ diagnostics["db.rlsEnabled"] = `RLS-enabled tables detected: ${parsed.rlsCount}`;
5711
5557
  } else {
5712
- diagnostics["db.rlsEnabled"] = `No RLS-enabled tables detected (raw=${rlsCountRaw})`;
5558
+ diagnostics["db.rlsEnabled"] = `No RLS-enabled tables detected (count=${parsed.rlsCount})`;
5713
5559
  }
5714
- const canSelectAnyRaw = await queryScalar({
5715
- repoRoot: params.repoRoot,
5716
- tmpDir: params.tmpDir,
5717
- databaseUrl: params.databaseUrlApp,
5718
- sql: `
5719
- WITH candidates AS (
5720
- SELECT quote_ident(table_schema)||'.'||quote_ident(table_name) AS tbl
5721
- FROM information_schema.tables
5722
- WHERE table_type='BASE TABLE'
5723
- AND table_schema NOT LIKE 'pg_%'
5724
- AND table_schema NOT IN ('information_schema')
5725
- LIMIT 50
5726
- ),
5727
- checks AS (
5728
- SELECT bool_or(has_table_privilege(current_user, tbl, 'select')) AS ok
5729
- FROM candidates
5730
- )
5731
- SELECT COALESCE(ok,false) FROM checks;
5732
- `.trim(),
5733
- logName: "cap-select-any"
5734
- });
5735
- const canSelectAny = parseBoolish(canSelectAnyRaw);
5736
- if (canSelectAny) {
5560
+ if (parsed.canSelectAny) {
5737
5561
  caps.add("db.appCanSelectSomeTable");
5738
5562
  diagnostics["db.appCanSelectSomeTable"] = "has_table_privilege(select) true for at least one table";
5739
5563
  } else {
5740
- diagnostics["db.appCanSelectSomeTable"] = `App role cannot SELECT any sampled table (raw=${canSelectAnyRaw})`;
5564
+ diagnostics["db.appCanSelectSomeTable"] = "App role cannot SELECT any sampled table";
5741
5565
  }
5742
- const authenticatedExistsRaw = await queryScalar({
5743
- repoRoot: params.repoRoot,
5744
- tmpDir: params.tmpDir,
5745
- databaseUrl: params.databaseUrlApp,
5746
- sql: "SELECT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='authenticated');",
5747
- logName: "cap-authenticated-role-exists"
5748
- });
5749
- const authenticatedExists = parseBoolish(authenticatedExistsRaw);
5750
- if (!authenticatedExists) {
5566
+ if (!parsed.authenticatedExists) {
5751
5567
  diagnostics["db.appIsAuthenticatedMember"] = "n/a (authenticated role does not exist)";
5752
5568
  return { capabilities: caps, diagnostics };
5753
5569
  }
5754
- const isMemberRaw = await queryScalar({
5755
- repoRoot: params.repoRoot,
5756
- tmpDir: params.tmpDir,
5757
- databaseUrl: params.databaseUrlApp,
5758
- sql: "SELECT pg_has_role(current_user, 'authenticated', 'member');",
5759
- logName: "cap-app-is-authenticated-member"
5760
- });
5761
- const isMember = parseBoolish(isMemberRaw);
5762
- if (isMember) {
5570
+ if (parsed.isMember) {
5763
5571
  caps.add("db.appIsAuthenticatedMember");
5764
5572
  diagnostics["db.appIsAuthenticatedMember"] = "pg_has_role(member, authenticated) is true";
5765
5573
  } else {
5766
- diagnostics["db.appIsAuthenticatedMember"] = `pg_has_role(member, authenticated)=false (raw=${isMemberRaw})`;
5574
+ diagnostics["db.appIsAuthenticatedMember"] = "pg_has_role(member, authenticated)=false";
5767
5575
  }
5768
5576
  return { capabilities: caps, diagnostics };
5769
5577
  }
@@ -5775,12 +5583,22 @@ function isPlaywrightIgnored(filePath) {
5775
5583
  async function detectGeneratedE2eCapabilities(params) {
5776
5584
  const caps = /* @__PURE__ */ new Set();
5777
5585
  const diagnostics = {};
5778
- const layer4Dir = path4.join(params.repoRoot, ".runa", "tests", "layer4");
5779
- const tmpDir = path4.join(params.repoRoot, ".runa", "tmp");
5586
+ let layer4Dir;
5587
+ let tmpDir;
5588
+ try {
5589
+ const discovery = resolveLayer4DiscoverySync(params.repoRoot);
5590
+ layer4Dir = discovery.currentOutputDir;
5591
+ tmpDir = discovery.tmpDir;
5592
+ } catch (error) {
5593
+ const message = error instanceof Error ? error.message : String(error);
5594
+ diagnostics["e2e.generatedLayer4Present"] = `Layer 4 discovery failed: ${message}`;
5595
+ diagnostics["e2e.generatedLayer4HasUnknown"] = "n/a (Layer 4 discovery failed)";
5596
+ return { capabilities: caps, diagnostics };
5597
+ }
5780
5598
  const hasLayer4Dir = await pathExists(layer4Dir);
5781
5599
  const hasTmpDir = await pathExists(tmpDir);
5782
5600
  if (!hasLayer4Dir && !hasTmpDir) {
5783
- diagnostics["e2e.generatedLayer4Present"] = "Missing .runa/tests/layer4 and .runa/tmp";
5601
+ diagnostics["e2e.generatedLayer4Present"] = `Missing current Layer 4 artifact dirs (${layer4Dir}, ${tmpDir})`;
5784
5602
  diagnostics["e2e.generatedLayer4HasUnknown"] = "n/a (layer4 tests not present)";
5785
5603
  return { capabilities: caps, diagnostics };
5786
5604
  }
@@ -5874,23 +5692,25 @@ async function detectUiContractReady(params) {
5874
5692
  async function detectCiCapabilities(params) {
5875
5693
  const caps = /* @__PURE__ */ new Set();
5876
5694
  const diagnostics = {};
5877
- const db = await detectDbCapabilities({
5878
- repoRoot: params.repoRoot,
5879
- tmpDir: params.tmpDir,
5880
- databaseUrlApp: params.databaseUrlApp
5881
- });
5882
- for (const c of db.capabilities) caps.add(c);
5883
- for (const [k, v] of Object.entries(db.diagnostics)) diagnostics[k] = String(v);
5884
- const e2e = await detectGeneratedE2eCapabilities({ repoRoot: params.repoRoot });
5885
- for (const c of e2e.capabilities) caps.add(c);
5886
- for (const [k, v] of Object.entries(e2e.diagnostics)) diagnostics[k] = String(v);
5695
+ const mergeDiagnostics = (result) => {
5696
+ for (const c of result.capabilities) caps.add(c);
5697
+ for (const [k, v] of Object.entries(result.diagnostics))
5698
+ diagnostics[k] = String(v);
5699
+ };
5700
+ const [db, e2e, ui] = await Promise.all([
5701
+ detectDbCapabilities({
5702
+ repoRoot: params.repoRoot,
5703
+ tmpDir: params.tmpDir,
5704
+ databaseUrlApp: params.databaseUrlApp
5705
+ }),
5706
+ detectGeneratedE2eCapabilities({ repoRoot: params.repoRoot }),
5707
+ detectUiContractReady({ baseUrl: params.baseUrl })
5708
+ ]);
5709
+ mergeDiagnostics(db);
5710
+ mergeDiagnostics(e2e);
5711
+ mergeDiagnostics(ui);
5887
5712
  const preview = await detectPreviewReachable({ baseUrl: params.baseUrl });
5888
- for (const c of preview.capabilities) caps.add(c);
5889
- for (const [k, v] of Object.entries(preview.diagnostics))
5890
- diagnostics[k] = String(v);
5891
- const ui = await detectUiContractReady({ baseUrl: params.baseUrl });
5892
- for (const c of ui.capabilities) caps.add(c);
5893
- for (const [k, v] of Object.entries(ui.diagnostics)) diagnostics[k] = String(v);
5713
+ mergeDiagnostics(preview);
5894
5714
  return { capabilities: Array.from(caps), diagnostics };
5895
5715
  }
5896
5716
  function resolveRequiredCapabilities(params) {
@@ -5937,6 +5757,58 @@ function assertCapabilities(params) {
5937
5757
  );
5938
5758
  }
5939
5759
 
5760
+ // src/commands/ci/machine/actors/test/capabilities.ts
5761
+ var capabilitiesActor = fromPromise(
5762
+ async ({ input }) => {
5763
+ const { repoRoot, tmpDir, databaseUrlApp, baseUrl } = input;
5764
+ try {
5765
+ const detected = await detectCiCapabilities({
5766
+ repoRoot,
5767
+ tmpDir,
5768
+ databaseUrlApp,
5769
+ baseUrl
5770
+ });
5771
+ return { detected };
5772
+ } catch (error) {
5773
+ const errorMsg = error instanceof Error ? error.message : String(error);
5774
+ console.warn(`[Capabilities] Detection failed: ${errorMsg}`);
5775
+ return {
5776
+ detected: { capabilities: [], diagnostics: {} },
5777
+ error: errorMsg
5778
+ };
5779
+ }
5780
+ }
5781
+ );
5782
+ fromPromise(
5783
+ async ({ input }) => {
5784
+ const { config, repoKind, layer, detected } = input;
5785
+ try {
5786
+ const required = resolveRequiredCapabilities({ config, repoKind, layer });
5787
+ const missing = required.filter((r) => !detected.capabilities.includes(r));
5788
+ if (missing.length > 0) {
5789
+ assertCapabilities({
5790
+ required,
5791
+ detected,
5792
+ label: `Layer ${layer} capability gate`
5793
+ });
5794
+ }
5795
+ return { passed: true, required, missing: [] };
5796
+ } catch (error) {
5797
+ const required = resolveRequiredCapabilities({ config, repoKind, layer });
5798
+ const missing = required.filter((r) => !detected.capabilities.includes(r));
5799
+ return {
5800
+ passed: false,
5801
+ required,
5802
+ missing,
5803
+ error: error instanceof Error ? error.message : String(error)
5804
+ };
5805
+ }
5806
+ }
5807
+ );
5808
+
5809
+ // src/commands/ci/machine/actors/test/layer-content.ts
5810
+ init_esm_shims();
5811
+
5940
5812
  // src/commands/ci/commands/ci-layer-content.ts
5941
5813
  init_esm_shims();
5942
5814
  var MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
@@ -6420,71 +6292,36 @@ async function detectLayerContent(repoRoot, selectedLayers, manifestPath) {
6420
6292
  return { layers, skippedLayers, runnableLayers };
6421
6293
  }
6422
6294
 
6423
- // src/commands/ci/machine/actors/test/capabilities.ts
6424
- var capabilitiesActor = fromPromise(
6295
+ // src/commands/ci/machine/actors/test/layer-content.ts
6296
+ var layerContentActor = fromPromise(
6425
6297
  async ({ input }) => {
6426
- const { repoRoot, tmpDir, databaseUrlApp, baseUrl, selectedLayers } = input;
6298
+ const { repoRoot, selectedLayers } = input;
6427
6299
  try {
6428
- const detected = await detectCiCapabilities({
6429
- repoRoot,
6430
- tmpDir,
6431
- databaseUrlApp,
6432
- baseUrl
6433
- });
6434
6300
  const layerContent = await detectLayerContent(repoRoot, selectedLayers);
6435
- return { detected, layerContent };
6301
+ return { layerContent };
6436
6302
  } catch (error) {
6437
- const errorMsg = error instanceof Error ? error.message : String(error);
6438
- console.warn(`[Capabilities] Layer content detection failed: ${errorMsg}`);
6439
- const fallbackLayers = {};
6440
- for (const layer of selectedLayers) {
6441
- fallbackLayers[layer] = {
6442
- layer,
6443
- hasContent: true,
6444
- // Conservative: assume content exists
6445
- contentType: "machines",
6446
- // Placeholder
6447
- count: 0,
6448
- source: "filesystem",
6449
- skipReason: `Detection failed: ${errorMsg}`
6450
- // Track why we're using fallback
6451
- };
6452
- }
6303
+ const errorMessage = error instanceof Error ? error.message : String(error);
6304
+ console.warn(`[Layer Content] Detection failed: ${errorMessage}`);
6453
6305
  const fallbackLayerContent = {
6454
- layers: fallbackLayers,
6306
+ layers: Object.fromEntries(
6307
+ selectedLayers.map((layer) => [
6308
+ layer,
6309
+ {
6310
+ layer,
6311
+ hasContent: true,
6312
+ contentType: "machines",
6313
+ count: 0,
6314
+ source: "filesystem",
6315
+ skipReason: `Detection failed: ${errorMessage}`
6316
+ }
6317
+ ])
6318
+ ),
6455
6319
  skippedLayers: [],
6456
6320
  runnableLayers: selectedLayers
6457
6321
  };
6458
6322
  return {
6459
- detected: { capabilities: [], diagnostics: {} },
6460
6323
  layerContent: fallbackLayerContent,
6461
- error: errorMsg
6462
- };
6463
- }
6464
- }
6465
- );
6466
- fromPromise(
6467
- async ({ input }) => {
6468
- const { config, repoKind, layer, detected } = input;
6469
- try {
6470
- const required = resolveRequiredCapabilities({ config, repoKind, layer });
6471
- const missing = required.filter((r) => !detected.capabilities.includes(r));
6472
- if (missing.length > 0) {
6473
- assertCapabilities({
6474
- required,
6475
- detected,
6476
- label: `Layer ${layer} capability gate`
6477
- });
6478
- }
6479
- return { passed: true, required, missing: [] };
6480
- } catch (error) {
6481
- const required = resolveRequiredCapabilities({ config, repoKind, layer });
6482
- const missing = required.filter((r) => !detected.capabilities.includes(r));
6483
- return {
6484
- passed: false,
6485
- required,
6486
- missing,
6487
- error: error instanceof Error ? error.message : String(error)
6324
+ error: errorMessage
6488
6325
  };
6489
6326
  }
6490
6327
  }
@@ -6711,9 +6548,6 @@ var runLayersActor = fromPromise(
6711
6548
  progressIntervalSeconds = 30
6712
6549
  } = input;
6713
6550
  try {
6714
- console.log(
6715
- `[DEBUG] runLayersActor: Starting with layers=${JSON.stringify(layers)}, failFast=${failFast}`
6716
- );
6717
6551
  const results = await runLayersInParallel({
6718
6552
  repoRoot,
6719
6553
  tmpDir,
@@ -6727,15 +6561,9 @@ var runLayersActor = fromPromise(
6727
6561
  });
6728
6562
  const failedLayers = results.filter((r) => !r.success).map((r) => r.layer);
6729
6563
  const allPassed = failedLayers.length === 0;
6730
- console.log(
6731
- `[DEBUG] runLayersActor: Complete. results=${results.length}, failedLayers=${JSON.stringify(failedLayers)}, allPassed=${allPassed}`
6732
- );
6733
6564
  return { results, allPassed, failedLayers };
6734
6565
  } catch (error) {
6735
6566
  const errorMessage = error instanceof Error ? error.message : String(error);
6736
- console.log(
6737
- `[DEBUG] runLayersActor: Caught error: ${errorMessage}. Returning failedLayers=${JSON.stringify(layers)}`
6738
- );
6739
6567
  return {
6740
6568
  results: [],
6741
6569
  allPassed: false,
@@ -6761,9 +6589,6 @@ function getEffectiveMode(context) {
6761
6589
  function isCiLocalMode(context) {
6762
6590
  return getEffectiveMode(context) === "ci-local";
6763
6591
  }
6764
- function isCiPrLocalMode(context) {
6765
- return getEffectiveMode(context) === "ci-pr-local";
6766
- }
6767
6592
  function isCiPrMode(context) {
6768
6593
  return getEffectiveMode(context) === "ci-pr-local";
6769
6594
  }
@@ -6785,7 +6610,9 @@ function shouldSkipStaticChecks(context) {
6785
6610
  return shouldSkipStep(context, "staticChecks");
6786
6611
  }
6787
6612
  function shouldSkipBuild(context) {
6788
- return shouldSkipStep(context, "build");
6613
+ if (shouldSkipStep(context, "build")) return true;
6614
+ const requiresBuild = context.selectedLayers.some((layer) => layer === 3 || layer === 4);
6615
+ return !requiresBuild;
6789
6616
  }
6790
6617
  function shouldSkipAppStart(context) {
6791
6618
  return shouldSkipStep(context, "appStart");
@@ -6812,8 +6639,19 @@ function shouldPostGitHubComment(context) {
6812
6639
  if (context.input.skipGithubComment === true) return false;
6813
6640
  return true;
6814
6641
  }
6642
+ function isObservabilityPhase(context) {
6643
+ return isCiPrMode(context) && context.phase === "observability";
6644
+ }
6815
6645
  function isTestPhase(context) {
6816
- return isCiPrMode(context) && context.input.phase === "test";
6646
+ return isCiPrMode(context) && context.phase === "test";
6647
+ }
6648
+ function shouldRunPrExecutionPhase(context) {
6649
+ if (!isCiPrMode(context)) return true;
6650
+ return !isObservabilityPhase(context);
6651
+ }
6652
+ function shouldRunPrObservabilityPhase(context) {
6653
+ if (!isCiPrMode(context)) return true;
6654
+ return !isTestPhase(context);
6817
6655
  }
6818
6656
  function hasError(context) {
6819
6657
  return context.error !== null;
@@ -6868,7 +6706,7 @@ init_esm_shims();
6868
6706
 
6869
6707
  // src/commands/ci/machine/formatters/sections/format-helpers.ts
6870
6708
  init_esm_shims();
6871
- function formatDuration4(ms) {
6709
+ function formatDuration3(ms) {
6872
6710
  if (ms < 1e3) return `${ms}ms`;
6873
6711
  const seconds = Math.floor(ms / 1e3);
6874
6712
  if (seconds < 60) return `${seconds}s`;
@@ -7560,9 +7398,10 @@ function truncateSql(sql, maxLength) {
7560
7398
  }
7561
7399
  function generatePreviewDetails(prodPreview) {
7562
7400
  const truncatedSql = truncateSql(prodPreview.planSql || "", 2e3);
7401
+ const profileLabel = prodPreview.profile ?? "compare-only";
7563
7402
  return [
7564
7403
  "<details>",
7565
- "<summary>\u{1F4CB} \u672C\u756A\u30B9\u30AD\u30FC\u30DE\u30D7\u30EC\u30D3\u30E5\u30FC (dry-run)</summary>",
7404
+ `<summary>\u{1F4CB} \u672C\u756A\u30B9\u30AD\u30FC\u30DE\u30D7\u30EC\u30D3\u30E5\u30FC (${profileLabel})</summary>`,
7566
7405
  "",
7567
7406
  "**\u672C\u756A\u306B\u9069\u7528\u4E88\u5B9A\u306ESQL:**",
7568
7407
  "",
@@ -7641,7 +7480,7 @@ function generatePreviewChangesDetectedSection(prodPreview, previewOnlyChanges)
7641
7480
  ];
7642
7481
  if (previewOnlyChanges) {
7643
7482
  lines.push(
7644
- "> \u88DC\u8DB3: `Prod semantic diff` / count diff / index diff \u3067\u306F\u5DEE\u5206\u304C\u306A\u304F\u3001`production --check` \u306E\u307F\u304C\u5909\u66F4\u3092\u691C\u51FA\u3057\u307E\u3057\u305F\u3002",
7483
+ "> \u88DC\u8DB3: `Prod semantic diff` / count diff / index diff \u3067\u306F\u5DEE\u5206\u304C\u306A\u304F\u3001`db preview --profile compare-only` \u306E\u307F\u304C\u5909\u66F4\u3092\u691C\u51FA\u3057\u307E\u3057\u305F\u3002",
7645
7484
  "> \u3053\u308C\u306F ownership / session config / planner-only rewrite \u306A\u3069\u3001canonical diff \u3067\u306F\u8FFD\u308F\u306A\u3044\u5DEE\u5206\u3067\u3042\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002",
7646
7485
  ""
7647
7486
  );
@@ -7684,7 +7523,7 @@ function generateComparisonMismatchSection(schemaStats, expectedDrift) {
7684
7523
  const lines = [
7685
7524
  "> \u{1F536} **\u672C\u756ADB\u3068\u306E\u5DEE\u5206\u691C\u51FA - \u672C\u756A\u30C7\u30D7\u30ED\u30A4\u304C\u5FC5\u8981\u3067\u3059**",
7686
7525
  ">",
7687
- "> `runa db apply production --check` \u306F\u5909\u66F4\u306A\u3057\u5224\u5B9A\u3067\u3057\u305F\u304C\u3001Reference \u2194 Prod \u306E\u6BD4\u8F03\u3067\u5DEE\u5206\u3092\u691C\u51FA\u3057\u307E\u3057\u305F\u3002",
7526
+ "> `runa db preview production --profile compare-only` \u306F\u5909\u66F4\u306A\u3057\u5224\u5B9A\u3067\u3057\u305F\u304C\u3001Reference \u2194 Prod \u306E\u6BD4\u8F03\u3067\u5DEE\u5206\u3092\u691C\u51FA\u3057\u307E\u3057\u305F\u3002",
7688
7527
  "> \u30B9\u30AD\u30FC\u30DE\u30DE\u30C8\u30EA\u30C3\u30AF\u30B9\u306E `Prod semantic diff` \u3068\u8A73\u7D30\u30ED\u30B0\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
7689
7528
  ""
7690
7529
  ];
@@ -7713,6 +7552,10 @@ function generateKnownDriftOnlySection(schemaStats, expectedDrift) {
7713
7552
  }
7714
7553
  function generateProductionPreviewSection(phase, prodPreview, schemaDrift, schemaStats, expectedDrift) {
7715
7554
  const signals = getProductionSchemaSignals(prodPreview, schemaStats, expectedDrift);
7555
+ const deferredSection = getDeferredProductionPreviewSection(phase, prodPreview);
7556
+ if (deferredSection.length > 0) {
7557
+ return deferredSection;
7558
+ }
7716
7559
  if (!prodPreview?.executed) {
7717
7560
  if (schemaDrift?.gitDiff?.filesChanged?.length) {
7718
7561
  return [
@@ -7736,19 +7579,34 @@ function generateProductionPreviewSection(phase, prodPreview, schemaDrift, schem
7736
7579
  }
7737
7580
  return ["**\u672C\u756A\u30D7\u30EC\u30D3\u30E5\u30FC**: \u2705 \u672C\u756A\u306B\u9069\u7528\u3059\u308B\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u306F\u3042\u308A\u307E\u305B\u3093", ""];
7738
7581
  }
7739
- function shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges2) {
7740
- if (hasSchemaChanges2) return true;
7582
+ function getDeferredProductionPreviewSection(phase, prodPreview) {
7583
+ if (prodPreview?.executed) return [];
7584
+ switch (phase) {
7585
+ case "test":
7586
+ return [
7587
+ "**\u672C\u756A\u30D7\u30EC\u30D3\u30E5\u30FC**: \u23ED\uFE0F phase=test \u306E\u305F\u3081\u30B9\u30AD\u30C3\u30D7",
7588
+ "",
7589
+ "> workflow-prepared DB \u3092\u518D\u5229\u7528\u3059\u308B\u8EFD\u91CF\u30D5\u30A7\u30FC\u30BA\u306E\u305F\u3081\u3001\u672C\u756A\u6BD4\u8F03\u306F\u5B9F\u884C\u3057\u3066\u3044\u307E\u305B\u3093\u3002",
7590
+ ""
7591
+ ];
7592
+ default:
7593
+ return [];
7594
+ }
7595
+ }
7596
+ function shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges) {
7597
+ if (hasSchemaChanges) return true;
7741
7598
  if (productionPreview?.error) return true;
7742
7599
  if (!productionPreview?.executed && schemaDrift?.gitDiff?.filesChanged?.length) return true;
7743
7600
  return false;
7744
7601
  }
7745
7602
  function generateDeploySection(exitCode, layerResults, phase, gitBranchName, productionPreview, schemaDrift, schemaStats, expectedDrift, env) {
7746
7603
  if (!checkIfDeployable(exitCode, layerResults)) return [];
7604
+ if (phase === "test") return [];
7747
7605
  const deployWorkflowUrl = env.repository ? `${env.serverUrl}/${env.repository}/actions/workflows/deploy-db.yml` : null;
7748
7606
  const signals = getProductionSchemaSignals(productionPreview, schemaStats, expectedDrift);
7749
- const hasSchemaChanges2 = signals.requiresDeploy;
7750
- const headerEmoji = hasSchemaChanges2 ? "\u{1F6A8}" : "\u{1F680}";
7751
- const headerText = hasSchemaChanges2 ? "\u672C\u756A\u30C7\u30D7\u30ED\u30A4 (\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u3042\u308A!)" : "\u672C\u756A\u30C7\u30D7\u30ED\u30A4";
7607
+ const hasSchemaChanges = signals.requiresDeploy;
7608
+ const headerEmoji = hasSchemaChanges ? "\u{1F6A8}" : "\u{1F680}";
7609
+ const headerText = hasSchemaChanges ? "\u672C\u756A\u30C7\u30D7\u30ED\u30A4 (\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u3042\u308A!)" : "\u672C\u756A\u30C7\u30D7\u30ED\u30A4";
7752
7610
  const lines = ["---", "", `### ${headerEmoji} ${headerText}`, ""];
7753
7611
  if (exitCode !== 0)
7754
7612
  lines.push("> **\u6CE8**: Layer 4 (E2E) \u306F\u5931\u6557\u3057\u307E\u3057\u305F\u304C\u3001\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u306F\u30C7\u30D7\u30ED\u30A4\u53EF\u80FD\u3067\u3059\u3002", "");
@@ -7761,9 +7619,9 @@ function generateDeploySection(exitCode, layerResults, phase, gitBranchName, pro
7761
7619
  expectedDrift
7762
7620
  )
7763
7621
  );
7764
- const showButton = shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges2);
7622
+ const showButton = shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges);
7765
7623
  if (deployWorkflowUrl && showButton) {
7766
- const buttonText = hasSchemaChanges2 ? "\u26A1 \u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u3092\u672C\u756A\u306B\u30C7\u30D7\u30ED\u30A4" : "\u25B6\uFE0F \u672C\u756A\u306B\u30C7\u30D7\u30ED\u30A4";
7624
+ const buttonText = hasSchemaChanges ? "\u26A1 \u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u3092\u672C\u756A\u306B\u30C7\u30D7\u30ED\u30A4" : "\u25B6\uFE0F \u672C\u756A\u306B\u30C7\u30D7\u30ED\u30A4";
7767
7625
  lines.push(
7768
7626
  `**\u30C7\u30D7\u30ED\u30A4\u30D6\u30E9\u30F3\u30C1**: \`${gitBranchName}\``,
7769
7627
  "",
@@ -7778,23 +7636,32 @@ function generateDeploySection(exitCode, layerResults, phase, gitBranchName, pro
7778
7636
  function generateObservabilitySectionBody(input) {
7779
7637
  const env = getGitHubEnv();
7780
7638
  const gitBranchName = input.prContext?.headBranch ?? input.branchName ?? "unknown";
7639
+ const deploySection = generateDeploySection(
7640
+ input.exitCode,
7641
+ input.layerResults,
7642
+ input.phase,
7643
+ gitBranchName,
7644
+ input.productionPreview,
7645
+ input.schemaDrift,
7646
+ input.schemaStats ?? null,
7647
+ input.expectedDrift ?? [],
7648
+ env
7649
+ );
7650
+ const standalonePreviewLines = deploySection.length === 0 ? generateProductionPreviewSection(
7651
+ input.phase,
7652
+ input.productionPreview,
7653
+ input.schemaDrift,
7654
+ input.schemaStats ?? null,
7655
+ input.expectedDrift ?? []
7656
+ ) : [];
7781
7657
  const lines = [
7782
7658
  ...formatSchemaMatrix(
7783
7659
  input.schemaStats,
7784
7660
  input.schemaDrift?.gitDiff ?? null,
7785
7661
  input.expectedDrift ?? []
7786
7662
  ),
7787
- ...generateDeploySection(
7788
- input.exitCode,
7789
- input.layerResults,
7790
- input.phase,
7791
- gitBranchName,
7792
- input.productionPreview,
7793
- input.schemaDrift,
7794
- input.schemaStats ?? null,
7795
- input.expectedDrift ?? [],
7796
- env
7797
- )
7663
+ ...standalonePreviewLines,
7664
+ ...deploySection
7798
7665
  ];
7799
7666
  return lines.join("\n").trim();
7800
7667
  }
@@ -7863,11 +7730,11 @@ function generateCommentBody(input) {
7863
7730
 
7864
7731
  // src/commands/ci/machine/formatters/sections/progress-comment.ts
7865
7732
  init_esm_shims();
7866
- function calculateTotalElapsed(stepTimings2) {
7867
- if (!stepTimings2) return 0;
7868
- return Object.values(stepTimings2).reduce((sum, t) => sum + (t ?? 0), 0);
7733
+ function calculateTotalElapsed(stepTimings) {
7734
+ if (!stepTimings) return 0;
7735
+ return Object.values(stepTimings).reduce((sum, t) => sum + (t ?? 0), 0);
7869
7736
  }
7870
- function formatProgressBar(currentStep, completedSteps, failedStep, skippedSteps) {
7737
+ function formatProgressBar(_currentStep, completedSteps, failedStep, skippedSteps) {
7871
7738
  const total = CI_STEPS.length;
7872
7739
  const completedBase = completedSteps.length + skippedSteps.length;
7873
7740
  const completed = failedStep ? completedBase : completedBase + 1;
@@ -8004,7 +7871,7 @@ function generateProgressHeader(currentStep, failedStep, completedSteps, skipped
8004
7871
  );
8005
7872
  }
8006
7873
  if (totalElapsed > 0) {
8007
- lines.push(`<sub>\u23F1\uFE0F \u7D4C\u904E\u6642\u9593: ${formatDuration4(totalElapsed)}</sub>`);
7874
+ lines.push(`<sub>\u23F1\uFE0F \u7D4C\u904E\u6642\u9593: ${formatDuration3(totalElapsed)}</sub>`);
8008
7875
  }
8009
7876
  lines.push("");
8010
7877
  return lines;
@@ -8018,13 +7885,13 @@ function generateInfoLine(branchName, supabaseUrl, runUrl) {
8018
7885
  if (runUrl) items.push(`[\u{1F517} \u30EF\u30FC\u30AF\u30D5\u30ED\u30FC](${runUrl})`);
8019
7886
  return [items.join(" \xB7 "), ""];
8020
7887
  }
8021
- function generateProgressSteps(currentStep, completedSteps, failedStep, skippedSteps, stepTimings2, schemaDrift, layerResults, productionPreview) {
7888
+ function generateProgressSteps(currentStep, completedSteps, failedStep, skippedSteps, stepTimings, schemaDrift, layerResults, productionPreview) {
8022
7889
  const lines = ["### \u9032\u6357", ""];
8023
7890
  for (const { step, label, detail } of CI_STEPS) {
8024
7891
  const status = getStepStatus(step, currentStep, completedSteps, failedStep, skippedSteps);
8025
7892
  const icon = getStepIcon(status);
8026
- const timing = stepTimings2?.[step];
8027
- const timingStr = timing !== void 0 ? ` \`${formatDuration4(timing)}\`` : "";
7893
+ const timing = stepTimings?.[step];
7894
+ const timingStr = timing !== void 0 ? ` \`${formatDuration3(timing)}\`` : "";
8028
7895
  const stepDetail = getStepDetail(step, status, schemaDrift, layerResults);
8029
7896
  const runningDetail = getRunningStepDetail(step, detail, productionPreview);
8030
7897
  if (status === "running" && runningDetail) {
@@ -8058,7 +7925,7 @@ function getFixSuggestions(failedStep) {
8058
7925
  ],
8059
7926
  observability: [
8060
7927
  "production preview / schema stats \u306E\u30ED\u30B0\u3092\u78BA\u8A8D",
8061
- "\u672C\u756A DB \u63A5\u7D9A\u60C5\u5831\u3068 compare-only dry-run \u306E\u51FA\u529B\u3092\u78BA\u8A8D",
7928
+ "\u672C\u756A DB \u63A5\u7D9A\u60C5\u5831\u3068 db preview compare-only \u306E\u51FA\u529B\u3092\u78BA\u8A8D",
8062
7929
  "schema stats \u304C\u5FC5\u8981\u306A\u30B1\u30FC\u30B9\u304B expected drift \u8A2D\u5B9A\u3092\u78BA\u8A8D"
8063
7930
  ],
8064
7931
  staticChecks: [
@@ -8125,6 +7992,11 @@ function generateTestResultsSection(layerResults) {
8125
7992
  }
8126
7993
  function generateProductionSchemaSection(productionPreview, phase, deployStatus, env) {
8127
7994
  const lines = ["### \u{1F4CB} \u672C\u756A\u30B9\u30AD\u30FC\u30DE\u72B6\u6CC1", ""];
7995
+ const deferredMessage = getDeferredProductionPreviewMessage(phase, productionPreview);
7996
+ if (!deployStatus?.deployed && deferredMessage) {
7997
+ lines.push(deferredMessage, "");
7998
+ return lines;
7999
+ }
8128
8000
  const state = resolveProductionSchemaState(productionPreview, deployStatus);
8129
8001
  switch (state) {
8130
8002
  case "deployed":
@@ -8149,6 +8021,15 @@ function generateProductionSchemaSection(productionPreview, phase, deployStatus,
8149
8021
  }
8150
8022
  return lines;
8151
8023
  }
8024
+ function getDeferredProductionPreviewMessage(phase, productionPreview) {
8025
+ if (productionPreview?.executed) return null;
8026
+ switch (phase) {
8027
+ case "test":
8028
+ return "\u23ED\uFE0F _phase=test \u306E\u305F\u3081\u3001\u672C\u756A\u6BD4\u8F03\u306F\u30B9\u30AD\u30C3\u30D7\u3055\u308C\u3066\u3044\u307E\u3059_";
8029
+ default:
8030
+ return null;
8031
+ }
8032
+ }
8152
8033
  function resolveProductionSchemaState(productionPreview, deployStatus) {
8153
8034
  if (deployStatus?.deployed) return "deployed";
8154
8035
  if (!productionPreview) return "checking";
@@ -8191,13 +8072,15 @@ function appendChangesPendingSection(lines, productionPreview, env) {
8191
8072
  if (!productionPreview) return;
8192
8073
  const hazardCount = productionPreview.hazards.length;
8193
8074
  const hazardText = hazardCount > 0 ? ` (\u26A0\uFE0F ${hazardCount}\u4EF6\u306E\u30CF\u30B6\u30FC\u30C9)` : "";
8194
- lines.push(`\u26A0\uFE0F **\u5909\u66F4\u5F85\u3061**${hazardText}`, "");
8075
+ const profileText = productionPreview.profile ? ` [${productionPreview.profile}]` : "";
8076
+ lines.push(`\u26A0\uFE0F **\u5909\u66F4\u5F85\u3061**${profileText}${hazardText}`, "");
8195
8077
  if (productionPreview.planSql) {
8196
8078
  appendPlanSqlPreview(lines, productionPreview.planSql);
8197
8079
  }
8198
8080
  appendDeployLink(lines, env);
8199
8081
  }
8200
- function generateDbDeploySection(layerResults, gitBranchName, productionPreview, env) {
8082
+ function generateDbDeploySection(phase, layerResults, gitBranchName, productionPreview, env) {
8083
+ if (phase === "test") return [];
8201
8084
  if (!checkBlockingLayersPassed(layerResults)) return [];
8202
8085
  if (productionPreview?.hasChanges) return [];
8203
8086
  const deployWorkflowUrl = env.repository ? `${env.serverUrl}/${env.repository}/actions/workflows/deploy-db.yml` : null;
@@ -8230,13 +8113,13 @@ function generateProgressCommentBody(input) {
8230
8113
  productionPreview,
8231
8114
  deployStatus,
8232
8115
  error,
8233
- stepTimings: stepTimings2
8116
+ stepTimings
8234
8117
  } = input;
8235
8118
  const env = getGitHubEnv();
8236
8119
  const runUrl = env.repository && env.runId ? `${env.serverUrl}/${env.repository}/actions/runs/${env.runId}` : null;
8237
8120
  const effectiveBranchName = branchName ?? input.prContext?.headBranch ?? "unknown";
8238
8121
  const supabaseUrl = supabase?.supabaseUrl ?? "";
8239
- const totalElapsed = calculateTotalElapsed(stepTimings2);
8122
+ const totalElapsed = calculateTotalElapsed(stepTimings);
8240
8123
  const gitBranchName = input.prContext?.headBranch ?? branchName ?? "unknown";
8241
8124
  const lines = [
8242
8125
  ...generateProgressHeader(
@@ -8252,7 +8135,7 @@ function generateProgressCommentBody(input) {
8252
8135
  completedSteps,
8253
8136
  failedStep,
8254
8137
  input.skippedSteps ?? [],
8255
- stepTimings2,
8138
+ stepTimings,
8256
8139
  schemaDrift,
8257
8140
  input.layerResults,
8258
8141
  productionPreview
@@ -8260,7 +8143,13 @@ function generateProgressCommentBody(input) {
8260
8143
  ...generateErrorSection(error ?? void 0, failedStep),
8261
8144
  ...generateTestResultsSection(input.layerResults),
8262
8145
  ...generateProductionSchemaSection(productionPreview, input.phase, deployStatus, env),
8263
- ...generateDbDeploySection(input.layerResults, gitBranchName, productionPreview, env)
8146
+ ...generateDbDeploySection(
8147
+ input.phase,
8148
+ input.layerResults,
8149
+ gitBranchName,
8150
+ productionPreview,
8151
+ env
8152
+ )
8264
8153
  ];
8265
8154
  const jst = new Date(Date.now() + 9 * 60 * 60 * 1e3);
8266
8155
  const now = jst.toISOString().replace("T", " ").substring(0, 19);
@@ -8269,13 +8158,25 @@ function generateProgressCommentBody(input) {
8269
8158
  }
8270
8159
 
8271
8160
  // src/commands/ci/machine/formatters/github-comment.ts
8161
+ function getSkippedStepsForPhase(phase) {
8162
+ switch (phase) {
8163
+ case "test":
8164
+ return ["syncSchema", "applySeeds", "observability"];
8165
+ case "blocking":
8166
+ return ["observability"];
8167
+ case "observability":
8168
+ return ["postSeedChecks", "staticChecks", "build", "runTests"];
8169
+ default:
8170
+ return [];
8171
+ }
8172
+ }
8272
8173
  function createCommentInput(context, options) {
8273
8174
  const skippedLayerNumbers = Object.keys(context.layerSkipReasons || {}).map(Number);
8274
8175
  const originalSelectedLayers = [
8275
8176
  .../* @__PURE__ */ new Set([...skippedLayerNumbers, ...context.selectedLayers])
8276
8177
  ].sort((a, b) => a - b);
8277
8178
  return {
8278
- phase: context.input.phase ?? "all",
8179
+ phase: context.phase,
8279
8180
  exitCode: context.exitCode,
8280
8181
  branchName: context.branchName,
8281
8182
  prContext: context.prContext,
@@ -8291,9 +8192,9 @@ function createCommentInput(context, options) {
8291
8192
  originalSelectedLayers
8292
8193
  };
8293
8194
  }
8294
- function createProgressCommentInput(context, currentStep, completedSteps, failedStep = null, stepTimings2) {
8295
- const phase = context.input.phase ?? "all";
8296
- const skippedSteps = phase === "test" ? ["syncSchema", "applySeeds", "observability"] : [];
8195
+ function createProgressCommentInput(context, currentStep, completedSteps, failedStep = null, stepTimings) {
8196
+ const phase = context.phase;
8197
+ const skippedSteps = getSkippedStepsForPhase(phase);
8297
8198
  return {
8298
8199
  phase,
8299
8200
  currentStep,
@@ -8307,7 +8208,7 @@ function createProgressCommentInput(context, currentStep, completedSteps, failed
8307
8208
  layerResults: context.layerResults,
8308
8209
  productionPreview: context.productionPreview,
8309
8210
  error: context.error,
8310
- stepTimings: stepTimings2
8211
+ stepTimings
8311
8212
  };
8312
8213
  }
8313
8214
 
@@ -8497,7 +8398,12 @@ function createSummaryInput(context) {
8497
8398
  endedAt: now,
8498
8399
  durationMs: Date.now() - context.startTime,
8499
8400
  repoKind: context.repoKind,
8500
- detected: {},
8401
+ detected: {
8402
+ ciProfile: context.input.internalProfile ?? "default",
8403
+ requestedLayers: context.input.layers ?? [1, 2, 3, 4],
8404
+ effectiveLayers: context.selectedLayers,
8405
+ phase: context.phase
8406
+ },
8501
8407
  diagnostics: context.diagnostics,
8502
8408
  steps: {},
8503
8409
  layers: formatLayerSummary(context.layerResults),
@@ -8634,6 +8540,12 @@ function createStaticChecksInput(context) {
8634
8540
  tmpDir: assertTmpDir(context)
8635
8541
  };
8636
8542
  }
8543
+ function createLayerContentInput(context) {
8544
+ return {
8545
+ repoRoot: assertRepoRoot(context),
8546
+ selectedLayers: context.selectedLayers
8547
+ };
8548
+ }
8637
8549
  function createBuildAndPlaywrightInput(context) {
8638
8550
  return {
8639
8551
  repoRoot: assertRepoRoot(context),
@@ -8662,8 +8574,7 @@ function createCapabilitiesInput(context) {
8662
8574
  repoRoot: assertRepoRoot(context),
8663
8575
  tmpDir: assertTmpDir(context),
8664
8576
  databaseUrlApp: getDatabaseUrlForRuntime(context),
8665
- baseUrl: context.baseUrl ?? `http://localhost:${context.app?.port ?? 3e3}`,
8666
- selectedLayers: context.selectedLayers
8577
+ baseUrl: context.appStarted ? context.baseUrl ?? `http://localhost:${context.app?.port ?? 3e3}` : null
8667
8578
  };
8668
8579
  }
8669
8580
  function createRunCoreTestsInput(context) {
@@ -8687,19 +8598,106 @@ function deriveCoreLayerResults(output) {
8687
8598
  return convertLayerResults(results);
8688
8599
  }
8689
8600
 
8601
+ // src/commands/ci/machine/selectors.ts
8602
+ init_esm_shims();
8603
+ function getStateName(snapshot) {
8604
+ return getSnapshotStateName(snapshot);
8605
+ }
8606
+ function isComplete(snapshot) {
8607
+ return isSnapshotComplete(snapshot);
8608
+ }
8609
+
8690
8610
  // src/commands/ci/machine/machine.ts
8691
- var ciMachine = setup({
8692
- types: {},
8693
- actors: {
8694
- // Setup
8695
- localSetup: localSetupActor,
8696
- prLocalSetup: prLocalSetupActor,
8697
- // DB
8698
- resetDb: resetDbActor,
8699
- pullProduction: pullProductionActor,
8700
- syncSchema: syncSchemaActor,
8701
- applySeeds: applySeedsActor,
8702
- productionPreview: productionPreviewActor,
8611
+ var FLAT_EXECUTION_META = {
8612
+ setupRoles: {
8613
+ e2e: {
8614
+ observable: "log",
8615
+ assertions: [{ type: "log", contains: "Setting up database roles" }]
8616
+ }
8617
+ },
8618
+ staticChecks: {
8619
+ e2e: {
8620
+ observable: "log",
8621
+ assertions: [{ type: "log", contains: "Running static checks" }]
8622
+ }
8623
+ },
8624
+ layerContent: {
8625
+ e2e: {
8626
+ observable: "log",
8627
+ assertions: [{ type: "log", contains: "Detecting runnable layers" }]
8628
+ }
8629
+ },
8630
+ buildAndPlaywright: {
8631
+ e2e: {
8632
+ observable: "log",
8633
+ assertions: [
8634
+ { type: "log", contains: "Building app" },
8635
+ { type: "log", contains: "Installing Playwright" }
8636
+ ]
8637
+ }
8638
+ },
8639
+ appStart: {
8640
+ e2e: {
8641
+ observable: "log",
8642
+ assertions: [{ type: "log", contains: "Starting app" }]
8643
+ }
8644
+ },
8645
+ capabilities: {
8646
+ e2e: {
8647
+ observable: "log",
8648
+ assertions: [{ type: "log", contains: "Detecting capabilities" }]
8649
+ }
8650
+ },
8651
+ runCoreTests: {
8652
+ e2e: {
8653
+ observable: "log",
8654
+ assertions: [{ type: "log", contains: "Running core tests" }]
8655
+ }
8656
+ }
8657
+ };
8658
+ function getSetupRolesApplied(output) {
8659
+ return output.skipped !== true;
8660
+ }
8661
+ function mergeSetupRolesStepOverrides(context, output, stepId) {
8662
+ if (output.skipped !== true) {
8663
+ return context.stepOverrides;
8664
+ }
8665
+ return {
8666
+ ...context.stepOverrides,
8667
+ [stepId]: {
8668
+ status: "skipped",
8669
+ reason: output.skipReason ?? "Skipped because db:setup-roles is unavailable"
8670
+ }
8671
+ };
8672
+ }
8673
+ function getRunnableLayers(context, layerContent) {
8674
+ return filterRunnableLayers(context.selectedLayers, layerContent);
8675
+ }
8676
+ function getMergedLayerSkipReasons(context, layerContent) {
8677
+ return {
8678
+ ...context.layerSkipReasons,
8679
+ ...extractLayerSkipReasons(layerContent)
8680
+ };
8681
+ }
8682
+ function getDetectedCapabilities(output) {
8683
+ return output.detected?.capabilities ?? [];
8684
+ }
8685
+ function getCoreSuccessLayerResults(context, output, strategy) {
8686
+ const nextResults = convertLayerResults(output.results ?? []);
8687
+ return strategy === "merge" ? mergeLayerResults(context.layerResults, nextResults) : nextResults;
8688
+ }
8689
+ var ciMachine = setup({
8690
+ types: {},
8691
+ actors: {
8692
+ // Setup
8693
+ localSetup: localSetupActor,
8694
+ prLocalSetup: prLocalSetupActor,
8695
+ // DB
8696
+ resetDb: resetDbActor,
8697
+ pullProduction: pullProductionActor,
8698
+ syncSchema: syncSchemaActor,
8699
+ applySeeds: applySeedsActor,
8700
+ productionPreview: productionPreviewActor,
8703
8701
  collectSchemaStats: collectSchemaStatsActor,
8704
8702
  setupRoles: setupRolesActor,
8705
8703
  installPgTap: installPgTapActor,
@@ -8710,6 +8708,7 @@ var ciMachine = setup({
8710
8708
  playwrightInstall: playwrightInstallActor,
8711
8709
  buildAndPlaywright: buildAndPlaywrightActor,
8712
8710
  // Test
8711
+ layerContent: layerContentActor,
8713
8712
  capabilities: capabilitiesActor,
8714
8713
  runLayers: runLayersActor,
8715
8714
  // Finalize
@@ -8717,7 +8716,7 @@ var ciMachine = setup({
8717
8716
  },
8718
8717
  guards: {
8719
8718
  isCiLocalMode: ({ context }) => isCiLocalMode(context),
8720
- isCiPrLocalMode: ({ context }) => isCiPrLocalMode(context),
8719
+ isCiPrLocalMode: ({ context }) => isCiPrMode(context),
8721
8720
  isCiPrMode: ({ context }) => isCiPrMode(context),
8722
8721
  shouldResetDb: ({ context }) => shouldResetDb(context),
8723
8722
  shouldPullProduction: ({ context }) => shouldPullProduction(context),
@@ -8728,6 +8727,8 @@ var ciMachine = setup({
8728
8727
  shouldInstallPgTap: ({ context }) => shouldInstallPgTap(context),
8729
8728
  shouldSetupRoles: ({ context }) => shouldSetupRoles(context),
8730
8729
  shouldPostGitHubComment: ({ context }) => shouldPostGitHubComment(context),
8730
+ shouldRunPrExecutionPhase: ({ context }) => shouldRunPrExecutionPhase(context),
8731
+ shouldRunPrObservabilityPhase: ({ context }) => shouldRunPrObservabilityPhase(context),
8731
8732
  isDryRun: ({ context }) => isDryRun(context),
8732
8733
  hasError: ({ context }) => hasError(context),
8733
8734
  allTestsPassed: ({ context }) => allTestsPassed(context)
@@ -9062,8 +9063,14 @@ var ciMachine = setup({
9062
9063
  },
9063
9064
  states: {
9064
9065
  execution: {
9065
- initial: "setupRoles",
9066
+ initial: "gate",
9066
9067
  states: {
9068
+ gate: {
9069
+ always: [
9070
+ { guard: "shouldRunPrExecutionPhase", target: "setupRoles" },
9071
+ { target: "done" }
9072
+ ]
9073
+ },
9067
9074
  setupRoles: {
9068
9075
  invoke: {
9069
9076
  src: "setupRoles",
@@ -9071,16 +9078,12 @@ var ciMachine = setup({
9071
9078
  onDone: {
9072
9079
  target: "staticChecks",
9073
9080
  actions: assign({
9074
- rolesSetup: ({ event }) => event.output.skipped !== true,
9075
- stepOverrides: ({ context, event }) => ({
9076
- ...context.stepOverrides,
9077
- ...event.output.skipped ? {
9078
- "postSeedPr.execution.setupRoles": {
9079
- status: "skipped",
9080
- reason: event.output.skipReason ?? "Skipped because db:setup-roles is unavailable"
9081
- }
9082
- } : {}
9083
- }),
9081
+ rolesSetup: ({ event }) => getSetupRolesApplied(event.output),
9082
+ stepOverrides: ({ context, event }) => mergeSetupRolesStepOverrides(
9083
+ context,
9084
+ event.output,
9085
+ "postSeedPr.execution.setupRoles"
9086
+ ),
9084
9087
  supabase: ({ context, event }) => mergeSetupRolesSupabase(context, event.output.appDatabaseUrl)
9085
9088
  })
9086
9089
  },
@@ -9091,7 +9094,7 @@ var ciMachine = setup({
9091
9094
  }
9092
9095
  },
9093
9096
  staticChecks: {
9094
- always: [{ guard: "shouldSkipStaticChecks", target: "buildAndPlaywright" }],
9097
+ always: [{ guard: "shouldSkipStaticChecks", target: "layerContent" }],
9095
9098
  invoke: {
9096
9099
  src: "staticChecks",
9097
9100
  input: ({ context }) => createStaticChecksInput(context),
@@ -9105,7 +9108,7 @@ var ciMachine = setup({
9105
9108
  })
9106
9109
  },
9107
9110
  {
9108
- target: "buildAndPlaywright",
9111
+ target: "layerContent",
9109
9112
  actions: assign({ staticChecksPassed: true })
9110
9113
  }
9111
9114
  ],
@@ -9118,6 +9121,22 @@ var ciMachine = setup({
9118
9121
  }
9119
9122
  }
9120
9123
  },
9124
+ layerContent: {
9125
+ invoke: {
9126
+ src: "layerContent",
9127
+ input: ({ context }) => createLayerContentInput(context),
9128
+ onDone: {
9129
+ target: "buildAndPlaywright",
9130
+ actions: assign({
9131
+ selectedLayers: ({ context, event }) => getRunnableLayers(context, event.output.layerContent),
9132
+ layerSkipReasons: ({ context, event }) => getMergedLayerSkipReasons(context, event.output.layerContent)
9133
+ })
9134
+ },
9135
+ onError: {
9136
+ target: "buildAndPlaywright"
9137
+ }
9138
+ }
9139
+ },
9121
9140
  buildAndPlaywright: {
9122
9141
  always: [{ guard: "shouldSkipBuild", target: "appStart" }],
9123
9142
  invoke: {
@@ -9194,9 +9213,7 @@ var ciMachine = setup({
9194
9213
  onDone: {
9195
9214
  target: "runCoreTests",
9196
9215
  actions: assign({
9197
- capabilities: ({ event }) => event.output.detected?.capabilities ?? [],
9198
- selectedLayers: ({ context, event }) => filterRunnableLayers(context.selectedLayers, event.output.layerContent),
9199
- layerSkipReasons: ({ event }) => extractLayerSkipReasons(event.output.layerContent)
9216
+ capabilities: ({ event }) => getDetectedCapabilities(event.output)
9200
9217
  })
9201
9218
  },
9202
9219
  onError: {
@@ -9224,10 +9241,7 @@ var ciMachine = setup({
9224
9241
  target: "coreTestsComplete",
9225
9242
  actions: assign({
9226
9243
  testsRun: true,
9227
- layerResults: ({ context, event }) => {
9228
- const nextResults = convertLayerResults(event.output.results);
9229
- return mergeLayerResults(context.layerResults, nextResults);
9230
- }
9244
+ layerResults: ({ context, event }) => getCoreSuccessLayerResults(context, event.output, "merge")
9231
9245
  })
9232
9246
  }
9233
9247
  ],
@@ -9357,8 +9371,14 @@ var ciMachine = setup({
9357
9371
  }
9358
9372
  },
9359
9373
  observability: {
9360
- initial: "productionPreview",
9374
+ initial: "gate",
9361
9375
  states: {
9376
+ gate: {
9377
+ always: [
9378
+ { guard: "shouldRunPrObservabilityPhase", target: "productionPreview" },
9379
+ { target: "done" }
9380
+ ]
9381
+ },
9362
9382
  productionPreview: {
9363
9383
  always: [
9364
9384
  {
@@ -9558,19 +9578,14 @@ var ciMachine = setup({
9558
9578
  // Setup Roles (ci-pr modes only)
9559
9579
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9560
9580
  setupRoles: {
9561
- meta: {
9562
- e2e: {
9563
- observable: "log",
9564
- assertions: [{ type: "log", contains: "Setting up database roles" }]
9565
- }
9566
- },
9581
+ meta: FLAT_EXECUTION_META.setupRoles,
9567
9582
  invoke: {
9568
9583
  src: "setupRoles",
9569
9584
  input: ({ context }) => createSetupRolesInput(context),
9570
9585
  onDone: {
9571
9586
  target: "staticChecks",
9572
9587
  actions: assign({
9573
- rolesSetup: true,
9588
+ rolesSetup: ({ event }) => getSetupRolesApplied(event.output),
9574
9589
  supabase: ({ context, event }) => mergeSetupRolesSupabase(context, event.output.appDatabaseUrl)
9575
9590
  })
9576
9591
  },
@@ -9585,13 +9600,8 @@ var ciMachine = setup({
9585
9600
  // Static Checks (ci-pr modes only)
9586
9601
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9587
9602
  staticChecks: {
9588
- meta: {
9589
- e2e: {
9590
- observable: "log",
9591
- assertions: [{ type: "log", contains: "Running static checks" }]
9592
- }
9593
- },
9594
- always: [{ guard: "shouldSkipStaticChecks", target: "buildAndPlaywright" }],
9603
+ meta: FLAT_EXECUTION_META.staticChecks,
9604
+ always: [{ guard: "shouldSkipStaticChecks", target: "layerContent" }],
9595
9605
  invoke: {
9596
9606
  src: "staticChecks",
9597
9607
  input: ({ context }) => createStaticChecksInput(context),
@@ -9605,7 +9615,7 @@ var ciMachine = setup({
9605
9615
  })
9606
9616
  },
9607
9617
  {
9608
- target: "buildAndPlaywright",
9618
+ target: "layerContent",
9609
9619
  actions: assign({ staticChecksPassed: true })
9610
9620
  }
9611
9621
  ],
@@ -9618,20 +9628,29 @@ var ciMachine = setup({
9618
9628
  }
9619
9629
  }
9620
9630
  },
9631
+ layerContent: {
9632
+ meta: FLAT_EXECUTION_META.layerContent,
9633
+ invoke: {
9634
+ src: "layerContent",
9635
+ input: ({ context }) => createLayerContentInput(context),
9636
+ onDone: {
9637
+ target: "buildAndPlaywright",
9638
+ actions: assign({
9639
+ selectedLayers: ({ context, event }) => getRunnableLayers(context, event.output.layerContent),
9640
+ layerSkipReasons: ({ context, event }) => getMergedLayerSkipReasons(context, event.output.layerContent)
9641
+ })
9642
+ },
9643
+ onError: {
9644
+ target: "buildAndPlaywright"
9645
+ }
9646
+ }
9647
+ },
9621
9648
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9622
9649
  // Build and Playwright Install (ci-pr modes only, parallel)
9623
9650
  // Optimization: Running build and playwright install concurrently saves ~30-60s
9624
9651
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9625
9652
  buildAndPlaywright: {
9626
- meta: {
9627
- e2e: {
9628
- observable: "log",
9629
- assertions: [
9630
- { type: "log", contains: "Building app" },
9631
- { type: "log", contains: "Installing Playwright" }
9632
- ]
9633
- }
9634
- },
9653
+ meta: FLAT_EXECUTION_META.buildAndPlaywright,
9635
9654
  always: [{ guard: "shouldSkipBuild", target: "appStart" }],
9636
9655
  invoke: {
9637
9656
  src: "buildAndPlaywright",
@@ -9672,12 +9691,7 @@ var ciMachine = setup({
9672
9691
  // App Start (ci-pr modes only)
9673
9692
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9674
9693
  appStart: {
9675
- meta: {
9676
- e2e: {
9677
- observable: "log",
9678
- assertions: [{ type: "log", contains: "Starting app" }]
9679
- }
9680
- },
9694
+ meta: FLAT_EXECUTION_META.appStart,
9681
9695
  always: [{ guard: "shouldSkipAppStart", target: "capabilities" }],
9682
9696
  invoke: {
9683
9697
  src: "appStart",
@@ -9712,26 +9726,16 @@ var ciMachine = setup({
9712
9726
  },
9713
9727
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9714
9728
  // Capability Detection (ci-pr modes only)
9715
- // Also detects layer content to filter layers without testable content
9716
9729
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9717
9730
  capabilities: {
9718
- meta: {
9719
- e2e: {
9720
- observable: "log",
9721
- assertions: [{ type: "log", contains: "Detecting capabilities" }]
9722
- }
9723
- },
9731
+ meta: FLAT_EXECUTION_META.capabilities,
9724
9732
  invoke: {
9725
9733
  src: "capabilities",
9726
9734
  input: ({ context }) => createCapabilitiesInput(context),
9727
9735
  onDone: {
9728
9736
  target: "runCoreTests",
9729
9737
  actions: assign({
9730
- capabilities: ({ event }) => event.output.detected?.capabilities ?? [],
9731
- // Filter to runnable layers only (those with content)
9732
- selectedLayers: ({ context, event }) => filterRunnableLayers(context.selectedLayers, event.output.layerContent),
9733
- // Store skip reasons for PR comment
9734
- layerSkipReasons: ({ event }) => extractLayerSkipReasons(event.output.layerContent)
9738
+ capabilities: ({ event }) => getDetectedCapabilities(event.output)
9735
9739
  })
9736
9740
  },
9737
9741
  onError: {
@@ -9747,12 +9751,7 @@ var ciMachine = setup({
9747
9751
  // ci-pr: Only core layers (1,2,3) - E2E runs in e2ePhase
9748
9752
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9749
9753
  runCoreTests: {
9750
- meta: {
9751
- e2e: {
9752
- observable: "log",
9753
- assertions: [{ type: "log", contains: "Running core tests" }]
9754
- }
9755
- },
9754
+ meta: FLAT_EXECUTION_META.runCoreTests,
9756
9755
  invoke: {
9757
9756
  src: "runLayers",
9758
9757
  input: ({ context }) => createRunCoreTestsInput(context),
@@ -9774,7 +9773,7 @@ var ciMachine = setup({
9774
9773
  target: "coreTestsComplete",
9775
9774
  actions: assign({
9776
9775
  testsRun: true,
9777
- layerResults: ({ event }) => convertLayerResults(event.output.results)
9776
+ layerResults: ({ context, event }) => getCoreSuccessLayerResults(context, event.output, "replace")
9778
9777
  })
9779
9778
  }
9780
9779
  ],
@@ -9997,18 +9996,94 @@ var ciMachine = setup({
9997
9996
  },
9998
9997
  output: ({ context }) => createOutput(context)
9999
9998
  });
10000
- function getStateName(snapshot) {
10001
- return getSnapshotStateName(snapshot);
10002
- }
10003
- function isComplete(snapshot) {
10004
- return isSnapshotComplete(snapshot);
10005
- }
10006
9999
 
10007
10000
  // src/commands/ci/machine/commands/machine-runner.ts
10008
10001
  init_esm_shims();
10009
10002
 
10010
- // src/commands/ci/machine/commands/step-telemetry.ts
10003
+ // src/commands/ci/machine/commands/ci-step-registry.ts
10011
10004
  init_esm_shims();
10005
+ var PROGRESS_STATE_TO_STEP = {
10006
+ setup: "setup",
10007
+ "setup.resolving": "setup",
10008
+ "setup.local": "setup",
10009
+ "setup.prLocal": "setup",
10010
+ "setup.complete": "setup",
10011
+ dbReset: "syncSchema",
10012
+ pullProduction: "syncSchema",
10013
+ syncSchema: "syncSchema",
10014
+ applySeeds: "applySeeds",
10015
+ productionPreview: "observability",
10016
+ collectSchemaStats: "observability",
10017
+ postSeedPr: "postSeedChecks",
10018
+ "postSeedPr.execution": "postSeedChecks",
10019
+ "postSeedPr.observability": "observability",
10020
+ "postSeedPr.execution.setupRoles": "postSeedChecks",
10021
+ "postSeedPr.observability.productionPreview": "observability",
10022
+ "postSeedPr.observability.collectSchemaStats": "observability",
10023
+ "postSeedPr.execution.staticChecks": "staticChecks",
10024
+ "postSeedPr.execution.layerContent": "build",
10025
+ "postSeedPr.execution.buildAndPlaywright": "build",
10026
+ "postSeedPr.execution.appStart": "build",
10027
+ "postSeedPr.execution.capabilities": "build",
10028
+ "postSeedPr.execution.runCoreTests": "runTests",
10029
+ "postSeedPr.execution.coreTestsComplete": "runTests",
10030
+ "postSeedPr.execution.coreTestsFailed": "runTests",
10031
+ "postSeedPr.execution.e2ePhase": "runTests",
10032
+ "postSeedPr.execution.done": "runTests",
10033
+ decidePath: "postSeedChecks",
10034
+ installPgTap: "applySeeds",
10035
+ setupRoles: "postSeedChecks",
10036
+ staticChecks: "staticChecks",
10037
+ layerContent: "build",
10038
+ buildAndPlaywright: "build",
10039
+ appStart: "build",
10040
+ capabilities: "build",
10041
+ runTests: "runTests",
10042
+ testsComplete: "runTests",
10043
+ testsFailed: "runTests",
10044
+ runCoreTests: "runTests",
10045
+ coreTestsComplete: "runTests",
10046
+ coreTestsFailed: "runTests",
10047
+ e2ePhase: "runTests",
10048
+ "e2ePhase.intermediateComment": "runTests",
10049
+ "e2ePhase.e2eTests": "runTests",
10050
+ finalize: "finalize",
10051
+ "finalize.writeSummary": "finalize",
10052
+ "finalize.postComment": "finalize",
10053
+ "finalize.complete": "finalize",
10054
+ done: "finalize",
10055
+ failed: "finalize"
10056
+ };
10057
+ var PROGRESS_STEP_ORDER = [
10058
+ "setup",
10059
+ "syncSchema",
10060
+ "applySeeds",
10061
+ "postSeedChecks",
10062
+ "observability",
10063
+ "staticChecks",
10064
+ "build",
10065
+ "runTests",
10066
+ "finalize"
10067
+ ];
10068
+ var OBSERVABILITY_STEP_IDS = [
10069
+ "postSeedPr.observability.productionPreview",
10070
+ "postSeedPr.observability.collectSchemaStats"
10071
+ ];
10072
+ var EXECUTION_STEP_IDS = [
10073
+ "postSeedPr.execution.setupRoles",
10074
+ "postSeedPr.execution.staticChecks",
10075
+ "postSeedPr.execution.layerContent",
10076
+ "postSeedPr.execution.buildAndPlaywright",
10077
+ "postSeedPr.execution.appStart",
10078
+ "postSeedPr.execution.capabilities",
10079
+ "postSeedPr.execution.runCoreTests",
10080
+ "postSeedPr.execution.e2ePhase"
10081
+ ];
10082
+ var BUILD_PREPARATION_SUBSTEP_IDS = {
10083
+ build: "postSeedPr.execution.buildAndPlaywright.build",
10084
+ manifestGenerate: "postSeedPr.execution.buildAndPlaywright.manifestGenerate",
10085
+ playwrightInstall: "postSeedPr.execution.buildAndPlaywright.playwrightInstall"
10086
+ };
10012
10087
  var STEP_METADATA = {
10013
10088
  "setup.local": {
10014
10089
  order: 10,
@@ -10050,74 +10125,80 @@ var STEP_METADATA = {
10050
10125
  optional: true,
10051
10126
  parentStep: "applySeeds"
10052
10127
  },
10053
- "postSeedPr.observability.productionPreview": {
10128
+ [OBSERVABILITY_STEP_IDS[0]]: {
10054
10129
  order: 50,
10055
10130
  title: "Run production preview",
10056
10131
  phase: "observability",
10057
10132
  optional: true,
10058
10133
  parentStep: "applySeeds"
10059
10134
  },
10060
- "postSeedPr.observability.collectSchemaStats": {
10135
+ [OBSERVABILITY_STEP_IDS[1]]: {
10061
10136
  order: 60,
10062
10137
  title: "Collect schema statistics",
10063
10138
  phase: "observability",
10064
10139
  optional: true,
10065
10140
  parentStep: "applySeeds"
10066
10141
  },
10067
- "postSeedPr.execution.setupRoles": {
10142
+ [EXECUTION_STEP_IDS[0]]: {
10068
10143
  order: 70,
10069
10144
  title: "Setup database roles",
10070
10145
  phase: "db",
10071
10146
  optional: true,
10072
10147
  parentStep: "applySeeds"
10073
10148
  },
10074
- "postSeedPr.execution.staticChecks": {
10149
+ [EXECUTION_STEP_IDS[1]]: {
10075
10150
  order: 80,
10076
10151
  title: "Run static checks",
10077
10152
  phase: "build"
10078
10153
  },
10079
- "postSeedPr.execution.buildAndPlaywright": {
10154
+ [EXECUTION_STEP_IDS[2]]: {
10155
+ order: 85,
10156
+ title: "Detect runnable test layers",
10157
+ phase: "build",
10158
+ optional: true
10159
+ },
10160
+ [EXECUTION_STEP_IDS[3]]: {
10080
10161
  order: 90,
10081
10162
  title: "Build app and prepare Playwright",
10082
10163
  phase: "build"
10083
10164
  },
10084
- "postSeedPr.execution.buildAndPlaywright.build": {
10165
+ [BUILD_PREPARATION_SUBSTEP_IDS.build]: {
10085
10166
  order: 91,
10086
10167
  title: "Build application",
10087
10168
  phase: "build",
10088
- parentStep: "postSeedPr.execution.buildAndPlaywright"
10169
+ parentStep: EXECUTION_STEP_IDS[3]
10089
10170
  },
10090
- "postSeedPr.execution.buildAndPlaywright.manifestGenerate": {
10171
+ [BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate]: {
10091
10172
  order: 92,
10092
10173
  title: "Generate manifest",
10093
10174
  phase: "build",
10094
10175
  optional: true,
10095
- parentStep: "postSeedPr.execution.buildAndPlaywright"
10176
+ parentStep: EXECUTION_STEP_IDS[3]
10096
10177
  },
10097
- "postSeedPr.execution.buildAndPlaywright.playwrightInstall": {
10178
+ [BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall]: {
10098
10179
  order: 93,
10099
10180
  title: "Install Playwright browsers",
10100
10181
  phase: "build",
10101
10182
  optional: true,
10102
- parentStep: "postSeedPr.execution.buildAndPlaywright"
10183
+ parentStep: EXECUTION_STEP_IDS[3]
10103
10184
  },
10104
- "postSeedPr.execution.appStart": {
10185
+ [EXECUTION_STEP_IDS[4]]: {
10105
10186
  order: 100,
10106
10187
  title: "Start application",
10107
10188
  phase: "build"
10108
10189
  },
10109
- "postSeedPr.execution.capabilities": {
10190
+ [EXECUTION_STEP_IDS[5]]: {
10110
10191
  order: 110,
10111
10192
  title: "Detect test capabilities",
10112
10193
  phase: "test",
10113
10194
  optional: true
10114
10195
  },
10115
- "postSeedPr.execution.runCoreTests": {
10196
+ [EXECUTION_STEP_IDS[6]]: {
10116
10197
  order: 120,
10117
10198
  title: "Run core test layers",
10118
10199
  phase: "test"
10119
10200
  },
10120
- "postSeedPr.execution.e2ePhase": {
10201
+ [EXECUTION_STEP_IDS[7]]: {
10121
10202
  order: 130,
10122
10203
  title: "Run E2E phase",
10123
10204
  phase: "test",
@@ -10136,10 +10217,7 @@ var STEP_METADATA = {
10136
10217
  }
10137
10218
  };
10138
10219
  var CANONICAL_STEP_IDS = Object.keys(STEP_METADATA).sort((a, b) => b.length - a.length);
10139
- function toIsoString(timestampMs) {
10140
- return new Date(timestampMs).toISOString();
10141
- }
10142
- function getMetadata(stepId) {
10220
+ function getStepMetadata(stepId) {
10143
10221
  return STEP_METADATA[stepId];
10144
10222
  }
10145
10223
  function getCanonicalStepId(statePath) {
@@ -10155,6 +10233,12 @@ function getStepOrderFromStatePath(statePath) {
10155
10233
  if (!canonical) return -1;
10156
10234
  return STEP_METADATA[canonical]?.order ?? -1;
10157
10235
  }
10236
+
10237
+ // src/commands/ci/machine/commands/step-telemetry.ts
10238
+ init_esm_shims();
10239
+ function toIsoString(timestampMs) {
10240
+ return new Date(timestampMs).toISOString();
10241
+ }
10158
10242
  function pickPrimaryStatePath(statePaths, previousStatePath) {
10159
10243
  if (statePaths.length === 0) return previousStatePath;
10160
10244
  if (statePaths.length === 1 && (statePaths[0] === "done" || statePaths[0] === "failed")) {
@@ -10171,7 +10255,7 @@ function createSkippedSummary(stepId, reason) {
10171
10255
  return createSyntheticSummary(stepId, "skipped", { reason });
10172
10256
  }
10173
10257
  function createSyntheticSummary(stepId, status, params = {}) {
10174
- const metadata = getMetadata(stepId);
10258
+ const metadata = getStepMetadata(stepId);
10175
10259
  if (!metadata) return null;
10176
10260
  const durationMs = params.startedAtMs !== void 0 && params.endedAtMs !== void 0 ? Math.max(0, params.endedAtMs - params.startedAtMs) : void 0;
10177
10261
  return [
@@ -10190,9 +10274,15 @@ function createSyntheticSummary(stepId, status, params = {}) {
10190
10274
  ];
10191
10275
  }
10192
10276
  function getBuildSkipReason(context) {
10193
- return isCiLocalMode(context) ? "Skipped in ci-local mode" : context.input.skipBuild === true ? "Skipped by --skip-build" : "Skipped by step guard";
10277
+ if (!shouldRunPrExecutionPhase(context)) {
10278
+ return getExecutionSkipReason(context);
10279
+ }
10280
+ return isCiLocalMode(context) ? "Skipped in ci-local mode" : context.input.skipBuild === true ? "Skipped by --skip-build" : !context.selectedLayers.some((layer) => layer === 3 || layer === 4) ? "Skipped because no build-dependent layers are selected" : "Skipped by step guard";
10194
10281
  }
10195
10282
  function getPlaywrightSkipReason(context) {
10283
+ if (!shouldRunPrExecutionPhase(context)) {
10284
+ return getExecutionSkipReason(context);
10285
+ }
10196
10286
  if (isCiLocalMode(context)) return "Skipped in ci-local mode";
10197
10287
  if (!context.selectedLayers.includes(4)) return "Skipped because Layer 4 is not selected";
10198
10288
  if (shouldReusePreparedPlaywright(context)) {
@@ -10203,6 +10293,18 @@ function getPlaywrightSkipReason(context) {
10203
10293
  }
10204
10294
  return "Skipped by step guard";
10205
10295
  }
10296
+ function getObservabilitySkipReason(context) {
10297
+ if (isTestPhase(context)) {
10298
+ return "Skipped in phase=test; observability DB checks are disabled";
10299
+ }
10300
+ return "Skipped by phase selection";
10301
+ }
10302
+ function getExecutionSkipReason(context) {
10303
+ if (isObservabilityPhase(context)) {
10304
+ return "Skipped in phase=observability; execution work is disabled";
10305
+ }
10306
+ return "Skipped by phase selection";
10307
+ }
10206
10308
  function getSyntheticBuildEntries(context, trackedStepIds, _stepStates, _nowMs) {
10207
10309
  const entries = [];
10208
10310
  const pushEntry = (entry) => {
@@ -10210,9 +10312,15 @@ function getSyntheticBuildEntries(context, trackedStepIds, _stepStates, _nowMs)
10210
10312
  if (trackedStepIds.has(entry[0])) return;
10211
10313
  entries.push(entry);
10212
10314
  };
10213
- const syntheticTimingParams = {};
10214
- if (shouldSkipBuild(context)) {
10215
- const reason = getBuildSkipReason(context);
10315
+ if (!shouldRunPrExecutionPhase(context)) {
10316
+ const reason = getExecutionSkipReason(context);
10317
+ pushEntry(createSkippedSummary(BUILD_PREPARATION_SUBSTEP_IDS.build, reason));
10318
+ pushEntry(createSkippedSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, reason));
10319
+ pushEntry(createSkippedSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, reason));
10320
+ return entries;
10321
+ }
10322
+ if (!shouldRunPrExecutionPhase(context)) {
10323
+ const reason = getExecutionSkipReason(context);
10216
10324
  pushEntry(createSkippedSummary("postSeedPr.execution.buildAndPlaywright.build", reason));
10217
10325
  pushEntry(
10218
10326
  createSkippedSummary("postSeedPr.execution.buildAndPlaywright.manifestGenerate", reason)
@@ -10222,106 +10330,64 @@ function getSyntheticBuildEntries(context, trackedStepIds, _stepStates, _nowMs)
10222
10330
  );
10223
10331
  return entries;
10224
10332
  }
10333
+ if (shouldSkipBuild(context)) {
10334
+ const reason = getBuildSkipReason(context);
10335
+ pushEntry(createSkippedSummary(BUILD_PREPARATION_SUBSTEP_IDS.build, reason));
10336
+ pushEntry(createSkippedSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, reason));
10337
+ pushEntry(createSkippedSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, reason));
10338
+ return entries;
10339
+ }
10225
10340
  if (context.appBuildPassed === true) {
10226
- pushEntry(
10227
- createSyntheticSummary(
10228
- "postSeedPr.execution.buildAndPlaywright.build",
10229
- "passed",
10230
- syntheticTimingParams
10231
- )
10232
- );
10341
+ pushEntry(createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.build, "passed"));
10233
10342
  } else if (context.appBuildPassed === false) {
10234
10343
  pushEntry(
10235
- createSyntheticSummary("postSeedPr.execution.buildAndPlaywright.build", "failed", {
10236
- ...syntheticTimingParams,
10344
+ createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.build, "failed", {
10237
10345
  reason: context.error ?? "App build failed"
10238
10346
  })
10239
10347
  );
10240
10348
  }
10241
10349
  if (context.appBuildPassed === true) {
10242
10350
  if (context.manifestGenerated === true) {
10243
- pushEntry(
10244
- createSyntheticSummary(
10245
- "postSeedPr.execution.buildAndPlaywright.manifestGenerate",
10246
- "passed",
10247
- syntheticTimingParams
10248
- )
10249
- );
10351
+ pushEntry(createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, "passed"));
10250
10352
  } else if (context.manifestGenerated === null) {
10251
10353
  pushEntry(
10252
- createSyntheticSummary(
10253
- "postSeedPr.execution.buildAndPlaywright.manifestGenerate",
10254
- "skipped",
10255
- {
10256
- ...syntheticTimingParams,
10257
- reason: "Skipped because manifest generation is optional for this repository"
10258
- }
10259
- )
10354
+ createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, "skipped", {
10355
+ reason: "Skipped because manifest generation is optional for this repository"
10356
+ })
10260
10357
  );
10261
10358
  } else if (context.manifestGenerated === false) {
10262
10359
  pushEntry(
10263
- createSyntheticSummary(
10264
- "postSeedPr.execution.buildAndPlaywright.manifestGenerate",
10265
- "failed",
10266
- {
10267
- ...syntheticTimingParams,
10268
- reason: "Manifest generation failed but CI continued because it is optional"
10269
- }
10270
- )
10360
+ createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, "failed", {
10361
+ reason: "Manifest generation failed but CI continued because it is optional"
10362
+ })
10271
10363
  );
10272
10364
  }
10273
10365
  } else if (context.appBuildPassed === false) {
10274
10366
  pushEntry(
10275
- createSyntheticSummary(
10276
- "postSeedPr.execution.buildAndPlaywright.manifestGenerate",
10277
- "skipped",
10278
- {
10279
- ...syntheticTimingParams,
10280
- reason: "Skipped because app build failed before manifest generation"
10281
- }
10282
- )
10367
+ createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, "skipped", {
10368
+ reason: "Skipped because app build failed before manifest generation"
10369
+ })
10283
10370
  );
10284
10371
  }
10285
10372
  if (shouldReusePreparedPlaywright(context)) {
10286
10373
  pushEntry(
10287
- createSyntheticSummary(
10288
- "postSeedPr.execution.buildAndPlaywright.playwrightInstall",
10289
- "skipped",
10290
- {
10291
- ...syntheticTimingParams,
10292
- reason: getPlaywrightSkipReason(context)
10293
- }
10294
- )
10374
+ createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, "skipped", {
10375
+ reason: getPlaywrightSkipReason(context)
10376
+ })
10295
10377
  );
10296
10378
  } else if (shouldSkipPlaywrightInstall(context)) {
10297
10379
  pushEntry(
10298
- createSyntheticSummary(
10299
- "postSeedPr.execution.buildAndPlaywright.playwrightInstall",
10300
- "skipped",
10301
- {
10302
- ...syntheticTimingParams,
10303
- reason: getPlaywrightSkipReason(context)
10304
- }
10305
- )
10380
+ createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, "skipped", {
10381
+ reason: getPlaywrightSkipReason(context)
10382
+ })
10306
10383
  );
10307
10384
  } else if (context.playwrightInstalled === true) {
10308
- pushEntry(
10309
- createSyntheticSummary(
10310
- "postSeedPr.execution.buildAndPlaywright.playwrightInstall",
10311
- "passed",
10312
- syntheticTimingParams
10313
- )
10314
- );
10385
+ pushEntry(createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, "passed"));
10315
10386
  } else if (context.playwrightInstalled === false) {
10316
10387
  pushEntry(
10317
- createSyntheticSummary(
10318
- "postSeedPr.execution.buildAndPlaywright.playwrightInstall",
10319
- "failed",
10320
- {
10321
- ...syntheticTimingParams,
10322
- reason: "Playwright browser install failed"
10323
- }
10324
- )
10388
+ createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, "failed", {
10389
+ reason: "Playwright browser install failed"
10390
+ })
10325
10391
  );
10326
10392
  }
10327
10393
  return entries;
@@ -10333,38 +10399,43 @@ function getSkippedStepEntries(context, trackedStepIds) {
10333
10399
  const entry = createSkippedSummary(stepId, reason);
10334
10400
  if (entry) skipped.push(entry);
10335
10401
  };
10402
+ if (isCiPrMode(context) && !shouldRunPrObservabilityPhase(context)) {
10403
+ const reason = getObservabilitySkipReason(context);
10404
+ for (const stepId of OBSERVABILITY_STEP_IDS) {
10405
+ pushSkipped(stepId, reason);
10406
+ }
10407
+ }
10336
10408
  if (isTestPhase(context)) {
10337
10409
  pushSkipped("syncSchema", "Skipped in phase=test; reusing prepared database");
10338
10410
  pushSkipped("applySeeds", "Skipped in phase=test; reusing prepared database");
10339
- pushSkipped(
10340
- "postSeedPr.observability.productionPreview",
10341
- "Skipped in phase=test; observability DB checks are disabled"
10342
- );
10343
- pushSkipped(
10344
- "postSeedPr.observability.collectSchemaStats",
10345
- "Skipped in phase=test; observability DB checks are disabled"
10346
- );
10347
10411
  }
10348
10412
  if (!shouldPostGitHubComment(context) || context.prContext?.prNumber === null) {
10349
10413
  const commentReason = context.input.skipGithubComment === true ? "Skipped by --skip-github-comment" : context.executionEnv !== "github-actions" ? "Skipped outside GitHub Actions" : "Skipped because PR context is unavailable";
10350
10414
  pushSkipped("initialComment", commentReason);
10351
10415
  pushSkipped("finalize.postComment", commentReason);
10352
10416
  }
10353
- if (shouldSkipStaticChecks(context)) {
10354
- const reason = isCiLocalMode(context) ? "Skipped in ci-local mode" : context.input.skipStaticChecks === true ? "Skipped by --skip-static-checks" : "Skipped by step guard";
10355
- pushSkipped("postSeedPr.execution.staticChecks", reason);
10356
- }
10357
- if (shouldSkipBuild(context)) {
10358
- pushSkipped("postSeedPr.execution.buildAndPlaywright", getBuildSkipReason(context));
10359
- }
10360
- if (shouldSkipAppStart(context)) {
10361
- const reason = isCiLocalMode(context) ? "Skipped in ci-local mode" : !context.selectedLayers.includes(4) ? "Skipped because Layer 4 is not selected" : "Skipped by step guard";
10362
- pushSkipped("postSeedPr.execution.appStart", reason);
10363
- }
10364
- if (!context.selectedLayers.includes(4)) {
10365
- pushSkipped("postSeedPr.execution.e2ePhase", "Skipped because Layer 4 is not selected");
10366
- } else if (Object.values(context.layerResults).some((result) => result.status === "failed")) {
10367
- pushSkipped("postSeedPr.execution.e2ePhase", "Skipped because blocking core tests failed");
10417
+ if (isCiPrMode(context) && !shouldRunPrExecutionPhase(context)) {
10418
+ const reason = getExecutionSkipReason(context);
10419
+ for (const stepId of EXECUTION_STEP_IDS) {
10420
+ pushSkipped(stepId, reason);
10421
+ }
10422
+ } else {
10423
+ if (shouldSkipStaticChecks(context)) {
10424
+ const reason = isCiLocalMode(context) ? "Skipped in ci-local mode" : context.input.skipStaticChecks === true ? "Skipped by --skip-static-checks" : "Skipped by step guard";
10425
+ pushSkipped(EXECUTION_STEP_IDS[1], reason);
10426
+ }
10427
+ if (shouldSkipBuild(context)) {
10428
+ pushSkipped(EXECUTION_STEP_IDS[3], getBuildSkipReason(context));
10429
+ }
10430
+ if (shouldSkipAppStart(context)) {
10431
+ const reason = isCiLocalMode(context) ? "Skipped in ci-local mode" : !context.selectedLayers.includes(4) ? "Skipped because Layer 4 is not selected" : "Skipped by step guard";
10432
+ pushSkipped(EXECUTION_STEP_IDS[4], reason);
10433
+ }
10434
+ if (!context.selectedLayers.includes(4)) {
10435
+ pushSkipped(EXECUTION_STEP_IDS[7], "Skipped because Layer 4 is not selected");
10436
+ } else if (Object.values(context.layerResults).some((result) => result.status === "failed")) {
10437
+ pushSkipped(EXECUTION_STEP_IDS[7], "Skipped because blocking core tests failed");
10438
+ }
10368
10439
  }
10369
10440
  return skipped;
10370
10441
  }
@@ -10372,7 +10443,7 @@ function createStepTelemetryTracker() {
10372
10443
  const stepStates = /* @__PURE__ */ new Map();
10373
10444
  let activeStepIds = /* @__PURE__ */ new Set();
10374
10445
  const startStep = (stepId, nowMs) => {
10375
- const metadata = getMetadata(stepId);
10446
+ const metadata = getStepMetadata(stepId);
10376
10447
  if (!metadata) return;
10377
10448
  if (stepStates.has(stepId)) return;
10378
10449
  stepStates.set(stepId, {
@@ -10411,8 +10482,7 @@ function createStepTelemetryTracker() {
10411
10482
  finishStep(stepId, "failed", nowMs);
10412
10483
  continue;
10413
10484
  }
10414
- const metadata = getMetadata(stepId);
10415
- finishStep(stepId, succeeded ? "passed" : metadata?.optional ? "killed" : "killed", nowMs);
10485
+ finishStep(stepId, succeeded ? "passed" : "killed", nowMs);
10416
10486
  }
10417
10487
  activeStepIds = /* @__PURE__ */ new Set();
10418
10488
  const trackedEntries = [...stepStates.entries()].sort((a, b) => a[1].metadata.order - b[1].metadata.order).map(([stepId, state]) => {
@@ -10445,7 +10515,7 @@ function createStepTelemetryTracker() {
10445
10515
  new Set([...trackedEntries, ...skippedEntries].map(([stepId]) => stepId)));
10446
10516
  return Object.fromEntries(
10447
10517
  [...trackedEntries, ...skippedEntries, ...syntheticBuildEntries].sort(
10448
- ([leftStepId], [rightStepId]) => (getMetadata(leftStepId)?.order ?? Number.MAX_SAFE_INTEGER) - (getMetadata(rightStepId)?.order ?? Number.MAX_SAFE_INTEGER)
10518
+ ([leftStepId], [rightStepId]) => (getStepMetadata(leftStepId)?.order ?? Number.MAX_SAFE_INTEGER) - (getStepMetadata(rightStepId)?.order ?? Number.MAX_SAFE_INTEGER)
10449
10519
  )
10450
10520
  );
10451
10521
  }
@@ -10454,17 +10524,22 @@ function createStepTelemetryTracker() {
10454
10524
 
10455
10525
  // src/commands/ci/machine/commands/machine-runner.ts
10456
10526
  var FLUSH_DELAY_MS = 100;
10457
- var progressUpdateSequence = 0;
10458
- var pendingProgressUpdate = null;
10459
- var progressHeartbeatTimer = null;
10460
- var latestSnapshot = null;
10461
- var heartbeatStep = null;
10462
- var heartbeatFailedStep = null;
10463
- var progressUpdateSuccessCount = 0;
10464
- var stepStartTime = Date.now();
10465
- var currentTrackedStep = null;
10466
- var stepTimings = {};
10467
- var previousActiveSteps = /* @__PURE__ */ new Set();
10527
+ function createProgressState() {
10528
+ return {
10529
+ updateSequence: 0,
10530
+ pendingUpdate: null,
10531
+ heartbeatTimer: null,
10532
+ latestSnapshot: null,
10533
+ heartbeatStep: null,
10534
+ heartbeatFailedStep: null,
10535
+ updateCount: 0,
10536
+ stepStartTime: Date.now(),
10537
+ currentTrackedStep: null,
10538
+ stepTimings: {},
10539
+ previousActiveSteps: /* @__PURE__ */ new Set()
10540
+ };
10541
+ }
10542
+ var progress = createProgressState();
10468
10543
  var DEFAULT_PROGRESS_HEARTBEAT_MS = 3e4;
10469
10544
  var HEARTBEAT_ELIGIBLE_STEPS = /* @__PURE__ */ new Set([
10470
10545
  "syncSchema",
@@ -10475,95 +10550,15 @@ var HEARTBEAT_ELIGIBLE_STEPS = /* @__PURE__ */ new Set([
10475
10550
  "runTests"
10476
10551
  ]);
10477
10552
  function resetProgressTracking() {
10478
- progressUpdateSequence = 0;
10479
- pendingProgressUpdate = null;
10480
- if (progressHeartbeatTimer) {
10481
- clearInterval(progressHeartbeatTimer);
10482
- progressHeartbeatTimer = null;
10483
- }
10484
- latestSnapshot = null;
10485
- heartbeatStep = null;
10486
- heartbeatFailedStep = null;
10487
- progressUpdateSuccessCount = 0;
10488
- previousActiveSteps = /* @__PURE__ */ new Set();
10489
- stepStartTime = Date.now();
10490
- currentTrackedStep = null;
10491
- for (const key of Object.keys(stepTimings)) {
10492
- delete stepTimings[key];
10493
- }
10494
- }
10495
- var STATE_TO_STEP = {
10496
- // Setup phase
10497
- setup: "setup",
10498
- "setup.resolving": "setup",
10499
- "setup.local": "setup",
10500
- "setup.prLocal": "setup",
10501
- "setup.complete": "setup",
10502
- // DB phase
10503
- dbReset: "syncSchema",
10504
- pullProduction: "syncSchema",
10505
- syncSchema: "syncSchema",
10506
- applySeeds: "applySeeds",
10507
- productionPreview: "observability",
10508
- collectSchemaStats: "observability",
10509
- postSeedPr: "postSeedChecks",
10510
- "postSeedPr.execution": "postSeedChecks",
10511
- "postSeedPr.observability": "observability",
10512
- "postSeedPr.execution.setupRoles": "postSeedChecks",
10513
- "postSeedPr.observability.productionPreview": "observability",
10514
- "postSeedPr.observability.collectSchemaStats": "observability",
10515
- "postSeedPr.execution.staticChecks": "staticChecks",
10516
- "postSeedPr.execution.buildAndPlaywright": "build",
10517
- "postSeedPr.execution.appStart": "build",
10518
- "postSeedPr.execution.capabilities": "build",
10519
- "postSeedPr.execution.runCoreTests": "runTests",
10520
- "postSeedPr.execution.coreTestsComplete": "runTests",
10521
- "postSeedPr.execution.coreTestsFailed": "runTests",
10522
- "postSeedPr.execution.e2ePhase": "runTests",
10523
- "postSeedPr.execution.done": "runTests",
10524
- decidePath: "postSeedChecks",
10525
- installPgTap: "applySeeds",
10526
- setupRoles: "applySeeds",
10527
- // Build phase
10528
- staticChecks: "staticChecks",
10529
- buildAndPlaywright: "build",
10530
- appStart: "build",
10531
- capabilities: "build",
10532
- // Test phase (ci-local)
10533
- runTests: "runTests",
10534
- testsComplete: "runTests",
10535
- testsFailed: "runTests",
10536
- // Test phase (ci-pr: blocking/non-blocking split)
10537
- runCoreTests: "runTests",
10538
- coreTestsComplete: "runTests",
10539
- coreTestsFailed: "runTests",
10540
- e2ePhase: "runTests",
10541
- "e2ePhase.intermediateComment": "runTests",
10542
- "e2ePhase.e2eTests": "runTests",
10543
- // Finalize phase
10544
- finalize: "finalize",
10545
- "finalize.writeSummary": "finalize",
10546
- "finalize.postComment": "finalize",
10547
- "finalize.complete": "finalize",
10548
- // Final states
10549
- done: "finalize",
10550
- failed: "finalize"
10551
- };
10552
- var STEP_ORDER = [
10553
- "setup",
10554
- "syncSchema",
10555
- "applySeeds",
10556
- "postSeedChecks",
10557
- "observability",
10558
- "staticChecks",
10559
- "build",
10560
- "runTests",
10561
- "finalize"
10562
- ];
10553
+ if (progress.heartbeatTimer) {
10554
+ clearInterval(progress.heartbeatTimer);
10555
+ }
10556
+ Object.assign(progress, createProgressState());
10557
+ }
10563
10558
  function resolveStepForState(state) {
10564
10559
  let cursor = state;
10565
10560
  while (cursor.length > 0) {
10566
- const step = STATE_TO_STEP[cursor];
10561
+ const step = PROGRESS_STATE_TO_STEP[cursor];
10567
10562
  if (step) return step;
10568
10563
  const nextDot = cursor.lastIndexOf(".");
10569
10564
  if (nextDot === -1) break;
@@ -10571,20 +10566,22 @@ function resolveStepForState(state) {
10571
10566
  }
10572
10567
  return void 0;
10573
10568
  }
10574
- function getCompletedSteps(currentStep, stepTimings2 = {}) {
10569
+ function getCompletedSteps(currentStep, stepTimings = {}) {
10575
10570
  const completed = new Set(
10576
- Object.keys(stepTimings2).filter((step) => STEP_ORDER.includes(step))
10571
+ Object.keys(stepTimings).filter(
10572
+ (step) => PROGRESS_STEP_ORDER.includes(step)
10573
+ )
10577
10574
  );
10578
10575
  completed.delete(currentStep);
10579
- return STEP_ORDER.filter((step) => completed.has(step));
10576
+ return PROGRESS_STEP_ORDER.filter((step) => completed.has(step));
10580
10577
  }
10581
10578
  function recordStepTiming(newStep) {
10582
10579
  const now = Date.now();
10583
- if (currentTrackedStep !== null) {
10584
- stepTimings[currentTrackedStep] = now - stepStartTime;
10580
+ if (progress.currentTrackedStep !== null) {
10581
+ progress.stepTimings[progress.currentTrackedStep] = now - progress.stepStartTime;
10585
10582
  }
10586
- currentTrackedStep = newStep;
10587
- stepStartTime = now;
10583
+ progress.currentTrackedStep = newStep;
10584
+ progress.stepStartTime = now;
10588
10585
  }
10589
10586
  function parseProgressHeartbeatMs(context) {
10590
10587
  const rawValue = context.input.progressIntervalSeconds?.trim();
@@ -10596,24 +10593,28 @@ function parseProgressHeartbeatMs(context) {
10596
10593
  return parsedSeconds * 1e3;
10597
10594
  }
10598
10595
  function stopProgressHeartbeat() {
10599
- if (progressHeartbeatTimer) {
10600
- clearInterval(progressHeartbeatTimer);
10601
- progressHeartbeatTimer = null;
10596
+ if (progress.heartbeatTimer) {
10597
+ clearInterval(progress.heartbeatTimer);
10598
+ progress.heartbeatTimer = null;
10602
10599
  }
10603
10600
  }
10604
10601
  function refreshProgressHeartbeat(currentStep, failedStep = null) {
10605
- heartbeatStep = currentStep;
10606
- heartbeatFailedStep = failedStep;
10602
+ progress.heartbeatStep = currentStep;
10603
+ progress.heartbeatFailedStep = failedStep;
10607
10604
  stopProgressHeartbeat();
10608
10605
  if (!HEARTBEAT_ELIGIBLE_STEPS.has(currentStep)) {
10609
10606
  return;
10610
10607
  }
10611
- const intervalMs = latestSnapshot ? parseProgressHeartbeatMs(latestSnapshot.context) : DEFAULT_PROGRESS_HEARTBEAT_MS;
10612
- progressHeartbeatTimer = setInterval(() => {
10613
- if (!latestSnapshot || !heartbeatStep) return;
10614
- updateProgressComment(latestSnapshot.context, heartbeatStep, heartbeatFailedStep);
10608
+ const intervalMs = progress.latestSnapshot ? parseProgressHeartbeatMs(progress.latestSnapshot.context) : DEFAULT_PROGRESS_HEARTBEAT_MS;
10609
+ progress.heartbeatTimer = setInterval(() => {
10610
+ if (!progress.latestSnapshot || !progress.heartbeatStep) return;
10611
+ updateProgressComment(
10612
+ progress.latestSnapshot.context,
10613
+ progress.heartbeatStep,
10614
+ progress.heartbeatFailedStep
10615
+ );
10615
10616
  }, intervalMs);
10616
- progressHeartbeatTimer.unref?.();
10617
+ progress.heartbeatTimer.unref?.();
10617
10618
  }
10618
10619
  function updateProgressComment(context, currentStep, failedStep = null) {
10619
10620
  if (!shouldPostGitHubComment(context)) return;
@@ -10622,25 +10623,26 @@ function updateProgressComment(context, currentStep, failedStep = null) {
10622
10623
  if (!token) return;
10623
10624
  const repository = process.env.GITHUB_REPOSITORY?.trim();
10624
10625
  if (!repository || !repository.includes("/")) return;
10625
- const mySeq = ++progressUpdateSequence;
10626
+ const mySeq = ++progress.updateSequence;
10627
+ progress.updateCount++;
10626
10628
  const doUpdate = async () => {
10627
- if (pendingProgressUpdate) {
10629
+ if (progress.pendingUpdate) {
10628
10630
  try {
10629
- await pendingProgressUpdate;
10631
+ await progress.pendingUpdate;
10630
10632
  } catch {
10631
10633
  }
10632
10634
  }
10633
- if (mySeq < progressUpdateSequence) {
10635
+ if (mySeq < progress.updateSequence) {
10634
10636
  return;
10635
10637
  }
10636
- const freshContext = latestSnapshot?.context ?? context;
10637
- const completedSteps = getCompletedSteps(currentStep, stepTimings);
10638
+ const freshContext = progress.latestSnapshot?.context ?? context;
10639
+ const completedSteps = getCompletedSteps(currentStep, progress.stepTimings);
10638
10640
  const progressInput = createProgressCommentInput(
10639
10641
  freshContext,
10640
10642
  currentStep,
10641
10643
  completedSteps,
10642
10644
  failedStep,
10643
- { ...stepTimings }
10645
+ { ...progress.stepTimings }
10644
10646
  );
10645
10647
  const body = `<!-- runa-ci-report -->
10646
10648
  ${generateProgressCommentBody(progressInput)}`;
@@ -10651,11 +10653,10 @@ ${generateProgressCommentBody(progressInput)}`;
10651
10653
  marker: "<!-- runa-ci-report -->",
10652
10654
  body
10653
10655
  });
10654
- progressUpdateSuccessCount++;
10655
- } catch (error) {
10656
+ } catch {
10656
10657
  }
10657
10658
  };
10658
- pendingProgressUpdate = doUpdate();
10659
+ progress.pendingUpdate = doUpdate();
10659
10660
  }
10660
10661
  function determineFailedStep(context) {
10661
10662
  if (context.testsRun && Object.values(context.layerResults).some((r) => r.status === "failed"))
@@ -10681,8 +10682,8 @@ function handleProgressCommentUpdate(snapshot, prevState, currentState, activeSt
10681
10682
  const currentStepSet = new Set(
10682
10683
  activeStatePaths.map((p) => resolveStepForState(p)).filter((s) => s !== void 0)
10683
10684
  );
10684
- const parallelStepChanged = !setsEqual(previousActiveSteps, currentStepSet);
10685
- previousActiveSteps = currentStepSet;
10685
+ const parallelStepChanged = !setsEqual(progress.previousActiveSteps, currentStepSet);
10686
+ progress.previousActiveSteps = currentStepSet;
10686
10687
  if (!currentStep || !parallelStepChanged && currentStep === prevStep && !shouldRefreshWithinStep)
10687
10688
  return;
10688
10689
  if (!shouldRefreshWithinStep) {
@@ -10725,11 +10726,10 @@ async function runCiMachine(input, logger, onStateChange) {
10725
10726
  let settled = false;
10726
10727
  const stepTelemetry = createStepTelemetryTracker();
10727
10728
  actor.subscribe((snapshot) => {
10728
- latestSnapshot = snapshot;
10729
+ progress.latestSnapshot = snapshot;
10729
10730
  const activeStatePaths = getSnapshotStatePaths(snapshot);
10730
10731
  stepTelemetry.transition(activeStatePaths);
10731
10732
  const currentState = pickPrimaryStatePath(activeStatePaths, previousState) ?? activeStatePaths[0] ?? previousState ?? "unknown";
10732
- snapshot.status;
10733
10733
  if (currentState && currentState !== "done" && currentState !== "failed" && !currentState.startsWith("finalize")) {
10734
10734
  lastActionableState = currentState;
10735
10735
  }
@@ -11006,7 +11006,7 @@ init_esm_shims();
11006
11006
  init_esm_shims();
11007
11007
  var CiModeSchema = z.enum(["ci-local", "ci-pr-local"]);
11008
11008
  var CiExecutionEnvSchema = z.enum(["github-actions", "local"]);
11009
- var CiPhaseSchema = z.enum(["all", "test"]);
11009
+ var CiPhaseSchema = z.enum(["all", "blocking", "observability", "test"]);
11010
11010
  var CiDbModeSchema = z.enum(["auto", "local"]);
11011
11011
  var RepoKindSchema = z.enum(["monorepo", "pj-repo", "unknown"]);
11012
11012
  var StepStatusSchema = z.enum(["passed", "failed", "skipped", "killed", "timeout"]);
@@ -11096,7 +11096,9 @@ z.object({
11096
11096
  /** Skip GitHub comment */
11097
11097
  skipGithubComment: z.boolean().optional(),
11098
11098
  /** Specific layers */
11099
- layers: z.array(z.number().int()).optional()
11099
+ layers: z.array(z.number().int()).optional(),
11100
+ /** Internal workflow-only CI profile */
11101
+ internalProfile: z.enum(["schema-only"]).optional()
11100
11102
  }).passthrough();
11101
11103
  z.object({
11102
11104
  passed: z.boolean(),
@@ -11235,7 +11237,6 @@ z.object({
11235
11237
  });
11236
11238
  z.object({
11237
11239
  allowLocalFallback: z.boolean(),
11238
- allowPartialPhases: z.boolean(),
11239
11240
  source: z.enum(["config", "default"])
11240
11241
  });
11241
11242
  z.object({
@@ -11245,6 +11246,43 @@ z.object({
11245
11246
  port: z.number().int()
11246
11247
  });
11247
11248
 
11249
+ // src/commands/ci/machine/commands/ci-pr-internal-profile.ts
11250
+ init_esm_shims();
11251
+ function parseInternalLayers(raw) {
11252
+ const values = raw.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
11253
+ if (values.length === 0) {
11254
+ throw new Error("RUNA_CI_INTERNAL_LAYERS must include at least one layer number");
11255
+ }
11256
+ const layers = values.map((token) => {
11257
+ const parsed = Number.parseInt(token, 10);
11258
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 4) {
11259
+ throw new Error(
11260
+ `Invalid RUNA_CI_INTERNAL_LAYERS value: "${raw}". Expected a comma-separated list of layers 1-4.`
11261
+ );
11262
+ }
11263
+ return parsed;
11264
+ });
11265
+ return Array.from(new Set(layers)).sort((a, b) => a - b);
11266
+ }
11267
+ function resolveInternalCiOverrides(env = process.env) {
11268
+ const profileRaw = env.RUNA_CI_INTERNAL_PROFILE?.trim();
11269
+ const layersRaw = env.RUNA_CI_INTERNAL_LAYERS?.trim();
11270
+ if (!profileRaw && !layersRaw) {
11271
+ return {};
11272
+ }
11273
+ let profile;
11274
+ if (profileRaw) {
11275
+ if (profileRaw !== "schema-only") {
11276
+ throw new Error(
11277
+ `Unsupported RUNA_CI_INTERNAL_PROFILE value: "${profileRaw}". Expected "schema-only".`
11278
+ );
11279
+ }
11280
+ profile = profileRaw;
11281
+ }
11282
+ const layers = layersRaw ? parseInternalLayers(layersRaw) : profile === "schema-only" ? [2] : void 0;
11283
+ return { profile, layers };
11284
+ }
11285
+
11248
11286
  // src/commands/ci/machine/commands/ci-pr.ts
11249
11287
  var cliOutputFormat = "human";
11250
11288
  var isGitHubActionsMode2 = false;
@@ -11299,6 +11337,73 @@ function logSetupRolesStatus(ctx, logger) {
11299
11337
  }
11300
11338
  }
11301
11339
  var CI_PR_UNKNOWN_STATE_LOG_SKIP = /* @__PURE__ */ new Set(["decidePath", "done", "failed"]);
11340
+ var logStaticChecks = (ctx, logger) => {
11341
+ logSetupRolesStatus(ctx, logger);
11342
+ logSection3("Static Checks: type-check + lint (parallel)");
11343
+ logger.info("Running: pnpm type-check || pnpm lint");
11344
+ };
11345
+ var logBuildAndPlaywright = (ctx, logger) => {
11346
+ if (ctx.staticChecksPassed) {
11347
+ logger.success("Static checks passed");
11348
+ }
11349
+ if (shouldReusePreparedPlaywright(ctx)) {
11350
+ logSection3("Build: App Build + Reuse Prepared Playwright");
11351
+ logger.info("Running: pnpm build (Playwright install owned by outer workflow)");
11352
+ return;
11353
+ }
11354
+ logSection3("Build: App Build + Playwright Install (parallel)");
11355
+ logger.info("Running: pnpm build || pnpm exec playwright install chromium");
11356
+ };
11357
+ var logLayerContent = (ctx, logger) => {
11358
+ if (ctx.staticChecksPassed) {
11359
+ logger.success("Static checks passed");
11360
+ }
11361
+ logSection3("Detect Runnable Layers");
11362
+ logger.info(`Selected layers before filtering: ${ctx.selectedLayers.join(", ")}`);
11363
+ };
11364
+ var logAppStart = (ctx, logger) => {
11365
+ if (ctx.appBuildPassed) {
11366
+ logger.success("App build completed");
11367
+ }
11368
+ if (ctx.manifestGenerated) {
11369
+ logger.success("Manifest generated (Layer 3 ready)");
11370
+ } else if (ctx.manifestGenerated === null) {
11371
+ logger.info("Manifest generation skipped (optional)");
11372
+ } else if (ctx.manifestGenerated === false) {
11373
+ logger.warn("Manifest generation failed (Layer 3 may be skipped)");
11374
+ }
11375
+ if (ctx.playwrightInstalled) {
11376
+ logger.success("Playwright browsers installed");
11377
+ }
11378
+ logSection3("Start Application");
11379
+ logger.info(`Starting app on port ${ctx.app?.port ?? 3e3}...`);
11380
+ };
11381
+ var logCapabilities = (ctx, logger) => {
11382
+ if (ctx.appStarted) {
11383
+ logger.success(`App started at ${ctx.baseUrl ?? "http://localhost:3000"}`);
11384
+ }
11385
+ logSection3("Detect Test Capabilities");
11386
+ };
11387
+ var logRunCoreTests = (ctx, logger) => {
11388
+ const coreLayers = ctx.selectedLayers.filter((l) => l !== 4);
11389
+ logSection3(`Core Tests: Layer ${coreLayers.join(", ")}`);
11390
+ logger.info(`Running core layers: ${coreLayers.map((l) => `test layer${l}`).join(", ")}`);
11391
+ };
11392
+ var logCollectSchemaStats = (ctx, logger) => {
11393
+ if (ctx.productionPreview?.executed) {
11394
+ if (ctx.productionPreview.hasChanges) {
11395
+ logger.info("Production preview: schema changes detected");
11396
+ } else {
11397
+ logger.success("Production preview: no schema changes");
11398
+ }
11399
+ }
11400
+ logSection3("Database: Collect Schema Statistics");
11401
+ logger.info("Comparing Local/CI/Production schemas...");
11402
+ };
11403
+ var logE2ePhase = (_ctx, logger) => {
11404
+ logSection3("E2E Phase: Layer 4 + Intermediate Comment");
11405
+ logger.info("Running Layer 4 E2E tests (parallel with intermediate PR comment)");
11406
+ };
11302
11407
  var stateLogHandlers2 = {
11303
11408
  // ─── Setup Phase ───────────────────────────────────────────
11304
11409
  setup: (_ctx, _logger) => {
@@ -11319,9 +11424,12 @@ var stateLogHandlers2 = {
11319
11424
  logger.info("Posting initial CI progress comment to PR...");
11320
11425
  },
11321
11426
  // ─── Database Phase ────────────────────────────────────────
11322
- syncSchema: (_ctx, logger) => {
11323
- logSection3("Database: Apply Schema (db apply)");
11324
- logger.info("Running: pnpm exec runa db apply preview --verbose");
11427
+ syncSchema: (ctx, logger) => {
11428
+ const isCiLocalMode2 = ctx.mode === "ci-local";
11429
+ logSection3(`Database: Apply Schema (${isCiLocalMode2 ? "db sync" : "db apply --no-seed"})`);
11430
+ logger.info(
11431
+ isCiLocalMode2 ? "Running: pnpm exec runa db sync local --verbose" : "Running: pnpm exec runa db apply preview --no-seed --verbose"
11432
+ );
11325
11433
  },
11326
11434
  applySeeds: (ctx, logger) => {
11327
11435
  if (ctx.schemaApplied) {
@@ -11330,6 +11438,7 @@ var stateLogHandlers2 = {
11330
11438
  logSection3("Database: Apply Seeds (db seed)");
11331
11439
  logger.info("Running: pnpm exec runa db seed ci --auto-approve");
11332
11440
  },
11441
+ // Nested paths (postSeedPr.*) — differ from flat counterparts
11333
11442
  "postSeedPr.execution.setupRoles": (ctx, logger) => {
11334
11443
  if (ctx.seedsApplied) {
11335
11444
  logger.success("Seeds applied successfully");
@@ -11342,68 +11451,18 @@ var stateLogHandlers2 = {
11342
11451
  logger.success("Seeds applied successfully");
11343
11452
  }
11344
11453
  logSection3("Database: Production Preview (dry-run)");
11345
- logger.info("Running: pnpm exec runa db apply production --check --compare-only");
11346
- },
11347
- "postSeedPr.observability.collectSchemaStats": (ctx, logger) => {
11348
- if (ctx.productionPreview?.executed) {
11349
- if (ctx.productionPreview.hasChanges) {
11350
- logger.info("Production preview: schema changes detected");
11351
- } else {
11352
- logger.success("Production preview: no schema changes");
11353
- }
11354
- }
11355
- logSection3("Database: Collect Schema Statistics");
11356
- logger.info("Comparing Local/CI/Production schemas...");
11357
- },
11358
- "postSeedPr.execution.staticChecks": (ctx, logger) => {
11359
- logSetupRolesStatus(ctx, logger);
11360
- logSection3("Static Checks: type-check + lint (parallel)");
11361
- logger.info("Running: pnpm type-check || pnpm lint");
11362
- },
11363
- "postSeedPr.execution.buildAndPlaywright": (ctx, logger) => {
11364
- if (ctx.staticChecksPassed) {
11365
- logger.success("Static checks passed");
11366
- }
11367
- if (shouldReusePreparedPlaywright(ctx)) {
11368
- logSection3("Build: App Build + Reuse Prepared Playwright");
11369
- logger.info("Running: pnpm build (Playwright install owned by outer workflow)");
11370
- return;
11371
- }
11372
- logSection3("Build: App Build + Playwright Install (parallel)");
11373
- logger.info("Running: pnpm build || pnpm exec playwright install chromium");
11374
- },
11375
- "postSeedPr.execution.appStart": (ctx, logger) => {
11376
- if (ctx.appBuildPassed) {
11377
- logger.success("App build completed");
11378
- }
11379
- if (ctx.manifestGenerated) {
11380
- logger.success("Manifest generated (Layer 3 ready)");
11381
- } else if (ctx.manifestGenerated === null) {
11382
- logger.info("Manifest generation skipped (optional)");
11383
- } else if (ctx.manifestGenerated === false) {
11384
- logger.warn("Manifest generation failed (Layer 3 may be skipped)");
11385
- }
11386
- if (ctx.playwrightInstalled) {
11387
- logger.success("Playwright browsers installed");
11388
- }
11389
- logSection3("Start Application");
11390
- logger.info(`Starting app on port ${ctx.app?.port ?? 3e3}...`);
11391
- },
11392
- "postSeedPr.execution.capabilities": (ctx, logger) => {
11393
- if (ctx.appStarted) {
11394
- logger.success(`App started at ${ctx.baseUrl ?? "http://localhost:3000"}`);
11395
- }
11396
- logSection3("Detect Test Capabilities");
11397
- },
11398
- "postSeedPr.execution.runCoreTests": (ctx, logger) => {
11399
- const coreLayers = ctx.selectedLayers.filter((l) => l !== 4);
11400
- logSection3(`Core Tests: Layer ${coreLayers.join(", ")}`);
11401
- logger.info(`Running core layers: ${coreLayers.map((l) => `test layer${l}`).join(", ")}`);
11402
- },
11403
- "postSeedPr.execution.e2ePhase": (_ctx, logger) => {
11404
- logSection3("E2E Phase: Layer 4 + Intermediate Comment");
11405
- logger.info("Running Layer 4 E2E tests (parallel with intermediate PR comment)");
11454
+ logger.info("Running: pnpm exec runa db preview production --profile compare-only");
11406
11455
  },
11456
+ // Shared handlers (nested paths alias to same handler as flat paths)
11457
+ "postSeedPr.observability.collectSchemaStats": logCollectSchemaStats,
11458
+ "postSeedPr.execution.staticChecks": logStaticChecks,
11459
+ "postSeedPr.execution.layerContent": logLayerContent,
11460
+ "postSeedPr.execution.buildAndPlaywright": logBuildAndPlaywright,
11461
+ "postSeedPr.execution.appStart": logAppStart,
11462
+ "postSeedPr.execution.capabilities": logCapabilities,
11463
+ "postSeedPr.execution.runCoreTests": logRunCoreTests,
11464
+ "postSeedPr.execution.e2ePhase": logE2ePhase,
11465
+ // Flat paths (differ from nested counterparts)
11407
11466
  productionPreview: (ctx, logger) => {
11408
11467
  if (ctx.seedsApplied) {
11409
11468
  logger.success("Seeds applied successfully");
@@ -11411,19 +11470,9 @@ var stateLogHandlers2 = {
11411
11470
  logger.info("Seeds skipped (no seed files or already seeded)");
11412
11471
  }
11413
11472
  logSection3("Database: Production Preview (dry-run)");
11414
- logger.info("Running: pnpm exec runa db apply production --check --compare-only");
11415
- },
11416
- collectSchemaStats: (ctx, logger) => {
11417
- if (ctx.productionPreview?.executed) {
11418
- if (ctx.productionPreview.hasChanges) {
11419
- logger.info("Production preview: schema changes detected");
11420
- } else {
11421
- logger.success("Production preview: no schema changes");
11422
- }
11423
- }
11424
- logSection3("Database: Collect Schema Statistics");
11425
- logger.info("Comparing Local/CI/Production schemas...");
11473
+ logger.info("Running: pnpm exec runa db preview production --profile compare-only");
11426
11474
  },
11475
+ collectSchemaStats: logCollectSchemaStats,
11427
11476
  setupRoles: (ctx, logger) => {
11428
11477
  if (ctx.schemaStats) {
11429
11478
  logger.success("Schema statistics collected");
@@ -11432,57 +11481,18 @@ var stateLogHandlers2 = {
11432
11481
  logger.info("Running: pnpm db:setup-roles");
11433
11482
  },
11434
11483
  // ─── Static Analysis Phase ─────────────────────────────────
11435
- staticChecks: (ctx, logger) => {
11436
- logSetupRolesStatus(ctx, logger);
11437
- logSection3("Static Checks: type-check + lint (parallel)");
11438
- logger.info("Running: pnpm type-check || pnpm lint");
11439
- },
11484
+ staticChecks: logStaticChecks,
11440
11485
  // ─── Build Phase ───────────────────────────────────────────
11441
- buildAndPlaywright: (ctx, logger) => {
11442
- if (ctx.staticChecksPassed) {
11443
- logger.success("Static checks passed");
11444
- }
11445
- if (shouldReusePreparedPlaywright(ctx)) {
11446
- logSection3("Build: App Build + Reuse Prepared Playwright");
11447
- logger.info("Running: pnpm build (Playwright install owned by outer workflow)");
11448
- return;
11449
- }
11450
- logSection3("Build: App Build + Playwright Install (parallel)");
11451
- logger.info("Running: pnpm build || pnpm exec playwright install chromium");
11452
- },
11453
- appStart: (ctx, logger) => {
11454
- if (ctx.appBuildPassed) {
11455
- logger.success("App build completed");
11456
- }
11457
- if (ctx.manifestGenerated) {
11458
- logger.success("Manifest generated (Layer 3 ready)");
11459
- } else if (ctx.manifestGenerated === null) {
11460
- logger.info("Manifest generation skipped (optional)");
11461
- } else if (ctx.manifestGenerated === false) {
11462
- logger.warn("Manifest generation failed (Layer 3 may be skipped)");
11463
- }
11464
- if (ctx.playwrightInstalled) {
11465
- logger.success("Playwright browsers installed");
11466
- }
11467
- logSection3("Start Application");
11468
- logger.info(`Starting app on port ${ctx.app?.port ?? 3e3}...`);
11469
- },
11486
+ layerContent: logLayerContent,
11487
+ buildAndPlaywright: logBuildAndPlaywright,
11488
+ appStart: logAppStart,
11470
11489
  // ─── Test Phase ────────────────────────────────────────────
11471
- capabilities: (ctx, logger) => {
11472
- if (ctx.appStarted) {
11473
- logger.success(`App started at ${ctx.baseUrl ?? "http://localhost:3000"}`);
11474
- }
11475
- logSection3("Detect Test Capabilities");
11476
- },
11490
+ capabilities: logCapabilities,
11477
11491
  runTests: (ctx, logger) => {
11478
11492
  logSection3(`Run Tests: Layer ${ctx.selectedLayers.join(", ")}`);
11479
11493
  logger.info(`Running layers: ${ctx.selectedLayers.map((l) => `test layer${l}`).join(", ")}`);
11480
11494
  },
11481
- runCoreTests: (ctx, logger) => {
11482
- const coreLayers = ctx.selectedLayers.filter((l) => l !== 4);
11483
- logSection3(`Core Tests: Layer ${coreLayers.join(", ")}`);
11484
- logger.info(`Running core layers: ${coreLayers.map((l) => `test layer${l}`).join(", ")}`);
11485
- },
11495
+ runCoreTests: logRunCoreTests,
11486
11496
  coreTestsComplete: (ctx, logger) => {
11487
11497
  const coreLayers = ctx.selectedLayers.filter((l) => l !== 4);
11488
11498
  const passedLayers = coreLayers.filter(
@@ -11496,10 +11506,7 @@ var stateLogHandlers2 = {
11496
11506
  const failedLayers = Object.entries(ctx.layerResults).filter(([_, result]) => result.status === "failed").map(([layer]) => layer);
11497
11507
  logger.error(`Core tests failed: Layer ${failedLayers.join(", ")}`);
11498
11508
  },
11499
- e2ePhase: (_ctx, logger) => {
11500
- logSection3("E2E Phase: Layer 4 + Intermediate Comment");
11501
- logger.info("Running Layer 4 E2E tests (parallel with intermediate PR comment)");
11502
- },
11509
+ e2ePhase: logE2ePhase,
11503
11510
  "e2ePhase.e2eTests": (_ctx, logger) => {
11504
11511
  logger.info("Running Layer 4 E2E tests...");
11505
11512
  },
@@ -11565,7 +11572,7 @@ function getGitHubEventAction() {
11565
11572
  return void 0;
11566
11573
  }
11567
11574
  }
11568
- function optionsToMachineInput(options) {
11575
+ function optionsToMachineInput(options, internalOverrides) {
11569
11576
  return {
11570
11577
  command: "pr",
11571
11578
  // Explicit command for mode detection
@@ -11586,7 +11593,8 @@ function optionsToMachineInput(options) {
11586
11593
  skipGithubComment: options.skipGithubComment,
11587
11594
  verbose: options.verbose,
11588
11595
  targetDir: process.cwd(),
11589
- layers: [1, 2, 3, 4],
11596
+ layers: internalOverrides.layers,
11597
+ internalProfile: internalOverrides.profile,
11590
11598
  // Environment capture (Environment Capture Pattern: capture at entry point, not in machine/guards)
11591
11599
  databaseUrl: options.databaseUrl ?? process.env.DATABASE_URL,
11592
11600
  // PRD: GH_DATABASE_URL_ADMIN = postgres role (DDL capable, for pg-schema-diff dry-run)
@@ -11614,36 +11622,47 @@ async function runCiPrCommand(options) {
11614
11622
  const skipStaticChecks = options.skipStaticChecks === true;
11615
11623
  const skipBuild = options.skipBuild === true;
11616
11624
  const phase = options.phase ?? "all";
11625
+ const blockingPhase = phase === "blocking";
11626
+ const observabilityPhase = phase === "observability";
11617
11627
  const testOnlyPhase = phase === "test";
11628
+ const runExecutionPath = !observabilityPhase;
11618
11629
  const preparedRuntime = options.skipLocalDbStart === true || options.assumeSupabaseReady === true;
11619
11630
  const preparedPlaywright = options.skipPlaywrightInstall === true;
11631
+ const dbDescription = testOnlyPhase ? "Skip schema apply/seed and reuse the prepared database" : observabilityPhase ? "db apply \u2192 db seed \u2192 production preview \u2192 schema stats" : blockingPhase ? "db apply \u2192 db seed \u2192 (production preview \u2225 schema stats \u2225 db:setup-roles)" : "db apply \u2192 db seed \u2192 (production preview \u2225 schema stats \u2225 db:setup-roles)";
11620
11632
  const planSteps = [
11621
11633
  {
11622
11634
  id: "setup",
11623
11635
  description: testOnlyPhase ? "Resolve repo/app context (optionally reuse pre-started local Supabase)" : preparedRuntime ? "Reuse workflow-prepared local Supabase runtime" : "Start local Supabase instance"
11624
11636
  },
11625
- ...testOnlyPhase ? [{ id: "db", description: "Skip schema apply/seed and reuse the prepared database" }] : [
11637
+ { id: "db", description: dbDescription },
11638
+ ...!runExecutionPath || skipStaticChecks ? [] : [{ id: "static", description: "pnpm type-check + pnpm lint (parallel)" }],
11639
+ ...!runExecutionPath || skipBuild ? [] : [
11626
11640
  {
11627
- id: "db",
11628
- description: "db apply \u2192 db seed \u2192 (production preview \u2225 schema stats \u2225 db:setup-roles)"
11641
+ id: "build",
11642
+ description: preparedPlaywright ? "Detect runnable layers \u2192 pnpm build \u2192 manifest:generate (reuse preinstalled Playwright)" : "Detect runnable layers \u2192 pnpm build \u2192 manifest:generate + playwright install"
11629
11643
  }
11630
11644
  ],
11631
- ...skipStaticChecks ? [] : [{ id: "static", description: "pnpm type-check + pnpm lint (parallel)" }],
11632
- ...skipBuild ? [] : [
11645
+ ...!runExecutionPath ? [] : [
11633
11646
  {
11634
- id: "build",
11635
- description: preparedPlaywright ? "pnpm build \u2192 manifest:generate (reuse preinstalled Playwright)" : "pnpm build \u2192 manifest:generate + playwright install"
11647
+ id: "test",
11648
+ description: "Start app if Layer 4 is runnable \u2192 detect capabilities \u2192 run layers"
11636
11649
  }
11637
11650
  ],
11638
- { id: "test", description: "Start app \u2192 detect capabilities \u2192 run layers 1-4" },
11639
11651
  {
11640
11652
  id: "finalize",
11641
11653
  description: "Write ci-summary.json + post GitHub comment"
11642
11654
  }
11643
11655
  ];
11644
11656
  logPlan2(planSteps);
11645
- const machineInput = optionsToMachineInput(options);
11646
11657
  try {
11658
+ const internalOverrides = resolveInternalCiOverrides();
11659
+ if (internalOverrides.profile && outputFormat !== "json") {
11660
+ logger.info(`Internal CI profile: ${internalOverrides.profile}`);
11661
+ if (internalOverrides.layers) {
11662
+ logger.info(`Internal CI layers: ${internalOverrides.layers.join(", ")}`);
11663
+ }
11664
+ }
11665
+ const machineInput = optionsToMachineInput(options, internalOverrides);
11647
11666
  const result = await runCiMachine(machineInput, logger, handleStateChange2);
11648
11667
  endCurrentGroup2();
11649
11668
  printSummary2(logger, result);