Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

liskov-github-actions

Reusable GitHub workflows + actions for Liskov CI, so each Liskov repo's CI is a short uses: …@v2 instead of copy-pasted YAML + scripts. Plan + decisions: liskov-agent-orchestrator BKLG-20260624-jn9l (extracted from liskov-diagnostic's inline scripts).

Reusable workflows (the one-liners)

acurast-app.yml — build → IPFS-pin → OIDC attest

The Liskov upload default described here is pending the next v2.x.0 release and movement of v2; this source change does not cut a release.

# .github/workflows/<app>.yml in an Acurast app repo
on:
  push: { branches: [main], paths: ['<app-dir>/**'] }
  workflow_dispatch:
permissions: { id-token: write, contents: read }
jobs:
  artifact:
    uses: proof-computer/liskov-github-actions/.github/workflows/acurast-app.yml@v2
    with:
      organization-id: your-liskov-organization-id # ordinary non-secret configuration
      app-id: uptime-prober
      working-directory: uptime-prober      # default "."
      entrypoint: app.cjs                    # default; use bundle.cjs + extra-files: app.cjs for a stage0 app
      authored-manifest-path: uptime-prober/.liskov/uptime-prober.policy.json

Runs pnpm install --frozen-lockfile → typecheck → test → build (optional smoke), pins the Acurast deploy zip to IPFS no-spend, and posts a GitHub-OIDC artifact pin. V4 targets remain bound to the exact authored and release-intent digests; identity-only V5 source targets are fenced by the server-owned source binding and never synthesize V4 digests or drafts. Callers keep the generated-zip inputs with app-id, authored-manifest-path, working-directory, entrypoint, extra-files, node-version, smoke, attest, and pin-url. The workflow exposes the CID, digest, uploaded build-manifest path, artifact-version IDs, per-Application result JSON, and target count.

With ipfs-endpoint empty, ipfs-pin uses GitHub OIDC (audience liskov-artifact-upload, requiring permissions: id-token: write) to request a single-use token from <liskov-url>/api/organizations/<organization-id>/applications/<app-id>/artifact-upload-intents. Set organization-id to the existing application's organization; it selects the resource and grants no permission. The default liskov-url is https://api.liskov.proof.computer; pin-url remains the separate attestation override. The upload uses the returned token and verifies exact bytes through PROOF's returned gateway template. The authored manifest path is repository-relative; its digest uses the same canonical policy contract as attestation. When that path is empty, the intent's manifest digest is the sha256 of the artifact bytes.

To retain the Acurast proxy path, commit its endpoint as the ipfs-endpoint input (it is not a secret) and pass its Bearer key, if it needs one, as the LISKOV_IPFS_API_KEY secret. A Liskov refusal fails the action with its code; it never falls back to the proxy. This includes 409 source_binding_revoked. The action adds upload-id (empty for proxy/reused paths) and upload-path (liskov, proxy, or reused) outputs; the token is masked and never output.

Callers that already prepare deploy bytes may set prepare-command and artifact-path. The path is relative to working-directory; ipfs-pin hashes and uploads those exact bytes without repacking them. artifact-metadata-path accepts a strict JSON sidecar containing only the currently supported Diagnostic evidence: optional devtools (enabled, injected, and uploadMode: "direct"), artifact (format, safe entrypoint, and restartPolicy), sourceBundleSha256, failure, and scenarioArtifact. Unknown fields and unsafe paths fail closed. OIDC claims, repository identity, refs, and workflow identity never come from this sidecar.

script-ipfs reuses an exact ipfs://CID without another upload while still hashing the local bytes. Set ipfs-gateway-url to a credential-free HTTPS URL (with optional {cid}) to override Liskov's gateway or opt into proxy/reused byte verification. Verification allows four propagation attempts with ten seconds between attempts. The build manifest is always uploaded as a GitHub Actions run artifact.

