@runa-ai/runa-cli 0.8.0 → 0.10.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 (110) 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-RB2ZUS76.js → chunk-EXR4J2JT.js} +52 -16
  7. package/dist/{chunk-JMJP4A47.js → chunk-GT5DMS5R.js} +20 -2
  8. package/dist/{chunk-3JO6YP3T.js → chunk-IEKYTCYA.js} +1 -1
  9. package/dist/{chunk-WPMR7RQ4.js → chunk-IWVXI5O4.js} +2 -2
  10. package/dist/chunk-KUH3G522.js +72 -0
  11. package/dist/{chunk-VSH3IXDQ.js → chunk-MAFJAA2P.js} +1 -1
  12. package/dist/{chunk-CCKG5R4Y.js → chunk-MILCC3B6.js} +1 -1
  13. package/dist/{chunk-5NKWR4FF.js → chunk-OERS32LW.js} +1 -1
  14. package/dist/{chunk-GHQH6UC5.js → chunk-OXQISY3J.js} +1 -1
  15. package/dist/chunk-QDOR3GTD.js +9043 -0
  16. package/dist/{chunk-2QX7T24B.js → chunk-QKGL6Q2S.js} +1 -1
  17. package/dist/{chunk-OBYZDT2E.js → chunk-URWDB7YL.js} +15 -78
  18. package/dist/{chunk-ZYT7OQJB.js → chunk-WGRVAGSR.js} +6 -6
  19. package/dist/chunk-ZWDWFMOX.js +1514 -0
  20. package/dist/{ci-ZK3LKYFX.js → ci-FLTJ2UXB.js} +992 -849
  21. package/dist/{cli-ZY5VRIJA.js → cli-THEA6T7N.js} +31 -31
  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/layer4-discovery.d.ts +2 -0
  25. package/dist/commands/ci/machine/actors/db/production-preview.d.ts +4 -3
  26. package/dist/commands/ci/machine/actors/db/sync-schema.d.ts +5 -1
  27. package/dist/commands/ci/machine/actors/test/capabilities.d.ts +2 -13
  28. package/dist/commands/ci/machine/actors/test/index.d.ts +1 -0
  29. package/dist/commands/ci/machine/actors/test/layer-content.d.ts +11 -0
  30. package/dist/commands/ci/machine/commands/ci-pr-internal-profile.d.ts +7 -0
  31. package/dist/commands/ci/machine/commands/ci-step-registry.d.ts +25 -0
  32. package/dist/commands/ci/machine/commands/step-telemetry.d.ts +1 -2
  33. package/dist/commands/ci/machine/contract.d.ts +3 -0
  34. package/dist/commands/ci/machine/guards.d.ts +3 -10
  35. package/dist/commands/ci/machine/helpers.d.ts +1 -1
  36. package/dist/commands/ci/machine/machine-execution-helpers.d.ts +5 -2
  37. package/dist/commands/ci/machine/machine.d.ts +24 -30
  38. package/dist/commands/ci/machine/selectors.d.ts +6 -0
  39. package/dist/commands/ci/machine/types.d.ts +3 -1
  40. package/dist/commands/ci/utils/ci-logging.d.ts +16 -0
  41. package/dist/commands/ci/utils/rls-verification.d.ts +3 -2
  42. package/dist/commands/db/apply/actors/pg-schema-diff-actors.d.ts +1 -0
  43. package/dist/commands/db/apply/contract.d.ts +209 -0
  44. package/dist/commands/db/apply/helpers/fresh-db-handler.d.ts +2 -1
  45. package/dist/commands/db/apply/helpers/index.d.ts +3 -1
  46. package/dist/commands/db/apply/helpers/plan-ast-sql-helpers.d.ts +19 -0
  47. package/dist/commands/db/apply/helpers/plan-ast.d.ts +1 -2
  48. package/dist/commands/db/apply/helpers/plan-check-filter.d.ts +0 -14
  49. package/dist/commands/db/apply/helpers/plan-validator.d.ts +34 -0
  50. package/dist/commands/db/apply/helpers/planner-artifact.d.ts +65 -0
  51. package/dist/commands/db/apply/helpers/retry-logic.d.ts +5 -0
  52. package/dist/commands/db/apply/machine.d.ts +50 -15
  53. package/dist/commands/db/commands/db-apply-error.d.ts +6 -1
  54. package/dist/commands/db/commands/db-apply.d.ts +5 -0
  55. package/dist/commands/db/commands/db-plan.d.ts +3 -0
  56. package/dist/commands/db/commands/db-preview-profile.d.ts +23 -0
  57. package/dist/commands/db/commands/db-preview.d.ts +3 -0
  58. package/dist/commands/db/sync/actors.d.ts +1 -0
  59. package/dist/commands/db/sync/contract.d.ts +16 -0
  60. package/dist/commands/db/sync/guardrail-orchestrator.d.ts +15 -0
  61. package/dist/commands/db/sync/guardrail-reporting.d.ts +12 -0
  62. package/dist/commands/db/sync/index.d.ts +4 -0
  63. package/dist/commands/db/sync/machine.d.ts +18 -13
  64. package/dist/commands/db/sync/schema-guardrail-config-test-support.d.ts +15 -0
  65. package/dist/commands/db/sync/schema-guardrail-config.d.ts +11 -0
  66. package/dist/commands/db/sync/schema-guardrail-ddl-order.d.ts +36 -0
  67. package/dist/commands/db/sync/schema-guardrail-graph-guidance.d.ts +15 -0
  68. package/dist/commands/db/sync/schema-guardrail-graph-metadata.d.ts +41 -0
  69. package/dist/commands/db/sync/schema-guardrail-graph-nodes.d.ts +61 -0
  70. package/dist/commands/db/sync/schema-guardrail-graph-sql-helpers.d.ts +31 -0
  71. package/dist/commands/db/sync/schema-guardrail-graph-types.d.ts +56 -0
  72. package/dist/commands/db/sync/schema-guardrail-graph.d.ts +20 -0
  73. package/dist/commands/db/sync/schema-guardrail-local-blockers.d.ts +7 -0
  74. package/dist/commands/db/sync/schema-guardrail-phases.d.ts +26 -0
  75. package/dist/commands/db/sync/schema-guardrail-production-check.d.ts +23 -0
  76. package/dist/commands/db/sync/schema-guardrail-rewrite.d.ts +46 -0
  77. package/dist/commands/db/sync/schema-guardrail-runtime.d.ts +5 -0
  78. package/dist/commands/db/sync/schema-guardrail-semantic-warnings.d.ts +9 -0
  79. package/dist/commands/db/sync/schema-guardrail-types.d.ts +243 -0
  80. package/dist/commands/db/sync/schema-guardrail.d.ts +10 -0
  81. package/dist/commands/db/utils/declarative-dependency-sql-utils.d.ts +1 -1
  82. package/dist/commands/db/utils/duplicate-function-ownership.d.ts +27 -1
  83. package/dist/commands/db/utils/policy-cross-schema-refs.d.ts +12 -0
  84. package/dist/commands/db/utils/sql-table-extractor.d.ts +6 -0
  85. package/dist/commands/test/commands/layer4-prereqs.d.ts +15 -0
  86. package/dist/{config-loader-GT3HAQ7U.js → config-loader-N5ODNMD5.js} +2 -2
  87. package/dist/db-IDKQ44VX.js +12757 -0
  88. package/dist/{dev-GB5ERUVR.js → dev-LGSMDFJN.js} +7 -6
  89. package/dist/{doctor-ROSWSMLH.js → doctor-GYX73IEW.js} +4 -4
  90. package/dist/{env-WP74UUMO.js → env-KYR6Q7WO.js} +15 -10
  91. package/dist/{env-files-HRNUGZ5O.js → env-files-ONBC47I6.js} +3 -3
  92. package/dist/{hotfix-TOSGTVCW.js → hotfix-RJIAPLAM.js} +4 -4
  93. package/dist/index.js +3 -3
  94. package/dist/{init-35JLDFHI.js → init-2O6ODG5Z.js} +2 -2
  95. package/dist/{inject-test-attrs-XN4I2AOR.js → inject-test-attrs-F5A346UV.js} +3 -3
  96. package/dist/{manifest-EGCAZ4TK.js → manifest-CI4BRWEB.js} +2 -2
  97. package/dist/{observability-CJA5UFIC.js → observability-WNSCJ5FV.js} +2 -2
  98. package/dist/pg-schema-diff-helpers-7377FS2D.js +7 -0
  99. package/dist/{sdk-XK6HQU7S.js → sdk-BTIVPEE5.js} +1 -1
  100. package/dist/{template-check-BDFMT6ZO.js → template-check-VNNQQXCX.js} +10 -0
  101. package/dist/{test-V4KQL574.js → test-QCPN6Z47.js} +74 -46
  102. package/dist/{upgrade-7L4JIE4K.js → upgrade-QZKEI3NJ.js} +2 -2
  103. package/dist/utils/db-url-utils.d.ts +4 -77
  104. package/dist/{vuln-check-G6I4YYDC.js → vuln-check-JRPMUHLF.js} +1 -1
  105. package/dist/{vuln-checker-CT2AYPIS.js → vuln-checker-Q7LSHUHJ.js} +1 -1
  106. package/dist/{watch-AL4LCBRM.js → watch-RFVCEQLH.js} +3 -3
  107. package/dist/{workflow-UZIZ2JUS.js → workflow-UOG6ZZMH.js} +3 -3
  108. package/package.json +3 -3
  109. package/dist/chunk-6E2DRXIL.js +0 -452
  110. package/dist/db-EPI2DQYN.js +0 -18275
@@ -1,24 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
- import { normalizeDatabaseUrlForDdl, parseBoolish, enhanceConnectionError, isIdempotentRoleHazard, detectAppSchemas, formatSchemasForSql } from './chunk-6E2DRXIL.js';
3
+ import { normalizeDatabaseUrlForDdl, parseBoolish, enhanceConnectionError, isIdempotentRoleHazard, detectAppSchemas, formatSchemasForSql, getDbPlanArtifactPath, runDbApply } from './chunk-QDOR3GTD.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-OXQISY3J.js';
10
+ import './chunk-B7C7CLW2.js';
4
11
  import './chunk-QDF7QXBL.js';
5
12
  import { getSnapshotStateName, getSnapshotStatePaths, isSnapshotComplete } from './chunk-XVNDDHAF.js';
6
- import { createInitialSummary, resolveMode, appendGithubStepSummary, buildCiProdApplyStepSummaryMarkdown, setSummaryErrorFromUnknown, writeEnvLocal, startAppBackground, waitForAppReady, executePrSetupBase, createErrorOutput, requireCiAutoApprove, resolveProdApplyInputs, parseIntOr, addGithubMask } from './chunk-RB2ZUS76.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';
7
15
  import { parsePostgresUrl, buildPsqlArgs, buildPsqlEnv, psqlSyncQuery } from './chunk-A6A7JIRD.js';
8
- import { ensureRunaTmpDir, runLogged } from './chunk-6FAU4IGR.js';
16
+ import { ensureRunaTmpDir, runLogged } from './chunk-ELXXQIGW.js';
9
17
  import { createMachineStateChangeLogger } from './chunk-5FT3F36G.js';
10
18
  import { getSafeEnv, getFilteredEnv, redactSecrets } from './chunk-II7VYQEM.js';
11
19
  import { init_constants, init_local_supabase, detectLocalSupabasePorts } from './chunk-QSEF4T3Y.js';
12
20
  import { emitJsonSuccess } from './chunk-KE6QJBZG.js';
13
21
  import './chunk-WJXC4MVY.js';
14
22
  import { setOutputFormat } from './chunk-HKUWEGUX.js';
15
- import './chunk-JMJP4A47.js';
23
+ import './chunk-OERS32LW.js';
24
+ import './chunk-GT5DMS5R.js';
16
25
  import { init_esm_shims } from './chunk-VRXHCR5K.js';
17
26
  import { Command } from 'commander';
18
27
  import { spawnSync, spawn, execFileSync } from 'child_process';
19
28
  import { mkdir, writeFile, readFile } from 'fs/promises';
20
29
  import path4, { join } from 'path';
