Skip to content

feat: establish forward engineering plan authority - #834

Open
seonghobae wants to merge 200 commits into
mainfrom
feat/forward-engineering-workflow
Open

feat: establish forward engineering plan authority#834
seonghobae wants to merge 200 commits into
mainfrom
feat/forward-engineering-workflow

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Exact current vertical slice

Exact head 14660e1953bf00591bdf21935f0144cc8d11aba2 advances the Phase 1 forward-engineering control plane without creating live DDL authority.

Newly implemented at this head

  • POST /api/migration-plans/{migration_plan_uuid}/apply-runs is a deployer-only, execution-free intent boundary.

  • The server revalidates the immutable unexpired plan, locks the schema-model row FOR UPDATE, rejects stale_revision unless the plan revision UUID/number/digest is still current, and binds the exact plan digest, same-plan passed dry-run UUID, exact observed base digest, plan-bound connection UUID/project, typed connection name, exact destructive-confirmation requirement, actor, and bounded idempotency key.

  • Alembic 0012_apply_intent_confirmation persists a restrictive passed-dry-run self-FK, confirmation digest, and destructive decision. Database checks require these fields only for apply rows and forbid them on dry runs.

  • The idempotency request digest includes the passed dry run and confirmation digest, so same-key/different-confirmation reuse fails closed.

  • A new apply intent stores one hash-chained genesis event but deliberately creates no migration_run_dispatch, Valkey signal, credential access, target connection, SQL, or DDL authority.

  • The real PostgreSQL 14–18 integration matrix now covers migration 0012, apply-intent persistence, exact passed-run linkage, confirmation digest shape, and absence of dispatch. This evidence must pass on the exact GitHub head before it is counted.

  • Role, tenant masking, stale-plan, malformed confirmation, cross-project target, wrong/cancelled/drifted dry-run evidence, destructive mismatch, reserved-evidence spoofing, and idempotency bindings have focused negative coverage.

  • ADR-0004/0005, PRD, TRD, Architecture, UML, data model, v1 contract, threat model, runbook, test strategy, audit, CLAUDE, and CHANGELOG describe the same intent/executor boundary.

  • The React/Vite client now has typed, credentialed transport for immutable plan retrieval, exact dry-run and non-dispatched apply-intent creation, durable run polling, and exact-version cancellation. Resource identifiers are encoded as single URL path segments, plan statements/blockers use the server's structured AST types, apply-intent serialization allowlists only the four server contract fields so caller-added SQL is discarded, and a standalone read-only plan review panel exposes provenance/risk/blockers/structured statement evidence without action authority; the complete Forward UI remains Planned.

Existing Phase 1 foundation

The branch also contains canonical PostgreSQL model validation and deterministic plans, strict snapshot capability adaptation, immutable model revisions, durable run/event/outbox/attempt identities, identifier-only Valkey delivery, execution-neutral dual-lease ownership and heartbeat, isolated dry-run and read-only live-preflight cores, strict result-to-plan bridges, and predecessor PostgreSQL 14–18/Valkey acceptance evidence.

The browser remains an editor/reviewer/intent surface. It cannot provide arbitrary SQL execution authority.

Exact-tree local verification

  • backend: 880 passed, 5 environment-gated skips;
  • focused apply-intent, API, schema, migration, documentation, and PostgreSQL slice: 190 passed, 3 environment-gated skips;
  • app.forward.migration_run: 100% statements and branches (465 statements, 238 branches) in the focused writer suite;
  • mypy: application success in 81 source files and changed production files success;
  • exact-head code-quality mixed-import findings were addressed by direct named imports; focused 88 passed and full backend 877 passed, 5 environment-gated skips after remediation;
  • frontend: 209 passed, typecheck and production build successful;
  • frontend/src/api.ts: 100% statements and branches in the focused transport suite;
  • read-only plan review panel: 100% statements, branches, functions, and lines in focused component coverage; renders provenance, risk, blockers, structured privileges/dependencies/preconditions, executable statements, and review-only proposals without action authority;
  • git diff --check: clean;
  • CodeGraph CLI was unavailable locally, so no CodeGraph result is claimed.

Still Planned and explicitly not production-ready

  • independent approval policy beyond the initiating deployer confirmation where required;
  • any apply dispatch or executor;
  • apply-time drift/CAS, privilege and data-precondition revalidation;
  • transactional/non-transactional segmentation, deterministic locks, bounded target timeouts, credential routing, cancellation propagation, crash/restart recovery, no-replay reconciliation, and post-apply convergence;
  • deployed sandbox/target worker identity and isolation evidence;
  • accessible browser edit → review → dry-run → preflight → confirm → apply/recover → audit E2E;
  • packaging, SBOM/provenance, operational acceptance, and release evidence.

Merge gates

Do not merge until this unchanged exact head has terminal-success required CI and security checks, zero valid unresolved findings, and qualifying independent non-author formal approval where repository policy requires it. CodeRabbit or status-only evidence is supporting evidence, not formal approval. This PR does not establish production apply readiness.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Safe Forward Engineering 제어면을 확장했습니다. 서버 권위형 스키마 모델과 불변 리비전을 저장합니다. PostgreSQL 스냅샷에서 구조화된 migration plan을 생성합니다. durable run, 취소 intent, outbox, Valkey relay와 이벤트 무결성 검증을 추가합니다. 실제 SQL 실행과 worker는 아직 계획 상태입니다.

Changes

Safe Forward Engineering

