Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions .github/workflows/app-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ name: Application CI

on:
pull_request:
branches:
- develop
- master
- "release/**"
push:
branches:
- develop
Expand All @@ -25,9 +21,24 @@ jobs:
strategy:
matrix:
python-version: ["3.14"]
services:
postgres:
image: pgvector/pgvector:pg16@sha256:ccc6e83d6e35e931dc7c5def2022729d5a6c370318d099181995567ff1fb4d6b
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U test -d test_db"
--health-interval 5s
--health-timeout 5s
--health-retries 12
env:
PYTHONWARNINGS: error
DISABLE_BACKGROUND_WORKERS: "1"
DATABASE_URL: postgresql+asyncpg://test:test@localhost:5432/test_db
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
Expand Down Expand Up @@ -66,6 +77,23 @@ jobs:
cd backend
python -m ruff check .

- name: Generate ephemeral CI runtime secret
run: |
python - <<'PY'
import os
import secrets

value = "Ci9!" + secrets.token_urlsafe(48)
print(f"::add-mask::{value}")
with open(os.environ["GITHUB_ENV"], "a", encoding="utf-8") as env_file:
env_file.write(f"AUTH_SESSION_HMAC_SECRET={value}\n")
PY

- name: Run database migrations
run: |
cd backend
python scripts/migrate_db.py

- name: Run backend tests
run: |
set -o pipefail
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/bandit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
branches: [ develop, master ]
pull_request:
branches: [ develop, master ]
workflow_dispatch:

permissions:
Expand Down
4 changes: 0 additions & 4 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ name: Dependency Review

on:
pull_request:
branches:
- develop
- master
- "release/**"
workflow_dispatch:

permissions:
Expand Down
4 changes: 0 additions & 4 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ on:
tags:
- "v*"
pull_request:
branches:
- develop
- master
- "release/**"

permissions:
contents: read
Expand Down
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@ in this repo.

## PR automation and review defaults

- Stacked-PR trigger tests must parse the YAML event configuration, not search
source text for `**`: a comment can satisfy that search, and a later
`!feature/**` pattern can exclude the very stack being validated. Preserve the
Actions `on` key when choosing a YAML loader, inspect ordered branch patterns,
and retain rejection tests for both cases. The installed hash-locked PyYAML
dependency is sufficient; do not add a second parser for this contract.

- Follow `docs/development/merge-gate-policy.md` for PR gate interpretation.
- PR Governance must stay metadata-only: no PR-head checkout, no admin merge, no
review dismissal, and no security-check suppression.
Expand Down
8 changes: 2 additions & 6 deletions backend/tests/test_release_governance.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,6 @@ def test_app_ci_runs_backend_and_frontend_checks_without_duplicate_release_pushe
workflow = read_repo_text(".github/workflows/app-ci.yml")

assert "pull_request:" in workflow
assert "release/**" in workflow
assert "python -m pytest" in workflow
assert "PYTHONWARNINGS: error" in workflow
assert 'DISABLE_BACKGROUND_WORKERS: "1"' in workflow
Expand All @@ -666,6 +665,7 @@ def test_app_ci_runs_backend_and_frontend_checks_without_duplicate_release_pushe
assert "uses: actions/setup-node@v" not in workflow

push_block = workflow.split("push:", 1)[1].split("pull_request:", 1)[0]
assert "develop" in push_block
assert "master" in push_block
assert "release/**" not in push_block

