Skip to content

ARCH-04E1A: bind post-submit routing source evidence #2445

ARCH-04E1A: bind post-submit routing source evidence

ARCH-04E1A: bind post-submit routing source evidence #2445

Workflow file for this run

name: Backend
on:
pull_request:
push:
branches:
- main
concurrency:
group: backend-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: read
env:
MINIO_IMAGE: workstream-minio:source
jobs:
minio-image:
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
artifact: ${{ steps.identity.outputs.artifact }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false
- name: Bind source image to this workflow attempt
id: identity
shell: bash
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "${GITHUB_SHA}"
echo "artifact=minio-source-${GITHUB_SHA}-${GITHUB_RUN_ATTEMPT}" >> "${GITHUB_OUTPUT}"
- name: Cache exact source image
id: cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: ${{ runner.temp }}/minio-image/minio.tar
key: minio-source-v1-${{ github.sha }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('docker/minio/**') }}
- name: Build source-pinned MinIO once
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
run: |
set -euo pipefail
mkdir -p "${RUNNER_TEMP}/minio-image"
docker build --tag "${MINIO_IMAGE}" docker/minio
docker save --output "${RUNNER_TEMP}/minio-image/minio.tar" "${MINIO_IMAGE}"
- name: Verify the cached or freshly built provider
shell: bash
run: |
set -euo pipefail
docker load --input "${RUNNER_TEMP}/minio-image/minio.tar"
docker run --rm "${MINIO_IMAGE}" --version
docker run --detach --rm --name minio-build-probe \
--publish 127.0.0.1:9000:9000 \
--env MINIO_ROOT_USER=workstream-minio \
--env MINIO_ROOT_PASSWORD=workstream-minio-secret-key \
"${MINIO_IMAGE}" server /data --address :9000
trap 'docker logs minio-build-probe; docker stop minio-build-probe' EXIT
for attempt in $(seq 1 60); do
if curl --fail --silent http://127.0.0.1:9000/minio/health/live >/dev/null; then
cd "${RUNNER_TEMP}/minio-image"
sha256sum minio.tar > minio.tar.sha256
exit 0
fi
sleep 1
done
exit 1
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: ${{ steps.identity.outputs.artifact }}
path: ${{ runner.temp }}/minio-image/
compression-level: 0
if-no-files-found: error
retention-days: 7
auth-boundary-preflight:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: "3.12"
- name: Bind exact checked-out tree
shell: bash
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "${GITHUB_SHA}"
test -z "$(git status --porcelain)"
test "$(git rev-parse "${GITHUB_SHA}^{tree}")" = "$(git rev-parse HEAD^{tree})"
- name: Install backend architecture dependencies
working-directory: backend
run: |
set -euo pipefail
python -m pip install --upgrade pip
python -m pip install -e ".[dev,agents]"
python -m pip install ruff==0.15.22
test "$(ruff --version)" = "ruff 0.15.22"
- name: Validate module, AUTH, and test boundaries
working-directory: backend
shell: bash
run: |
set -euo pipefail
ruff check \
app/modules/authorization/api \
scripts/authorization_boundary.py \
scripts/module_boundaries.py \
scripts/test_structure_boundary.py \
tests/architecture/test_authorization_boundary.py \
tests/architecture/test_module_boundaries.py \
tests/architecture/test_test_structure_boundary.py
python -m scripts.module_boundaries validate \
--protected-base "${{ github.event.pull_request.base.sha || github.event.before }}"
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest -q \
-p pytest_asyncio.plugin \
tests/architecture/test_module_boundaries.py \
tests/architecture/test_authorization_boundary.py
python -m scripts.authorization_boundary validate \
--ledger ../.ci/auth-boundaries/IMPORT_LEDGER.md
python -m scripts.test_structure_boundary validate \
--policy ../.ci/auth-boundaries/TEST_STRUCTURE_POLICY.md \
--ledger ../.ci/auth-boundaries/TEST_STRUCTURE_DEBT.json
python -m scripts.behavior_ownership validate
lanes:
needs: minio-image
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
lane:
- shared_foundations_a
- shared_foundations_b
- schema_contracts
- project_lifecycle_a
- project_lifecycle_b
- project_lifecycle_c
- task_lifecycle_a
- task_lifecycle_b
- task_lifecycle_c
services:
redis:
image: redis:7-alpine@sha256:520775a41a63e77e06c73e35d2fd9cc15921a609516818796b4ecbb813078bc7
ports:
- 6380:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 5s
--health-retries 10
postgres:
image: public.ecr.aws/docker/library/postgres:16@sha256:33f923b05f64ca54ac4401c01126a6b92afe839a0aa0a52bc5aeb5cc958e5f20
env:
POSTGRES_DB: workstream_test
POSTGRES_USER: workstream
POSTGRES_PASSWORD: workstream
ports:
- 5433:5432
options: >-
${{ matrix.lane != 'schema_contracts' && '--tmpfs /var/lib/postgresql/data:rw,nosuid,nodev,noexec,size=2147483648' || '' }}
--health-cmd "pg_isready -U workstream -d workstream_test"
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: "3.12"
- id: identity
name: Bind exact checked-out tree
shell: bash
run: |
set -euo pipefail
job_start_epoch="$(date +%s)"
tree_sha="$(git rev-parse HEAD)"
test "${tree_sha}" = "${GITHUB_SHA}"
test -z "$(git status --porcelain)"
test "$(git rev-parse "${tree_sha}^{tree}")" = "$(git rev-parse HEAD^{tree})"
echo "job_start_epoch=${job_start_epoch}" >> "${GITHUB_OUTPUT}"
echo "tree_sha=${tree_sha}" >> "${GITHUB_OUTPUT}"
- name: Install backend and exact Ruff
working-directory: backend
run: |
set -euo pipefail
python -m pip install --upgrade pip
python -m pip install -e ".[dev,agents]"
python -m pip install ruff==0.15.22
test "$(ruff --version)" = "ruff 0.15.22"
- name: Lint
working-directory: backend
run: ruff check app tests scripts
- name: Docstring coverage
working-directory: backend
run: docstr-coverage --config .docstr.yaml
- name: Download source-pinned MinIO image
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: ${{ needs.minio-image.outputs.artifact }}
path: ${{ runner.temp }}/minio-image
- name: Start real MinIO artifact provider
shell: bash
run: |
set -euo pipefail
(cd "${RUNNER_TEMP}/minio-image" && sha256sum --check minio.tar.sha256)
docker load --input "${RUNNER_TEMP}/minio-image/minio.tar"
docker run --detach --rm --name workstream-minio \
--publish 127.0.0.1:9000:9000 \
--env MINIO_ROOT_USER=workstream-minio \
--env MINIO_ROOT_PASSWORD=workstream-minio-secret-key \
"${MINIO_IMAGE}" server /data --address :9000
for attempt in $(seq 1 60); do
if curl --fail --silent http://127.0.0.1:9000/minio/health/live >/dev/null; then
exit 0
fi
sleep 1
done
docker logs workstream-minio
exit 1
- name: Verify PostgreSQL CI storage and write settings
env:
POSTGRES_CONTAINER: ${{ job.services.postgres.id }}
EXPECTED_PG_STORAGE: ${{ matrix.lane == 'schema_contracts' && 'disk' || 'tmpfs' }}
shell: bash
run: |
set -euo pipefail
storage=$(docker exec "${POSTGRES_CONTAINER}" stat -f -c '%T %b %S' /var/lib/postgresql/data)
read -r filesystem blocks block_size <<< "${storage}"
[[ -n "${filesystem}" && "${blocks}" =~ ^[0-9]+$ && "${block_size}" =~ ^[0-9]+$ ]]
if [[ "${EXPECTED_PG_STORAGE}" == tmpfs ]]; then
[[ "${filesystem}" == tmpfs ]]
(( blocks * block_size == 2147483648 ))
elif [[ "${EXPECTED_PG_STORAGE}" == disk ]]; then
[[ "${filesystem}" != tmpfs ]]
else
exit 1
fi
settings=$(docker exec "${POSTGRES_CONTAINER}" psql -U workstream -d workstream_test -At -c "SELECT current_setting('fsync'), current_setting('full_page_writes'), current_setting('synchronous_commit'), current_setting('data_directory')")
[[ "${settings}" == 'on|on|on|/var/lib/postgresql/data' ]]
echo "PostgreSQL CI storage: ${filesystem}, bytes=$((blocks * block_size)); write settings: ${settings}"
- name: Execute semantic lane ${{ matrix.lane }}
working-directory: backend
env:
COVERAGE_FILE: .coverage
PYTEST_DISABLE_PLUGIN_AUTOLOAD: "1"
WORKSTREAM_TEST_BROKER_URL: redis://localhost:6380/0
WORKSTREAM_TEST_ADMIN_DATABASE_URL: postgresql+asyncpg://workstream:workstream@localhost:5433/postgres
WORKSTREAM_TEST_MINIO_ENDPOINT: http://127.0.0.1:9000
shell: bash
run: |
set -uo pipefail
install -d -m 700 ".ci/lane-bundle/${{ matrix.lane }}"
python scripts/run_test_lanes.py \
--lane "${{ matrix.lane }}" \
--metadata-dir ".ci/lane-bundle/${{ matrix.lane }}/metadata" \
--summary-json ".ci/lane-bundle/${{ matrix.lane }}/summary.json" \
--timeout-seconds 1200
- name: Bind lane bundle timing
if: ${{ always() }}
working-directory: backend
shell: bash
run: |
install -d -m 700 '.ci/lane-bundle/${{ matrix.lane }}'
printf '%s\n' '${{ steps.identity.outputs.job_start_epoch }}' \
> '.ci/lane-bundle/${{ matrix.lane }}/job-start-epoch.txt'
- name: Upload authenticated lane ${{ matrix.lane }}
if: ${{ always() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: backend-lane-${{ github.sha }}-${{ matrix.lane }}-attempt-${{ github.run_attempt }}
path: |
backend/.ci/lane-bundle/${{ matrix.lane }}/summary.json
backend/.ci/lane-bundle/${{ matrix.lane }}/job-start-epoch.txt
backend/.ci/lane-bundle/${{ matrix.lane }}/metadata/manifest.json
backend/.ci/lane-bundle/${{ matrix.lane }}/metadata/${{ matrix.lane }}.json
backend/.ci/lane-bundle/${{ matrix.lane }}/metadata/${{ matrix.lane }}.database.json
backend/.ci/lane-bundle/${{ matrix.lane }}/metadata/.coverage.${{ matrix.lane }}
backend/.ci/lane-bundle/${{ matrix.lane }}/metadata/${{ matrix.lane }}.log
backend/.ci/lane-bundle/${{ matrix.lane }}/metadata/${{ matrix.lane }}.admin.log
include-hidden-files: true
if-no-files-found: error
retention-days: 7
test:
if: ${{ always() }}
needs: [auth-boundary-preflight, lanes, minio-image]
runs-on: ubuntu-latest
timeout-minutes: 30
services:
redis:
image: redis:7-alpine@sha256:520775a41a63e77e06c73e35d2fd9cc15921a609516818796b4ecbb813078bc7
ports:
- 6380:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 5s
--health-retries 10
postgres:
image: public.ecr.aws/docker/library/postgres:16@sha256:33f923b05f64ca54ac4401c01126a6b92afe839a0aa0a52bc5aeb5cc958e5f20
env:
POSTGRES_DB: workstream_test
POSTGRES_USER: workstream
POSTGRES_PASSWORD: workstream
ports:
- 5433:5432
options: >-
--health-cmd "pg_isready -U workstream -d workstream_test"
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: "3.12"
- id: identity
name: Bind exact checked-out tree
shell: bash
run: |
set -euo pipefail
job_start_epoch="$(date +%s)"
tree_sha="$(git rev-parse HEAD)"
test "${tree_sha}" = "${GITHUB_SHA}"
test -z "$(git status --porcelain)"
echo "job_start_epoch=${job_start_epoch}" >> "${GITHUB_OUTPUT}"
echo "tree_sha=${tree_sha}" >> "${GITHUB_OUTPUT}"
- name: Install backend
working-directory: backend
run: python -m pip install -e ".[dev,agents]"
- name: Download source-pinned MinIO image
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: ${{ needs.minio-image.outputs.artifact }}
path: ${{ runner.temp }}/minio-image
- name: Start real MinIO API provider
shell: bash
run: |
set -euo pipefail
(cd "${RUNNER_TEMP}/minio-image" && sha256sum --check minio.tar.sha256)
docker load --input "${RUNNER_TEMP}/minio-image/minio.tar"
docker run --detach --rm --name workstream-minio \
--publish 127.0.0.1:9000:9000 \
--env MINIO_ROOT_USER=workstream-minio \
--env MINIO_ROOT_PASSWORD=workstream-minio-secret-key \
"${MINIO_IMAGE}" server /data --address :9000
for attempt in $(seq 1 60); do
if curl --fail --silent http://127.0.0.1:9000/minio/health/live >/dev/null; then
exit 0
fi
sleep 1
done
docker logs workstream-minio
exit 1
- name: Download separate lane attempts from this run
if: ${{ always() }}
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
pattern: backend-lane-${{ github.sha }}-*-attempt-*
path: backend/.ci/download
merge-multiple: false
- name: Require preflight and every semantic lane
if: ${{ always() }}
env:
PREFLIGHT_RESULT: ${{ needs.auth-boundary-preflight.result }}
LANES_RESULT: ${{ needs.lanes.result }}
run: test "${PREFLIGHT_RESULT}" = success && test "${LANES_RESULT}" = success
- name: Merge and independently validate exact lane custody
working-directory: backend
shell: bash
run: |
set -euo pipefail
install -d -m 700 .ci/test-lanes
python -m scripts.merge_test_lane_evidence \
--input-root .ci/download \
--metadata-dir .ci/test-lanes/run \
--summary-json .ci/test-lanes/run-summary.json \
--expected-head "${GITHUB_SHA}" \
--run-attempt "${GITHUB_RUN_ATTEMPT}"
python scripts/validate_test_lane_evidence.py \
--metadata-dir .ci/test-lanes/run \
--summary-json .ci/test-lanes/run-summary.json
- name: Combine semantic-lane coverage exactly once
working-directory: backend
shell: bash
run: |
set -euo pipefail
shopt -s nullglob
coverage_files=(.ci/test-lanes/run/.coverage.*)
test "${#coverage_files[@]}" -eq 9
for source in "${coverage_files[@]}"; do
destination="$(basename "${source}")"
test -f "${source}"
test ! -L "${source}"
test ! -e "${destination}"
cp -- "${source}" "${destination}"
test "$(sha256sum "${source}" | cut -d' ' -f1)" = \
"$(sha256sum "${destination}" | cut -d' ' -f1)"
done
coverage combine
- name: API contract real API e2e
working-directory: backend
env:
WORKSTREAM_TEST_BROKER_URL: redis://localhost:6380/0
WORKSTREAM_TEST_ADMIN_DATABASE_URL: postgresql+asyncpg://workstream:workstream@localhost:5433/postgres
WORKSTREAM_TEST_MINIO_ENDPOINT: http://127.0.0.1:9000
run: >-
python scripts/run_isolated_tests.py
--metadata-json "${RUNNER_TEMP}/api-database.json"
--timeout-seconds 1500
-- python scripts/api_contract_e2e.py
- name: Backend coverage diagnostics (no percentage gate)
working-directory: backend
run: coverage report --precision=2
- name: Record hosted timing, complete execution and diagnostic coverage
working-directory: backend
env:
EXPECTED_HEAD_SHA: ${{ steps.identity.outputs.tree_sha }}
shell: bash
run: |
set -euo pipefail
coverage json --include='app/*' -o .ci/test-lanes/coverage.json
python - <<'PY'
from __future__ import annotations
from collections import Counter
import hashlib
import json
import math
import os
from pathlib import Path
import subprocess
import time
from scripts.merge_test_lane_evidence import select_lane_bundles
evidence_root = Path(".ci/test-lanes")
run_root = evidence_root / "run"
summary_path = evidence_root / "run-summary.json"
coverage_path = evidence_root / "coverage.json"
output_path = Path(".ci/test-lanes/hosted-evidence.json")
def read_json(path: Path) -> dict[str, object]:
if not path.is_file() or path.is_symlink():
raise SystemExit(f"invalid hosted evidence input: {path}")
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise SystemExit(f"invalid hosted evidence object: {path}")
return value
def digest(path: Path) -> str:
if not path.is_file() or path.is_symlink():
raise SystemExit(f"invalid hosted digest input: {path}")
return hashlib.sha256(path.read_bytes()).hexdigest()
expected_head = os.environ["EXPECTED_HEAD_SHA"]
actual_head = subprocess.check_output(
["git", "rev-parse", "HEAD"], text=True
).strip()
summary = read_json(summary_path)
coverage = read_json(coverage_path)
if actual_head != expected_head or summary.get("head_sha") != expected_head:
raise SystemExit("hosted evidence head drift")
lanes = summary.get("lanes")
if not isinstance(lanes, list) or len(lanes) != 9:
raise SystemExit("invalid hosted lane inventory")
collected: list[str] = []
completed: list[str] = []
lane_elapsed: list[float] = []
for lane in lanes:
if not isinstance(lane, dict):
raise SystemExit("invalid hosted lane row")
elapsed = lane.get("elapsed_seconds")
if (
isinstance(elapsed, bool)
or not isinstance(elapsed, (int, float))
or not math.isfinite(elapsed)
or elapsed < 0
):
raise SystemExit("invalid hosted lane timing")
lane_elapsed.append(float(elapsed))
for file_key, digest_key in (
("evidence_file", "evidence_sha256"),
("coverage_file", "coverage_sha256"),
):
name = lane.get(file_key)
expected_digest = lane.get(digest_key)
if not isinstance(name, str) or not isinstance(expected_digest, str):
raise SystemExit("invalid hosted lane digest binding")
path = run_root / name
if path.parent != run_root or digest(path) != expected_digest:
raise SystemExit("hosted lane digest drift")
lane_evidence = read_json(run_root / str(lane["evidence_file"]))
lane_collected = lane_evidence.get("collected_nodes")
lane_completed = lane_evidence.get("completed_nodes")
if not isinstance(lane_collected, list) or not isinstance(lane_completed, list):
raise SystemExit("invalid hosted node custody")
collected.extend(lane_collected)
completed.extend(lane_completed)
canonical_count = summary.get("canonical_node_count")
if (
isinstance(canonical_count, bool)
or not isinstance(canonical_count, int)
or canonical_count <= 0
or len(collected) != canonical_count
or len(completed) != canonical_count
or Counter(collected) != Counter(completed)
or len(set(collected)) != canonical_count
):
raise SystemExit("hosted node count mismatch")
aggregate = summary.get("aggregate_runner_seconds")
slowest = summary.get("slowest_lane_seconds")
for value in (aggregate, slowest):
if (
isinstance(value, bool)
or not isinstance(value, (int, float))
or not math.isfinite(value)
or value < 0
):
raise SystemExit("invalid hosted summary timing")
if not math.isclose(
float(aggregate), math.fsum(lane_elapsed), rel_tol=0.0, abs_tol=1e-9
):
raise SystemExit("aggregate runner timing drift")
if not math.isclose(
float(slowest), max(lane_elapsed), rel_tol=0.0, abs_tol=1e-9
):
raise SystemExit("slowest lane timing drift")
start_epochs = []
selected_bundles = select_lane_bundles(
Path(".ci/download"), expected_head, int(os.environ["GITHUB_RUN_ATTEMPT"]),
)
for bundle in selected_bundles.values():
timing_path = bundle / "job-start-epoch.txt"
if timing_path.is_symlink() or not timing_path.is_file():
raise SystemExit("missing lane start timing")
timing_value = timing_path.read_text(encoding="ascii").strip()
if not timing_value.isdigit():
raise SystemExit("invalid lane start timing")
start_epochs.append(int(timing_value))
start_epoch = min(start_epochs)
total_wall = time.time() - start_epoch
if not math.isfinite(total_wall) or total_wall < 0:
raise SystemExit("invalid Backend hosted wall time")
totals = coverage.get("totals")
percent = totals.get("percent_covered") if isinstance(totals, dict) else None
if (
isinstance(percent, bool)
or not isinstance(percent, (int, float))
or not math.isfinite(percent)
or percent < 0
or percent > 100
):
raise SystemExit("invalid hosted global coverage")
hosted = {
"aggregate_runner_seconds": float(aggregate),
"canonical_collected_count": len(collected),
"completed_count": len(completed),
"global_coverage_percent": float(percent),
"global_coverage_sha256": digest(coverage_path),
"head_sha": expected_head,
"run_summary_sha256": digest(summary_path),
"slowest_lane_seconds": float(slowest),
"timing_target_met": total_wall <= 480,
"total_backend_wall_seconds": round(total_wall, 3),
}
output_path.write_text(
json.dumps(hosted, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
PY
- name: Reassert exact tree custody
if: ${{ always() }}
shell: bash
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "${{ steps.identity.outputs.tree_sha }}"
test "$(git rev-parse HEAD^{tree})" = "$(git rev-parse "${{ steps.identity.outputs.tree_sha }}^{tree}")"
- name: Upload semantic-lane evidence and timing
if: ${{ always() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: backend-semantic-lane-evidence-${{ steps.identity.outputs.tree_sha }}-attempt-${{ github.run_attempt }}
path: |
backend/.ci/download/**
backend/.ci/test-lanes/**
include-hidden-files: true
if-no-files-found: warn
retention-days: 7