For one prepared artifact shared by multiple Applications, set artifact-targets-path instead of authored-manifest-path. It is a repository-root relative non-empty JSON array with no extra keys:

[
  {"applicationId":"diagnostic-a","authoredManifestPath":".liskov/diagnostic-a.json"},
  {"applicationId":"diagnostic-b","authoredManifestPath":".liskov/diagnostic-b.json"}
]

Each exact target must name the matching Application and be either a V4 unencrypted ipfs_bundle build manifest or a retained V5 JavaScript source document. The shared action mints one OIDC token and posts one artifact per target. V4 requests retain the deployed domain and byte shape; V5 requests use proof.liskov.github-source-artifact.v1 with manifest path, CID, and digest only, so Liskov resolves repository/ref/workflow/commit authority from verified OIDC plus the current source binding. The original single-target inputs and first artifact-version-id output remain compatible.

marketplace-ingest.yml — catalog OIDC push (ADR-0006 §A1)

on: { push: { branches: [main] } }
permissions: { id-token: write, contents: read }
jobs:
  publish:
    uses: proof-computer/liskov-github-actions/.github/workflows/marketplace-ingest.yml@v2

Mints an OIDC token and tells liskov-rs to reconcile the catalog to this commit. The /api/marketplace/ingest endpoint is not built yet (catalog schema spec, Part 2) — this is ready for it.

V4 manifest import is closed

policy-sync.yml and actions/policy-import were removed in v2.3.0, when Liskov closed V4 manifest import. Create the Application with proof liskov application create, bind its repository with proof liskov application source-binding set, and publish a V5 manifest with proof liskov application policy publish.

runtime-image.yml — V5 source document → scoped image upload → bootstrap

on:
  workflow_dispatch:
    inputs:
      image_url: { description: Pinned PRoot image URL, required: true, type: string }
      expected_sha256: { description: Optional SHA-256, required: false, type: string }
permissions: { id-token: write, contents: read }
jobs:
  runtime-image:
    uses: proof-computer/liskov-github-actions/.github/workflows/runtime-image.yml@v2
    with:
      application-id: my-app
      manifest-path: .liskov/my-app.v5.json   # V5 source document
      image-url: ${{ inputs.image_url }}
      expected-sha256: ${{ inputs.expected_sha256 }}
      attestation-repository: proof-computer/liskov-runtime-images
      attestation-source-digest: ${{ inputs.image_source_commit }}
      attestation-signer-workflow: proof-computer/liskov-runtime-images/.github/workflows/ci.yml

Downloads the supplied image and locally verifies its SHA-256 before any upload. When attestation-repository, attestation-source-digest, and attestation-signer-workflow are supplied, the workflow requires expected-sha256 and verifies the downloaded file's GitHub artifact attestation against that exact source commit and signer workflow before any state-changing step. The three attestation inputs are all-or-none. Omitting all three preserves the existing unattested direct-URL migration/debug path.

manifest-path is a repo-relative V5 source document (release.mode: source, runtime.kind: native_image) whose applicationId is application-id. The workflow imports nothing. The scoped upload action validates the document with the bundled policy client and opens the upload session with the document itself and its path. Liskov fences the verified GitHub OIDC identity against the Application's current source binding before creating one-object Tigris credentials, and the action requires the session to echo the authored digest it computed and the manifest path before any byte is uploaded. It then uploads without exposing the credentials, remints OIDC, finalizes, and returns the image, bootstrap, artifact-version, cleanup, and safe provenance outputs. Set liskov-url to use a custom Liskov base.

Finalizing publishes nothing (auto-published is false). The workflow returns the build evidence a V5 release.mode: source publication supplies:

Output Publish flag
artifact-digest (the bootstrap bundle digest) --artifact-digest
binding-revision --binding-revision
revocation-epoch --revocation-epoch
source-ref --source-ref
source-commit --source-commit
source-workflow-identity --workflow-identity
source-repository (the repository the binding verified)

