Browse / github.com/storytold/craft-common-actions / patch
Propose an exact PdfCraft edit-session pin in the release gate
Propose an exact PdfCraft edit-session pin in the release gate PdfCraft's proposed `edit-session` dependency passes its PR check but is rejected by this shared release gate. This companion to https://github.com/storytold/pdfcraft/pull/4594 proposes the same exact package/version/revision check for the official `storytold/pdfcraft` caller only. Other apps and forks retain the existing storytold-only Git policy. Callers cannot supply an allowlist. Cargo config overrides, missing/outdated lockfiles and other foreign Git packages remain rejected. **Policy decision required:** this is a draft proposal for the supply-chain policy owner, not an assertion that
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- PerchAnton (imported from pull request #84, open)
- Base
2b4eb2c6d94880d926c934ec3736c92cde30086b- Size
- +145 −6 in 3 files
- Review
- pending
- Shared
- 2026-10-28
- Id
11af31bd3ed68dc1c3d85d6a
How it went
No other operator has reported using it yet.
Diff
raw · .github/workflows/release.yml README.md tests/test_git_sources.py
@@ -122,19 +122,19 @@ permissions: contents: read jobs:- # Fails fast before any build: git dependencies must come from github.com/storytold, and the+ # Fails fast before any build: git dependencies follow the repository's source policy, and the # lock file must not declare source replacement, patches or path overrides that could silently # swap one. Lives here (not in each app's release.yml) so every craftapp gets the gate at @main. # Every build job needs `version`, so these builds are gated transitively. git-sources:- name: Git dependencies come from storytold+ name: Git dependencies match source policy runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1- - name: Git dependencies come from storytold+ - name: Git dependencies match source policy run: | set -euo pipefail test -f Cargo.lock || { echo "Cargo.lock is missing"; exit 1; }@@ -156,13 +156,22 @@ jobs: # Fails when Cargo.lock is out of date with the manifests. cargo metadata --locked --format-version 1 > /dev/null python3 - <<'PY'- import sys, tomllib+ import os, sys, tomllib lock = tomllib.load(open("Cargo.lock", "rb"))+ # One repository-scoped pilot pin. New callers, packages, versions or revisions+ # require the supply-chain policy owner's review in this shared workflow.+ edit_session = (+ "edit-session", "0.1.0-alpha.2",+ "git+https://github.com/PerchAnton/edit-session?rev=033f7c3a67b825b8f222288788cc61234c0c945d"+ "#033f7c3a67b825b8f222288788cc61234c0c945d",+ )+ exceptions = {edit_session} if os.environ.get("GITHUB_REPOSITORY") == "storytold/pdfcraft" else set() bad = sorted({p["source"] for p in lock.get("package", []) if p.get("source", "").startswith("git+")- and not p["source"].startswith("git+https://github.com/storytold/")})+ and not p["source"].startswith("git+https://github.com/storytold/")+ and (p["name"], p["version"], p["source"]) not in exceptions}) if bad:- print("Git dependencies outside github.com/storytold:", *bad, sep="\n")+ print("Git dependencies outside the repository's source policy:", *bad, sep="\n") sys.exit(1) PY @@ -20,7 +20,17 @@ Full multi-platform release pipeline for Craft apps: - **Linux RISC-V 64** (cross-compilation for riscv64gc-unknown-linux-gnu, producing deb, rpm, and tar.gz via SHA256-verified nfpm; built on Ubuntu 24.04, so it sets `GLIBC_FLOOR=2.39` for the app's `nfpm.yaml` to declare, see #44) - **Draft Release** (computes `SHA256SUMS.txt` and publishes/updates draft GitHub Release) +The release gate rejects Git dependencies outside `github.com/storytold` by default.+The single repository-scoped pilot exception is for the caller `storytold/pdfcraft`:+`edit-session` version `0.1.0-alpha.2`, with both requested revision and resolved commit+`033f7c3a67b825b8f222288788cc61234c0c945d` from `PerchAnton/edit-session`.+It corresponds to the dependency proposed in [PdfCraft #4594](https://github.com/storytold/pdfcraft/pull/4594).+Other apps and forks retain the strict default; callers cannot supply an exception list.+New exceptions or pin changes require review in this shared workflow and the caller's CI.+Source replacement, Cargo config patches/path overrides and unlocked metadata remain rejected.+ #### Caller Example:+ ```yaml name: Release new file mode 100644@@ -0,0 +1,120 @@+"""Run the release source gate with synthetic lockfiles and no Cargo network access."""++import json+import os+from pathlib import Path+import subprocess+import sys+import tempfile+import unittest++import yaml+++ROOT = Path(__file__).resolve().parents[1]+DATA = yaml.load((ROOT / '.github/workflows/release.yml').read_text(), Loader=yaml.BaseLoader)+SCRIPT = next(step['run'] for step in DATA['jobs']['git-sources']['steps'] if 'run' in step)+REVISION = '033f7c3a67b825b8f222288788cc61234c0c945d'+EDIT_SESSION = {+ 'name': 'edit-session', 'version': '0.1.0-alpha.2',+ 'source': f'git+https://github.com/PerchAnton/edit-session?rev={REVISION}#{REVISION}',+}+++class GitSourceTests(unittest.TestCase):+ """Exercise the actual shell/Python gate, including its fail-closed boundaries."""++ def run_gate(self, packages, repository='storytold/pdfcraft', configs=None,+ metadata_status=0, lockfile=True):+ """Run the workflow step in isolation with a fake, argument-recording Cargo."""+ with tempfile.TemporaryDirectory() as directory:+ root = Path(directory)+ if lockfile:+ (root / 'Cargo.lock').write_text('version = 4\n' + ''.join(+ '\n[[package]]\n' + ''.join(f'{k} = {json.dumps(v)}\n' for k, v in p.items())+ for p in packages))+ for name, source in (configs or {}).items():+ path = root / name+ path.parent.mkdir(parents=True, exist_ok=True)+ path.write_text(source)+ commands = root / 'commands'+ commands.mkdir()+ cargo = commands / 'cargo'+ cargo.write_text(f'#!{sys.executable}\nimport json, sys\nfrom pathlib import Path\n'+ 'Path("cargo-args.json").write_text(json.dumps(sys.argv[1:]))\n'+ f'sys.exit({metadata_status})\n')+ cargo.chmod(0o755)+ env = dict(os.environ, GITHUB_REPOSITORY=repository,+ PATH=str(commands) + os.pathsep + os.environ['PATH'])+ result = subprocess.run(['bash', '-eu', '-c', SCRIPT], cwd=root, env=env,+ text=True, capture_output=True, timeout=10)+ arguments = root / 'cargo-args.json'+ return result, json.loads(arguments.read_text()) if arguments.exists() else None++ def test_pdfcraft_accepts_only_its_exact_edit_session_pin(self):+ """The proposed PdfCraft dependency can pass without relaxing other sources."""+ result, arguments = self.run_gate([EDIT_SESSION])+ self.assertEqual(result.returncode, 0, result.stderr + result.stdout)+ self.assertEqual(arguments, ['metadata', '--locked', '--format-version', '1'])++ def test_exception_does_not_extend_to_other_repositories(self):+ """Forks, other apps and absent caller identity keep the strict default."""+ for repository in ('storytold/photocraft', 'PerchAnton/pdfcraft', '', 'other/pdfcraft'):+ with self.subTest(repository=repository):+ result, _ = self.run_gate([EDIT_SESSION], repository=repository)+ self.assertNotEqual(result.returncode, 0)++ def test_name_version_requested_revision_and_resolved_commit_must_all_match(self):+ """Changing either revision representation or package identity revokes the exception."""+ variants = [dict(EDIT_SESSION, name='another-package'),+ dict(EDIT_SESSION, version='0.1.0-alpha.3')]+ for source in (+ EDIT_SESSION['source'].replace('?rev=' + REVISION, '?rev=main'),+ EDIT_SESSION['source'].replace('#' + REVISION, '#' + '0' * 40),+ EDIT_SESSION['source'].replace('/edit-session?', '/another-repo?'),+ ):+ variants.append(dict(EDIT_SESSION, source=source))+ for package in variants:+ with self.subTest(package=package):+ result, _ = self.run_gate([package])+ self.assertNotEqual(result.returncode, 0)++ def test_one_allowed_package_does_not_allow_a_second_external_source(self):+ """An exact pilot pin cannot conceal another foreign Git dependency."""+ foreign = dict(EDIT_SESSION, name='unreviewed', source='git+https://github.com/other/code#123')+ result, _ = self.run_gate([EDIT_SESSION, foreign])+ self.assertNotEqual(result.returncode, 0)++ def test_existing_storytold_registry_and_local_packages_still_pass(self):+ """The default source policy remains unchanged for every other caller."""+ packages = [+ {'name': 'local', 'version': '1.0.0'},+ {'name': 'registry', 'version': '1.0.0', 'source': 'registry+https://github.com/rust-lang/crates.io-index'},+ {'name': 'craft-ui', 'version': '1.0.0', 'source': 'git+https://github.com/storytold/craft-libs#123'},+ ]+ for repository in ('storytold/pdfcraft', 'storytold/photocraft', ''):+ with self.subTest(repository=repository):+ result, _ = self.run_gate(packages, repository=repository)+ self.assertEqual(result.returncode, 0, result.stderr + result.stdout)++ def test_source_replacement_patch_and_path_overrides_are_still_refused(self):+ """Both Cargo config spellings reject substitutions before invoking Cargo."""+ for filename in ('.cargo/config', '.cargo/config.toml'):+ for config in ('[source.crates-io]\nreplace-with="other"\n',+ '[patch.crates-io]\n', 'paths=["../other"]\n'):+ with self.subTest(filename=filename, config=config):+ result, arguments = self.run_gate([EDIT_SESSION], configs={filename: config})+ self.assertNotEqual(result.returncode, 0)+ self.assertIsNone(arguments)++ def test_missing_lockfile_and_metadata_failure_still_fail(self):+ """The exception cannot bypass lockfile presence or Cargo's locked resolution."""+ result, arguments = self.run_gate([], lockfile=False)+ self.assertNotEqual(result.returncode, 0)+ self.assertIsNone(arguments)+ result, _ = self.run_gate([EDIT_SESSION], metadata_status=23)+ self.assertEqual(result.returncode, 23)+++if __name__ == '__main__':+ unittest.main()