@runa-ai/runa-cli 0.7.0 → 0.7.1

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 (27) hide show
  1. package/dist/{build-V66FAQXB.js → build-HUDIP6KU.js} +1 -1
  2. package/dist/{chunk-AIP6MR42.js → chunk-AFY3TX4I.js} +1 -1
  3. package/dist/{chunk-KWX3JHCY.js → chunk-AKZAN4BC.js} +6 -1
  4. package/dist/{chunk-SGJG3BKD.js → chunk-CCW3PLQY.js} +1 -1
  5. package/dist/{chunk-IBVVGH6X.js → chunk-EMB6IZFT.js} +17 -4
  6. package/dist/{ci-ZWRVWNFX.js → ci-XY6IKEDC.js} +844 -120
  7. package/dist/{cli-2JNBJUBB.js → cli-UZA4RBNQ.js} +13 -13
  8. package/dist/commands/ci/machine/actors/db/collect-schema-stats.d.ts +4 -1
  9. package/dist/commands/ci/machine/actors/db/production-preview.d.ts +10 -0
  10. package/dist/commands/ci/machine/actors/db/schema-canonical-diff.d.ts +22 -0
  11. package/dist/commands/ci/machine/commands/machine-runner.d.ts +2 -0
  12. package/dist/commands/ci/machine/formatters/sections/production-schema-status.d.ts +30 -0
  13. package/dist/commands/ci/machine/helpers.d.ts +8 -0
  14. package/dist/commands/ci/machine/machine.d.ts +57 -4
  15. package/dist/commands/template-check/commands/template-check.d.ts +1 -0
  16. package/dist/commands/template-check/contract.d.ts +1 -0
  17. package/dist/constants/versions.d.ts +1 -1
  18. package/dist/{db-XULCILOU.js → db-Q3GF7JWP.js} +78 -18
  19. package/dist/{env-SS66PZ4B.js → env-GMB3THRG.js} +1 -1
  20. package/dist/{hotfix-YA3DGLOM.js → hotfix-NDTPY2T4.js} +1 -1
  21. package/dist/index.js +3 -3
  22. package/dist/{init-ZIL6LRFO.js → init-U4VCRHTD.js} +1 -1
  23. package/dist/{template-check-3P4HZXVY.js → template-check-FFJVDLBF.js} +23 -6
  24. package/dist/{upgrade-NUK3ZBCL.js → upgrade-7TWORWBV.js} +1 -1
  25. package/dist/{vuln-check-2W7N5TA2.js → vuln-check-6CMNPSBR.js} +1 -1
  26. package/dist/{vuln-checker-IQJ56RUV.js → vuln-checker-EJJTNDNE.js} +1 -1
  27. package/package.json +2 -2
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
- import { normalizeDatabaseUrlForDdl, enhanceConnectionError, parseBoolish, detectAppSchemas, formatSchemasForSql } from './chunk-XDCHRVE3.js';
3
+ import { normalizeDatabaseUrlForDdl, parseBoolish, enhanceConnectionError, detectAppSchemas, formatSchemasForSql } from './chunk-XDCHRVE3.js';
4
4
  import { isPathContained } from './chunk-DRSUEMAK.js';
5
5
  import './chunk-QDF7QXBL.js';
6
- import { getSnapshotStateName, isSnapshotComplete } from './chunk-IBVVGH6X.js';
6
+ import { getSnapshotStateName, isSnapshotComplete } from './chunk-EMB6IZFT.js';
7
7
  import { writeEnvLocal, startAppBackground, waitForAppReady, executePrSetupBase, createErrorOutput } from './chunk-HD74F6W2.js';
8
- import { psqlSyncQuery, parsePostgresUrl, buildPsqlArgs, buildPsqlEnv } from './chunk-7B5C6U2K.js';
8
+ import { parsePostgresUrl, buildPsqlArgs, buildPsqlEnv, psqlSyncQuery } from './chunk-7B5C6U2K.js';
9
9
  import { ensureRunaTmpDir, runLogged } from './chunk-6FAU4IGR.js';
10
10
  import { createMachineStateChangeLogger } from './chunk-5FT3F36G.js';
11
11
  import { getSafeEnv, getFilteredEnv, redactSecrets } from './chunk-II7VYQEM.js';