21
- import { CommandOutcomeSchema, createCLILogger, CLIError, syncFromProduction, GITHUB_API, getClassificationForProfile, formatDuration, 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';
22
31
  import { z } from 'zod';
23
32
  import { existsSync, createWriteStream, readFileSync, readdirSync, statSync, promises, lstatSync } from 'fs';
24
33
  import { resolve4 } from 'dns/promises';
@@ -27,6 +36,7 @@ import { fromPromise, setup, assign, createActor } from 'xstate';
27
36
  import { randomUUID, createHash } from 'crypto';
28
37
  import { execa } from 'execa';
29
38
  import postgres from 'postgres';
39
+ import { resolveLayer4DiscoverySync } from '@runa-ai/runa/test-config';
30
40
  import { glob } from 'glob';
31
41
 
32
42
  createRequire(import.meta.url);
@@ -293,29 +303,30 @@ async function loadCiConfig(params) {
293
303
 
294
304
  // src/commands/ci/utils/ci-logging.ts
295
305
  init_esm_shims();
306
+ var write = (msg) => process.stderr.write(msg + "\n");
296
307
  function logSection(title) {
297
- console.log("");
298
- 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");
299
- console.log(title);
300
- 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");
301
- 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("");
302
313
  }
303
314
  function logKeyValueTable(params) {
304
315
  logSection(params.title);
305
316
  for (const [k, v] of params.rows) {
306
- console.log(`${k}: ${v}`);
317
+ write(`${k}: ${v}`);
307
318
  }
308
319
  }
309
320
  function logPlan(steps) {
310
321
  logSection("Plan");
311
322
  for (const step of steps) {
312
- console.log(`- ${step.id}: ${step.description}`);
323
+ write(`- ${step.id}: ${step.description}`);
313
324
  }
314
325
  }
315
326
  function logNextActions(items) {
316
327
  logSection("Next");
317
328
  for (const item of items) {
318
- console.log(`- ${item}`);
329
+ write(`- ${item}`);
319
330
  }
320
331
  }
321
332
 
@@ -483,7 +494,7 @@ async function githubRequest(params) {
483
494
  url.searchParams.set(k, String(v));
484
495
  }
485
496
  const startTime = Date.now();
486
- console.log(`[DEBUG] GitHub API: ${params.method} ${params.path} (starting)`);
497
+ process.stderr.write(`[DEBUG] GitHub API: ${params.method} ${params.path} (starting)`);
487
498
  const controller = new AbortController();
488
499
  const timeoutId = setTimeout(() => controller.abort(), GITHUB_API_TIMEOUT_MS);
489
500
  try {
@@ -502,7 +513,7 @@ async function githubRequest(params) {
502
513
  const duration = Date.now() - startTime;
503
514
  if (!res.ok) {
504
515
  const text = await res.text().catch(() => "");
505
- console.error(
516
+ process.stderr.write(
506
517
  `[DEBUG] GitHub API: ${params.method} ${params.path} failed (${res.status}) after ${duration}ms`
507
518
  );
508
519
  throw new CLIError(
@@ -513,7 +524,7 @@ async function githubRequest(params) {
513
524
  20
514
525
  );
515
526
  }
516
- console.log(
527
+ process.stderr.write(
517
528
  `[DEBUG] GitHub API: ${params.method} ${params.path} succeeded (${res.status}) in ${duration}ms`
518
529
  );
519
530
  if (res.status === 204) return null;
@@ -523,7 +534,7 @@ async function githubRequest(params) {
523
534
  clearTimeout(timeoutId);
524
535
  const duration = Date.now() - startTime;
525
536
  if (error instanceof Error && error.name === "AbortError") {
526
- console.error(
537
+ process.stderr.write(
527
538
  `[DEBUG] GitHub API: ${params.method} ${params.path} timed out after ${duration}ms`
528
539
  );
529
540
  throw new CLIError(
@@ -534,7 +545,7 @@ async function githubRequest(params) {
534
545
  21
535
546
  );
536
547
  }
537
- console.error(
548
+ process.stderr.write(
538
549
  `[DEBUG] GitHub API: ${params.method} ${params.path} error after ${duration}ms: ${error instanceof Error ? error.message : String(error)}`
539
550
  );
540
551
  throw error;
@@ -661,17 +672,6 @@ async function detectStack(repoRoot, tmpDir, productionDbUrlAdmin) {
661
672
  });
662
673
  return String(res.stdout ?? "").trim();
663
674
  }
664
- function runGitDiffQuiet(repoRoot, diffPaths) {
665
- const result = spawnSync("git", ["diff", "--quiet", "origin/main", "HEAD", "--", ...diffPaths], {
666
- cwd: repoRoot,
667
- stdio: "ignore",
668
- timeout: 3e4
669
- });
670
- if (result.error || result.signal === "SIGTERM") {
671
- return null;
672
- }
673
- return result.status !== 0;
674
- }
675
675
  function getSqlSchemaDiffPaths(repoRoot) {
676
676
  const declarativeSqlPath = path4.join(repoRoot, "supabase/schemas/declarative");
677
677
  const idempotentSqlPath = path4.join(repoRoot, "supabase/schemas/idempotent");
@@ -688,6 +688,38 @@ function getLegacySchemaDiffPath(repoRoot) {
688
688
  const schemaPath = path4.join(detected, "src", "schema");
689
689
  return existsSync(path4.join(repoRoot, schemaPath)) ? `${schemaPath}/` : null;
690
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
+ }
691
723
  function checkIfInitialDeployment(repoRoot, productionDbUrl) {
692
724
  const schemasDir = path4.join(repoRoot, "supabase", "schemas", "declarative");
693
725
  let schemas;
@@ -754,71 +786,32 @@ function checkIfInitialDeployment(repoRoot, productionDbUrl) {
754
786
  };
755
787
  }
756
788
  }
757
- function hasSchemaChanges(repoRoot) {
758
- const sqlDiffPaths = getSqlSchemaDiffPaths(repoRoot);
759
- if (sqlDiffPaths.length > 0) {
760
- return runGitDiffQuiet(repoRoot, sqlDiffPaths);
761
- }
762
- const legacySchemaPath = getLegacySchemaDiffPath(repoRoot);
763
- if (legacySchemaPath) {
764
- 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
+ };
765
798
  }
766
- return null;
767
- }
768
- async function showSchemaDiff(repoRoot, tmpDir) {
769
799
  const diffLog = path4.join(tmpDir, "schema-diff.log");
770
- const safeEnv = getSafeEnv();
771
- const declarativeSqlPath = path4.join(repoRoot, "supabase/schemas/declarative");
772
- if (existsSync(declarativeSqlPath)) {
773
- await runLogged({
774
- cwd: repoRoot,
775
- env: safeEnv,
776
- label: "git diff schema (supabase/schemas/declarative)",
777
- command: "git",
778
- args: ["diff", "origin/main", "HEAD", "--", "supabase/schemas/declarative/"],
779
- logFile: diffLog
780
- });
781
- return;
782
- }
783
- const detected = detectDatabasePackage(repoRoot);
784
- if (detected) {
785
- const schemaPath = path4.join(detected, "src", "schema");
786
- if (existsSync(path4.join(repoRoot, schemaPath))) {
787
- await runLogged({
788
- cwd: repoRoot,
789
- env: safeEnv,
790
- label: `git diff schema (${detected})`,
791
- command: "git",
792
- args: ["diff", "origin/main", "HEAD", "--", `${schemaPath}/`],
793
- logFile: diffLog
794
- });
795
- return;
796
- }
797
- }
798
- for (const candidate of DATABASE_PACKAGE_CANDIDATES) {
799
- const candidatePath = path4.join("packages", candidate, "src", "schema");
800
- if (existsSync(path4.join(repoRoot, candidatePath))) {
801
- await runLogged({
802
- cwd: repoRoot,
803
- env: safeEnv,
804
- label: `git diff schema (packages/${candidate})`,
805
- command: "git",
806
- args: ["diff", "origin/main", "HEAD", "--", `${candidatePath}/`],
807
- logFile: diffLog
808
- });
809
- return;
810
- }
811
- }
812
- if (existsSync(path4.join(repoRoot, "src", "schema"))) {
813
- await runLogged({
814
- cwd: repoRoot,
815
- env: safeEnv,
816
- label: "git diff schema (src/schema)",
817
- command: "git",
818
- args: ["diff", "origin/main", "HEAD", "--", "src/schema/"],
819
- logFile: diffLog
820
- });
821
- }
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
+ };
822
815
  }
823
816
  async function detectRisks(repoRoot, tmpDir, timeoutMs) {
824
817
  const logFile = path4.join(tmpDir, "db-risks.log");
@@ -886,15 +879,12 @@ async function snapshotRestoreLatest(repoRoot, tmpDir, productionDbUrlAdmin, tim
886
879
  timeoutMs
887
880
  });
888
881
  }
889
- function parseApplyLog(logContent) {
890
- const hazards = [];
882
+ function summarizePlanSql(sql) {
891
883
  let statements = 0;
892
884
  const changes = [];
893
- let retryAttempts;
894
- let retryWaitMs;
895
- const createMatches = logContent.match(/CREATE\s+(TABLE|INDEX|POLICY|FUNCTION|TRIGGER)/gi);
896
- const alterMatches = logContent.match(/ALTER\s+(TABLE|COLUMN|POLICY)/gi);
897
- 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);
898
888
  const creates = createMatches?.length ?? 0;
899
889
  const alters = alterMatches?.length ?? 0;
900
890
  const drops = dropMatches?.length ?? 0;
@@ -902,87 +892,123 @@ function parseApplyLog(logContent) {
902
892
  if (creates > 0) changes.push(`${creates} CREATE`);
903
893
  if (alters > 0) changes.push(`${alters} ALTER`);
904
894
  if (drops > 0) changes.push(`${drops} DROP`);
905
- const hazardSet = /* @__PURE__ */ new Set();
906
- const hazardMatches = logContent.matchAll(/-- Hazard (\w+): (.+)/g);
907
- for (const match of hazardMatches) {
908
- hazardSet.add(`${match[1]}: ${match[2]}`);
909
- }
910
- hazards.push(...hazardSet);
911
- const retryMatch = logContent.match(/Retry (\d+)\/(\d+) after (\d+)ms/g);
912
- if (retryMatch) {
913
- retryAttempts = retryMatch.length;
914
- const waitMatches = logContent.matchAll(/after (\d+)ms/g);
915
- retryWaitMs = 0;
916
- for (const m of waitMatches) {
917
- retryWaitMs += Number.parseInt(m[1], 10);
918
- }
895
+ if (!sql.trim()) {
896
+ return { statements: 0, summary: "No schema changes" };
919
897
  }
920
- const metricsMatch = logContent.match(/retries=(\d+)/);
921
- if (metricsMatch) {
922
- 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
+ };
923
910
  }
924
- if (logContent.toLowerCase().includes("no schema changes detected")) {
925
- 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
+ };
926
916
  }
927
- const summary = changes.length > 0 ? changes.join(", ") : "Schema applied";
928
- return { statements, hazards, summary, retryAttempts, retryWaitMs };
917
+ return {
918
+ statements,
919
+ summary: `${statements} structural statement${statements === 1 ? "" : "s"} applied`
920
+ };
929
921
  }
930
- async function applyProductionSchema(repoRoot, tmpDir, productionDbUrlAdmin, productionDbUrl, options) {
931
- const args = [
932
- "exec",
933
- "runa",
934
- "db",
935
- "apply",
936
- "production",
937
- "--auto-approve",
938
- "--no-seed",
939
- "--verbose"
940
- ];
941
- if (options?.allowDataLoss) {
942
- 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
+ }
943
942
  }
944
- if (options?.confirmAuthzUpdate) {
945
- args.push("--confirm-authz-update");
943
+ }
944
+ async function withTimeout(promise, timeoutMs, label) {
945
+ if (!timeoutMs || timeoutMs <= 0) {
946
+ return promise;
946
947
  }
947
- if (options?.maxLockWaitMs !== void 0) {
948
- 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
+ }
949
963
  }
950
- 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");
951
967
  const startTime = Date.now();
952
- await runLogged({
953
- cwd: repoRoot,
954
- env: {
968
+ const plannerArtifactPath = getDbPlanArtifactPath({
969
+ repoRoot,
970
+ environment: "production",
971
+ previewProfile: "full"
972
+ });
973
+ const result = await withTemporaryEnv(
974
+ {
955
975
  ...getFilteredEnv(),
956
- // PRD Naming Convention:
957
- // - GH_DATABASE_URL_ADMIN = postgres role (DDL capable, for pg-schema-diff)
958
- // - GH_DATABASE_URL = drizzle_app role (app runtime)
959
976
  GH_DATABASE_URL_ADMIN: productionDbUrlAdmin,
960
977
  GH_DATABASE_URL: productionDbUrl || productionDbUrlAdmin
961
978
  },
962
- label: "db apply production",
963
- command: "pnpm",
964
- args,
965
- logFile: logPath,
966
- timeoutMs: options?.timeoutMs
967
- });
968
- const totalMs = Date.now() - startTime;
969
- let logContent = "";
970
- try {
971
- const { readFileSync: readFileSync4 } = await import('fs');
972
- logContent = readFileSync4(logPath, "utf-8");
973
- } 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);
974
998
  }
975
- const parsed = parseApplyLog(logContent);
999
+ const totalMs = Date.now() - startTime;
1000
+ const parsed = summarizeApplyResult(result);
976
1001
  return {
977
1002
  statementsExecuted: parsed.statements,
978
- hazards: parsed.hazards,
1003
+ hazards: result.hazards,
979
1004
  changeSummary: parsed.summary,
980
1005
  logPath,
981
1006
  metrics: {
982
- totalMs,
983
- retryAttempts: parsed.retryAttempts,
984
- retryWaitMs: parsed.retryWaitMs
985
- }
1007
+ totalMs: result.metrics?.totalMs ?? totalMs,
1008
+ retryAttempts: result.metrics?.retryAttempts,
1009
+ retryWaitMs: result.metrics?.retryWaitMs
1010
+ },
1011
+ planner: result.planner
986
1012
  };
987
1013
  }
988
1014
  async function auditRecord(repoRoot, tmpDir, productionDbUrlAdmin, params) {
@@ -996,30 +1022,17 @@ async function auditRecord(repoRoot, tmpDir, productionDbUrlAdmin, params) {
996
1022
  logFile: path4.join(tmpDir, "git-commit-message.log")
997
1023
  });
998
1024
  const commitMsg = String(msg.stdout ?? "").trim();
999
- const schemaPaths = [];
1000
- const declarativeSqlPath = path4.join(repoRoot, "supabase/schemas/declarative");
1001
- if (existsSync(declarativeSqlPath)) {
1002
- schemaPaths.push("supabase/schemas/declarative/");
1003
- } else {
1004
- const detected = detectDatabasePackage(repoRoot);
1005
- if (detected) {
1006
- schemaPaths.push(`${detected}/src/schema/`);
1007
- } else {
1008
- for (const candidate of DATABASE_PACKAGE_CANDIDATES) {
1009
- schemaPaths.push(`packages/${candidate}/src/schema/`);
1010
- }
1011
- schemaPaths.push("src/schema/");
1012
- }
1013
- }
1014
- const diff = await runLogged({
1015
- cwd: repoRoot,
1016
- env: safeEnv,
1017
- label: "git diff (schema)",
1018
- command: "git",
1019
- args: ["diff", "origin/main", "HEAD", "--", ...schemaPaths],
1020
- logFile: path4.join(tmpDir, "git-schema-diff.log")
1021
- });
1022
- 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
+ ) : "";
1023
1036
  const diffLimited = diffRaw.length > 1e4 ? diffRaw.slice(0, 1e4) : diffRaw;
1024
1037
  await runLogged({
1025
1038
  cwd: repoRoot,
@@ -1079,6 +1092,24 @@ async function notifyDeployment(repoRoot, tmpDir, params) {
1079
1092
  }
1080
1093
 
1081
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
+ }
1082
1113
  async function maybeNotifyExternal(params) {
1083
1114
  if (params.skipNotify) return;
1084
1115
  const runaAppUrl = process.env.RUNA_APP_URL?.trim() || "";
@@ -1119,6 +1150,17 @@ async function maybeAddGithubLabelAndComment(params) {
1119
1150
  if (result.statementsExecuted > 0) {
1120
1151
  changeSummaryLines.push(`- **Statements executed**: ${result.statementsExecuted}`);
1121
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
+ }
1122
1164
  if (result.hazards.length > 0) {
1123
1165
  const hazardList = result.hazards.map((h) => ` - ${h}`).join("\n");
1124
1166
  changeSummaryLines.push(`- **Hazards**:
@@ -1371,8 +1413,8 @@ var CiProdApplyWorkflow = class {
1371
1413
  id: "schema-diff",
1372
1414
  description: "Show schema diff",
1373
1415
  run: async (ctx) => {
1374
- await showSchemaDiff(ctx.repoRoot, ctx.tmpDir);
1375
- ctx.hasSchemaChanges = hasSchemaChanges(ctx.repoRoot);
1416
+ ctx.schemaChangeSummary = await collectSchemaChangeSummary(ctx.repoRoot, ctx.tmpDir);
1417
+ ctx.hasSchemaChanges = ctx.schemaChangeSummary.hasChanges;
1376
1418
  if (ctx.hasSchemaChanges === false) {
1377
1419
  ctx.logger.info("No schema changes detected \u2014 schema apply steps will be skipped");
1378
1420
  } else if (ctx.hasSchemaChanges === null) {
@@ -1469,6 +1511,17 @@ var CiProdApplyWorkflow = class {
1469
1511
  timeoutMs: ctx.options.applyTimeoutMs
1470
1512
  }
1471
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
+ }
1472
1525
  } catch (error) {
1473
1526
  ctx.logger.error("Schema apply failed; attempting rollback from snapshot");
1474
1527
  await snapshotRestoreLatest(
@@ -1560,18 +1613,18 @@ var CiProdApplyWorkflow = class {
1560
1613
  console.log(`${prefix} done (${duration}ms)`);
1561
1614
  } catch (error) {
1562
1615
  const message = error instanceof Error ? error.message : String(error);
1563
- const timeoutLike = isTimeoutLikeMessage(message);
1616
+ const classified = classifyCiProdApplyError(error);
1564
1617
  phases.push({
1565
1618
  id: step.id,
1566
1619
  label: step.description,
1567
- status: timeoutLike ? "timeout" : "failed",
1620
+ status: classified.retryable ? "timeout" : "failed",
1568
1621
  startedAt: new Date(startedAt).toISOString(),
1569
1622
  endedAt: (/* @__PURE__ */ new Date()).toISOString(),
1570
1623
  durationMs: Date.now() - startedAt,
1571
1624
  error: {
1572
- code: timeoutLike ? "PHASE_TIMEOUT" : "CI_PROD_APPLY_FAILED",
1625
+ code: classified.code === "CI_ERROR" ? "CI_PROD_APPLY_FAILED" : classified.code,
1573
1626
  message,
1574
- retryable: timeoutLike,
1627
+ retryable: classified.retryable,
1575
1628
  phase: step.id
1576
1629
  }
1577
1630
  });
@@ -1710,7 +1763,7 @@ function guardProductionDeployment(dbUrl, commit) {
1710
1763
  };
1711
1764
  }
1712
1765
  if (!idempotencyResult.checkSuccessful && idempotencyResult.error) {
1713
- console.warn(
1766
+ process.stderr.write(
1714
1767
  `[WARN] Idempotency check failed: ${idempotencyResult.error}. Proceeding with deployment.`
1715
1768
  );
1716
1769
  }
@@ -1861,7 +1914,7 @@ var ciProdApplyCommand = new Command("prod-apply").description("Apply production
1861
1914
  console.log(`status: ${summary.status}`);
1862
1915
  console.log(`summary: ${path4.relative(repoRoot, summaryPath)}`);
1863
1916
  logNextActions([
1864
- "Inspect .runa/tmp logs (db-apply-production.log, snapshot-*.log)",
1917
+ "Inspect .runa/tmp artifacts (db-apply-production.json, snapshot-*.log)",
1865
1918
  "If rollback succeeded, database should be restored to pre-deploy snapshot"
1866
1919
  ]);
1867
1920
  await appendGithubStepSummary(
@@ -4002,6 +4055,7 @@ function buildSkipResult(hasUrl) {
4002
4055
  return {
4003
4056
  preview: {
4004
4057
  executed: false,
4058
+ profile: null,
4005
4059
  planSql: null,
4006
4060
  hazards: [],
4007
4061
  hasChanges: false,
@@ -4196,6 +4250,7 @@ var PreviewOutputSchema = z.object({
4196
4250
  filteredPlanSql: z.string().optional(),
4197
4251
  hazards: z.array(z.string()),
4198
4252
  checkOnly: z.boolean().optional(),
4253
+ previewProfile: z.enum(["compare-only", "full"]).optional(),
4199
4254
  metrics: z.object({
4200
4255
  totalMs: z.number(),
4201
4256
  idempotentMs: z.number().optional(),
@@ -4219,6 +4274,7 @@ function tryParseStructuredOutput(stdout) {
4219
4274
  hazards: data.hazards,
4220
4275
  hasChanges,
4221
4276
  checkOnly: data.checkOnly ?? false,
4277
+ previewProfile: data.previewProfile ?? null,
4222
4278
  metrics: data.metrics
4223
4279
  };
4224
4280
  } catch {
@@ -4334,6 +4390,7 @@ function buildErrorResult(error, databaseUrl) {
4334
4390
  return {
4335
4391
  preview: {
4336
4392
  executed: true,
4393
+ profile: null,
4337
4394
  planSql: null,
4338
4395
  hazards: [],
4339
4396
  hasChanges: false,
@@ -4349,6 +4406,7 @@ function buildPreviewFromOutput(stdout, _stderr, fullOutput, repoRoot, productio
4349
4406
  hazards: structured.hazards,
4350
4407
  hazardDetails: void 0,
4351
4408
  hasChanges: structured.hasChanges,
4409
+ profile: structured.previewProfile,
4352
4410
  error: null
4353
4411
  };
4354
4412
  }
@@ -4368,6 +4426,7 @@ function buildPreviewFromOutput(stdout, _stderr, fullOutput, repoRoot, productio
4368
4426
  hazards,
4369
4427
  hazardDetails: hazardDetails.length > 0 ? hazardDetails : void 0,
4370
4428
  hasChanges,
4429
+ profile: null,
4371
4430
  error: enhanceConnectionError(rawError, productionUrl)
4372
4431
  };
4373
4432
  }
@@ -4381,6 +4440,7 @@ function buildSuccessPreviewResult(exitCode, stdout, stderr, fullOutput, repoRoo
4381
4440
  hazards: preview.hazards,
4382
4441
  hazardDetails: preview.hazardDetails,
4383
4442
  hasChanges: preview.hasChanges,
4443
+ profile: preview.profile ?? null,
4384
4444
  error: isSuccess ? null : preview.error
4385
4445
  }
4386
4446
  };
@@ -4394,9 +4454,11 @@ var productionPreviewActor = fromPromise(
4394
4454
  }
4395
4455
  const logFile = path4.join(tmpDir, "ci-production-preview.log");
4396
4456
  const timeoutMs = resolveProductionPreviewTimeoutMs();
4397
- console.log("\u25B6 production preview (dry-run): runa db apply production --check --compare-only");
4398
4457
  console.log(
4399
- "\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)"
4400
4462
  );
4401
4463
  console.log(`\u25B6 production preview timeout: ${formatDurationMs(timeoutMs)}`);
4402
4464
  const heartbeat = startHeartbeat("production preview");
@@ -4406,7 +4468,7 @@ var productionPreviewActor = fromPromise(
4406
4468
  try {
4407
4469
  const child = execa(
4408
4470
  "pnpm",
4409
- ["exec", "runa", "db", "apply", "production", "--check", "--compare-only"],
4471
+ ["exec", "runa", "db", "preview", "production", "--profile", "compare-only"],
4410
4472
  {
4411
4473
  cwd: repoRoot,
4412
4474
  env: {
@@ -4593,7 +4655,6 @@ function findBaseRef(repoRoot) {
4593
4655
  stdio: ["pipe", "pipe", "pipe"]
4594
4656
  });
4595
4657
  if (checkOriginMain.status === 0) {
4596
- console.log("[DEBUG] getSchemaGitDiff: Using origin/main as base");
4597
4658
  return "origin/main";
4598
4659
  }
4599
4660
  const baseRef = process.env.GITHUB_BASE_REF;
@@ -4604,9 +4665,6 @@ function findBaseRef(repoRoot) {
4604
4665
  stdio: ["pipe", "pipe", "pipe"]
4605
4666
  });
4606
4667
  if (checkBaseRef.status === 0) {
4607
- console.log(
4608
- `[DEBUG] getSchemaGitDiff: Using origin/${baseRef} as base (from GITHUB_BASE_REF)`
4609
- );
4610
4668
  return `origin/${baseRef}`;
4611
4669
  }
4612
4670
  }
@@ -4616,65 +4674,62 @@ function findBaseRef(repoRoot) {
4616
4674
  stdio: ["pipe", "pipe", "pipe"]
4617
4675
  });
4618
4676
  if (mergeBaseResult.status === 0 && mergeBaseResult.stdout?.trim()) {
4619
- console.log("[DEBUG] getSchemaGitDiff: Using merge-base as base");
4620
4677
  return mergeBaseResult.stdout.trim();
4621
4678
  }
4622
- console.log("[DEBUG] getSchemaGitDiff: No valid base reference found");
4623
4679
  return null;
4624
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
+ }
4625
4717
  function getSchemaGitDiff(repoRoot) {
4626
4718
  try {
4627
4719
  const baseRef = findBaseRef(repoRoot);
4628
4720
  if (!baseRef) {
4629
- console.log("[DEBUG] getSchemaGitDiff: Cannot determine base reference, skipping diff");
4630
- return null;
4631
- }
4632
- const filesResult = spawnSync(
4633
- "git",
4634
- ["diff", "--name-only", `${baseRef}...HEAD`, "--", "supabase/schemas/"],
4635
- { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4636
- );
4637
- if (filesResult.status !== 0) {
4638
- console.log(
4639
- `[DEBUG] getSchemaGitDiff: git diff failed: ${filesResult.stderr || "unknown error"}`
4640
- );
4641
- return null;
4642
- }
4643
- const filesChanged = (filesResult.stdout || "").split("\n").filter((f) => f.trim().length > 0);
4644
- console.log(`[DEBUG] getSchemaGitDiff: Found ${filesChanged.length} changed schema file(s)`);
4645
- if (filesChanged.length === 0) {
4646
4721
  return null;
4647
4722
  }
4648
- const statResult = spawnSync(
4649
- "git",
4650
- ["diff", "--stat", `${baseRef}...HEAD`, "--", "supabase/schemas/"],
4651
- { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4652
- );
4653
- const statOutput = statResult.stdout || "";
4654
- const statMatch = statOutput.match(/(\d+) insertions?\(\+\)/);
4655
- const delMatch = statOutput.match(/(\d+) deletions?\(-\)/);
4656
- const linesAdded = statMatch ? Number.parseInt(statMatch[1], 10) : 0;
4657
- const linesDeleted = delMatch ? Number.parseInt(delMatch[1], 10) : 0;
4658
4723
  const diffResult = spawnSync(
4659
4724
  "git",
4660
4725
  ["diff", "--no-color", `${baseRef}...HEAD`, "--", "supabase/schemas/"],
4661
4726
  { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4662
4727
  );
4663
- const fullDiff = diffResult.stdout || "";
4664
- const maxLines = 100;
4665
- const diffLines = fullDiff.split("\n");
4666
- const diffSummary = diffLines.length > maxLines ? `${diffLines.slice(0, maxLines).join("\n")}
4667
- ... (${diffLines.length - maxLines} more lines)` : fullDiff;
4668
- return {
4669
- filesChanged,
4670
- linesAdded,
4671
- linesDeleted,
4672
- diffSummary
4673
- };
4674
- } catch (error) {
4675
- console.log(
4676
- `[DEBUG] getSchemaGitDiff: Exception: ${error instanceof Error ? error.message : String(error)}`
4677
- );
4728
+ if (diffResult.status !== 0) {
4729
+ return null;
4730
+ }
4731
+ return extractSchemaGitDiffSnapshot(diffResult.stdout || "");
4732
+ } catch {
4678
4733
  return null;
4679
4734
  }
4680
4735
  }
@@ -5249,7 +5304,7 @@ function shouldSkipSchemaPostCheck(context) {
5249
5304
  return context.mode === "ci-pr-local" && context.executionEnv === "github-actions";
5250
5305
  }
5251
5306
  function shouldReuseCiReferenceStats(context) {
5252
- 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;
5253
5308
  }
5254
5309
  function mergeLayerResults(coreResults, e2eResults) {
5255
5310
  return { ...coreResults, ...e2eResults };
@@ -5385,6 +5440,11 @@ init_esm_shims();
5385
5440
 
5386
5441
  // src/commands/ci/commands/ci-pr-capabilities.ts
5387
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
5388
5448
  async function pathExists(p) {
5389
5449
  return promises.access(p).then(() => true).catch(() => false);
5390
5450
  }
@@ -5523,12 +5583,22 @@ function isPlaywrightIgnored(filePath) {
5523
5583
  async function detectGeneratedE2eCapabilities(params) {
5524
5584
  const caps = /* @__PURE__ */ new Set();
5525
5585
  const diagnostics = {};
5526
- const layer4Dir = path4.join(params.repoRoot, ".runa", "tests", "layer4");
5527
- 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
+ }
5528
5598
  const hasLayer4Dir = await pathExists(layer4Dir);
5529
5599
  const hasTmpDir = await pathExists(tmpDir);
5530
5600
  if (!hasLayer4Dir && !hasTmpDir) {
5531
- diagnostics["e2e.generatedLayer4Present"] = "Missing .runa/tests/layer4 and .runa/tmp";
5601
+ diagnostics["e2e.generatedLayer4Present"] = `Missing current Layer 4 artifact dirs (${layer4Dir}, ${tmpDir})`;
5532
5602
  diagnostics["e2e.generatedLayer4HasUnknown"] = "n/a (layer4 tests not present)";
5533
5603
  return { capabilities: caps, diagnostics };
5534
5604
  }
@@ -5687,37 +5757,89 @@ function assertCapabilities(params) {
5687
5757
  );
5688
5758
  }
5689
5759
 
5690
- // src/commands/ci/commands/ci-layer-content.ts
5691
- init_esm_shims();
5692
- var MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
5693
- var SAFE_GLOB_OPTIONS = {
5694
- follow: false
5695
- // Don't follow symlinks (security + performance)
5696
- };
5697
- function createBaseStatus(layer, contentType, source = "manifest") {
5698
- return { layer, contentType, source };
5699
- }
5700
- function createSuccessStatus(base, count, source) {
5701
- return {
5702
- ...base,
5703
- hasContent: true,
5704
- count,
5705
- source: source ?? base.source
5706
- };
5707
- }
5708
- function createSkipStatus(base, skipReason) {
5709
- return {
5710
- ...base,
5711
- hasContent: false,
5712
- count: 0,
5713
- skipReason
5714
- };
5715
- }
5716
- function safeReadFile(filePath) {
5717
- try {
5718
- const lstats = lstatSync(filePath);
5719
- if (lstats.isSymbolicLink()) {
5720
- return null;
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
+
5812
+ // src/commands/ci/commands/ci-layer-content.ts
5813
+ init_esm_shims();
5814
+ var MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
5815
+ var SAFE_GLOB_OPTIONS = {
5816
+ follow: false
5817
+ // Don't follow symlinks (security + performance)
5818
+ };
5819
+ function createBaseStatus(layer, contentType, source = "manifest") {
5820
+ return { layer, contentType, source };
5821
+ }
5822
+ function createSuccessStatus(base, count, source) {
5823
+ return {
5824
+ ...base,
5825
+ hasContent: true,
5826
+ count,
5827
+ source: source ?? base.source
5828
+ };
5829
+ }
5830
+ function createSkipStatus(base, skipReason) {
5831
+ return {
5832
+ ...base,
5833
+ hasContent: false,
5834
+ count: 0,
5835
+ skipReason
5836
+ };
5837
+ }
5838
+ function safeReadFile(filePath) {
5839
+ try {
5840
+ const lstats = lstatSync(filePath);
5841
+ if (lstats.isSymbolicLink()) {
5842
+ return null;
5721
5843
  }
5722
5844
  const stats = statSync(filePath);
5723
5845
  if (stats.size > MAX_FILE_SIZE_BYTES) {
@@ -6170,71 +6292,36 @@ async function detectLayerContent(repoRoot, selectedLayers, manifestPath) {
6170
6292
  return { layers, skippedLayers, runnableLayers };
6171
6293
  }
6172
6294
 
6173
- // src/commands/ci/machine/actors/test/capabilities.ts
6174
- var capabilitiesActor = fromPromise(
6295
+ // src/commands/ci/machine/actors/test/layer-content.ts
6296
+ var layerContentActor = fromPromise(
6175
6297
  async ({ input }) => {
6176
- const { repoRoot, tmpDir, databaseUrlApp, baseUrl, selectedLayers } = input;
6298
+ const { repoRoot, selectedLayers } = input;
6177
6299
  try {
6178
- const detected = await detectCiCapabilities({
6179
- repoRoot,
6180
- tmpDir,
6181
- databaseUrlApp,
6182
- baseUrl
6183
- });
6184
6300
  const layerContent = await detectLayerContent(repoRoot, selectedLayers);
6185
- return { detected, layerContent };
6301
+ return { layerContent };
6186
6302
  } catch (error) {
6187
- const errorMsg = error instanceof Error ? error.message : String(error);
6188
- console.warn(`[Capabilities] Layer content detection failed: ${errorMsg}`);
6189
- const fallbackLayers = {};
6190
- for (const layer of selectedLayers) {
6191
- fallbackLayers[layer] = {
6192
- layer,
6193
- hasContent: true,
6194
- // Conservative: assume content exists
6195
- contentType: "machines",
6196
- // Placeholder
6197
- count: 0,
6198
- source: "filesystem",
6199
- skipReason: `Detection failed: ${errorMsg}`
6200
- // Track why we're using fallback
6201
- };
6202
- }
6303
+ const errorMessage = error instanceof Error ? error.message : String(error);
6304
+ console.warn(`[Layer Content] Detection failed: ${errorMessage}`);
6203
6305
  const fallbackLayerContent = {
6204
- 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
+ ),
6205
6319
  skippedLayers: [],
6206
6320
  runnableLayers: selectedLayers
6207
6321
  };
6208
6322
  return {
6209
- detected: { capabilities: [], diagnostics: {} },
6210
6323
  layerContent: fallbackLayerContent,
6211
- error: errorMsg
6212
- };
6213
- }
6214
- }
6215
- );
6216
- fromPromise(
6217
- async ({ input }) => {
6218
- const { config, repoKind, layer, detected } = input;
6219
- try {
6220
- const required = resolveRequiredCapabilities({ config, repoKind, layer });
6221
- const missing = required.filter((r) => !detected.capabilities.includes(r));
6222
- if (missing.length > 0) {
6223
- assertCapabilities({
6224
- required,
6225
- detected,
6226
- label: `Layer ${layer} capability gate`
6227
- });
6228
- }
6229
- return { passed: true, required, missing: [] };
6230
- } catch (error) {
6231
- const required = resolveRequiredCapabilities({ config, repoKind, layer });
6232
- const missing = required.filter((r) => !detected.capabilities.includes(r));
6233
- return {
6234
- passed: false,
6235
- required,
6236
- missing,
6237
- error: error instanceof Error ? error.message : String(error)
6324
+ error: errorMessage
6238
6325
  };
6239
6326
  }
6240
6327
  }
@@ -6502,9 +6589,6 @@ function getEffectiveMode(context) {
6502
6589
  function isCiLocalMode(context) {
6503
6590
  return getEffectiveMode(context) === "ci-local";
6504
6591
  }
6505
- function isCiPrLocalMode(context) {
6506
- return getEffectiveMode(context) === "ci-pr-local";
6507
- }
6508
6592
  function isCiPrMode(context) {
6509
6593
  return getEffectiveMode(context) === "ci-pr-local";
6510
6594
  }
@@ -6526,7 +6610,9 @@ function shouldSkipStaticChecks(context) {
6526
6610
  return shouldSkipStep(context, "staticChecks");
6527
6611
  }
6528
6612
  function shouldSkipBuild(context) {
6529
- 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;
6530
6616
  }
6531
6617
  function shouldSkipAppStart(context) {
6532
6618
  return shouldSkipStep(context, "appStart");
@@ -6553,9 +6639,6 @@ function shouldPostGitHubComment(context) {
6553
6639
  if (context.input.skipGithubComment === true) return false;
6554
6640
  return true;
6555
6641
  }
6556
- function isBlockingPhase(context) {
6557
- return isCiPrMode(context) && context.phase === "blocking";
6558
- }
6559
6642
  function isObservabilityPhase(context) {
6560
6643
  return isCiPrMode(context) && context.phase === "observability";
6561
6644
  }
@@ -6568,7 +6651,7 @@ function shouldRunPrExecutionPhase(context) {
6568
6651
  }
6569
6652
  function shouldRunPrObservabilityPhase(context) {
6570
6653
  if (!isCiPrMode(context)) return true;
6571
- return !isBlockingPhase(context) && !isTestPhase(context);
6654
+ return !isTestPhase(context);
6572
6655
  }
6573
6656
  function hasError(context) {
6574
6657
  return context.error !== null;
@@ -7315,9 +7398,10 @@ function truncateSql(sql, maxLength) {
7315
7398
  }
7316
7399
  function generatePreviewDetails(prodPreview) {
7317
7400
  const truncatedSql = truncateSql(prodPreview.planSql || "", 2e3);
7401
+ const profileLabel = prodPreview.profile ?? "compare-only";
7318
7402
  return [
7319
7403
  "<details>",
7320
- "<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>`,
7321
7405
  "",
7322
7406
  "**\u672C\u756A\u306B\u9069\u7528\u4E88\u5B9A\u306ESQL:**",
7323
7407
  "",
@@ -7396,7 +7480,7 @@ function generatePreviewChangesDetectedSection(prodPreview, previewOnlyChanges)
7396
7480
  ];
7397
7481
  if (previewOnlyChanges) {
7398
7482
  lines.push(
7399
- "> \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",
7400
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",
7401
7485
  ""
7402
7486
  );
@@ -7439,7 +7523,7 @@ function generateComparisonMismatchSection(schemaStats, expectedDrift) {
7439
7523
  const lines = [
7440
7524
  "> \u{1F536} **\u672C\u756ADB\u3068\u306E\u5DEE\u5206\u691C\u51FA - \u672C\u756A\u30C7\u30D7\u30ED\u30A4\u304C\u5FC5\u8981\u3067\u3059**",
7441
7525
  ">",
7442
- "> `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",
7443
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",
7444
7528
  ""
7445
7529
  ];
@@ -7498,13 +7582,6 @@ function generateProductionPreviewSection(phase, prodPreview, schemaDrift, schem
7498
7582
  function getDeferredProductionPreviewSection(phase, prodPreview) {
7499
7583
  if (prodPreview?.executed) return [];
7500
7584
  switch (phase) {
7501
- case "blocking":
7502
- return [
7503
- "**\u672C\u756A\u30D7\u30EC\u30D3\u30E5\u30FC**: \u23ED\uFE0F blocking phase \u3067\u306F\u5B9F\u884C\u3057\u307E\u305B\u3093",
7504
- "",
7505
- "> \u672C\u756A\u6BD4\u8F03\u3068 schema stats \u306F blocking CI \u306E\u30AF\u30EA\u30C6\u30A3\u30AB\u30EB\u30D1\u30B9\u304B\u3089\u5207\u308A\u96E2\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u672C\u756A\u53CD\u6620\u524D\u306B\u306F `deploy-db.yml` \u304C compare-only dry-run \u3067\u518D\u691C\u8A3C\u3057\u307E\u3059\u3002",
7506
- ""
7507
- ];
7508
7585
  case "test":
7509
7586
  return [
7510
7587
  "**\u672C\u756A\u30D7\u30EC\u30D3\u30E5\u30FC**: \u23ED\uFE0F phase=test \u306E\u305F\u3081\u30B9\u30AD\u30C3\u30D7",
@@ -7516,20 +7593,20 @@ function getDeferredProductionPreviewSection(phase, prodPreview) {
7516
7593
  return [];
7517
7594
  }
7518
7595
  }
7519
- function shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges2) {
7520
- if (hasSchemaChanges2) return true;
7596
+ function shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges) {
7597
+ if (hasSchemaChanges) return true;
7521
7598
  if (productionPreview?.error) return true;
7522
7599
  if (!productionPreview?.executed && schemaDrift?.gitDiff?.filesChanged?.length) return true;
7523
7600
  return false;
7524
7601
  }
7525
7602
  function generateDeploySection(exitCode, layerResults, phase, gitBranchName, productionPreview, schemaDrift, schemaStats, expectedDrift, env) {
7526
7603
  if (!checkIfDeployable(exitCode, layerResults)) return [];
7527
- if (phase === "blocking" || phase === "test") return [];
7604
+ if (phase === "test") return [];
7528
7605
  const deployWorkflowUrl = env.repository ? `${env.serverUrl}/${env.repository}/actions/workflows/deploy-db.yml` : null;
7529
7606
  const signals = getProductionSchemaSignals(productionPreview, schemaStats, expectedDrift);
7530
- const hasSchemaChanges2 = signals.requiresDeploy;
7531
- const headerEmoji = hasSchemaChanges2 ? "\u{1F6A8}" : "\u{1F680}";
7532
- 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";
7533
7610
  const lines = ["---", "", `### ${headerEmoji} ${headerText}`, ""];
7534
7611
  if (exitCode !== 0)
7535
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", "");
@@ -7542,9 +7619,9 @@ function generateDeploySection(exitCode, layerResults, phase, gitBranchName, pro
7542
7619
  expectedDrift
7543
7620
  )
7544
7621
  );
7545
- const showButton = shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges2);
7622
+ const showButton = shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges);
7546
7623
  if (deployWorkflowUrl && showButton) {
7547
- 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";
7548
7625
  lines.push(
7549
7626
  `**\u30C7\u30D7\u30ED\u30A4\u30D6\u30E9\u30F3\u30C1**: \`${gitBranchName}\``,
7550
7627
  "",
@@ -7559,7 +7636,18 @@ function generateDeploySection(exitCode, layerResults, phase, gitBranchName, pro
7559
7636
  function generateObservabilitySectionBody(input) {
7560
7637
  const env = getGitHubEnv();
7561
7638
  const gitBranchName = input.prContext?.headBranch ?? input.branchName ?? "unknown";
7562
- const deferredPreviewLines = input.phase === "blocking" || input.phase === "test" ? generateProductionPreviewSection(
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(
7563
7651
  input.phase,
7564
7652
  input.productionPreview,
7565
7653
  input.schemaDrift,
@@ -7572,18 +7660,8 @@ function generateObservabilitySectionBody(input) {
7572
7660
  input.schemaDrift?.gitDiff ?? null,
7573
7661
  input.expectedDrift ?? []
7574
7662
  ),
7575
- ...deferredPreviewLines,
7576
- ...generateDeploySection(
7577
- input.exitCode,
7578
- input.layerResults,
7579
- input.phase,
7580
- gitBranchName,
7581
- input.productionPreview,
7582
- input.schemaDrift,
7583
- input.schemaStats ?? null,
7584
- input.expectedDrift ?? [],
7585
- env
7586
- )
7663
+ ...standalonePreviewLines,
7664
+ ...deploySection
7587
7665
  ];
7588
7666
  return lines.join("\n").trim();
7589
7667
  }
@@ -7652,9 +7730,9 @@ function generateCommentBody(input) {
7652
7730
 
7653
7731
  // src/commands/ci/machine/formatters/sections/progress-comment.ts
7654
7732
  init_esm_shims();
7655
- function calculateTotalElapsed(stepTimings2) {
7656
- if (!stepTimings2) return 0;
7657
- 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);
7658
7736
  }
7659
7737
  function formatProgressBar(_currentStep, completedSteps, failedStep, skippedSteps) {
7660
7738
  const total = CI_STEPS.length;
@@ -7807,12 +7885,12 @@ function generateInfoLine(branchName, supabaseUrl, runUrl) {
7807
7885
  if (runUrl) items.push(`[\u{1F517} \u30EF\u30FC\u30AF\u30D5\u30ED\u30FC](${runUrl})`);
7808
7886
  return [items.join(" \xB7 "), ""];
7809
7887
  }
7810
- function generateProgressSteps(currentStep, completedSteps, failedStep, skippedSteps, stepTimings2, schemaDrift, layerResults, productionPreview) {
7888
+ function generateProgressSteps(currentStep, completedSteps, failedStep, skippedSteps, stepTimings, schemaDrift, layerResults, productionPreview) {
7811
7889
  const lines = ["### \u9032\u6357", ""];
7812
7890
  for (const { step, label, detail } of CI_STEPS) {
7813
7891
  const status = getStepStatus(step, currentStep, completedSteps, failedStep, skippedSteps);
7814
7892
  const icon = getStepIcon(status);
7815
- const timing = stepTimings2?.[step];
7893
+ const timing = stepTimings?.[step];
7816
7894
  const timingStr = timing !== void 0 ? ` \`${formatDuration3(timing)}\`` : "";
7817
7895
  const stepDetail = getStepDetail(step, status, schemaDrift, layerResults);
7818
7896
  const runningDetail = getRunningStepDetail(step, detail, productionPreview);
@@ -7847,7 +7925,7 @@ function getFixSuggestions(failedStep) {
7847
7925
  ],
7848
7926
  observability: [
7849
7927
  "production preview / schema stats \u306E\u30ED\u30B0\u3092\u78BA\u8A8D",
7850
- "\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",
7851
7929
  "schema stats \u304C\u5FC5\u8981\u306A\u30B1\u30FC\u30B9\u304B expected drift \u8A2D\u5B9A\u3092\u78BA\u8A8D"
7852
7930
  ],
7853
7931
  staticChecks: [
@@ -7946,8 +8024,6 @@ function generateProductionSchemaSection(productionPreview, phase, deployStatus,
7946
8024
  function getDeferredProductionPreviewMessage(phase, productionPreview) {
7947
8025
  if (productionPreview?.executed) return null;
7948
8026
  switch (phase) {
7949
- case "blocking":
7950
- return "\u23ED\uFE0F _blocking phase \u306E\u305F\u3081\u3001\u672C\u756A\u6BD4\u8F03\u3068 schema stats \u306F\u3053\u306E job \u3067\u306F\u5B9F\u884C\u3057\u307E\u305B\u3093_";
7951
8027
  case "test":
7952
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_";
7953
8029
  default:
@@ -7996,14 +8072,15 @@ function appendChangesPendingSection(lines, productionPreview, env) {
7996
8072
  if (!productionPreview) return;
7997
8073
  const hazardCount = productionPreview.hazards.length;
7998
8074
  const hazardText = hazardCount > 0 ? ` (\u26A0\uFE0F ${hazardCount}\u4EF6\u306E\u30CF\u30B6\u30FC\u30C9)` : "";
7999
- 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}`, "");
8000
8077
  if (productionPreview.planSql) {
8001
8078
  appendPlanSqlPreview(lines, productionPreview.planSql);
8002
8079
  }
8003
8080
  appendDeployLink(lines, env);
8004
8081
  }
8005
8082
  function generateDbDeploySection(phase, layerResults, gitBranchName, productionPreview, env) {
8006
- if (phase === "blocking" || phase === "test") return [];
8083
+ if (phase === "test") return [];
8007
8084
  if (!checkBlockingLayersPassed(layerResults)) return [];
8008
8085
  if (productionPreview?.hasChanges) return [];
8009
8086
  const deployWorkflowUrl = env.repository ? `${env.serverUrl}/${env.repository}/actions/workflows/deploy-db.yml` : null;
@@ -8036,13 +8113,13 @@ function generateProgressCommentBody(input) {
8036
8113
  productionPreview,
8037
8114
  deployStatus,
8038
8115
  error,
8039
- stepTimings: stepTimings2
8116
+ stepTimings
8040
8117
  } = input;
8041
8118
  const env = getGitHubEnv();
8042
8119
  const runUrl = env.repository && env.runId ? `${env.serverUrl}/${env.repository}/actions/runs/${env.runId}` : null;
8043
8120
  const effectiveBranchName = branchName ?? input.prContext?.headBranch ?? "unknown";
8044
8121
  const supabaseUrl = supabase?.supabaseUrl ?? "";
8045
- const totalElapsed = calculateTotalElapsed(stepTimings2);
8122
+ const totalElapsed = calculateTotalElapsed(stepTimings);
8046
8123
  const gitBranchName = input.prContext?.headBranch ?? branchName ?? "unknown";
8047
8124
  const lines = [
8048
8125
  ...generateProgressHeader(
@@ -8058,7 +8135,7 @@ function generateProgressCommentBody(input) {
8058
8135
  completedSteps,
8059
8136
  failedStep,
8060
8137
  input.skippedSteps ?? [],
8061
- stepTimings2,
8138
+ stepTimings,
8062
8139
  schemaDrift,
8063
8140
  input.layerResults,
8064
8141
  productionPreview
@@ -8115,7 +8192,7 @@ function createCommentInput(context, options) {
8115
8192
  originalSelectedLayers
8116
8193
  };
8117
8194
  }
8118
- function createProgressCommentInput(context, currentStep, completedSteps, failedStep = null, stepTimings2) {
8195
+ function createProgressCommentInput(context, currentStep, completedSteps, failedStep = null, stepTimings) {
8119
8196
  const phase = context.phase;
8120
8197
  const skippedSteps = getSkippedStepsForPhase(phase);
8121
8198
  return {
@@ -8131,7 +8208,7 @@ function createProgressCommentInput(context, currentStep, completedSteps, failed
8131
8208
  layerResults: context.layerResults,
8132
8209
  productionPreview: context.productionPreview,
8133
8210
  error: context.error,
8134
- stepTimings: stepTimings2
8211
+ stepTimings
8135
8212
  };
8136
8213
  }
8137
8214
 
@@ -8321,7 +8398,12 @@ function createSummaryInput(context) {
8321
8398
  endedAt: now,
8322
8399
  durationMs: Date.now() - context.startTime,
8323
8400
  repoKind: context.repoKind,
8324
- 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
+ },
8325
8407
  diagnostics: context.diagnostics,
8326
8408
  steps: {},
8327
8409
  layers: formatLayerSummary(context.layerResults),
@@ -8458,6 +8540,12 @@ function createStaticChecksInput(context) {
8458
8540
  tmpDir: assertTmpDir(context)
8459
8541
  };
8460
8542
  }
8543
+ function createLayerContentInput(context) {
8544
+ return {
8545
+ repoRoot: assertRepoRoot(context),
8546
+ selectedLayers: context.selectedLayers
8547
+ };
8548
+ }
8461
8549
  function createBuildAndPlaywrightInput(context) {
8462
8550
  return {
8463
8551
  repoRoot: assertRepoRoot(context),
@@ -8486,8 +8574,7 @@ function createCapabilitiesInput(context) {
8486
8574
  repoRoot: assertRepoRoot(context),
8487
8575
  tmpDir: assertTmpDir(context),
8488
8576
  databaseUrlApp: getDatabaseUrlForRuntime(context),
8489
- baseUrl: context.baseUrl ?? `http://localhost:${context.app?.port ?? 3e3}`,
8490
- selectedLayers: context.selectedLayers
8577
+ baseUrl: context.appStarted ? context.baseUrl ?? `http://localhost:${context.app?.port ?? 3e3}` : null
8491
8578
  };
8492
8579
  }
8493
8580
  function createRunCoreTestsInput(context) {
@@ -8511,7 +8598,94 @@ function deriveCoreLayerResults(output) {
8511
8598
  return convertLayerResults(results);
8512
8599
  }
8513
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
+
8514
8610
  // src/commands/ci/machine/machine.ts
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
+ }
8515
8689
  var ciMachine = setup({
8516
8690
  types: {},
8517
8691
  actors: {
@@ -8534,6 +8708,7 @@ var ciMachine = setup({
8534
8708
  playwrightInstall: playwrightInstallActor,
8535
8709
  buildAndPlaywright: buildAndPlaywrightActor,
8536
8710
  // Test
8711
+ layerContent: layerContentActor,
8537
8712
  capabilities: capabilitiesActor,
8538
8713
  runLayers: runLayersActor,
8539
8714
  // Finalize
@@ -8541,7 +8716,7 @@ var ciMachine = setup({
8541
8716
  },
8542
8717
  guards: {
8543
8718
  isCiLocalMode: ({ context }) => isCiLocalMode(context),
8544
- isCiPrLocalMode: ({ context }) => isCiPrLocalMode(context),
8719
+ isCiPrLocalMode: ({ context }) => isCiPrMode(context),
8545
8720
  isCiPrMode: ({ context }) => isCiPrMode(context),
8546
8721
  shouldResetDb: ({ context }) => shouldResetDb(context),
8547
8722
  shouldPullProduction: ({ context }) => shouldPullProduction(context),
@@ -8903,16 +9078,12 @@ var ciMachine = setup({
8903
9078
  onDone: {
8904
9079
  target: "staticChecks",
8905
9080
  actions: assign({
8906
- rolesSetup: ({ event }) => event.output.skipped !== true,
8907
- stepOverrides: ({ context, event }) => ({
8908
- ...context.stepOverrides,
8909
- ...event.output.skipped ? {
8910
- "postSeedPr.execution.setupRoles": {
8911
- status: "skipped",
8912
- reason: event.output.skipReason ?? "Skipped because db:setup-roles is unavailable"
8913
- }
8914
- } : {}
8915
- }),
9081
+ rolesSetup: ({ event }) => getSetupRolesApplied(event.output),
9082
+ stepOverrides: ({ context, event }) => mergeSetupRolesStepOverrides(
9083
+ context,
9084
+ event.output,
9085
+ "postSeedPr.execution.setupRoles"
9086
+ ),
8916
9087
  supabase: ({ context, event }) => mergeSetupRolesSupabase(context, event.output.appDatabaseUrl)
8917
9088
  })
8918
9089
  },
@@ -8923,7 +9094,7 @@ var ciMachine = setup({
8923
9094
  }
8924
9095
  },
8925
9096
  staticChecks: {
8926
- always: [{ guard: "shouldSkipStaticChecks", target: "buildAndPlaywright" }],
9097
+ always: [{ guard: "shouldSkipStaticChecks", target: "layerContent" }],
8927
9098
  invoke: {
8928
9099
  src: "staticChecks",
8929
9100
  input: ({ context }) => createStaticChecksInput(context),
@@ -8937,7 +9108,7 @@ var ciMachine = setup({
8937
9108
  })
8938
9109
  },
8939
9110
  {
8940
- target: "buildAndPlaywright",
9111
+ target: "layerContent",
8941
9112
  actions: assign({ staticChecksPassed: true })
8942
9113
  }
8943
9114
  ],
@@ -8950,6 +9121,22 @@ var ciMachine = setup({
8950
9121
  }
8951
9122
  }
8952
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
+ },
8953
9140
  buildAndPlaywright: {
8954
9141
  always: [{ guard: "shouldSkipBuild", target: "appStart" }],
8955
9142
  invoke: {
@@ -9026,9 +9213,7 @@ var ciMachine = setup({
9026
9213
  onDone: {
9027
9214
  target: "runCoreTests",
9028
9215
  actions: assign({
9029
- capabilities: ({ event }) => event.output.detected?.capabilities ?? [],
9030
- selectedLayers: ({ context, event }) => filterRunnableLayers(context.selectedLayers, event.output.layerContent),
9031
- layerSkipReasons: ({ event }) => extractLayerSkipReasons(event.output.layerContent)
9216
+ capabilities: ({ event }) => getDetectedCapabilities(event.output)
9032
9217
  })
9033
9218
  },
9034
9219
  onError: {
@@ -9056,10 +9241,7 @@ var ciMachine = setup({
9056
9241
  target: "coreTestsComplete",
9057
9242
  actions: assign({
9058
9243
  testsRun: true,
9059
- layerResults: ({ context, event }) => {
9060
- const nextResults = convertLayerResults(event.output.results);
9061
- return mergeLayerResults(context.layerResults, nextResults);
9062
- }
9244
+ layerResults: ({ context, event }) => getCoreSuccessLayerResults(context, event.output, "merge")
9063
9245
  })
9064
9246
  }
9065
9247
  ],
@@ -9396,19 +9578,14 @@ var ciMachine = setup({
9396
9578
  // Setup Roles (ci-pr modes only)
9397
9579
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9398
9580
  setupRoles: {
9399
- meta: {
9400
- e2e: {
9401
- observable: "log",
9402
- assertions: [{ type: "log", contains: "Setting up database roles" }]
9403
- }
9404
- },
9581
+ meta: FLAT_EXECUTION_META.setupRoles,
9405
9582
  invoke: {
9406
9583
  src: "setupRoles",
9407
9584
  input: ({ context }) => createSetupRolesInput(context),
9408
9585
  onDone: {
9409
9586
  target: "staticChecks",
9410
9587
  actions: assign({
9411
- rolesSetup: true,
9588
+ rolesSetup: ({ event }) => getSetupRolesApplied(event.output),
9412
9589
  supabase: ({ context, event }) => mergeSetupRolesSupabase(context, event.output.appDatabaseUrl)
9413
9590
  })
9414
9591
  },
@@ -9423,13 +9600,8 @@ var ciMachine = setup({
9423
9600
  // Static Checks (ci-pr modes only)
9424
9601
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9425
9602
  staticChecks: {
9426
- meta: {
9427
- e2e: {
9428
- observable: "log",
9429
- assertions: [{ type: "log", contains: "Running static checks" }]
9430
- }
9431
- },
9432
- always: [{ guard: "shouldSkipStaticChecks", target: "buildAndPlaywright" }],
9603
+ meta: FLAT_EXECUTION_META.staticChecks,
9604
+ always: [{ guard: "shouldSkipStaticChecks", target: "layerContent" }],
9433
9605
  invoke: {
9434
9606
  src: "staticChecks",
9435
9607
  input: ({ context }) => createStaticChecksInput(context),
@@ -9443,7 +9615,7 @@ var ciMachine = setup({
9443
9615
  })
9444
9616
  },
9445
9617
  {
9446
- target: "buildAndPlaywright",
9618
+ target: "layerContent",
9447
9619
  actions: assign({ staticChecksPassed: true })
9448
9620
  }
9449
9621
  ],
@@ -9456,20 +9628,29 @@ var ciMachine = setup({
9456
9628
  }
9457
9629
  }
9458
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
+ },
9459
9648
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9460
9649
  // Build and Playwright Install (ci-pr modes only, parallel)
9461
9650
  // Optimization: Running build and playwright install concurrently saves ~30-60s
9462
9651
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9463
9652
  buildAndPlaywright: {
9464
- meta: {
9465
- e2e: {
9466
- observable: "log",
9467
- assertions: [
9468
- { type: "log", contains: "Building app" },
9469
- { type: "log", contains: "Installing Playwright" }
9470
- ]
9471
- }
9472
- },
9653
+ meta: FLAT_EXECUTION_META.buildAndPlaywright,
9473
9654
  always: [{ guard: "shouldSkipBuild", target: "appStart" }],
9474
9655
  invoke: {
9475
9656
  src: "buildAndPlaywright",
@@ -9510,12 +9691,7 @@ var ciMachine = setup({
9510
9691
  // App Start (ci-pr modes only)
9511
9692
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9512
9693
  appStart: {
9513
- meta: {
9514
- e2e: {
9515
- observable: "log",
9516
- assertions: [{ type: "log", contains: "Starting app" }]
9517
- }
9518
- },
9694
+ meta: FLAT_EXECUTION_META.appStart,
9519
9695
  always: [{ guard: "shouldSkipAppStart", target: "capabilities" }],
9520
9696
  invoke: {
9521
9697
  src: "appStart",
@@ -9550,26 +9726,16 @@ var ciMachine = setup({
9550
9726
  },
9551
9727
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9552
9728
  // Capability Detection (ci-pr modes only)
9553
- // Also detects layer content to filter layers without testable content
9554
9729
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9555
9730
  capabilities: {
9556
- meta: {
9557
- e2e: {
9558
- observable: "log",
9559
- assertions: [{ type: "log", contains: "Detecting capabilities" }]
9560
- }
9561
- },
9731
+ meta: FLAT_EXECUTION_META.capabilities,
9562
9732
  invoke: {
9563
9733
  src: "capabilities",
9564
9734
  input: ({ context }) => createCapabilitiesInput(context),
9565
9735
  onDone: {
9566
9736
  target: "runCoreTests",
9567
9737
  actions: assign({
9568
- capabilities: ({ event }) => event.output.detected?.capabilities ?? [],
9569
- // Filter to runnable layers only (those with content)
9570
- selectedLayers: ({ context, event }) => filterRunnableLayers(context.selectedLayers, event.output.layerContent),
9571
- // Store skip reasons for PR comment
9572
- layerSkipReasons: ({ event }) => extractLayerSkipReasons(event.output.layerContent)
9738
+ capabilities: ({ event }) => getDetectedCapabilities(event.output)
9573
9739
  })
9574
9740
  },
9575
9741
  onError: {
@@ -9585,12 +9751,7 @@ var ciMachine = setup({
9585
9751
  // ci-pr: Only core layers (1,2,3) - E2E runs in e2ePhase
9586
9752
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9587
9753
  runCoreTests: {
9588
- meta: {
9589
- e2e: {
9590
- observable: "log",
9591
- assertions: [{ type: "log", contains: "Running core tests" }]
9592
- }
9593
- },
9754
+ meta: FLAT_EXECUTION_META.runCoreTests,
9594
9755
  invoke: {
9595
9756
  src: "runLayers",
9596
9757
  input: ({ context }) => createRunCoreTestsInput(context),
@@ -9612,7 +9773,7 @@ var ciMachine = setup({
9612
9773
  target: "coreTestsComplete",
9613
9774
  actions: assign({
9614
9775
  testsRun: true,
9615
- layerResults: ({ event }) => convertLayerResults(event.output.results)
9776
+ layerResults: ({ context, event }) => getCoreSuccessLayerResults(context, event.output, "replace")
9616
9777
  })
9617
9778
  }
9618
9779
  ],
@@ -9835,18 +9996,94 @@ var ciMachine = setup({
9835
9996
  },
9836
9997
  output: ({ context }) => createOutput(context)
9837
9998
  });
9838
- function getStateName(snapshot) {
9839
- return getSnapshotStateName(snapshot);
9840
- }
9841
- function isComplete(snapshot) {
9842
- return isSnapshotComplete(snapshot);
9843
- }
9844
9999
 
9845
10000
  // src/commands/ci/machine/commands/machine-runner.ts
9846
10001
  init_esm_shims();
9847
10002
 
9848
- // src/commands/ci/machine/commands/step-telemetry.ts
10003
+ // src/commands/ci/machine/commands/ci-step-registry.ts
9849
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
+ };
9850
10087
  var STEP_METADATA = {
9851
10088
  "setup.local": {
9852
10089
  order: 10,
@@ -9888,74 +10125,80 @@ var STEP_METADATA = {
9888
10125
  optional: true,
9889
10126
  parentStep: "applySeeds"
9890
10127
  },
9891
- "postSeedPr.observability.productionPreview": {
10128
+ [OBSERVABILITY_STEP_IDS[0]]: {
9892
10129
  order: 50,
9893
10130
  title: "Run production preview",
9894
10131
  phase: "observability",
9895
10132
  optional: true,
9896
10133
  parentStep: "applySeeds"
9897
10134
  },
9898
- "postSeedPr.observability.collectSchemaStats": {
10135
+ [OBSERVABILITY_STEP_IDS[1]]: {
9899
10136
  order: 60,
9900
10137
  title: "Collect schema statistics",
9901
10138
  phase: "observability",
9902
10139
  optional: true,
9903
10140
  parentStep: "applySeeds"
9904
10141
  },
9905
- "postSeedPr.execution.setupRoles": {
10142
+ [EXECUTION_STEP_IDS[0]]: {
9906
10143
  order: 70,
9907
10144
  title: "Setup database roles",
9908
10145
  phase: "db",
9909
10146
  optional: true,
9910
10147
  parentStep: "applySeeds"
9911
10148
  },
9912
- "postSeedPr.execution.staticChecks": {
10149
+ [EXECUTION_STEP_IDS[1]]: {
9913
10150
  order: 80,
9914
10151
  title: "Run static checks",
9915
10152
  phase: "build"
9916
10153
  },
9917
- "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]]: {
9918
10161
  order: 90,
9919
10162
  title: "Build app and prepare Playwright",
9920
10163
  phase: "build"
9921
10164
  },
9922
- "postSeedPr.execution.buildAndPlaywright.build": {
10165
+ [BUILD_PREPARATION_SUBSTEP_IDS.build]: {
9923
10166
  order: 91,
9924
10167
  title: "Build application",
9925
10168
  phase: "build",
9926
- parentStep: "postSeedPr.execution.buildAndPlaywright"
10169
+ parentStep: EXECUTION_STEP_IDS[3]
9927
10170
  },
9928
- "postSeedPr.execution.buildAndPlaywright.manifestGenerate": {
10171
+ [BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate]: {
9929
10172
  order: 92,
9930
10173
  title: "Generate manifest",
9931
10174
  phase: "build",
9932
10175
  optional: true,
9933
- parentStep: "postSeedPr.execution.buildAndPlaywright"
10176
+ parentStep: EXECUTION_STEP_IDS[3]
9934
10177
  },
9935
- "postSeedPr.execution.buildAndPlaywright.playwrightInstall": {
10178
+ [BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall]: {
9936
10179
  order: 93,
9937
10180
  title: "Install Playwright browsers",
9938
10181
  phase: "build",
9939
10182
  optional: true,
9940
- parentStep: "postSeedPr.execution.buildAndPlaywright"
10183
+ parentStep: EXECUTION_STEP_IDS[3]
9941
10184
  },
9942
- "postSeedPr.execution.appStart": {
10185
+ [EXECUTION_STEP_IDS[4]]: {
9943
10186
  order: 100,
9944
10187
  title: "Start application",
9945
10188
  phase: "build"
9946
10189
  },
9947
- "postSeedPr.execution.capabilities": {
10190
+ [EXECUTION_STEP_IDS[5]]: {
9948
10191
  order: 110,
9949
10192
  title: "Detect test capabilities",
9950
10193
  phase: "test",
9951
10194
  optional: true
9952
10195
  },
9953
- "postSeedPr.execution.runCoreTests": {
10196
+ [EXECUTION_STEP_IDS[6]]: {
9954
10197
  order: 120,
9955
10198
  title: "Run core test layers",
9956
10199
  phase: "test"
9957
10200
  },
9958
- "postSeedPr.execution.e2ePhase": {
10201
+ [EXECUTION_STEP_IDS[7]]: {
9959
10202
  order: 130,
9960
10203
  title: "Run E2E phase",
9961
10204
  phase: "test",
@@ -9974,10 +10217,7 @@ var STEP_METADATA = {
9974
10217
  }
9975
10218
  };
9976
10219
  var CANONICAL_STEP_IDS = Object.keys(STEP_METADATA).sort((a, b) => b.length - a.length);
9977
- function toIsoString(timestampMs) {
9978
- return new Date(timestampMs).toISOString();
9979
- }
9980
- function getMetadata(stepId) {
10220
+ function getStepMetadata(stepId) {
9981
10221
  return STEP_METADATA[stepId];
9982
10222
  }
9983
10223
  function getCanonicalStepId(statePath) {
@@ -9993,6 +10233,12 @@ function getStepOrderFromStatePath(statePath) {
9993
10233
  if (!canonical) return -1;
9994
10234
  return STEP_METADATA[canonical]?.order ?? -1;
9995
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
+ }
9996
10242
  function pickPrimaryStatePath(statePaths, previousStatePath) {
9997
10243
  if (statePaths.length === 0) return previousStatePath;
9998
10244
  if (statePaths.length === 1 && (statePaths[0] === "done" || statePaths[0] === "failed")) {
@@ -10009,7 +10255,7 @@ function createSkippedSummary(stepId, reason) {
10009
10255
  return createSyntheticSummary(stepId, "skipped", { reason });
10010
10256
  }
10011
10257
  function createSyntheticSummary(stepId, status, params = {}) {
10012
- const metadata = getMetadata(stepId);
10258
+ const metadata = getStepMetadata(stepId);
10013
10259
  if (!metadata) return null;
10014
10260
  const durationMs = params.startedAtMs !== void 0 && params.endedAtMs !== void 0 ? Math.max(0, params.endedAtMs - params.startedAtMs) : void 0;
10015
10261
  return [
@@ -10031,7 +10277,7 @@ function getBuildSkipReason(context) {
10031
10277
  if (!shouldRunPrExecutionPhase(context)) {
10032
10278
  return getExecutionSkipReason(context);
10033
10279
  }
10034
- return isCiLocalMode(context) ? "Skipped in ci-local mode" : context.input.skipBuild === true ? "Skipped by --skip-build" : "Skipped by step guard";
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";
10035
10281
  }
10036
10282
  function getPlaywrightSkipReason(context) {
10037
10283
  if (!shouldRunPrExecutionPhase(context)) {
@@ -10051,9 +10297,6 @@ function getObservabilitySkipReason(context) {
10051
10297
  if (isTestPhase(context)) {
10052
10298
  return "Skipped in phase=test; observability DB checks are disabled";
10053
10299
  }
10054
- if (isBlockingPhase(context)) {
10055
- return "Skipped in phase=blocking; observability runs outside the blocking path";
10056
- }
10057
10300
  return "Skipped by phase selection";
10058
10301
  }
10059
10302
  function getExecutionSkipReason(context) {
@@ -10069,7 +10312,13 @@ function getSyntheticBuildEntries(context, trackedStepIds, _stepStates, _nowMs)
10069
10312
  if (trackedStepIds.has(entry[0])) return;
10070
10313
  entries.push(entry);
10071
10314
  };
10072
- const syntheticTimingParams = {};
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
+ }
10073
10322
  if (!shouldRunPrExecutionPhase(context)) {
10074
10323
  const reason = getExecutionSkipReason(context);
10075
10324
  pushEntry(createSkippedSummary("postSeedPr.execution.buildAndPlaywright.build", reason));
@@ -10083,115 +10332,62 @@ function getSyntheticBuildEntries(context, trackedStepIds, _stepStates, _nowMs)
10083
10332
  }
10084
10333
  if (shouldSkipBuild(context)) {
10085
10334
  const reason = getBuildSkipReason(context);
10086
- pushEntry(createSkippedSummary("postSeedPr.execution.buildAndPlaywright.build", reason));
10087
- pushEntry(
10088
- createSkippedSummary("postSeedPr.execution.buildAndPlaywright.manifestGenerate", reason)
10089
- );
10090
- pushEntry(
10091
- createSkippedSummary("postSeedPr.execution.buildAndPlaywright.playwrightInstall", reason)
10092
- );
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));
10093
10338
  return entries;
10094
10339
  }
10095
10340
  if (context.appBuildPassed === true) {
10096
- pushEntry(
10097
- createSyntheticSummary(
10098
- "postSeedPr.execution.buildAndPlaywright.build",
10099
- "passed",
10100
- syntheticTimingParams
10101
- )
10102
- );
10341
+ pushEntry(createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.build, "passed"));
10103
10342
  } else if (context.appBuildPassed === false) {
10104
10343
  pushEntry(
10105
- createSyntheticSummary("postSeedPr.execution.buildAndPlaywright.build", "failed", {
10106
- ...syntheticTimingParams,
10344
+ createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.build, "failed", {
10107
10345
  reason: context.error ?? "App build failed"
10108
10346
  })
10109
10347
  );
10110
10348
  }
10111
10349
  if (context.appBuildPassed === true) {
10112
10350
  if (context.manifestGenerated === true) {
10113
- pushEntry(
10114
- createSyntheticSummary(
10115
- "postSeedPr.execution.buildAndPlaywright.manifestGenerate",
10116
- "passed",
10117
- syntheticTimingParams
10118
- )
10119
- );
10351
+ pushEntry(createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, "passed"));
10120
10352
  } else if (context.manifestGenerated === null) {
10121
10353
  pushEntry(
10122
- createSyntheticSummary(
10123
- "postSeedPr.execution.buildAndPlaywright.manifestGenerate",
10124
- "skipped",
10125
- {
10126
- ...syntheticTimingParams,
10127
- reason: "Skipped because manifest generation is optional for this repository"
10128
- }
10129
- )
10354
+ createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, "skipped", {
10355
+ reason: "Skipped because manifest generation is optional for this repository"
10356
+ })
10130
10357
  );
10131
10358
  } else if (context.manifestGenerated === false) {
10132
10359
  pushEntry(
10133
- createSyntheticSummary(
10134
- "postSeedPr.execution.buildAndPlaywright.manifestGenerate",
10135
- "failed",
10136
- {
10137
- ...syntheticTimingParams,
10138
- reason: "Manifest generation failed but CI continued because it is optional"
10139
- }
10140
- )
10360
+ createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, "failed", {
10361
+ reason: "Manifest generation failed but CI continued because it is optional"
10362
+ })
10141
10363
  );
10142
10364
  }
10143
10365
  } else if (context.appBuildPassed === false) {
10144
10366
  pushEntry(
10145
- createSyntheticSummary(
10146
- "postSeedPr.execution.buildAndPlaywright.manifestGenerate",
10147
- "skipped",
10148
- {
10149
- ...syntheticTimingParams,
10150
- reason: "Skipped because app build failed before manifest generation"
10151
- }
10152
- )
10367
+ createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.manifestGenerate, "skipped", {
10368
+ reason: "Skipped because app build failed before manifest generation"
10369
+ })
10153
10370
  );
10154
10371
  }
10155
10372
  if (shouldReusePreparedPlaywright(context)) {
10156
10373
  pushEntry(
10157
- createSyntheticSummary(
10158
- "postSeedPr.execution.buildAndPlaywright.playwrightInstall",
10159
- "skipped",
10160
- {
10161
- ...syntheticTimingParams,
10162
- reason: getPlaywrightSkipReason(context)
10163
- }
10164
- )
10374
+ createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, "skipped", {
10375
+ reason: getPlaywrightSkipReason(context)
10376
+ })
10165
10377
  );
10166
10378
  } else if (shouldSkipPlaywrightInstall(context)) {
10167
10379
  pushEntry(
10168
- createSyntheticSummary(
10169
- "postSeedPr.execution.buildAndPlaywright.playwrightInstall",
10170
- "skipped",
10171
- {
10172
- ...syntheticTimingParams,
10173
- reason: getPlaywrightSkipReason(context)
10174
- }
10175
- )
10380
+ createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, "skipped", {
10381
+ reason: getPlaywrightSkipReason(context)
10382
+ })
10176
10383
  );
10177
10384
  } else if (context.playwrightInstalled === true) {
10178
- pushEntry(
10179
- createSyntheticSummary(
10180
- "postSeedPr.execution.buildAndPlaywright.playwrightInstall",
10181
- "passed",
10182
- syntheticTimingParams
10183
- )
10184
- );
10385
+ pushEntry(createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, "passed"));
10185
10386
  } else if (context.playwrightInstalled === false) {
10186
10387
  pushEntry(
10187
- createSyntheticSummary(
10188
- "postSeedPr.execution.buildAndPlaywright.playwrightInstall",
10189
- "failed",
10190
- {
10191
- ...syntheticTimingParams,
10192
- reason: "Playwright browser install failed"
10193
- }
10194
- )
10388
+ createSyntheticSummary(BUILD_PREPARATION_SUBSTEP_IDS.playwrightInstall, "failed", {
10389
+ reason: "Playwright browser install failed"
10390
+ })
10195
10391
  );
10196
10392
  }
10197
10393
  return entries;
@@ -10205,8 +10401,9 @@ function getSkippedStepEntries(context, trackedStepIds) {
10205
10401
  };
10206
10402
  if (isCiPrMode(context) && !shouldRunPrObservabilityPhase(context)) {
10207
10403
  const reason = getObservabilitySkipReason(context);
10208
- pushSkipped("postSeedPr.observability.productionPreview", reason);
10209
- pushSkipped("postSeedPr.observability.collectSchemaStats", reason);
10404
+ for (const stepId of OBSERVABILITY_STEP_IDS) {
10405
+ pushSkipped(stepId, reason);
10406
+ }
10210
10407
  }
10211
10408
  if (isTestPhase(context)) {
10212
10409
  pushSkipped("syncSchema", "Skipped in phase=test; reusing prepared database");
@@ -10219,29 +10416,25 @@ function getSkippedStepEntries(context, trackedStepIds) {
10219
10416
  }
10220
10417
  if (isCiPrMode(context) && !shouldRunPrExecutionPhase(context)) {
10221
10418
  const reason = getExecutionSkipReason(context);
10222
- pushSkipped("postSeedPr.execution.setupRoles", reason);
10223
- pushSkipped("postSeedPr.execution.staticChecks", reason);
10224
- pushSkipped("postSeedPr.execution.buildAndPlaywright", reason);
10225
- pushSkipped("postSeedPr.execution.appStart", reason);
10226
- pushSkipped("postSeedPr.execution.capabilities", reason);
10227
- pushSkipped("postSeedPr.execution.runCoreTests", reason);
10228
- pushSkipped("postSeedPr.execution.e2ePhase", reason);
10419
+ for (const stepId of EXECUTION_STEP_IDS) {
10420
+ pushSkipped(stepId, reason);
10421
+ }
10229
10422
  } else {
10230
10423
  if (shouldSkipStaticChecks(context)) {
10231
10424
  const reason = isCiLocalMode(context) ? "Skipped in ci-local mode" : context.input.skipStaticChecks === true ? "Skipped by --skip-static-checks" : "Skipped by step guard";
10232
- pushSkipped("postSeedPr.execution.staticChecks", reason);
10425
+ pushSkipped(EXECUTION_STEP_IDS[1], reason);
10233
10426
  }
10234
10427
  if (shouldSkipBuild(context)) {
10235
- pushSkipped("postSeedPr.execution.buildAndPlaywright", getBuildSkipReason(context));
10428
+ pushSkipped(EXECUTION_STEP_IDS[3], getBuildSkipReason(context));
10236
10429
  }
10237
10430
  if (shouldSkipAppStart(context)) {
10238
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";
10239
- pushSkipped("postSeedPr.execution.appStart", reason);
10432
+ pushSkipped(EXECUTION_STEP_IDS[4], reason);
10240
10433
  }
10241
10434
  if (!context.selectedLayers.includes(4)) {
10242
- pushSkipped("postSeedPr.execution.e2ePhase", "Skipped because Layer 4 is not selected");
10435
+ pushSkipped(EXECUTION_STEP_IDS[7], "Skipped because Layer 4 is not selected");
10243
10436
  } else if (Object.values(context.layerResults).some((result) => result.status === "failed")) {
10244
- pushSkipped("postSeedPr.execution.e2ePhase", "Skipped because blocking core tests failed");
10437
+ pushSkipped(EXECUTION_STEP_IDS[7], "Skipped because blocking core tests failed");
10245
10438
  }
10246
10439
  }
10247
10440
  return skipped;
@@ -10250,7 +10443,7 @@ function createStepTelemetryTracker() {
10250
10443
  const stepStates = /* @__PURE__ */ new Map();
10251
10444
  let activeStepIds = /* @__PURE__ */ new Set();
10252
10445
  const startStep = (stepId, nowMs) => {
10253
- const metadata = getMetadata(stepId);
10446
+ const metadata = getStepMetadata(stepId);
10254
10447
  if (!metadata) return;
10255
10448
  if (stepStates.has(stepId)) return;
10256
10449
  stepStates.set(stepId, {
@@ -10289,8 +10482,7 @@ function createStepTelemetryTracker() {
10289
10482
  finishStep(stepId, "failed", nowMs);
10290
10483
  continue;
10291
10484
  }
10292
- const metadata = getMetadata(stepId);
10293
- finishStep(stepId, succeeded ? "passed" : metadata?.optional ? "killed" : "killed", nowMs);
10485
+ finishStep(stepId, succeeded ? "passed" : "killed", nowMs);
10294
10486
  }
10295
10487
  activeStepIds = /* @__PURE__ */ new Set();
10296
10488
  const trackedEntries = [...stepStates.entries()].sort((a, b) => a[1].metadata.order - b[1].metadata.order).map(([stepId, state]) => {
@@ -10323,7 +10515,7 @@ function createStepTelemetryTracker() {
10323
10515
  new Set([...trackedEntries, ...skippedEntries].map(([stepId]) => stepId)));
10324
10516
  return Object.fromEntries(
10325
10517
  [...trackedEntries, ...skippedEntries, ...syntheticBuildEntries].sort(
10326
- ([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)
10327
10519
  )
10328
10520
  );
10329
10521
  }
@@ -10332,17 +10524,22 @@ function createStepTelemetryTracker() {
10332
10524
 
10333
10525
  // src/commands/ci/machine/commands/machine-runner.ts
10334
10526
  var FLUSH_DELAY_MS = 100;
10335
- var progressUpdateSequence = 0;
10336
- var pendingProgressUpdate = null;
10337
- var progressHeartbeatTimer = null;
10338
- var latestSnapshot = null;
10339
- var heartbeatStep = null;
10340
- var heartbeatFailedStep = null;
10341
- var _progressUpdateSuccessCount = 0;
10342
- var stepStartTime = Date.now();
10343
- var currentTrackedStep = null;
10344
- var stepTimings = {};
10345
- 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();
10346
10543
  var DEFAULT_PROGRESS_HEARTBEAT_MS = 3e4;
10347
10544
  var HEARTBEAT_ELIGIBLE_STEPS = /* @__PURE__ */ new Set([
10348
10545
  "syncSchema",
@@ -10353,95 +10550,15 @@ var HEARTBEAT_ELIGIBLE_STEPS = /* @__PURE__ */ new Set([
10353
10550
  "runTests"
10354
10551
  ]);
10355
10552
  function resetProgressTracking() {
10356
- progressUpdateSequence = 0;
10357
- pendingProgressUpdate = null;
10358
- if (progressHeartbeatTimer) {
10359
- clearInterval(progressHeartbeatTimer);
10360
- progressHeartbeatTimer = null;
10361
- }
10362
- latestSnapshot = null;
10363
- heartbeatStep = null;
10364
- heartbeatFailedStep = null;
10365
- _progressUpdateSuccessCount = 0;
10366
- previousActiveSteps = /* @__PURE__ */ new Set();
10367
- stepStartTime = Date.now();
10368
- currentTrackedStep = null;
10369
- for (const key of Object.keys(stepTimings)) {
10370
- delete stepTimings[key];
10371
- }
10372
- }
10373
- var STATE_TO_STEP = {
10374
- // Setup phase
10375
- setup: "setup",
10376
- "setup.resolving": "setup",
10377
- "setup.local": "setup",
10378
- "setup.prLocal": "setup",
10379
- "setup.complete": "setup",
10380
- // DB phase
10381
- dbReset: "syncSchema",
10382
- pullProduction: "syncSchema",
10383
- syncSchema: "syncSchema",
10384
- applySeeds: "applySeeds",
10385
- productionPreview: "observability",
10386
- collectSchemaStats: "observability",
10387
- postSeedPr: "postSeedChecks",
10388
- "postSeedPr.execution": "postSeedChecks",
10389
- "postSeedPr.observability": "observability",
10390
- "postSeedPr.execution.setupRoles": "postSeedChecks",
10391
- "postSeedPr.observability.productionPreview": "observability",
10392
- "postSeedPr.observability.collectSchemaStats": "observability",
10393
- "postSeedPr.execution.staticChecks": "staticChecks",
10394
- "postSeedPr.execution.buildAndPlaywright": "build",
10395
- "postSeedPr.execution.appStart": "build",
10396
- "postSeedPr.execution.capabilities": "build",
10397
- "postSeedPr.execution.runCoreTests": "runTests",
10398
- "postSeedPr.execution.coreTestsComplete": "runTests",
10399
- "postSeedPr.execution.coreTestsFailed": "runTests",
10400
- "postSeedPr.execution.e2ePhase": "runTests",
10401
- "postSeedPr.execution.done": "runTests",
10402
- decidePath: "postSeedChecks",
10403
- installPgTap: "applySeeds",
10404
- setupRoles: "applySeeds",
10405
- // Build phase
10406
- staticChecks: "staticChecks",
10407
- buildAndPlaywright: "build",
10408
- appStart: "build",
10409
- capabilities: "build",
10410
- // Test phase (ci-local)
10411
- runTests: "runTests",
10412
- testsComplete: "runTests",
10413
- testsFailed: "runTests",
10414
- // Test phase (ci-pr: blocking/non-blocking split)
10415
- runCoreTests: "runTests",
10416
- coreTestsComplete: "runTests",
10417
- coreTestsFailed: "runTests",
10418
- e2ePhase: "runTests",
10419
- "e2ePhase.intermediateComment": "runTests",
10420
- "e2ePhase.e2eTests": "runTests",
10421
- // Finalize phase
10422
- finalize: "finalize",
10423
- "finalize.writeSummary": "finalize",
10424
- "finalize.postComment": "finalize",
10425
- "finalize.complete": "finalize",
10426
- // Final states
10427
- done: "finalize",
10428
- failed: "finalize"
10429
- };
10430
- var STEP_ORDER = [
10431
- "setup",
10432
- "syncSchema",
10433
- "applySeeds",
10434
- "postSeedChecks",
10435
- "observability",
10436
- "staticChecks",
10437
- "build",
10438
- "runTests",
10439
- "finalize"
10440
- ];
10553
+ if (progress.heartbeatTimer) {
10554
+ clearInterval(progress.heartbeatTimer);
10555
+ }
10556
+ Object.assign(progress, createProgressState());
10557
+ }
10441
10558
  function resolveStepForState(state) {
10442
10559
  let cursor = state;
10443
10560
  while (cursor.length > 0) {
10444
- const step = STATE_TO_STEP[cursor];
10561
+ const step = PROGRESS_STATE_TO_STEP[cursor];
10445
10562
  if (step) return step;
10446
10563
  const nextDot = cursor.lastIndexOf(".");
10447
10564
  if (nextDot === -1) break;
@@ -10449,20 +10566,22 @@ function resolveStepForState(state) {
10449
10566
  }
10450
10567
  return void 0;
10451
10568
  }
10452
- function getCompletedSteps(currentStep, stepTimings2 = {}) {
10569
+ function getCompletedSteps(currentStep, stepTimings = {}) {
10453
10570
  const completed = new Set(
10454
- Object.keys(stepTimings2).filter((step) => STEP_ORDER.includes(step))
10571
+ Object.keys(stepTimings).filter(
10572
+ (step) => PROGRESS_STEP_ORDER.includes(step)
10573
+ )
10455
10574
  );
10456
10575
  completed.delete(currentStep);
10457
- return STEP_ORDER.filter((step) => completed.has(step));
10576
+ return PROGRESS_STEP_ORDER.filter((step) => completed.has(step));
10458
10577
  }
10459
10578
  function recordStepTiming(newStep) {
10460
10579
  const now = Date.now();
10461
- if (currentTrackedStep !== null) {
10462
- stepTimings[currentTrackedStep] = now - stepStartTime;
10580
+ if (progress.currentTrackedStep !== null) {
10581
+ progress.stepTimings[progress.currentTrackedStep] = now - progress.stepStartTime;
10463
10582
  }
10464
- currentTrackedStep = newStep;
10465
- stepStartTime = now;
10583
+ progress.currentTrackedStep = newStep;
10584
+ progress.stepStartTime = now;
10466
10585
  }
10467
10586
  function parseProgressHeartbeatMs(context) {
10468
10587
  const rawValue = context.input.progressIntervalSeconds?.trim();
@@ -10474,24 +10593,28 @@ function parseProgressHeartbeatMs(context) {
10474
10593
  return parsedSeconds * 1e3;
10475
10594
  }
10476
10595
  function stopProgressHeartbeat() {
10477
- if (progressHeartbeatTimer) {
10478
- clearInterval(progressHeartbeatTimer);
10479
- progressHeartbeatTimer = null;
10596
+ if (progress.heartbeatTimer) {
10597
+ clearInterval(progress.heartbeatTimer);
10598
+ progress.heartbeatTimer = null;
10480
10599
  }
10481
10600
  }
10482
10601
  function refreshProgressHeartbeat(currentStep, failedStep = null) {
10483
- heartbeatStep = currentStep;
10484
- heartbeatFailedStep = failedStep;
10602
+ progress.heartbeatStep = currentStep;
10603
+ progress.heartbeatFailedStep = failedStep;
10485
10604
  stopProgressHeartbeat();
10486
10605
  if (!HEARTBEAT_ELIGIBLE_STEPS.has(currentStep)) {
10487
10606
  return;
10488
10607
  }
10489
- const intervalMs = latestSnapshot ? parseProgressHeartbeatMs(latestSnapshot.context) : DEFAULT_PROGRESS_HEARTBEAT_MS;
10490
- progressHeartbeatTimer = setInterval(() => {
10491
- if (!latestSnapshot || !heartbeatStep) return;
10492
- 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
+ );
10493
10616
  }, intervalMs);
10494
- progressHeartbeatTimer.unref?.();
10617
+ progress.heartbeatTimer.unref?.();
10495
10618
  }
10496
10619
  function updateProgressComment(context, currentStep, failedStep = null) {
10497
10620
  if (!shouldPostGitHubComment(context)) return;
@@ -10500,25 +10623,26 @@ function updateProgressComment(context, currentStep, failedStep = null) {
10500
10623
  if (!token) return;
10501
10624
  const repository = process.env.GITHUB_REPOSITORY?.trim();
10502
10625
  if (!repository || !repository.includes("/")) return;
10503
- const mySeq = ++progressUpdateSequence;
10626
+ const mySeq = ++progress.updateSequence;
10627
+ progress.updateCount++;
10504
10628
  const doUpdate = async () => {
10505
- if (pendingProgressUpdate) {
10629
+ if (progress.pendingUpdate) {
10506
10630
  try {
10507
- await pendingProgressUpdate;
10631
+ await progress.pendingUpdate;
10508
10632
  } catch {
10509
10633
  }
10510
10634
  }
10511
- if (mySeq < progressUpdateSequence) {
10635
+ if (mySeq < progress.updateSequence) {
10512
10636
  return;
10513
10637
  }
10514
- const freshContext = latestSnapshot?.context ?? context;
10515
- const completedSteps = getCompletedSteps(currentStep, stepTimings);
10638
+ const freshContext = progress.latestSnapshot?.context ?? context;
10639
+ const completedSteps = getCompletedSteps(currentStep, progress.stepTimings);
10516
10640
  const progressInput = createProgressCommentInput(
10517
10641
  freshContext,
10518
10642
  currentStep,
10519
10643
  completedSteps,
10520
10644
  failedStep,
10521
- { ...stepTimings }
10645
+ { ...progress.stepTimings }
10522
10646
  );
10523
10647
  const body = `<!-- runa-ci-report -->
10524
10648
  ${generateProgressCommentBody(progressInput)}`;
@@ -10529,11 +10653,10 @@ ${generateProgressCommentBody(progressInput)}`;
10529
10653
  marker: "<!-- runa-ci-report -->",
10530
10654
  body
10531
10655
  });
10532
- _progressUpdateSuccessCount++;
10533
- } catch (_error) {
10656
+ } catch {
10534
10657
  }
10535
10658
  };
10536
- pendingProgressUpdate = doUpdate();
10659
+ progress.pendingUpdate = doUpdate();
10537
10660
  }
10538
10661
  function determineFailedStep(context) {
10539
10662
  if (context.testsRun && Object.values(context.layerResults).some((r) => r.status === "failed"))
@@ -10559,8 +10682,8 @@ function handleProgressCommentUpdate(snapshot, prevState, currentState, activeSt
10559
10682
  const currentStepSet = new Set(
10560
10683
  activeStatePaths.map((p) => resolveStepForState(p)).filter((s) => s !== void 0)
10561
10684
  );
10562
- const parallelStepChanged = !setsEqual(previousActiveSteps, currentStepSet);
10563
- previousActiveSteps = currentStepSet;
10685
+ const parallelStepChanged = !setsEqual(progress.previousActiveSteps, currentStepSet);
10686
+ progress.previousActiveSteps = currentStepSet;
10564
10687
  if (!currentStep || !parallelStepChanged && currentStep === prevStep && !shouldRefreshWithinStep)
10565
10688
  return;
10566
10689
  if (!shouldRefreshWithinStep) {
@@ -10603,11 +10726,10 @@ async function runCiMachine(input, logger, onStateChange) {
10603
10726
  let settled = false;
10604
10727
  const stepTelemetry = createStepTelemetryTracker();
10605
10728
  actor.subscribe((snapshot) => {
10606
- latestSnapshot = snapshot;
10729
+ progress.latestSnapshot = snapshot;
10607
10730
  const activeStatePaths = getSnapshotStatePaths(snapshot);
10608
10731
  stepTelemetry.transition(activeStatePaths);
10609
10732
  const currentState = pickPrimaryStatePath(activeStatePaths, previousState) ?? activeStatePaths[0] ?? previousState ?? "unknown";
10610
- snapshot.status;
10611
10733
  if (currentState && currentState !== "done" && currentState !== "failed" && !currentState.startsWith("finalize")) {
10612
10734
  lastActionableState = currentState;
10613
10735
  }
@@ -10974,7 +11096,9 @@ z.object({
10974
11096
  /** Skip GitHub comment */
10975
11097
  skipGithubComment: z.boolean().optional(),
10976
11098
  /** Specific layers */
10977
- 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()
10978
11102
  }).passthrough();
10979
11103
  z.object({
10980
11104
  passed: z.boolean(),
@@ -11122,6 +11246,43 @@ z.object({
11122
11246
  port: z.number().int()
11123
11247
  });
11124
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
+
11125
11286
  // src/commands/ci/machine/commands/ci-pr.ts
11126
11287
  var cliOutputFormat = "human";
11127
11288
  var isGitHubActionsMode2 = false;
@@ -11176,6 +11337,73 @@ function logSetupRolesStatus(ctx, logger) {
11176
11337
  }
11177
11338
  }
11178
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
+ };
11179
11407
  var stateLogHandlers2 = {
11180
11408
  // ─── Setup Phase ───────────────────────────────────────────
11181
11409
  setup: (_ctx, _logger) => {
@@ -11196,9 +11424,12 @@ var stateLogHandlers2 = {
11196
11424
  logger.info("Posting initial CI progress comment to PR...");
11197
11425
  },
11198
11426
  // ─── Database Phase ────────────────────────────────────────
11199
- syncSchema: (_ctx, logger) => {
11200
- logSection3("Database: Apply Schema (db apply)");
11201
- 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
+ );
11202
11433
  },
11203
11434
  applySeeds: (ctx, logger) => {
11204
11435
  if (ctx.schemaApplied) {
@@ -11207,6 +11438,7 @@ var stateLogHandlers2 = {
11207
11438
  logSection3("Database: Apply Seeds (db seed)");
11208
11439
  logger.info("Running: pnpm exec runa db seed ci --auto-approve");
11209
11440
  },
11441
+ // Nested paths (postSeedPr.*) — differ from flat counterparts
11210
11442
  "postSeedPr.execution.setupRoles": (ctx, logger) => {
11211
11443
  if (ctx.seedsApplied) {
11212
11444
  logger.success("Seeds applied successfully");
@@ -11219,68 +11451,18 @@ var stateLogHandlers2 = {
11219
11451
  logger.success("Seeds applied successfully");
11220
11452
  }
11221
11453
  logSection3("Database: Production Preview (dry-run)");
11222
- logger.info("Running: pnpm exec runa db apply production --check --compare-only");
11223
- },
11224
- "postSeedPr.observability.collectSchemaStats": (ctx, logger) => {
11225
- if (ctx.productionPreview?.executed) {
11226
- if (ctx.productionPreview.hasChanges) {
11227
- logger.info("Production preview: schema changes detected");
11228
- } else {
11229
- logger.success("Production preview: no schema changes");
11230
- }
11231
- }
11232
- logSection3("Database: Collect Schema Statistics");
11233
- logger.info("Comparing Local/CI/Production schemas...");
11234
- },
11235
- "postSeedPr.execution.staticChecks": (ctx, logger) => {
11236
- logSetupRolesStatus(ctx, logger);
11237
- logSection3("Static Checks: type-check + lint (parallel)");
11238
- logger.info("Running: pnpm type-check || pnpm lint");
11239
- },
11240
- "postSeedPr.execution.buildAndPlaywright": (ctx, logger) => {
11241
- if (ctx.staticChecksPassed) {
11242
- logger.success("Static checks passed");
11243
- }
11244
- if (shouldReusePreparedPlaywright(ctx)) {
11245
- logSection3("Build: App Build + Reuse Prepared Playwright");
11246
- logger.info("Running: pnpm build (Playwright install owned by outer workflow)");
11247
- return;
11248
- }
11249
- logSection3("Build: App Build + Playwright Install (parallel)");
11250
- logger.info("Running: pnpm build || pnpm exec playwright install chromium");
11251
- },
11252
- "postSeedPr.execution.appStart": (ctx, logger) => {
11253
- if (ctx.appBuildPassed) {
11254
- logger.success("App build completed");
11255
- }
11256
- if (ctx.manifestGenerated) {
11257
- logger.success("Manifest generated (Layer 3 ready)");
11258
- } else if (ctx.manifestGenerated === null) {
11259
- logger.info("Manifest generation skipped (optional)");
11260
- } else if (ctx.manifestGenerated === false) {
11261
- logger.warn("Manifest generation failed (Layer 3 may be skipped)");
11262
- }
11263
- if (ctx.playwrightInstalled) {
11264
- logger.success("Playwright browsers installed");
11265
- }
11266
- logSection3("Start Application");
11267
- logger.info(`Starting app on port ${ctx.app?.port ?? 3e3}...`);
11268
- },
11269
- "postSeedPr.execution.capabilities": (ctx, logger) => {
11270
- if (ctx.appStarted) {
11271
- logger.success(`App started at ${ctx.baseUrl ?? "http://localhost:3000"}`);
11272
- }
11273
- logSection3("Detect Test Capabilities");
11274
- },
11275
- "postSeedPr.execution.runCoreTests": (ctx, logger) => {
11276
- const coreLayers = ctx.selectedLayers.filter((l) => l !== 4);
11277
- logSection3(`Core Tests: Layer ${coreLayers.join(", ")}`);
11278
- logger.info(`Running core layers: ${coreLayers.map((l) => `test layer${l}`).join(", ")}`);
11279
- },
11280
- "postSeedPr.execution.e2ePhase": (_ctx, logger) => {
11281
- logSection3("E2E Phase: Layer 4 + Intermediate Comment");
11282
- 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");
11283
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)
11284
11466
  productionPreview: (ctx, logger) => {
11285
11467
  if (ctx.seedsApplied) {
11286
11468
  logger.success("Seeds applied successfully");
@@ -11288,19 +11470,9 @@ var stateLogHandlers2 = {
11288
11470
  logger.info("Seeds skipped (no seed files or already seeded)");
11289
11471
  }
11290
11472
  logSection3("Database: Production Preview (dry-run)");
11291
- logger.info("Running: pnpm exec runa db apply production --check --compare-only");
11292
- },
11293
- collectSchemaStats: (ctx, logger) => {
11294
- if (ctx.productionPreview?.executed) {
11295
- if (ctx.productionPreview.hasChanges) {
11296
- logger.info("Production preview: schema changes detected");
11297
- } else {
11298
- logger.success("Production preview: no schema changes");
11299
- }
11300
- }
11301
- logSection3("Database: Collect Schema Statistics");
11302
- logger.info("Comparing Local/CI/Production schemas...");
11473
+ logger.info("Running: pnpm exec runa db preview production --profile compare-only");
11303
11474
  },
11475
+ collectSchemaStats: logCollectSchemaStats,
11304
11476
  setupRoles: (ctx, logger) => {
11305
11477
  if (ctx.schemaStats) {
11306
11478
  logger.success("Schema statistics collected");
@@ -11309,57 +11481,18 @@ var stateLogHandlers2 = {
11309
11481
  logger.info("Running: pnpm db:setup-roles");
11310
11482
  },
11311
11483
  // ─── Static Analysis Phase ─────────────────────────────────
11312
- staticChecks: (ctx, logger) => {
11313
- logSetupRolesStatus(ctx, logger);
11314
- logSection3("Static Checks: type-check + lint (parallel)");
11315
- logger.info("Running: pnpm type-check || pnpm lint");
11316
- },
11484
+ staticChecks: logStaticChecks,
11317
11485
  // ─── Build Phase ───────────────────────────────────────────
11318
- buildAndPlaywright: (ctx, logger) => {
11319
- if (ctx.staticChecksPassed) {
11320
- logger.success("Static checks passed");
11321
- }
11322
- if (shouldReusePreparedPlaywright(ctx)) {
11323
- logSection3("Build: App Build + Reuse Prepared Playwright");
11324
- logger.info("Running: pnpm build (Playwright install owned by outer workflow)");
11325
- return;
11326
- }
11327
- logSection3("Build: App Build + Playwright Install (parallel)");
11328
- logger.info("Running: pnpm build || pnpm exec playwright install chromium");
11329
- },
11330
- appStart: (ctx, logger) => {
11331
- if (ctx.appBuildPassed) {
11332
- logger.success("App build completed");
11333
- }
11334
- if (ctx.manifestGenerated) {
11335
- logger.success("Manifest generated (Layer 3 ready)");
11336
- } else if (ctx.manifestGenerated === null) {
11337
- logger.info("Manifest generation skipped (optional)");
11338
- } else if (ctx.manifestGenerated === false) {
11339
- logger.warn("Manifest generation failed (Layer 3 may be skipped)");
11340
- }
11341
- if (ctx.playwrightInstalled) {
11342
- logger.success("Playwright browsers installed");
11343
- }
11344
- logSection3("Start Application");
11345
- logger.info(`Starting app on port ${ctx.app?.port ?? 3e3}...`);
11346
- },
11486
+ layerContent: logLayerContent,
11487
+ buildAndPlaywright: logBuildAndPlaywright,
11488
+ appStart: logAppStart,
11347
11489
  // ─── Test Phase ────────────────────────────────────────────
11348
- capabilities: (ctx, logger) => {
11349
- if (ctx.appStarted) {
11350
- logger.success(`App started at ${ctx.baseUrl ?? "http://localhost:3000"}`);
11351
- }
11352
- logSection3("Detect Test Capabilities");
11353
- },
11490
+ capabilities: logCapabilities,
11354
11491
  runTests: (ctx, logger) => {
11355
11492
  logSection3(`Run Tests: Layer ${ctx.selectedLayers.join(", ")}`);
11356
11493
  logger.info(`Running layers: ${ctx.selectedLayers.map((l) => `test layer${l}`).join(", ")}`);
11357
11494
  },
11358
- runCoreTests: (ctx, logger) => {
11359
- const coreLayers = ctx.selectedLayers.filter((l) => l !== 4);
11360
- logSection3(`Core Tests: Layer ${coreLayers.join(", ")}`);
11361
- logger.info(`Running core layers: ${coreLayers.map((l) => `test layer${l}`).join(", ")}`);
11362
- },
11495
+ runCoreTests: logRunCoreTests,
11363
11496
  coreTestsComplete: (ctx, logger) => {
11364
11497
  const coreLayers = ctx.selectedLayers.filter((l) => l !== 4);
11365
11498
  const passedLayers = coreLayers.filter(
@@ -11373,10 +11506,7 @@ var stateLogHandlers2 = {
11373
11506
  const failedLayers = Object.entries(ctx.layerResults).filter(([_, result]) => result.status === "failed").map(([layer]) => layer);
11374
11507
  logger.error(`Core tests failed: Layer ${failedLayers.join(", ")}`);
11375
11508
  },
11376
- e2ePhase: (_ctx, logger) => {
11377
- logSection3("E2E Phase: Layer 4 + Intermediate Comment");
11378
- logger.info("Running Layer 4 E2E tests (parallel with intermediate PR comment)");
11379
- },
11509
+ e2ePhase: logE2ePhase,
11380
11510
  "e2ePhase.e2eTests": (_ctx, logger) => {
11381
11511
  logger.info("Running Layer 4 E2E tests...");
11382
11512
  },
@@ -11442,7 +11572,7 @@ function getGitHubEventAction() {
11442
11572
  return void 0;
11443
11573
  }
11444
11574
  }
11445
- function optionsToMachineInput(options) {
11575
+ function optionsToMachineInput(options, internalOverrides) {
11446
11576
  return {
11447
11577
  command: "pr",
11448
11578
  // Explicit command for mode detection
@@ -11463,7 +11593,8 @@ function optionsToMachineInput(options) {
11463
11593
  skipGithubComment: options.skipGithubComment,
11464
11594
  verbose: options.verbose,
11465
11595
  targetDir: process.cwd(),
11466
- layers: [1, 2, 3, 4],
11596
+ layers: internalOverrides.layers,
11597
+ internalProfile: internalOverrides.profile,
11467
11598
  // Environment capture (Environment Capture Pattern: capture at entry point, not in machine/guards)
11468
11599
  databaseUrl: options.databaseUrl ?? process.env.DATABASE_URL,
11469
11600
  // PRD: GH_DATABASE_URL_ADMIN = postgres role (DDL capable, for pg-schema-diff dry-run)
@@ -11497,7 +11628,7 @@ async function runCiPrCommand(options) {
11497
11628
  const runExecutionPath = !observabilityPhase;
11498
11629
  const preparedRuntime = options.skipLocalDbStart === true || options.assumeSupabaseReady === true;
11499
11630
  const preparedPlaywright = options.skipPlaywrightInstall === true;
11500
- 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 db:setup-roles (observability deferred)" : "db apply \u2192 db seed \u2192 (production preview \u2225 schema stats \u2225 db:setup-roles)";
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)";
11501
11632
  const planSteps = [
11502
11633
  {
11503
11634
  id: "setup",
@@ -11508,18 +11639,30 @@ async function runCiPrCommand(options) {
11508
11639
  ...!runExecutionPath || skipBuild ? [] : [
11509
11640
  {
11510
11641
  id: "build",
11511
- description: preparedPlaywright ? "pnpm build \u2192 manifest:generate (reuse preinstalled Playwright)" : "pnpm build \u2192 manifest:generate + playwright install"
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"
11643
+ }
11644
+ ],
11645
+ ...!runExecutionPath ? [] : [
11646
+ {
11647
+ id: "test",
11648
+ description: "Start app if Layer 4 is runnable \u2192 detect capabilities \u2192 run layers"
11512
11649
  }
11513
11650
  ],
11514
- ...!runExecutionPath ? [] : [{ id: "test", description: "Start app \u2192 detect capabilities \u2192 run layers 1-4" }],
11515
11651
  {
11516
11652
  id: "finalize",
11517
11653
  description: "Write ci-summary.json + post GitHub comment"
11518
11654
  }
11519
11655
  ];
11520
11656
  logPlan2(planSteps);
11521
- const machineInput = optionsToMachineInput(options);
11522
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);
11523
11666
  const result = await runCiMachine(machineInput, logger, handleStateChange2);
11524
11667
  endCurrentGroup2();
11525
11668
  printSummary2(logger, result);