Publish the same document with that evidence:

proof liskov application policy publish <app> --file <document> \
  --artifact-digest <artifact-digest> \
  --binding-revision <binding-revision> --revocation-epoch <revocation-epoch> \
  --source-ref <source-ref> --source-commit <source-commit> \
  --workflow-identity <source-workflow-identity> \
  --expected-pointer-version <n> --yes

or publish a release.mode: pinned document that names artifact-digest.

The Application must already carry a source binding for this repository, ref, workflow and manifest path (proof liskov application source-binding set). This V5 path needs a Liskov server that accepts the proof.liskov.runtime-image-source-upload-session.v1 domain. @v1 carries the retired V4 import path (policy-import, then an upload bound to the imported digest pair), which Liskov refuses; a caller moves to @v2 with a V5 document.

cargo-runtime-image.yml — locked Rust build → deterministic image → upload

This workflow is the reusable supply-chain path for native Cargo applications. It requires Cargo.lock, an exact rust-toolchain.toml channel, and an attested, digest-pinned helperless Liskov rootfs. On a native ARM64 runner it builds the selected binary twice for aarch64-unknown-linux-musl, rejects a dynamic interpreter, and requires identical binary bytes. It then overlays each build at the declared absolute path, normalizes the rootfs archive, rejects any embedded liskov-runtime-contact, and requires both complete images to have the same digest and bytes.

Only after that proof does the workflow optionally attest the generated image and invoke the scoped upload/finalization action with the V5 source document at manifest-path (release.mode: source, runtime.kind: native_image). It imports nothing, and returns the same publication evidence and takes the same publish command as runtime-image.yml: artifact-digest, source-repository, source-ref, source-commit, source-workflow-identity, binding-revision and revocation-epoch. @v1 carries the retired V4 import path. Generated-image attestation defaults on. Set attest-runtime-image: false when GitHub artifact attestations are unavailable, including private repositories whose organization plan does not provide them. This skips only publication of GitHub build provenance for the derived image: Liskov still verifies the caller's GitHub OIDC identity and binds the exact Application, source document, source commit, workflow, and image digest. The base image remains digest- and attestation-verified in both modes. GitHub requires the caller to grant every permission declared by a reusable workflow, so callers must retain attestations: write even when this input is false; the skipped step creates no attestation.

Callers must pin the base image's SHA-256, source commit, repository, and signer workflow. Release callers should use the maintained promoted rootfs; a release-candidate rootfs remains suitable only for an explicitly controlled canary.

permissions:
  attestations: write
  contents: read
  id-token: write

jobs:
  image:
    uses: proof-computer/liskov-github-actions/.github/workflows/cargo-runtime-image.yml@v2
    with:
      application-id: rust-hello-world
      manifest-path: .liskov/rust-hello-world.v5.json # V5 source document
      working-directory: rust-hello-world
      binary-name: rust-hello-world
      install-path: /usr/local/bin/rust-hello-world
      base-image-url: <immutable release asset URL>
      base-image-sha256: <64 lowercase hexadecimal characters>
      base-attestation-repository: proof-computer/liskov-runtime-images
      base-attestation-source-digest: <exact source commit>
      base-attestation-signer-workflow: proof-computer/liskov-runtime-images/.github/workflows/ci.yml
      attest-runtime-image: false # optional; defaults to true
      source-assurance: true # optional; defaults to false

With source-assurance: true, the workflow regenerates and verifies the committed sbom.cdx.json in working-directory before building. It exposes source-digest (git-tree:<sha> of that directory at HEAD) and sbom-path (sbom.cdx.json, relative to that directory) for the Marketplace descriptor's sourceAssurance.sourceDigest and sourceAssurance.sbomRef. Both outputs are empty when the input is omitted or false.

Generate the SBOM locally with Python 3.11 or newer and commit it alongside the source before enabling assurance:

python3 <actions-checkout>/actions/source-assurance/sbom.py --directory <app-dir>

