docs: make Method Factory README reader-first #40
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: release-gate | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| test: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.11", "3.12"] | |
| steps: | |
| # Explicitly check out the ACTUAL pull-request HEAD SHA, not GitHub's | |
| # synthetic merge ref (actions/checkout defaults to the merge ref on | |
| # pull_request). This makes the CI evidence literal-commit proof: the | |
| # tested tree is exactly the branch head, not a candidate-tree- | |
| # equivalent merge. | |
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 (pinned SHA) | |
| with: | |
| ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} | |
| # Full history required: migration tests generate fixtures from the | |
| # exact public v0.1.2 commit fb5641c (tag v0.1.2-integrity), which | |
| # is not present in a depth-1 checkout of this branch. | |
| fetch-depth: 0 | |
| # Literal-commit identity assertion: the checked-out HEAD MUST equal the | |
| # event's expected SHA. A mismatch fails CI. This is the proof that a | |
| # successful run tested the exact commit, not a synthetic merge tree. | |
| - name: Assert checkout identity | |
| run: | | |
| ACTUAL="$(git rev-parse HEAD)" | |
| EXPECTED="${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}" | |
| echo "actual HEAD: $ACTUAL" | |
| echo "expected SHA: $EXPECTED" | |
| if [ "$ACTUAL" != "$EXPECTED" ]; then | |
| echo "::error::checkout identity mismatch (actual != expected)"; exit 1 | |
| fi | |
| echo "checkout identity OK: tested tree is the exact expected commit" | |
| - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 (pinned SHA) | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install with test extras | |
| run: pip install -e ".[test]" | |
| - name: Canonical unit tests | |
| run: python -m unittest discover -s methodfactory/tests -t . | |
| - name: Packaging smoke — mf entry point | |
| run: | | |
| mf --version | |
| python -m methodfactory --version | |
| test "$(mf --version)" = "methodfactory 2.0.0rc1" | |
| test "$(python -m methodfactory --version)" = "methodfactory 2.0.0rc1" | |
| # RC1 packaging proof — build the distributable WHEEL from the EXACT | |
| # candidate source into a disposable directory OUTSIDE the repo, so the | |
| # tracked-worktree and artifact-scan steps below stay clean. No new | |
| # build dependency: pip wheel uses the declared setuptools backend. | |
| # (sdist is NOT claimed by CI; any sdist evidence is local-only | |
| # supplemental unless independently CI-produced.) | |
| - name: Build distributable wheel | |
| run: | | |
| set -euo pipefail | |
| export MF_DIST="$RUNNER_TEMP/mf-dist" | |
| mkdir -p "$MF_DIST" "$RUNNER_TEMP/mf-src" | |
| git archive HEAD | tar -x -C "$RUNNER_TEMP/mf-src" | |
| cd "$RUNNER_TEMP/mf-src" | |
| python -m pip wheel . --no-deps -w "$MF_DIST" | |
| ls -la "$MF_DIST" | |
| python - <<'EOF' | |
| import hashlib, os, pathlib | |
| dist = pathlib.Path(os.environ["MF_DIST"]) | |
| wheels = sorted(dist.glob("methodfactory-2.0.0rc1-*.whl")) | |
| assert len(wheels) == 1, f"expected exactly one rc1 wheel, got {[p.name for p in dist.iterdir()]}" | |
| for p in wheels: | |
| print(p.name, hashlib.sha256(p.read_bytes()).hexdigest()) | |
| EOF | |
| - name: Isolated wheel install + version proof | |
| run: | | |
| set -euo pipefail | |
| export MF_VENV="$RUNNER_TEMP/mf-venv" | |
| export MF_DIST="$RUNNER_TEMP/mf-dist" | |
| python -m venv "$MF_VENV" | |
| "$MF_VENV/bin/python" -m pip install --no-deps "$MF_DIST"/*.whl | |
| # Run OUTSIDE the repo: a heredoc through `python -` puts CWD on | |
| # sys.path, which would shadow the installed package with the | |
| # checkout copy and break the import-provenance proof. | |
| cd "$RUNNER_TEMP" | |
| # import provenance: must resolve from the venv site-packages, not | |
| # the development checkout. | |
| "$MF_VENV/bin/python" - <<'EOF' | |
| import methodfactory, os, pathlib | |
| print("import file:", methodfactory.__file__) | |
| venv = pathlib.Path(os.environ["MF_VENV"]).resolve() | |
| assert pathlib.Path(methodfactory.__file__).resolve().is_relative_to(venv), \ | |
| "methodfactory resolved outside the isolated venv" | |
| assert methodfactory.__version__ == "2.0.0rc1", methodfactory.__version__ | |
| EOF | |
| test "$("$MF_VENV/bin/mf" --version)" = "methodfactory 2.0.0rc1" | |
| test "$("$MF_VENV/bin/python" -m methodfactory --version)" = "methodfactory 2.0.0rc1" | |
| # CLI surface: only migrate-store + export may be present. | |
| "$MF_VENV/bin/mf" --help | |
| for banned in create apply status summary review trial ship triage; do | |
| if "$MF_VENV/bin/mf" --help | grep -qE "^\s+$banned\b"; then | |
| echo "::error::forbidden lifecycle command exposed: $banned"; exit 1 | |
| fi | |
| done | |
| # Packaged functional smoke: canonical exact-fb5641c fixture -> installed | |
| # mf migrate-store -> authoritative validation -> installed mf export | |
| # (both formats) -> legacy evidence revalidated by the frozen reader; | |
| # source must remain unchanged. | |
| - name: Packaged migration/export smoke (isolated env) | |
| run: | | |
| set -euo pipefail | |
| export MF_SMOKE="$RUNNER_TEMP/mf-smoke" | |
| export MF_STORE="$RUNNER_TEMP/mf-store" | |
| export MF_LEGACY_REVAL="$RUNNER_TEMP/mf-legacy-revalidate" | |
| export MF_VENV="$RUNNER_TEMP/mf-venv" | |
| python - <<'EOF' | |
| # Generate the canonical fixture through the sanctioned mechanism | |
| # (exact public fb5641c code via the repo's disposable worktree). | |
| # Runs from the checkout on purpose: the editable install provides | |
| # methodfactory.tests._fixtures. | |
| import os, pathlib, shutil | |
| from methodfactory.tests._fixtures import generate_fixture, standard_workflow | |
| root = pathlib.Path(os.environ["MF_SMOKE"]) | |
| shutil.rmtree(root, ignore_errors=True) | |
| generate_fixture(str(root), workflow=standard_workflow) | |
| EOF | |
| mkdir -p "$MF_STORE" | |
| chmod 700 "$MF_STORE" | |
| # source immutability baseline | |
| sha256sum "$MF_SMOKE"/events/*.events.jsonl > "$MF_STORE/source.before" | |
| # Run the installed tooling OUTSIDE the repo (CWD on sys.path would | |
| # shadow the installed package with the checkout copy). | |
| cd "$RUNNER_TEMP" | |
| # installed migrate-store | |
| "$MF_VENV/bin/mf" migrate-store --source "$MF_SMOKE" --dest "$MF_STORE/methodfactory.sqlite3" | |
| # authoritative validation via installed primitives | |
| "$MF_VENV/bin/python" - <<'EOF' | |
| import os | |
| from methodfactory.storage.store import SqliteManifestStore | |
| store = SqliteManifestStore(os.environ["MF_STORE"]) | |
| try: | |
| for pkg in store.list_package_ids(): | |
| result = store.validate_chain(pkg, verify_artifacts=True) | |
| assert result["valid"], result | |
| print("validated", pkg, result) | |
| finally: | |
| store.close() | |
| EOF | |
| # installed exports (both formats) | |
| "$MF_VENV/bin/mf" export --store "$MF_STORE" --output "$MF_STORE/events-v1.jsonl" --format method-factory-events-v1 | |
| "$MF_VENV/bin/mf" export --store "$MF_STORE" --output "$MF_STORE/legacy.jsonl" --format legacy-v012-jsonl | |
| test -s "$MF_STORE/events-v1.jsonl" | |
| test -s "$MF_STORE/legacy.jsonl" | |
| # legacy evidence export revalidates under the frozen legacy reader | |
| "$MF_VENV/bin/python" - <<'EOF' | |
| import os, pathlib, shutil | |
| from methodfactory.migrations.v012_jsonl import LegacySource | |
| lr = pathlib.Path(os.environ["MF_LEGACY_REVAL"]) | |
| store = pathlib.Path(os.environ["MF_STORE"]) | |
| shutil.rmtree(lr, ignore_errors=True) | |
| (lr / "events").mkdir(parents=True) | |
| (lr / "packages").mkdir() | |
| (lr / "artifacts" / "blobs").mkdir(parents=True) | |
| shutil.copy(store / "legacy.jsonl", lr / "events" / "pkg_demo_001.events.jsonl") | |
| for blob in (store / "blobs").iterdir(): | |
| shutil.copy(blob, lr / "artifacts" / "blobs" / blob.name) | |
| LegacySource(lr).validate() | |
| print("legacy evidence export revalidated OK") | |
| EOF | |
| # source unchanged after migration/export | |
| sha256sum "$MF_SMOKE"/events/*.events.jsonl > "$MF_STORE/source.after" | |
| diff "$MF_STORE/source.before" "$MF_STORE/source.after" | |
| echo "source unchanged OK" | |
| # Honest terminology (Finding 4 item 2): this proves a CLEAN TRACKED | |
| # WORKTREE (no uncommitted/untracked tracked-tree changes). Generated | |
| # runtime/build files are gitignored, so their absence is proven by the | |
| # explicit artifact scan below, not by git status alone. | |
| - name: Clean tracked worktree | |
| run: | | |
| test -z "$(git status --porcelain)" || { echo "::error::dirty tracked worktree"; git status --porcelain; exit 1; } | |
| # Explicit runtime/build-artifact scan with a documented allowlist | |
| # (Finding 4 item 2): runtime store artifacts and SQLite files must not | |
| # exist in the filesystem. methodfactory.egg-info IS allowlisted here: | |
| # it is a build byproduct of the `pip install -e ".[test]"` step above | |
| # (generated + gitignored), not a runtime store artifact. build/, dist/, | |
| # and .pytest_cache are checked when they would be produced by this job. | |
| - name: Runtime/build-artifact scan (explicit) | |
| run: | | |
| FAIL=0 | |
| for p in .mf build dist .pytest_cache; do | |
| if [ -e "$p" ]; then echo "::error::forbidden generated artifact present: $p"; FAIL=1; fi | |
| done | |
| # SQLite rollback-journal files are forbidden (Finding 4 item 3). | |
| found=$(find . -name '*.sqlite3' -o -name '*.sqlite' -o -name '*.db' \ | |
| -o -name '*.wal' -o -name '*.shm' -o -name '*.journal' 2>/dev/null | grep -v '^\./\.git/' || true) | |
| if [ -n "$found" ]; then echo "::error::forbidden SQLite/journal artifact:"; echo "$found"; FAIL=1; fi | |
| exit $FAIL |