Retained Application Manifest V5 reference
This reference covers RC
sha256:549272988045e9357c4945850706569ed8dc7f0c6f419b7cf5c57d54b294bb10.
Capabilities and limits owns current availability.
Existing Application Manifest V4 Applications keep
running until they are retired; new publication is V5.
For a guided path, see Author a retained Application Manifest V5.
Contract identity
| Artifact | Exact identity |
|---|---|
| RC domain | proof.liskov.policy-v5-rc.v1 |
| RC digest | sha256:549272988045e9357c4945850706569ed8dc7f0c6f419b7cf5c57d54b294bb10 |
| Manifest schema | sha256:38ca88eefe599d9a13b0906fb7ae86be002fb7aa15767925a2fe11908fec95da |
| Effective-policy schema | sha256:5907054022521f9926164d1e899fa89ecf931ea916da5d7989a6c58015053c30 |
| Retained corpus | 25 manifests from liskov-examples@880ea6d8b6fb3bb9d0ff7b70cb6cd2c0cc6a0451 |
| Implementation closeout | 31 retained rows, implemented dark |
| Production registration | v4_and_v5 (handler generation 96, verified 2026-09-04) |
| Activation authorized | true |
The generated authored schema is served read-only at:
GET /api/application-manifest/v5/schema
The route is available to authoring tools. A schema response describes the exact document shape; publication and execution still apply capability, entitlement, evidence, and spend gates.
Conventions
- Fields marked required must be present. Unknown fields fail closed.
- JSON and YAML are accepted. Duplicate keys fail in either form. YAML anchors, aliases, merge keys, explicit tags, and multiple documents fail.
- Durations are one integer plus
ms,s,m,h, ord. - Byte sizes are one integer plus
B,KiB,MiB,GiB,TiB,KB,MB,GB, orTB. - Money values are decimal strings matching
0|[1-9][0-9]{0,24}. - UTC bounds use exactly
YYYY-MM-DDTHH:MM:SSZand must be real dates. - Tagged unions are closed; a discriminator never permits fields from another arm.
Root document
| Field | Required | Type and rule |
|---|---|---|
schema | yes | proof.liskov.application-manifest |
schemaVersion | yes | integer 5 |
applicationId | yes | organization-local slug, 1–63 lowercase letters/digits/hyphens |
metadata | no | authored-only description and labels |
release | yes | source or pinned artifact |
runtime | yes | JavaScript or curated native image |
execution | yes | once, continuous, or interval |
deployment | yes | jobs, paid window, placement, lifecycle, spend |
access | no | retained managed Runtime SSH only |
configuration | no | variables and secrets |
observability | no | managed logging |
state | yes | exactly { "mode": "off" } |
debug | no | diagnostic hold, fixtures only |
metadata.description is at most 500 characters. metadata.labels has at most
32 values matching [a-z0-9][a-z0-9._-]{0,62}. Metadata does not enter the
effective policy digest and cannot re-run a one-shot application.
release
mode | Required fields | Meaning |
|---|---|---|
source | none | Build from exact server-bound GitHub evidence |
pinned | artifact.digest | Use one artifact by sha256:<64 lowercase hex> |
Source builder repository, ref, workflow, and manifest path are server-side authority. A pinned artifact contains only its digest; CID, encryption mode, and artifact kind are resolved evidence.
runtime
JavaScript
{
"kind": "javascript",
"engine": "nodejs",
"entrypoint": {"file": "bundle.js"}
}
engine defaults to nodejs. The entrypoint is relative to the immutable
artifact root, cannot begin with /, and cannot contain a .. segment.
Native image
{
"kind": "native_image",
"image": {
"catalog": "liskov-runtime-images",
"name": "debian-trixie",
"version": "0.1"
},
"entrypoint": {
"executable": "/bin/sh",
"args": ["/app/start.sh"]
}
}
image.catalog defaults to liskov-runtime-images. Catalogue and image names
are 1–63 lowercase letters/digits/hyphens and are checked server-side. Version
is a one- to three-part numeric floor, resolved to an exact digest. Entrypoint
arguments have at most 64 items. No shell string or inline program is parsed.
execution
mode | Fields | Meaning |
|---|---|---|
once | none | One occurrence, then terminal |
continuous | optional until | Successive paid windows until the bound or indefinitely |
interval | required every, optional until | New occurrence on a cadence |
A settled once application does not run again on its own, and Liskov never
re-runs it as a recovery retry. The supported way to run the same document
again today is a new Application, which counts against your organization's
job slots; an explicit re-arm of the same Application is release-gated, and
Capabilities and limits owns its current boundary.
interval
Fixed-interval execution is release-gated: the schema accepts it, but Liskov does not launch an interval Application yet. Check Capabilities and limits before relying on it.
every is a duration, such as 1h. until is an optional UTC bound; no
occurrence starts at or after it. Boundaries are fixed durations in UTC, so
there is no time zone, daylight-saving, or calendar arithmetic. The accepted
behavior is:
- at most one occurrence, and one Service Credit reserve, for each boundary, across restarts and concurrent Liskov workers;
- no overlap: a boundary that arrives while the previous occurrence is still active is skipped and recorded, with no reserve and no job;
- a boundary that cannot start in time is recorded as missed rather than run late; and
- no catch-up burst: after a pause or a Liskov outage, the schedule resumes at the next future boundary.
A due boundary is not spend authority. Placement, balance, spend limits, and retirement still apply to each occurrence. Cron expressions, calendar rules, catch-up queues, overlap modes, and manual runs of an interval Application are not part of V5.
deployment
| Field | Required | Type, default, and bound |
|---|---|---|
jobs | no | integer 1–256, default 1; first-public admission maximum 2 |
schedule.duration | yes | duration of one paid job window |
schedule.phasing | no | simultaneous or evenly_spaced; default behavior is simultaneous |
placement | no | capability minimums and/or an exact processor pool |
lifecycle | no | renewal and update policy |
spend | yes | managed Service Credit authority |
For evenly_spaced, jobsPerPhase defaults to 1, must not exceed jobs, and
must divide it exactly. simultaneous accepts no jobsPerPhase.
placement
minimums contains at least one of:
| Field | Meaning |
|---|---|
memory | total device RAM floor as a byte size |
storage | available storage floor as a byte size |
cpuSingleCoreScore | integer benchmark floor, 1–9,007,199,254,740,991 |
cpuMultiCoreScore | integer benchmark floor, 1–9,007,199,254,740,991 |
processorSelection has mode: exact and 1–64 processorIds. Omitting it
uses open-market selection. Authored evidence profiles, allow/exclude rules,
spread, distribution, topology, and manager/static selectors are absent.
An exact processorIds list is the allowed pool for each job. The length of
the list is independent of deployment.jobs: one job may name several
processors, and a list longer than the job count stays valid. The list and
the job count do not have to match, and the order of the ids is not placement
priority. Extra ids are further candidates. They are not discarded, and they
are not a separate placement mode.
Choosing an eligible processor from that whole pool is release-gated. The manifest accepts the list. Do not rely on a later id being selected when an earlier one is unavailable until that serving behavior is released. Capabilities and limits records the exact selection you can use today, including warmup.
When that selection is released, Liskov chooses one eligible processor from the complete list for each initial launch, renewal, and permitted recovery. One job can allow three processors. If the first has a stale heartbeat and the second fails a hard gate, such as attestation, version, schedule, or price, Liskov can select the third when that processor is eligible. A processor that fails a capacity minimum is skipped the same way. Missing current evidence fails closed. When no listed processor is eligible, Liskov does not fall back to the open market and does not admit the job on a processor outside the list.
Once a job has been admitted, that selection stays fixed for the occurrence. A cheaper processor that becomes eligible later does not replace it. The next choice is made when the job's normal lifecycle admits a new occurrence.
An exact processor ID can be assigned directly while Acurast reports
WarmupUntil; this does not make it eligible for open-market selection.
Liskov still checks the selected processor's attestation, version, modules,
heartbeat, schedule, consumer access, and price before registration.
lifecycle
| Field | Values and default |
|---|---|
renewal.leadTime | automatic (default) or fixed with required duration |
update.timing | immediate (default): the successor starts at the next executor pass; next_renewal: one renewal lead before the running job's scheduled end |
update.existingJobs.mode | stop_when_successor_ready (default) stops the old job once the new runtime is ready; run_until_scheduled_end lets it run until its scheduled end |
An executor pass is the next time Liskov checks the Application and starts
work that is due. A renewal lead is how long before the scheduled end Liskov
starts the successor when timing is next_renewal. The scheduled end is the
end time registered for the current job's window. A job that has failed, or
that Liskov has lost contact with, is not waited for in either mode when
timing is immediate.
Renewal is valid only for continuous execution. If existingJobs is present,
its mode is required; an empty object fails.
spend
| Field | Required | Rule |
|---|---|---|
unit | yes | exactly service_credit_micros |
perJob | yes | total exposure for one delivered occurrence |
rate.amount | for recurring execution | lifetime ceiling expressed over a window |
rate.window | no | duration; defaults to 30d |
continuous and interval require rate. The organization guard is not a
fallback for an omitted authored rate.
access
The only retained access arm is:
{
"ssh": {
"provider": {"kind": "liskov_managed"}
}
}
It is valid only with runtime.kind: native_image and requires both the Runtime
SSH capability and a Developer-or-above entitlement. No authorized
keys, port, mode, or alternate provider is authored. Organization operator keys
are snapshotted into an exact job attachment by the server.
configuration
variables and secrets each contain at most 64 entries.
| Variable source | Required fields | Optional fields |
|---|---|---|
literal | name, value | none |
managed | name | required (default true), default |
Environment names match [A-Z_][A-Z0-9_]{0,127}.
| Secret field | Rule |
|---|---|
secretId | 1–127 letters/digits/dot/underscore/hyphen |
required | defaults to true |
destination | environment with name, or file with absolute path |
An environment name may be claimed only once across variables and secret destinations.
observability, state, and debug
observability.logs.enabledis the only authored logging field.stateis required and has onlymode: off. This opts out of durable storage; it does not pause the Application or disable continuous execution. Pause is a separate Application lifecycle action.debug.holdOnFailuredefaults to false and is reserved for diagnostic fixtures, not ordinary customer operation.
Deferred to a future policy version
The following are not gated V5 features; they are absent from the exact schema:
- public or provider-owned ingress;
- provider integration blocks;
- cohort membership and discovery;
- join/drain hooks and health probes;
- durable volumes, snapshot, or restore;
- placement allow/exclude, diversity, spread, or distribution;
- Tailscale, tunnel, or other SSH providers; and
acu_planckor other self-custody spend.
Their old draft spellings are rejected as unknown fields or closed-union arms.
Registered publication setup hold
The registered POST /api/applications/{id}/policy-versions request accepts
dryRun: true to compile and resolve the submitted document and release
evidence, then roll back without committing policy, pointer or wakeup. Its
response adds dryRun: true; an ordinary committed response remains unchanged.
This does not authorize a schema pair that the server's current generation
has not enabled.
postPublishStatus: "paused" plus a trimmed 1–500-character reason commits
a paused Application in the same transaction as publication. The reason and
actor are retained, and the setup hold appears in Application activity. The
fields must be provided together; active is not an accepted post-publication
option. Omitting them preserves normal publication, including activation of a
previously disabled Application and preservation of an existing pause.
expectedActivePointerVersion remains the optimistic concurrency fence.
Use it for both preview and confirmation. invalid_publication_pause is a
400 response for an invalid reason/pair; active_pointer_version_conflict
requires fresh review. CLI application policy publish --dry-run and
--paused --reason TEXT expose these fields from version 0.13.0.