The generator uses pnpm-lock.yaml when present, otherwise Cargo.lock. Cargo registry crates carry pkg:cargo/<name>@<version> package URLs and their lockfile SHA-256 checksums; path and git crates carry no hash. Output is deterministic, and existing pnpm output is byte-for-byte unchanged. The generator records only lockfile facts; it does not infer dependency licences or replace the separate third-party licence inventory required by ADR-0106.

À-la-carte actions

Compose your own job from these (uses: proof-computer/liskov-github-actions/actions/<name>@v2):

Action Kind Does
setup composite pnpm + Node + install --frozen-lockfile
acurast-build composite typecheck → test → build (+ optional smoke)
ipfs-pin JS generate/read exact bytes → Liskov OIDC upload + PROOF gateway verification (explicit ipfs-endpoint selects proxy), or reuse CID → cid/digest/manifest-path/upload-id/upload-path
artifact-pin-attest JS OIDC → one or more manifest-bound POST /api/applications/<id>/artifact-pins/github calls
marketplace-ingest JS OIDC → POST /api/marketplace/ingest
runtime-image-upload JS Hash → scoped Tigris upload bound to a V5 source document (manifest-path) → fresh-OIDC finalize → publication evidence outputs
cargo-runtime-image-build composite + Python Safely overlay one static AArch64 binary and emit a normalized helperless-rootfs-derived tar.xz
source-assurance composite + Python Verify a committed pnpm or Cargo CycloneDX SBOM → relative sbom-path and git-tree source-digest

Versioning

  • Tag releases vX.Y.Z; release.yml moves the vX major tag so consumers pin @v2 and get the latest v2.x. Security-sensitive callers can pin a commit SHA.
  • v1.0.1 adds optional source-commit and signer-workflow verification for runtime images without changing existing direct-URL callers.
  • v1.0.2 adds the default-standard runtime-image bootstrap-mode input; the bridge-probe value remains restricted by Liskov to its exact internal canary workflow.
  • v1.2.0 adds backward-compatible caller-prepared artifact bytes, sanitized Diagnostic metadata, existing-CID gateway verification, multi-target artifact-pin attestation, reusable-workflow outputs, and durable build-manifest run artifacts.
  • v1.2.3 allows Cargo callers to skip publishing derived-image GitHub build provenance while retaining deterministic byte proof, base-image attestation, and Liskov's exact OIDC/manifest/image binding.
  • v1.2.4 lets one attested IPFS bundle target both existing V4 build manifests and identity-only V5 source documents without creating a V4 compatibility draft.
  • v2.0.0 changes the reusable workflow's secret contract, so it is a new major. acurast-app.yml takes its IPFS endpoint as the ipfs-endpoint input and declares exactly two secrets, LISKOV_CODE_ENCRYPTION_KEY and LISKOV_IPFS_API_KEY. ipfs-pin reads its key from LISKOV_IPFS_API_KEY and its endpoint only from ipfs-endpoint; artifact-pin-attest reads LISKOV_ARTIFACT_PIN_URL behind its pin-url input. v1 is frozen at v1.3.2; a caller moves to @v2 and its new names in one change.
  • v2.3.0 removes V4 manifest import: policy-sync.yml, actions/policy-import and the V4 mode of runtime-image-upload. No @v2 caller used them.
  • Reusable workflows reference their own JS actions by the literal @v2 major tag, so a caller pinned to @v2 executes the matching released action surface.

Security posture

Only no-spend (IPFS pin), OIDC attest/ingest, and scoped runtime-image upload operations are exposed; the spend-capable ACURAST_MNEMONIC CLI-upload path is intentionally not ported. Runtime-image sessions require the exact V5 source document under the Application's current source binding, and upload credentials are masked, retained only in memory, scoped to one object, and never emitted as outputs. The attest/ingest/upload endpoints are gated server-side on repository, ref, workflow, and manifest authority.