Layer / File(s) Summary
모델·스냅샷·계획 계약
backend/app/forward/*, backend/app/pg_introspect/*, backend/app/schemas.py, backend/app/models.py, backend/alembic/versions/0008_schema_model_revision.py, backend/alembic/versions/0009_migration_plan.py, backend/tests/test_forward_*
정규화된 스키마 모델, snapshot 변환, canonical digest, immutable revision과 migration plan 저장을 추가했습니다. 지원하지 않는 PostgreSQL 의미는 blocker 또는 검증 오류로 처리합니다.
모델·계획 API와 동시성 제어
backend/app/api/schema_models.py, backend/app/api/migration_plans.py, backend/app/main.py, backend/tests/test_api_schema_models.py, backend/tests/test_api_migration_plans.py
모델과 계획 API를 추가했습니다. ETagIf-Match로 stale revision을 거부합니다. 계획 입력 바인딩, digest 무결성, 만료, 크기 제한, IDOR masking과 idempotency를 검증합니다.
내구성 실행과 이벤트 조회
backend/app/forward/migration_run.py, backend/app/api/migration_runs.py, backend/alembic/versions/0010_migration_run.py, backend/tests/test_forward_migration_run.py, backend/tests/test_api_migration_runs.py
durable run과 append-only 이벤트 체인을 추가했습니다. 상태 전이는 CAS로 수행합니다. 멱등성, 취소 intent, 증거 제한과 digest 검증을 적용합니다.
Dispatch relay와 통합 검증
backend/app/jobs/migration_dispatch_relay.py, backend/app/jobs/valkey_queue.py, backend/app/main.py, backend/tests/test_migration_dispatch_*, backend/tests/test_postgres_migration_run_integration.py, backend/tests/test_valkey_queue*, .github/workflows/ci.yml
기한이 된 dispatch를 claim하고 실행 UUID만 Valkey에 발행합니다. 정확한 claim만 published 상태로 변경합니다. PostgreSQL 및 Valkey 통합 테스트와 수명주기 검증을 추가합니다.
권한·introspection·오류 경계
backend/app/permissions.py, backend/app/api/connections.py, backend/app/pg_introspect/*, backend/app/jobs/worker.py, 관련 테스트
deployer 역할과 적용 권한을 추가했습니다. introspection은 read-only repeatable_read 트랜잭션에서 수행합니다. 워커 오류는 고정 코드로 저장합니다. CORS는 If-MatchETag를 지원합니다.
설계·보안·운영 계약
ARCHITECTURE.md, README.md, SECURITY.md, docs/*, CHANGELOG.md, CLAUDE.md, backend/tests/test_documentation_contract.py
현재 구현 범위와 예정된 sandbox, preflight, apply, convergence 및 frontend 흐름을 문서화했습니다. 문서 상태, Mermaid 계약, CI 기준과 보안 경계를 검증합니다.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant SchemaModelsAPI
  participant MetadataDB
  participant SnapshotAdapter
  participant MigrationPlansAPI
  participant MigrationPlanCompiler
  participant MigrationRunAPI
  participant DispatchRelay
  participant Valkey

  Browser->>SchemaModelsAPI: Submit canonical schema model
  SchemaModelsAPI->>MetadataDB: Store immutable model revision
  Browser->>MigrationPlansAPI: Request migration plan
  MigrationPlansAPI->>SnapshotAdapter: Convert validated snapshot
  SnapshotAdapter-->>MigrationPlansAPI: Return canonical base model
  MigrationPlansAPI->>MigrationPlanCompiler: Compile model difference
  MigrationPlanCompiler-->>MigrationPlansAPI: Return statements, risks, and blockers
  MigrationPlansAPI->>MetadataDB: Store immutable migration plan
  Browser->>MigrationRunAPI: Create or cancel dry-run
  MigrationRunAPI->>MetadataDB: Store run, event, and identifier-only outbox
  DispatchRelay->>MetadataDB: Claim due dispatch
  DispatchRelay->>Valkey: Publish migration run UUID
  DispatchRelay->>MetadataDB: Mark exact dispatch attempt published
  Browser->>MigrationRunAPI: Retrieve validated run history
  MigrationRunAPI->>MetadataDB: Verify event digest chain
  MigrationRunAPI-->>Browser: Return validated state and events
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.20% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 정규 스키마 모델과 불변 마이그레이션 계획을 중심으로 순방향 엔지니어링 권위 체계를 수립하는 주요 변경을 명확히 요약합니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/forward-engineering-workflow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread backend/app/forward/snapshot_adapter.py Fixed

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Rate Limit Exceeded

@seonghobae have exceeded the limit for the number of chat messages per hour. Please wait 0 minutes and 39 seconds before sending another message.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (14)
backend/app/schemas.py (1)

170-184: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

계획 페이로드에 구조화된 모델을 도입하는 것을 검토하십시오.

statements, proposed_statements, blockers, risk_summary는 형식이 없는 dict입니다. 이 페이로드는 파괴적 변경을 검토하는 주요 산출물입니다. 전용 Pydantic 모델을 정의하면 OpenAPI 문서와 검증이 강화됩니다. 후속 단계에서 처리해도 됩니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/schemas.py` around lines 170 - 184, Define dedicated Pydantic
models for the structured payload fields in MigrationPlanOut, then replace the
untyped list[dict] and dict annotations for statements, proposed_statements,
blockers, and risk_summary with those models. Preserve the existing response
shape while ensuring OpenAPI schemas and validation describe each field
explicitly.
backend/app/api/migration_plans.py (1)

114-116: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

plan_json 키 접근 방식을 통일하십시오.

Line 114는 proposed_statements.get(..., [])로 읽습니다. 그러나 Line 116, 130-133, 147-154는 statements, compiler_version, blockers, risk_summary를 직접 인덱싱합니다. compile_migration_plan의 출력 계약이 proposed_statements를 항상 포함한다면 직접 인덱싱하십시오. 포함을 보장하지 않는다면 나머지 키도 방어적으로 읽어야 합니다. 근본 원인은 컴파일러 출력 계약이 명시되지 않은 점입니다. compile_migration_plan에 TypedDict 반환 타입을 도입하면 두 방식의 불일치가 사라집니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/api/migration_plans.py` around lines 114 - 116, Unify plan_json
access in compile_migration_plan by defining a TypedDict return contract for the
compiler output, including proposed_statements, statements, compiler_version,
blockers, and risk_summary. Then update the surrounding accesses to consistently
follow that contract, using direct indexing when fields are guaranteed or
defensive defaults when they are optional.
backend/tests/test_pg_introspect_connection.py (1)

28-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

type: ignore 대신 반환 타입을 정확히 선언하십시오.

fetchvalSELECT EXISTS 조회에 False를 반환하고 그 밖에는 "16.0"을 반환합니다. 근본 원인은 반환 애노테이션이 str로 좁게 선언된 점입니다. 억제 주석을 추가하는 대신 애노테이션을 넓히십시오.

♻️ 제안 변경
-    async def fetchval(self, *_args: object) -> str:
+    async def fetchval(self, *_args: object) -> str | bool:
         if _args and "SELECT EXISTS" in str(_args[0]):
-            return False  # type: ignore[return-value]
+            return False
         return "16.0"
As per coding guidelines: "Keep backend Python code strictly typed; public definitions require docstrings, and mypy plus interrogate checks must continue to pass."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/test_pg_introspect_connection.py` around lines 28 - 31, Update
the fetchval method’s return annotation to accurately allow both the boolean
False result for SELECT EXISTS queries and the string version result, then
remove the type: ignore suppression while preserving the existing return
behavior.

Source: Coding guidelines

backend/tests/test_api_schema_models.py (1)

25-33: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

_validate_base_snapshot 분기에 대한 커버리지를 추가하십시오.

FakeWriteSessionget을 제공하지 않습니다. 모든 테스트가 base_schema_snapshot_uuid를 생략하므로 _validate_base_snapshot이 즉시 반환하고, session.get은 호출되지 않습니다. 따라서 다음 분기가 검증되지 않습니다.

  • 스냅샷이 존재하지 않는 경우
  • 스냅샷이 다른 프로젝트에 속한 경우
  • 스냅샷 status"succeeded"가 아닌 경우

이 분기는 프로젝트 경계를 강제합니다. 422 응답을 확인하는 테스트를 추가하십시오. 제가 테스트 코드를 작성해 드릴까요?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/test_api_schema_models.py` around lines 25 - 33,
FakeWriteSession에 비동기 get 모킹을 추가하고, base_schema_snapshot_uuid를 전달해
_validate_base_snapshot 분기를 실행하는 API 테스트를 보강하십시오. 스냅샷이 없거나 다른 프로젝트에 속하거나 status가
"succeeded"가 아닌 각각의 경우에 대해 422 응답을 검증하고, 유효한 프로젝트 스냅샷 경로의 기존 동작은 유지하십시오.
backend/tests/test_forward_snapshot_adapter.py (1)

311-311: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

mutate 매개변수에 타입을 지정하십시오.

이 파일의 다른 테스트는 모두 매개변수와 반환값에 타입을 지정합니다. mutate만 타입이 없습니다. strict mypy 설정에서는 인자 하나가 미주석이면 함수 전체가 untyped로 처리되어 검사가 실패할 수 있습니다.

♻️ 제안 변경
+from collections.abc import Callable
+from typing import Any
...
-def test_snapshot_adapter_fails_closed_for_uncompiled_features(mutate, message: str) -> None:
+def test_snapshot_adapter_fails_closed_for_uncompiled_features(
+    mutate: Callable[[dict[str, Any]], object], message: str
+) -> None:
As per coding guidelines: "Keep backend Python code strictly typed; public definitions require docstrings, and mypy plus interrogate checks must continue to pass."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/test_forward_snapshot_adapter.py` at line 311,
test_snapshot_adapter_fails_closed_for_uncompiled_features의 mutate 매개변수에 해당
테스트에서 사용하는 변이 함수의 정확한 타입을 지정하고, 기존 message 타입과 반환 타입은 유지하십시오. 인라인 람다나 호출 가능한 객체를
받는다면 저장소의 기존 테스트 타입 별칭을 재사용해 strict mypy 검사를 통과하게 하십시오.

Source: Coding guidelines

backend/app/forward/schema_model.py (1)

257-264: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

선택 필드 처리 규칙을 통일하십시오.

unsupported_features는 Line 238에서 기본값 []을 허용합니다. 그러나 unique_constraints, foreign_keys, indexes는 키가 없으면 _list(None, ...)가 "must be a list" 오류를 발생시킵니다. 결과 canonical JSON은 항상 세 필드를 빈 리스트로 포함하므로, 입력에서도 생략을 허용하면 계약이 일관됩니다.

♻️ 제안 변경
     for field in ("unique_constraints", "foreign_keys", "indexes"):
-        entries = _list(table.get(field), f"{path}.{field}")
+        entries = _list(table.get(field, []), f"{path}.{field}")
         if entries:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/forward/schema_model.py` around lines 257 - 264, Update the
validation loop for unique_constraints, foreign_keys, and indexes to default
missing table fields to empty lists before calling _list, matching the existing
unsupported_features optional-field behavior. Preserve validation of explicitly
provided values and ensure canonical output continues to include all three
fields as empty lists when omitted.
backend/app/forward/snapshot_adapter.py (1)

183-190: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

table_oid 폴백은 키가 없을 때만 동작합니다.

dict.get(key, default)는 키가 없을 때만 기본값을 반환합니다. 스냅샷 행이 relation_oid: None을 포함하면 table_oid 폴백이 적용되지 않습니다. 현재는 뒤이어 예외가 발생하므로 fail-closed입니다. 의도를 명확히 하려면 명시적으로 처리하십시오.

♻️ 제안 변경
-        relation_oid = index_row.get("relation_oid", index_row.get("table_oid"))
+        relation_oid = index_row.get("relation_oid")
+        if relation_oid is None:
+            relation_oid = index_row.get("table_oid")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/forward/snapshot_adapter.py` around lines 183 - 190, Update the
relation_oid resolution in the index loop so table_oid is used when relation_oid
is absent or explicitly None, while preserving a valid relation_oid when
present. Keep the existing primary-key backing-index validation in place.
backend/alembic/versions/0009_migration_plan.py (1)

62-74: 🧹 Nitpick | 🔵 Trivial

만료 계획 조회용 인덱스를 고려하십시오.

expires_at은 만료 검사와 정리 작업의 조건 컬럼이 됩니다. 현재 인덱스는 project_space_uuidschema_model_revision_uuid뿐입니다. 계획 수가 늘어나면 만료 정리 쿼리가 전체 테이블 스캔을 수행합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/alembic/versions/0009_migration_plan.py` around lines 62 - 74, Add an
index on the expires_at column in the migration_plan table alongside the
existing indexes, so expiration checks and cleanup queries can efficiently
filter plans by expiry time.
backend/app/pg_introspect/introspect.py (1)

164-164: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

citus_distributed_tables에 명시적 타입 주석을 추가하십시오.

빈 리스트 리터럴은 mypy strict 모드에서 var-annotated 오류를 유발할 수 있습니다. 백엔드 Python 코드는 mypy 검사를 통과해야 합니다.

♻️ 제안 수정
-            citus_distributed_tables = []
+            citus_distributed_tables: list[asyncpg.Record] = []

As per coding guidelines: "Keep backend Python code strictly typed; public definitions require docstrings, and mypy plus interrogate checks must continue to pass."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/pg_introspect/introspect.py` at line 164, 변수
citus_distributed_tables에 명시적 타입 주석을 추가하여 빈 리스트의 요소 타입을 선언하고 mypy strict 검사를
통과하도록 수정하십시오.

Source: Coding guidelines

backend/app/forward/migration_plan.py (1)

428-432: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

계획 정체성에 스냅샷 계약 버전을 포함하는 방안을 고려하십시오.

계획 digest는 compiler_version, 모델 digest, 문장 목록으로 계산됩니다. 기반 스냅샷을 모델로 변환하는 계약(CURRENT_POSTGRES_SNAPSHOT_CONTRACT_VERSION)은 포함되지 않습니다. 어댑터 의미가 바뀌면 동일한 digest가 서로 다른 의미의 계획을 가리킬 수 있습니다.

snapshot_contract_version을 계획 본문에 추가하면 정체성이 명확해집니다.

Also applies to: 563-576

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/forward/migration_plan.py` around lines 428 - 432, Update the
plan construction flow so each plan includes snapshot_contract_version set from
CURRENT_POSTGRES_SNAPSHOT_CONTRACT_VERSION before _digest_plan computes its
digest. Ensure the field is part of the serialized plan body, so changes to the
snapshot adapter contract produce a distinct plan identity while preserving the
existing digest inputs.
backend/tests/test_forward_schema_model.py (2)

139-139: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

정규식 패턴에 raw string을 사용하세요.

match=에 전달된 패턴은 정규식으로 처리됩니다. primary_key.*not nullable에는 메타문자 .*가 있습니다. 의도가 정규식이면 raw string으로 표시하고, 리터럴 매칭이면 re.escape()를 사용하세요. Ruff RUF043 경고와 일치합니다.

♻️ 제안 수정
-    with pytest.raises(SchemaModelValidationError, match="primary_key.*not nullable"):
+    with pytest.raises(SchemaModelValidationError, match=r"primary_key.*not nullable"):
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/test_forward_schema_model.py` at line 139, Update the
pytest.raises call around the primary_key validation assertion to express its
regex pattern as a raw string, preserving the existing matching behavior and
resolving Ruff RUF043.

Source: Linters/SAST tools


199-204: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

mypy 설정에서 backend/tests만 제외하지 않았습니다. 테스트 함수의 변수 인자에 mutate: Callable[[dict[str, Any]], object]value: object 주석을 추가하세요. 또한 setup.cfg의 mypy 설정도 함께 확인해 적용 범위를 최종 확실히 하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/test_forward_schema_model.py` around lines 199 - 204, Update
test_model_validation_fails_closed to annotate mutate as Callable[[dict[str,
Any]], object] and value as object wherever the test’s variable arguments are
declared. Also inspect setup.cfg’s mypy configuration and ensure the intended
backend/tests exclusion or coverage is correctly applied.

Source: Coding guidelines

backend/tests/test_api_migration_plans.py (1)

122-130: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

유니크 인덱스 경로도 함께 검사하세요.

이 테스트는 __table__.constraintsUniqueConstraint만 확인합니다. SQLAlchemy에서 Index(..., unique=True)로 선언한 유니크 제약은 __table__.indexes에 들어가며 constraints에는 나타나지 않습니다. 현재 형태로는 유니크 인덱스로 추가된 idempotency key를 감지하지 못합니다.

💚 제안 수정
     unique_column_sets = {
         tuple(column.name for column in constraint.columns)
         for constraint in MigrationPlan.__table__.constraints
         if isinstance(constraint, UniqueConstraint)
     }
+    unique_column_sets |= {
+        tuple(column.name for column in index.columns)
+        for index in MigrationPlan.__table__.indexes
+        if index.unique
+    }
 
     assert ("project_space_uuid", "statement_digest") not in unique_column_sets
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/test_api_migration_plans.py` around lines 122 - 130, Extend
test_migration_plans_do_not_use_plan_digest_as_database_idempotency_key to also
inspect MigrationPlan.__table__.indexes for unique indexes, and assert that no
unique index covers (“project_space_uuid”, “statement_digest”). Keep the
existing UniqueConstraint check intact.
backend/tests/test_forward_migration_plan.py (1)

71-89: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

변수 이름이 인자 위치와 반대입니다.

target이라는 변수가 compile_migration_plan의 첫 번째 인자, 즉 base 모델로 전달됩니다. 동작은 맞습니다. 이름만 혼동을 유발합니다. base로 바꾸면 drop 방향이 명확해집니다.

♻️ 제안 수정
-    target = _table_model()
-    target["schemas"][0]["tables"][0]["columns"].append(
+    base = _table_model()
+    base["schemas"][0]["tables"][0]["columns"].append(
         {
             "column_name": "Legacy Value",
             "data_type": "text",
             "nullable": True,
             "ordinal_position": 2,
         }
     )
-    plan = compile_migration_plan(target, _table_model())
+    plan = compile_migration_plan(base, _table_model())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/test_forward_migration_plan.py` around lines 71 - 89, Rename
the local variable target to base in
test_destructive_drop_has_explicit_risk_and_recovery_boundary, and pass base as
the first argument to compile_migration_plan while preserving the existing drop
assertions and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/app/api/migration_plans.py`:
- Around line 106-120: In the async handler around snapshot_to_schema_model and
compile_migration_plan, offload the CPU-intensive compilation and json.dumps
work with anyio.to_thread.run_sync so the event loop remains responsive. Keep
the existing SchemaModelValidationError-to-422 behavior and perform the
MAX_PLAN_STATEMENTS/MAX_PLAN_BYTES validation on the resulting plan and
serialized payload.

In `@backend/app/api/schema_models.py`:
- Around line 90-93: Align the ETag documentation and concurrency tests with
_revision_etag using the revision UUID. In backend/app/api/schema_models.py
lines 90-93, update revise_schema_model’s docstring and _revision_etag
documentation to describe the UUID-based ETag. In
backend/tests/test_api_schema_models.py lines 132-170, use the quoted current
revision UUID for if_match and assert that changing only the base snapshot
creates a new revision; in lines 174-201, use the weak UUID ETag and assert its
rejection.
- Around line 90-93: Update the docstrings for _revision_etag and
revise_schema_model to state that the strong ETag and If-Match value identify
the current revision via schema_model_revision_uuid, not revision_digest or a
digest. Ensure all related documentation, including the additionally referenced
text, consistently describes the UUID-based ETag contract.

In `@backend/app/forward/migration_plan.py`:
- Around line 87-91: _column_sql에서 모델의 column["default"]를 누락하지 않도록 DEFAULT 절을 생성
SQL에 반영하고, CREATE TABLE 및 ADD COLUMN 경로에서 동일한 의미가 유지되게 하세요. 기본값 표현을 안전하게 SQL로
변환하는 기존 유틸리티가 있으면 재사용하고, 지원할 수 없는 default 형식은 계획을 safe로 표시하지 말고 blocker로 처리하여
fail-closed 동작을 유지하세요.
- Around line 197-238: Update the ordinal baseline used by the added-column
validation in the migration-plan logic so deleted-column gaps are not treated as
required positions. Derive the expected ordinals from the current existing
columns’ ranks, then validate each sorted added column as contiguous after that
current sequence while preserving the existing blocker structure.

In `@backend/app/models.py`:
- Around line 236-275: Enforce uniqueness for the immutable plan identity
`(schema_model_revision_uuid, db_connection_uuid, base_schema_snapshot_uuid,
statement_digest)` on `MigrationPlan`, adding an `expires_at` index if expiry
cleanup is planned, and create the required database migration. Update
`create_migration_plan` to look up and reuse an existing valid plan for the same
identity instead of inserting duplicates, while preserving server-authoritative
deterministic behavior.

In `@backend/app/pg_introspect/introspect.py`:
- Around line 169-182: Update the Citus metadata query handling around
CITUS_DISTRIBUTED_TABLES_SQL to catch InsufficientPrivilegeError,
UndefinedColumnError, and UndefinedFunctionError alongside UndefinedTableError;
roll back the savepoint and set citus_distributed_tables to an empty list for
all of these optional Citus failures.

In `@backend/tests/test_api_apply_sql.py`:
- Around line 96-116: Update
test_live_apply_requires_deployer_role_while_dry_run_requires_editor to also
invoke apply_sql with dry_run=True and assert that require_project_member is
called with minimum_role="editor"; retain the existing dry_run=False assertion
for "deployer" so both authorization paths are covered.

In `@backend/tests/test_documentation_contract.py`:
- Around line 68-81: Add concise docstrings to every public test function in
backend/tests/test_documentation_contract.py, including
test_canonical_forward_engineering_documents_exist_and_are_nonempty and the
additional public tests referenced by the comment. Each docstring should briefly
state the test’s contract while preserving the existing test logic.

In `@backend/tests/test_forward_snapshot_adapter.py`:
- Line 60: Update the pytest.raises match patterns at the shown locations to use
raw string literals, preserving the existing “recapture|required” alternation
and resolving Ruff RUF043.

In `@docs/superpowers/specs/2026-08-09-forward-engineering-design.md`:
- Line 8: Adjust the “Implementation snapshot” heading hierarchy so it follows
the preceding top-level heading: change `### Implementation snapshot` to `##
Implementation snapshot`, unless an appropriate intermediate `##` section is
intentionally added.

In `@docs/TEST_STRATEGY.md`:
- Around line 197-215: Add PR workflow security gates for osv-scan,
dependency-review, and trivy-fs under .github/workflows, including database
refresh before trivy-fs and scanning the merge ref rather than the PR head.
Update docs/TEST_STRATEGY.md to document these checks as active PR requirements
instead of deferring them to the release workflow.

---

Nitpick comments:
In `@backend/alembic/versions/0009_migration_plan.py`:
- Around line 62-74: Add an index on the expires_at column in the migration_plan
table alongside the existing indexes, so expiration checks and cleanup queries
can efficiently filter plans by expiry time.

In `@backend/app/api/migration_plans.py`:
- Around line 114-116: Unify plan_json access in compile_migration_plan by
defining a TypedDict return contract for the compiler output, including
proposed_statements, statements, compiler_version, blockers, and risk_summary.
Then update the surrounding accesses to consistently follow that contract, using
direct indexing when fields are guaranteed or defensive defaults when they are
optional.

In `@backend/app/forward/migration_plan.py`:
- Around line 428-432: Update the plan construction flow so each plan includes
snapshot_contract_version set from CURRENT_POSTGRES_SNAPSHOT_CONTRACT_VERSION
before _digest_plan computes its digest. Ensure the field is part of the
serialized plan body, so changes to the snapshot adapter contract produce a
distinct plan identity while preserving the existing digest inputs.

In `@backend/app/forward/schema_model.py`:
- Around line 257-264: Update the validation loop for unique_constraints,
foreign_keys, and indexes to default missing table fields to empty lists before
calling _list, matching the existing unsupported_features optional-field
behavior. Preserve validation of explicitly provided values and ensure canonical
output continues to include all three fields as empty lists when omitted.

In `@backend/app/forward/snapshot_adapter.py`:
- Around line 183-190: Update the relation_oid resolution in the index loop so
table_oid is used when relation_oid is absent or explicitly None, while
preserving a valid relation_oid when present. Keep the existing primary-key
backing-index validation in place.

In `@backend/app/pg_introspect/introspect.py`:
- Line 164: 변수 citus_distributed_tables에 명시적 타입 주석을 추가하여 빈 리스트의 요소 타입을 선언하고 mypy
strict 검사를 통과하도록 수정하십시오.

In `@backend/app/schemas.py`:
- Around line 170-184: Define dedicated Pydantic models for the structured
payload fields in MigrationPlanOut, then replace the untyped list[dict] and dict
annotations for statements, proposed_statements, blockers, and risk_summary with
those models. Preserve the existing response shape while ensuring OpenAPI
schemas and validation describe each field explicitly.

In `@backend/tests/test_api_migration_plans.py`:
- Around line 122-130: Extend
test_migration_plans_do_not_use_plan_digest_as_database_idempotency_key to also
inspect MigrationPlan.__table__.indexes for unique indexes, and assert that no
unique index covers (“project_space_uuid”, “statement_digest”). Keep the
existing UniqueConstraint check intact.

In `@backend/tests/test_api_schema_models.py`:
- Around line 25-33: FakeWriteSession에 비동기 get 모킹을 추가하고,
base_schema_snapshot_uuid를 전달해 _validate_base_snapshot 분기를 실행하는 API 테스트를 보강하십시오.
스냅샷이 없거나 다른 프로젝트에 속하거나 status가 "succeeded"가 아닌 각각의 경우에 대해 422 응답을 검증하고, 유효한 프로젝트
스냅샷 경로의 기존 동작은 유지하십시오.

In `@backend/tests/test_forward_migration_plan.py`:
- Around line 71-89: Rename the local variable target to base in
test_destructive_drop_has_explicit_risk_and_recovery_boundary, and pass base as
the first argument to compile_migration_plan while preserving the existing drop
assertions and behavior.

In `@backend/tests/test_forward_schema_model.py`:
- Line 139: Update the pytest.raises call around the primary_key validation
assertion to express its regex pattern as a raw string, preserving the existing
matching behavior and resolving Ruff RUF043.
- Around line 199-204: Update test_model_validation_fails_closed to annotate
mutate as Callable[[dict[str, Any]], object] and value as object wherever the
test’s variable arguments are declared. Also inspect setup.cfg’s mypy
configuration and ensure the intended backend/tests exclusion or coverage is
correctly applied.

In `@backend/tests/test_forward_snapshot_adapter.py`:
- Line 311: test_snapshot_adapter_fails_closed_for_uncompiled_features의 mutate
매개변수에 해당 테스트에서 사용하는 변이 함수의 정확한 타입을 지정하고, 기존 message 타입과 반환 타입은 유지하십시오. 인라인 람다나
호출 가능한 객체를 받는다면 저장소의 기존 테스트 타입 별칭을 재사용해 strict mypy 검사를 통과하게 하십시오.

In `@backend/tests/test_pg_introspect_connection.py`:
- Around line 28-31: Update the fetchval method’s return annotation to
accurately allow both the boolean False result for SELECT EXISTS queries and the
string version result, then remove the type: ignore suppression while preserving
the existing return behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a9abd63-20cb-44a7-ab15-e459756ada5d

📥 Commits

Reviewing files that changed from the base of the PR and between 72afe6d and 68df39b.

📒 Files selected for processing (50)
  • ARCHITECTURE.md
  • CHANGELOG.md
  • CLAUDE.md
  • README.md
  • SECURITY.md
  • backend/alembic/versions/0008_schema_model_revision.py
  • backend/alembic/versions/0009_migration_plan.py
  • backend/app/api/connections.py
  • backend/app/api/migration_plans.py
  • backend/app/api/schema_models.py
  • backend/app/forward/__init__.py
  • backend/app/forward/migration_plan.py
  • backend/app/forward/schema_model.py
  • backend/app/forward/snapshot_adapter.py
  • backend/app/main.py
  • backend/app/models.py
  • backend/app/permissions.py
  • backend/app/pg_introspect/introspect.py
  • backend/app/pg_introspect/queries.py
  • backend/app/pg_introspect/snapshot_contract.py
  • backend/app/schemas.py
  • backend/tests/test_api_apply_sql.py
  • backend/tests/test_api_migration_plans.py
  • backend/tests/test_api_schema_models.py
  • backend/tests/test_documentation_contract.py
  • backend/tests/test_forward_migration_plan.py
  • backend/tests/test_forward_schema_model.py
  • backend/tests/test_forward_snapshot_adapter.py
  • backend/tests/test_permissions.py
  • backend/tests/test_pg_introspect_connection.py
  • backend/tests/test_pg_introspect_queries.py
  • backend/tests/test_schema_validation.py
  • backend/tests/test_security_headers.py
  • docs/DATA_MODEL.md
  • docs/DOCUMENTATION_AUDIT.md
  • docs/PRD.md
  • docs/STANDARDS.md
  • docs/TEST_STRATEGY.md
  • docs/TRD.md
  • docs/UML.md
  • docs/adr/ADR-0001-server-authoritative-planning.md
  • docs/adr/ADR-0002-isolated-dry-run-and-preflight.md
  • docs/adr/ADR-0003-plan-execution-segmentation.md
  • docs/adr/ADR-0004-durable-runs-and-recovery.md
  • docs/adr/ADR-0005-authority-approvals-and-convergence.md
  • docs/adr/README.md
  • docs/contracts/forward-engineering-v1.md
  • docs/runbooks/forward-engineering.md
  • docs/security/forward-engineering-threat-model.md
  • docs/superpowers/specs/2026-08-09-forward-engineering-design.md

Comment thread backend/app/api/migration_plans.py
Comment thread backend/app/api/schema_models.py
Comment thread backend/app/forward/migration_plan.py
Comment thread backend/app/forward/migration_plan.py
Comment thread backend/app/models.py
Comment thread backend/tests/test_api_apply_sql.py Outdated
Comment thread backend/tests/test_documentation_contract.py
Comment thread backend/tests/test_forward_snapshot_adapter.py Outdated
Comment thread docs/superpowers/specs/2026-08-09-forward-engineering-design.md Outdated
Comment thread docs/TEST_STRATEGY.md Outdated

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please review exact head 16394784f7abda041b9d7971b3051e7a74e42be3. The successor delta adds direct fail-closed branch coverage only; app.forward.migration_run now has 100% statement and branch coverage in the focused suite.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="16394784f7abda041b9d7971b3051e7a74e42be3"
prior="aebac6a068709cbe1308b08736f70e6f0abb3740"
base="72afe6db712b145baaba084f64a1ff4fb36d9fd0"

show_head() {
  local file="$1" start="$2" end="$3"
  echo "-- ${file}:${start}-${end}"
  git show "${head}:${file}" |
    awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%6d  %s\n", NR, $0 }'
}

echo '== Exact commit identity and PR binding =='
git cat-file -e "${head}^{commit}"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "${head}"
printf 'checked_out='; git rev-parse HEAD
gh pr view 834 --json headRefOid,baseRefOid,url --jq '{headRefOid,baseRefOid,url}'
git merge-base --is-ancestor "${prior}" "${head}"
echo "prior_is_ancestor=$?"

echo '== Exact successor delta and whitespace =='
git log --oneline "${prior}..${head}"
git diff --name-status "${prior}..${head}"
git diff --stat "${prior}..${head}"
git diff -U55 "${prior}..${head}" -- \
  backend/app/forward/migration_run.py \
  backend/tests/test_forward_migration_run.py \
  backend/tests/test_documentation_contract.py \
  backend/tests
git diff --check "${head}^" "${head}"
git diff --check "${base}..${head}"

echo '== Changed Python file structure =='
git diff --name-only "${prior}..${head}" -- '*.py' |
while IFS= read -r file; do
  [ -n "${file}" ] || continue
  echo "-- ${file}"
  git show "${head}:${file}" > "/tmp/review-$(basename "${file}")"
  ast-grep outline "/tmp/review-$(basename "${file}")" --items all || true
done

echo '== Exact changed test code =='
git diff --name-only "${prior}..${head}" -- '*.py' |
while IFS= read -r file; do
  [ -n "${file}" ] || continue
  echo "-- ${file}"
  git show "${head}:${file}" | nl -ba | sed -n '1,2600p'
done

echo '== Migration-run fail-closed branches and corresponding test references =='
ast-grep outline backend/app/forward/migration_run.py --items all
git show "${head}:backend/app/forward/migration_run.py" |
  rg -n -C 12 \
  'raise |except |if not | is None|!=|<=|>=|fail|invalid|expired|cancel|digest|attempt|lease|evidence|precondition|idempotenc|IntegrityError|HTTPException'
git show "${head}:backend/tests/test_forward_migration_run.py" |
  rg -n -C 12 \
  'fail.closed|fail_closed|raises|invalid|expired|cancel|digest|attempt|lease|evidence|precondition|idempotenc|IntegrityError'

echo '== Published apply-intent route classification =='
show_head backend/tests/test_documentation_contract.py 1 115
git show "${head}:backend/app/main.py" |
  rg -n -C 5 'include_router|migration_plan'
git show "${head}:backend/app/api/migration_plans.py" |
  rg -n -C 12 'apply-runs|create_apply_run'

echo '== Production changes outside tests in successor delta =='
git diff --name-only "${prior}..${head}" -- backend/app

echo '== Exact-head workflow state =='
branch="$(gh pr view 834 --json headRefName --jq .headRefName)"
gh run list --branch "${branch}" --limit 100 \
  --json databaseId,headSha,status,conclusion,workflowName,createdAt,updatedAt,url \
  --jq ".[] | select(.headSha == \"${head}\") | {databaseId,workflowName,status,conclusion,createdAt,updatedAt,url}" || true
gh pr checks 834 --json name,state,workflow,link,startedAt,completedAt \
  --jq '.[] | {name,state,workflow,startedAt,completedAt,link}' || true

Length of output: 19745


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="16394784f7abda041b9d7971b3051e7a74e42be3"
prior="aebac6a068709cbe1308b08736f70e6f0abb3740"

show_head() {
  local file="$1" start="$2" end="$3"
  echo "-- ${file}:${start}-${end}"
  git show "${head}:${file}" |
    awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%6d  %s\n", NR, $0 }'
}

echo '== Added coverage hunk =='
git diff -U30 "${prior}..${head}" -- backend/tests/test_forward_migration_run.py

echo '== Digest and run-creation implementation paths =='
ast-grep outline backend/app/forward/migration_run.py --items all
git show "${head}:backend/app/forward/migration_run.py" |
  rg -n -C 24 \
  'def digest_run_request|async def create_migration_run|passed dry run|apply confirmation|dry-run confirmation|requires_destructive_confirmation|typed_connection_name'

echo '== Relevant full test methods =='
show_head backend/tests/test_forward_migration_run.py 185 245
show_head backend/tests/test_forward_migration_run.py 2583 2705

echo '== Documentation route classification and published route =='
show_head backend/tests/test_documentation_contract.py 35 75
git show "${head}:backend/app/main.py" |
  rg -n -C 4 'migration_plans_router|include_router'
git show "${head}:backend/app/api/migration_plans.py" |
  rg -n -C 10 'apply-runs|create_apply_run'

echo '== Test-only successor confirmation =='
git diff --name-only "${prior}..${head}"
git diff --check "${head}^" "${head}"

echo '== Exact-head workflow state =='
branch="$(gh pr view 834 --json headRefName --jq .headRefName)"
gh run list --branch "${branch}" --limit 100 \
  --json databaseId,headSha,status,conclusion,workflowName,createdAt,updatedAt,url \
  --jq ".[] | select(.headSha == \"${head}\") | {databaseId,workflowName,status,conclusion,createdAt,updatedAt,url}" || true
gh pr checks 834 --json name,state,workflow,link,startedAt,completedAt \
  --jq '.[] | {name,state,workflow,startedAt,completedAt,link}' || true

Length of output: 44810


@seonghobae I reviewed exact head 16394784f7abda041b9d7971b3051e7a74e42be3.

The successor delta contains only direct tests in backend/tests/test_forward_migration_run.py.

The new cases correctly cover these fail-closed paths:

  • digest_run_request() rejects an apply intent without passed_dry_run_uuid.
  • digest_run_request() rejects an invalid apply confirmation digest.
  • create_migration_run() rejects confirmation inputs on a dry run.
  • create_migration_run() rejects a non-boolean requires_destructive_confirmation value before database access.

These tests match the existing validation in backend/app/forward/migration_run.py. I found no defect in the coverage-only delta.

One predecessor finding remains unresolved.

  1. P2 — Classify the published apply-intent route as implemented.
    backend/tests/test_documentation_contract.py, Lines 47-49 still classifies:

    POST /api/migration-plans/{migration_plan_uuid}/apply-runs
    

    as a planned route. However, backend/app/main.py registers migration_plans_router, and backend/app/api/migration_plans.py publishes create_apply_run() at that route.

    Move the route to CURRENT_ROUTES. Keep apply dispatch, worker execution, credential routing, and DDL execution classified as planned.

git diff --check passed for the exact commit. The exact-head backend job succeeded. Several PostgreSQL, Valkey, security, SAST, and CodeQL jobs were still queued or in progress.

You are interacting with an AI system.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please review exact head 3c10a4477aa9119489129d9b67457f3ce402f027. This successor closes the current-model-revision race: the API locks the plan's schema-model row FOR UPDATE, and the writer rejects any non-current exact revision UUID/number/digest/project binding before inserting a non-dispatched intent.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="3c10a4477aa9119489129d9b67457f3ce402f027"
prior="16394784f7abda041b9d7971b3051e7a74e42be3"
base="72afe6db712b145baaba084f64a1ff4fb36d9fd0"

show_head() {
  local file="$1" start="$2" end="$3"
  echo "-- ${file}:${start}-${end}"
  git show "${head}:${file}" |
    awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%6d  %s\n", NR, $0 }'
}

echo '== Exact identity and PR binding =='
git cat-file -e "${head}^{commit}"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "${head}"
printf 'checked_out='; git rev-parse HEAD
gh pr view 834 --json headRefOid,baseRefOid,url --jq '{headRefOid,baseRefOid,url}'
git merge-base --is-ancestor "${prior}" "${head}"
echo "prior_is_ancestor=$?"

echo '== Exact successor delta and whitespace =='
git log --oneline "${prior}..${head}"
git diff --name-status "${prior}..${head}"
git diff --stat "${prior}..${head}"
git diff -U60 "${prior}..${head}" -- \
  backend/app/api/migration_plans.py \
  backend/app/forward/migration_run.py \
  backend/app/models.py \
  backend/app/schemas.py \
  backend/tests/test_api_migration_plans.py \
  backend/tests/test_api_migration_runs.py \
  backend/tests/test_forward_migration_run.py \
  backend/tests/test_postgres_migration_run_integration.py \
  backend/tests/test_documentation_contract.py \
  backend/app/jobs \
  backend/app/main.py \
  docs ARCHITECTURE.md CHANGELOG.md README.md SECURITY.md
git diff --check "${head}^" "${head}"
git diff --check "${base}..${head}"

echo '== Changed Python structure =='
git diff --name-only "${prior}..${head}" -- '*.py' |
while IFS= read -r file; do
  [ -n "${file}" ] || continue
  echo "-- ${file}"
  git show "${head}:${file}" > "/tmp/review-$(basename "${file}")"
  ast-grep outline "/tmp/review-$(basename "${file}")" --items all || true
done

echo '== API and domain implementation context =='
for spec in \
  'backend/app/api/migration_plans.py:300:560' \
  'backend/app/forward/migration_run.py:450:980' \
  'backend/app/models.py:180:460' \
  'backend/tests/test_api_migration_plans.py:1:1400' \
  'backend/tests/test_api_migration_runs.py:1:1500' \
  'backend/tests/test_forward_migration_run.py:1:3000' \
  'backend/tests/test_postgres_migration_run_integration.py:1:1600' \
  'backend/tests/test_documentation_contract.py:1:130'
do
  file="${spec%%:*}"
  rest="${spec#*:}"
  start="${rest%%:*}"
  end="${rest##*:}"
  if git cat-file -e "${head}:${file}" 2>/dev/null; then
    show_head "${file}" "${start}" "${end}"
  fi
done

echo '== Current-revision locking, binding validation, idempotency, and authority references =='
git grep -n -I -C 10 -E \
  'SchemaModel|SchemaModelRevision|with_for_update|FOR UPDATE|revision_uuid|revision_number|revision_digest|model_digest|current_revision|project_space_uuid|create_apply_run|create_migration_run|idempotency|MigrationRunDispatch|dispatch|enqueue|signal|credential|dsn|password|execute_|apply' \
  "${head}" -- backend/app backend/tests docs || true

echo '== Apply creation and dispatch construction callers outside tests =='
git grep -n -I -C 6 \
  'create_migration_run(' "${head}" -- backend/app
git grep -n -I -C 6 \
  'MigrationRunDispatch(' "${head}" -- backend/app
git grep -n -I -C 6 \
  'with_for_update(' "${head}" -- backend/app

echo '== Exact-head checks =='
branch="$(gh pr view 834 --json headRefName --jq .headRefName)"
gh run list --branch "${branch}" --limit 100 \
  --json databaseId,headSha,status,conclusion,workflowName,createdAt,updatedAt,url \
  --jq ".[] | select(.headSha == \"${head}\") | {databaseId,workflowName,status,conclusion,createdAt,updatedAt,url}" || true
gh pr checks 834 --json name,state,workflow,link,startedAt,completedAt \
  --jq '.[] | {name,state,workflow,startedAt,completedAt,link}' || true

Length of output: 204260


The first inspection confirms the intended scope. I need the focused code and regression assertions to verify the lock and validation order.


🧠 Learnings used
Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T01:38:08.391Z
Learning: In PR `#834`, `POST /api/migration-plans/{migration_plan_uuid}/dry-runs` is an editor-authorized control-plane endpoint. It requires an exact plan digest and a bounded `Idempotency-Key`, persists or reuses a queued dry-run intent, and does not enqueue work, signal a worker, or execute SQL. Sandbox and worker execution remain planned.

You are interacting with an AI system.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please review exact head 19e38ff897caca55d810ea921ee1e0807823d426. This test-only successor closes the valid P2 documentation-contract finding by classifying the already-published, non-dispatched apply-runs intent route as Current while keeping dispatch, credentials, worker execution, and DDL apply Planned. Full backend: 878 passed, 5 skipped; mypy: 81 source files.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="19e38ff897caca55d810ea921ee1e0807823d426"
prior="3c10a4477aa9119489129d9b67457f3ce402f027"
base="72afe6db712b145baaba084f64a1ff4fb36d9fd0"

show_head() {
  local file="$1" start="$2" end="$3"
  echo "-- ${file}:${start}-${end}"
  git show "${head}:${file}" |
    awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%6d  %s\n", NR, $0 }'
}

echo '== Exact commit and PR binding =='
git cat-file -e "${head}^{commit}"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "${head}"
printf 'checked_out='; git rev-parse HEAD
gh pr view 834 --json headRefOid,baseRefOid,url --jq '{headRefOid,baseRefOid,url}'
git merge-base --is-ancestor "${prior}" "${head}"
echo "prior_is_ancestor=$?"

echo '== Successor delta and whitespace =='
git log --oneline "${prior}..${head}"
git diff --name-status "${prior}..${head}"
git diff --stat "${prior}..${head}"
git diff -U60 "${prior}..${head}"
git diff --check "${head}^" "${head}"
git diff --check "${base}..${head}"

echo '== Documentation contract route classification =='
ast-grep outline backend/tests/test_documentation_contract.py --items all
show_head backend/tests/test_documentation_contract.py 1 125
git show "${head}:backend/tests/test_documentation_contract.py" |
  rg -n -C 10 \
  'CURRENT_ROUTES|PLANNED_ROUTES|POST /api/migration-plans/\{migration_plan_uuid\}/apply-runs|apply.intent|non-dispatched|dispatch|credential|worker|DDL|apply'

echo '== Route publication and authority boundary =='
git show "${head}:backend/app/main.py" |
  rg -n -C 6 'include_router|migration_plans_router'
git show "${head}:backend/app/api/migration_plans.py" |
  rg -n -C 14 'apply-runs|create_apply_run|MigrationRunDispatch|dispatch|enqueue|signal|credential|execute'
git show "${head}:backend/app/forward/migration_run.py" |
  rg -n -C 12 'async def create_migration_run|MigrationRunDispatch|run_kind == "apply"|dispatch'

echo '== Contract test references and exact-head checks =='
git grep -n -I -C 5 -E \
  'apply-runs|CURRENT_ROUTES|PLANNED_ROUTES|apply intent|non-dispatched|dispatch|credential|worker|DDL' \
  "${head}" -- backend/tests docs ARCHITECTURE.md CHANGELOG.md README.md SECURITY.md || true

branch="$(gh pr view 834 --json headRefName --jq .headRefName)"
gh run list --branch "${branch}" --limit 100 \
  --json databaseId,headSha,status,conclusion,workflowName,createdAt,updatedAt,url \
  --jq ".[] | select(.headSha == \"${head}\") | {databaseId,workflowName,status,conclusion,createdAt,updatedAt,url}" || true
gh pr checks 834 --json name,state,workflow,link,startedAt,completedAt \
  --jq '.[] | {name,state,workflow,startedAt,completedAt,link}' || true

Length of output: 202865


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="19e38ff897caca55d810ea921ee1e0807823d426"
prior="3c10a4477aa9119489129d9b67457f3ce402f027"
log="/tmp/coderabbit-shell-logs/shell-output-u3FcS2"

echo '== Exact successor diff =='
git diff -U25 "${prior}..${head}" -- backend/tests/test_documentation_contract.py

echo '== Current and planned route declarations =='
git show "${head}:backend/tests/test_documentation_contract.py" |
  nl -ba | sed -n '25,95p'

echo '== Route classification test body =='
git show "${head}:backend/tests/test_documentation_contract.py" |
  rg -n -C 18 \
  'CURRENT_ROUTES|PLANNED_ROUTES|test_v1_contract|apply-runs|apply intent|non-dispatched'

echo '== Only changed files =='
git diff --name-only "${prior}..${head}"
git diff --check "${head}^" "${head}"

echo '== First inspection identity and diff sections =='
rg -n -C 4 \
  'Exact commit and PR binding|Successor delta and whitespace|Documentation contract route classification|CURRENT_ROUTES|PLANNED_ROUTES' \
  "${log}" | head -n 320

Length of output: 6242


@seonghobae I reviewed exact head 19e38ff897caca55d810ea921ee1e0807823d426.

I found no actionable source-level defect.

The successor changes only backend/tests/test_documentation_contract.py.

It resolves the predecessor P2.

  • POST /api/migration-plans/{migration_plan_uuid}/apply-runs is now in CURRENT_ROUTES.
  • The route is no longer in PLANNED_ROUTES.
  • test_published_apply_intent_route_is_not_classified_as_planned() prevents this classification from regressing.

The route remains correctly scoped as an implemented non-dispatched apply-intent boundary. The contract keeps dispatch, credential access, worker execution, and DDL execution planned.

git diff --check reported no whitespace error for the successor diff.

Exact-head CI acceptance is not verified. CodeQL JavaScript completed successfully. CodeQL Python, backend, PostgreSQL 14–18, Valkey, Semgrep, and central security checks were queued. Strix was in progress during inspection.


🧠 Learnings used
Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-12T01:30:06.739Z
Learning: In PR `#834`, `backend/app/jobs/migration_run_consumer.py` passes the exact `MigrationRunSignalClaim` to its injected `MigrationRunHandler`. The claim contains `migration_run_uuid` and an opaque `lease_token`; the consumer uses that exact claim for acknowledgement after handler success or retry release after a sanitized ordinary handler failure. The ready Valkey payload remains UUID-only, and the lease token is processing metadata rather than execution authority. Application consumer wiring, durable worker identity and attempt acquisition, credentials, and execution authority remain planned.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T07:13:38.918Z
Learning: In PR `#834`, `backend/app/jobs/migration_run_consumer.py` defines an execution-neutral migration-run consumer contract. It accepts an injected handler and a `migration_run_uuid` through `MigrationRunSignalClaim`; it acknowledges the exact lease only after handler success, releases that exact lease after sanitized handler failure, and raises a fixed lease-loss error when completion ownership is lost. `backend/app/main.py` does not start this consumer; application consumer wiring and worker execution remain planned.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T01:38:08.391Z
Learning: In PR `#834`, `POST /api/migration-plans/{migration_plan_uuid}/dry-runs` is an editor-authorized control-plane endpoint. It requires an exact plan digest and a bounded `Idempotency-Key`, persists or reuses a queued dry-run intent, and does not enqueue work, signal a worker, or execute SQL. Sandbox and worker execution remain planned.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T05:05:31.406Z
Learning: In PR `#834`, `backend/app/jobs/valkey_queue.py` implements UUID-only migration signal lease primitives. The ready and processing sorted-set members are `migration_run_uuid`; an exact `lease_token` is stored separately and is required for acknowledgement or retry release. These primitives do not load plans or credentials, access a target, create a consumer lifecycle, or execute SQL.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please review exact head 4c648f8c1d7784f7ccedbaf2dd02bb09172cd315. This successor adds a typed browser transport boundary for the already-published Forward Engineering control-plane routes: plan retrieval, exact dry-run/apply intent creation, run polling, and version-bound cancellation. It exposes no arbitrary SQL request field and does not add UI, dispatch, credentials, worker, or DDL authority. Local evidence: backend 879 passed/5 skipped; frontend 202 passed; typecheck and production build successful.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="4c648f8c1d7784f7ccedbaf2dd02bb09172cd315"
prior="19e38ff897caca55d810ea921ee1e0807823d426"
base="72afe6db712b145baaba084f64a1ff4fb36d9fd0"

show_head() {
  local file="$1" start="$2" end="$3"
  echo "-- ${file}:${start}-${end}"
  git show "${head}:${file}" |
    awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%6d  %s\n", NR, $0 }'
}

echo '== Exact identity and PR binding =='
git cat-file -e "${head}^{commit}"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "${head}"
printf 'checked_out='; git rev-parse HEAD
gh pr view 834 --json headRefOid,baseRefOid,url --jq '{headRefOid,baseRefOid,url}'
git merge-base --is-ancestor "${prior}" "${head}"
echo "prior_is_ancestor=$?"

echo '== Exact successor delta and whitespace =='
git log --oneline "${prior}..${head}"
git diff --name-status "${prior}..${head}"
git diff --stat "${prior}..${head}"
git diff -U55 "${prior}..${head}"
git diff --check "${head}^" "${head}"
git diff --check "${base}..${head}"

echo '== Changed source structure =='
git diff --name-only "${prior}..${head}" -- '*.{ts,tsx,js,jsx}' '*.py' |
while IFS= read -r file; do
  [ -n "${file}" ] || continue
  echo "-- ${file}"
  git show "${head}:${file}" > "/tmp/review-$(basename "${file}")"
  case "${file}" in
    *.ts|*.tsx) ast-grep outline "/tmp/review-$(basename "${file}")" --lang tsx --items all || true ;;
    *.js|*.jsx) ast-grep outline "/tmp/review-$(basename "${file}")" --lang tsx --items all || true ;;
    *.py) ast-grep outline "/tmp/review-$(basename "${file}")" --items all || true ;;
  esac
done

echo '== Complete changed source and tests =='
git diff --name-only "${prior}..${head}" |
while IFS= read -r file; do
  [ -n "${file}" ] || continue
  echo "-- ${file}"
  git show "${head}:${file}" | nl -ba | sed -n '1,1600p'
done

echo '== Transport API, routes, unsafe fields, and authority references =='
git grep -n -I -C 8 -E \
  'migration-plans|migration-runs|dry-runs|apply-runs|cancel|If-Match|Idempotency-Key|plan_digest|passed_dry_run_uuid|confirmation|sql|query|statement|dispatch|enqueue|worker|credential|password|dsn|connection_string|execute|apply' \
  "${head}" -- frontend backend/app backend/tests docs || true

echo '== Frontend transport calls outside tests =='
git grep -n -I -C 5 -E \
  '(fetch|axios|request|apiClient).*migration|/api/migration-(plans|runs)|/dry-runs|/apply-runs' \
  "${head}" -- frontend || true

echo '== Backend route contracts and implementation boundaries =='
git show "${head}:backend/app/api/migration_plans.py" |
  rg -n -C 12 'dry-runs|apply-runs|create_(dry_run|apply_run)|MigrationRunDispatch|dispatch|enqueue|credential|execute'
git show "${head}:backend/app/api/migration_runs.py" |
  rg -n -C 12 'cancel|If-Match|version|MigrationRunCancelIn|dispatch|credential|execute'
git show "${head}:backend/app/main.py" |
  rg -n -C 5 'include_router|run_migration_run_consumer_forever|create_task' || true

echo '== Exact-head workflow state =='
branch="$(gh pr view 834 --json headRefName --jq .headRefName)"
gh run list --branch "${branch}" --limit 100 \
  --json databaseId,headSha,status,conclusion,workflowName,createdAt,updatedAt,url \
  --jq ".[] | select(.headSha == \"${head}\") | {databaseId,workflowName,status,conclusion,createdAt,updatedAt,url}" || true
gh pr checks 834 --json name,state,workflow,link,startedAt,completedAt \
  --jq '.[] | {name,state,workflow,startedAt,completedAt,link}' || true

Length of output: 73385


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="4c648f8c1d7784f7ccedbaf2dd02bb09172cd315"
prior="19e38ff897caca55d810ea921ee1e0807823d426"

show() {
  local file="$1" start="$2" end="$3"
  echo "-- ${file}:${start}-${end}"
  git show "${head}:${file}" | sed -n "${start},${end}p"
}

echo '== Exact changed frontend files =='
for file in frontend/src/api.ts frontend/src/types.ts frontend/src/forwardApi.test.ts; do
  echo "-- ${file}"
  git show "${head}:${file}" | sed -n '1,800p'
done

echo '== Existing JSON/CSRF header helper =='
git show "${head}:frontend/src/api.ts" |
  rg -n -C 18 'async function jsonHeaders|const jsonHeaders|CSRF|csrf'

echo '== Backend request and response schema contracts =='
git show "${head}:backend/app/schemas.py" |
  rg -n -C 18 \
  'class MigrationPlanOut|class MigrationRunActionOut|class MigrationRunEventOut|class MigrationRunOut|class MigrationRunCancelIn|class MigrationRunCreateIn|class MigrationApplyRunCreateIn|MigrationRunState'

echo '== Endpoint implementations =='
git show "${head}:backend/app/api/migration_plans.py" |
  rg -n -C 22 'async def get_migration_plan|async def create_dry_run|async def create_apply_run|`@router`\.(get|post)'
git show "${head}:backend/app/api/migration_runs.py" |
  rg -n -C 22 'async def get_migration_run|async def cancel_migration_run|`@router`\.(get|post)'

echo '== Browser API use sites outside the new test =='
git grep -n -I -C 6 -E \
  '\b(getMigrationPlan|createDryRun|createApplyRun|getMigrationRun|cancelMigrationRun)\s*\(' \
  "${head}" -- frontend/src ':!frontend/src/forwardApi.test.ts' || true

echo '== SQL and authority fields in added frontend surface =='
git diff -U10 "${prior}..${head}" -- frontend/src/api.ts frontend/src/types.ts frontend/src/forwardApi.test.ts |
  rg -n -i -C 4 \
  'sql|statement|query|dispatch|enqueue|worker|credential|password|dsn|connection_string|execute|apply' || true

echo '== Exact-head checks =='
branch="$(gh pr view 834 --json headRefName --jq .headRefName)"
gh run list --branch "${branch}" --limit 100 \
  --json databaseId,headSha,status,conclusion,workflowName,createdAt,updatedAt,url \
  --jq ".[] | select(.headSha == \"${head}\") | {workflowName,status,conclusion,createdAt,updatedAt,url}" || true
gh pr checks 834 --json name,state,workflow,link,startedAt,completedAt \
  --jq '.[] | {name,state,workflow,startedAt,completedAt,link}' || true

echo '== Whitespace =='
git diff --check "${head}^" "${head}"

Length of output: 63338


@seonghobae I reviewed exact head 4c648f8c1d7784f7ccedbaf2dd02bb09172cd315.

I found one actionable issue.

  1. P2 — Construct the apply-intent request from an allowlist.
    frontend/src/api.ts, Line 313 serializes the caller-supplied object directly:

    body: JSON.stringify(intent)

    TypeScript structural typing permits an object that has the required MigrationApplyIntent fields plus an extra sql field. createApplyRun() then sends that extra field. The current test only proves that its selected literal has no sql field.

    Construct the body explicitly from these four fields:

    • plan_digest
    • passed_dry_run_uuid
    • target_connection_name
    • destructive_acknowledged

    Add a test that passes an object with an extra sql property and verifies that the serialized request body omits it.

    The backend currently restricts MigrationApplyRunCreateIn to the intended fields. Therefore, this issue does not create DDL authority. It does violate the stated browser transport boundary and makes the client dependent on the backend continuing to discard unknown fields.

The remaining transport behavior is correct.

  • Plan and run reads use credentialed same-origin requests.
  • Dry-run and apply-intent creation use CSRF and Idempotency-Key headers.
  • The dry-run body includes only plan_digest.
  • Cancellation sends only expected_state_version.
  • The frontend has no production call sites for the new functions. It does not add a Forward UI.
  • The backend apply-intent route remains non-dispatched. It does not add a worker signal, credential access, or SQL execution.
  • The browser types match the backend request and response contracts for the declared fields.

git diff --check passed for the exact commit.

Exact-head CI acceptance is not verified. The frontend, backend, PostgreSQL 14–18, Valkey, CodeQL, Semgrep, and security jobs were queued during inspection.

I used the existing PR scope context that apply-runs is a non-dispatched apply-intent endpoint.


🧠 Learnings used
Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T01:38:08.391Z
Learning: In PR `#834`, `POST /api/migration-plans/{migration_plan_uuid}/dry-runs` is an editor-authorized control-plane endpoint. It requires an exact plan digest and a bounded `Idempotency-Key`, persists or reuses a queued dry-run intent, and does not enqueue work, signal a worker, or execute SQL. Sandbox and worker execution remain planned.

You are interacting with an AI system.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please review exact head 232ea5d2bc806a7a9e1b3c6c690dd8c414ebd629. Treat predecessor-head findings and status checks as historical.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please review exact head e8c4ddca255f6453357511581376df309afa99a0. The predecessor request at 232ea5d… is stale.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please review exact head 7f00daa99b3a895df7ebb3c0390a97a53cd2ffcd. Earlier review requests are stale.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="232ea5d2bc806a7a9e1b3c6c690dd8c414ebd629"
prior="4c648f8c1d7784f7ccedbaf2dd02bb09172cd315"
base="72afe6db712b145baaba084f64a1ff4fb36d9fd0"

show_head() {
  local file="$1" start="$2" end="$3"
  echo "-- ${file}:${start}-${end}"
  git show "${head}:${file}" |
    awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%6d  %s\n", NR, $0 }'
}

echo '== Exact identity and PR binding =='
git cat-file -e "${head}^{commit}"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "${head}"
printf 'checked_out='; git rev-parse HEAD
gh pr view 834 --json headRefOid,baseRefOid,url --jq '{headRefOid,baseRefOid,url}'
git merge-base --is-ancestor "${prior}" "${head}"
echo "prior_is_ancestor=$?"

echo '== Exact successor delta and whitespace =='
git log --oneline "${prior}..${head}"
git diff --name-status "${prior}..${head}"
git diff --stat "${prior}..${head}"
git diff -U55 "${prior}..${head}"
git diff --check "${head}^" "${head}"
git diff --check "${base}..${head}"

echo '== Changed source structure =='
git diff --name-only "${prior}..${head}" -- '*.{ts,tsx,js,jsx}' '*.py' |
while IFS= read -r file; do
  [ -n "${file}" ] || continue
  echo "-- ${file}"
  git show "${head}:${file}" > "/tmp/review-$(basename "${file}")"
  case "${file}" in
    *.ts|*.tsx|*.js|*.jsx) ast-grep outline "/tmp/review-$(basename "${file}")" --lang tsx --items all || true ;;
    *.py) ast-grep outline "/tmp/review-$(basename "${file}")" --items all || true ;;
  esac
done

echo '== Changed files =='
git diff --name-only "${prior}..${head}" |
while IFS= read -r file; do
  [ -n "${file}" ] || continue
  echo "-- ${file}"
  git show "${head}:${file}" | nl -ba | sed -n '1,1600p'
done

echo '== Focused browser transport and route contracts =='
for spec in \
  'frontend/src/api.ts:1:520' \
  'frontend/src/types.ts:1:700' \
  'frontend/src/forwardApi.test.ts:1:1000'
do
  file="${spec%%:*}"
  rest="${spec#*:}"
  start="${rest%%:*}"
  end="${rest##*:}"
  if git cat-file -e "${head}:${file}" 2>/dev/null; then
    show_head "${file}" "${start}" "${end}"
  fi
done

echo '== Transport input serialization and authority references =='
git grep -n -I -C 8 -E \
  'createApplyRun|createDryRun|cancelMigrationRun|getMigrationPlan|getMigrationRun|JSON\.stringify|plan_digest|passed_dry_run_uuid|target_connection_name|destructive_acknowledged|sql|statement|query|dispatch|enqueue|worker|credential|password|dsn|execute' \
  "${head}" -- frontend/src backend/app backend/tests docs || true

echo '== Backend route boundary and production transport call sites =='
git show "${head}:backend/app/api/migration_plans.py" |
  rg -n -C 12 'apply-runs|create_apply_run|MigrationRunDispatch|dispatch|enqueue|credential|execute' || true
git show "${head}:backend/app/main.py" |
  rg -n -C 5 'include_router|run_migration_run_consumer_forever|create_task' || true
git grep -n -I -C 5 -E \
  '\b(getMigrationPlan|createDryRun|createApplyRun|getMigrationRun|cancelMigrationRun)\s*\(' \
  "${head}" -- frontend/src ':!frontend/src/forwardApi.test.ts' || true

Length of output: 13608


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="232ea5d2bc806a7a9e1b3c6c690dd8c414ebd629"
prior="4c648f8c1d7784f7ccedbaf2dd02bb09172cd315"

echo '== Exact delta and whitespace =='
git diff -U30 "${prior}..${head}" -- \
  frontend/src/api.ts \
  frontend/src/forwardApi.test.ts \
  frontend/vitest.config.ts
git diff --check "${head}^" "${head}"

echo '== Forward resource route definitions =='
git show "${head}:frontend/src/api.ts" |
  rg -n -C 12 \
  'export async function (getMigrationPlan|createDryRun|createApplyRun|getMigrationRun|cancelMigrationRun)|migration-plans|migration-runs|encodeURIComponent|JSON\.stringify'

echo '== Encoding test and test configuration =='
git show "${head}:frontend/src/forwardApi.test.ts" |
  sed -n '1,260p'
git show "${head}:frontend/vitest.config.ts"
git show "${head}:frontend/vite.config.ts" | sed -n '1,260p'

echo '== TypeScript import-extension settings =='
fd -a -i 'tsconfig*.json' frontend -x sh -c \
  'echo "-- $1"; sed -n "1,260p" "$1"' sh {}

echo '== All frontend resource path interpolation forms =='
git show "${head}:frontend/src/api.ts" |
  rg -n -C 4 \
  '\$\{API_BASE\}/api/(migration-plans|migration-runs)/|encodeURIComponent\('

echo '== Configuration import conventions =='
git grep -n -I -C 3 -E \
  "from ['\"].*\\.config\\.ts['\"]|from ['\"].*\\.ts['\"]" \
  "${head}" -- frontend ':!frontend/node_modules' || true

Length of output: 22779


@seonghobae I reviewed exact head 232ea5d2bc806a7a9e1b3c6c690dd8c414ebd629.

I found no actionable defect in this successor delta.

frontend/src/api.ts now encodes every Forward Engineering resource identifier as one path segment:

  • getMigrationPlan()
  • createDryRun()
  • createApplyRun()
  • getMigrationRun()
  • cancelMigrationRun()

This prevents /, ?, and related characters in a plan or run identifier from changing the requested route or query string.

frontend/src/forwardApi.test.ts verifies both path traversal-shaped input and query-shaped input. The expected URLs contain the encoded single-segment values.

The explicit ./vite.config.ts import in frontend/vitest.config.ts is consistent with the Vite-loaded configuration boundary. The frontend tsconfig.json only includes src, so this import does not change application source compilation.

The successor does not add SQL input, dispatching, credentials, worker startup, or DDL authority.

git diff --check passed for the exact successor commit.

I treated predecessor findings and status checks as historical, as requested.


🧠 Learnings used
Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T19:37:25.016Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` must sanitize driver failures from transaction construction, transaction startup, query execution, and rollback. It must expose only fixed `LivePreflightContractError` diagnostics without a cause or context for ordinary driver failures, and must call rollback only after transaction startup succeeds.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T17:35:50.945Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` is an execution-neutral primitive for structured `table_is_empty`, `no_null_values`, and `castable_values` preconditions. It server-quotes identifiers, uses the canonical PostgreSQL type allow-list, enforces at most 1,000 queries, runs a read-only repeatable-read transaction with bounded timeouts, and returns only boolean evidence. It must not own target credentials, worker lifecycle, run transitions, sandboxing, DDL, or apply authority.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T22:26:01.081Z
Learning: In PR `#834`, PostgreSQL rejects role names that use its reserved `pg_` prefix. The PostgreSQL 14–18 CI workflow must use the ephemeral restricted preflight login `cwl_erd_preflight`, not `pg_erd_cloud_preflight`. `backend/tests/test_documentation_contract.py` enforces this by requiring `CREATE ROLE cwl_erd_preflight` and rejecting `CREATE ROLE pg_` in `.github/workflows/ci.yml`.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T17:44:18.090Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` contains `compare_live_preflight_snapshot(plan, snapshot)`. This execution-neutral function strictly adapts a caller-supplied PostgreSQL snapshot, calculates its canonical schema-model digest, and compares it with immutable plan field `base_digest`. It returns only `observed_base_digest` and `matches_plan_base`; worker-owned fresh target capture, credential and connection authority, attempt binding, run transitions, sandboxing, and apply remain outside this primitive.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T19:25:58.553Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` must bind the PostgreSQL statement timeout through the literal `SELECT pg_catalog.set_config('statement_timeout', $1, true)` query with a separate `"<milliseconds>ms"` parameter. The `true` argument keeps the GUC transaction-local and avoids dynamically formatted SQL at the asyncpg execution sink.

You are interacting with an AI system.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="7f00daa99b3a895df7ebb3c0390a97a53cd2ffcd"
prior="4c648f8c1d7784f7ccedbaf2dd02bb09172cd315"

show_head() {
  local file="$1" start="$2" end="$3"
  echo "-- ${file}:${start}-${end}"
  git show "${head}:${file}" |
    awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%6d  %s\n", NR, $0 }'
}

echo '== Exact identity and PR binding =='
git cat-file -e "${head}^{commit}"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "${head}"
printf 'checked_out='; git rev-parse HEAD
gh pr view 834 --json headRefOid,baseRefOid,url --jq '{headRefOid,baseRefOid,url}'
git merge-base --is-ancestor "${prior}" "${head}"
echo "prior_is_ancestor=$?"

echo '== Successor range and whitespace =='
git log --oneline "${prior}..${head}"
git diff --name-status "${prior}..${head}"
git diff --stat "${prior}..${head}"
git diff -U45 "${prior}..${head}"
git diff --check "${head}^" "${head}"
git diff --check "${prior}..${head}"

echo '== Changed source outlines =='
git diff --name-only "${prior}..${head}" -- '*.py' '*.{ts,tsx,js,jsx}' |
while IFS= read -r file; do
  [ -n "${file}" ] || continue
  echo "-- ${file}"
  git show "${head}:${file}" > "/tmp/review-$(basename "${file}")"
  case "${file}" in
    *.py) ast-grep outline "/tmp/review-$(basename "${file}")" --items all || true ;;
    *) ast-grep outline "/tmp/review-$(basename "${file}")" --lang tsx --items all || true ;;
  esac
done

echo '== Complete changed files =='
git diff --name-only "${prior}..${head}" |
while IFS= read -r file; do
  [ -n "${file}" ] || continue
  echo "-- ${file}"
  git show "${head}:${file}" | nl -ba | sed -n '1,1800p'
done

echo '== Forward-engineering boundary and unsafe transport references =='
git grep -n -I -C 8 -E \
  'migration-plans|migration-runs|dry-runs|apply-runs|If-Match|Idempotency-Key|plan_digest|passed_dry_run_uuid|destructive_acknowledged|sql|statement|query|dispatch|enqueue|worker|credential|password|dsn|connection_string|execute|create_task|apply' \
  "${head}" -- frontend backend/app backend/tests docs || true

echo '== Production call sites and route registrations =='
git grep -n -I -C 6 -E \
  '\b(getMigrationPlan|createDryRun|createApplyRun|getMigrationRun|cancelMigrationRun)\s*\(|include_router|MigrationRunDispatch\(|enqueue_migration_run_signal\(|run_migration_run_consumer_forever\(|execute_(bound_)?live_preflight\(|execute_isolated_dry_run\(' \
  "${head}" -- frontend/src backend/app || true

echo '== Exact-head checks =='
branch="$(gh pr view 834 --json headRefName --jq .headRefName)"
gh run list --branch "${branch}" --limit 100 \
  --json databaseId,headSha,status,conclusion,workflowName,createdAt,updatedAt,url \
  --jq ".[] | select(.headSha == \"${head}\") | {databaseId,workflowName,status,conclusion,createdAt,updatedAt,url}" || true
gh pr checks 834 --json name,state,workflow,link,startedAt,completedAt \
  --jq '.[] | {name,state,workflow,startedAt,completedAt,link}' || true

Length of output: 34966


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="7f00daa99b3a895df7ebb3c0390a97a53cd2ffcd"
prior="4c648f8c1d7784f7ccedbaf2dd02bb09172cd315"

echo '== Exact range whitespace =='
git diff --check "${head}^" "${head}"
git diff --check "${prior}..${head}"

echo '== Full browser transport and its type =='
git show "${head}:frontend/src/api.ts" | sed -n '275,370p'
git show "${head}:frontend/src/types.ts" |
  rg -n -C 12 'MigrationApplyIntent|MigrationRunAction|MigrationPlan|MigrationRun'

echo '== Complete forward transport regression suite =='
git show "${head}:frontend/src/forwardApi.test.ts" | sed -n '1,260p'

echo '== Path-parameter transport call coverage =='
git show "${head}:frontend/src/api.ts" |
  rg -n -C 7 \
  'encodeURIComponent\((planId|runId)\)|migration-plans/.+\$\{|migration-runs/.+\$\{'
git show "${head}:frontend/src/forwardApi.test.ts" |
  rg -n -C 12 \
  'encodes resource identifiers|createDryRun|createApplyRun|cancelMigrationRun'

echo '== Exact TRD apply-boundary contents =='
git show "${head}:docs/TRD.md" |
  rg -n -C 10 \
  'FE-TRD-010|Current persistence model|Current HTTP contract|apply-intent|future apply routes|0012_apply_intent_confirmation'

echo '== Documentation contract assertions =='
git show "${head}:backend/tests/test_documentation_contract.py" |
  sed -n '100,165p'

echo '== Production Forward transport use sites =='
git grep -n -I -C 5 -E \
  '\b(getMigrationPlan|createDryRun|createApplyRun|getMigrationRun|cancelMigrationRun)\s*\(' \
  "${head}" -- frontend/src ':!frontend/src/forwardApi.test.ts' || true

echo '== Exact-head checks =='
branch="$(gh pr view 834 --json headRefName --jq .headRefName)"
gh run list --branch "${branch}" --limit 100 \
  --json databaseId,headSha,status,conclusion,workflowName,createdAt,updatedAt,url \
  --jq ".[] | select(.headSha == \"${head}\") | {databaseId,workflowName,status,conclusion,createdAt,updatedAt,url}" || true
gh pr checks 834 --json name,state,workflow,link,startedAt,completedAt \
  --jq '.[] | {name,state,workflow,startedAt,completedAt,link}' || true

Length of output: 44760


@seonghobae I reviewed exact head 7f00daa99b3a895df7ebb3c0390a97a53cd2ffcd.

I found one actionable issue.

  1. P2 — Serialize the apply-intent request from an allowlist.
    frontend/src/api.ts, Line 313 still uses:

    body: JSON.stringify(intent),

    MigrationApplyIntent defines the required fields, but it does not remove extra runtime properties. A caller can pass an object with an additional sql property. createApplyRun() then sends that property to the server.

    Build the request body from only these fields:

    • plan_digest
    • passed_dry_run_uuid
    • target_connection_name
    • destructive_acknowledged

    Add a test that passes an object with an extra sql property and verifies that the serialized body omits it.

    The backend rejects undeclared request fields. Therefore, this issue does not create DDL authority. It still violates the documented browser transport boundary and relies on backend behavior to remove unsafe fields.

The successor changes are otherwise correct.

  • All Forward Engineering plan and run path identifiers now use encodeURIComponent().
  • The new test proves that plan and run identifiers remain one URL path segment.
  • Write failures report only the operation name and HTTP status. They do not echo request content.
  • docs/TRD.md correctly classifies non-dispatched apply-intent creation as implemented.
  • The documentation keeps queue consumption, recovery, credentials, worker execution, and apply execution as Planned.
  • git diff --check passed for the exact-head commit and the successor range.

Exact-head CI acceptance is not verified. The backend, frontend, PostgreSQL 14–18 plus Valkey, CodeQL, Semgrep, and security jobs were queued or pending during inspection.


🧠 Learnings used
Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T17:35:50.945Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` is an execution-neutral primitive for structured `table_is_empty`, `no_null_values`, and `castable_values` preconditions. It server-quotes identifiers, uses the canonical PostgreSQL type allow-list, enforces at most 1,000 queries, runs a read-only repeatable-read transaction with bounded timeouts, and returns only boolean evidence. It must not own target credentials, worker lifecycle, run transitions, sandboxing, DDL, or apply authority.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T19:37:25.016Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` must sanitize driver failures from transaction construction, transaction startup, query execution, and rollback. It must expose only fixed `LivePreflightContractError` diagnostics without a cause or context for ordinary driver failures, and must call rollback only after transaction startup succeeds.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T17:44:18.090Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` contains `compare_live_preflight_snapshot(plan, snapshot)`. This execution-neutral function strictly adapts a caller-supplied PostgreSQL snapshot, calculates its canonical schema-model digest, and compares it with immutable plan field `base_digest`. It returns only `observed_base_digest` and `matches_plan_base`; worker-owned fresh target capture, credential and connection authority, attempt binding, run transitions, sandboxing, and apply remain outside this primitive.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T22:26:01.081Z
Learning: In PR `#834`, PostgreSQL rejects role names that use its reserved `pg_` prefix. The PostgreSQL 14–18 CI workflow must use the ephemeral restricted preflight login `cwl_erd_preflight`, not `pg_erd_cloud_preflight`. `backend/tests/test_documentation_contract.py` enforces this by requiring `CREATE ROLE cwl_erd_preflight` and rejecting `CREATE ROLE pg_` in `.github/workflows/ci.yml`.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T07:13:38.918Z
Learning: In PR `#834`, `backend/app/jobs/migration_run_consumer.py` defines an execution-neutral migration-run consumer contract. It accepts an injected handler and a `migration_run_uuid` through `MigrationRunSignalClaim`; it acknowledges the exact lease only after handler success, releases that exact lease after sanitized handler failure, and raises a fixed lease-loss error when completion ownership is lost. `backend/app/main.py` does not start this consumer; application consumer wiring and worker execution remain planned.

You are interacting with an AI system.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please review exact head f860fb2d659a210fecea1c708a10ad7734ed35bf. Earlier review requests are stale.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="e8c4ddca255f6453357511581376df309afa99a0"
prior="232ea5d2bc806a7a9e1b3c6c690dd8c414ebd629"
successor="7f00daa99b3a895df7ebb3c0390a97a53cd2ffcd"
base="72afe6db712b145baaba084f64a1ff4fb36d9fd0"

echo '== Exact identity and PR binding =='
git cat-file -e "${head}^{commit}"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "${head}"
gh pr view 834 --json headRefOid,baseRefOid,url --jq '{headRefOid,baseRefOid,url}'
git merge-base --is-ancestor "${prior}" "${head}"
echo "prior_is_ancestor=$?"
git merge-base --is-ancestor "${head}" "${successor}"
echo "head_is_ancestor_of_7f00daa=$?"

echo '== Delta prior..head =='
git diff --name-status "${prior}..${head}"
git diff --stat "${prior}..${head}"
git diff -U40 "${prior}..${head}"
git diff --check "${head}^" "${head}"
git diff --check "${base}..${head}"

echo '== Delta head..successor (if head is ancestor) =='
git diff --name-status "${head}..${successor}" || true

Length of output: 4069


@seonghobae I reviewed exact head e8c4ddca255f6453357511581376df309afa99a0.

I treated the predecessor request at 232ea5d… as stale, as you noted.

The delta from 232ea5d2bc806a7a9e1b3c6c690dd8c414ebd629 to this head changes only frontend/src/forwardApi.test.ts. It adds one parameterized test. The test covers createDryRun(), createApplyRun(), and cancelMigrationRun(). It mocks a 409 write failure after the CSRF fetch. It asserts that each function rejects with ${name} failed: 409 and does not echo request data in the error.

I found no defect in this test-only delta.

I confirm that this head is an exact ancestor of 7f00daa99b3a895df7ebb3c0390a97a53cd2ffcd, which I already reviewed. The delta from this head to 7f00daa changes only backend/tests/test_documentation_contract.py and docs/TRD.md. frontend/src/api.ts is unchanged across this range.

This means the P2 finding from my 7f00daa review still applies at this head:

P2 — Serialize the apply-intent request from an allowlist.
frontend/src/api.ts, Line 313 still uses body: JSON.stringify(intent). Build the request body from only plan_digest, passed_dry_run_uuid, target_connection_name, and destructive_acknowledged. Add a test that passes an object with an extra sql property and confirms the serialized body omits it.

git diff --check passed for the exact commit and the full PR range.

Exact-head CI acceptance is not verified for this specific commit, since I did not re-query workflow state for this head; the most recent exact-head PostgreSQL 14–18, Valkey, and security check results reported for 7f00daa remain the latest evidence I have.


🧠 Learnings used
Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 834
File: docs/TEST_STRATEGY.md:0-0
Timestamp: 2026-08-09T14:44:31.377Z
Learning: For ContextualWisdomLab/pg-erd-cloud, organization-required pull-request workflows centrally operate the authoritative `osv-scan`, `dependency-review`, `trivy-fs`, OpenSSF Scorecard, and separate SAST Semgrep checks. Leaf repository workflows must require exact-head results from these checks but must not duplicate their implementation.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T17:35:50.945Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` is an execution-neutral primitive for structured `table_is_empty`, `no_null_values`, and `castable_values` preconditions. It server-quotes identifiers, uses the canonical PostgreSQL type allow-list, enforces at most 1,000 queries, runs a read-only repeatable-read transaction with bounded timeouts, and returns only boolean evidence. It must not own target credentials, worker lifecycle, run transitions, sandboxing, DDL, or apply authority.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T19:37:25.016Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` must sanitize driver failures from transaction construction, transaction startup, query execution, and rollback. It must expose only fixed `LivePreflightContractError` diagnostics without a cause or context for ordinary driver failures, and must call rollback only after transaction startup succeeds.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T22:26:01.081Z
Learning: In PR `#834`, PostgreSQL rejects role names that use its reserved `pg_` prefix. The PostgreSQL 14–18 CI workflow must use the ephemeral restricted preflight login `cwl_erd_preflight`, not `pg_erd_cloud_preflight`. `backend/tests/test_documentation_contract.py` enforces this by requiring `CREATE ROLE cwl_erd_preflight` and rejecting `CREATE ROLE pg_` in `.github/workflows/ci.yml`.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T07:13:38.918Z
Learning: In PR `#834`, `backend/app/jobs/migration_run_consumer.py` defines an execution-neutral migration-run consumer contract. It accepts an injected handler and a `migration_run_uuid` through `MigrationRunSignalClaim`; it acknowledges the exact lease only after handler success, releases that exact lease after sanitized handler failure, and raises a fixed lease-loss error when completion ownership is lost. `backend/app/main.py` does not start this consumer; application consumer wiring and worker execution remain planned.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T01:38:08.391Z
Learning: In PR `#834`, `POST /api/migration-plans/{migration_plan_uuid}/dry-runs` is an editor-authorized control-plane endpoint. It requires an exact plan digest and a bounded `Idempotency-Key`, persists or reuses a queued dry-run intent, and does not enqueue work, signal a worker, or execute SQL. Sandbox and worker execution remain planned.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T19:25:58.553Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` must bind the PostgreSQL statement timeout through the literal `SELECT pg_catalog.set_config('statement_timeout', $1, true)` query with a separate `"<milliseconds>ms"` parameter. The `true` argument keeps the GUC transaction-local and avoids dynamically formatted SQL at the asyncpg execution sink.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T05:05:31.406Z
Learning: In PR `#834`, `backend/app/jobs/valkey_queue.py` implements UUID-only migration signal lease primitives. The ready and processing sorted-set members are `migration_run_uuid`; an exact `lease_token` is stored separately and is required for acknowledgement or retry release. These primitives do not load plans or credentials, access a target, create a consumer lifecycle, or execute SQL.

You are interacting with an AI system.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="f860fb2d659a210fecea1c708a10ad7734ed35bf"
prior="7f00daa99b3a895df7ebb3c0390a97a53cd2ffcd"
base="72afe6db712b145baaba084f64a1ff4fb36d9fd0"

show_head() {
  local file="$1" start="$2" end="$3"
  echo "-- ${file}:${start}-${end}"
  git show "${head}:${file}" |
    awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%6d  %s\n", NR, $0 }'
}

echo '== Exact commit and PR binding =='
git cat-file -e "${head}^{commit}"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "${head}"
printf 'checked_out='; git rev-parse HEAD
gh pr view 834 --json headRefOid,baseRefOid,url --jq '{headRefOid,baseRefOid,url}'
git merge-base --is-ancestor "${prior}" "${head}"
echo "prior_is_ancestor=$?"

echo '== Successor range and whitespace =='
git log --oneline "${prior}..${head}"
git diff --name-status "${prior}..${head}"
git diff --stat "${prior}..${head}"
git diff -U45 "${prior}..${head}"
git diff --check "${head}^" "${head}"
git diff --check "${prior}..${head}"

echo '== Changed source outlines =='
git diff --name-only "${prior}..${head}" -- '*.py' '*.{ts,tsx,js,jsx}' |
while IFS= read -r file; do
  [ -n "${file}" ] || continue
  echo "-- ${file}"
  git show "${head}:${file}" > "/tmp/review-$(basename "${file}")"
  case "${file}" in
    *.py) ast-grep outline "/tmp/review-$(basename "${file}")" --items all || true ;;
    *) ast-grep outline "/tmp/review-$(basename "${file}")" --lang tsx --items all || true ;;
  esac
done

echo '== Changed file contents =='
git diff --name-only "${prior}..${head}" |
while IFS= read -r file; do
  [ -n "${file}" ] || continue
  echo "-- ${file}"
  git show "${head}:${file}" | nl -ba | sed -n '1,1800p'
done

echo '== Forward transport implementation and tests =='
for spec in \
  'frontend/src/api.ts:260:390' \
  'frontend/src/types.ts:1:700' \
  'frontend/src/forwardApi.test.ts:1:1000'
do
  file="${spec%%:*}"
  rest="${spec#*:}"
  start="${rest%%:*}"
  end="${rest##*:}"
  if git cat-file -e "${head}:${file}" 2>/dev/null; then
    show_head "${file}" "${start}" "${end}"
  fi
done

echo '== Input serialization, unsafe-field handling, and authority boundary =='
git grep -n -I -C 8 -E \
  'createApplyRun|JSON\.stringify|plan_digest|passed_dry_run_uuid|target_connection_name|destructive_acknowledged|sql|statement|query|dispatch|enqueue|worker|credential|password|dsn|execute|create_task' \
  "${head}" -- frontend/src backend/app backend/tests docs || true

echo '== Production transport call sites =='
git grep -n -I -C 5 -E \
  '\b(getMigrationPlan|createDryRun|createApplyRun|getMigrationRun|cancelMigrationRun)\s*\(' \
  "${head}" -- frontend/src ':!frontend/src/forwardApi.test.ts' || true

echo '== Exact-head checks =='
branch="$(gh pr view 834 --json headRefName --jq .headRefName)"
gh run list --branch "${branch}" --limit 100 \
  --json databaseId,headSha,status,conclusion,workflowName,createdAt,updatedAt,url \
  --jq ".[] | select(.headSha == \"${head}\") | {databaseId,workflowName,status,conclusion,createdAt,updatedAt,url}" || true
gh pr checks 834 --json name,state,workflow,link,startedAt,completedAt \
  --jq '.[] | {name,state,workflow,startedAt,completedAt,link}' || true

Length of output: 12604


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="f860fb2d659a210fecea1c708a10ad7734ed35bf"
prior="7f00daa99b3a895df7ebb3c0390a97a53cd2ffcd"

echo '== Exact successor diff and whitespace =='
git diff -U35 "${prior}..${head}" -- \
  frontend/src/types.ts \
  frontend/src/forwardApi.test.ts
git diff --check "${head}^" "${head}"
git diff --check "${prior}..${head}"

echo '== Complete changed frontend files =='
for file in frontend/src/types.ts frontend/src/forwardApi.test.ts; do
  echo "-- ${file}"
  git show "${head}:${file}" | sed -n '1,900p'
done

echo '== Frontend type definitions and transport use =='
ast-grep outline frontend/src/types.ts --lang ts --items all
git show "${head}:frontend/src/types.ts" |
  rg -n -C 16 \
  'MigrationPlanObjectRef|MigrationPlanRisk|MigrationPlanStatement|MigrationPlanBlocker|MigrationPlan =|MigrationApplyIntent'
git show "${head}:frontend/src/api.ts" |
  rg -n -C 12 \
  'getMigrationPlan|createDryRun|createApplyRun|getMigrationRun|cancelMigrationRun|JSON\.stringify'

echo '== Backend response schema contract =='
ast-grep outline backend/app/schemas.py --items all
git show "${head}:backend/app/schemas.py" |
  rg -n -C 18 \
  'class MigrationPlanObjectRef|class MigrationPlanRisk|class MigrationPlanStatement|class MigrationPlanBlocker|class MigrationPlanOut'

echo '== Type-level and runtime regression assertions =='
git show "${head}:frontend/src/forwardApi.test.ts" |
  rg -n -C 18 \
  'expectTypeOf|MigrationPlan|statements|proposed_statements|blockers|creates exact dry-run|apply intents|sql'

echo '== All use sites of typed migration-plan fields =='
git grep -n -I -C 5 -E \
  '\.(statements|proposed_statements|blockers|object_ref|dependency_refs|required_privileges|preconditions)\b' \
  "${head}" -- frontend/src ':!frontend/src/forwardApi.test.ts' || true

echo '== Exact-head checks =='
branch="$(gh pr view 834 --json headRefName --jq .headRefName)"
gh run list --branch "${branch}" --limit 100 \
  --json databaseId,headSha,status,conclusion,workflowName,createdAt,updatedAt,url \
  --jq ".[] | select(.headSha == \"${head}\") | {databaseId,workflowName,status,conclusion,createdAt,updatedAt,url}" || true
gh pr checks 834 --json name,state,workflow,link,startedAt,completedAt \
  --jq '.[] | {name,state,workflow,startedAt,completedAt,link}' || true

Length of output: 56872


@seonghobae I reviewed exact head f860fb2d659a210fecea1c708a10ad7734ed35bf.

I found one actionable issue.

  1. P2 — Serialize the apply-intent request from an allowlist.
    frontend/src/api.ts, Line 313 still uses:

    body: JSON.stringify(intent),

    MigrationApplyIntent defines required TypeScript fields. It does not remove extra runtime properties. A caller can pass an object with an additional sql property. createApplyRun() then sends that property to the server.

    Construct the request body from only these fields:

    • plan_digest
    • passed_dry_run_uuid
    • target_connection_name
    • destructive_acknowledged

    Add a regression test that passes an object with an extra sql property and verifies that the serialized body omits it.

    The backend rejects undeclared request fields. This issue does not create DDL authority. It does violate the stated browser transport boundary and depends on continued backend rejection.

The typed plan contract is otherwise correct.

  • MigrationPlanObjectRef, MigrationPlanRisk, MigrationPlanStatement, and MigrationPlanBlocker match the required fields in backend/app/schemas.py.
  • The new type-level tests protect the structured server-authoritative statement and blocker contract.
  • The change does not add UI, dispatch, credentials, worker startup, or DDL execution.

git diff --check passed for the exact commit and the successor range.

Exact-head CI acceptance is not complete. The backend, frontend, PostgreSQL 14–18, Valkey, CodeQL, Semgrep, and central security checks were queued during inspection.


🧠 Learnings used
Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 834
File: docs/TEST_STRATEGY.md:0-0
Timestamp: 2026-08-09T14:44:31.377Z
Learning: For ContextualWisdomLab/pg-erd-cloud, organization-required pull-request workflows centrally operate the authoritative `osv-scan`, `dependency-review`, `trivy-fs`, OpenSSF Scorecard, and separate SAST Semgrep checks. Leaf repository workflows must require exact-head results from these checks but must not duplicate their implementation.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T17:35:50.945Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` is an execution-neutral primitive for structured `table_is_empty`, `no_null_values`, and `castable_values` preconditions. It server-quotes identifiers, uses the canonical PostgreSQL type allow-list, enforces at most 1,000 queries, runs a read-only repeatable-read transaction with bounded timeouts, and returns only boolean evidence. It must not own target credentials, worker lifecycle, run transitions, sandboxing, DDL, or apply authority.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T17:44:18.090Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` contains `compare_live_preflight_snapshot(plan, snapshot)`. This execution-neutral function strictly adapts a caller-supplied PostgreSQL snapshot, calculates its canonical schema-model digest, and compares it with immutable plan field `base_digest`. It returns only `observed_base_digest` and `matches_plan_base`; worker-owned fresh target capture, credential and connection authority, attempt binding, run transitions, sandboxing, and apply remain outside this primitive.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T19:37:25.016Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` must sanitize driver failures from transaction construction, transaction startup, query execution, and rollback. It must expose only fixed `LivePreflightContractError` diagnostics without a cause or context for ordinary driver failures, and must call rollback only after transaction startup succeeds.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T07:13:38.918Z
Learning: In PR `#834`, `backend/app/jobs/migration_run_consumer.py` defines an execution-neutral migration-run consumer contract. It accepts an injected handler and a `migration_run_uuid` through `MigrationRunSignalClaim`; it acknowledges the exact lease only after handler success, releases that exact lease after sanitized handler failure, and raises a fixed lease-loss error when completion ownership is lost. `backend/app/main.py` does not start this consumer; application consumer wiring and worker execution remain planned.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T05:05:31.406Z
Learning: In PR `#834`, `backend/app/jobs/valkey_queue.py` implements UUID-only migration signal lease primitives. The ready and processing sorted-set members are `migration_run_uuid`; an exact `lease_token` is stored separately and is required for acknowledgement or retry release. These primitives do not load plans or credentials, access a target, create a consumer lifecycle, or execute SQL.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T01:38:08.391Z
Learning: In PR `#834`, `POST /api/migration-plans/{migration_plan_uuid}/dry-runs` is an editor-authorized control-plane endpoint. It requires an exact plan digest and a bounded `Idempotency-Key`, persists or reuses a queued dry-run intent, and does not enqueue work, signal a worker, or execute SQL. Sandbox and worker execution remain planned.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T22:26:01.081Z
Learning: In PR `#834`, PostgreSQL rejects role names that use its reserved `pg_` prefix. The PostgreSQL 14–18 CI workflow must use the ephemeral restricted preflight login `cwl_erd_preflight`, not `pg_erd_cloud_preflight`. `backend/tests/test_documentation_contract.py` enforces this by requiring `CREATE ROLE cwl_erd_preflight` and rejecting `CREATE ROLE pg_` in `.github/workflows/ci.yml`.

Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T19:25:58.553Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` must bind the PostgreSQL statement timeout through the literal `SELECT pg_catalog.set_config('statement_timeout', $1, true)` query with a separate `"<milliseconds>ms"` parameter. The `true` argument keeps the GUC transaction-local and avoids dynamically formatted SQL at the asyncpg execution sink.

You are interacting with an AI system.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please review exact head 52b96c4a5d60d7c105b76053ebf50f824cf5168c. The P2 allowlist finding on f860fb2… is addressed test-first; earlier requests are stale.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="52b96c4a5d60d7c105b76053ebf50f824cf5168c"
prior="f860fb2d659a210fecea1c708a10ad7734ed35bf"
base="72afe6db712b145baaba084f64a1ff4fb36d9fd0"

show_head() {
  local file="$1" start="$2" end="$3"
  echo "-- ${file}:${start}-${end}"
  git show "${head}:${file}" |
    awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%6d  %s\n", NR, $0 }'
}

echo '== Exact commit identity and PR binding =='
git cat-file -e "${head}^{commit}"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "${head}"
printf 'checked_out='; git rev-parse HEAD
gh pr view 834 --json headRefOid,baseRefOid,url --jq '{headRefOid,baseRefOid,url}'
git merge-base --is-ancestor "${prior}" "${head}"
echo "prior_is_ancestor=$?"

echo '== Exact successor delta and whitespace =='
git log --oneline "${prior}..${head}"
git diff --name-status "${prior}..${head}"
git diff --stat "${prior}..${head}"
git diff -U45 "${prior}..${head}" -- \
  frontend/src/api.ts \
  frontend/src/types.ts \
  frontend/src/forwardApi.test.ts \
  frontend \
  backend/app \
  backend/tests \
  docs
git diff --check "${head}^" "${head}"
git diff --check "${prior}..${head}"

echo '== Changed source structure =='
git diff --name-only "${prior}..${head}" -- '*.ts' '*.tsx' '*.js' '*.jsx' '*.py' |
while IFS= read -r file; do
  [ -n "${file}" ] || continue
  echo "-- ${file}"
  git show "${head}:${file}" > "/tmp/review-$(basename "${file}")"
  case "${file}" in
    *.py) ast-grep outline "/tmp/review-$(basename "${file}")" --items all || true ;;
    *) ast-grep outline "/tmp/review-$(basename "${file}")" --lang tsx --items all || true ;;
  esac
done

echo '== Allowlisted transport implementation and regression suite =='
show_head frontend/src/api.ts 260 350
show_head frontend/src/forwardApi.test.ts 1 650

echo '== Transport call sites outside tests =='
git grep -n -I -C 5 -E \
  '\b(createApplyRun|getMigrationPlan|createDryRun|getMigrationRun|cancelMigrationRun)\s*\(' \
  "${head}" -- frontend/src ':!frontend/src/forwardApi.test.ts' || true

echo '== Unsafe fields and execution-authority references in successor range =='
git diff -U12 "${prior}..${head}" -- frontend backend/app backend/tests docs |
  rg -n -i -C 4 \
  'sql|statement|query|dispatch|enqueue|worker|credential|password|dsn|connection_string|execute|create_task|apply' || true

echo '== Backend apply-intent boundary context =='
git show "${head}:backend/app/api/migration_plans.py" |
  rg -n -C 12 'apply-runs|create_apply_run|MigrationRunDispatch|dispatch|enqueue|credential|execute' || true

echo '== Exact-head workflow state =='
branch="$(gh pr view 834 --json headRefName --jq .headRefName)"
gh run list --branch "${branch}" --limit 100 \
  --json databaseId,headSha,status,conclusion,workflowName,createdAt,updatedAt,url \
  --jq ".[] | select(.headSha == \"${head}\") | {workflowName,status,conclusion,createdAt,updatedAt,url}" || true
gh pr checks 834 --json name,state,workflow,link,startedAt,completedAt \
  --jq '.[] | {name,state,workflow,startedAt,completedAt,link}' || true

Length of output: 45220


@seonghobae I reviewed exact head 52b96c4a5d60d7c105b76053ebf50f824cf5168c.

I found no actionable source-level defect.

This change resolves the predecessor P2.

  • createApplyRun() now builds requestBody from only:
    • plan_digest
    • passed_dry_run_uuid
    • target_connection_name
    • destructive_acknowledged
  • The request now serializes requestBody, not the caller-supplied intent.
  • frontend/src/forwardApi.test.ts passes an intent object with sql: 'DROP SCHEMA public CASCADE;'.
  • The test verifies that the serialized apply-intent body does not contain sql.

The successor changes only frontend/src/api.ts and frontend/src/forwardApi.test.ts. It does not add dispatch, credentials, worker startup, or DDL execution authority.

git diff --check passed for the exact commit and the successor range.

Exact-head CI acceptance is not complete. The frontend, backend, PostgreSQL 14–18, Valkey, CodeQL, Semgrep, and security jobs were queued during inspection.


🧠 Learnings used
Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 0
File: :0-0
Timestamp: 2026-08-11T17:35:50.945Z
Learning: In PR `#834`, `backend/app/forward/live_preflight.py` is an execution-neutral primitive for structured `table_is_empty`, `no_null_values`, and `castable_values` preconditions. It server-quotes identifiers, uses the canonical PostgreSQL type allow-list, enforces at most 1,000 queries, runs a read-only repeatable-read transaction with bounded timeouts, and returns only boolean evidence. It must not own target credentials, worker lifecycle, run transitions, sandboxing, DDL, or apply authority.

You are interacting with an AI system.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please review exact head 14660e1953bf00591bdf21935f0144cc8d11aba2. This adds a read-only, actionless plan review panel and maturity-contract updates; predecessor reviews and checks are stale.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="14660e1953bf00591bdf21935f0144cc8d11aba2"
prior="52b96c4a5d60d7c105b76053ebf50f824cf5168c"
base="72afe6db712b145baaba084f64a1ff4fb36d9fd0"

show_head() {
  local file="$1" start="$2" end="$3"
  echo "-- ${file}:${start}-${end}"
  git show "${head}:${file}" |
    awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%6d  %s\n", NR, $0 }'
}

echo '== Exact identity and pull request binding =='
git cat-file -e "${head}^{commit}"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "${head}"
printf 'checked_out='; git rev-parse HEAD
gh pr view 834 --json headRefOid,baseRefOid,url --jq '{headRefOid,baseRefOid,url}'
git merge-base --is-ancestor "${prior}" "${head}"
echo "prior_is_ancestor=$?"

echo '== Successor range and whitespace =='
git log --oneline "${prior}..${head}"
git diff --name-status "${prior}..${head}"
git diff --stat "${prior}..${head}"
git diff -U50 "${prior}..${head}"
git diff --check "${head}^" "${head}"
git diff --check "${base}..${head}"

echo '== Changed source structure =='
git diff --name-only "${prior}..${head}" -- '*.py' '*.ts' '*.tsx' '*.js' '*.jsx' |
while IFS= read -r file; do
  [ -n "${file}" ] || continue
  echo "-- ${file}"
  git show "${head}:${file}" > "/tmp/review-$(basename "${file}")"
  case "${file}" in
    *.py) ast-grep outline "/tmp/review-$(basename "${file}")" --items all || true ;;
    *) ast-grep outline "/tmp/review-$(basename "${file}")" --lang tsx --items all || true ;;
  esac
done

echo '== Focused frontend and contract files =='
for file in \
  frontend/src \
  backend/tests/test_documentation_contract.py \
  docs/TRD.md \
  docs/contracts/forward-engineering-v1.md \
  ARCHITECTURE.md \
  README.md \
  CHANGELOG.md
do
  if [ -f "$file" ]; then
    echo "-- local outline/content target: $file"
  fi
done

git diff --name-only "${prior}..${head}" |
while IFS= read -r file; do
  [ -n "${file}" ] || continue
  echo "-- content: ${file}"
  git show "${head}:${file}" | nl -ba | sed -n '1,1800p'
done

echo '== Plan-review reads, writes, rendering, and authority references =='
git grep -n -I -C 8 -E \
  'getMigrationPlan|createDryRun|createApplyRun|cancelMigrationRun|getMigrationRun|migration-plans|migration-runs|dry-runs|apply-runs|fetch\(|onClick|button|form|navigate|dangerouslySetInnerHTML|innerHTML|sql|statement|query|dispatch|enqueue|worker|credential|password|dsn|connection_string|execute|create_task|apply' \
  "${head}" -- frontend/src backend/app backend/tests docs ARCHITECTURE.md README.md CHANGELOG.md || true

echo '== Production call sites of Forward transport functions =='
git grep -n -I -C 5 -E \
  '\b(getMigrationPlan|createDryRun|createApplyRun|getMigrationRun|cancelMigrationRun)\s*\(' \
  "${head}" -- frontend/src ':!frontend/src/forwardApi.test.ts' || true

echo '== Maturity status and documentation contract references =='
git grep -n -I -C 7 -E \
  'plan review|Plan Review|read-only|actionless|Current|Implemented|Planned|dispatch|credential|worker|DDL|apply' \
  "${head}" -- backend/tests/test_documentation_contract.py docs ARCHITECTURE.md README.md CHANGELOG.md SECURITY.md || true

echo '== Exact-head workflow state, informational only =='
branch="$(gh pr view 834 --json headRefName --jq .headRefName)"
gh run list --branch "${branch}" --limit 100 \
  --json databaseId,headSha,status,conclusion,workflowName,createdAt,updatedAt,url \
  --jq ".[] | select(.headSha == \"${head}\") | {workflowName,status,conclusion,createdAt,updatedAt,url}" || true
gh pr checks 834 --json name,state,workflow,link,startedAt,completedAt \
  --jq '.[] | {name,state,workflow,startedAt,completedAt,link}' || true

Length of output: 74654


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head="14660e1953bf00591bdf21935f0144cc8d11aba2"
prior="52b96c4a5d60d7c105b76053ebf50f824cf5168c"

show_head() {
  local file="$1" start="$2" end="$3"
  echo "-- ${file}:${start}-${end}"
  git show "${head}:${file}" |
    awk -v start="$start" -v end="$end" \
      'NR >= start && NR <= end { printf "%6d  %s\n", NR, $0 }'
}

echo '== Exact focused diff =='
git diff -U35 "${prior}..${head}" -- \
  frontend/src/components/forward/PlanReviewPanel.tsx \
  frontend/src/components/forward/PlanReviewPanel.test.tsx \
  frontend/src/components/forward/index.ts \
  frontend/src/styles.css \
  backend/tests/test_documentation_contract.py \
  ARCHITECTURE.md CHANGELOG.md docs/PRD.md docs/TRD.md docs/TEST_STRATEGY.md
git diff --check "${head}^" "${head}"
git diff --check "${prior}..${head}"

echo '== Panel and test =='
show_head frontend/src/components/forward/PlanReviewPanel.tsx 1 220
show_head frontend/src/components/forward/PlanReviewPanel.test.tsx 1 260
show_head frontend/src/components/forward/index.ts 1 60

echo '== Test discovery and frontend conventions =='
fd -a -i 'vitest.config.*' frontend -x sh -c \
  'echo "-- $1"; sed -n "1,260p" "$1"' sh {}
fd -a -i 'package.json' frontend -x sh -c \
  'echo "-- $1"; sed -n "1,220p" "$1"' sh {}
fd -a -i 'tsconfig*.json' frontend -x sh -c \
  'echo "-- $1"; sed -n "1,220p" "$1"' sh {}

echo '== Production imports and all forward-panel references =='
git grep -n -I -C 5 -E \
  'PlanReviewPanel|components/forward|from .*/forward' \
  "${head}" -- frontend/src ':!frontend/src/components/forward/PlanReviewPanel.test.tsx' || true

