release: harden RC-04 Linux artifact chain

This commit is contained in:
2026-09-20 13:31:14 +08:00
parent 891e352ab4
commit 56b9565742
22 changed files with 12767 additions and 16 deletions
+8
View File
@@ -358,6 +358,14 @@ Keep `CONTRACT_API_AI_PLATFORM_TIMEOUT_SECONDS` at least five seconds above `AI_
Start from `yuqei-infra\compose\.env.production.example` and replace every `CHANGE_ME` value before deployment.
The production inventory requires immutable digest references for Contract API,
Contract Web, AI Platform API, and AI Worker. The production override clears the
shared development `build` definitions with the Compose Specification `!reset`
tag. On a host whose images were imported offline, append
`compose/docker-compose.production.offline.yml` last so every production service,
including `backend-hosted-web` and `ops` profile services, uses
`pull_policy: never`. See `deploy/README.md` for the complete file order.
Run migrations explicitly before replacing application containers.
The 2-second read cache is process-local and is automatically disabled when `CONTRACT_API_UVICORN_WORKERS` is greater than `1`. Use a shared cache with cross-process invalidation before increasing the production worker count if the same list/detail p95 target must be retained.
@@ -3,6 +3,7 @@
COMPOSE_PROJECT_NAME=yuqei-v2-production
RELEASE_ID=CHANGE_ME_IMMUTABLE_RC_TAG
CONTRACT_API_IMAGE=registry.example.com/yuqei/contract-api@sha256:CHANGE_ME_64_HEX_DIGEST
CONTRACT_WEB_IMAGE=registry.example.com/yuqei/contract-web@sha256:CHANGE_ME_64_HEX_DIGEST
AI_PLATFORM_API_IMAGE=registry.example.com/yuqei/ai-platform-api@sha256:CHANGE_ME_64_HEX_DIGEST
AI_PLATFORM_RELEASE_IMAGE_DIGEST=sha256:CHANGE_ME_64_HEX_DIGEST
AI_WORKER_IMAGE=registry.example.com/yuqei/ai-worker@sha256:CHANGE_ME_64_HEX_DIGEST
@@ -0,0 +1,48 @@
services:
postgres:
pull_policy: never
postgres-init-databases:
pull_policy: never
redis:
pull_policy: never
minio:
pull_policy: never
contract-api-migrate:
pull_policy: never
contract-api:
pull_policy: never
contract-review-worker:
pull_policy: never
contract-playbook-governance-worker:
pull_policy: never
ai-platform-api-migrate:
pull_policy: never
ai-platform-prompt-assets-import:
pull_policy: never
ai-platform-api:
pull_policy: never
worker:
pull_policy: never
contract-web:
pull_policy: never
edge:
pull_policy: never
contract-files-backup:
pull_policy: never
minio-backup:
pull_policy: never
@@ -18,10 +18,12 @@ services:
contract-api-migrate:
image: ${CONTRACT_API_IMAGE:?CONTRACT_API_IMAGE must reference an immutable production image}
build: !reset null
pull_policy: always
contract-api:
image: ${CONTRACT_API_IMAGE:?CONTRACT_API_IMAGE must reference an immutable production image}
build: !reset null
pull_policy: always
# SMTP remains opt-in through docker-compose.production.smtp.yml so disabled mode has no secret-file dependency.
environment:
@@ -38,20 +40,24 @@ services:
contract-review-worker:
image: ${CONTRACT_API_IMAGE:?CONTRACT_API_IMAGE must reference an immutable production image}
build: !reset null
pull_policy: always
volumes:
- contract-files:/data/contract-files
contract-playbook-governance-worker:
image: ${CONTRACT_API_IMAGE:?CONTRACT_API_IMAGE must reference an immutable production image}
build: !reset null
pull_policy: always
ai-platform-api-migrate:
image: ${AI_PLATFORM_API_IMAGE:?AI_PLATFORM_API_IMAGE must reference an immutable production image}
build: !reset null
pull_policy: always
ai-platform-prompt-assets-import:
image: ${AI_PLATFORM_API_IMAGE:?AI_PLATFORM_API_IMAGE must reference an immutable production image}
build: !reset null
pull_policy: always
# Production startup validates the packaged assets without publishing them.
# An approved publication batch must invoke the importer separately with --apply.
@@ -59,6 +65,7 @@ services:
ai-platform-api:
image: ${AI_PLATFORM_API_IMAGE:?AI_PLATFORM_API_IMAGE must reference an immutable production image}
build: !reset null
pull_policy: always
environment:
AI_PLATFORM_SECRET_FILE_ROOT: /run/secrets
@@ -80,9 +87,13 @@ services:
worker:
image: ${AI_WORKER_IMAGE:?AI_WORKER_IMAGE must reference an immutable production image}
build: !reset null
pull_policy: always
contract-web:
image: ${CONTRACT_WEB_IMAGE:?CONTRACT_WEB_IMAGE must reference an immutable production image}
build: !reset null
pull_policy: always
profiles: ["backend-hosted-web"]
edge:
+42 -11
View File
@@ -1,18 +1,21 @@
# V2 production backend deployment
The production backend runs on `192.168.3.90`; Contract Web is deployed separately to Alibaba Cloud. The production compose override therefore disables Contract Web by default and exposes only the HTTPS edge service publicly.
The production backend runs on `192.168.3.90`; Contract Web is deployed separately to Alibaba Cloud. The production compose override therefore keeps Contract Web behind the opt-in `backend-hosted-web` profile and exposes only the HTTPS edge service publicly by default. Its image is still required and digest-pinned so every production profile has one immutable image inventory.
## Files
- `../compose/docker-compose.yml`: shared service definitions used by local and smoke environments.
- `../compose/docker-compose.production.yml`: backend-only production image, volume, TLS edge, and backup-helper overrides.
- `../compose/docker-compose.production.yml`: production image, volume, TLS edge, and backup-helper overrides. It uses the Compose Specification `!reset null` tag to remove every application `build` inherited from the shared file.
- `../compose/docker-compose.production.offline.yml`: final, offline-only override that forbids registry pulls for every service that production can enable, including optional profiles.
- `../compose/docker-compose.production.smtp.yml`: optional SMTP password secret mount; include only when email delivery mode is `smtp`.
- `../compose/.env.production.example`: reviewed variable inventory. The real file belongs at `/etc/yuqei/production.env`, mode `0600`, outside the repository.
- `preflight-production.py`: read-only production configuration gate.
- `verify-production-compose.py`: read-only merged-model gate. It renders Docker Compose JSON for online and offline modes across the default, `backend-hosted-web`, `ops`, and combined profile sets.
- `release-package.py`: backend-only release-package verifier and planner. Its production apply entry point remains deliberately closed until the complete infrastructure-image import plan and externally trusted backup/compatibility approvals are available.
## Prepare without changing traffic
1. Create an immutable RC commit and publish the three application images by digest. Pin PostgreSQL/pgvector, Redis, MinIO, mc, Nginx, and the backup helper by digest as well.
1. Create an immutable RC commit and publish the four application images by digest: Contract API, Contract Web, AI Platform API, and AI Worker. Pin PostgreSQL/pgvector, Redis, MinIO, mc, Nginx, and the backup helper by digest as well.
2. Create a new production PostgreSQL/volume set. Do not reuse or rename the smoke database or volumes.
3. Mount an off-host backup filesystem at `BACKUP_HOST_DIR`.
4. Place `tls.crt`, `tls.key`, and the AI provider key in protected host directories. When enabling SMTP, also create the file named by `CONTRACT_API_IDENTITY_SMTP_PASSWORD_FILE_HOST`, mode `0600`.
@@ -29,15 +32,11 @@ python3 yuqei-infra/deploy/preflight-production.py \
--artifact-dir /var/lib/yuqei/preflight
```
7. Validate and pull images without starting services:
7. Validate the merged Compose models, then pull online images without starting services:
```bash
docker compose \
--project-name yuqei-v2-production \
--env-file /etc/yuqei/production.env \
-f yuqei-infra/compose/docker-compose.yml \
-f yuqei-infra/compose/docker-compose.production.yml \
config --quiet
python3 yuqei-infra/deploy/verify-production-compose.py \
--env-file /etc/yuqei/production.env
docker compose \
--project-name yuqei-v2-production \
@@ -47,7 +46,39 @@ docker compose \
pull
```
These commands do not start containers and do not switch traffic.
The merge gate invokes only `docker compose config --format json`; it does not
start, build, pull, or remove anything. The separate `pull` command downloads
the already approved online image references, but does not start containers or
switch traffic.
Use a current Docker Compose V2 release that implements the Compose Specification
`!reset` tag. A successful `config --quiet` invocation proves only that Compose
can parse and interpolate the files; it does not prove the merged image, build,
or pull-policy content. The JSON merge gate performs those semantic checks.
For an offline host, import every approved image into the local engine through
the approved artifact-transfer procedure. The JSON merge gate always appends
`docker-compose.production.offline.yml` after the base and production files when
it verifies offline mode.
Pass `--profile backend-hosted-web` and/or `--profile ops` when those services are
intended. The offline override sets `pull_policy: never` for all services in every
core production profile, so a missing local image fails closed instead of contacting
a registry. A companion overlay that introduces another image-bearing service needs
an explicit offline pull policy of its own before it is approved for this path. Do
not add the offline override to the online `pull` command.
`release-package.py` validates the current four-application Docker import plan,
including archive size/SHA-256 and the expected config-based image IDs used for
offline Compose overrides. That is not yet a clean-host offline installation
closure: PostgreSQL, the PostgreSQL client, Redis, MinIO, mc, Nginx, and the
backup helper do not all have approved import archives, and the packaged backup
and rollback-compatibility records are only self-hashed rather than externally
signed. Consequently `verify` reports structural validity without apply
eligibility, plans expose the blocking reasons, and the normal `--apply` entry
point fails before creating state or loading an image. Contract Web remains an
identity-bound, separately deployed artifact; this backend executor never claims
that the full cross-host release has been installed or rolled back.
The production override also replaces `ai-platform-prompt-assets-import` with the
importer's default dry-run command. That Compose service may validate and report the
@@ -124,6 +124,7 @@ def validate_environment(values: dict[str, str], *, template_mode: bool = False)
"COMPOSE_PROJECT_NAME",
"RELEASE_ID",
"CONTRACT_API_IMAGE",
"CONTRACT_WEB_IMAGE",
"AI_PLATFORM_API_IMAGE",
"AI_PLATFORM_RELEASE_IMAGE_DIGEST",
"AI_WORKER_IMAGE",
@@ -197,6 +198,7 @@ def validate_environment(values: dict[str, str], *, template_mode: bool = False)
image_keys = (
"CONTRACT_API_IMAGE",
"CONTRACT_WEB_IMAGE",
"AI_PLATFORM_API_IMAGE",
"AI_WORKER_IMAGE",
"POSTGRES_IMAGE",
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,481 @@
#!/usr/bin/env python3
"""Fail-closed validation of the merged production Compose data model.
This gate runs only ``docker compose config --format json``. It never starts,
builds, pulls, or removes containers or images. Both the online stack and the
offline stack are rendered for every supported production profile combination
so validation is performed on Compose's merged model rather than on source
YAML text.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, Mapping, Sequence
REPO_ROOT = Path(__file__).resolve().parents[2]
DOCKER_CLI = "/usr/bin/docker"
LOCAL_DOCKER_HOST = "unix:///var/run/docker.sock"
BASE_COMPOSE_RELATIVE = "yuqei-infra/compose/docker-compose.yml"
PRODUCTION_COMPOSE_RELATIVE = "yuqei-infra/compose/docker-compose.production.yml"
OFFLINE_COMPOSE_RELATIVE = "yuqei-infra/compose/docker-compose.production.offline.yml"
SCHEMA_VERSION = "yuqei.production-compose-merge-gate.v1"
MAX_ENV_BYTES = 1024 * 1024
MAX_CONFIG_BYTES = 8 * 1024 * 1024
ENV_KEY_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
PROJECT_RE = re.compile(r"^[a-z0-9][a-z0-9_-]{0,62}$")
IMAGE_REFERENCE_RE = re.compile(
r"^[a-z0-9](?:[a-z0-9._:/-]{0,510}[a-z0-9])?@sha256:[0-9a-f]{64}$"
)
APPLICATION_SERVICE_IMAGE_ENV: Mapping[str, str] = {
"contract-api-migrate": "CONTRACT_API_IMAGE",
"contract-api": "CONTRACT_API_IMAGE",
"contract-review-worker": "CONTRACT_API_IMAGE",
"contract-playbook-governance-worker": "CONTRACT_API_IMAGE",
"ai-platform-api-migrate": "AI_PLATFORM_API_IMAGE",
"ai-platform-prompt-assets-import": "AI_PLATFORM_API_IMAGE",
"ai-platform-api": "AI_PLATFORM_API_IMAGE",
"worker": "AI_WORKER_IMAGE",
"contract-web": "CONTRACT_WEB_IMAGE",
}
PRODUCTION_SERVICES = (
"postgres",
"postgres-init-databases",
"redis",
"minio",
"contract-api-migrate",
"contract-api",
"contract-review-worker",
"contract-playbook-governance-worker",
"ai-platform-api-migrate",
"ai-platform-prompt-assets-import",
"ai-platform-api",
"worker",
"contract-web",
"edge",
"contract-files-backup",
"minio-backup",
)
PROFILE_ONLY_SERVICES: Mapping[str, str] = {
"contract-web": "backend-hosted-web",
"contract-files-backup": "ops",
"minio-backup": "ops",
}
@dataclass(frozen=True)
class ProfileCase:
name: str
profiles: tuple[str, ...]
PROFILE_CASES = (
ProfileCase("default", ()),
ProfileCase("backend-hosted-web", ("backend-hosted-web",)),
ProfileCase("ops", ("ops",)),
ProfileCase("backend-hosted-web+ops", ("backend-hosted-web", "ops")),
)
class ComposeGateError(RuntimeError):
"""Raised when production Compose validation fails closed."""
Runner = Callable[..., subprocess.CompletedProcess[Any]]
def _canonical_json_bytes(value: Any) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
allow_nan=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
try:
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
except OSError as exc:
raise ComposeGateError(f"Cannot read required file: {path}") from exc
return digest.hexdigest()
def _validated_file(path: Path, *, label: str, max_bytes: int | None = None) -> Path:
candidate = Path(path)
if candidate.is_symlink():
raise ComposeGateError(f"{label} must not be a symbolic link.")
try:
resolved = candidate.resolve(strict=True)
stat = resolved.stat()
except OSError as exc:
raise ComposeGateError(f"{label} is missing or unreadable: {candidate}") from exc
if not resolved.is_file():
raise ComposeGateError(f"{label} must be a regular file.")
if max_bytes is not None and stat.st_size > max_bytes:
raise ComposeGateError(f"{label} exceeds the maximum allowed size.")
return resolved
def _compose_inputs(repo_root: Path) -> tuple[Path, Path, Path]:
try:
root = Path(repo_root).resolve(strict=True)
except OSError as exc:
raise ComposeGateError("Repository root is missing or unreadable.") from exc
if not root.is_dir():
raise ComposeGateError("Repository root must be a directory.")
results: list[Path] = []
for label, relative in (
("base Compose file", BASE_COMPOSE_RELATIVE),
("production Compose file", PRODUCTION_COMPOSE_RELATIVE),
("offline Compose override", OFFLINE_COMPOSE_RELATIVE),
):
candidate = root / relative
validated = _validated_file(candidate, label=label, max_bytes=MAX_CONFIG_BYTES)
try:
validated.relative_to(root)
except ValueError as exc:
raise ComposeGateError(f"{label} escapes the repository root.") from exc
results.append(validated)
return results[0], results[1], results[2]
def parse_strict_env(path: Path) -> tuple[Path, dict[str, str]]:
target = _validated_file(path, label="production env file", max_bytes=MAX_ENV_BYTES)
try:
text = target.read_text(encoding="utf-8-sig")
except (OSError, UnicodeError) as exc:
raise ComposeGateError("Production env file must be readable UTF-8 text.") from exc
values: dict[str, str] = {}
for line_number, raw_line in enumerate(text.splitlines(), start=1):
line = raw_line.strip()
if not line or line.startswith("#"):
continue
if line.startswith("export ") or "=" not in line:
raise ComposeGateError(f"Invalid strict env syntax at line {line_number}.")
raw_key, raw_value = line.split("=", 1)
if raw_key != raw_key.strip():
raise ComposeGateError(f"Invalid strict env key at line {line_number}.")
key = raw_key
if not ENV_KEY_RE.fullmatch(key) or key in values:
raise ComposeGateError(f"Invalid or duplicate strict env key at line {line_number}.")
values[key] = raw_value.strip()
project_name = values.get("COMPOSE_PROJECT_NAME")
if project_name is None or not PROJECT_RE.fullmatch(project_name):
raise ComposeGateError("COMPOSE_PROJECT_NAME is missing or unsafe in the strict env file.")
for env_key in sorted(set(APPLICATION_SERVICE_IMAGE_ENV.values())):
image = values.get(env_key)
if image is None or not IMAGE_REFERENCE_RE.fullmatch(image):
raise ComposeGateError(
f"{env_key} must be an explicit lowercase immutable @sha256 image "
"in the strict env file."
)
return target, values
def _reject_json_constant(value: str) -> None:
raise ComposeGateError(f"Compose JSON contains forbidden numeric constant {value}.")
def _json_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
result: dict[str, Any] = {}
for key, value in pairs:
if key in result:
raise ComposeGateError(f"Compose JSON contains duplicate key {key!r}.")
result[key] = value
return result
def _parse_compose_json(stdout: Any) -> dict[str, Any]:
if isinstance(stdout, bytes):
try:
raw = stdout.decode("utf-8")
except UnicodeError as exc:
raise ComposeGateError("Docker Compose emitted non-UTF-8 JSON.") from exc
elif isinstance(stdout, str):
raw = stdout
else:
raise ComposeGateError("Docker Compose did not emit textual JSON.")
if len(raw.encode("utf-8")) > MAX_CONFIG_BYTES:
raise ComposeGateError("Docker Compose JSON exceeds the maximum allowed size.")
try:
payload = json.loads(
raw,
object_pairs_hook=_json_object,
parse_constant=_reject_json_constant,
)
except ComposeGateError:
raise
except (TypeError, ValueError, json.JSONDecodeError) as exc:
raise ComposeGateError("Docker Compose did not emit valid JSON.") from exc
if not isinstance(payload, dict):
raise ComposeGateError("Docker Compose JSON must be an object.")
return payload
def _required_services(profiles: Sequence[str]) -> set[str]:
active = set(profiles)
return {
service
for service in PRODUCTION_SERVICES
if service not in PROFILE_ONLY_SERVICES
or PROFILE_ONLY_SERVICES[service] in active
}
def _validate_model(
payload: Mapping[str, Any],
*,
env_values: Mapping[str, str],
mode: str,
profile_case: ProfileCase,
) -> dict[str, Any]:
services = payload.get("services")
if not isinstance(services, dict):
raise ComposeGateError(
f"{mode}/{profile_case.name}: merged Compose model has no services object."
)
actual_names = set(services)
allowed_names = set(PRODUCTION_SERVICES)
unexpected = sorted(actual_names - allowed_names)
if unexpected:
raise ComposeGateError(
f"{mode}/{profile_case.name}: unexpected production services: {', '.join(unexpected)}."
)
missing = sorted(_required_services(profile_case.profiles) - actual_names)
if missing:
raise ComposeGateError(
f"{mode}/{profile_case.name}: required services are missing: {', '.join(missing)}."
)
validated_application_services: list[str] = []
for service_name, env_key in APPLICATION_SERVICE_IMAGE_ENV.items():
if service_name not in services:
continue
service = services[service_name]
if not isinstance(service, dict):
raise ComposeGateError(
f"{mode}/{profile_case.name}: service {service_name} is not an object."
)
if service.get("image") != env_values[env_key]:
raise ComposeGateError(
f"{mode}/{profile_case.name}: service {service_name} image does not "
f"exactly match {env_key}."
)
if service.get("build") is not None:
raise ComposeGateError(
f"{mode}/{profile_case.name}: service {service_name} retains a "
"build definition."
)
expected_pull = "never" if mode == "offline" else "always"
if service.get("pull_policy") != expected_pull:
raise ComposeGateError(
f"{mode}/{profile_case.name}: service {service_name} pull_policy "
f"must be {expected_pull}."
)
validated_application_services.append(service_name)
if mode == "offline":
for service_name in sorted(actual_names):
service = services[service_name]
if not isinstance(service, dict):
raise ComposeGateError(
f"offline/{profile_case.name}: service {service_name} is not an object."
)
if service.get("build") is not None:
raise ComposeGateError(
f"offline/{profile_case.name}: service {service_name} retains "
"a build definition."
)
if service.get("pull_policy") != "never":
raise ComposeGateError(
f"offline/{profile_case.name}: service {service_name} "
"pull_policy must be never."
)
return {
"mode": mode,
"profile_case": profile_case.name,
"profiles": list(profile_case.profiles),
"service_count": len(actual_names),
"validated_application_services": validated_application_services,
"config_sha256": hashlib.sha256(_canonical_json_bytes(payload)).hexdigest(),
}
def _command(
*,
project_name: str,
env_file: Path,
compose_files: Sequence[Path],
profile_case: ProfileCase,
) -> list[str]:
command = [
str(DOCKER_CLI),
"--host",
LOCAL_DOCKER_HOST,
"compose",
"--project-name",
project_name,
"--env-file",
str(env_file),
]
for compose_file in compose_files:
command.extend(("-f", str(compose_file)))
for profile in profile_case.profiles:
command.extend(("--profile", profile))
command.extend(("config", "--format", "json"))
return command
def _safe_subprocess_env(env_values: Mapping[str, str]) -> dict[str, str]:
# The env file is the only interpolation source. In particular, an
# ambient Docker context, daemon, Compose file/profile, config directory,
# or executable search path must not redirect this read-only gate.
_ = env_values
return {
"HOME": "/var/empty",
"LANG": "C.UTF-8",
"LC_ALL": "C.UTF-8",
}
def _run_config(
command: list[str],
*,
repo_root: Path,
runner: Runner,
child_env: Mapping[str, str],
label: str,
) -> dict[str, Any]:
if command[:4] != [str(DOCKER_CLI), "--host", LOCAL_DOCKER_HOST, "compose"]:
raise ComposeGateError("Internal error: Docker target is not the fixed local daemon.")
if command[-3:] != ["config", "--format", "json"]:
raise ComposeGateError("Internal error: only Compose config JSON commands are permitted.")
if any(token in {"up", "pull", "build", "down", "run", "exec"} for token in command):
raise ComposeGateError("Internal error: a mutating Compose command was constructed.")
try:
completed = runner(
command,
check=False,
capture_output=True,
text=True,
shell=False,
cwd=str(repo_root),
env=dict(child_env),
timeout=60,
)
except (FileNotFoundError, OSError, subprocess.TimeoutExpired) as exc:
raise ComposeGateError(f"{label}: Docker Compose config could not be executed.") from exc
if completed.returncode != 0:
stderr = completed.stderr
if isinstance(stderr, bytes):
stderr = stderr.decode("utf-8", errors="replace")
detail = str(stderr or "").strip().replace("\r", " ").replace("\n", " ")[:500]
suffix = f": {detail}" if detail else ""
raise ComposeGateError(
f"{label}: Docker Compose config failed with exit code {completed.returncode}{suffix}"
)
return _parse_compose_json(completed.stdout)
def verify_production_compose(
*,
env_file: Path,
repo_root: Path = REPO_ROOT,
runner: Runner = subprocess.run,
) -> dict[str, Any]:
"""Render and validate every supported online/offline profile combination."""
env_target, env_values = parse_strict_env(env_file)
try:
root = Path(repo_root).resolve(strict=True)
except OSError as exc:
raise ComposeGateError("Repository root is missing or unreadable.") from exc
base, production, offline = _compose_inputs(root)
child_env = _safe_subprocess_env(env_values)
results: list[dict[str, Any]] = []
for profile_case in PROFILE_CASES:
for mode, compose_files in (
("online", (base, production)),
("offline", (base, production, offline)),
):
command = _command(
project_name=env_values["COMPOSE_PROJECT_NAME"],
env_file=env_target,
compose_files=compose_files,
profile_case=profile_case,
)
payload = _run_config(
command,
repo_root=root,
runner=runner,
child_env=child_env,
label=f"{mode}/{profile_case.name}",
)
results.append(
_validate_model(
payload,
env_values=env_values,
mode=mode,
profile_case=profile_case,
)
)
return {
"schema_version": SCHEMA_VERSION,
"status": "passed",
"env_file_sha256": _sha256(env_target),
"compose_inputs": [
{"path": path.relative_to(root).as_posix(), "sha256": _sha256(path)}
for path in (base, production, offline)
],
"application_service_count": len(APPLICATION_SERVICE_IMAGE_ENV),
"production_service_count": len(PRODUCTION_SERVICES),
"cases": results,
}
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Read-only gate for merged online/offline production Compose models."
)
parser.add_argument("--env-file", required=True, type=Path)
return parser
def main(argv: Sequence[str] | None = None) -> int:
args = _parser().parse_args(argv)
try:
report = verify_production_compose(env_file=args.env_file)
except ComposeGateError as exc:
print(f"production Compose gate failed: {exc}", file=sys.stderr)
return 1
print(_canonical_json_bytes(report).decode("utf-8"))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+18 -1
View File
@@ -18,7 +18,24 @@ COPY yuqei-contract-platform/packages/design-system yuqei-contract-platform/pack
RUN npm --workspace @yuqei/contract-web run build
# npm is required only while assembling the Next.js bundle. Do not ship the
# package-manager CLI (and its independent dependency tree) in the runtime
# image; start the already installed Next.js CLI with Node directly instead.
RUN rm -rf \
/usr/local/lib/node_modules/npm \
/usr/local/lib/node_modules/corepack \
/usr/local/bin/npm \
/usr/local/bin/npx \
/usr/local/bin/corepack \
/usr/local/bin/yarn \
/usr/local/bin/yarnpkg \
/usr/local/bin/pnpm \
/opt/yarn* \
&& chown -R node:node /app
ENV NODE_ENV=production
USER node
EXPOSE 3000
CMD ["npm", "--workspace", "@yuqei/contract-web", "run", "start"]
CMD ["node", "/app/node_modules/next/dist/bin/next", "start", "/app/yuqei-contract-platform/apps/contract-web", "-p", "3000"]
@@ -0,0 +1,309 @@
{
"schema_version": "yuqei.linux-release-security-policy.v1",
"target": {
"architecture": "amd64",
"os": "linux",
"platform": "linux/amd64"
},
"builder": {
"allowed_drivers": [
"docker-container"
],
"maximum_nodes": 1,
"required_buildkit_version": "0.13.2",
"required_driver_image": "docker.m.daocloud.io/moby/buildkit@sha256:9194b5ec1be368f41c516df7f93f7f540630ea06136056b2ffebb62226ed4ad6",
"required_driver_image_digest": "sha256:9194b5ec1be368f41c516df7f93f7f540630ea06136056b2ffebb62226ed4ad6",
"required_exporters": [
"oci",
"docker"
],
"required_local_driver_image_id": "sha256:1a21071780b069d68bd7ef277675f1c5273fee488cec145494cf072777260797",
"required_platform": "linux/amd64"
},
"compose_gate": {
"case_count": 8,
"compose_inputs": [
"yuqei-infra/compose/docker-compose.yml",
"yuqei-infra/compose/docker-compose.production.yml",
"yuqei-infra/compose/docker-compose.production.offline.yml"
],
"production_secrets_forbidden": true,
"schema_version": "yuqei.production-compose-merge-gate.v1",
"synthetic_environment_required": true,
"validator": "yuqei-infra/deploy/verify-production-compose.py"
},
"execution": {
"command_timeouts_seconds": {
"base_image_inspect": 120,
"builder_inspect": 120,
"compose_gate": 600,
"container_inspect": 30,
"git_validation": 30,
"image_build": 3600,
"sbom_generation": 600,
"tool_version": 30,
"trivy_db_download": 600,
"trivy_db_inspect": 120,
"vulnerability_scan": 900
},
"controlled_environment": {
"BUILDX_CONFIG": "/root/.docker/buildx",
"DOCKER_CONFIG": "/var/empty/docker",
"HOME": "/var/empty",
"LANG": "C.UTF-8",
"LC_ALL": "C.UTF-8",
"PATH": "/usr/bin:/bin"
},
"docker_host": "unix:///var/run/docker.sock",
"pinned_executables": {
"docker": "/usr/bin/docker",
"git": "/usr/bin/git",
"syft": "/usr/bin/syft",
"trivy": "/usr/bin/trivy"
}
},
"toolchain": {
"buildx": {
"allowed_versions": [
"0.14.0"
]
},
"compose": {
"allowed_versions": [
"2.27.0"
]
},
"docker_cli": {
"allowed_versions": [
"26.1.3"
]
},
"docker_engine": {
"allowed_versions": [
"26.1.3"
]
},
"syft": {
"allowed_versions": [
"1.52.0"
]
},
"trivy": {
"allowed_versions": [
"0.74.0"
]
}
},
"images": [
{
"base_image": {
"build_arg": "NODE_BASE_IMAGE",
"content_digest": "sha256:b64da1de5a51067ab8e75f0bc8dbd0905d8894baa22261f439a4572f41291e50",
"source_reference": "node:22-alpine@sha256:b64da1de5a51067ab8e75f0bc8dbd0905d8894baa22261f439a4572f41291e50",
"transport": "domestic_registry_mirror",
"transport_reference": "docker.m.daocloud.io/library/node:22-alpine@sha256:b64da1de5a51067ab8e75f0bc8dbd0905d8894baa22261f439a4572f41291e50"
},
"dockerfile": "yuqei-infra/docker/contract-web.Dockerfile",
"environment_key": "CONTRACT_WEB_IMAGE",
"name": "yuqei/contract-web",
"role": "contract-web"
},
{
"base_image": {
"build_arg": "PYTHON_BASE_IMAGE",
"content_digest": "sha256:44ff437bba879d4941b710a369a8f19266aea34b29002807f0c487fabc9eec9b",
"source_reference": "python:3.12-slim@sha256:44ff437bba879d4941b710a369a8f19266aea34b29002807f0c487fabc9eec9b",
"transport": "domestic_registry_mirror",
"transport_reference": "docker.m.daocloud.io/library/python:3.12-slim@sha256:44ff437bba879d4941b710a369a8f19266aea34b29002807f0c487fabc9eec9b"
},
"dockerfile": "yuqei-infra/docker/contract-api.Dockerfile",
"environment_key": "CONTRACT_API_IMAGE",
"name": "yuqei/contract-api",
"role": "contract-api"
},
{
"base_image": {
"build_arg": "PYTHON_BASE_IMAGE",
"content_digest": "sha256:44ff437bba879d4941b710a369a8f19266aea34b29002807f0c487fabc9eec9b",
"source_reference": "python:3.12-slim@sha256:44ff437bba879d4941b710a369a8f19266aea34b29002807f0c487fabc9eec9b",
"transport": "domestic_registry_mirror",
"transport_reference": "docker.m.daocloud.io/library/python:3.12-slim@sha256:44ff437bba879d4941b710a369a8f19266aea34b29002807f0c487fabc9eec9b"
},
"dockerfile": "yuqei-infra/docker/ai-platform-api.Dockerfile",
"environment_key": "AI_PLATFORM_API_IMAGE",
"name": "yuqei/ai-platform-api",
"role": "ai-platform-api"
},
{
"base_image": {
"build_arg": "PYTHON_BASE_IMAGE",
"content_digest": "sha256:44ff437bba879d4941b710a369a8f19266aea34b29002807f0c487fabc9eec9b",
"source_reference": "python:3.12-slim@sha256:44ff437bba879d4941b710a369a8f19266aea34b29002807f0c487fabc9eec9b",
"transport": "domestic_registry_mirror",
"transport_reference": "docker.m.daocloud.io/library/python:3.12-slim@sha256:44ff437bba879d4941b710a369a8f19266aea34b29002807f0c487fabc9eec9b"
},
"dockerfile": "yuqei-infra/docker/worker.Dockerfile",
"environment_key": "AI_WORKER_IMAGE",
"name": "yuqei/ai-worker",
"role": "ai-worker"
}
],
"sbom": {
"format": "spdx-json",
"generator": "syft",
"spdx_version": "SPDX-2.3"
},
"vulnerability_scan": {
"database": {
"fail_closed": true,
"maximum_age_hours": 24,
"repository_transport": {
"allowed_media_types": [
"application/vnd.oci.image.manifest.v1+json"
],
"reference": "docker.m.daocloud.io/aquasec/trivy-db:2",
"schema_version": 2,
"transport": "domestic_registry_mirror"
},
"require_next_update_after_scan": true
},
"exemptions": [
{
"id": "RC04.4-DEBIAN13-PYTHON-UNFIXED-HIGH-001",
"expires_at": "2026-10-20T00:00:00Z",
"scope": {
"roles": [
"contract-api",
"ai-platform-api",
"ai-worker"
],
"class": "os-pkgs",
"type": "debian",
"os_family": "debian",
"severity": "HIGH",
"fixed_version_must_be_empty": true
},
"findings": [
{
"vulnerability_id": "CVE-2025-69720",
"package_names": [
"libncursesw6",
"libtinfo6",
"ncurses-base",
"ncurses-bin"
],
"installed_version": "6.5+20250216-2"
},
{
"vulnerability_id": "CVE-2026-16742",
"package_names": [
"libsystemd0",
"libudev1"
],
"installed_version": "257.13-1~deb13u1"
},
{
"vulnerability_id": "CVE-2026-54369",
"package_names": [
"libacl1"
],
"installed_version": "2.3.2-2+b1"
},
{
"vulnerability_id": "CVE-2026-76642",
"package_names": [
"bsdutils",
"libblkid1",
"liblastlog2-2",
"libmount1",
"libsmartcols1",
"libuuid1",
"login",
"mount",
"util-linux"
],
"installed_version": "1:2.41.5-0+deb13u1"
},
{
"vulnerability_id": "CVE-2026-78408",
"package_names": [
"bsdutils",
"libblkid1",
"liblastlog2-2",
"libmount1",
"libsmartcols1",
"libuuid1",
"login",
"mount",
"util-linux"
],
"installed_version": "1:2.41.5-0+deb13u1"
},
{
"vulnerability_id": "CVE-2026-78409",
"package_names": [
"bsdutils",
"libblkid1",
"liblastlog2-2",
"libmount1",
"libsmartcols1",
"libuuid1",
"login",
"mount",
"util-linux"
],
"installed_version": "1:2.41.5-0+deb13u1"
},
{
"vulnerability_id": "CVE-2026-78410",
"package_names": [
"bsdutils",
"libblkid1",
"liblastlog2-2",
"libmount1",
"libsmartcols1",
"libuuid1",
"login",
"mount",
"util-linux"
],
"installed_version": "1:2.41.5-0+deb13u1"
},
{
"vulnerability_id": "CVE-2026-9538",
"package_names": [
"perl-base"
],
"installed_version": "5.40.1-6+deb13u1"
}
]
}
],
"fail_closed_on": [
"database_download_failure",
"database_metadata_missing",
"database_expired",
"database_too_old",
"scanner_execution_failure",
"scan_output_missing",
"json_parse_failure",
"schema_validation_failure",
"waiver_expired",
"waiver_scope_mismatch",
"waiver_finding_mismatch"
],
"format": "json",
"ignore_unfixed": false,
"scanner": "trivy",
"scanners": [
"vuln"
],
"thresholds": {
"CRITICAL": 0,
"HIGH": 0
}
},
"unsupported_tools": {
"skopeo": "not_required_and_must_not_be_used_by_this_pipeline"
}
}
@@ -0,0 +1,977 @@
#!/usr/bin/env python3
"""Validate a Linux build bundle and emit its canonical installable manifest.
The assembler copies nothing and trusts no receipt claim without re-reading the
referenced bytes. It refuses missing/extra roles, duplicate identities or
paths, digest drift, traversal, symlinks, stale scanner metadata, and any
non-passing vulnerability report.
"""
from __future__ import annotations
import argparse
import errno
import hashlib
import json
import os
import re
import stat
import sys
from pathlib import Path
from typing import Any
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from linux_release_common import ( # noqa: E402
COMMAND_TIMEOUTS_SECONDS,
COMPOSE_GATE_POLICY,
CONTROLLED_SUBPROCESS_ENV,
INSTALLABLE_MANIFEST_SCHEMA,
LOCAL_DOCKER_HOST,
PINNED_EXECUTABLES,
REQUIRED_IMAGE_SPECS,
REQUIRED_ROLES,
REQUIRED_TOOL_VERSIONS,
TARGET,
TRIVY_DB_REPOSITORY_TRANSPORT,
ReleaseValidationError,
canonical_json_bytes,
ensure_regular_file_under,
file_record,
inspect_docker_archive,
inspect_oci_archive,
load_json_file,
load_policy,
load_source_manifest,
parse_rfc3339,
validate_receipt_payload_digest,
validate_compose_gate_report,
validate_candidate_checkout,
validate_manifest_commit_tree,
validate_relative_path,
validate_spdx,
validate_tag_binding,
validate_trivy_db_metadata,
validate_trivy_scan,
verify_file_record,
verify_source_archive,
synthetic_compose_gate_env_bytes,
with_payload_digest,
)
RELEASE_CONTROL_PATHS = {
"runner": "yuqei-infra/scripts/run-linux-release-build.py",
"common": "yuqei-infra/scripts/linux_release_common.py",
"policy": "yuqei-infra/release/linux-release-security-policy.v1.json",
}
def _mapping(value: Any, label: str) -> dict[str, Any]:
if not isinstance(value, dict):
raise ReleaseValidationError(f"{label} must be a JSON object")
return value
def _list(value: Any, label: str) -> list[Any]:
if not isinstance(value, list):
raise ReleaseValidationError(f"{label} must be a JSON array")
return value
def _record_path(record: Any, label: str) -> str:
value = _mapping(record, label).get("path")
if not isinstance(value, str):
raise ReleaseValidationError(f"{label}.path must be a string")
return value
def _compare_summary(record: dict[str, Any], summary: dict[str, Any], *, label: str) -> None:
drift = {
key: {"receipt": record.get(key), "actual": value}
for key, value in summary.items()
if record.get(key) != value
}
if drift:
raise ReleaseValidationError(f"{label} parsed metadata drift: {json.dumps(drift, sort_keys=True)}")
def _path_relative_to_root(root: Path, path: Path, *, label: str) -> str:
try:
relative = path.resolve(strict=True).relative_to(root.resolve(strict=True))
except (OSError, ValueError) as error:
raise ReleaseValidationError(f"{label} must be within the artifact root") from error
result = relative.as_posix()
ensure_regular_file_under(root, result)
return result
def _reject_unlisted_entries(root: Path, expected_files: set[str], output: Path) -> None:
output_relative = None
try:
output_relative = output.resolve(strict=False).relative_to(root.resolve(strict=True)).as_posix()
except (OSError, ValueError):
pass
actual_files: set[str] = set()
for path in root.rglob("*"):
relative = path.relative_to(root).as_posix()
if path.is_symlink():
raise ReleaseValidationError(f"installable artifact tree contains a symlink: {relative}")
if path.is_file():
actual_files.add(relative)
elif not path.is_dir():
raise ReleaseValidationError(f"installable artifact tree contains a special entry: {relative}")
if output_relative:
actual_files.discard(output_relative)
if actual_files != expected_files:
raise ReleaseValidationError(
"artifact tree differs from the receipt: "
f"missing={sorted(expected_files - actual_files)}, unlisted={sorted(actual_files - expected_files)}"
)
def _write_exclusive_manifest(output: Path, payload: dict[str, Any]) -> None:
"""Durably create an owner-only manifest without following or replacing paths."""
parent = output.parent
if parent.is_symlink() or not parent.is_dir():
raise ReleaseValidationError(
"installable manifest parent must be a real directory"
)
if os.name != "nt":
parent_metadata = parent.stat()
if (
parent_metadata.st_uid != os.geteuid()
or stat.S_IMODE(parent_metadata.st_mode) & 0o022
):
raise ReleaseValidationError(
"installable manifest parent must be owned by the executor and not group/world writable"
)
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0)
flags |= getattr(os, "O_NOFOLLOW", 0)
descriptor = -1
try:
descriptor = os.open(output, flags, 0o600)
opened = os.fstat(descriptor)
if not stat.S_ISREG(opened.st_mode) or opened.st_nlink != 1:
raise ReleaseValidationError(
"installable manifest output must be a single-link regular file"
)
data = canonical_json_bytes(payload)
view = memoryview(data)
while view:
written = os.write(descriptor, view)
if written <= 0:
raise OSError("short write while creating installable manifest")
view = view[written:]
if os.name != "nt":
os.fchmod(descriptor, 0o600)
os.fsync(descriptor)
final = os.fstat(descriptor)
if not stat.S_ISREG(final.st_mode) or final.st_nlink != 1:
raise ReleaseValidationError(
"installable manifest output changed identity while being written"
)
try:
path_identity = os.lstat(output)
except OSError as error:
raise ReleaseValidationError(
"installable manifest output path disappeared while being written"
) from error
if (
not stat.S_ISREG(path_identity.st_mode)
or path_identity.st_nlink != 1
or path_identity.st_dev != final.st_dev
or path_identity.st_ino != final.st_ino
):
raise ReleaseValidationError(
"installable manifest output path changed identity while being written"
)
except FileExistsError as error:
raise ReleaseValidationError(
"installable manifest output must not already exist"
) from error
except OSError as error:
if error.errno == errno.ELOOP:
raise ReleaseValidationError(
"installable manifest output must not be a symbolic link"
) from error
raise
finally:
if descriptor >= 0:
os.close(descriptor)
if os.name != "nt":
directory_descriptor = os.open(
output.parent,
os.O_RDONLY | getattr(os, "O_DIRECTORY", 0),
)
try:
os.fsync(directory_descriptor)
finally:
os.close(directory_descriptor)
def assemble_installable_release(
*,
artifact_root: Path,
receipt_path: Path,
expected_receipt_sha256: str,
repo: Path,
output: Path,
write_output: bool = True,
) -> dict[str, Any]:
if artifact_root.is_symlink() or not artifact_root.is_dir():
raise ReleaseValidationError("artifact root must be a real directory")
receipt_relative = _path_relative_to_root(artifact_root, receipt_path, label="build receipt")
receipt_bytes = receipt_path.read_bytes()
if not re.fullmatch(r"[0-9a-f]{64}", expected_receipt_sha256):
raise ReleaseValidationError("expected build receipt raw SHA-256 is invalid")
if hashlib.sha256(receipt_bytes).hexdigest() != expected_receipt_sha256:
raise ReleaseValidationError(
"build receipt does not match the independently supplied raw SHA-256"
)
receipt = validate_receipt_payload_digest(load_json_file(receipt_path, label="build receipt"))
if canonical_json_bytes(receipt) != receipt_bytes:
raise ReleaseValidationError("build receipt is not serialized as canonical JSON")
policy_record = _mapping(receipt.get("policy"), "receipt policy")
policy_path = verify_file_record(artifact_root, policy_record, label="receipt policy")
policy = load_policy(policy_path)
if policy_record.get("schema_version") != policy["schema_version"] or policy_record.get("target") != TARGET:
raise ReleaseValidationError("receipt policy metadata drift")
candidate = _mapping(receipt.get("candidate"), "receipt candidate")
required_candidate = {
"commit",
"tag",
"tag_object_oid",
"tree",
"commit_tree",
"commit_timestamp",
"source_manifest_payload_sha256",
"source_manifest",
"source_archive",
}
if set(candidate) != required_candidate:
raise ReleaseValidationError("receipt candidate fields are missing or unknown")
source_manifest_path = verify_file_record(
artifact_root, candidate["source_manifest"], label="source manifest"
)
source_manifest = load_source_manifest(source_manifest_path, commit=candidate["commit"])
if source_manifest["manifest_payload_sha256"] != candidate["source_manifest_payload_sha256"]:
raise ReleaseValidationError("source manifest payload digest drift in build receipt")
if source_manifest["candidate"]["tree"] != candidate["tree"]:
raise ReleaseValidationError("source manifest tree drift in build receipt")
if source_manifest["candidate"]["commit_timestamp"] != candidate["commit_timestamp"]:
raise ReleaseValidationError("source manifest timestamp drift in build receipt")
source_archive_path = verify_file_record(
artifact_root, candidate["source_archive"], label="source archive"
)
archive_summary = verify_source_archive(source_archive_path, source_manifest)
for key in ("root_prefix", "file_count"):
if candidate["source_archive"].get(key) != archive_summary[key]:
raise ReleaseValidationError(f"source archive {key} drift in build receipt")
commit_tree = validate_manifest_commit_tree(
repo, commit=candidate["commit"], manifest=source_manifest
)
if candidate["commit_tree"] != commit_tree:
raise ReleaseValidationError("candidate commit-tree evidence drift in build receipt")
tag_binding = validate_tag_binding(
repo,
tag=candidate["tag"],
commit=candidate["commit"],
expected_tree=candidate["tree"],
)
if candidate["tag_object_oid"] != tag_binding["tag_object_oid"]:
raise ReleaseValidationError("annotated tag object drift in build receipt")
builder = _mapping(receipt.get("builder"), "receipt builder")
required_builder_fields = {
"os",
"architecture",
"target_platform",
"tools",
"executables",
"controlled_environment",
"docker_host",
"command_timeouts_seconds",
"buildx_builder",
"base_image_transports",
"candidate_checkout",
"release_controls",
"skopeo_used",
}
if set(builder) != required_builder_fields:
raise ReleaseValidationError("receipt builder fields are missing or unknown")
if builder.get("os") != "linux" or builder.get("architecture") != "amd64":
raise ReleaseValidationError("build receipt was not produced by a Linux amd64 builder")
if builder.get("target_platform") != TARGET["platform"]:
raise ReleaseValidationError("build receipt target platform drift")
if builder.get("tools") != REQUIRED_TOOL_VERSIONS:
raise ReleaseValidationError("build receipt tool versions are not the pinned toolchain")
if builder.get("command_timeouts_seconds") != COMMAND_TIMEOUTS_SECONDS:
raise ReleaseValidationError("build receipt external-command timeout contract drift")
if builder.get("controlled_environment") != CONTROLLED_SUBPROCESS_ENV:
raise ReleaseValidationError("build receipt controlled subprocess environment drift")
if builder.get("docker_host") != LOCAL_DOCKER_HOST:
raise ReleaseValidationError("build receipt Docker daemon endpoint drift")
candidate_checkout = _mapping(
builder.get("candidate_checkout"), "receipt candidate checkout"
)
if set(candidate_checkout) != {
"head",
"worktree_clean",
"replace_refs_present",
"status_sha256",
}:
raise ReleaseValidationError(
"receipt candidate checkout fields are missing or unknown"
)
actual_checkout = validate_candidate_checkout(repo, commit=candidate["commit"])
if candidate_checkout != actual_checkout:
raise ReleaseValidationError("receipt candidate checkout evidence drift")
source_records = {
item["path"]: item for item in source_manifest["files"]["items"]
}
release_controls = _mapping(
builder.get("release_controls"), "receipt release controls"
)
if set(release_controls) != set(RELEASE_CONTROL_PATHS):
raise ReleaseValidationError("receipt release control inventory drift")
for name, relative in RELEASE_CONTROL_PATHS.items():
record = _mapping(release_controls[name], f"receipt release control {name}")
if set(record) != {"path", "size_bytes", "sha256", "git_blob_oid"}:
raise ReleaseValidationError(
f"receipt release control fields are missing or unknown for {name}"
)
source_record = source_records.get(relative)
expected_record = (
{
"path": relative,
"size_bytes": source_record["size"],
"sha256": source_record["sha256"],
"git_blob_oid": source_record["git_blob_oid"],
}
if isinstance(source_record, dict)
else None
)
if record != expected_record:
raise ReleaseValidationError(
f"receipt release control source binding drift for {name}"
)
executable_records = _mapping(builder.get("executables"), "receipt executables")
if set(executable_records) != set(PINNED_EXECUTABLES):
raise ReleaseValidationError("build receipt pinned executable inventory drift")
for name, expected_path in PINNED_EXECUTABLES.items():
executable = _mapping(executable_records[name], f"receipt executable {name}")
if (
executable.get("path") != expected_path
or executable.get("owner_uid") != 0
or executable.get("mode") != "0755"
or not isinstance(executable.get("size_bytes"), int)
or isinstance(executable.get("size_bytes"), bool)
or executable["size_bytes"] <= 0
or not isinstance(executable.get("sha256"), str)
or not re.fullmatch(r"[0-9a-f]{64}", executable["sha256"])
):
raise ReleaseValidationError(f"build receipt executable evidence drift for {name}")
buildx_builder = _mapping(builder.get("buildx_builder"), "receipt Buildx builder")
if (
buildx_builder.get("driver") not in policy["builder"]["allowed_drivers"]
or buildx_builder.get("node_status") != "running"
or buildx_builder.get("exporters") != policy["builder"]["required_exporters"]
or len(_list(buildx_builder.get("platforms"), "Buildx builder platforms")) == 0
or policy["builder"]["required_platform"] not in buildx_builder["platforms"]
):
raise ReleaseValidationError("build receipt does not prove the required Buildx builder capabilities")
if buildx_builder.get("buildkit_version") != policy["builder"]["required_buildkit_version"]:
raise ReleaseValidationError("build receipt BuildKit version drift")
for receipt_key, policy_key in (
("driver_image", "required_driver_image"),
("driver_image_digest", "required_driver_image_digest"),
("local_driver_image_id", "required_local_driver_image_id"),
):
if buildx_builder.get(receipt_key) != policy["builder"][policy_key]:
raise ReleaseValidationError(f"build receipt {receipt_key} drift")
if buildx_builder.get("image_transport") != "domestic_registry_mirror":
raise ReleaseValidationError("BuildKit driver image transport is not the pinned domestic mirror")
config_root = _mapping(buildx_builder.get("config_root"), "receipt Buildx config root")
if (
config_root.get("path") != CONTROLLED_SUBPROCESS_ENV["BUILDX_CONFIG"]
or config_root.get("selected_builder_instance")
!= f"instances/{buildx_builder.get('name')}"
or config_root.get("root_owned") is not True
or config_root.get("group_or_other_writable") is not False
or config_root.get("symlinks_allowed") is not False
or not isinstance(config_root.get("entry_count"), int)
or isinstance(config_root.get("entry_count"), bool)
or config_root["entry_count"] < 1
or not isinstance(config_root.get("metadata_sha256"), str)
or not re.fullmatch(r"[0-9a-f]{64}", config_root["metadata_sha256"])
):
raise ReleaseValidationError("build receipt fixed Buildx config evidence drift")
if not isinstance(buildx_builder.get("inspect_sha256"), str) or not re.fullmatch(
r"[0-9a-f]{64}", buildx_builder["inspect_sha256"]
):
raise ReleaseValidationError("build receipt has no Buildx inspection digest")
if not isinstance(buildx_builder.get("container_inspect_sha256"), str) or not re.fullmatch(
r"[0-9a-f]{64}", buildx_builder["container_inspect_sha256"]
):
raise ReleaseValidationError("build receipt has no BuildKit container inspection digest")
transport_records = _list(
builder.get("base_image_transports"), "receipt base-image transports"
)
expected_transports = {
image["base_image"]["transport_reference"]: image["base_image"]["content_digest"]
for image in policy["images"]
}
if len(transport_records) != len(expected_transports):
raise ReleaseValidationError("base-image transport evidence is missing or duplicated")
observed_transports: set[str] = set()
for record in transport_records:
item = _mapping(record, "base-image transport record")
reference = item.get("transport_reference")
if reference in observed_transports or expected_transports.get(reference) != item.get("content_digest"):
raise ReleaseValidationError("base-image transport reference or digest drift")
observed_transports.add(reference)
if (
item.get("transport") != "domestic_registry_mirror"
or item.get("media_type") != "application/vnd.oci.image.manifest.v1+json"
or not isinstance(item.get("inspect_sha256"), str)
or not re.fullmatch(r"[0-9a-f]{64}", item["inspect_sha256"])
):
raise ReleaseValidationError("base-image mirror inspection evidence is invalid")
if observed_transports != set(expected_transports):
raise ReleaseValidationError("base-image transport evidence does not cover the policy")
if builder.get("skopeo_used") is not False:
raise ReleaseValidationError("Skopeo is outside the supported release pipeline")
build = _mapping(receipt.get("build"), "receipt build")
if build.get("parallelism") != 1 or build.get("image_order") != list(REQUIRED_ROLES):
raise ReleaseValidationError("four images were not recorded in the required serial order")
started = parse_rfc3339(build.get("started_at"), label="build.started_at")
completed = parse_rfc3339(build.get("completed_at"), label="build.completed_at")
if completed < started:
raise ReleaseValidationError("build completion precedes build start")
if build.get("source_date_epoch") != int(
parse_rfc3339(candidate["commit_timestamp"], label="candidate.commit_timestamp").timestamp()
):
raise ReleaseValidationError("SOURCE_DATE_EPOCH is not bound to the candidate commit timestamp")
compose_gate = _mapping(build.get("compose_gate"), "receipt production Compose gate")
if (
set(compose_gate)
!= {
"synthetic_environment",
"production_secrets_used",
"execution_engine",
"validator",
"environment",
"report",
}
or compose_gate.get("synthetic_environment") is not True
or compose_gate.get("production_secrets_used") is not False
or compose_gate.get("execution_engine")
!= "self_bound_release_runner_builtin_v1"
):
raise ReleaseValidationError(
"production Compose gate fields or self-bound execution engine drifted"
)
compose_validator = _mapping(
compose_gate.get("validator"), "receipt production Compose gate validator"
)
expected_validator = source_records.get(COMPOSE_GATE_POLICY["validator"])
if (
expected_validator is None
or compose_validator.get("source_path") != COMPOSE_GATE_POLICY["validator"]
or compose_validator.get("sha256") != expected_validator["sha256"]
or compose_validator.get("size_bytes") != expected_validator["size"]
):
raise ReleaseValidationError("production Compose gate validator source binding drift")
compose_env_record = _mapping(
compose_gate.get("environment"), "receipt synthetic Compose gate environment"
)
compose_env_path = verify_file_record(
artifact_root,
compose_env_record,
label="synthetic Compose gate environment",
)
expected_compose_env = synthetic_compose_gate_env_bytes()
if compose_env_path.read_bytes() != expected_compose_env:
raise ReleaseValidationError("synthetic Compose gate environment content drift")
compose_report_record = _mapping(
compose_gate.get("report"), "receipt production Compose gate report"
)
compose_report_path = verify_file_record(
artifact_root,
compose_report_record,
label="production Compose gate report",
)
compose_report_bytes = compose_report_path.read_bytes()
compose_report_payload = load_json_file(
compose_report_path, label="production Compose gate report"
)
if canonical_json_bytes(compose_report_payload) != compose_report_bytes:
raise ReleaseValidationError("production Compose gate report is not canonical JSON")
compose_report_summary = validate_compose_gate_report(
compose_report_payload,
policy,
expected_env_sha256=compose_env_record["sha256"],
source_file_sha256={path: item["sha256"] for path, item in source_records.items()},
)
_compare_summary(
compose_report_record,
compose_report_summary,
label="production Compose gate report",
)
security = _mapping(receipt.get("security"), "receipt security")
if security.get("passed") is not True or security.get("fail_closed") is not True:
raise ReleaseValidationError("build receipt security gate did not pass fail closed")
if security.get("thresholds") != policy["vulnerability_scan"]["thresholds"]:
raise ReleaseValidationError("receipt vulnerability threshold drift")
if security.get("exemptions") != policy["vulnerability_scan"]["exemptions"]:
raise ReleaseValidationError("receipt vulnerability waiver policy drift")
claimed_vulnerability_counts = _mapping(
security.get("vulnerability_counts"), "receipt vulnerability counts"
)
claimed_waiver_ids = _list(security.get("waiver_ids"), "receipt waiver IDs")
database_record = _mapping(security.get("database"), "receipt Trivy database")
database_path = verify_file_record(
artifact_root, database_record, label="Trivy database metadata"
)
validated_at = parse_rfc3339(database_record.get("validated_at"), label="database.validated_at")
database_summary = validate_trivy_db_metadata(
load_json_file(database_path, label="Trivy database metadata"),
policy,
at=validated_at,
)
_compare_summary(database_record, database_summary, label="Trivy database")
repository_transport = _mapping(
database_record.get("repository_transport"),
"receipt Trivy database repository transport",
)
if (
repository_transport.get("reference")
!= TRIVY_DB_REPOSITORY_TRANSPORT["reference"]
or repository_transport.get("transport")
!= TRIVY_DB_REPOSITORY_TRANSPORT["transport"]
or repository_transport.get("schema_version")
!= TRIVY_DB_REPOSITORY_TRANSPORT["schema_version"]
or repository_transport.get("media_type")
not in TRIVY_DB_REPOSITORY_TRANSPORT["allowed_media_types"]
or repository_transport.get("stable_during_download") is not True
):
raise ReleaseValidationError("Trivy database repository transport evidence drift")
resolved_digest = repository_transport.get("resolved_digest")
if not isinstance(resolved_digest, str) or not re.fullmatch(
r"sha256:[0-9a-f]{64}", resolved_digest
):
raise ReleaseValidationError("Trivy database repository resolved digest is invalid")
for field in (
"before_download_inspect_sha256",
"after_download_inspect_sha256",
):
value = repository_transport.get(field)
if not isinstance(value, str) or not re.fullmatch(r"[0-9a-f]{64}", value):
raise ReleaseValidationError(
f"Trivy database repository {field} evidence is invalid"
)
if not (started <= validated_at <= completed):
raise ReleaseValidationError("Trivy database was not validated during the recorded build")
images = _list(receipt.get("images"), "receipt images")
roles = [image.get("role") if isinstance(image, dict) else None for image in images]
if len(roles) != len(set(roles)):
raise ReleaseValidationError("duplicate image role in build receipt")
if roles != list(REQUIRED_ROLES):
missing = sorted(set(REQUIRED_ROLES) - set(roles))
extra = sorted(str(role) for role in set(roles) - set(REQUIRED_ROLES))
raise ReleaseValidationError(f"image roles are incomplete or unordered: missing={missing}, extra={extra}")
tracked_paths: dict[str, str] = {receipt_relative.casefold(): receipt_relative}
expected_files: set[str] = {receipt_relative}
manifest_images: list[dict[str, Any]] = []
oci_manifest_digests: set[str] = set()
aggregate_vulnerability_counts = {
bucket: {severity: 0 for severity in policy["vulnerability_scan"]["thresholds"]}
for bucket in ("total", "exempted", "blocking")
}
applied_waiver_ids: set[str] = set()
def track(record: dict[str, Any], label: str) -> None:
path = _record_path(record, label)
validate_relative_path(path, label=f"{label}.path")
folded = path.casefold()
if folded in tracked_paths:
raise ReleaseValidationError(
f"duplicate or case-colliding artifact path: {path} and {tracked_paths[folded]}"
)
tracked_paths[folded] = path
expected_files.add(path)
track(policy_record, "policy")
track(_mapping(candidate["source_manifest"], "source manifest"), "source manifest")
track(_mapping(candidate["source_archive"], "source archive"), "source archive")
track(database_record, "Trivy database metadata")
track(compose_env_record, "synthetic Compose gate environment")
track(compose_report_record, "production Compose gate report")
if compose_env_record["path"] != "validation/synthetic-compose-gate.env":
raise ReleaseValidationError("synthetic Compose gate environment path drift")
if compose_report_record["path"] != "validation/production-compose-gate.json":
raise ReleaseValidationError("production Compose gate report path drift")
for index, (raw_image, expected) in enumerate(zip(images, REQUIRED_IMAGE_SPECS, strict=True), 1):
image = _mapping(raw_image, f"receipt image {index}")
if image.get("sequence") != index:
raise ReleaseValidationError(f"image sequence drift for {expected['role']}")
if image.get("role") != expected["role"] or image.get("image_name") != expected["name"]:
raise ReleaseValidationError(f"image identity drift for sequence {index}")
if image.get("environment_key") != expected["environment_key"]:
raise ReleaseValidationError(f"deployment environment key drift for {expected['role']}")
if image.get("dockerfile") != expected["dockerfile"]:
raise ReleaseValidationError(f"Dockerfile drift for {expected['role']}")
if image.get("base_image") != expected["base_image"]:
raise ReleaseValidationError(f"base-image transport/digest drift for {expected['role']}")
if image.get("platform") != TARGET["platform"]:
raise ReleaseValidationError(f"platform drift for {expected['role']}")
expected_ref = f"{expected['name']}:{candidate['commit']}"
if image.get("image_ref") != expected_ref:
raise ReleaseValidationError(f"image reference is not commit-bound for {expected['role']}")
oci_record = _mapping(image.get("oci"), f"{expected['role']} OCI record")
docker_record = _mapping(
image.get("docker_archive"), f"{expected['role']} Docker archive record"
)
sbom_record = _mapping(image.get("sbom"), f"{expected['role']} SBOM record")
scan_record = _mapping(image.get("scan"), f"{expected['role']} scan record")
for record, kind, required_prefix in (
(oci_record, "OCI", "images/"),
(docker_record, "Docker archive", "images-docker/"),
(sbom_record, "SBOM", "sbom/"),
(scan_record, "scan", "scans/"),
):
track(record, f"{expected['role']} {kind}")
if not _record_path(record, f"{expected['role']} {kind}").startswith(required_prefix):
raise ReleaseValidationError(f"{expected['role']} {kind} path is outside {required_prefix}")
oci_path = verify_file_record(artifact_root, oci_record, label=f"{expected['role']} OCI")
oci_summary = inspect_oci_archive(oci_path)
_compare_summary(oci_record, oci_summary, label=f"{expected['role']} OCI")
if oci_summary["revision"] != candidate["commit"] or oci_summary["version"] != candidate["tag"]:
raise ReleaseValidationError(f"OCI candidate labels drift for {expected['role']}")
if (
oci_summary["release_role"] != expected["role"]
or oci_summary["title"] != expected["name"]
):
raise ReleaseValidationError(f"OCI role labels drift for {expected['role']}")
manifest_digest = oci_summary["manifest_digest"]
if manifest_digest in oci_manifest_digests:
raise ReleaseValidationError(f"duplicate OCI manifest digest across image roles: {manifest_digest}")
oci_manifest_digests.add(manifest_digest)
docker_path = verify_file_record(
artifact_root, docker_record, label=f"{expected['role']} Docker archive"
)
docker_summary = inspect_docker_archive(
docker_path, expected_repo_tag=expected_ref
)
_compare_summary(
docker_record, docker_summary, label=f"{expected['role']} Docker archive"
)
if docker_summary["config_digest"] != oci_summary["config_digest"]:
raise ReleaseValidationError(
f"OCI/Docker archive image identity drift for {expected['role']}"
)
if (
docker_summary["revision"] != candidate["commit"]
or docker_summary["version"] != candidate["tag"]
or docker_summary["release_role"] != expected["role"]
or docker_summary["title"] != expected["name"]
):
raise ReleaseValidationError(f"Docker archive role labels drift for {expected['role']}")
sbom_path = verify_file_record(artifact_root, sbom_record, label=f"{expected['role']} SBOM")
sbom_summary = validate_spdx(load_json_file(sbom_path, label=f"{expected['role']} SBOM"), policy)
_compare_summary(sbom_record, sbom_summary, label=f"{expected['role']} SBOM")
scan_path = verify_file_record(artifact_root, scan_record, label=f"{expected['role']} scan")
scan_evaluated_at = parse_rfc3339(
scan_record.get("waiver_evaluated_at"),
label=f"{expected['role']} scan waiver_evaluated_at",
)
if not (started <= scan_evaluated_at <= completed):
raise ReleaseValidationError(
f"{expected['role']} vulnerability waiver was not evaluated during the build"
)
scan_summary = validate_trivy_scan(
load_json_file(scan_path, label=f"{expected['role']} Trivy scan"),
policy,
expected_config_digest=oci_summary["config_digest"],
expected_artifact_basename=docker_path.name,
role=expected["role"],
at=scan_evaluated_at,
)
_compare_summary(scan_record, scan_summary, label=f"{expected['role']} scan")
if scan_record.get("scanner") != "trivy" or scan_record.get("scanner_version") != REQUIRED_TOOL_VERSIONS["trivy"]:
raise ReleaseValidationError(f"scanner identity drift for {expected['role']}")
if scan_record.get("passed") is not True:
raise ReleaseValidationError(f"scan is not passing for {expected['role']}")
for bucket in aggregate_vulnerability_counts:
for severity in aggregate_vulnerability_counts[bucket]:
aggregate_vulnerability_counts[bucket][severity] += scan_summary["counts"][
bucket
][severity]
applied_waiver_ids.update(scan_summary["waiver_ids"])
manifest_images.append(
{
"role": expected["role"],
"image_name": expected["name"],
"environment_key": expected["environment_key"],
"image_ref": expected_ref,
"platform": TARGET["platform"],
"manifest_digest": manifest_digest,
"config_digest": oci_summary["config_digest"],
"registry_reference": f"{expected['name']}@{manifest_digest}",
"offline_image_id": oci_summary["config_digest"],
"oci": {key: oci_record[key] for key in ("path", "sha256", "size_bytes")},
"docker_archive": {
key: docker_record[key] for key in ("path", "sha256", "size_bytes")
},
"sbom": {key: sbom_record[key] for key in ("path", "sha256", "size_bytes")},
"scan": {key: scan_record[key] for key in ("path", "sha256", "size_bytes")},
}
)
if claimed_vulnerability_counts != aggregate_vulnerability_counts:
raise ReleaseValidationError("receipt aggregate vulnerability counts drift")
if claimed_waiver_ids != sorted(applied_waiver_ids):
raise ReleaseValidationError("receipt applied waiver ID inventory drift")
if output.parent.resolve(strict=True) != artifact_root.resolve(strict=True):
raise ReleaseValidationError("installable manifest must be written directly in the artifact root")
_reject_unlisted_entries(artifact_root, expected_files, output)
indexed_records = [
{
"path": path,
"sha256": sha_record["sha256"],
"size_bytes": sha_record["size_bytes"],
}
for sha_record in (
policy_record,
candidate["source_manifest"],
candidate["source_archive"],
database_record,
compose_env_record,
compose_report_record,
*(
artifact
for image in images
for artifact in (
image["oci"],
image["docker_archive"],
image["sbom"],
image["scan"],
)
),
)
for path in [sha_record["path"]]
]
indexed_records.append(file_record(receipt_path, receipt_relative))
indexed_records.sort(key=lambda item: item["path"].encode("utf-8"))
installable_without_digest = {
"schema_version": INSTALLABLE_MANIFEST_SCHEMA,
"candidate": {
"commit": candidate["commit"],
"tag": candidate["tag"],
"tag_object_oid": candidate["tag_object_oid"],
"tree": candidate["tree"],
"commit_tree": candidate["commit_tree"],
"source_manifest_payload_sha256": candidate["source_manifest_payload_sha256"],
"checkout": candidate_checkout,
},
"target": TARGET,
"policy": {
"path": policy_record["path"],
"schema_version": policy["schema_version"],
"sha256": policy_record["sha256"],
"size_bytes": policy_record["size_bytes"],
"target": TARGET,
},
"build_receipt": {
**file_record(receipt_path, receipt_relative),
"payload_sha256": receipt["receipt_payload_sha256"],
},
"security_gate": {
"passed": True,
"fail_closed": True,
"thresholds": policy["vulnerability_scan"]["thresholds"],
"exemptions": policy["vulnerability_scan"]["exemptions"],
"vulnerability_counts": aggregate_vulnerability_counts,
"waiver_ids": sorted(applied_waiver_ids),
"trivy_database": {
**database_summary,
"repository_transport": repository_transport,
},
},
"execution_contract": {
"command_timeouts_seconds": COMMAND_TIMEOUTS_SECONDS,
"controlled_environment": CONTROLLED_SUBPROCESS_ENV,
"docker_host": LOCAL_DOCKER_HOST,
"pinned_executables": PINNED_EXECUTABLES,
"release_controls": release_controls,
},
"compose_gate": {
"passed": True,
"synthetic_environment": True,
"production_secrets_used": False,
"execution_engine": "self_bound_release_runner_builtin_v1",
"validator": compose_validator,
"environment": {
key: compose_env_record[key] for key in ("path", "sha256", "size_bytes")
},
"report": {
key: compose_report_record[key]
for key in ("path", "sha256", "size_bytes")
},
"case_count": compose_report_summary["case_count"],
"compose_inputs": compose_report_summary["compose_inputs"],
},
"images": manifest_images,
"docker_import_plan": {
"schema_version": "yuqei.docker-image-import-plan.v1",
"implicit_preloaded_images_allowed": False,
"images": [
{
"sequence": index,
"role": image["role"],
"environment_key": image["environment_key"],
"archive": image["docker_archive"],
"command_argv": [
PINNED_EXECUTABLES["docker"],
"--host",
LOCAL_DOCKER_HOST,
"image",
"load",
"--input",
image["docker_archive"]["path"],
],
"expected_repo_tag": image["image_ref"],
"expected_loaded_image_id": image["config_digest"],
"expected_oci_manifest_digest": image["manifest_digest"],
"offline_compose_override": image["offline_image_id"],
"registry_reference_for_online_release": image["registry_reference"],
"repo_digests_expected_after_load": [],
}
for index, image in enumerate(manifest_images, 1)
],
"consumer_requirement": (
"An installer must verify docker load produced the exact config-based image ID, "
"then use that sha256 image ID as the offline Compose override. The registry "
"reference is online-only because docker load does not restore RepoDigests. "
"This assembler does not import images."
),
},
"files": indexed_records,
"claims_not_made": [
"This manifest does not claim that an installer consumed its Docker import plan.",
"This manifest does not claim installation, deployment, migration, health, UAT, rollback, or public-release approval."
],
}
installable = with_payload_digest(installable_without_digest, "manifest_payload_sha256")
if write_output:
_write_exclusive_manifest(output, installable)
return installable
def validate_installable_release_manifest(
*,
artifact_root: Path,
manifest_path: Path,
expected_receipt_sha256: str,
repo: Path,
) -> dict[str, Any]:
"""Rebuild and byte-compare an existing manifest with the strict assembler."""
manifest_relative = _path_relative_to_root(
artifact_root,
manifest_path,
label="installable release manifest",
)
if Path(manifest_relative).parent != Path("."):
raise ReleaseValidationError(
"installable release manifest must be directly in the artifact root"
)
manifest_bytes = manifest_path.read_bytes()
manifest = load_json_file(manifest_path, label="installable release manifest")
if canonical_json_bytes(manifest) != manifest_bytes:
raise ReleaseValidationError(
"installable release manifest is not serialized as canonical JSON"
)
build_receipt = _mapping(manifest.get("build_receipt"), "installable build_receipt")
if set(build_receipt) != {"path", "sha256", "size_bytes", "payload_sha256"}:
raise ReleaseValidationError("installable build_receipt fields are missing or unknown")
receipt_relative = _record_path(build_receipt, "installable build_receipt")
receipt_path = ensure_regular_file_under(artifact_root, receipt_relative)
expected = assemble_installable_release(
artifact_root=artifact_root,
receipt_path=receipt_path,
expected_receipt_sha256=expected_receipt_sha256,
repo=repo,
output=manifest_path,
write_output=False,
)
if manifest != expected:
raise ReleaseValidationError(
"installable release manifest differs from the strict reconstructed manifest"
)
return manifest
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--artifact-root", type=Path, required=True)
parser.add_argument("--receipt", type=Path, help="defaults to ARTIFACT_ROOT/build-receipt.json")
parser.add_argument(
"--expected-build-receipt-sha256",
required=True,
help="Independently supplied raw SHA-256 of the canonical build receipt.",
)
parser.add_argument("--repo", type=Path, default=Path.cwd(), help="Git repository containing the exact tag")
parser.add_argument("--output", type=Path, help="defaults to ARTIFACT_ROOT/installable-release-manifest.json")
args = parser.parse_args(argv)
root = args.artifact_root.resolve()
receipt = args.receipt.resolve() if args.receipt else root / "build-receipt.json"
output = args.output.resolve() if args.output else root / "installable-release-manifest.json"
try:
manifest = assemble_installable_release(
artifact_root=root,
receipt_path=receipt,
expected_receipt_sha256=args.expected_build_receipt_sha256,
repo=args.repo.resolve(),
output=output,
)
print(
json.dumps(
{
"status": "passed",
"output": str(output),
"commit": manifest["candidate"]["commit"],
"images": len(manifest["images"]),
},
sort_keys=True,
)
)
return 0
except (OSError, ReleaseValidationError) as error:
print(f"installable_release_error: {error}", file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())
@@ -7,7 +7,9 @@ import argparse
import ast
import hashlib
import json
import os
import re
import shutil
import subprocess
import sys
import tomllib
@@ -37,6 +39,33 @@ REQUIREMENT_LOCK_PATHS = (
"yuqei-infra/requirements/contract-api-linux-x86_64-py312.txt",
"yuqei-infra/requirements/ai-runtime-linux-x86_64-py312.txt",
)
_WINDOWS_GIT_EXECUTABLE = (
None
if sys.platform.startswith("linux")
else (
str(Path(discovered).resolve())
if (discovered := shutil.which("git")) is not None
else None
)
)
RELEASE_CONTROL_PATHS = (
"yuqei-infra/scripts/build-release-candidate-manifest.py",
"yuqei-infra/scripts/build-release-worktree-ownership.py",
"yuqei-infra/compose/docker-compose.production.offline.yml",
"yuqei-infra/deploy/preflight-production.py",
"yuqei-infra/deploy/verify-production-compose.py",
"yuqei-infra/deploy/release-package.py",
"yuqei-infra/release/linux-release-security-policy.v1.json",
"yuqei-infra/scripts/linux_release_common.py",
"yuqei-infra/scripts/run-linux-release-build.py",
"yuqei-infra/scripts/build-installable-release.py",
"yuqei-infra/tests/test_production_compose_preflight.py",
"yuqei-infra/tests/test_production_compose_merge_gate.py",
"yuqei-infra/tests/test_release_package_executor.py",
"yuqei-infra/tests/test_linux_release_build_tools.py",
"yuqei-infra/tests/test_installable_release_assembler_hardening.py",
"yuqei-infra/tests/test_release_candidate_manifest_tools.py",
)
CONTRACT_LOCK_PATH = REQUIREMENT_LOCK_PATHS[0]
CONTRACT_DOCKERFILE_PATH = "yuqei-infra/docker/contract-api.Dockerfile"
REQUIRED_PATHS = (
@@ -47,6 +76,7 @@ REQUIRED_PATHS = (
*OPENAPI_PATHS,
*PROMPT_DEFINITION_PATHS,
*REQUIREMENT_LOCK_PATHS,
*RELEASE_CONTROL_PATHS,
BASELINE_PATH,
"yuqei-ai-sdk-python/src/yuqei_sdk/__init__.py",
"yuqei-ai-sdk-python/src/yuqei_sdk/ai_platform.py",
@@ -113,11 +143,49 @@ class TreeEntry:
path: str
def _controlled_git_environment() -> dict[str, str]:
if sys.platform.startswith("linux"):
environment = {
"HOME": "/var/empty",
"LANG": "C.UTF-8",
"LC_ALL": "C.UTF-8",
"PATH": "/usr/bin:/bin",
}
else:
environment = {
key: os.environ[key]
for key in ("SYSTEMROOT", "WINDIR", "TEMP", "TMP")
if key in os.environ
}
if not _WINDOWS_GIT_EXECUTABLE:
raise ManifestError("Git executable is unavailable")
environment["PATH"] = str(Path(_WINDOWS_GIT_EXECUTABLE).parent)
environment.update({"LANG": "C.UTF-8", "LC_ALL": "C.UTF-8"})
environment.update(
{
"GIT_CONFIG_NOSYSTEM": "1",
"GIT_NO_REPLACE_OBJECTS": "1",
"GIT_OPTIONAL_LOCKS": "0",
"GIT_TERMINAL_PROMPT": "0",
}
)
return environment
def _git_command(repo: Path, *args: str) -> list[str]:
git_path = "/usr/bin/git" if sys.platform.startswith("linux") else _WINDOWS_GIT_EXECUTABLE
if not git_path:
raise ManifestError("Git executable is unavailable")
return [str(git_path), "--no-replace-objects", "-C", str(repo), *args]
def _git(repo: Path, *args: str, binary: bool = False) -> bytes | str:
completed = subprocess.run(
["git", "-C", str(repo), *args],
_git_command(repo, *args),
check=False,
capture_output=True,
env=_controlled_git_environment(),
timeout=30,
)
if completed.returncode:
message = completed.stderr.decode("utf-8", "replace").strip()
@@ -136,6 +204,11 @@ def _is_within(path: Path, parent: Path) -> bool:
def validate_candidate_identity(repo: Path, commit: str, output: Path) -> str:
if not FULL_COMMIT_RE.fullmatch(commit):
raise ManifestError("--commit must be a full hexadecimal commit object id; names such as HEAD are refused")
replacement_refs = str(
_git(repo, "for-each-ref", "--format=%(refname)", "refs/replace/")
)
if replacement_refs:
raise ManifestError("repository contains forbidden refs/replace entries")
resolved = str(_git(repo, "rev-parse", "--verify", f"{commit}^{{commit}}"))
if resolved != commit:
raise ManifestError(f"commit object id did not resolve exactly: {commit} -> {resolved}")
@@ -174,10 +247,11 @@ def list_commit_tree(repo: Path, commit: str) -> list[TreeEntry]:
def read_commit_blobs(repo: Path, entries: Iterable[TreeEntry]) -> dict[str, bytes]:
process = subprocess.Popen(
["git", "-C", str(repo), "cat-file", "--batch"],
_git_command(repo, "cat-file", "--batch"),
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=_controlled_git_environment(),
)
assert process.stdin is not None and process.stdout is not None and process.stderr is not None
blobs: dict[str, bytes] = {}
@@ -440,6 +514,9 @@ def configuration_summary(blobs: dict[str, bytes]) -> list[dict[str, Any]]:
name.endswith("Dockerfile")
or path.startswith("yuqei-infra/compose/")
or path.startswith("yuqei-infra/nginx/")
or path.startswith("yuqei-infra/deploy/")
or path.startswith("yuqei-infra/release/")
or path in RELEASE_CONTROL_PATHS
or name in {"pyproject.toml", "package.json", "package-lock.json", "alembic.ini", "tsconfig.base.json"}
or path in REQUIREMENT_LOCK_PATHS
):
@@ -455,7 +532,20 @@ def runtime_graph_summary(blobs: dict[str, bytes]) -> list[dict[str, Any]]:
("web_bff_design_system", ("yuqei-contract-platform/apps/contract-web/src/", "yuqei-contract-platform/packages/design-system/src/")),
("openapi", OPENAPI_PATHS),
("migrations", ("yuqei-contract-platform/services/contract-api/migrations/", "yuqei-ai-platform/services/ai-platform-api/migrations/")),
("deployment", ("yuqei-infra/docker/", "yuqei-infra/compose/", "yuqei-infra/nginx/")),
(
"deployment_and_release_controls",
(
"yuqei-infra/docker/",
"yuqei-infra/compose/",
"yuqei-infra/nginx/",
"yuqei-infra/deploy/",
"yuqei-infra/release/",
"yuqei-infra/scripts/linux_release_common.py",
"yuqei-infra/scripts/run-linux-release-build.py",
"yuqei-infra/scripts/build-installable-release.py",
*RELEASE_CONTROL_PATHS,
),
),
)
result = []
for name, prefixes in components:
@@ -78,6 +78,14 @@ RUNTIME_COMPONENTS = {
"yuqei-infra/compose/",
"yuqei-infra/nginx/",
"yuqei-infra/requirements/",
"yuqei-infra/deploy/",
"yuqei-infra/release/",
"yuqei-infra/scripts/linux_release_common.py",
"yuqei-infra/scripts/run-linux-release-build.py",
"yuqei-infra/scripts/build-installable-release.py",
"yuqei-infra/scripts/build-release-candidate-manifest.py",
"yuqei-infra/scripts/build-release-worktree-ownership.py",
"yuqei-infra/tests/test_installable_release_assembler_hardening.py",
),
}
REQUIRED_WORKTREE_ANCHORS = (
@@ -104,7 +112,23 @@ REQUIRED_WORKTREE_ANCHORS = (
"yuqei-infra/docker/contract-web.Dockerfile",
"yuqei-infra/compose/docker-compose.yml",
"yuqei-infra/compose/docker-compose.production.yml",
"yuqei-infra/compose/docker-compose.production.offline.yml",
"yuqei-infra/nginx/templates/default.conf.template",
"yuqei-infra/scripts/build-release-candidate-manifest.py",
"yuqei-infra/scripts/build-release-worktree-ownership.py",
"yuqei-infra/deploy/preflight-production.py",
"yuqei-infra/deploy/verify-production-compose.py",
"yuqei-infra/deploy/release-package.py",
"yuqei-infra/release/linux-release-security-policy.v1.json",
"yuqei-infra/scripts/linux_release_common.py",
"yuqei-infra/scripts/run-linux-release-build.py",
"yuqei-infra/scripts/build-installable-release.py",
"yuqei-infra/tests/test_production_compose_preflight.py",
"yuqei-infra/tests/test_production_compose_merge_gate.py",
"yuqei-infra/tests/test_release_package_executor.py",
"yuqei-infra/tests/test_linux_release_build_tools.py",
"yuqei-infra/tests/test_installable_release_assembler_hardening.py",
"yuqei-infra/tests/test_release_candidate_manifest_tools.py",
)
@@ -354,7 +378,20 @@ def closure_audit(repo: Path, entries: list[dict[str, Any]]) -> dict[str, Any]:
config_paths = [
path
for path in REQUIRED_WORKTREE_ANCHORS
if path.startswith(("yuqei-infra/docker/", "yuqei-infra/compose/", "yuqei-infra/nginx/"))
if path.startswith(
(
"yuqei-infra/docker/",
"yuqei-infra/compose/",
"yuqei-infra/nginx/",
"yuqei-infra/deploy/",
"yuqei-infra/release/",
)
)
or path in {
"yuqei-infra/scripts/linux_release_common.py",
"yuqei-infra/scripts/run-linux-release-build.py",
"yuqei-infra/scripts/build-installable-release.py",
}
]
ownership_blockers = [
item["path"]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,168 @@
from __future__ import annotations
import importlib.util
import errno
import os
import stat
import sys
from pathlib import Path
from typing import Any
import pytest
REPO_ROOT = Path(__file__).resolve().parents[2]
def _load_module(path: Path, name: str) -> Any:
existing = sys.modules.get(name)
if existing is not None:
return existing
spec = importlib.util.spec_from_file_location(name, path)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
assembler = _load_module(
REPO_ROOT / "yuqei-infra/scripts/build-installable-release.py",
"installable_release_assembler_hardening",
)
build_helpers = _load_module(
REPO_ROOT / "yuqei-infra/tests/test_linux_release_build_tools.py",
"installable_release_assembler_fixture_helpers",
)
def test_out_of_band_receipt_sha_mismatch_rejects_without_output(tmp_path: Path) -> None:
bundle = build_helpers._bundle(tmp_path)
output = bundle["root"] / "installable-release-manifest.json"
with pytest.raises(
assembler.ReleaseValidationError,
match="independently supplied raw SHA-256",
):
assembler.assemble_installable_release(
artifact_root=bundle["root"],
receipt_path=bundle["receipt_path"],
expected_receipt_sha256="0" * 64,
repo=bundle["repo"],
output=output,
)
assert not output.exists()
def test_exclusive_writer_refuses_to_overwrite_existing_file(tmp_path: Path) -> None:
output = tmp_path / "installable-release-manifest.json"
output.write_bytes(b"trusted-existing-bytes")
with pytest.raises(
assembler.ReleaseValidationError,
match="must not already exist",
):
assembler._write_exclusive_manifest(output, {"new": "payload"})
assert output.read_bytes() == b"trusted-existing-bytes"
def test_exclusive_writer_refuses_symlink_without_touching_target(
tmp_path: Path,
monkeypatch,
) -> None:
target = tmp_path / "trusted-target.json"
target.write_bytes(b"trusted-target-bytes")
output = tmp_path / "installable-release-manifest.json"
try:
output.symlink_to(target)
except OSError:
original_open = os.open
def simulated_no_follow(path, flags, mode=0o777, *, dir_fd=None):
if Path(path) == output:
assert flags & getattr(os, "O_NOFOLLOW", 0) or os.name == "nt"
raise OSError(errno.ELOOP, "simulated no-follow symlink rejection")
if dir_fd is None:
return original_open(path, flags, mode)
return original_open(path, flags, mode, dir_fd=dir_fd)
monkeypatch.setattr(assembler.os, "open", simulated_no_follow)
with pytest.raises(
assembler.ReleaseValidationError,
match="must not already exist|must not be a symbolic link",
):
assembler._write_exclusive_manifest(output, {"new": "payload"})
if output.exists():
assert output.is_symlink()
assert target.read_bytes() == b"trusted-target-bytes"
def test_exclusive_writer_rejects_link_count_change(tmp_path: Path, monkeypatch) -> None:
output = tmp_path / "installable-release-manifest.json"
original_fstat = os.fstat
def forged_fstat(descriptor: int) -> os.stat_result:
result = original_fstat(descriptor)
values = list(result)
values[3] = 2
return os.stat_result(values)
monkeypatch.setattr(assembler.os, "fstat", forged_fstat)
with pytest.raises(
assembler.ReleaseValidationError,
match="single-link regular file",
):
assembler._write_exclusive_manifest(output, {"new": "payload"})
assert output.exists()
assert output.read_bytes() == b""
def test_exclusive_writer_rejects_output_path_identity_change(
tmp_path: Path,
monkeypatch,
) -> None:
output = tmp_path / "installable-release-manifest.json"
original_lstat = os.lstat
def forged_lstat(path: os.PathLike[str] | str) -> os.stat_result:
result = original_lstat(path)
if Path(path) == output:
values = list(result)
values[1] = int(result.st_ino) + 1
return os.stat_result(values)
return result
monkeypatch.setattr(assembler.os, "lstat", forged_lstat)
with pytest.raises(
assembler.ReleaseValidationError,
match="output path changed identity",
):
assembler._write_exclusive_manifest(output, {"new": "payload"})
def test_exclusive_writer_is_owner_only_and_fsyncs_file_and_directory(
tmp_path: Path,
monkeypatch,
) -> None:
output = tmp_path / "installable-release-manifest.json"
original_fsync = os.fsync
fsync_calls: list[int] = []
def recording_fsync(descriptor: int) -> None:
fsync_calls.append(descriptor)
original_fsync(descriptor)
monkeypatch.setattr(assembler.os, "fsync", recording_fsync)
assembler._write_exclusive_manifest(output, {"schema_version": "test.v1"})
assert output.read_bytes() == assembler.canonical_json_bytes(
{"schema_version": "test.v1"}
)
assert len(fsync_calls) >= (1 if os.name == "nt" else 2)
if os.name != "nt":
assert stat.S_IMODE(output.stat().st_mode) == 0o600
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,362 @@
from __future__ import annotations
import hashlib
import importlib.util
import json
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable
import pytest
REPO_ROOT = Path(__file__).resolve().parents[2]
def _load_script():
path = REPO_ROOT / "yuqei-infra/deploy/verify-production-compose.py"
spec = importlib.util.spec_from_file_location("verify_production_compose", path)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
gate = _load_script()
@dataclass
class GateFixture:
repo: Path
env_file: Path
images: dict[str, str]
base: Path
production: Path
offline: Path
def _image(name: str, digit: str) -> str:
return f"registry.example.test/yuqei/{name}@sha256:{digit * 64}"
def _make_fixture(tmp_path: Path) -> GateFixture:
repo = tmp_path / "repo"
base = repo / gate.BASE_COMPOSE_RELATIVE
production = repo / gate.PRODUCTION_COMPOSE_RELATIVE
offline = repo / gate.OFFLINE_COMPOSE_RELATIVE
base.parent.mkdir(parents=True)
base.write_text("services: {}\n", encoding="utf-8", newline="\n")
production.write_text("services: {}\n", encoding="utf-8", newline="\n")
offline.write_text("services: {}\n", encoding="utf-8", newline="\n")
images = {
"CONTRACT_API_IMAGE": _image("contract-api", "1"),
"CONTRACT_WEB_IMAGE": _image("contract-web", "2"),
"AI_PLATFORM_API_IMAGE": _image("ai-platform-api", "3"),
"AI_WORKER_IMAGE": _image("ai-worker", "4"),
}
env_file = tmp_path / "strict-production.env"
env_file.write_text(
"COMPOSE_PROJECT_NAME=yuqei-prod\n"
+ "".join(f"{key}={value}\n" for key, value in images.items()),
encoding="utf-8",
newline="\n",
)
return GateFixture(repo, env_file, images, base, production, offline)
def _profiles(command: list[str]) -> set[str]:
return {
command[index + 1]
for index, token in enumerate(command[:-1])
if token == "--profile"
}
def _compose_files(command: list[str]) -> list[str]:
return [
command[index + 1]
for index, token in enumerate(command[:-1])
if token == "-f"
]
class FakeComposeRunner:
def __init__(
self,
fixture: GateFixture,
mutate: Callable[[dict[str, Any], bool, set[str], list[str]], None] | None = None,
) -> None:
self.fixture = fixture
self.mutate = mutate
self.calls: list[tuple[list[str], dict[str, Any]]] = []
def __call__(self, command: list[str], **kwargs: Any) -> subprocess.CompletedProcess[str]:
self.calls.append((list(command), kwargs))
profiles = _profiles(command)
offline = str(self.fixture.offline.resolve()) in _compose_files(command)
services: dict[str, dict[str, Any]] = {}
for service_name in gate.PRODUCTION_SERVICES:
required_profile = gate.PROFILE_ONLY_SERVICES.get(service_name)
if required_profile is not None and required_profile not in profiles:
continue
service: dict[str, Any] = {
"image": f"registry.example.test/infra/{service_name}@sha256:{'e' * 64}"
}
image_env = gate.APPLICATION_SERVICE_IMAGE_ENV.get(service_name)
if image_env is not None:
service["image"] = self.fixture.images[image_env]
service["pull_policy"] = "never" if offline else "always"
elif offline:
service["pull_policy"] = "never"
services[service_name] = service
if self.mutate is not None:
self.mutate(services, offline, profiles, command)
stdout = json.dumps({"name": "yuqei-prod", "services": services})
return subprocess.CompletedProcess(command, 0, stdout=stdout, stderr="")
def test_gate_renders_online_and_offline_for_all_profile_combinations(tmp_path: Path) -> None:
fixture = _make_fixture(tmp_path)
runner = FakeComposeRunner(fixture)
report = gate.verify_production_compose(
env_file=fixture.env_file,
repo_root=fixture.repo,
runner=runner,
)
assert report["status"] == "passed"
assert report["application_service_count"] == 9
assert report["production_service_count"] == 16
assert len(report["cases"]) == 8
assert [(case["profile_case"], case["mode"]) for case in report["cases"]] == [
("default", "online"),
("default", "offline"),
("backend-hosted-web", "online"),
("backend-hosted-web", "offline"),
("ops", "online"),
("ops", "offline"),
("backend-hosted-web+ops", "online"),
("backend-hosted-web+ops", "offline"),
]
assert [case["service_count"] for case in report["cases"]] == [
13,
13,
14,
14,
15,
15,
16,
16,
]
assert len(runner.calls) == 8
for command, kwargs in runner.calls:
assert command[:4] == [
gate.DOCKER_CLI,
"--host",
gate.LOCAL_DOCKER_HOST,
"compose",
]
files = _compose_files(command)
assert files[:2] == [str(fixture.base.resolve()), str(fixture.production.resolve())]
if str(fixture.offline.resolve()) in files:
assert files == [
str(fixture.base.resolve()),
str(fixture.production.resolve()),
str(fixture.offline.resolve()),
]
else:
assert files == [str(fixture.base.resolve()), str(fixture.production.resolve())]
assert command[-3:] == ["config", "--format", "json"]
assert all(token not in command for token in ("up", "pull", "build", "down", "run"))
assert kwargs["shell"] is False
assert kwargs["check"] is False
assert kwargs["cwd"] == str(fixture.repo.resolve())
assert kwargs["env"] == {
"HOME": "/var/empty",
"LANG": "C.UTF-8",
"LC_ALL": "C.UTF-8",
}
assert report["env_file_sha256"] == hashlib.sha256(fixture.env_file.read_bytes()).hexdigest()
assert [item["path"] for item in report["compose_inputs"]] == [
gate.BASE_COMPOSE_RELATIVE,
gate.PRODUCTION_COMPOSE_RELATIVE,
gate.OFFLINE_COMPOSE_RELATIVE,
]
assert [item["sha256"] for item in report["compose_inputs"]] == [
hashlib.sha256(fixture.base.read_bytes()).hexdigest(),
hashlib.sha256(fixture.production.read_bytes()).hexdigest(),
hashlib.sha256(fixture.offline.read_bytes()).hexdigest(),
]
def test_hardcoded_latest_image_mutation_fails_closed(tmp_path: Path) -> None:
fixture = _make_fixture(tmp_path)
def mutate(services, _offline, _profiles, _command):
services["contract-api"]["image"] = "registry.example.test/yuqei/contract-api:latest"
runner = FakeComposeRunner(fixture, mutate)
with pytest.raises(gate.ComposeGateError, match="exactly match CONTRACT_API_IMAGE"):
gate.verify_production_compose(
env_file=fixture.env_file,
repo_root=fixture.repo,
runner=runner,
)
assert len(runner.calls) == 1
def test_application_build_mutation_fails_closed(tmp_path: Path) -> None:
fixture = _make_fixture(tmp_path)
def mutate(services, _offline, _profiles, _command):
services["worker"]["build"] = {"context": "../.."}
with pytest.raises(gate.ComposeGateError, match="retains a build definition"):
gate.verify_production_compose(
env_file=fixture.env_file,
repo_root=fixture.repo,
runner=FakeComposeRunner(fixture, mutate),
)
def test_online_pull_policy_mutation_fails_closed(tmp_path: Path) -> None:
fixture = _make_fixture(tmp_path)
def mutate(services, offline, _profiles, _command):
if not offline:
services["ai-platform-api"]["pull_policy"] = "missing"
with pytest.raises(gate.ComposeGateError, match="pull_policy must be always"):
gate.verify_production_compose(
env_file=fixture.env_file,
repo_root=fixture.repo,
runner=FakeComposeRunner(fixture, mutate),
)
def test_offline_pull_policy_and_build_are_checked_for_infrastructure(tmp_path: Path) -> None:
fixture = _make_fixture(tmp_path)
def mutate(services, offline, _profiles, _command):
if offline:
services["edge"]["pull_policy"] = "always"
services["postgres"]["build"] = "."
with pytest.raises(gate.ComposeGateError, match="service edge pull_policy must be never"):
gate.verify_production_compose(
env_file=fixture.env_file,
repo_root=fixture.repo,
runner=FakeComposeRunner(fixture, mutate),
)
def test_both_profiles_require_all_sixteen_services(tmp_path: Path) -> None:
fixture = _make_fixture(tmp_path)
def mutate(services, _offline, profiles, _command):
if profiles == {"backend-hosted-web", "ops"}:
services.pop("minio-backup")
runner = FakeComposeRunner(fixture, mutate)
with pytest.raises(gate.ComposeGateError, match="required services are missing: minio-backup"):
gate.verify_production_compose(
env_file=fixture.env_file,
repo_root=fixture.repo,
runner=runner,
)
assert len(runner.calls) == 7
@pytest.mark.parametrize(
("line", "message"),
[
("COMPOSE_PROJECT_NAME=prod;touch-pwned", "missing or unsafe"),
("CONTRACT_API_IMAGE=registry.example.test/yuqei/contract-api:latest", "immutable"),
],
)
def test_strict_env_rejects_command_injection_and_mutable_images(
tmp_path: Path, line: str, message: str
) -> None:
fixture = _make_fixture(tmp_path)
lines = fixture.env_file.read_text(encoding="utf-8").splitlines()
key = line.split("=", 1)[0]
fixture.env_file.write_text(
"\n".join(line if item.startswith(key + "=") else item for item in lines) + "\n",
encoding="utf-8",
newline="\n",
)
runner = FakeComposeRunner(fixture)
with pytest.raises(gate.ComposeGateError, match=message):
gate.verify_production_compose(
env_file=fixture.env_file,
repo_root=fixture.repo,
runner=runner,
)
assert runner.calls == []
def test_duplicate_env_key_fails_before_runner(tmp_path: Path) -> None:
fixture = _make_fixture(tmp_path)
with fixture.env_file.open("a", encoding="utf-8", newline="\n") as handle:
handle.write("CONTRACT_WEB_IMAGE=" + fixture.images["CONTRACT_WEB_IMAGE"] + "\n")
runner = FakeComposeRunner(fixture)
with pytest.raises(gate.ComposeGateError, match="duplicate strict env key"):
gate.verify_production_compose(
env_file=fixture.env_file,
repo_root=fixture.repo,
runner=runner,
)
assert runner.calls == []
def test_missing_offline_override_fails_before_runner(tmp_path: Path) -> None:
fixture = _make_fixture(tmp_path)
fixture.offline.unlink()
runner = FakeComposeRunner(fixture)
with pytest.raises(gate.ComposeGateError, match="offline Compose override is missing"):
gate.verify_production_compose(
env_file=fixture.env_file,
repo_root=fixture.repo,
runner=runner,
)
assert runner.calls == []
def test_runner_failure_stops_at_first_config(tmp_path: Path) -> None:
fixture = _make_fixture(tmp_path)
calls: list[list[str]] = []
def runner(command, **_kwargs):
calls.append(command)
return subprocess.CompletedProcess(command, 23, stdout="", stderr="invalid merge")
with pytest.raises(gate.ComposeGateError, match="exit code 23"):
gate.verify_production_compose(
env_file=fixture.env_file,
repo_root=fixture.repo,
runner=runner,
)
assert len(calls) == 1
def test_unexpected_service_cannot_bypass_offline_policy_gate(tmp_path: Path) -> None:
fixture = _make_fixture(tmp_path)
def mutate(services, _offline, _profiles, _command):
services["unreviewed-sidecar"] = {"image": "example.invalid/sidecar:latest"}
with pytest.raises(gate.ComposeGateError, match="unexpected production services"):
gate.verify_production_compose(
env_file=fixture.env_file,
repo_root=fixture.repo,
runner=FakeComposeRunner(fixture, mutate),
)
@@ -0,0 +1,168 @@
from __future__ import annotations
import copy
import importlib.util
import sys
from pathlib import Path
from types import ModuleType
from typing import Any
import yaml
REPO_ROOT = Path(__file__).resolve().parents[2]
COMPOSE_DIR = REPO_ROOT / "yuqei-infra" / "compose"
BASE_COMPOSE = COMPOSE_DIR / "docker-compose.yml"
PRODUCTION_COMPOSE = COMPOSE_DIR / "docker-compose.production.yml"
OFFLINE_COMPOSE = COMPOSE_DIR / "docker-compose.production.offline.yml"
ENV_EXAMPLE = COMPOSE_DIR / ".env.production.example"
APPLICATION_SERVICES = {
"contract-api-migrate",
"contract-api",
"contract-review-worker",
"contract-playbook-governance-worker",
"ai-platform-api-migrate",
"ai-platform-prompt-assets-import",
"ai-platform-api",
"worker",
"contract-web",
}
PRODUCTION_IMAGE_SERVICES = APPLICATION_SERVICES | {
"postgres",
"postgres-init-databases",
"redis",
"minio",
"edge",
"contract-files-backup",
"minio-backup",
}
PROFILE_SETS = (
frozenset(),
frozenset({"backend-hosted-web"}),
frozenset({"ops"}),
frozenset({"backend-hosted-web", "ops"}),
)
RESET_MARKER = "__compose_reset_marker__"
def _load_script(name: str, relative_path: str) -> ModuleType:
path = REPO_ROOT / relative_path
spec = importlib.util.spec_from_file_location(name, path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
preflight = _load_script(
"yuqei_production_compose_preflight",
"yuqei-infra/deploy/preflight-production.py",
)
class _ComposeLoader(yaml.SafeLoader):
pass
def _construct_reset(_loader: _ComposeLoader, _node: yaml.Node) -> str:
return RESET_MARKER
_ComposeLoader.add_constructor("!reset", _construct_reset)
def _load_compose(path: Path) -> dict[str, Any]:
document = yaml.load(path.read_text(encoding="utf-8"), Loader=_ComposeLoader)
assert isinstance(document, dict)
return document
def _merge_mapping(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
merged = copy.deepcopy(base)
for key, value in override.items():
if value == RESET_MARKER:
merged.pop(key, None)
elif isinstance(value, dict) and isinstance(merged.get(key), dict):
merged[key] = _merge_mapping(merged[key], value)
else:
merged[key] = copy.deepcopy(value)
return merged
def _selected_services(model: dict[str, Any], profiles: frozenset[str]) -> dict[str, Any]:
selected: dict[str, Any] = {}
for name, service in model["services"].items():
service_profiles = set(service.get("profiles", []))
if not service_profiles or service_profiles & profiles:
selected[name] = service
return selected
def _production_model() -> dict[str, Any]:
return _merge_mapping(_load_compose(BASE_COMPOSE), _load_compose(PRODUCTION_COMPOSE))
def test_production_merge_removes_all_inherited_application_builds() -> None:
base = _load_compose(BASE_COMPOSE)
assert {
name for name, service in base["services"].items() if "build" in service
} == APPLICATION_SERVICES
production = _production_model()
for profiles in PROFILE_SETS:
selected = _selected_services(production, profiles)
assert all("build" not in service for service in selected.values())
for name in APPLICATION_SERVICES:
service = production["services"][name]
assert "build" not in service
assert service["image"]
assert service["pull_policy"] == "always"
def test_offline_merge_disables_pulls_for_every_production_profile() -> None:
offline_override = _load_compose(OFFLINE_COMPOSE)
assert set(offline_override["services"]) == PRODUCTION_IMAGE_SERVICES
offline = _merge_mapping(_production_model(), offline_override)
assert set(offline["services"]) == PRODUCTION_IMAGE_SERVICES
for profiles in PROFILE_SETS:
selected = _selected_services(offline, profiles)
assert all(service["pull_policy"] == "never" for service in selected.values())
assert all("build" not in service for service in selected.values())
def test_contract_web_image_is_required_and_must_be_immutable() -> None:
values = preflight.parse_env_file(ENV_EXAMPLE)
assert "CONTRACT_WEB_IMAGE" in values
values["CONTRACT_WEB_IMAGE"] = ""
checks = {check.key: check for check in preflight.validate_environment(values)}
assert checks["required_values"].status == "fail"
assert "CONTRACT_WEB_IMAGE" in checks["required_values"].message
digest = "sha256:" + "d" * 64
for key in (
"CONTRACT_API_IMAGE",
"CONTRACT_WEB_IMAGE",
"AI_PLATFORM_API_IMAGE",
"AI_WORKER_IMAGE",
"POSTGRES_IMAGE",
"POSTGRES_CLIENT_IMAGE",
"REDIS_IMAGE",
"MINIO_IMAGE",
"MINIO_MC_IMAGE",
"NGINX_IMAGE",
"BACKUP_UTILITY_IMAGE",
):
values[key] = f"registry.internal/{key.lower()}@{digest}"
checks = {check.key: check for check in preflight.validate_environment(values)}
assert checks["immutable_images"].status == "pass"
values["CONTRACT_WEB_IMAGE"] = "registry.internal/contract-web:latest"
checks = {check.key: check for check in preflight.validate_environment(values)}
assert checks["immutable_images"].status == "fail"
assert "CONTRACT_WEB_IMAGE" in checks["immutable_images"].message
@@ -58,6 +58,7 @@ def production_values() -> dict[str, str]:
replacements = {
"RELEASE_ID": "v2.0.0-rc.1",
"CONTRACT_API_IMAGE": f"registry.internal/contract-api@sha256:{'1' * 64}",
"CONTRACT_WEB_IMAGE": f"registry.internal/contract-web@sha256:{'c' * 64}",
"AI_PLATFORM_API_IMAGE": f"registry.internal/ai-platform-api@sha256:{'2' * 64}",
"AI_PLATFORM_RELEASE_IMAGE_DIGEST": f"sha256:{'2' * 64}",
"CONTRACT_API_PUBLIC_POLICY_RELEASE_DIGEST": f"sha256:{'1' * 64}",
@@ -305,6 +305,13 @@ def test_worktree_classifier_has_explicit_release_ownership(path: str, category:
def test_manifest_is_deterministic_and_summarizes_complete_committed_graph(tmp_path: Path) -> None:
assembler_hardening_test = (
"yuqei-infra/tests/test_installable_release_assembler_hardening.py"
)
assert assembler_hardening_test in manifest.RELEASE_CONTROL_PATHS
assert assembler_hardening_test in ownership.REQUIRED_WORKTREE_ANCHORS
assert assembler_hardening_test in ownership.RUNTIME_COMPONENTS["deployment"]
assert ownership.classify_path(assembler_hardening_test)[0] == "release_test_include"
pinned_bases = {
"yuqei-infra/docker/contract-api.Dockerfile": (
"ARG PYTHON_BASE_IMAGE=python:3.12-slim@sha256:44ff437bba879d4941b710a369a8f19266aea34b29002807f0c487fabc9eec9b",
@@ -329,6 +336,18 @@ def test_manifest_is_deterministic_and_summarizes_complete_committed_graph(tmp_p
assert from_line in lines
assert sum(line.startswith("FROM ") for line in lines) == 1
web_dockerfile = (
REPO_ROOT / "yuqei-infra/docker/contract-web.Dockerfile"
).read_text(encoding="utf-8")
assert "/usr/local/lib/node_modules/npm" in web_dockerfile
assert "USER node" in web_dockerfile.splitlines()
assert (
'CMD ["node", "/app/node_modules/next/dist/bin/next", "start", '
'"/app/yuqei-contract-platform/apps/contract-web", "-p", "3000"]'
in web_dockerfile
)
assert 'CMD ["npm"' not in web_dockerfile
dockerignore_rules = {
line.strip()
for line in (REPO_ROOT / ".dockerignore").read_text(encoding="utf-8").splitlines()
@@ -411,6 +430,16 @@ def test_manifest_is_deterministic_and_summarizes_complete_committed_graph(tmp_p
assert first["summaries"]["runtime_compatibility"]["minimum_glibc"] == "2.28"
assert first["summaries"]["migrations"][0]["heads"] == ["contract-head"]
assert first["summaries"]["migrations"][1]["heads"] == ["ai-head"]
recorded_paths = {item["path"] for item in first["files"]["items"]}
assert set(manifest.RELEASE_CONTROL_PATHS) <= recorded_paths
configuration_paths = {item["path"] for item in first["summaries"]["configuration"]}
assert set(manifest.RELEASE_CONTROL_PATHS) <= configuration_paths
release_component = next(
item
for item in first["summaries"]["runtime_build_graph"]
if item["component"] == "deployment_and_release_controls"
)
assert release_component["file_count"] >= len(manifest.RELEASE_CONTROL_PATHS)
package_record = next(item for item in first["files"]["items"] if item["path"] == "package.json")
package_bytes = (repo / "package.json").read_bytes()
assert package_record["size"] == len(package_bytes)
@@ -431,6 +460,11 @@ def test_manifest_refuses_ambiguous_dirty_missing_and_secret_candidates(tmp_path
without_package = [item for item in entries if item.path != "package.json"]
with pytest.raises(manifest.ManifestError, match="missing_paths"):
manifest.validate_tree_paths(without_package)
without_release_control = [
item for item in entries if item.path != manifest.RELEASE_CONTROL_PATHS[0]
]
with pytest.raises(manifest.ManifestError, match="missing_paths"):
manifest.validate_tree_paths(without_release_control)
_write(repo, "dirty.txt", "not committed\n")
with pytest.raises(manifest.ManifestError, match="working tree is dirty"):
@@ -446,6 +480,65 @@ def test_manifest_refuses_ambiguous_dirty_missing_and_secret_candidates(tmp_path
manifest.build_manifest(repo, secret_commit, external_output)
def test_manifest_rejects_git_replace_refs_before_reading_candidate_objects(
tmp_path: Path,
) -> None:
repo, commit = _candidate_repo(tmp_path)
tree = _git(repo, "rev-parse", f"{commit}^{{tree}}")
replacement = _git(
repo,
"commit-tree",
tree,
"-p",
commit,
"-m",
"forbidden replacement commit",
)
_git(repo, "replace", commit, replacement)
with pytest.raises(manifest.ManifestError, match="refs/replace"):
manifest.build_manifest(repo, commit, tmp_path / "manifest.json")
def test_manifest_git_run_and_batch_reads_disable_replacements_and_ambient_config(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
repo, commit = _candidate_repo(tmp_path)
monkeypatch.setenv("GIT_CONFIG_SYSTEM", str(tmp_path / "attacker-system-config"))
monkeypatch.setenv("GIT_REPLACE_REF_BASE", "refs/attacker-replace/")
observed_runs: list[tuple[list[str], dict[str, object]]] = []
observed_popen: list[tuple[list[str], dict[str, object]]] = []
actual_run = subprocess.run
actual_popen = subprocess.Popen
def capture_run(arguments, **kwargs):
observed_runs.append((list(arguments), dict(kwargs)))
return actual_run(arguments, **kwargs)
def capture_popen(arguments, **kwargs):
observed_popen.append((list(arguments), dict(kwargs)))
return actual_popen(arguments, **kwargs)
monkeypatch.setattr(manifest.subprocess, "run", capture_run)
monkeypatch.setattr(manifest.subprocess, "Popen", capture_popen)
assert manifest._git(repo, "rev-parse", "--verify", f"{commit}^{{commit}}") == commit
entry = manifest.list_commit_tree(repo, commit)[0]
blobs = manifest.read_commit_blobs(repo, [entry])
assert entry.path in blobs
assert observed_runs
assert observed_popen
for arguments, kwargs in [*observed_runs, *observed_popen]:
assert arguments[1] == "--no-replace-objects"
child_env = kwargs["env"]
assert child_env["GIT_CONFIG_NOSYSTEM"] == "1"
assert child_env["GIT_NO_REPLACE_OBJECTS"] == "1"
assert child_env["GIT_OPTIONAL_LOCKS"] == "0"
assert child_env["GIT_TERMINAL_PROMPT"] == "0"
assert "GIT_CONFIG_SYSTEM" not in child_env
assert "GIT_REPLACE_REF_BASE" not in child_env
def test_manifest_distinguishes_secret_domain_source_from_secret_material() -> None:
safe_source_paths = (
"yuqei-ai-platform/services/ai-platform-api/src/yuqei_ai_platform_api/secret_refs.py",
File diff suppressed because it is too large Load Diff