Expand Down Expand Up @@ -705,12 +705,8 @@ def test_docker_publish_validates_pr_images_and_publishes_semver_images_only_on_
== 2
)
push_block = workflow.split("push:", 1)[1].split("pull_request:", 1)[0]
pull_request_block = workflow.split("pull_request:", 1)[1].split("permissions:", 1)[
0
]
assert "tags:" in push_block
assert "branches:" not in push_block
assert "develop" in pull_request_block
assert "ai_email_client-backend" in workflow
assert "ai_email_client-frontend" in workflow
assert workflow.count("image: naruon") == 2
Expand Down Expand Up @@ -1202,4 +1198,4 @@ def test_agents_records_ghcr_visibility_publication_runbook() -> None:
assert "Package settings" in agents
assert "Danger Zone" in agents
assert "Change visibility" in normalized_agents
assert "anonymous pull/token access" in agents
assert "anonymous pull/token access" in agents
51 changes: 51 additions & 0 deletions backend/tests/test_stacked_pr_workflow_triggers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Guard repo-local PR validation on dependent stacked pull requests."""

from __future__ import annotations

from pathlib import Path

import pytest
import yaml


REPO_ROOT = Path(__file__).resolve().parents[2]
PR_VALIDATION_WORKFLOWS = (
".github/workflows/app-ci.yml",
".github/workflows/bandit.yml",
".github/workflows/dependency-review.yml",
".github/workflows/docker-publish.yml",
)


@pytest.mark.parametrize("workflow_path", PR_VALIDATION_WORKFLOWS)
def test_repo_local_pr_validation_accepts_every_base_branch(workflow_path: str) -> None:
"""Require an unfiltered pull_request trigger for every stacked PR base."""
workflow_text = (REPO_ROOT / workflow_path).read_text(encoding="utf-8")
# BaseLoader preserves the Actions `on` key instead of YAML 1.1 boolean coercion.
workflow_events = yaml.load(workflow_text, Loader=yaml.BaseLoader)["on"]
assert "pull_request" in workflow_events
pull_request_config = workflow_events["pull_request"] or {}
assert "branches" not in pull_request_config
assert "branches-ignore" not in pull_request_config


@pytest.mark.parametrize(
"branch_filter",
[
"branches: ['**']",
"branches: ['**', '!feature/**']",
"branches: [develop] # '**' is only a comment",
"branches-ignore: [archive/**]",
],
)
def test_stacked_trigger_guard_rejects_any_base_filter(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, branch_filter: str
) -> None:
"""Do not encode all-base verification through mutable branch patterns."""
workflow_path = tmp_path / "workflow.yml"
workflow_path.write_text(
f"on:\n pull_request:\n {branch_filter}\n", encoding="utf-8"
)
monkeypatch.setitem(globals(), "REPO_ROOT", tmp_path)
with pytest.raises(AssertionError):
test_repo_local_pr_validation_accepts_every_base_branch("workflow.yml")
83 changes: 83 additions & 0 deletions tests/test_postgres_ci_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Repository contract for PostgreSQL-backed backend acceptance."""

from pathlib import Path

import yaml


REPO_ROOT = Path(__file__).resolve().parents[1]
Comment thread
seonghobae marked this conversation as resolved.
Outdated
APP_CI = REPO_ROOT / ".github" / "workflows" / "app-ci.yml"
PGVECTOR_CI_IMAGE = (
"pgvector/pgvector:pg16@"
"sha256:ccc6e83d6e35e931dc7c5def2022729d5a6c370318d099181995567ff1fb4d6b"
)


def _workflow() -> dict[str, object]:
"""Load the workflow without YAML 1.1 coercing the `on` key to a boolean."""
return yaml.load(APP_CI.read_text(encoding="utf-8"), Loader=yaml.BaseLoader)


def test_backend_ci_provisions_migrated_pgvector_database() -> None:
"""Real PostgreSQL tests must run against a ready, migrated CI database."""
workflow = _workflow()
jobs = workflow["jobs"]
assert isinstance(jobs, dict)
backend = jobs["backend"]
assert isinstance(backend, dict)

services = backend.get("services")
assert isinstance(services, dict), "backend CI must provision PostgreSQL"
postgres = services.get("postgres")
assert isinstance(postgres, dict), "backend CI must declare a postgres service"
assert postgres.get("image") == PGVECTOR_CI_IMAGE
assert postgres.get("env") == {
"POSTGRES_USER": "test",
"POSTGRES_PASSWORD": "test",
"POSTGRES_DB": "test_db",
}
options = postgres.get("options")
assert isinstance(options, str)
assert "pg_isready -U test -d test_db" in options

environment = backend.get("env")
assert isinstance(environment, dict)
assert environment.get("DATABASE_URL") == (
"postgresql+asyncpg://test:test@localhost:5432/test_db"
)
assert "AUTH_SESSION_HMAC_SECRET" not in environment, (
"CI runtime auth material must be generated per job, not committed as a fixture"
)

steps = backend.get("steps")
assert isinstance(steps, list)
named_steps = {
step.get("name"): step
for step in steps
if isinstance(step, dict) and isinstance(step.get("name"), str)
}
runtime_secret = named_steps.get("Generate ephemeral CI runtime secret")
assert isinstance(runtime_secret, dict), (
"backend CI must generate auth material before importing runtime settings"
)
runtime_secret_script = str(runtime_secret.get("run", ""))
assert "secrets.token_urlsafe(48)" in runtime_secret_script
assert "AUTH_SESSION_HMAC_SECRET" in runtime_secret_script
assert "GITHUB_ENV" in runtime_secret_script
assert 'print(f"::add-mask::{value}")' in runtime_secret_script, (
"generated runtime auth material must be masked before later steps expose env"
)
assert runtime_secret_script.index("::add-mask::") < runtime_secret_script.index(
"GITHUB_ENV"
)

migration = named_steps.get("Run database migrations")
assert isinstance(migration, dict), "backend CI must migrate before pytest"
assert "python scripts/migrate_db.py" in str(migration.get("run", ""))

step_names = [
step.get("name") for step in steps if isinstance(step, dict) and step.get("name")
]
assert step_names.index("Generate ephemeral CI runtime secret") < step_names.index(
"Run database migrations"
) < step_names.index("Run backend tests")
Loading