echo '== Action and unsafe-rendering searches within the component subtree =='
git grep -n -I -C 4 -E \
  'onClick|<button|<form|createDryRun|createApplyRun|cancelMigrationRun|fetch\(|dangerouslySetInnerHTML|innerHTML|href=|window\.|document\.' \
  "${head}" -- frontend/src/components/forward || true

echo '== Backend MigrationPlan public shape used by the panel =='
git show "${head}:backend/app/schemas.py" |
  rg -n -C 18 \
  'class MigrationPlanObjectRef|class MigrationPlanRisk|class MigrationPlanStatement|class MigrationPlanBlocker|class MigrationPlanOut'

echo '== Maturity contract assertions and changed document context =='
show_head backend/tests/test_documentation_contract.py 135 180
show_head docs/PRD.md 10 90
show_head docs/TRD.md 142 196
show_head docs/TEST_STRATEGY.md 270 325
show_head ARCHITECTURE.md 16 48

echo '== Exact-head workflow state =='
branch="$(gh pr view 834 --json headRefName --jq .headRefName)"
gh run list --branch "${branch}" --limit 100 \
  --json databaseId,headSha,status,conclusion,workflowName,createdAt,updatedAt,url \
  --jq ".[] | select(.headSha == \"${head}\") | {workflowName,status,conclusion,createdAt,updatedAt,url}" || true
