a3s-code-core defines a closed, bounded admission contract for an immutable
Agent release manifest at .a3s/asset.acl. The current schema identifier is
a3s.code.agent-release.v1.
The release module owns:
- parsing untrusted manifest bytes with explicit limits;
- validating the closed v1 schema and semantic constraints;
- deriving a schema-aware canonical ACL document and release identity; and
- checking the declared protocol and capability requirements before activation.
It does not build an OCI image, start an Agent daemon, implement the declared health endpoints, enforce the shutdown deadline, or certify a deployment as a Runtime Service. Those runtime pieces must be implemented and tested before an Agent release is considered deployable.
The repository fixture at
fixtures/agent-release-contract/.a3s/asset.acl
is a parser and identity contract fixture. Its repeated hexadecimal digests are
test values, not published OCI or provenance digests, so the fixture is not a
deployable release.
Its expected schema-aware identity is
sha256:18a6f165a9dce546db0cc61402f9a55d9be138e5f4e52a7649e0935c51bd504b.
Cross-repository parser tests may use that value to detect canonicalization
drift, but must not treat it as an artifact digest or deployment certification.
A release producer places exactly one agent_release block in
.a3s/asset.acl:
agent_release {
schema = "a3s.code.agent-release.v1"
protocol = "a3s.code.agent.v1"
artifact {
digest = "sha256:1111111111111111111111111111111111111111111111111111111111111111"
media_type = "application/vnd.oci.image.manifest.v1+json"
}
entrypoint {
command = "/usr/bin/a3s-code-agent"
args = ["serve", "--manifest", "/app/.a3s/asset.acl"]
}
health {
transport = "http"
port = 8080
readiness_path = "/health/ready"
liveness_path = "/health/live"
shutdown_grace_seconds = 30
}
storage {
workspace = "ephemeral"
cache = "ephemeral"
persistent_data = "none"
}
capability "runtime.service" {
level = 1
}
provenance "source" {
uri = "https://github.com/A3S-Lab/Code"
digest = "sha256:2222222222222222222222222222222222222222222222222222222222222222"
}
}
Unknown attributes, blocks, calls, and value shapes fail admission. All strings below are measured in UTF-8 bytes.
schema must be exactly a3s.code.agent-release.v1. protocol is independently
versioned and uses the canonical form a3s.code.agent.v<N>, where N is an
integer from 1 through 65,535 with no leading zero. Admission understands that
version syntax; activation still requires an exact match with a protocol the
runtime supplies.
| Field | v1 requirement |
|---|---|
artifact.digest |
Lowercase sha256: plus exactly 64 hexadecimal characters |
artifact.media_type |
Exactly application/vnd.oci.image.manifest.v1+json |
entrypoint.command |
Absolute container path, at most 1,024 bytes, with no whitespace or control characters |
entrypoint.args |
Ordered list of at most 64 strings; each is at most 4,096 bytes and contains no control characters |
The artifact digest is immutable input to the declared release identity. A tag, branch, mutable workspace, or registry label is not accepted as an artifact reference.
| Field | v1 requirement |
|---|---|
health.transport |
Exactly http |
health.port |
Integer from 1 through 65,535 |
health.readiness_path |
Canonical absolute path of non-empty ASCII unreserved segments |
health.liveness_path |
Same path rules as readiness and different from the readiness path |
health.shutdown_grace_seconds |
Integer from 1 through 3,600 |
Path segments may contain ASCII letters, digits, -, ., _, or ~.
Empty, . and .. segments are rejected.
These fields are declarations consumed by a future headless runtime and its controller. Admission does not make either endpoint observable. A conforming runtime must keep readiness false until it can accept work, report liveness without exposing release secrets, stop accepting new work during shutdown, and reach a terminal state within the declared grace period.
The v1 modes are deliberately finite:
| Boundary | Accepted values | Meaning |
|---|---|---|
storage.workspace |
read_only, ephemeral |
The release may receive a read-only workspace or a release-scoped writable workspace that is discarded |
storage.cache |
none, ephemeral |
No cache is mounted, or cache data is disposable |
storage.persistent_data |
none, external |
The release owns no durable data, or durable data is supplied and governed outside the image |
The manifest cannot name host paths, volume identifiers, buckets, tenants, or mutable workspace snapshots. A runtime must reject a deployment it cannot isolate according to these modes.
At least one capability "<name>" block is required. Names are unique and at
most 128 bytes. Each dot-separated segment starts with a lowercase ASCII letter
and continues with lowercase ASCII letters, digits, or -. level is an
integer from 1 through 65,535.
Capability blocks form a schema-declared unordered set. Reordering them does not change canonical bytes or identity. A duplicate name fails admission instead of choosing one occurrence.
A secret block declares a typed injection slot:
secret "provider-api-key" {
target = "environment"
destination = "PROVIDER_API_KEY"
}
secret "signing-key" {
target = "file"
destination = "/run/secrets/signing/key.pem"
}
Secret names use the same bounded dotted-name grammar as capabilities. The accepted targets are:
| Target | Destination |
|---|---|
environment |
POSIX-style uppercase environment name: [A-Z_][A-Z0-9_]*, at most 128 bytes |
file |
Canonical path below /run/secrets/, at most 256 bytes; each non-empty segment contains only ASCII letters, digits, -, ., or _ |
Names and (target, destination) pairs must both be unique. Any secret block
also requires a secrets.external capability declaration.
The release manifest contains only the slot name and injection destination. It must never contain a plaintext value, ciphertext, external secret identifier, vault path, provider resource name, or tenant reference. A deployment controller resolves an external secret outside the manifest and injects only runtime material into the declared environment variable or file. The value must not be copied into artifact metadata, logs, diagnostics, provenance, or health responses.
The schema is closed, so an attempted value, reference, or other extra
field fails before canonicalization. Admission errors expose stable codes and
structural fields or indexes without echoing manifest values.
At least one provenance "<kind>" block is required. Kinds use the bounded
dotted-name grammar and must be unique. Each reference contains:
- an ASCII
httpsURI with a host, or a non-empty ASCIIurnURI; - no credentials, query, or fragment; and
- a lowercase SHA-256 digest in the same form as the artifact digest.
The URI is a locator or subject name; the digest binds the immutable provenance object. Provenance blocks form a schema-declared unordered set.
AgentReleaseManifest::parse and AgentReleaseManifest::from_file apply the
same ACL limits:
| Budget | Limit |
|---|---|
| Document | 64 KiB |
| Nesting depth | 8 |
| Collection items | 256 |
| Token | 8 KiB |
| Diagnostics | 20 |
from_file reads at most 64 KiB plus one byte before rejecting an oversized
file. Invalid UTF-8 fails before ACL parsing.
After schema and semantic admission, Core computes:
canonical_acl = canonical_bytes_with_schema(document, v1_schema)
identity = "sha256:" + sha256(canonical_acl)
The schema declares capability, secret, and provenance occurrences as
unordered sets. Their source order, comments, whitespace, and ordinary ACL
formatting therefore do not affect identity. Ordered values such as
entrypoint.args retain their order. Duplicate set keys are rejected before
identity is returned.
Because artifact.digest and all provenance digests are canonical manifest
fields, the identity binds them. The same admitted manifest and artifact digest
produce the same declared identity. Changing the artifact digest, entrypoint,
health declaration, storage boundary, capability, secret slot, or provenance
changes the identity.
Callers should persist and compare AgentReleaseManifest::identity(), not a
digest of the original source formatting.
The runtime supplies an AgentReleaseCompatibility containing its exact
protocol and unique available capability levels:
use a3s_code_core::release::{
AgentReleaseCapability, AgentReleaseCompatibility, AgentReleaseManifest,
AGENT_PROTOCOL_V1,
};
# fn admit(source: &str) -> Result<(), Box<dyn std::error::Error>> {
let release = AgentReleaseManifest::parse(source)?;
let runtime = AgentReleaseCompatibility::new(
AGENT_PROTOCOL_V1,
[
AgentReleaseCapability::new("runtime.service", 1)?,
AgentReleaseCapability::new("secrets.external", 1)?,
],
)?;
release.verify_compatibility(&runtime)?;
# Ok(())
# }Activation fails when the protocol is not an exact match, a required capability is absent, or the available level is below the required level. Errors expose stable machine codes:
| Condition | Code |
|---|---|
| Protocol mismatch | a3s.code.agent_release.incompatible_protocol |
| Missing or insufficient capability | a3s.code.agent_release.unsupported_capability |
| Invalid semantic field | a3s.code.agent_release.invalid_field |
| Closed-schema rejection | a3s.code.agent_release.schema |
Compatibility verification is an admission gate only. A successful result does not prove artifact availability, secret resolution, storage isolation, process readiness, or Runtime Service health.
The schema and protocol are versioned independently:
schemagoverns manifest syntax, admission rules, canonicalization, and release identity.protocolgoverns the headless Agent request and lifecycle behavior expected from the artifact.
Version-one readers never reinterpret a v1 document with new semantics. Changing any accepted field, mode, limit, required block, set-order rule, canonical byte rule, or identity meaning requires a new release schema identifier. This includes adding an otherwise optional field, because old closed-schema readers would reject it. A new secret target or destination grammar also requires a new schema version.
A breaking headless request, readiness, liveness, shutdown, or exit-semantics change requires a new protocol identifier. Runtimes may support multiple schema or protocol versions explicitly, but they must select by the exact identifier and fail closed when no supported version matches.
Implementation changes that preserve the complete v1 admission set, typed meaning, canonical bytes, identity, and protocol behavior do not require a new identifier. New runtime capability names and higher available capability levels are also compatible because each release declares and verifies its own requirements.