Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/workflows/validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Pull request validation

on:
pull_request:
types: [opened, synchronize, reopened, edited, ready_for_review]
push:
branches: [main]

permissions:
contents: read

jobs:
tests-package:
name: Tests and package (Python ${{ matrix.python }})
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
python: ['3.11', '3.13']
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: ${{ matrix.python }}
- name: Build wheel
run: |
python -m pip install build
python -m build --wheel
- name: Test installed wheel and interfaces
run: python scripts/check_package.py --repository . --wheel-directory dist

release-check:
name: Release check
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.13'
- name: Install validator
run: python -m pip install .
- name: Discover releases and validate additions
run: python -m container_platform.ci --repository . --github-event
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Container Platform defines the contract for an **Online Shop** release composed

The project does not build application code or operate a Kubernetes cluster. Its target workflow turns images published to Docker Hub into a versioned release bundle pinned by digests for review, validation and promotion to the SRE Platform.

> **Status:** Release contract v1 is defined; a digest-pinned baseline candidate and profile inventory are available for review. Offline and opt-in registry metadata checks are implemented; PR gates and integrations remain planned.
> **Status:** Release contract v1 is defined; a digest-pinned baseline candidate and profile inventory are available for review. Offline and opt-in registry metadata checks are implemented; Pull request checks are available; required-check rules are owner-configured. Compose and SRE integrations remain planned.

## Why this platform exists

Expand Down Expand Up @@ -39,7 +39,7 @@ The two paths into the SRE Platform have different roles:
- Docker Hub is the data path: Kubernetes nodes pull the approved images from `dmitryzhuravlev/online-shop-*`.
- Container Platform is the release-control path: it identifies the exact image digests that are approved together as one Online Shop release.

The diagram describes target relationships. The offline validator and opt-in Docker Hub metadata checks are implemented. GitHub Actions gates, Compose integration and SRE handoff remain planned.
The diagram describes target relationships. The offline validator and opt-in Docker Hub metadata checks are implemented. GitHub Actions validation checks are available; required-check configuration remains an owner action. Compose integration and SRE handoff remain planned.

## Responsibilities

Expand Down Expand Up @@ -112,3 +112,7 @@ The committed baseline passes structure and inventory checks but returns exit co
## Optional Docker Hub verification

Add `--registry` to inspect immutable manifest/config bytes and declared platforms after local prerequisites pass. Legacy failures remain visible and keep the baseline unsuccessful. No layers are downloaded, and metadata success grants no approval or deployment authorization. See [transport restrictions, output v2 and limits](docs/registry-verification.md).

## Pull request checks

[GitHub Actions validation](docs/pull-request-validation.md) runs tests/package checks on Python 3.11 and 3.13 and a stable release check. New candidates receive opt-in registry validation; merged release files are immutable. No candidate changes produces an explicit successful result. The historical legacy baseline remains a negative regression fixture. Workflow availability does not configure required merge checks or grant release approval.
62 changes: 62 additions & 0 deletions docs/pull-request-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Pull request validation

The `Pull request validation` workflow runs on every `pull_request` opened, synchronized, reopened, edited or marked ready for review, and on pushes to `main`. There are no path filters: documentation-only changes still produce explicit check results.

## Stable checks and scope

| Check name | Scope |
|---|---|
| `Tests and package (Python 3.11)` | Minimum supported Python; offline/mocked tests and installed-wheel smoke checks |
| `Tests and package (Python 3.13)` | Newer supported Python; same checks |
| `Release check` | Git discovery/immutability and opt-in registry validation of new candidates |

Both matrix entries use GitHub-hosted Ubuntu 24.04 with a 15-minute job timeout. The release job has a 20-minute timeout. This small Python matrix exercises the supported minimum and a newer interpreter without duplicating operating-system jobs. All checkout/setup Actions are pinned to full official commit SHAs with major-version comments. Checkout fetches full history (`fetch-depth: 0`) and does not persist credentials.

The wheel is built, installed into a new temporary venv, and tested there. The helper executes the module and console commands outside the checkout, compares the packaged schema with the canonical JSON, and asserts the committed baseline's **expected negative** offline result: seven passing prerequisite stages, 12 `legacy_ineligible` diagnostics, exit 1. Automated tests use mocked registry exchanges; this suite makes no real registry requests. The baseline remains ineligible and is never granted an exception to make CI pass.

## Candidate paths and cumulative comparison

Candidate locations are direct-child `releases/*.json` files except `*.observations.json`. `examples/`, nested release directories and observation records are excluded. Selection is based on Git filenames, not the ability to parse JSON or read `purpose`: malformed new manifests remain selected for the existing CLI to reject. A synthetic example moved into the candidate location is selected and rejected by its candidate-purpose check.

For pull requests, event `pull_request.base.sha` and `head.sha` are validated as full commit objects; their merge-base defines the cumulative change set. This includes all commits in the PR, rather than only the most recent commit. Releases added to the current base after divergence are also considered when checking path/ID collisions; they are not treated as deletions by an older feature branch. Local result-tree identity checking combines the immutable base release catalog with head additions. It does not attempt to resolve unrelated merge conflicts; GitHub mergeability/review remains separate.

Push events use exact `before` and `after` trees, including multi-commit updates. Missing commits, unavailable merge-bases, all-zero initial/deleted revisions and unsupported events fail explicitly. There is no checkout/fetch or `HEAD~1` fallback inside orchestration. Required inventory objects must already be present locally; the release CLI reports missing objects rather than substituting working-tree content. Full checkout history supplies normal reachable baseline/inventory history, but cannot guarantee arbitrary unpublished references exist.

Diff names and trees use NUL-delimited Git output. Filenames remain data, never shell fragments. Non-UTF-8 Git filenames fail explicitly. Candidate content is read from regular blobs in the selected head revision and passed to the CLI through a temporary fixed-name file. Symlinks/gitlinks are not new candidate files. Summaries identify only counts/results; detailed CLI diagnostics in step logs use safe candidate/component indices, without copying arbitrary release IDs or paths into shell commands or summaries.

## Immutability and identity rules

- Added candidates are validated with the installed `python -m container_platform ... --registry --format json` interface.
- Modification, deletion or rename of a candidate already present in the comparison base fails discovery/immutability before registry access. Moving it into observations, examples or another directory is also forbidden.
- Moving an excluded file into a candidate location is a new candidate and receives normal validation. Changing an observation record alone is not candidate discovery.
- Every new candidate must have a new `releaseId` relative to other candidates in the resulting release catalog. Historical candidates are read for identity only, not rechecked for registry eligibility. If an existing identity cannot be read, uniqueness cannot be established and an addition fails. Malformed new identities are left for CLI diagnostics, not silently excluded.
- With no candidate changes, `Release check` returns **exit 0 / `no_candidate_changes`**, with an explicit small summary. The historical baseline is not inspected against the live registry on unrelated PRs.

These storage rules apply to every previously merged candidate manifest, including historical and not-yet-approved candidates. Immutability does not establish release approval. Corrections require a new file and a new release ID. A candidate newly introduced in the current PR can be edited or renamed repeatedly before merge: only the cumulative final change relative to base is evaluated. A rename of a file already in base remains forbidden.

All new candidates must satisfy both offline and registry rules. CLI exits **1 and 2 fail the release check**, including legacy ineligibility, missing inventory objects, registry outages, rate limits, timeouts and execution failures. There is no `continue-on-error` or blanket allow-failure. Independent traceability/registry diagnostics are retained in the JSON step log. The orchestration result preserves candidate exit codes and gives execution errors precedence when multiple candidates fail. A blocked registry must be investigated/retried after recovery, not treated as eligibility success. Normal registry request/retry/response bounds remain as [documented](registry-verification.md).

## Local execution

After package installation, use explicit full commit SHAs (replace the example labels with actual SHAs):

```text
python -m container_platform.ci --repository . --base BASE_SHA --head HEAD_SHA --pull-request
python -m container_platform.ci --repository . --base BEFORE_SHA --head AFTER_SHA
python -m unittest discover -s tests -v
python -m pip install build
python -m build --wheel
python scripts/check_package.py --repository . --wheel-directory dist
```

Use a fresh `dist` directory containing exactly one project wheel. The package helper makes its own clean temporary venv. New valid candidates cause real registry requests in orchestration; unit tests inject responses or use malformed inputs rejected before networking. `--github-event` is the workflow form and reads `GITHUB_EVENT_NAME` and `GITHUB_EVENT_PATH` directly, without interpolating event strings into shell commands.

Orchestration JSON version 1 includes the overall result/exit, discovery result, new-candidate count, stable diagnostic codes and ordered candidate results containing the existing CLI reports. No candidate change returns 0; immutability/identity or CLI validation failure returns 1; comparison/usage/execution failure returns 2. The summary is limited to tests/package outcome or candidate count, discovery result and validation scope. It contains no large raw reports or local-machine/TLS-investigation details.

## Owner-configured merge protection

This change supplies workflow checks; it does **not** configure required checks or protect merges. The owner should configure a `main` ruleset/branch protection requiring pull-request review and all three stable checks above. Require the PR branch to be current with the base before merging, or introduce an explicitly designed merge-queue workflow later; `merge_group` is not supported here. Review ownership should cover workflows, validator/orchestration, schema, inventory, package helper and tests. Configure owner review requirements separately; no CODEOWNERS identities or rulesets are created automatically by this task.

Only `pull_request` executes PR code, never `pull_request_target`. Jobs use GitHub-hosted unprivileged runners and `contents: read`; there are no registry/cloud credentials, new secrets, write-token/comment jobs, OIDC permissions or privileged workflow handoffs. The workflow tests code supplied by the PR author, including workflow/validator changes: it is **not independent tamper-proof enforcement**. Approval and required owner review/rules remain essential. Fork workflow approval or Actions organization policies may require owner action before jobs run.

Checks establish regression behavior and selected candidate eligibility under implemented policy, not application compatibility, container-release approval or SRE deployment authorization. Compose integration, image publication, promotion and legacy exceptions remain outside this change.
6 changes: 4 additions & 2 deletions docs/release-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ The pull reference is `repository@digest`. Tags are not accepted selectors. For
5. **SRE promotion (planned):** SRE separately authorizes suitability for a specific environment through a reviewed GitOps change. It must record the full immutable Container Platform commit SHA and manifest path, and consume exactly that image set. A moving branch/tag is insufficient. Checking rendered SRE values against the manifest, making that check mandatory and protecting merges remain future implementation; bypasses are not blocked today.
6. **Deployment and runtime:** Argo CD applies environment configuration; Kubernetes pulls from Docker Hub. SRE evaluates rollout health, observability, data/state constraints and operational acceptance.

Evidence must be rerun or explicitly re-evaluated after candidate changes; results for another revision do not automatically apply. The offline rules are executable locally; mandatory PR gates and authorization policy remain future implementation. This contract does not claim protected merges or deployment enforcement.
Evidence must be rerun or explicitly re-evaluated after candidate changes; results for another revision do not automatically apply. The offline rules are executable locally; [PR validation workflows](pull-request-validation.md) are available; owner-required check configuration and authorization policy remain separate actions. This contract does not claim protected merges or deployment enforcement.

## Rollback

Expand All @@ -77,4 +77,6 @@ Draft202012Validator.check_schema(schema)
Draft202012Validator(schema).validate(example)
```

The snippet above checks structure only. Use the [offline CLI](offline-validation.md) for immutable-inventory and traceability evaluation; [Opt-in registry metadata verification](registry-verification.md) is implemented; CI gates remain planned.
The snippet above checks structure only. Use the [offline CLI](offline-validation.md) for immutable-inventory and traceability evaluation; [Opt-in registry metadata verification](registry-verification.md) is implemented; [PR checks](pull-request-validation.md) are available; required merge protection remains an owner action.

Merged manifest files remain immutable even before release approval; corrections use a new file and release ID. The [PR checks](pull-request-validation.md) evaluate cumulative additions and do not grant eligibility to historical legacy fixtures.
4 changes: 3 additions & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ The platform must remain focused on image-release composition and validation. It
- define the promotion and rollback semantics at a repository level;
- define MVP non-goals and trust boundaries.

Artifacts: [architecture](architecture.md), [canonical contract](release-contract.md), [v1 schema](../schemas/release-v1.schema.json), and [non-deployable example](../examples/release-v1.example.json). Offline validation and opt-in registry metadata verification are implemented; PR gates and integrations remain planned.
Artifacts: [architecture](architecture.md), [canonical contract](release-contract.md), [v1 schema](../schemas/release-v1.schema.json), and [non-deployable example](../examples/release-v1.example.json). Offline validation and opt-in registry metadata verification are implemented; PR validation workflows are available; owner-required checks and integrations remain separate work.

### Target outcome

Expand Down Expand Up @@ -87,6 +87,8 @@ Invalid, incomplete, mutable, or unavailable releases cannot be presented as val

**Goal:** Make release validation repeatable and prove that the published images compose into an application.

[Pull request checks](pull-request-validation.md) provide tests/package and new-candidate validation. Required-check settings are owner-configured; Compose integration remains unimplemented, so this milestone is not complete.

### Scope

- add GitHub Actions checks for release-manifest pull requests;
Expand Down
Loading