Author a retained Application Manifest V5
This guide covers RC
sha256:549272988045e9357c4945850706569ed8dc7f0c6f419b7cf5c57d54b294bb10.
Capabilities and limits owns the supported
surface; Application Manifest V4 remains supported.
The retained V5 release candidate is deliberately narrow. It supports source or pinned releases, JavaScript or a curated native image, once/continuous/interval execution, at most two jobs on the first public surface, capability minimums, lifecycle timing, managed Service Credit spend, variables, secrets, logging, and Liskov-managed Runtime SSH.
V5 does not contain public ingress, provider integrations, cohort membership,
lifecycle hooks, durable state beyond off, placement diversity rules,
non-managed SSH providers, or self-custody spend. Those concepts require a
future policy version; adding their old draft spellings makes V5 fail closed.
1. Build the checked once-mode starter
The checked
retained V5 starter fixture
is a complete repository root: source, tests, package and lock file, TypeScript
configuration, manifest, and reusable-workflow caller. It makes one GET
request to https://example.com/ and records only the host, success flag, and
HTTP status through managed logs. It sends no customer data and needs no
variable or secret.
Its entrypoint uses @proof-computer/liskov-runtime v0.3.32:
import {bootstrapLiskovRuntime} from '@proof-computer/liskov-runtime';
import {checkExampleDotCom} from './check.js';
const runtime = await bootstrapLiskovRuntime({
component: 'hello-liskov',
logging: {mode: 'required'},
secrets: {mode: 'off'},
});
try {
await runtime.whenReady();
const result = await checkExampleDotCom();
await runtime.log('starter.fetch.completed', result, {
severity: 'info',
labels: {component: 'hello-liskov'},
});
} catch (error) {
await runtime.diagnostics.fatal({
kind: 'explicit',
code: 'starter_fetch_failed',
component: 'hello-liskov',
error,
});
throw error;
} finally {
await runtime.flush();
runtime.stop();
}
Commit this JSON document as .liskov/application-manifest.json:
{
"schema": "proof.liskov.application-manifest",
"schemaVersion": 5,
"applicationId": "hello-liskov",
"metadata": {
"description": "Fetch example.com and record the HTTP result in managed logs."
},
"release": {
"mode": "source"
},
"runtime": {
"kind": "javascript",
"engine": "nodejs",
"entrypoint": {
"file": "bundle.js"
}
},
"execution": {
"mode": "once"
},
"deployment": {
"schedule": {
"duration": "60s"
},
"spend": {
"unit": "service_credit_micros",
"perJob": "50000"
}
},
"state": {
"mode": "off"
},
"observability": {
"logs": {
"enabled": true
}
}
}
state.mode: off is required. It makes the absence of Liskov-managed durable
state explicit. Unknown fields, duplicate JSON/YAML keys, YAML anchors, aliases,
merge keys, tags, and multiple YAML documents are rejected.
From a clean checkout, install the exact lock, test, and build before creating or publishing anything:
pnpm install --frozen-lockfile
pnpm typecheck
pnpm test
pnpm build
test -s dist/bundle.js
Then validate the manifest locally:
proof liskov application manifest validate \
--file .liskov/application-manifest.json \
--json
Use @proof-computer/proof-cli-liskov 0.14.0. Its released tag contains
source commit e135604ed2f6c59ffc737fce5fe08eaa19d77d0c; the earlier v0.7.0 tag does
not. The application source-binding verbs in step 2 are included.
The 60-second schedule is deliberate. The Manifest V5 schema can parse shorter durations, but the Acurast marketplace refuses a job registration below its 60-second provider minimum. Local schema validation alone does not prove that a schedule can launch.
2. Create the Application and bind its exact GitHub source
For release.mode: source, the repository, ref, workflow identity, manifest
path, and artifact digest come from verified GitHub evidence. They do not
belong in the manifest: they are bound to the Application once, and every
build attests them.
Create the Application from identity alone, naming the repository that holds the document. Creation writes no draft and spends nothing:
proof liskov application create hello-liskov \
--repository OWNER/REPO \
--json
Bind the exact source the Application may publish from. This requires an
organization admin; a maintainer cannot retarget source. The first binding
is revision 1; a later change must name the revision it expects, and is
refused if another change landed first:
proof liskov application source-binding set hello-liskov \
--repository OWNER/REPO \
--allowed-ref refs/heads/main \
--workflow-identity OWNER/REPO/.github/workflows/liskov.yml@refs/heads/main \
--manifest-path .liskov/application-manifest.json \
--reason "first binding" \
--yes --json
application source-binding show fetch --json reads the binding, its
revision, and its revocation epoch; application source-binding revoke
withdraws it. A publication whose ref is outside allowedRefs, whose workflow
identity differs, or whose manifest path differs is refused.
Run Application creation and source binding before the first push to main
that contains this workflow. The workflow's first dependent build must see the
existing binding; a successful local build does not create one.
Then let the reusable workflow build, pin, and attest the document on every
push to the bound ref. Its app-id, repository, workflow path, and
authored-manifest-path must be the bound values. The moving v1 tag is
verified at v1.2.4, which contains the retained V5 source binding
(aa1b83f0fd4b08ac33a6c9970d2077885922d79c):
name: Build Liskov Application
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
id-token: write
jobs:
artifact:
uses: proof-computer/liskov-github-actions/.github/workflows/acurast-app.yml@v1
with:
app-id: hello-liskov
working-directory: .
entrypoint: bundle.js
authored-manifest-path: .liskov/application-manifest.json
The workflow uploads the bundle to the Acurast IPFS proxy without spending and
attests the artifact digest, the source commit and ref, and the workflow
identity to Liskov. It publishes nothing. Its run log prints the artifact
digest as Artifact sha256:, and the called workflow exposes it as the
digest output.
3. Publish from the attested build
Publish the exact document the run attested. Every value must match what was bound and attested, or the publication is refused before anything is spent:
proof liskov application policy publish hello-liskov \
--file .liskov/application-manifest.json \
--artifact-digest sha256:ARTIFACT_DIGEST_FROM_THE_RUN \
--source-commit COMMIT_THE_RUN_ATTESTED \
--source-ref refs/heads/main \
--workflow-identity OWNER/REPO/.github/workflows/liskov.yml@refs/heads/main \
--binding-revision 1 \
--revocation-epoch 0 \
--expected-pointer-version 0 \
--yes --json
proof liskov application policy explain hello-liskov --json
proof liskov application status hello-liskov --json
Publishing is the mutation: it commits an immutable effective policy and
begins a spend-bearing deployment under the document's spend. Nothing is
sent without --yes, and the local document is validated first.
--expected-pointer-version is the active policy pointer you observed, 0
for a first publication; a stale value is refused rather than overwriting a
concurrent publication. --binding-revision and --revocation-epoch are the
values source-binding show reports.
Read the canonical explanation rather than recomputing policy client-side. Its
publication, execution, spend-closeout, and managed-SSH sections report
absent, notApplicable, refused, or satisfied with server-owned next
actions.
Execution and spend
Choose one execution arm:
{"mode": "once"}
A once Application runs one job and settles. It does not run again on its own.
To run the same document again today, create a new Application, which counts
against your organization's job slots. A manual re-run verb is planned.
{"mode": "continuous", "until": "2027-01-01T00:00:00Z"}
{"mode": "interval", "every": "6h", "until": "2027-01-01T00:00:00Z"}
until is an optional bound. Durations are one integer plus ms, s, m,
h, or d; compound and unitless values fail.
Every deployment requires schedule.duration and spend. Recurring execution
also requires a rate:
{
"unit": "service_credit_micros",
"perJob": "600000",
"rate": {
"amount": "15000000",
"window": "1d"
}
}
Amounts are non-negative decimal strings, never JSON numbers. Thin V5 supports
only managed-custody service_credit_micros.
One or two jobs
deployment.jobs defaults to 1. The retained contract can parse 1–256, but
the first public capability and entitlement limit is exactly 2. Three or
more fails before spend.
{
"jobs": 2,
"schedule": {
"duration": "1h",
"phasing": {
"mode": "evenly_spaced",
"jobsPerPhase": 1
}
}
}
Use simultaneous to place all jobs in one phase or evenly_spaced to spread
equal phases across the paid window. jobsPerPhase must not exceed jobs and
must divide it exactly. Two jobs do not create a cluster: V5 has no membership,
topology, state replication, or public-service routing contract.
Sizing and processor selection
Omit placement for the open market, select exact processors, or state measured minimums:
{
"processorSelection": {
"mode": "exact",
"processorIds": ["processor-id"]
},
"minimums": {
"memory": "4GiB",
"storage": "16GiB",
"cpuSingleCoreScore": 1200
}
}
Minimums are filters, not reserved resources. Memory is total device RAM; storage is available space. Missing current evidence fails closed. Country, region, WAN-IP, allow/exclude, spread, distribution, and authored evidence profiles are not V5 fields.
Renewal and updates
Lifecycle defaults to immediate update with the predecessor stopping when the
successor is ready. Use run_until_scheduled_end when overlap would be unsafe:
{
"renewal": {
"leadTime": {"mode": "fixed", "duration": "10m"}
},
"update": {
"timing": "next_renewal",
"existingJobs": {"mode": "run_until_scheduled_end"}
}
}
Renewal is valid only for continuous execution. Lead time is automatic or
fixed; update timing is immediate or next_renewal.
Variables, secrets, logs, and Runtime SSH
Literal variables participate in the authored digest. Managed variables resolve at delivery time. Secrets name a server-side secret and an environment or file destination; secret material never appears in the manifest or effective policy.
{
"variables": [
{"source": "literal", "name": "LOG_LEVEL", "value": "info"},
{"source": "managed", "name": "API_ORIGIN", "required": true}
],
"secrets": [
{
"secretId": "api-token",
"required": true,
"destination": {"kind": "file", "path": "/run/secrets/api-token"}
}
]
}
Environment destinations cannot collide. Arrays contain at most 64 entries.
Managed logging has one authored switch: observability.logs.enabled.
Managed Runtime SSH is native-image-only and uses this policy arm:
{
"access": {
"ssh": {
"provider": {"kind": "liskov_managed"}
}
}
}
Keys are not embedded in V5 policy. Read the prepared V5 Managed Runtime SSH procedure before enabling the arm.
Exact reference and release identity
- Application Manifest V5 reference
- Capabilities and limits
- Contract RC:
sha256:549272988045e9357c4945850706569ed8dc7f0c6f419b7cf5c57d54b294bb10 - Manifest schema:
sha256:38ca88eefe599d9a13b0906fb7ae86be002fb7aa15767925a2fe11908fec95da