Repository navigation
Add quickstart and GHCR container publishing #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
de89abe
Add files via upload
cybertechsoft 6bf0eae
Create .dockerignore
cybertechsoft 276ab1c
Update tests.yml
cybertechsoft 0a1f558
Create publish-container.yml
cybertechsoft 0549510
Update .gitignore
cybertechsoft 7143523
Update quickstart.py
cybertechsoft File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| .git | ||
| .gitignore | ||
| .github | ||
| .env | ||
| .venv | ||
| venv | ||
| __pycache__ | ||
| *.py[cod] | ||
| .pytest_cache | ||
| .mypy_cache | ||
| .ruff_cache | ||
| node_modules | ||
| demo-output | ||
| .quickstart-ci | ||
| data | ||
| *.db | ||
| *.sqlite | ||
| *.sqlite3 | ||
| *.pem | ||
| *.key | ||
| *.bin | ||
| *.zip | ||
| dist | ||
| build |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| name: Publish container | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
| paths: | ||
| - 'app/**' | ||
| - 'verifier/**' | ||
| - 'requirements.txt' | ||
| - 'run.py' | ||
| - 'Dockerfile' | ||
| - '.dockerignore' | ||
| - '.github/workflows/publish-container.yml' | ||
| release: | ||
| types: [published] | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
| packages: write | ||
|
|
||
| concurrency: | ||
| group: publish-container-${{ github.ref }} | ||
| cancel-in-progress: false | ||
|
|
||
| env: | ||
| REGISTRY: ghcr.io | ||
| IMAGE_NAME: ${{ github.repository }} | ||
|
|
||
| jobs: | ||
| publish: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Check out source | ||
| uses: actions/checkout@v7 | ||
| with: | ||
| ref: ${{ github.event.release.tag_name || github.ref }} | ||
|
|
||
| - name: Set up QEMU | ||
| uses: docker/setup-qemu-action@v4 | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v4 | ||
|
|
||
| - name: Log in to GitHub Container Registry | ||
| uses: docker/login-action@v4 | ||
| with: | ||
| registry: ${{ env.REGISTRY }} | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Extract image metadata | ||
| id: meta | ||
| uses: docker/metadata-action@v6 | ||
| with: | ||
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | ||
| flavor: | | ||
| latest=false | ||
| tags: | | ||
| type=raw,value=edge,enable=${{ github.event_name == 'push' }} | ||
| type=sha,prefix=sha-,format=short,enable=${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} | ||
| type=raw,value=manual,enable=${{ github.event_name == 'workflow_dispatch' }} | ||
| type=semver,pattern={{version}},value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' }} | ||
| type=semver,pattern={{major}}.{{minor}},value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' }} | ||
| type=raw,value=latest,enable=${{ github.event_name == 'release' && !github.event.release.prerelease }} | ||
|
|
||
| - name: Build and push multi-platform image | ||
| uses: docker/build-push-action@v7 | ||
| with: | ||
| context: . | ||
| platforms: linux/amd64,linux/arm64 | ||
| push: true | ||
| tags: ${{ steps.meta.outputs.tags }} | ||
| labels: ${{ steps.meta.outputs.labels }} | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max |
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,3 +13,5 @@ build/ | |
| *.egg-info/ | ||
| node_modules/ | ||
| .DS_Store | ||
| demo-output/ | ||
| .quickstart-ci/ | ||
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import json | ||
| import os | ||
| import sys | ||
| import tempfile | ||
| from pathlib import Path | ||
|
|
||
| ROOT = Path(__file__).resolve().parents[1] | ||
| if str(ROOT) not in sys.path: | ||
| sys.path.insert(0, str(ROOT)) | ||
|
|
||
|
|
||
| def _sqlite_url(path: Path) -> str: | ||
| return "sqlite:///" + path.resolve().as_posix() | ||
|
|
||
|
|
||
| def _prepare_local_environment(state_dir: Path) -> None: | ||
| """Configure an isolated, local-only LoopGrid evaluation environment. | ||
|
|
||
| Environment variables are set before importing the LoopGrid application because | ||
| runtime settings and the signing provider are initialized at import time. | ||
| """ | ||
| state_dir.mkdir(parents=True, exist_ok=True) | ||
| os.environ["LOOPGRID_ENV"] = "development" | ||
| os.environ["LOOPGRID_REQUIRE_USER_AUTH"] = "false" | ||
| os.environ["LOOPGRID_DATABASE_URL"] = _sqlite_url(state_dir / "loopgrid.db") | ||
| os.environ["LOOPGRID_SIGNER_PROVIDER"] = "local_ed25519" | ||
| os.environ["LOOPGRID_SIGNING_KEY_PATH"] = str(state_dir / "signing-key.pem") | ||
| os.environ["LOOPGRID_PUBLIC_KEY_PATH"] = str(state_dir / "signing-public.pem") | ||
| os.environ["LOOPGRID_PAYLOAD_KEY_PATH"] = str(state_dir / "payload-key.bin") | ||
| os.environ["LOOPGRID_STRICT_PRODUCTION_SAFETY"] = "true" | ||
|
|
||
|
|
||
| def run(output_dir: Path) -> int: | ||
| output_dir = output_dir.resolve() | ||
| output_dir.mkdir(parents=True, exist_ok=True) | ||
|
|
||
| # Never reuse an existing evaluation database or signing key. The quickstart is | ||
| # intended to be deterministic from the user's point of view and isolated from | ||
| # any real/local LoopGrid deployment they may already have. | ||
| with tempfile.TemporaryDirectory(prefix="loopgrid-quickstart-") as tmp: | ||
| state_dir = Path(tmp) | ||
| _prepare_local_environment(state_dir) | ||
|
|
||
| try: | ||
| from fastapi.testclient import TestClient | ||
| from app.main import app | ||
| from verifier.loopgrid_verify import verify_bundle | ||
| except Exception as exc: # pragma: no cover - friendly CLI path | ||
| print("[FAIL] LoopGrid dependencies are not installed.") | ||
| print("Run: python -m pip install -r requirements.txt") | ||
| print(f"Detail: {type(exc).__name__}: {exc}") | ||
| return 1 | ||
|
|
||
| bundle_path = output_dir / "evidence.zip" | ||
| trusted_key_path = output_dir / "trusted-public-key.pem" | ||
| summary_path = output_dir / "quickstart-result.json" | ||
|
|
||
| for path in (bundle_path, trusted_key_path, summary_path): | ||
| if path.exists(): | ||
| path.unlink() | ||
|
|
||
| print("LoopGrid quickstart") | ||
| print("-------------------") | ||
| print("Creating an isolated local evaluation using SQLite and a temporary Ed25519 signer...") | ||
|
|
||
| try: | ||
| with TestClient(app) as client: | ||
| demo = client.post("/api/v1/demo/refund") | ||
| demo.raise_for_status() | ||
| payload = demo.json() | ||
| decision_id = payload["summary"]["decision_id"] | ||
|
|
||
| evidence = client.get( | ||
| f"/api/v1/decisions/{decision_id}/evidence", | ||
| params={"include_payloads": "false"}, | ||
| ) | ||
| evidence.raise_for_status() | ||
| bundle_path.write_bytes(evidence.content) | ||
|
|
||
| public_key = client.get("/api/v1/public-key.pem") | ||
| public_key.raise_for_status() | ||
| trusted_key_path.write_bytes(public_key.content) | ||
|
|
||
| result = verify_bundle( | ||
| str(bundle_path), | ||
| trusted_public_key=str(trusted_key_path), | ||
| ) | ||
| except Exception as exc: | ||
| print(f"[FAIL] Quickstart failed: {type(exc).__name__}: {exc}") | ||
| return 1 | ||
|
|
||
| verification_valid = bool(result.get("valid")) | ||
| failure_count = len(result.get("failures") or []) | ||
| warning_count = len(result.get("warnings") or []) | ||
| coverage_score = int((payload.get("coverage") or {}).get("score") or 0) | ||
|
|
||
| # Keep the machine-readable quickstart summary intentionally minimal. | ||
| # Verifier internals and key material/paths are not persisted or echoed here. | ||
| summary = { | ||
| "decision_id": decision_id, | ||
| "workspace_id": payload["summary"].get("workspace_id"), | ||
| "decision_type": payload["summary"].get("decision_type"), | ||
| "coverage_score": coverage_score, | ||
| "verification_status": "VERIFIED" if verification_valid else "INVALID", | ||
| "failure_count": failure_count, | ||
| "warning_count": warning_count, | ||
| "evidence_file": bundle_path.name, | ||
| } | ||
| summary_path.write_text(json.dumps(summary, indent=2), encoding="utf-8") | ||
|
|
||
| print(f"[OK] Decision captured: {decision_id}") | ||
| print(f"[OK] Evidence exported: {bundle_path}") | ||
| print(f"[OK] Trusted public key exported: {trusted_key_path}") | ||
| print(f"[OK] Evidence coverage: {coverage_score}%") | ||
| if verification_valid: | ||
| print("[OK] VERIFIED") | ||
| print(" The exported bundle verified with an out-of-band trusted public key.") | ||
| print(f" Details: {summary_path}") | ||
| return 0 | ||
|
|
||
| print("[FAIL] INVALID") | ||
| print(f" Verifier reported {failure_count} failure(s) and {warning_count} warning(s).") | ||
| print(f" Details: {summary_path}") | ||
| return 2 | ||
|
|
||
|
|
||
| def main() -> None: | ||
| parser = argparse.ArgumentParser( | ||
| description="Create and independently verify a synthetic LoopGrid evidence bundle locally." | ||
| ) | ||
| parser.add_argument( | ||
| "--output-dir", | ||
| default="demo-output", | ||
| help="Directory for evidence.zip, trusted-public-key.pem, and result JSON (default: demo-output)", | ||
| ) | ||
| args = parser.parse_args() | ||
| raise SystemExit(run(Path(args.output_dir))) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.