gh pr checks 834 --json name,state,workflow,link,startedAt,completedAt \
  --jq '.[] | {name,state,workflow,startedAt,completedAt,link}' || true

Length of output: 112046


@seonghobae I reviewed exact head 14660e1953bf00591bdf21935f0144cc8d11aba2.

I found no actionable source-level defect.

frontend/src/components/forward/PlanReviewPanel.tsx is an actionless presentation component.

  • It accepts a typed MigrationPlan.
  • It has no transport call, action callback, button, form, navigation, credential access, dispatch, worker control, or DDL authority.
  • It renders immutable provenance, risk summary, blockers, executable statements, and review-only proposals.
  • React renders SQL and blocker content as text. The hostile-markup regression confirms that SQL content does not create an HTML element.

frontend/src/components/forward/PlanReviewPanel.test.tsx covers:

  • immutable plan provenance and digest display;
  • structured statement risk, dependency, privilege, and precondition data;
  • blocker alert content;
  • review-only proposed SQL;
  • absence of action controls.

The new test file is within the default Vitest discovery scope.

The maturity updates are consistent.

  • ARCHITECTURE.md, docs/PRD.md, docs/TRD.md, and docs/TEST_STRATEGY.md classify the panel as Partially implemented.
  • The documents retain the Forward workflow, browser E2E, full accessibility evidence, dispatch, credentials, worker execution, and DDL apply as Planned.
  • The documentation contract verifies both the panel boundary and the maturity wording.

git diff --check passed for the exact commit and successor range.

I used the existing PR workflow guidance: the centrally operated security workflows are authoritative exact-head evidence. At inspection time, backend, frontend, PostgreSQL 14–18 plus Valkey, CodeQL, Semgrep, and security checks were queued. Exact-head CI acceptance is not complete.


🧠 Learnings used
Learnt from: seonghobae
Repo: ContextualWisdomLab/pg-erd-cloud PR: 834
File: docs/TEST_STRATEGY.md:0-0
Timestamp: 2026-08-09T14:44:31.377Z
Learning: For ContextualWisdomLab/pg-erd-cloud, organization-required pull-request workflows centrally operate the authoritative `osv-scan`, `dependency-review`, `trivy-fs`, OpenSSF Scorecard, and separate SAST Semgrep checks. Leaf repository workflows must require exact-head results from these checks but must not duplicate their implementation.

You are interacting with an AI system.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants