From 516436794152190a2a8bfb0ee1e86aae5ac60331 Mon Sep 17 00:00:00 2001 From: John McChesney TenEyck Jr <59268465+jmcte@users.noreply.github.com> Date: Wed, 15 Jul 2026 09:05:14 +0100 Subject: [PATCH 1/2] Finalize Public Repository Standard decision record Signed-off-by: John McChesney TenEyck Jr <59268465+jmcte@users.noreply.github.com> --- README.md | 6 +++++- .../ADR-0001-public-repository-standard-v1.md | 15 ++++++++++++++- docs/policy-index.md | 8 ++++++++ docs/public-repository-standard-gap-analysis.md | 16 ++++++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9c938b6..e3c008c 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ For the high-level relationship between `flow`, `bootstrap`, `.github`, individu - [`scripts/flow/inspect_repo_flow.py`](scripts/flow/inspect_repo_flow.py) - read-only-by-default inspector for repo flow metadata; see the [operator guide](docs/flow-inspector.md). - [`docs/autonomous-flow-platform.md`](docs/autonomous-flow-platform.md) - design rationale and rollout plan. - [`docs/omt-global-operating-map.md`](docs/omt-global-operating-map.md) - org-level ownership map. +- [`docs/policy-index.md`](docs/policy-index.md) - current policy authority, release, and implementation evidence. +- [`docs/public-repository-standard-gap-analysis.md`](docs/public-repository-standard-gap-analysis.md) - historical discovery record and links to remaining migration work. ## Controller Loop @@ -81,7 +83,9 @@ paths. Review the proposed writes in the default report before following the ## Release Standard -This repository uses release maturity level `simple`. Level 1 `simple` keeps immutable exact SemVer tags such as `v1.2.3`, then automatically advances the floating compatibility tags `v1.2` and `v1` to the same commit. Level 2 `governed` adds preflight, full validation, explicit publish approval, postpublish verification, and release evidence. +The current immutable policy release is [`v1.0.1`](https://github.com/OMT-Global/flow/releases/tag/v1.0.1). Production consumers must pin an exact SemVer release or immutable commit SHA; `main` and floating compatibility aliases are discovery aids only and are never valid production policy references. + +This repository uses release maturity level `simple`. Level 1 `simple` publishes immutable exact SemVer tags such as `v1.2.3` and may advance floating discovery aliases such as `v1.2` and `v1`. Level 2 `governed` adds preflight, full validation, explicit publish approval, postpublish verification, and release evidence. Cut patch releases from `release/X.Y` branches when you maintain an older minor line. Cut new minor and major releases from `main`. diff --git a/docs/decisions/ADR-0001-public-repository-standard-v1.md b/docs/decisions/ADR-0001-public-repository-standard-v1.md index bd7a7c1..a904bbc 100644 --- a/docs/decisions/ADR-0001-public-repository-standard-v1.md +++ b/docs/decisions/ADR-0001-public-repository-standard-v1.md @@ -1,7 +1,8 @@ # ADR-0001: Make Flow the authority for Public Repository Standard v1 -- Status: Proposed +- Status: Accepted - Date: 2026-07-11 +- Accepted: 2026-07-15 - Decision owners: Flow maintainers - Material notification: [Flow issue #8](https://github.com/OMT-Global/flow/issues/8) and the discovery pull request @@ -64,6 +65,18 @@ Rejected because `project.bootstrap.yaml` must remain the sole editable reposito Implementation is tracked by [#8](https://github.com/OMT-Global/flow/issues/8), [#9](https://github.com/OMT-Global/flow/issues/9), [#10](https://github.com/OMT-Global/flow/issues/10), [#11](https://github.com/OMT-Global/flow/issues/11), [#12](https://github.com/OMT-Global/flow/issues/12), and [#13](https://github.com/OMT-Global/flow/issues/13). +## Implementation evidence + +The decision is implemented and released: + +- [PR #15](https://github.com/OMT-Global/flow/pull/15) established the publisher-neutral policy bundle and schema. +- [PR #16](https://github.com/OMT-Global/flow/pull/16) made work and release transitions executable. +- [PR #17](https://github.com/OMT-Global/flow/pull/17) added contribution-lifecycle, review, and immutable-release contracts. +- [PR #18](https://github.com/OMT-Global/flow/pull/18) defined security response and provenance semantics. +- The immutable [v1.0.1 policy release](https://github.com/OMT-Global/flow/releases/tag/v1.0.1) was published and independently verified under [issue #23](https://github.com/OMT-Global/flow/issues/23). + +Acceptance records the policy decision; it does not claim that every consumer has migrated. Flow's plan-first Bootstrap dogfood remains tracked by [issue #13](https://github.com/OMT-Global/flow/issues/13), and generated governance reconciliation remains tracked by [issue #25](https://github.com/OMT-Global/flow/issues/25). + ## Security and privacy The public policy bundle contains no secrets or private provenance. It defines redaction, hard-stop, and provenance-failure behavior. Physical private-sink details and credentials remain outside repository configuration. diff --git a/docs/policy-index.md b/docs/policy-index.md index 44fa538..58974d3 100644 --- a/docs/policy-index.md +++ b/docs/policy-index.md @@ -2,6 +2,14 @@ Flow publishes the authoritative repository policy vocabulary and work-state semantics. Bootstrap consumes released policy and projects it into managed repositories. +## Current authority + +- Current immutable release: [`v1.0.1`](https://github.com/OMT-Global/flow/releases/tag/v1.0.1) +- Accepted decision: [`ADR-0001`](decisions/ADR-0001-public-repository-standard-v1.md) +- Historical discovery record: [`Public Repository Standard v1 gap analysis`](public-repository-standard-gap-analysis.md) + +The files below are the canonical source for policy semantics on the repository's development branch. Production consumers must resolve the immutable release rather than this branch. + ## Public Repository Standard v1 - Canonical bundle: [`policies/public-repository-standard-v1.json`](../policies/public-repository-standard-v1.json) diff --git a/docs/public-repository-standard-gap-analysis.md b/docs/public-repository-standard-gap-analysis.md index 5c092a5..af2dad3 100644 --- a/docs/public-repository-standard-gap-analysis.md +++ b/docs/public-repository-standard-gap-analysis.md @@ -1,5 +1,21 @@ # Public Repository Standard v1 gap analysis: Flow +> **Historical analysis — superseded for current status.** This document records the repository state reviewed on 2026-07-11. The implemented policy bundle, its immutable release, and the linked follow-up issues are the current evidence; the original findings below are preserved rather than rewritten. + +## Implementation outcome + +As of 2026-07-15, the policy-definition lanes identified here have landed: + +| Lane | Current evidence | +|---|---| +| Vocabulary and policy bundle | [Issue #8](https://github.com/OMT-Global/flow/issues/8), merged by [PR #15](https://github.com/OMT-Global/flow/pull/15) | +| Executable transitions | [Issue #9](https://github.com/OMT-Global/flow/issues/9), merged by [PR #16](https://github.com/OMT-Global/flow/pull/16) | +| Contribution lifecycle and release contract | [Issues #10](https://github.com/OMT-Global/flow/issues/10) and [#12](https://github.com/OMT-Global/flow/issues/12), merged by [PR #17](https://github.com/OMT-Global/flow/pull/17) | +| Security and provenance | [Issue #11](https://github.com/OMT-Global/flow/issues/11), merged by [PR #18](https://github.com/OMT-Global/flow/pull/18) | +| Immutable policy publication | [v1.0.1](https://github.com/OMT-Global/flow/releases/tag/v1.0.1), verified under [issue #23](https://github.com/OMT-Global/flow/issues/23) | + +The current source of truth is the [policy index](policy-index.md), which points to the canonical policy data, schemas, compatibility guidance, and accepted architecture decision. Remaining migration work is intentionally tracked outside this historical snapshot: [#13](https://github.com/OMT-Global/flow/issues/13) for Flow dogfooding, [#25](https://github.com/OMT-Global/flow/issues/25) for generated governance and operator guidance, and [#26](https://github.com/OMT-Global/flow/issues/26) for human-approved stewardship surfaces. This document's retirement is tracked by [#27](https://github.com/OMT-Global/flow/issues/27). + ## Outcome Flow has solid fragments for agent roles, autonomy, issue/PR work states, merge gates, and a governed release train. It does not yet provide the single versioned, publisher-neutral, machine-readable policy model required by Public Repository Standard v1. The safe migration is additive: establish vocabulary and schemas first, test them, publish an immutable policy release, then let Bootstrap consume it. From caf1eae9d8be1a059f9a709dbff6675bc87b9648 Mon Sep 17 00:00:00 2001 From: John McChesney TenEyck Jr <59268465+jmcte@users.noreply.github.com> Date: Wed, 15 Jul 2026 09:12:29 +0100 Subject: [PATCH 2/2] Reconcile repository governance contract Restore executable CI and hook entrypoints, align runner and release guidance, enforce canonical template projection, and prevent cache drift.\n\nCloses #25. Signed-off-by: John McChesney TenEyck Jr <59268465+jmcte@users.noreply.github.com> --- .githooks/pre-commit | 18 +++ .github/actionlint.yaml | 6 + .github/workflows/ci.yml | 43 +++--- .gitignore | 5 + AGENTS.md | 2 +- CONTRIBUTING.md | 4 +- README.md | 11 +- docs/bootstrap/onboarding.md | 33 +++++ github/ISSUE_TEMPLATE/flow-blocker.yml | 14 +- github/ISSUE_TEMPLATE/implementation.yml | 25 +--- github/ISSUE_TEMPLATE/release-train.yml | 68 ++++++++++ github/pull_request_template.md | 38 ++++-- scripts/ci/run-extended-validation.sh | 12 ++ scripts/ci/run-fast-checks.sh | 18 +++ scripts/ci/validate_repository_contract.py | 150 +++++++++++++++++++++ tests/test_repository_contract.py | 39 ++++++ 16 files changed, 424 insertions(+), 62 deletions(-) create mode 100755 .githooks/pre-commit create mode 100644 .github/actionlint.yaml create mode 100644 .gitignore create mode 100644 docs/bootstrap/onboarding.md create mode 100644 github/ISSUE_TEMPLATE/release-train.yml create mode 100755 scripts/ci/run-extended-validation.sh create mode 100755 scripts/ci/run-fast-checks.sh create mode 100755 scripts/ci/validate_repository_contract.py create mode 100644 tests/test_repository_contract.py diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..b6d7219 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,18 @@ +#!/bin/sh +set -eu + +branch="$(git symbolic-ref --quiet --short HEAD || true)" +case "$branch" in + main|master) + echo "Direct commits to $branch are blocked; create a feature branch." >&2 + exit 1 + ;; +esac + +staged="$(git diff --cached --name-only --diff-filter=ACMR)" +if printf '%s\n' "$staged" | grep -Eq '(^|/)\.env($|\.)|(^|/)(__pycache__|\.pytest_cache)(/|$)|\.py[co]$'; then + echo "Refusing to commit runtime environment or Python cache files." >&2 + exit 1 +fi + +bash scripts/ci/run-fast-checks.sh diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 0000000..3cc53a9 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,6 @@ +self-hosted-runner: + labels: + - self-hosted + - linux + - shell-only + - public diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac85cf8..89f1012 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,7 @@ on: push: branches: - main + workflow_dispatch: permissions: contents: read @@ -21,26 +22,30 @@ jobs: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Validate JSON schemas - run: python -m json.tool schemas/issue-contract.schema.json >/dev/null && python -m json.tool schemas/pr-contract.schema.json >/dev/null && python -m json.tool schemas/public-repository-standard-v1.schema.json >/dev/null && python -m json.tool schemas/provenance-manifest-v1.schema.json >/dev/null + - name: Run fast checks + run: bash scripts/ci/run-fast-checks.sh - - name: Validate public repository policy bundle - run: python scripts/flow/validate_policy_bundle.py - - - name: Validate executable transition policy - run: python scripts/flow/validate_transition.py - - - name: Validate security and provenance policy - run: python scripts/flow/validate_provenance.py - - - name: Validate contribution lifecycle policy - run: python scripts/flow/evaluate_contribution.py + workflow-lint: + name: Workflow Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Build and verify policy release offline - run: python scripts/flow/build_policy_release.py --output /tmp/flow-policy-release-ci --version 1.0.0 --verify + - name: Run actionlint with repository runner labels + run: go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.7 - - name: Test public repository policy bundle - run: python -m unittest discover -s tests -v + extended-validation: + name: Extended Validation + if: github.event_name != 'pull_request' + runs-on: + - self-hosted + - linux + - shell-only + - public + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Compile flow inspector - run: python -m py_compile scripts/flow/inspector_core.py scripts/flow/inspect_repo_flow.py + - name: Build and verify release contract + run: bash scripts/ci/run-extended-validation.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac6a333 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +*.py[cod] +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ diff --git a/AGENTS.md b/AGENTS.md index 54003c8..c2f0454 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,7 @@ - Always work on a feature branch. Hooks block commits to `main` and `master`; enable them with `git config core.hooksPath .githooks`. - Stack baseline: Generic polyglot. - CI baseline: fast PR checks stay cheap and shell-safe; extended validation runs on `main`, nightly, or manual dispatch. -- Self-hosted runner policy: shell-safe jobs may use `[self-hosted, synology, shell-only, public]`; anything needing Docker, service containers, browser infra, or `container:` must stay on GitHub-hosted runners. +- Self-hosted runner policy: shell-safe jobs may use `[self-hosted, linux, shell-only, public]`; anything needing Docker, service containers, browser infra, or `container:` must stay on GitHub-hosted runners. - Add or update tests for every interactive, branching, or operator-facing behavior change. - PRs must use the generated pull request template. The required PR gate validates summary, issue linkage, validation evidence, and risk notes. - Never commit real secrets, runtime auth, or machine-local env files. Use templates and GitHub environments instead. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 424a3d2..3676ca2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,6 +7,7 @@ Contributions should start from a GitHub issue that is assigned or explicitly en - Install dependencies for the selected stack before changing code. - Enable repo hooks with `git config core.hooksPath .githooks`; they block direct commits to `main` and catch committed runtime env files. - Use `project.bootstrap.yaml` as the source of truth for governance, CI, environments, and bootstrap-managed guidance files. +- Review `docs/bootstrap/onboarding.md` before the first merge after governance changes. ## Change Expectations @@ -17,7 +18,8 @@ Contributions should start from a GitHub issue that is assigned or explicitly en ## Validation -- Run the relevant local checks before opening a PR. +- Run `bash scripts/ci/run-fast-checks.sh` before opening a PR. +- Run `bash scripts/ci/run-extended-validation.sh` for release, policy-package, or mainline validation changes. - For this bootstrap contract, the required PR check surface is `CI Gate`. - Document any skipped checks in the PR with a concrete reason. diff --git a/README.md b/README.md index e3c008c..961ea3d 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,13 @@ Open PR clearance has priority over starting new implementation work: ## Validation ```sh -python -m json.tool schemas/issue-contract.schema.json >/dev/null -python -m json.tool schemas/pr-contract.schema.json >/dev/null -python -m py_compile scripts/flow/inspector_core.py scripts/flow/inspect_repo_flow.py -python -m unittest discover -s tests -v +bash scripts/ci/run-fast-checks.sh +``` + +For mainline or release-contract validation, including an offline deterministic policy build: + +```sh +bash scripts/ci/run-extended-validation.sh ``` Inspect a repository without mutating GitHub or local assignment state: diff --git a/docs/bootstrap/onboarding.md b/docs/bootstrap/onboarding.md new file mode 100644 index 0000000..7814471 --- /dev/null +++ b/docs/bootstrap/onboarding.md @@ -0,0 +1,33 @@ +# Bootstrap onboarding + +Review this file before the first merge after Bootstrap changes repository governance. + +## Managed and product-owned boundaries + +`project.bootstrap.yaml` is the repository control plane. Its `repo.managedPaths` list identifies Bootstrap-managed guidance and GitHub templates. Product code, policy data, validators, CI scripts, and canonical `github/` inputs remain product-owned unless the manifest says otherwise. + +Run Bootstrap in plan mode and review the managed/product-owned inventory before apply. Flow issue [#13](https://github.com/OMT-Global/flow/issues/13) records the current plan evidence and the external resolver/projection blocker. + +## Review and merge gates + +- Governing work must link a GitHub issue. +- `CI Gate` is the required repository check. +- One non-author approval from `OMT-Codeowners` is required. +- The PR author enables squash auto-merge when checks, review, and conversation gates can converge safely. +- Policy releases follow the exact-pin and independent-review rules in [Policy releases and upgrades](../policy-release-and-upgrades.md). + +## Runner policy + +The manifest's `hybrid-safe` policy maps Flow's shell-safe CI Gate to `[self-hosted, linux, shell-only, public]`. The live job used to verify this contract on 2026-07-15 ran in runner group `linux-public` with those exact labels. Jobs requiring Docker, service containers, browser infrastructure, or a workflow-level `container:` declaration stay on GitHub-hosted runners. + +If the workflow selector, this document, `AGENTS.md`, or live job metadata disagree, stop and reconcile the trust boundary before merging. + +## Local checks + +```sh +git config core.hooksPath .githooks +bash scripts/ci/run-fast-checks.sh +bash scripts/ci/run-extended-validation.sh +``` + +The fast check validates local Markdown targets, canonical-to-projected governance drift, workflow runner labels, release-pin guidance, policy data, and unit tests. Extended validation additionally builds and verifies the deterministic policy release offline. diff --git a/github/ISSUE_TEMPLATE/flow-blocker.yml b/github/ISSUE_TEMPLATE/flow-blocker.yml index 6cc6c6a..e4142e6 100644 --- a/github/ISSUE_TEMPLATE/flow-blocker.yml +++ b/github/ISSUE_TEMPLATE/flow-blocker.yml @@ -1,7 +1,8 @@ name: Flow blocker -about: Record a blocked unit of work with a next actor and unblock target. +description: Record a blocked unit of work with a next actor and unblock target title: "Flow blocker: " -labels: ["state:blocked-infra"] +labels: + - state:blocked-infra body: - type: textarea id: blocked_item @@ -33,7 +34,14 @@ body: id: next_actor attributes: label: Next actor - options: [Pheidon, Apollo, Ares, Daedalus, Hephaestus, Hermes, Human] + options: + - Pheidon + - Apollo + - Ares + - Daedalus + - Hephaestus + - Hermes + - Human validations: required: true - type: textarea diff --git a/github/ISSUE_TEMPLATE/implementation.yml b/github/ISSUE_TEMPLATE/implementation.yml index fb7cc28..c7df5e9 100644 --- a/github/ISSUE_TEMPLATE/implementation.yml +++ b/github/ISSUE_TEMPLATE/implementation.yml @@ -1,12 +1,9 @@ name: Implementation work -about: Durable contract for autonomous or review-gated implementation work. +description: Durable contract for autonomous or review-gated implementation work title: "" -labels: ["state:intake"] +labels: + - state:intake body: - - type: markdown - attributes: - value: | - Use this form for work that should be executable by Pheidon or a worker lane. - type: textarea id: problem attributes: @@ -19,9 +16,6 @@ body: attributes: label: Acceptance criteria description: Concrete conditions that make this done. - placeholder: | - - ... - - ... validations: required: true - type: textarea @@ -29,9 +23,6 @@ body: attributes: label: Validation commands description: Commands or checks the worker should run. - placeholder: | - - npm test - - cargo test --locked validations: required: true - type: dropdown @@ -60,13 +51,3 @@ body: - Human validations: required: true - - type: textarea - id: dependencies - attributes: - label: Dependencies / blockers - description: Known blockers, prerequisites, or related issues/PRs. - - type: textarea - id: out_of_scope - attributes: - label: Out of scope - description: Explicitly excluded work. diff --git a/github/ISSUE_TEMPLATE/release-train.yml b/github/ISSUE_TEMPLATE/release-train.yml new file mode 100644 index 0000000..3d113c6 --- /dev/null +++ b/github/ISSUE_TEMPLATE/release-train.yml @@ -0,0 +1,68 @@ +name: Release Train +description: Governed release train for an OMT-Global repository +title: "Release: vX.Y.Z" +labels: + - release:train + - review:release +body: + - type: input + id: version + attributes: + label: Version + placeholder: v1.2.3 or v1.2.3-rc.1 + validations: + required: true + + - type: dropdown + id: channel + attributes: + label: Channel + options: + - rc + - beta + - stable + - maintenance + validations: + required: true + + - type: input + id: release_branch + attributes: + label: Release branch + placeholder: release/1.2 + + - type: input + id: target_sha + attributes: + label: Target SHA + placeholder: Full commit SHA for the release candidate + + - type: textarea + id: scope + attributes: + label: Scope + description: User-facing changes, fixes, risks, exclusions. + validations: + required: true + + - type: textarea + id: gates + attributes: + label: Gates + value: | + - [ ] Release branch created + - [ ] Scope locked + - [ ] Changelog/release notes prepared + - [ ] Version surfaces updated + - [ ] Preflight passed + - [ ] preflight_run_id recorded: + - [ ] Full validation passed + - [ ] validation_run_id recorded: + - [ ] Exact tag created + - [ ] Publish approval granted + - [ ] Artifacts published + - [ ] GitHub Release created or updated + - [ ] Release evidence uploaded + - [ ] Postpublish verification passed + - [ ] Floating tags/channels promoted if applicable + - [ ] Release issue closed diff --git a/github/pull_request_template.md b/github/pull_request_template.md index fe57cdb..57bb1ba 100644 --- a/github/pull_request_template.md +++ b/github/pull_request_template.md @@ -2,27 +2,41 @@ - -## Linked issue +## Governing Issue -Closes # +Refs # -## Flow contract +## Validation + +- [ ] Relevant local checks passed +- [ ] Required PR checks are expected to satisfy `CI Gate` +- [ ] Skipped checks are explained below + +## Bootstrap Governance + +- [ ] Changes are scoped to the linked issue +- [ ] Contributor or PR guidance changes are reflected in `CONTRIBUTING.md`, `.github/PULL_REQUEST_TEMPLATE.md`, and `docs/bootstrap/onboarding.md` when applicable +- [ ] PR author enabled auto-merge where GitHub allows it, or GitHub plan-limit evidence/unavailable reason is recorded and the fallback merge-readiness policy applies +- [ ] No real secrets, runtime auth, or machine-local env files are committed + +## Flow Contract - Owner lane: - Repair owner: - Autonomy class: - Risk class: -## Validation +## Flow Merge Readiness -Commands/checks run: +- [ ] Every blocker has a next actor and next action +- [ ] No active blocking requested changes remain +- [ ] Non-author approval is present when required +- [ ] PR author enabled auto-merge where GitHub allows it, or recorded why it is unavailable/unsafe -- [ ] +## Merge Automation -## Merge readiness +- [ ] PR author enabled auto-merge with `gh pr merge --auto --squash`, or the reason it is unavailable/unsafe is noted below -- [ ] Linked issue or no-issue rationale is present -- [ ] Required checks pass or are intentionally non-blocking -- [ ] No active blocking requested changes remain -- [ ] Non-author approval is present if required -- [ ] Auto-merge is appropriate when gates pass +## Notes + +- diff --git a/scripts/ci/run-extended-validation.sh b/scripts/ci/run-extended-validation.sh new file mode 100755 index 0000000..08adec3 --- /dev/null +++ b/scripts/ci/run-extended-validation.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +bash scripts/ci/run-fast-checks.sh + +artifact_dir="$(mktemp -d "${TMPDIR:-/tmp}/flow-policy-release.XXXXXX")" +trap 'rm -rf "$artifact_dir"' EXIT + +python3 scripts/flow/build_policy_release.py \ + --output "$artifact_dir" \ + --version 1.0.0 \ + --verify diff --git a/scripts/ci/run-fast-checks.sh b/scripts/ci/run-fast-checks.sh new file mode 100755 index 0000000..6d11a9b --- /dev/null +++ b/scripts/ci/run-fast-checks.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +export PYTHONDONTWRITEBYTECODE=1 + +python3 -m json.tool schemas/issue-contract.schema.json >/dev/null +python3 -m json.tool schemas/pr-contract.schema.json >/dev/null +python3 -m json.tool schemas/public-repository-standard-v1.schema.json >/dev/null +python3 -m json.tool schemas/provenance-manifest-v1.schema.json >/dev/null + +python3 scripts/ci/validate_repository_contract.py +python3 scripts/flow/validate_policy_bundle.py +python3 scripts/flow/validate_transition.py +python3 scripts/flow/evaluate_contribution.py +python3 scripts/flow/validate_provenance.py +python3 -m unittest discover -s tests -v + +python3 -B -c 'from pathlib import Path; [compile(path.read_text(), str(path), "exec") for path in sorted(Path("scripts").rglob("*.py"))]' diff --git a/scripts/ci/validate_repository_contract.py b/scripts/ci/validate_repository_contract.py new file mode 100755 index 0000000..4ee44fd --- /dev/null +++ b/scripts/ci/validate_repository_contract.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +"""Validate Flow's product-owned and projected repository contract.""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path +from typing import Iterable, Sequence +from urllib.parse import unquote + + +ROOT = Path(__file__).resolve().parents[2] + +GENERATED_FILE_PAIRS = ( + ("github/pull_request_template.md", ".github/PULL_REQUEST_TEMPLATE.md"), + ("github/ISSUE_TEMPLATE/implementation.yml", ".github/ISSUE_TEMPLATE/implementation.yml"), + ("github/ISSUE_TEMPLATE/flow-blocker.yml", ".github/ISSUE_TEMPLATE/flow_blocker.yml"), + ("github/ISSUE_TEMPLATE/release-train.yml", ".github/ISSUE_TEMPLATE/release_train.yml"), +) + +REQUIRED_LOCAL_PATHS = ( + ".githooks/pre-commit", + "docs/bootstrap/onboarding.md", + "scripts/ci/run-fast-checks.sh", + "scripts/ci/run-extended-validation.sh", +) + +RUNNER_LABELS = ("self-hosted", "linux", "shell-only", "public") +MARKDOWN_LINK = re.compile(r"\[[^]]*\]\(([^)]+)\)") + + +def check_generated_file_pairs( + root: Path, + pairs: Sequence[tuple[str, str]] = GENERATED_FILE_PAIRS, +) -> list[str]: + errors: list[str] = [] + for canonical_name, projected_name in pairs: + canonical = root / canonical_name + projected = root / projected_name + if not canonical.is_file(): + errors.append(f"missing canonical governance input: {canonical_name}") + continue + if not projected.is_file(): + errors.append(f"missing projected governance file: {projected_name}") + continue + if canonical.read_bytes() != projected.read_bytes(): + errors.append( + f"generated governance drift: {canonical_name} != {projected_name}" + ) + return errors + + +def markdown_files(root: Path) -> Iterable[Path]: + yield from sorted(root.glob("*.md")) + yield from sorted((root / "docs").rglob("*.md")) + + +def check_markdown_links(root: Path) -> list[str]: + errors: list[str] = [] + for document in markdown_files(root): + for raw_target in MARKDOWN_LINK.findall(document.read_text()): + target = raw_target.strip().strip("<>") + if not target or target.startswith(("#", "https://", "http://", "mailto:")): + continue + path_text = unquote(target.split("#", 1)[0]) + if path_text and not (document.parent / path_text).exists(): + errors.append( + f"missing Markdown target in {document.relative_to(root)}: {path_text}" + ) + return errors + + +def check_required_paths(root: Path) -> list[str]: + return [ + f"operator guidance target is missing: {path}" + for path in REQUIRED_LOCAL_PATHS + if not (root / path).exists() + ] + + +def check_runner_contract(root: Path) -> list[str]: + errors: list[str] = [] + workflow = (root / ".github/workflows/ci.yml").read_text() + runner_block = re.search( + r"(?ms)^ ci-gate:.*?^ runs-on:\s*\n((?:^ - [^\n]+\n)+)", + workflow, + ) + if runner_block is None: + errors.append("CI Gate does not declare a list-form runner selector") + else: + labels = tuple( + line.removeprefix(" - ").strip() + for line in runner_block.group(1).splitlines() + ) + if labels != RUNNER_LABELS: + errors.append( + f"CI Gate runner labels {labels!r} do not match {RUNNER_LABELS!r}" + ) + + documented_selector = f"[{', '.join(RUNNER_LABELS)}]" + for path in ("AGENTS.md", "docs/bootstrap/onboarding.md"): + if documented_selector not in (root / path).read_text(): + errors.append(f"{path} does not document runner selector {documented_selector}") + + manifest = (root / "project.bootstrap.yaml").read_text() + if "runnerPolicy: hybrid-safe" not in manifest: + errors.append("project.bootstrap.yaml must declare ci.runnerPolicy: hybrid-safe") + + actionlint = (root / ".github/actionlint.yaml").read_text() + for label in RUNNER_LABELS: + if f" - {label}\n" not in actionlint: + errors.append(f"actionlint is missing custom runner label: {label}") + return errors + + +def check_release_guidance(root: Path) -> list[str]: + errors: list[str] = [] + readme = (root / "README.md").read_text() + release_guide = (root / "docs/policy-release-and-upgrades.md").read_text() + if "Production consumers must pin an exact SemVer release or immutable commit SHA" not in readme: + errors.append("README must require an exact immutable production policy pin") + if "Compatibility aliases may exist for discovery, but never replace immutable production pins" not in release_guide: + errors.append("release guidance must limit floating aliases to discovery") + return errors + + +def validate(root: Path = ROOT) -> list[str]: + checks = ( + check_generated_file_pairs, + check_markdown_links, + check_required_paths, + check_runner_contract, + check_release_guidance, + ) + return [error for check in checks for error in check(root)] + + +def main() -> int: + errors = validate() + if errors: + for error in errors: + print(f"error: {error}", file=sys.stderr) + return 1 + print("repository contract valid") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_repository_contract.py b/tests/test_repository_contract.py new file mode 100644 index 0000000..3fb183d --- /dev/null +++ b/tests/test_repository_contract.py @@ -0,0 +1,39 @@ +import importlib.util +import tempfile +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +MODULE_PATH = ROOT / "scripts" / "ci" / "validate_repository_contract.py" +SPEC = importlib.util.spec_from_file_location("repository_contract", MODULE_PATH) +assert SPEC is not None and SPEC.loader is not None +repository_contract = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(repository_contract) + + +class RepositoryContractTests(unittest.TestCase): + def test_repository_contract_is_current(self): + self.assertEqual(repository_contract.validate(ROOT), []) + + def test_generated_file_drift_fails(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + canonical = root / "canonical.txt" + projected = root / "projected.txt" + canonical.write_text("current\n") + projected.write_text("stale\n") + + errors = repository_contract.check_generated_file_pairs( + root, + (("canonical.txt", "projected.txt"),), + ) + + self.assertEqual( + errors, + ["generated governance drift: canonical.txt != projected.txt"], + ) + + +if __name__ == "__main__": + unittest.main()