@@ -1884,24 +1884,41 @@ var ciProdApplyCommand = new Command("prod-apply").description("Apply production
1884
1884
 
1885
1885
  // src/commands/ci/commands/ci-static.ts
1886
1886
  init_esm_shims();
1887
- function getChecks() {
1888
- return [
1889
- {
1887
+ function hasNativeActionlint() {
1888
+ try {
1889
+ const result = spawnSync("actionlint", ["-version"], {
1890
+ encoding: "utf-8",
1891
+ stdio: ["ignore", "pipe", "pipe"],
1892
+ timeout: 5e3
1893
+ });
1894
+ return result.status === 0;
1895
+ } catch {
1896
+ return false;
1897
+ }
1898
+ }
1899
+ function createActionlintCheck(mode) {
1900
+ if (mode === "local" && hasNativeActionlint()) {
1901
+ return {
1890
1902
  name: "actionlint",
1891
1903
  label: "Workflow Lint",
1892
1904
  description: "GitHub Actions syntax validation",
1893
- command: "docker",
1894
- args: [
1895
- "run",
1896
- "--rm",
1897
- "-v",
1898
- `${process.cwd()}:/repo`,
1899
- "-w",
1900
- "/repo",
1901
- "rhysd/actionlint:1.7.9"
1902
- ],
1905
+ command: "actionlint",
1906
+ args: ["-color"],
1903
1907
  category: "workflow"
1904
- },
1908
+ };
1909
+ }
1910
+ return {
1911
+ name: "actionlint",
1912
+ label: "Workflow Lint",
1913
+ description: "GitHub Actions syntax validation",
1914
+ command: "docker",
1915
+ args: ["run", "--rm", "-v", `${process.cwd()}:/repo`, "-w", "/repo", "rhysd/actionlint:1.7.9"],
1916
+ category: "workflow"
1917
+ };
1918
+ }
1919
+ function getChecks(mode) {
1920
+ return [
1921
+ createActionlintCheck(mode),
1905
1922
  {
1906
1923
  name: "type-check",
1907
1924
  label: "Type Check",
@@ -2014,7 +2031,7 @@ function buildStepSummaryMarkdown2(summary) {
2014
2031
  return lines.join("\n");
2015
2032
  }
2016
2033
  function getChecksToRun(options) {
2017
- let checks = getChecks().filter((c) => {
2034
+ let checks = getChecks(options.mode).filter((c) => {
2018
2035
  if (c.category === "workflow") return true;
2019
2036
  if (c.category === "core") return true;
2020
2037
  if (c.category === "build") return options.includeBuild;
@@ -2534,6 +2551,19 @@ function normalizeWhitespace(value) {
2534
2551
  function stableHash(payload) {
2535
2552
  return createHash("sha256").update(JSON.stringify(payload)).digest("hex").slice(0, 16);
2536
2553
  }
2554
+ function createCanonicalTableColumnPayload(row) {
2555
+ return {
2556
+ name: row.column_name,
2557
+ type: normalizeWhitespace(row.data_type),
2558
+ notNull: row.not_null,
2559
+ default: normalizeWhitespace(row.default_expr),
2560
+ identity: row.identity_kind ?? "",
2561
+ generated: row.generated_kind ?? ""
2562
+ };
2563
+ }
2564
+ function sortCanonicalTableColumns(columns) {
2565
+ return [...columns].sort((left, right) => left.name.localeCompare(right.name));
2566
+ }
2537
2567
  function createCanonicalObject(kind, schema, name, label, payload, parentName) {
2538
2568
  return {
2539
2569
  kind,
@@ -2623,15 +2653,7 @@ function buildTableObjects(columns, flags, constraints) {
2623
2653
  rlsEnabled: false,
2624
2654
  rlsForced: false
2625
2655
  };
2626
- entry.columns.push({
2627
- attnum: row.attnum,
2628
- name: row.column_name,
2629
- type: normalizeWhitespace(row.data_type),
2630
- notNull: row.not_null,
2631
- default: normalizeWhitespace(row.default_expr),
2632
- identity: row.identity_kind ?? "",
2633
- generated: row.generated_kind ?? ""
2634
- });
2656
+ entry.columns.push(createCanonicalTableColumnPayload(row));
2635
2657
  tables.set(key, entry);
2636
2658
  }
2637
2659
  for (const row of flags) {
@@ -2667,7 +2689,7 @@ function buildTableObjects(columns, flags, constraints) {
2667
2689
  }
2668
2690
  return Array.from(tables.values()).map(
2669
2691
  (table) => createCanonicalObject("table", table.schema, table.table, `${table.schema}.${table.table}`, {
2670
- columns: table.columns,
2692
+ columns: sortCanonicalTableColumns(table.columns),
2671
2693
  constraints: table.constraints,
2672
2694
  rlsEnabled: table.rlsEnabled,
2673
2695
  rlsForced: table.rlsForced
@@ -3261,6 +3283,17 @@ function unavailableStats(error) {
3261
3283
  canonicalSnapshot: createUnavailableCanonicalSnapshot(error)
3262
3284
  };
3263
3285
  }
3286
+ function cloneEnvironmentStats(stats) {
3287
+ return {
3288
+ ...stats,
3289
+ schemas: Object.fromEntries(
3290
+ Object.entries(stats.schemas).map(([schema, values]) => [schema, { ...values }])
3291
+ ),
3292
+ total: { ...stats.total },
3293
+ indexList: stats.indexList?.map((index) => ({ ...index })),
3294
+ canonicalSnapshot: stats.canonicalSnapshot
3295
+ };
3296
+ }
3264
3297
  function logSchemaStats(label, stats, includeSchemas = false) {
3265
3298
  const base = `[schema-stats] ${label}: ${stats.total.tables}T ${stats.total.functions}F ${stats.total.policies}P`;
3266
3299
  if (!includeSchemas) {
@@ -3368,20 +3401,60 @@ ${result.stderr || ""}`;
3368
3401
  dropReferenceDb(sourceDbUrl, referenceDb.dbName);
3369
3402
  }
3370
3403
  }
3404
+ async function resolveReferenceAndCiStats(params) {
3405
+ const { repoRoot, tmpDir, referenceDbUrl, ciDbUrl, referenceStrategy } = params;
3406
+ if (referenceStrategy === "reuse-ci" && ciDbUrl) {
3407
+ console.log(
3408
+ "[schema-stats] Reusing synced CI database as reference schema (skip temporary rebuild)"
3409
+ );
3410
+ const [ciResult2] = await Promise.allSettled([collectEnvironmentStats(ciDbUrl, "CI")]);
3411
+ const ci = resolveSettledStats(ciResult2, "CI");
3412
+ const reference = ci.available === false ? unavailableStats(ci.error ?? "CI database is unavailable for reference schema reuse") : cloneEnvironmentStats(ci);
3413
+ return [reference, ci];
3414
+ }
3415
+ const [referenceResult, ciResult] = await Promise.allSettled([
3416
+ buildReferenceStats(repoRoot, tmpDir, referenceDbUrl),
3417
+ collectEnvironmentStats(ciDbUrl, "CI")
3418
+ ]);
3419
+ return [resolveSettledStats(referenceResult, "reference"), resolveSettledStats(ciResult, "CI")];
3420
+ }
3371
3421
  var collectSchemaStatsActor = fromPromise(
3372
3422
  async ({ input }) => {
3373
- const { repoRoot, referenceDbUrl, ciDbUrl, queryProduction, tmpDir } = input;
3423
+ const {
3424
+ repoRoot,
3425
+ referenceDbUrl,
3426
+ ciDbUrl,
3427
+ referenceStrategy = "rebuild",
3428
+ queryProduction,
3429
+ tmpDir
3430
+ } = input;
3374
3431
  console.log(
3375
3432
  "[schema-stats] Collecting schema statistics (Reference/CI/Production, parallel)..."
3376
3433
  );
3377
3434
  const productionUrl = process.env.GH_DATABASE_URL_ADMIN || process.env.GH_DATABASE_URL;
3378
- const [referenceResult, ciResult, productionResult] = await Promise.allSettled([
3379
- buildReferenceStats(repoRoot, tmpDir, referenceDbUrl),
3380
- collectEnvironmentStats(ciDbUrl, "CI"),
3435
+ const [referenceResult, productionResult] = await Promise.allSettled([
3436
+ resolveReferenceAndCiStats({
3437
+ repoRoot,
3438
+ tmpDir,
3439
+ referenceDbUrl,
3440
+ ciDbUrl,
3441
+ referenceStrategy
3442
+ }),
3381
3443
  collectEnvironmentStats(queryProduction ? productionUrl ?? null : null, "Production")
3382
3444
  ]);
3383
- const local = resolveSettledStats(referenceResult, "reference");
3384
- const ci = resolveSettledStats(ciResult, "CI");
3445
+ let local;
3446
+ let ci;
3447
+ if (referenceResult.status === "fulfilled") {
3448
+ local = referenceResult.value[0];
3449
+ ci = referenceResult.value[1];
3450
+ } else {
3451
+ console.warn(
3452
+ `[schema-stats] Failed to collect reference/CI schema statistics: ${referenceResult.reason}`
3453
+ );
3454
+ const reason = String(referenceResult.reason);
3455
+ local = unavailableStats(reason);
3456
+ ci = unavailableStats(reason);
3457
+ }
3385
3458
  const production = resolveSettledProductionStats(productionResult);
3386
3459
  return {
3387
3460
  schemaStats: {
@@ -3713,24 +3786,27 @@ function extractHazardsWithContext(fullOutput, repoRoot) {
3713
3786
  appendEmojiHazards(fullOutput, idempotentRoles, hazards);
3714
3787
  return hazards;
3715
3788
  }
3716
- function detectNoChanges(fullOutput) {
3789
+ function detectSchemaChangeState(fullOutput) {
3717
3790
  const lowerOutput = fullOutput.toLowerCase();
3791
+ const changesIndicators = [
3792
+ "check mode: schema changes detected (not applied)",
3793
+ "schema changes: detected"
3794
+ ];
3795
+ if (changesIndicators.some((indicator) => lowerOutput.includes(indicator))) {
3796
+ return "changes";
3797
+ }
3718
3798
  const noChangesIndicators = [
3799
+ "check mode: all changes are for idempotent-managed objects (nothing to apply)",
3800
+ "schema changes: none",
3719
3801
  "no schema changes detected",
3720
- // From actors.ts line 969
3721
- "no schema changes",
3722
- // Shorter variant
3723
3802
  "already in sync",
3724
- "nothing to apply",
3725
3803
  "schemas are up to date",
3726
- "no changes were applied",
3727
- // From db-apply.ts line 150
3728
- "no changes",
3729
- // From actors.ts line 578, 968
3730
- "in sync"
3731
- // From db-sync.ts line 80
3804
+ "nothing to apply"
3732
3805
  ];
3733
- return noChangesIndicators.some((indicator) => lowerOutput.includes(indicator));
3806
+ if (noChangesIndicators.some((indicator) => lowerOutput.includes(indicator))) {
3807
+ return "no-changes";
3808
+ }
3809
+ return "unknown";
3734
3810
  }
3735
3811
  function isCommandSuccess(exitCode, fullOutput) {
3736
3812
  return exitCode === 0 || fullOutput.includes("check complete") || fullOutput.includes("Check Summary");
@@ -3786,6 +3862,39 @@ function buildErrorResult(error, databaseUrl) {
3786
3862
  }
3787
3863
  };
3788
3864
  }
3865
+ function buildPreviewFromOutput(fullOutput, repoRoot, productionUrl) {
3866
+ const lines = fullOutput.split("\n");
3867
+ const planSql = extractSqlFromLines(lines) || extractSqlFromSchemaChanges(fullOutput);
3868
+ const hazardDetails = extractHazardsWithContext(fullOutput, repoRoot);
3869
+ const hazards = hazardDetails.map((h) => `${h.type}: ${h.message}`);
3870
+ const schemaChangeState = detectSchemaChangeState(fullOutput);
3871
+ const hasSqlPlan = planSql !== null && planSql.trim().length > 0;
3872
+ const hasHazards = hazards.length > 0;
3873
+ const hasChanges = schemaChangeState === "changes" ? true : schemaChangeState === "no-changes" ? false : hasSqlPlan || hasHazards;
3874
+ const effectivePlanSql = hasChanges ? planSql?.substring(0, 5e3) || null : null;
3875
+ const rawError = extractErrorMessage(fullOutput);
3876
+ return {
3877
+ planSql: effectivePlanSql,
3878
+ hazards,
3879
+ hazardDetails: hazardDetails.length > 0 ? hazardDetails : void 0,
3880
+ hasChanges,
3881
+ error: enhanceConnectionError(rawError, productionUrl)
3882
+ };
3883
+ }
3884
+ function buildSuccessPreviewResult(exitCode, fullOutput, repoRoot, productionUrl) {
3885
+ const isSuccess = isCommandSuccess(exitCode, fullOutput);
3886
+ const preview = buildPreviewFromOutput(fullOutput, repoRoot, productionUrl);
3887
+ return {
3888
+ preview: {
3889
+ executed: true,
3890
+ planSql: preview.planSql,
3891
+ hazards: preview.hazards,
3892
+ hazardDetails: preview.hazardDetails,
3893
+ hasChanges: preview.hasChanges,
3894
+ error: isSuccess ? null : preview.error
3895
+ }
3896
+ };
3897
+ }
3789
3898
  var productionPreviewActor = fromPromise(
3790
3899
  async ({ input }) => {
3791
3900
  const { repoRoot, tmpDir, shouldExecute } = input;
@@ -3810,28 +3919,7 @@ var productionPreviewActor = fromPromise(
3810
3919
  const fullOutput = `${stdout}
3811
3920
  ${stderr}`;
3812
3921
  await writeLogFile(logFile, fullOutput);
3813
- const lines = fullOutput.split("\n");
3814
- const planSql = extractSqlFromLines(lines) || extractSqlFromSchemaChanges(fullOutput);
3815
- const hazardDetails = extractHazardsWithContext(fullOutput, repoRoot);
3816
- const hazards = hazardDetails.map((h) => `${h.type}: ${h.message}`);
3817
- const hasNoChangesIndicator = detectNoChanges(fullOutput);
3818
- const hasSqlPlan = planSql !== null && planSql.trim().length > 0;
3819
- const hasHazards = hazards.length > 0;
3820
- const hasChanges = hasNoChangesIndicator ? false : hasSqlPlan || hasHazards;
3821
- const effectivePlanSql = hasChanges ? planSql?.substring(0, 5e3) || null : null;
3822
- const isSuccess = isCommandSuccess(result.exitCode, fullOutput);
3823
- const rawError = !isSuccess ? extractErrorMessage(fullOutput) : null;
3824
- const errorMessage = rawError ? enhanceConnectionError(rawError, productionUrl) : null;
3825
- return {
3826
- preview: {
3827
- executed: true,
3828
- planSql: effectivePlanSql,
3829
- hazards,
3830
- hazardDetails: hazardDetails.length > 0 ? hazardDetails : void 0,
3831
- hasChanges,
3832
- error: errorMessage
3833
- }
3834
- };
3922
+ return buildSuccessPreviewResult(result.exitCode, fullOutput, repoRoot, productionUrl);
3835
3923
  } catch (error) {
3836
3924
  return buildErrorResult(error, productionUrl);
3837
3925
  }
@@ -5783,6 +5871,9 @@ function getLayersForCorePhase(selectedLayers, mode) {
5783
5871
  function hasE2ELayer(selectedLayers) {
5784
5872
  return selectedLayers.includes(E2E_LAYER);
5785
5873
  }
5874
+ function shouldReuseCiReferenceStats(context) {
5875
+ return context.mode !== "ci-local" && context.schemaApplied && context.schemaDrift !== null && context.schemaDrift?.hasDrift === false;
5876
+ }
5786
5877
  function mergeLayerResults(coreResults, e2eResults) {
5787
5878
  return { ...coreResults, ...e2eResults };
5788
5879
  }
@@ -5879,7 +5970,7 @@ init_esm_shims();
5879
5970
  var CI_STEPS = [
5880
5971
  { step: "setup", label: "\u74B0\u5883\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7", detail: "\u30EA\u30DD\u30B8\u30C8\u30EA\u691C\u51FA\u3001Supabase\u8D77\u52D5" },
5881
5972
  { step: "syncSchema", label: "\u30B9\u30AD\u30FC\u30DE\u540C\u671F", detail: "pg-schema-diff \u2192 CI DB" },
5882
- { step: "applySeeds", label: "\u30B7\u30FC\u30C9\u9069\u7528", detail: "ci.sql + pgTAP + roles" },
5973
+ { step: "applySeeds", label: "\u30B7\u30FC\u30C9\u9069\u7528", detail: "ci.sql + prerequisite seeds" },
5883
5974
  { step: "staticChecks", label: "\u9759\u7684\u30C1\u30A7\u30C3\u30AF", detail: "\u578B\u30C1\u30A7\u30C3\u30AF \u2225 lint" },
5884
5975
  { step: "build", label: "\u30D3\u30EB\u30C9", detail: "build \u2225 playwright install" },
5885
5976
  { step: "runTests", label: "\u30C6\u30B9\u30C8\u5B9F\u884C", detail: "L0-L3 (\u30D6\u30ED\u30C3\u30AD\u30F3\u30B0) \u2192 L4 (E2E)" },
@@ -6043,11 +6134,123 @@ function formatSkippedLayers(layerSkipReasons, originalSelectedLayers) {
6043
6134
  return lines;
6044
6135
  }
6045
6136
 
6046
- // src/commands/ci/machine/formatters/sections/schema-matrix.ts
6137
+ // src/commands/ci/machine/formatters/sections/production-schema-status.ts
6047
6138
  init_esm_shims();
6139
+ var COUNT_FIELDS = [
6140
+ "tables",
6141
+ "functions",
6142
+ "policies",
6143
+ "indexes",
6144
+ "triggers"
6145
+ ];
6048
6146
  function emptyStats2() {
6049
6147
  return { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 };
6050
6148
  }
6149
+ function findExpectedCountDrift(schemaName, field, expectedDrift) {
6150
+ return expectedDrift.find(
6151
+ (entry) => entry.field === field && (entry.schemas === void 0 || entry.schemas.includes(schemaName))
6152
+ ) ?? null;
6153
+ }
6154
+ function buildCountDiffs(schemaStats, expectedDrift) {
6155
+ if (!schemaStats?.local || !schemaStats.production) return [];
6156
+ if (schemaStats.local.available === false || schemaStats.production.available === false) {
6157
+ return [];
6158
+ }
6159
+ const schemaNames = /* @__PURE__ */ new Set([
6160
+ ...Object.keys(schemaStats.local.schemas),
6161
+ ...Object.keys(schemaStats.production.schemas)
6162
+ ]);
6163
+ const diffs = [];
6164
+ for (const schemaName of Array.from(schemaNames).sort()) {
6165
+ const reference = schemaStats.local.schemas[schemaName] ?? emptyStats2();
6166
+ const production = schemaStats.production.schemas[schemaName] ?? emptyStats2();
6167
+ for (const field of COUNT_FIELDS) {
6168
+ if (reference[field] === production[field]) continue;
6169
+ const expected = findExpectedCountDrift(schemaName, field, expectedDrift);
6170
+ diffs.push({
6171
+ schema: schemaName,
6172
+ field,
6173
+ value: production[field],
6174
+ reference: reference[field],
6175
+ delta: production[field] - reference[field],
6176
+ expected: expected !== null,
6177
+ reason: expected?.reason
6178
+ });
6179
+ }
6180
+ }
6181
+ return diffs;
6182
+ }
6183
+ function areIndexDiffsCoveredByExpectedDrift(indexDiff, countDiffs) {
6184
+ if (!indexDiff || !hasIndexDiff(indexDiff)) return false;
6185
+ const touchedSchemas = /* @__PURE__ */ new Set([
6186
+ ...indexDiff.missing.map((index) => index.schema),
6187
+ ...indexDiff.extra.map((index) => index.schema)
6188
+ ]);
6189
+ if (touchedSchemas.size === 0) return false;
6190
+ const expectedIndexSchemas = new Set(
6191
+ countDiffs.filter((diff) => diff.field === "indexes" && diff.expected).map((diff) => diff.schema)
6192
+ );
6193
+ const unexpectedIndexSchemas = new Set(
6194
+ countDiffs.filter((diff) => diff.field === "indexes" && !diff.expected).map((diff) => diff.schema)
6195
+ );
6196
+ for (const schema of touchedSchemas) {
6197
+ if (unexpectedIndexSchemas.has(schema)) return false;
6198
+ if (!expectedIndexSchemas.has(schema)) return false;
6199
+ }
6200
+ return true;
6201
+ }
6202
+ function buildIndexDiff(schemaStats) {
6203
+ if (!schemaStats?.local || !schemaStats.production) return null;
6204
+ if (schemaStats.local.available === false || schemaStats.production.available === false) {
6205
+ return null;
6206
+ }
6207
+ return compareIndexLists(schemaStats.local, schemaStats.production);
6208
+ }
6209
+ function buildSemanticDiff(schemaStats) {
6210
+ const referenceSnapshot = schemaStats?.local?.canonicalSnapshot;
6211
+ const productionSnapshot = schemaStats?.production?.canonicalSnapshot;
6212
+ if (!referenceSnapshot || !productionSnapshot) return null;
6213
+ if (referenceSnapshot.available !== true || productionSnapshot.available !== true) return null;
6214
+ return compareCanonicalSnapshots(referenceSnapshot, productionSnapshot);
6215
+ }
6216
+ function getProductionSchemaSignals(productionPreview, schemaStats, expectedDrift = []) {
6217
+ const previewHasChanges = productionPreview?.hasChanges === true;
6218
+ const indexDiff = buildIndexDiff(schemaStats);
6219
+ const semanticDiff = buildSemanticDiff(schemaStats);
6220
+ const countDiffs = buildCountDiffs(schemaStats, expectedDrift);
6221
+ const hasIndexChanges = indexDiff ? hasIndexDiff(indexDiff) : false;
6222
+ const hasUnexpectedIndexChanges = hasIndexChanges && !areIndexDiffsCoveredByExpectedDrift(indexDiff, countDiffs);
6223
+ const hasSemanticChanges = semanticDiff ? hasCanonicalChanges(semanticDiff) : false;
6224
+ const hasCountChanges = countDiffs.length > 0;
6225
+ const hasUnexpectedCountChanges = countDiffs.some((diff) => !diff.expected);
6226
+ const knownDriftReasons = Array.from(
6227
+ new Set(countDiffs.filter((diff) => diff.expected).flatMap((diff) => diff.reason ?? []))
6228
+ );
6229
+ const hasKnownDriftOnly = hasCountChanges && !previewHasChanges && !hasUnexpectedCountChanges && !hasUnexpectedIndexChanges && !hasSemanticChanges;
6230
+ const requiresDeploy = previewHasChanges || hasUnexpectedIndexChanges || hasSemanticChanges || hasUnexpectedCountChanges;
6231
+ return {
6232
+ previewHasChanges,
6233
+ indexDiff,
6234
+ semanticDiff,
6235
+ countDiffs,
6236
+ hasIndexChanges,
6237
+ hasUnexpectedIndexChanges,
6238
+ hasSemanticChanges,
6239
+ hasCountChanges,
6240
+ hasUnexpectedCountChanges,
6241
+ hasKnownDriftOnly,
6242
+ knownDriftReasons,
6243
+ requiresDeploy,
6244
+ previewMissedChanges: !previewHasChanges && (hasUnexpectedIndexChanges || hasSemanticChanges || hasUnexpectedCountChanges),
6245
+ previewOnlyChanges: previewHasChanges && !hasUnexpectedIndexChanges && !hasSemanticChanges && !hasUnexpectedCountChanges
6246
+ };
6247
+ }
6248
+
6249
+ // src/commands/ci/machine/formatters/sections/schema-matrix.ts
6250
+ init_esm_shims();
6251
+ function emptyStats3() {
6252
+ return { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 };
6253
+ }
6051
6254
  function isStatsAvailable(stats) {
6052
6255
  return stats != null && stats.available !== false;
6053
6256
  }
@@ -6127,8 +6330,8 @@ function getSortedActiveSchemas(schemaStats) {
6127
6330
  function generateSchemaTableRows(schemaStats, sortedSchemas, hasProduction, expectedDrift, ciDiffBySchema, productionDiffBySchema) {
6128
6331
  const lines = [];
6129
6332
  for (const schemaName of sortedSchemas) {
6130
- const localStats = schemaStats.local.schemas[schemaName] ?? emptyStats2();
6131
- const ciStats = schemaStats.ci.schemas[schemaName] ?? emptyStats2();
6333
+ const localStats = schemaStats.local.schemas[schemaName] ?? emptyStats3();
6334
+ const ciStats = schemaStats.ci.schemas[schemaName] ?? emptyStats3();
6132
6335
  const prodStats = schemaStats.production?.schemas[schemaName] ?? null;
6133
6336
  const localCell = formatReferenceCell(localStats, isStatsAvailable(schemaStats.local));
6134
6337
  const ciCell = formatCiCellWithDiff(
@@ -6285,7 +6488,7 @@ function generateExpectedDriftFootnotes(schemaStats, sortedSchemas, expectedDrif
6285
6488
  if (!isStatsAvailable(schemaStats.local) || !isStatsAvailable(schemaStats.production)) return [];
6286
6489
  const allReasons = /* @__PURE__ */ new Set();
6287
6490
  for (const schemaName of sortedSchemas) {
6288
- const localStats = schemaStats.local.schemas[schemaName] ?? emptyStats2();
6491
+ const localStats = schemaStats.local.schemas[schemaName] ?? emptyStats3();
6289
6492
  const prodStats = schemaStats.production.schemas[schemaName];
6290
6493
  if (!prodStats || !hasDisplayedStatsDiff(prodStats, localStats)) continue;
6291
6494
  const fieldDiffs = getFieldDiffs(prodStats, localStats);
@@ -6507,23 +6710,6 @@ function classifyPreviewError(error) {
6507
6710
  }
6508
6711
  return { label: "\u672C\u756A\u30D7\u30EC\u30D3\u30E5\u30FC\u5931\u6557", guidance: "" };
6509
6712
  }
6510
- function generateIndexDiffWarning(schemaStats) {
6511
- if (!schemaStats?.local || !schemaStats?.production) return [];
6512
- if (schemaStats.local.available === false || schemaStats.production.available === false)
6513
- return [];
6514
- const indexDiff = compareIndexLists(schemaStats.local, schemaStats.production);
6515
- if (!hasIndexDiff(indexDiff)) return [];
6516
- const diffParts = [];
6517
- if (indexDiff.missing.length > 0) diffParts.push(`${indexDiff.missing.length}\u4EF6\u4E0D\u8DB3`);
6518
- if (indexDiff.extra.length > 0) diffParts.push(`${indexDiff.extra.length}\u4EF6\u4F59\u5206`);
6519
- return [
6520
- `> \u26A0\uFE0F **\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u5DEE\u5206\u691C\u51FA** (${diffParts.join(", ")})`,
6521
- ">",
6522
- "> pg-schema-diff \u3067\u306F\u691C\u51FA\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u304C\u3001\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u306E\u5DEE\u5206\u304C\u3042\u308A\u307E\u3059\u3002",
6523
- "> \u30B9\u30AD\u30FC\u30DE\u30DE\u30C8\u30EA\u30C3\u30AF\u30B9\u306E\u8A73\u7D30\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
6524
- ""
6525
- ];
6526
- }
6527
6713
  function generatePreviewError(error) {
6528
6714
  const { label, guidance } = classifyPreviewError(error);
6529
6715
  const lines = [`> \u26A0\uFE0F **${label}**`];
@@ -6544,7 +6730,82 @@ function generatePreviewError(error) {
6544
6730
  );
6545
6731
  return lines;
6546
6732
  }
6547
- function generateProductionPreviewSection(prodPreview, schemaDrift, schemaStats) {
6733
+ function generatePreviewChangesDetectedSection(prodPreview, previewOnlyChanges) {
6734
+ const lines = [
6735
+ "> \u{1F536} **\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u691C\u51FA - \u672C\u756A\u30C7\u30D7\u30ED\u30A4\u304C\u5FC5\u8981\u3067\u3059**",
6736
+ ">",
6737
+ "> \u3053\u306EPR\u3092\u30DE\u30FC\u30B8\u3057\u305F\u5F8C\u3001\u672C\u756A\u30C7\u30D7\u30ED\u30A4\u30EF\u30FC\u30AF\u30D5\u30ED\u30FC\u3092\u5B9F\u884C\u3057\u3066\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u3092\u9069\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
6738
+ ""
6739
+ ];
6740
+ if (previewOnlyChanges) {
6741
+ lines.push(
6742
+ "> \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",
6743
+ "> \u3053\u308C\u306F grants / ACL / ownership \u306A\u3069 canonical semantic diff \u306E\u5BFE\u8C61\u5916\u5DEE\u5206\u3067\u3042\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002",
6744
+ ""
6745
+ );
6746
+ }
6747
+ if (prodPreview.planSql) {
6748
+ lines.push(...generatePreviewDetails(prodPreview));
6749
+ }
6750
+ return lines;
6751
+ }
6752
+ function buildComparisonMismatchReasons(schemaStats, expectedDrift) {
6753
+ const signals = getProductionSchemaSignals(null, schemaStats, expectedDrift);
6754
+ const reasons = [];
6755
+ if (signals.hasSemanticChanges && signals.semanticDiff) {
6756
+ const diff = signals.semanticDiff;
6757
+ reasons.push(
6758
+ `semantic diff: changed ${diff.changed.length}, missing ${diff.missing.length}, extra ${diff.extra.length}, renamed ${diff.renamed.length}`
6759
+ );
6760
+ }
6761
+ if (signals.hasIndexChanges && signals.indexDiff) {
6762
+ reasons.push(
6763
+ `index diff: missing ${signals.indexDiff.missing.length}, extra ${signals.indexDiff.extra.length}`
6764
+ );
6765
+ }
6766
+ if (signals.hasUnexpectedCountChanges) {
6767
+ const unexpectedDiffs = signals.countDiffs.filter((diff) => !diff.expected);
6768
+ const formatted = unexpectedDiffs.slice(0, 6).map(
6769
+ (diff) => `${diff.schema}.${diff.field} ${diff.reference} -> ${diff.value} (${diff.delta >= 0 ? "+" : ""}${diff.delta})`
6770
+ );
6771
+ reasons.push(`count diff: ${formatted.join(", ")}${unexpectedDiffs.length > 6 ? ", ..." : ""}`);
6772
+ }
6773
+ return reasons;
6774
+ }
6775
+ function generateComparisonMismatchSection(schemaStats, expectedDrift) {
6776
+ const reasons = buildComparisonMismatchReasons(schemaStats, expectedDrift);
6777
+ const lines = [
6778
+ "> \u{1F536} **\u672C\u756ADB\u3068\u306E\u5DEE\u5206\u691C\u51FA - \u672C\u756A\u30C7\u30D7\u30ED\u30A4\u304C\u5FC5\u8981\u3067\u3059**",
6779
+ ">",
6780
+ "> `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",
6781
+ "> \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",
6782
+ ""
6783
+ ];
6784
+ for (const reason of reasons) {
6785
+ lines.push(`> - ${reason}`);
6786
+ }
6787
+ if (reasons.length > 0) {
6788
+ lines.push("");
6789
+ }
6790
+ return lines;
6791
+ }
6792
+ function generateKnownDriftOnlySection(schemaStats, expectedDrift) {
6793
+ const signals = getProductionSchemaSignals(null, schemaStats, expectedDrift);
6794
+ const lines = [
6795
+ "**\u672C\u756A\u30D7\u30EC\u30D3\u30E5\u30FC**: \u2705 \u672C\u756A\u30C7\u30D7\u30ED\u30A4\u304C\u5FC5\u8981\u306A\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u306F\u3042\u308A\u307E\u305B\u3093",
6796
+ "",
6797
+ "> \u{1F4CB} **\u65E2\u77E5\u306E\u5DEE\u5206\u306E\u307F\u691C\u51FA**",
6798
+ ">",
6799
+ "> Reference \u2194 Prod \u306B\u306F `expectedDrift` \u3067\u8A31\u5BB9\u6E08\u307F\u306E\u5DEE\u5206\u306E\u307F\u304C\u3042\u308A\u307E\u3059\u3002\u8FFD\u52A0\u306E db apply/deploy \u306F\u4E0D\u8981\u3067\u3059\u3002"
6800
+ ];
6801
+ for (const reason of signals.knownDriftReasons) {
6802
+ lines.push(`> - ${reason}`);
6803
+ }
6804
+ lines.push("");
6805
+ return lines;
6806
+ }
6807
+ function generateProductionPreviewSection(prodPreview, schemaDrift, schemaStats, expectedDrift) {
6808
+ const signals = getProductionSchemaSignals(prodPreview, schemaStats, expectedDrift);
6548
6809
  if (!prodPreview?.executed) {
6549
6810
  if (schemaDrift?.gitDiff?.filesChanged?.length) {
6550
6811
  return [
@@ -6557,20 +6818,15 @@ function generateProductionPreviewSection(prodPreview, schemaDrift, schemaStats)
6557
6818
  return [];
6558
6819
  }
6559
6820
  if (prodPreview.error) return generatePreviewError(prodPreview.error);
6560
- if (prodPreview.hasChanges) {
6561
- const lines = [
6562
- "> \u{1F536} **\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u691C\u51FA - \u672C\u756A\u30C7\u30D7\u30ED\u30A4\u304C\u5FC5\u8981\u3067\u3059**",
6563
- ">",
6564
- "> \u3053\u306EPR\u3092\u30DE\u30FC\u30B8\u3057\u305F\u5F8C\u3001\u672C\u756A\u30C7\u30D7\u30ED\u30A4\u30EF\u30FC\u30AF\u30D5\u30ED\u30FC\u3092\u5B9F\u884C\u3057\u3066\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u3092\u9069\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
6565
- ""
6566
- ];
6567
- if (prodPreview.planSql) {
6568
- lines.push(...generatePreviewDetails(prodPreview));
6569
- }
6570
- return lines;
6821
+ if (signals.previewHasChanges) {
6822
+ return generatePreviewChangesDetectedSection(prodPreview, signals.previewOnlyChanges);
6823
+ }
6824
+ if (signals.previewMissedChanges) {
6825
+ return generateComparisonMismatchSection(schemaStats, expectedDrift);
6826
+ }
6827
+ if (signals.hasKnownDriftOnly) {
6828
+ return generateKnownDriftOnlySection(schemaStats, expectedDrift);
6571
6829
  }
6572
- const indexWarning = generateIndexDiffWarning(schemaStats);
6573
- if (indexWarning.length > 0) return indexWarning;
6574
6830
  return ["**\u672C\u756A\u30D7\u30EC\u30D3\u30E5\u30FC**: \u2705 \u672C\u756A\u306B\u9069\u7528\u3059\u308B\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u306F\u3042\u308A\u307E\u305B\u3093", ""];
6575
6831
  }
6576
6832
  function shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges2) {
@@ -6579,18 +6835,19 @@ function shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges
6579
6835
  if (!productionPreview?.executed && schemaDrift?.gitDiff?.filesChanged?.length) return true;
6580
6836
  return false;
6581
6837
  }
6582
- function generateDeploySection(exitCode, layerResults, gitBranchName, productionPreview, schemaDrift, schemaStats, env) {
6838
+ function generateDeploySection(exitCode, layerResults, gitBranchName, productionPreview, schemaDrift, schemaStats, expectedDrift, env) {
6583
6839
  if (!checkIfDeployable(exitCode, layerResults)) return [];
6584
6840
  const deployWorkflowUrl = env.repository ? `${env.serverUrl}/${env.repository}/actions/workflows/deploy-db.yml` : null;
6585
- const hasPgSchemaDiffChanges = productionPreview?.hasChanges === true;
6586
- const hasIndexChanges = schemaStats?.local && schemaStats?.production ? hasIndexDiff(compareIndexLists(schemaStats.local, schemaStats.production)) : false;
6587
- const hasSchemaChanges2 = hasPgSchemaDiffChanges || hasIndexChanges;
6841
+ const signals = getProductionSchemaSignals(productionPreview, schemaStats, expectedDrift);
6842
+ const hasSchemaChanges2 = signals.requiresDeploy;
6588
6843
  const headerEmoji = hasSchemaChanges2 ? "\u{1F6A8}" : "\u{1F680}";
6589
6844
  const headerText = hasSchemaChanges2 ? "\u672C\u756A\u30C7\u30D7\u30ED\u30A4 (\u30B9\u30AD\u30FC\u30DE\u5909\u66F4\u3042\u308A!)" : "\u672C\u756A\u30C7\u30D7\u30ED\u30A4";
6590
6845
  const lines = ["---", "", `### ${headerEmoji} ${headerText}`, ""];
6591
6846
  if (exitCode !== 0)
6592
6847
  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", "");
6593
- lines.push(...generateProductionPreviewSection(productionPreview, schemaDrift, schemaStats));
6848
+ lines.push(
6849
+ ...generateProductionPreviewSection(productionPreview, schemaDrift, schemaStats, expectedDrift)
6850
+ );
6594
6851
  const showButton = shouldShowDeployButton(productionPreview, schemaDrift, hasSchemaChanges2);
6595
6852
  if (deployWorkflowUrl && showButton) {
6596
6853
  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";
@@ -6630,6 +6887,7 @@ function generateIntermediateCommentBody(input) {
6630
6887
  productionPreview,
6631
6888
  schemaDrift,
6632
6889
  input.schemaStats ?? null,
6890
+ input.expectedDrift ?? [],
6633
6891
  env
6634
6892
  ),
6635
6893
  "<sub>\u{1F916} RUNA CI \u751F\u6210 (\u4E2D\u9593\u66F4\u65B0)</sub>"
@@ -6665,6 +6923,7 @@ function generateCommentBody(input) {
6665
6923
  input.productionPreview,
6666
6924
  schemaDrift,
6667
6925
  input.schemaStats ?? null,
6926
+ input.expectedDrift ?? [],
6668
6927
  env
6669
6928
  ),
6670
6929
  "<sub>\u{1F916} RUNA CI \u751F\u6210</sub>"
@@ -6714,7 +6973,7 @@ function formatSyncSchemaDetail(schemaDrift) {
6714
6973
  if (!hasGitChanges) {
6715
6974
  const hasSqlContent = schemaDrift.beforeSql && schemaDrift.beforeSql.trim().length > 0;
6716
6975
  if (hasSqlContent) {
6717
- return "ci.sql + pgTAP + roles";
6976
+ return "ci.sql + prerequisite seeds";
6718
6977
  }
6719
6978
  return "SQL\u30D5\u30A1\u30A4\u30EB\u5909\u66F4\u306A\u3057";
6720
6979
  }
@@ -6754,7 +7013,7 @@ function formatRunTestsDetail(layerResults) {
6754
7013
  }
6755
7014
  var stepDetailHandlers = {
6756
7015
  syncSchema: (_status, schemaDrift) => formatSyncSchemaDetail(schemaDrift),
6757
- applySeeds: () => "ci.sql + pgTAP + roles",
7016
+ applySeeds: () => "ci.sql + prerequisite seeds",
6758
7017
  staticChecks: (status) => status === "passed" ? "\u578B\u30C1\u30A7\u30C3\u30AF \u2713 lint \u2713" : "\u578B\u30C1\u30A7\u30C3\u30AF \u2717 \u307E\u305F\u306F lint \u2717",
6759
7018
  build: (status) => status === "passed" ? "\u30A2\u30D7\u30EA\u30D3\u30EB\u30C9\u5B8C\u4E86, Playwright\u6E96\u5099\u5B8C\u4E86" : "\u30D3\u30EB\u30C9\u5931\u6557",
6760
7019
  runTests: (_status, _schemaDrift, layerResults) => formatRunTestsDetail(layerResults)
@@ -7182,7 +7441,7 @@ function createInitialContext(input) {
7182
7441
  function createOutput(context) {
7183
7442
  return {
7184
7443
  mode: context.mode,
7185
- status: context.error ? "failure" : "success",
7444
+ status: context.exitCode === 0 && !context.error ? "success" : "failure",
7186
7445
  repoKind: context.repoKind,
7187
7446
  steps: {},
7188
7447
  // Populated from summary
@@ -7751,10 +8010,17 @@ var ciMachine = setup({
7751
8010
  databaseUrl: context.supabase?.databaseUrlRaw ?? "",
7752
8011
  mode: context.mode
7753
8012
  }),
7754
- onDone: {
7755
- actions: assign({ seedsApplied: ({ event }) => event.output.applied }),
7756
- target: "productionPreview"
7757
- },
8013
+ onDone: [
8014
+ {
8015
+ guard: "isCiLocalMode",
8016
+ actions: assign({ seedsApplied: ({ event }) => event.output.applied }),
8017
+ target: "productionPreview"
8018
+ },
8019
+ {
8020
+ actions: assign({ seedsApplied: ({ event }) => event.output.applied }),
8021
+ target: "postSeedPr"
8022
+ }
8023
+ ],
7758
8024
  onError: {
7759
8025
  // Seeds failure is CRITICAL - blocks CI (Layer 2/3 tests depend on seeds)
7760
8026
  target: "failed",
@@ -7766,6 +8032,371 @@ var ciMachine = setup({
7766
8032
  }
7767
8033
  },
7768
8034
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
8035
+ // Post-Seed PR Phase
8036
+ // CI PR path only: run observability work in parallel with the critical build/test path
8037
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
8038
+ postSeedPr: {
8039
+ type: "parallel",
8040
+ meta: {
8041
+ e2e: {
8042
+ observable: "log",
8043
+ assertions: [{ type: "log", contains: "Database roles configured" }]
8044
+ }
8045
+ },
8046
+ states: {
8047
+ execution: {
8048
+ initial: "setupRoles",
8049
+ states: {
8050
+ setupRoles: {
8051
+ invoke: {
8052
+ src: "setupRoles",
8053
+ input: ({ context }) => createSetupRolesInput(context),
8054
+ onDone: {
8055
+ target: "staticChecks",
8056
+ actions: assign({
8057
+ rolesSetup: true,
8058
+ supabase: ({ context, event }) => mergeSetupRolesSupabase(context, event.output.appDatabaseUrl)
8059
+ })
8060
+ },
8061
+ onError: {
8062
+ target: "staticChecks",
8063
+ actions: assign({ rolesSetup: false })
8064
+ }
8065
+ }
8066
+ },
8067
+ staticChecks: {
8068
+ always: [{ guard: "shouldSkipStaticChecks", target: "buildAndPlaywright" }],
8069
+ invoke: {
8070
+ src: "staticChecks",
8071
+ input: ({ context }) => createStaticChecksInput(context),
8072
+ onDone: [
8073
+ {
8074
+ guard: ({ event }) => !event.output.passed,
8075
+ target: "failed",
8076
+ actions: assign({
8077
+ staticChecksPassed: false,
8078
+ error: ({ event }) => event.output.error ?? "Static checks failed"
8079
+ })
8080
+ },
8081
+ {
8082
+ target: "buildAndPlaywright",
8083
+ actions: assign({ staticChecksPassed: true })
8084
+ }
8085
+ ],
8086
+ onError: {
8087
+ target: "failed",
8088
+ actions: assign({
8089
+ staticChecksPassed: false,
8090
+ error: ({ event }) => extractErrorMessage2(event, "Static checks failed")
8091
+ })
8092
+ }
8093
+ }
8094
+ },
8095
+ buildAndPlaywright: {
8096
+ always: [{ guard: "shouldSkipBuild", target: "appStart" }],
8097
+ invoke: {
8098
+ src: "buildAndPlaywright",
8099
+ input: ({ context }) => ({
8100
+ ...createBuildAndPlaywrightInput(context),
8101
+ skipPlaywright: shouldSkipPlaywrightInstall(context)
8102
+ }),
8103
+ onDone: [
8104
+ {
8105
+ guard: ({ event }) => !event.output.buildPassed,
8106
+ target: "failed",
8107
+ actions: assign({
8108
+ appBuildPassed: false,
8109
+ playwrightInstalled: ({ event }) => event.output.playwrightInstalled,
8110
+ manifestGenerated: ({ event }) => event.output.manifestGenerated,
8111
+ error: ({ event }) => event.output.buildError ?? "App build failed"
8112
+ })
8113
+ },
8114
+ {
8115
+ target: "appStart",
8116
+ actions: assign({
8117
+ appBuildPassed: true,
8118
+ playwrightInstalled: ({ event }) => event.output.playwrightInstalled,
8119
+ manifestGenerated: ({ event }) => event.output.manifestGenerated
8120
+ })
8121
+ }
8122
+ ],
8123
+ onError: {
8124
+ target: "failed",
8125
+ actions: assign({
8126
+ appBuildPassed: false,
8127
+ error: ({ event }) => extractErrorMessage2(event, "App build failed")
8128
+ })
8129
+ }
8130
+ }
8131
+ },
8132
+ appStart: {
8133
+ always: [{ guard: "shouldSkipAppStart", target: "capabilities" }],
8134
+ invoke: {
8135
+ src: "appStart",
8136
+ input: ({ context }) => createAppStartInput(context),
8137
+ onDone: [
8138
+ {
8139
+ guard: ({ event }) => !event.output.started,
8140
+ target: "failed",
8141
+ actions: assign({
8142
+ appStarted: false,
8143
+ error: ({ event }) => event.output.error ?? "App start failed"
8144
+ })
8145
+ },
8146
+ {
8147
+ target: "capabilities",
8148
+ actions: assign({
8149
+ appStarted: true,
8150
+ appPid: ({ event }) => event.output.pid ?? null,
8151
+ baseUrl: ({ event }) => event.output.baseUrl ?? null
8152
+ })
8153
+ }
8154
+ ],
8155
+ onError: {
8156
+ target: "failed",
8157
+ actions: assign({
8158
+ appStarted: false,
8159
+ error: ({ event }) => extractErrorMessage2(event, "App start failed")
8160
+ })
8161
+ }
8162
+ }
8163
+ },
8164
+ capabilities: {
8165
+ invoke: {
8166
+ src: "capabilities",
8167
+ input: ({ context }) => createCapabilitiesInput(context),
8168
+ onDone: {
8169
+ target: "runCoreTests",
8170
+ actions: assign({
8171
+ capabilities: ({ event }) => event.output.detected?.capabilities ?? [],
8172
+ selectedLayers: ({ context, event }) => filterRunnableLayers(context.selectedLayers, event.output.layerContent),
8173
+ layerSkipReasons: ({ event }) => extractLayerSkipReasons(event.output.layerContent)
8174
+ })
8175
+ },
8176
+ onError: {
8177
+ target: "runCoreTests"
8178
+ }
8179
+ }
8180
+ },
8181
+ runCoreTests: {
8182
+ invoke: {
8183
+ src: "runLayers",
8184
+ input: ({ context }) => createRunCoreTestsInput(context),
8185
+ onDone: [
8186
+ {
8187
+ guard: ({ event }) => {
8188
+ const failedLayers = event.output.failedLayers ?? [];
8189
+ return failedLayers.length > 0;
8190
+ },
8191
+ target: "coreTestsFailed",
8192
+ actions: assign({
8193
+ testsRun: true,
8194
+ layerResults: ({ event }) => deriveCoreLayerResults(event.output)
8195
+ })
8196
+ },
8197
+ {
8198
+ target: "coreTestsComplete",
8199
+ actions: assign({
8200
+ testsRun: true,
8201
+ layerResults: ({ context, event }) => {
8202
+ const nextResults = convertLayerResults(event.output.results);
8203
+ return mergeLayerResults(context.layerResults, nextResults);
8204
+ }
8205
+ })
8206
+ }
8207
+ ],
8208
+ onError: {
8209
+ target: "coreTestsFailed",
8210
+ actions: assign({
8211
+ testsRun: true,
8212
+ error: ({ event }) => extractErrorMessage2(event, "Core tests failed")
8213
+ })
8214
+ }
8215
+ }
8216
+ },
8217
+ coreTestsComplete: {
8218
+ always: [
8219
+ { guard: ({ context }) => hasE2ELayer(context.selectedLayers), target: "e2ePhase" },
8220
+ {
8221
+ target: "done",
8222
+ actions: assign({
8223
+ exitCode: ({ context }) => computeExitCodeFromLayerResults(context.layerResults)
8224
+ })
8225
+ }
8226
+ ]
8227
+ },
8228
+ coreTestsFailed: {
8229
+ entry: assign({
8230
+ exitCode: ({ context }) => {
8231
+ if (Object.keys(context.layerResults).length === 0) {
8232
+ return 1;
8233
+ }
8234
+ return computeExitCodeFromLayerResults(context.layerResults);
8235
+ }
8236
+ }),
8237
+ always: [{ target: "#ci.finalize" }]
8238
+ },
8239
+ e2ePhase: {
8240
+ type: "parallel",
8241
+ states: {
8242
+ intermediateComment: {
8243
+ initial: "checking",
8244
+ states: {
8245
+ checking: {
8246
+ always: [
8247
+ {
8248
+ guard: ({ context }) => !context.prContext?.prNumber,
8249
+ target: "done"
8250
+ },
8251
+ {
8252
+ guard: ({ context }) => !shouldPostGitHubComment(context),
8253
+ target: "done"
8254
+ },
8255
+ { target: "posting" }
8256
+ ]
8257
+ },
8258
+ posting: {
8259
+ invoke: {
8260
+ src: "upsertComment",
8261
+ input: ({ context }) => createIntermediateCommentRequest(context),
8262
+ onDone: "done",
8263
+ onError: "done"
8264
+ }
8265
+ },
8266
+ done: { type: "final" }
8267
+ }
8268
+ },
8269
+ e2eTests: {
8270
+ initial: "running",
8271
+ states: {
8272
+ running: {
8273
+ invoke: {
8274
+ src: "runLayers",
8275
+ input: ({ context }) => createE2ERunLayersInput(context),
8276
+ onDone: {
8277
+ target: "done",
8278
+ actions: assign({
8279
+ layerResults: ({ context, event }) => {
8280
+ const e2eResults = convertLayerResults(event.output.results);
8281
+ return mergeLayerResults(context.layerResults, e2eResults);
8282
+ }
8283
+ })
8284
+ },
8285
+ onError: {
8286
+ target: "done",
8287
+ actions: assign({
8288
+ layerResults: ({ context }) => ({
8289
+ ...context.layerResults,
8290
+ [E2E_LAYER]: { status: "failed", exitCode: 1 }
8291
+ })
8292
+ })
8293
+ }
8294
+ }
8295
+ },
8296
+ done: { type: "final" }
8297
+ }
8298
+ }
8299
+ },
8300
+ onDone: [
8301
+ {
8302
+ guard: ({ context }) => Object.values(context.layerResults).some(
8303
+ (result) => result.status === "failed"
8304
+ ),
8305
+ target: "failed",
8306
+ actions: assign({
8307
+ exitCode: ({ context }) => computeExitCodeFromLayerResults(context.layerResults)
8308
+ })
8309
+ },
8310
+ {
8311
+ target: "done",
8312
+ actions: assign({
8313
+ exitCode: ({ context }) => computeExitCodeFromLayerResults(context.layerResults)
8314
+ })
8315
+ }
8316
+ ]
8317
+ },
8318
+ failed: {
8319
+ always: [
8320
+ {
8321
+ target: "#ci.finalize",
8322
+ actions: assign({
8323
+ exitCode: ({ context }) => context.exitCode !== 0 ? context.exitCode : 1
8324
+ })
8325
+ }
8326
+ ]
8327
+ },
8328
+ done: {
8329
+ type: "final"
8330
+ }
8331
+ }
8332
+ },
8333
+ observability: {
8334
+ initial: "productionPreview",
8335
+ states: {
8336
+ productionPreview: {
8337
+ invoke: {
8338
+ src: "productionPreview",
8339
+ input: ({ context }) => ({
8340
+ repoRoot: assertRepoRoot(context),
8341
+ tmpDir: assertTmpDir(context),
8342
+ shouldExecute: Boolean(context.input.productionDatabaseUrl?.trim())
8343
+ }),
8344
+ onDone: {
8345
+ target: "collectSchemaStats",
8346
+ actions: assign({
8347
+ productionPreview: ({ event }) => event.output.preview
8348
+ })
8349
+ },
8350
+ onError: {
8351
+ target: "collectSchemaStats",
8352
+ actions: assign({
8353
+ productionPreview: ({ event }) => ({
8354
+ executed: true,
8355
+ planSql: null,
8356
+ hazards: [],
8357
+ hasChanges: false,
8358
+ error: event.error instanceof Error ? event.error.message : "Production preview failed"
8359
+ })
8360
+ })
8361
+ }
8362
+ }
8363
+ },
8364
+ collectSchemaStats: {
8365
+ invoke: {
8366
+ src: "collectSchemaStats",
8367
+ input: ({ context }) => ({
8368
+ repoRoot: context.repoRoot ?? process.cwd(),
8369
+ referenceDbUrl: context.supabase?.databaseUrlRaw ?? null,
8370
+ ciDbUrl: context.supabase?.databaseUrlRaw ?? null,
8371
+ referenceStrategy: shouldReuseCiReferenceStats(context) ? "reuse-ci" : "rebuild",
8372
+ queryProduction: true,
8373
+ tmpDir: context.tmpDir ?? process.cwd()
8374
+ }),
8375
+ onDone: {
8376
+ target: "done",
8377
+ actions: assign({
8378
+ schemaStats: ({ event }) => event.output.schemaStats
8379
+ })
8380
+ },
8381
+ onError: {
8382
+ target: "done",
8383
+ actions: assign({
8384
+ schemaStats: () => null
8385
+ })
8386
+ }
8387
+ }
8388
+ },
8389
+ done: {
8390
+ type: "final"
8391
+ }
8392
+ }
8393
+ }
8394
+ },
8395
+ onDone: {
8396
+ target: "finalize"
8397
+ }
8398
+ },
8399
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
7769
8400
  // Production Preview (ci-pr modes only, when schema changes exist)
7770
8401
  // Runs dry-run against production to show what SQL would be applied
7771
8402
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -7825,7 +8456,7 @@ var ciMachine = setup({
7825
8456
  repoRoot: context.repoRoot ?? process.cwd(),
7826
8457
  referenceDbUrl: context.supabase?.databaseUrlRaw ?? null,
7827
8458
  ciDbUrl: context.supabase?.databaseUrlRaw ?? null,
7828
- // Query production only in ci-pr modes
8459
+ referenceStrategy: shouldReuseCiReferenceStats(context) ? "reuse-ci" : "rebuild",
7829
8460
  queryProduction: context.mode !== "ci-local",
7830
8461
  tmpDir: context.tmpDir ?? process.cwd()
7831
8462
  }),
@@ -8361,6 +8992,19 @@ var STATE_TO_STEP = {
8361
8992
  pullProduction: "syncSchema",
8362
8993
  syncSchema: "syncSchema",
8363
8994
  applySeeds: "applySeeds",
8995
+ postSeedPr: "applySeeds",
8996
+ "postSeedPr.execution.setupRoles": "applySeeds",
8997
+ "postSeedPr.execution.staticChecks": "staticChecks",
8998
+ "postSeedPr.execution.buildAndPlaywright": "build",
8999
+ "postSeedPr.execution.appStart": "build",
9000
+ "postSeedPr.execution.capabilities": "build",
9001
+ "postSeedPr.execution.runCoreTests": "runTests",
9002
+ "postSeedPr.execution.coreTestsComplete": "runTests",
9003
+ "postSeedPr.execution.coreTestsFailed": "runTests",
9004
+ "postSeedPr.execution.e2ePhase": "runTests",
9005
+ "postSeedPr.execution.done": "runTests",
9006
+ "postSeedPr.observability.productionPreview": "applySeeds",
9007
+ "postSeedPr.observability.collectSchemaStats": "applySeeds",
8364
9008
  decidePath: "applySeeds",
8365
9009
  installPgTap: "applySeeds",
8366
9010
  setupRoles: "applySeeds",
@@ -8398,6 +9042,17 @@ var STEP_ORDER = [
8398
9042
  "runTests",
8399
9043
  "finalize"
8400
9044
  ];
9045
+ function resolveStepForState(state) {
9046
+ let cursor = state;
9047
+ while (cursor.length > 0) {
9048
+ const step = STATE_TO_STEP[cursor];
9049
+ if (step) return step;
9050
+ const nextDot = cursor.lastIndexOf(".");
9051
+ if (nextDot === -1) break;
9052
+ cursor = cursor.slice(0, nextDot);
9053
+ }
9054
+ return void 0;
9055
+ }
8401
9056
  function getCompletedSteps(currentStep) {
8402
9057
  const currentIndex = STEP_ORDER.indexOf(currentStep);
8403
9058
  if (currentIndex <= 0) return [];
@@ -8486,8 +9141,8 @@ function determineFailedStep(context) {
8486
9141
  function handleProgressCommentUpdate(snapshot, prevState) {
8487
9142
  const currentState = getStateName(snapshot);
8488
9143
  const context = snapshot.context;
8489
- const currentStep = STATE_TO_STEP[currentState];
8490
- const prevStep = STATE_TO_STEP[prevState];
9144
+ const currentStep = resolveStepForState(currentState);
9145
+ const prevStep = resolveStepForState(prevState);
8491
9146
  if (!currentStep || currentStep === prevStep) return;
8492
9147
  recordStepTiming(currentStep);
8493
9148
  if (currentStep === "finalize") {
@@ -9054,6 +9709,75 @@ var stateLogHandlers2 = {
9054
9709
  logSection3("Database: Apply Seeds (db seed)");
9055
9710
  logger.info("Running: pnpm exec runa db seed ci --auto-approve");
9056
9711
  },
9712
+ "postSeedPr.execution.setupRoles": (ctx, logger) => {
9713
+ if (ctx.seedsApplied) {
9714
+ logger.success("Seeds applied successfully");
9715
+ }
9716
+ logSection3("Database: Setup Roles (db:setup-roles)");
9717
+ logger.info("Running: pnpm db:setup-roles");
9718
+ },
9719
+ "postSeedPr.observability.productionPreview": (ctx, logger) => {
9720
+ if (ctx.seedsApplied) {
9721
+ logger.success("Seeds applied successfully");
9722
+ }
9723
+ logSection3("Database: Production Preview (dry-run)");
9724
+ logger.info("Running: pnpm exec runa db apply production --check");
9725
+ },
9726
+ "postSeedPr.observability.collectSchemaStats": (ctx, logger) => {
9727
+ if (ctx.productionPreview?.executed) {
9728
+ if (ctx.productionPreview.hasChanges) {
9729
+ logger.info("Production preview: schema changes detected");
9730
+ } else {
9731
+ logger.success("Production preview: no schema changes");
9732
+ }
9733
+ }
9734
+ logSection3("Database: Collect Schema Statistics");
9735
+ logger.info("Comparing Local/CI/Production schemas...");
9736
+ },
9737
+ "postSeedPr.execution.staticChecks": (ctx, logger) => {
9738
+ if (ctx.rolesSetup) {
9739
+ logger.success("Database roles configured");
9740
+ }
9741
+ logSection3("Static Checks: type-check + lint (parallel)");
9742
+ logger.info("Running: pnpm type-check || pnpm lint");
9743
+ },
9744
+ "postSeedPr.execution.buildAndPlaywright": (ctx, logger) => {
9745
+ if (ctx.staticChecksPassed) {
9746
+ logger.success("Static checks passed");
9747
+ }
9748
+ logSection3("Build: App Build + Playwright Install (parallel)");
9749
+ logger.info("Running: pnpm build || pnpm exec playwright install chromium");
9750
+ },
9751
+ "postSeedPr.execution.appStart": (ctx, logger) => {
9752
+ if (ctx.appBuildPassed) {
9753
+ logger.success("App build completed");
9754
+ }
9755
+ if (ctx.manifestGenerated) {
9756
+ logger.success("Manifest generated (Layer 3 ready)");
9757
+ } else if (ctx.manifestGenerated === false) {
9758
+ logger.warn("Manifest generation failed (Layer 3 may be skipped)");
9759
+ }
9760
+ if (ctx.playwrightInstalled) {
9761
+ logger.success("Playwright browsers installed");
9762
+ }
9763
+ logSection3("Start Application");
9764
+ logger.info(`Starting app on port ${ctx.app?.port ?? 3e3}...`);
9765
+ },
9766
+ "postSeedPr.execution.capabilities": (ctx, logger) => {
9767
+ if (ctx.appStarted) {
9768
+ logger.success(`App started at ${ctx.baseUrl ?? "http://localhost:3000"}`);
9769
+ }
9770
+ logSection3("Detect Test Capabilities");
9771
+ },
9772
+ "postSeedPr.execution.runCoreTests": (ctx, logger) => {
9773
+ const coreLayers = ctx.selectedLayers.filter((l) => l !== 4);
9774
+ logSection3(`Core Tests: Layer ${coreLayers.join(", ")}`);
9775
+ logger.info(`Running core layers: ${coreLayers.map((l) => `test layer${l}`).join(", ")}`);
9776
+ },
9777
+ "postSeedPr.execution.e2ePhase": (_ctx, logger) => {
9778
+ logSection3("E2E Phase: Layer 4 + Intermediate Comment");
9779
+ logger.info("Running Layer 4 E2E tests (parallel with intermediate PR comment)");
9780
+ },
9057
9781
  productionPreview: (ctx, logger) => {
9058
9782
  if (ctx.seedsApplied) {
9059
9783
  logger.success("Seeds applied successfully");
@@ -9260,7 +9984,7 @@ async function runCiPrCommand(options) {
9260
9984
  { id: "setup", description: "Start local Supabase instance" },
9261
9985
  {
9262
9986
  id: "db",
9263
- description: "db apply \u2192 db seed \u2192 production preview \u2192 schema stats \u2192 db:setup-roles"
9987
+ description: "db apply \u2192 db seed \u2192 (production preview \u2225 schema stats \u2225 db:setup-roles)"
9264
9988
  },
9265
9989
  ...skipStaticChecks ? [] : [{ id: "static", description: "pnpm type-check + pnpm lint (parallel)" }],
9266
9990
  ...skipBuild ? [] : [{ id: "build", description: "pnpm build \u2192 manifest:generate + playwright install" }],