Development

pnpm install
pnpm typecheck
pnpm build         # bundles each JS action src/ -> dist/index.cjs (committed)
pnpm check:dist    # build + fail if committed dist drifted (CI runs this)

actions/*/dist/index.cjs is committed (the node24 Actions runtime has no deps installed). Edit src/, run pnpm build, commit both.

Encrypted JavaScript delivery

encryption-mode: aes-256-gcm-payload-v1 packages a public bootstrap and an AES-256-GCM encrypted, self-contained CommonJS payload. The payload exports async start(runtime) and uses the handle supplied by the bootstrap. It must not start another runtime. The default none path is unchanged. The historical V4 aes256_gcm whole-bundle requirement remains a different, unsupported format.

jobs:
  artifact:
    uses: proof-computer/liskov-github-actions/.github/workflows/acurast-app.yml@v2
    with:
      app-id: encrypted-worker
      authored-manifest-path: .liskov/encrypted-worker.json
      entrypoint: app.cjs
      encryption-mode: aes-256-gcm-payload-v1
      encryption-secret-id: application-code-key
    secrets:
      LISKOV_CODE_ENCRYPTION_KEY: ${{ secrets.LISKOV_CODE_ENCRYPTION_KEY }}

This path requires a V5 release.mode: source, Node.js, the same authored entrypoint file, and a required configuration.secrets declaration for application-code-key with destination { "kind": "environment", "name": "LISKOV_CODE_KEY" }. Supply canonical standard base64 encoding of a random 32-byte key in the GitHub secret, and install that same value through the existing managed Lockbox secret path. The workflow does not generate, upload or rotate customer secrets, publish policy, or authorize spend. It refuses extra files, prepared archives and reused CIDs in encrypted mode, so those inputs cannot quietly publish plaintext or reuse a nonce.

The runtime needs a processor with a working P-256 grant-response key. The Android implementation requires Android 12 or later, and a DataEncryption advertisement alone does not establish support. Select compatible candidates in the Application manifest. Follow the encrypted JavaScript guide to publish paused, confirm the managed key save, then resume within the reviewed spend cap and verify signed loader and application outcomes.

Actions v2.1.0 embeds runtime SDK v0.3.35. Actions 1.3.2 embeds runtime SDK 0.3.30, including fresh-home creation, CommonJS module loading and bounded failure-phase diagnostics. The public bootstrap locates its runtime home inside the job bundle directory, where Acurast grants filesystem access; it does not depend on device HOME or /tmp. The action embeds the immutable runtime SDK commit recorded in package.json and commits the built public loader beside its action bundle. Only the loader, public descriptor, ciphertext and Acurast manifest enter the ZIP. OIDC evidence binds the ZIP digest and encryptedCode metadata; payload digests are separate from the ZIP digest. The runtime requires the key to have been installed by an authenticated Lockbox grant and verifies both digests and the GCM tag before loading a local file. The fixed AESGCM vector is shared with Rust and the SDK.

The released path completed production acceptance on 6 September 2026: job 160393 verified the managed grant, loaded the encrypted payload and emitted signed application completion. The public guide and capability matrix retain the separate registered V5 customer-release gate. Cargo image confidentiality, PROOF operator blindness and zero-knowledge custody are not established by this loader. A private repository alone does not protect ordinary none artifacts.

License

This repository is licensed under the Functional Source License, Version 1.1, with Apache-2.0 as the future license (SPDX FSL-1.1-Apache-2.0); see LICENSE. The licensor is Moose Labs Ltd. Each version becomes available under Apache-2.0 two years after it is made available.

You may use, copy, modify and redistribute these actions and reusable workflows for any purpose other than a Competing Use as LICENSE defines it. Using them from any customer workflow, including by uses: proof-computer/liskov-github-actions/..., to build, test and publish your own work is not a Competing Use.

The change is forward-only: release tags cut before this license was added keep the terms they shipped with.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages