Agent lease record
One line of the lease journal: what the issuer decided about a lease, and when. Schema set 1.2. Nine events. granted carries the signed lease (and, from a 1.2 writer, the declared manifest and granted_hash, the SHA-256 of the canonical bytes of the GRANTED manifest, so a reader can tie the record to the exact bytes decided); refused carries no lease but a fresh id that names the refusal, the reserved reason codes, and declared_hash, so a refusal weighs the same as a grant (SPEC §5.2); narrowed carries the narrowing summary; attached says how the governor contained the process; revoked names who revoked (by: an ancestor lease or the issuer) and from the record's at the lease opens nothing; stopped, completed (with usage) and expired close it; heartbeat records liveness and never extends expiry. The journal is append-only and every status is folded from these lines. A record MAY carry the same declared_hash and granted_hash as the audit journal's lease.* events, which are the durable copy.
- Schema
lease-record.schema.json, served as JSON at its$id: https://cognitive-delivery.github.io/contract/1.x/lease-record.schema.json- Dialect
http://json-schema.org/draft-07/schema#- Root
- object, open (additional properties are carried)
required:schema_version,at,event,lease_id
conditional requirements (below) - Stability
- none stated at the root; every declared property carries its own
- Properties
- 106 declared: 16 under the root, 90 in definitions
Properties
Every declared property under the root, in the schema's own order. [] is an array's items, .* the shape of every unnamed member, #name a definition. Objects carry additional properties unless a row says otherwise.
| Path | Type | Required | Description | Constraints | Stability |
|---|---|---|---|---|---|
schema_version | string | yes | Contract version this record was written against, major.minor. A reader compares the major only. | pattern ^\d+\.\d+$ | stable |
at | string | yes | When the issuer decided, UTC with milliseconds and Z: the same single form as the lease's own timestamps, because a granted record's at is the lease's issued_at. | pattern ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$ | stable |
event | string | yes | What happened. Closed: a reader folds status from these and an unknown event would have no fold. | one of "granted", "refused", "narrowed", "heartbeat", "attached", "revoked", "stopped", "completed", "expired" | stable |
lease_id | string #uuid | yes | The lease this record is about. For refused, a fresh id that names the refusal; there is no lease. | stable | |
parent_lease_id | string #uuid | The parent the lease was narrowed against, when it has one. | stable | ||
lease | object #lease | when event is "granted" | The signed lease a granted record carries. Inlined from agent-lease.schema.json and held identical to it. | stable | |
declared | object #manifest | The declared manifest, when a granted record carries it: the same shape as the lease's granted manifest, so the narrowing diff is a plain comparison. Inlined from agent-lease-manifest.schema.json and held identical to it. | stable | ||
declared_hash | string #sha256 | when event is "refused" | SHA-256 of the canonical bytes of the manifest as DECLARED: the input identity. Required on refused, where there is no lease to carry it. | stable | |
granted_hash | string #sha256 | SHA-256 of the canonical bytes of the GRANTED manifest (lease.manifest): the enforced identity. A 1.2 writer records it on granted; a reader meeting a granted record without it MAY compute it from lease.manifest. | stable | ||
reasons | array | when event is "refused" | Why a refused record refused: one or more reserved codes. | min items 1 | stable |
reasons[] | string #reasonCode | A refusal reason in one of three reserved forms (SPEC §5.2). R<n>: the specification's own conditions, R1 to R6 today; R7 and above are reserved for the specification to assign, and an issuer MUST NOT mint one, although the pattern admits R7 to R99. runtime_error:<name>: the issuer could not decide (no signing key, parent unknown, schema invalid), named by the issuer. vendor:<vendor>:<code>: anything else, namespaced by whoever defines it. Prose goes in reason, never here, so two implementations can compare refusals by code. | |||
reason | string | when event is "narrowed", "revoked" or "stopped" | Prose: the narrowing summary on narrowed, why on revoked and stopped, and the human reading of the codes on refused. Never prompt text. | max length 500 | stable |
by | string | when event is "revoked" | Who revoked: the lease_id of an ancestor of the revoked lease, or issuer. A reader meeting a by that is neither MUST treat the record as invalid (SPEC §6, rule L3). | any of: (1) #uuid; (2) string, one of "issuer" | stable |
signature | string #sha256 | Optional on revoked: the issuer's HMAC-SHA256 over the record's canonical bytes without signature, under the same key as the lease. | stable | ||
usage | object #usage | What a completed lease consumed, as far as the issuer could measure. Absent fields are unmeasured, not zero. | stable | ||
containment | string | when event is "attached" | On attached: enforced when an OS sandbox confined the process, advisory when only the environment reached it. Never implied: no attached record means no governor attached. | one of "enforced", "advisory" | stable |
platform | string | when event is "attached" | On attached: the platform the process ran on, as the runtime names it (darwin, linux, win32). | max length 32 | stable |
Definitions
The named shapes this schema refers to as #name. A row above that links here is not expanded in place; its constraints are the definition's.
#manifest
An inlined copy of agent-lease-manifest.schema.json (its reference), carried so the schema is self-contained and held identical to its source by npm test.
| Path | Type | Required | Description | Constraints | Stability |
|---|---|---|---|---|---|
#manifestan inlined copy of agent-lease-manifest.schema.json, held identical to its source | object | The declared manifest, when a granted record carries it: the same shape as the lease's granted manifest, so the narrowing diff is a plain comparison. Inlined from agent-lease-manifest.schema.json and held identical to it. | |||
#manifest.schema_version | string | yes | Contract version this record was written against, as major.minor. One rule across every schema (schema set 1.2): a reader compares the major only, and a minor it has not met is additive by the contract's own rule. | pattern ^\d+\.\d+$ | stable |
#manifest.agent | object | yes | Who is asking. name is a label for the cockpit; kind says how the harness runs it; runtime_agent is the existing closed agent vocabulary so a lease keys to the same identity every audit event already carries. | stable | |
#manifest.agent.name | string | yes | Display label for the cockpit, at most 120 characters: the worker role, the session, the plugin. Not an identity, and never a person's name, email address or git identity; this value is recorded in an audit journal that is retained indefinitely. | min length 1 max length 120 | stable |
#manifest.agent.kind | string | yes | How the harness runs this agent. native: inside the kernel's own process. delegated-cli: a spawned coding CLI. plugin: loaded into the host. external: presented over a bridge from outside the harness. | one of "native", "delegated-cli", "plugin", "external" | stable |
#manifest.agent.runtime_agent | string | yes | The closed agent identity vocabulary. unknown is a first-class value, never an error: the kernel looked and could not tell. cdf-native (schema set 1.0, additive) is the harness's own native turn loop: the kernel drives a model provider directly, with no vendor CLI or SDK. | one of "codex-cli", "claude-code", "gemini-code-assist", "github-copilot", "cdf-native", "unknown" | stable |
#manifest.agent.provider | string | The organisation or product providing the agent, when it differs from the runtime. | stable | ||
#manifest.agent.model | object | The model behind the agent, for attribution. Never a prompt, never a key. | stable | ||
#manifest.agent.model.vendor | string | yes | min length 1 | stable | |
#manifest.agent.model.family | string | yes | min length 1 | stable | |
#manifest.agent.model.version | string | stable | |||
#manifest.agent.harness_version | string | Version of the harness that generated or presented this manifest. | stable | ||
#manifest.parent_lease_id | string | The lease this manifest is presented under, when it is a child. A root manifest has none and is narrowed against the workspace policy instead. | pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ | stable | |
#manifest.intent | object | yes | Why the agent is running. purpose is required and non-empty: a manifest with no stated purpose is refused. | stable | |
#manifest.intent.spec_slug | string | The governed spec this run serves, when there is one. | stable | ||
#manifest.intent.task_id | string | The task within that spec, when there is one. | stable | ||
#manifest.intent.purpose | string | yes | One sentence of intent, at most 500 characters. Recorded in the audit journal, so no prompt and no content. | min length 1 max length 500 | stable |
#manifest.allow | object | yes | What the agent asks to be allowed. Every list is required and may be empty; an empty tools list in a GRANTED manifest is a lease that opens nothing, which is why the kernel refuses rather than grants it. Granted lists are always a subset of the parent's. | stable | |
#manifest.allow.tools | array | yes | Governed tool names the agent may call. An empty list in a GRANTED manifest is refused (SPEC §5.2 R2). | stable | |
#manifest.allow.tools[] | string | A governed tool name the agent may call. At most 128 characters of letters, digits, _, ., :, / and -, so a governed cdf_ name and a <server>/<tool> pair both fit. * is refused: a lease that names every tool has not named one, and SPEC §5.2 R2 exists because a grant must say what it opens. Whitespace is refused. | pattern ^[A-Za-z0-9_][A-Za-z0-9_.:/-]{0,127}$min length 1 | ||
#manifest.allow.read_paths | array | yes | Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2-based validator can load the rule: a leading /, any .. segment, a leading ~, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root. | stable | |
#manifest.allow.read_paths[] | string | min length 1 must not match ^/, (^|/)\.\.(/|$), ^~, ^[A-Za-z]:, \\ | |||
#manifest.allow.write_paths | array | yes | Workspace-relative POSIX globs the agent may change. Refused here, without lookahead: a leading /, any .. segment, a leading ~, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root, and a change outside the granted set is a scope violation that revokes the lease. | stable | |
#manifest.allow.write_paths[] | string | min length 1 must not match ^/, (^|/)\.\.(/|$), ^~, ^[A-Za-z]:, \\ | |||
#manifest.allow.hosts | array | yes | Hosts the agent may reach. *.example.com matches api.example.com and not example.com; matching a bare parent domain requires listing it. Enforced by the egress proxy once it keys off the lease. | stable | |
#manifest.allow.hosts[] | string | A host the agent may reach. A lower-case DNS name of one or more labels, an IPv4 literal or localhost (both are DNS-name shaped), with an optional single leading *. label, or the bare *. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs. | pattern ^(\*|(\*\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$min length 1 max length 253 | ||
#manifest.allow.commands | array | yes | Executables the agent may run, by basename. | stable | |
#manifest.allow.commands[] | string | An executable the agent may run. The basename of the executable, at most 128 characters: letters, digits, ., _, +, -. No path separator (identity is the resolved executable's basename, not where it was found), no whitespace (an argument is not part of the name) and no shell operator. | pattern ^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$min length 1 | ||
#manifest.allow.tool_args | object | DECLARED argument constraints per tool (schema set 1.2, development stability): tool name to a JSON Schema the agent proposes for its own calls to that tool, after Progent's argument-schema policies. A declaration, not a grant: in 1.x an issuer MAY omit it from the granted manifest and MUST NOT treat it as granted authority, because the reference gate does not yet evaluate argument schemas and a narrowing rule without an enforcing gate is a claim the corpus cannot test. The vector tool-args-dropped shows the reference dropping it. It becomes a rule when a gate enforces it. | keys must match ^[A-Za-z0-9_][A-Za-z0-9_.:/-]{0,127}$additional properties: see #manifest.allow.tool_args.* | development a declaration an issuer MAY omit from the grant (SPEC 4.4); becomes a rule when a gate enforces it | |
#manifest.allow.tool_args.* | object | A JSON Schema. Not validated as one here: draft-07 cannot validate a schema as data without a meta-schema $ref, which the self-contained rule forbids. | |||
#manifest.deny | object | yes | What the agent must not do even if allow would otherwise permit it. Granted deny lists are the UNION of the declared and the parent's, so a child can never shed a parent's refusal. | stable | |
#manifest.deny.commands | array | yes | Executable names refused outright, for example sudo, su, docker, ssh, curl, wget, launchctl, systemctl. | stable | |
#manifest.deny.commands[] | string | An executable refused outright. The basename of the executable, at most 128 characters: letters, digits, ., _, +, -. No path separator (identity is the resolved executable's basename, not where it was found), no whitespace (an argument is not part of the name) and no shell operator. | pattern ^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$min length 1 | ||
#manifest.deny.paths | array | yes | POSIX globs the agent must not touch: credentials, governance evidence, the lease journal itself. A denial MAY be home-relative (~/.ssh/**), because refusing a path outside the workspace is meaningful even though no allow could grant it; a leading /, any .. segment and any backslash are refused. | stable | |
#manifest.deny.paths[] | string | min length 1 must not match ^/, (^|/)\.\.(/|$), \\ | |||
#manifest.deny.hosts | array | yes | Hostnames refused outright. | stable | |
#manifest.deny.hosts[] | string | A host refused outright. A lower-case DNS name of one or more labels, an IPv4 literal or localhost (both are DNS-name shaped), with an optional single leading *. label, or the bare *. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs. | pattern ^(\*|(\*\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$min length 1 max length 253 | ||
#manifest.budget | object | yes | Ceilings. Every field is optional and non-negative; an absent field means the parent's remaining budget applies. A child's granted budget is clamped to its parent's remaining, depth is decremented per generation and fan_out is decremented on the parent as each child is issued. | stable | |
#manifest.budget.steps | integer | Maximum tool calls or turns. | min 0 max 9007199254740991 | stable | |
#manifest.budget.wall_clock_ms | integer | Maximum lifetime in milliseconds; the lease expires when it is spent. | min 0 max 9007199254740991 | stable | |
#manifest.budget.tokens | integer | Maximum model tokens, input plus output. | min 0 max 9007199254740991 | stable | |
#manifest.budget.cost_usd | number | Maximum spend in US dollars. | min 0 | stable | |
#manifest.budget.depth | integer | How many generations of child lease may be issued beneath this one. Zero means no children. At most 16: the reference root policy allows 4, and a declaration above the ceiling is nonsense rather than something to clamp. | min 0 max 16 | stable | |
#manifest.budget.fan_out | integer | How many child leases may be live under this one at once. At most 256: the reference root policy allows 8. | min 0 max 256 | stable | |
#manifest.approvals | array | yes | Action names that need a human before the agent may perform them, under the same identifier rule as tool names. Required and may be empty. | stable | |
#manifest.approvals[] | string | An action that needs a human first; the same identifier rule as a tool name. At most 128 characters of letters, digits, _, ., :, / and -, so a governed cdf_ name and a <server>/<tool> pair both fit. * is refused: a lease that names every tool has not named one, and SPEC §5.2 R2 exists because a grant must say what it opens. Whitespace is refused. | pattern ^[A-Za-z0-9_][A-Za-z0-9_.:/-]{0,127}$min length 1 | ||
#manifest.attestation | object | Who vouches for the declaration. Optional: an agent presenting its own manifest has nothing to sign with, and the bridge or harness signs on its behalf. | stable | ||
#manifest.attestation.issuer | string | yes | Which party produced the declaration. | one of "agent", "bridge", "harness", "plugin" | stable |
#manifest.attestation.signature | string | Lower-case hex signature over the canonical bytes of the manifest, when the issuer can sign: 64 hex characters, which is HMAC-SHA256 like the lease's own signature. An odd-length or longer value is not a signature this contract defines. | pattern ^[0-9a-f]{64}$ | stable | |
#manifest.attestation.key_fingerprint | string | Fingerprint of the key that produced signature. Never the key. | min length 1 | stable |
#lease
An inlined copy of agent-lease.schema.json (its reference), carried so the schema is self-contained and held identical to its source by npm test.
| Path | Type | Required | Description | Constraints | Stability |
|---|---|---|---|---|---|
#leasean inlined copy of agent-lease.schema.json, held identical to its source | object | The signed lease a granted record carries. Inlined from agent-lease.schema.json and held identical to it. | |||
#lease.schema_version | string | yes | Contract version this record was written against, as major.minor. One rule across every schema (schema set 1.2): a reader compares the major only, and a minor it has not met is additive by the contract's own rule. | pattern ^\d+\.\d+$ | stable |
#lease.lease_id | string | yes | The grant's identity. Lower-case UUID, the same shape as a capability token id. | pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ | stable |
#lease.manifest | object | yes | The GRANTED manifest: the same shape as the Agent Lease Manifest the agent declared, after narrowing. This is a dereferenced copy of agent-lease-manifest.schema.json, inlined because the contract keeps every schema self-contained (a reader compiles each file on its own, with no cross-file references). A test in the consumer asserts the copy is identical to the source. | stable | |
#lease.manifest.schema_version | string | yes | Contract version this record was written against, as major.minor. One rule across every schema (schema set 1.2): a reader compares the major only, and a minor it has not met is additive by the contract's own rule. | pattern ^\d+\.\d+$ | stable |
#lease.manifest.agent | object | yes | Who is asking. name is a label for the cockpit; kind says how the harness runs it; runtime_agent is the existing closed agent vocabulary so a lease keys to the same identity every audit event already carries. | stable | |
#lease.manifest.agent.name | string | yes | Display label for the cockpit, at most 120 characters: the worker role, the session, the plugin. Not an identity, and never a person's name, email address or git identity; this value is recorded in an audit journal that is retained indefinitely. | min length 1 max length 120 | stable |
#lease.manifest.agent.kind | string | yes | How the harness runs this agent. native: inside the kernel's own process. delegated-cli: a spawned coding CLI. plugin: loaded into the host. external: presented over a bridge from outside the harness. | one of "native", "delegated-cli", "plugin", "external" | stable |
#lease.manifest.agent.runtime_agent | string | yes | The closed agent identity vocabulary. unknown is a first-class value, never an error: the kernel looked and could not tell. cdf-native (schema set 1.0, additive) is the harness's own native turn loop: the kernel drives a model provider directly, with no vendor CLI or SDK. | one of "codex-cli", "claude-code", "gemini-code-assist", "github-copilot", "cdf-native", "unknown" | stable |
#lease.manifest.agent.provider | string | The organisation or product providing the agent, when it differs from the runtime. | stable | ||
#lease.manifest.agent.model | object | The model behind the agent, for attribution. Never a prompt, never a key. | stable | ||
#lease.manifest.agent.model.vendor | string | yes | min length 1 | stable | |
#lease.manifest.agent.model.family | string | yes | min length 1 | stable | |
#lease.manifest.agent.model.version | string | stable | |||
#lease.manifest.agent.harness_version | string | Version of the harness that generated or presented this manifest. | stable | ||
#lease.manifest.parent_lease_id | string | The lease this manifest is presented under, when it is a child. A root manifest has none and is narrowed against the workspace policy instead. | pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ | stable | |
#lease.manifest.intent | object | yes | Why the agent is running. purpose is required and non-empty: a manifest with no stated purpose is refused. | stable | |
#lease.manifest.intent.spec_slug | string | The governed spec this run serves, when there is one. | stable | ||
#lease.manifest.intent.task_id | string | The task within that spec, when there is one. | stable | ||
#lease.manifest.intent.purpose | string | yes | One sentence of intent, at most 500 characters. Recorded in the audit journal, so no prompt and no content. | min length 1 max length 500 | stable |
#lease.manifest.allow | object | yes | What the agent asks to be allowed. Every list is required and may be empty; an empty tools list in a GRANTED manifest is a lease that opens nothing, which is why the kernel refuses rather than grants it. Granted lists are always a subset of the parent's. | stable | |
#lease.manifest.allow.tools | array | yes | Governed tool names the agent may call. An empty list in a GRANTED manifest is refused (SPEC §5.2 R2). | stable | |
#lease.manifest.allow.tools[] | string | A governed tool name the agent may call. At most 128 characters of letters, digits, _, ., :, / and -, so a governed cdf_ name and a <server>/<tool> pair both fit. * is refused: a lease that names every tool has not named one, and SPEC §5.2 R2 exists because a grant must say what it opens. Whitespace is refused. | pattern ^[A-Za-z0-9_][A-Za-z0-9_.:/-]{0,127}$min length 1 | ||
#lease.manifest.allow.read_paths | array | yes | Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2-based validator can load the rule: a leading /, any .. segment, a leading ~, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root. | stable | |
#lease.manifest.allow.read_paths[] | string | min length 1 must not match ^/, (^|/)\.\.(/|$), ^~, ^[A-Za-z]:, \\ | |||
#lease.manifest.allow.write_paths | array | yes | Workspace-relative POSIX globs the agent may change. Refused here, without lookahead: a leading /, any .. segment, a leading ~, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root, and a change outside the granted set is a scope violation that revokes the lease. | stable | |
#lease.manifest.allow.write_paths[] | string | min length 1 must not match ^/, (^|/)\.\.(/|$), ^~, ^[A-Za-z]:, \\ | |||
#lease.manifest.allow.hosts | array | yes | Hosts the agent may reach. *.example.com matches api.example.com and not example.com; matching a bare parent domain requires listing it. Enforced by the egress proxy once it keys off the lease. | stable | |
#lease.manifest.allow.hosts[] | string | A host the agent may reach. A lower-case DNS name of one or more labels, an IPv4 literal or localhost (both are DNS-name shaped), with an optional single leading *. label, or the bare *. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs. | pattern ^(\*|(\*\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$min length 1 max length 253 | ||
#lease.manifest.allow.commands | array | yes | Executables the agent may run, by basename. | stable | |
#lease.manifest.allow.commands[] | string | An executable the agent may run. The basename of the executable, at most 128 characters: letters, digits, ., _, +, -. No path separator (identity is the resolved executable's basename, not where it was found), no whitespace (an argument is not part of the name) and no shell operator. | pattern ^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$min length 1 | ||
#lease.manifest.allow.tool_args | object | DECLARED argument constraints per tool (schema set 1.2, development stability): tool name to a JSON Schema the agent proposes for its own calls to that tool, after Progent's argument-schema policies. A declaration, not a grant: in 1.x an issuer MAY omit it from the granted manifest and MUST NOT treat it as granted authority, because the reference gate does not yet evaluate argument schemas and a narrowing rule without an enforcing gate is a claim the corpus cannot test. The vector tool-args-dropped shows the reference dropping it. It becomes a rule when a gate enforces it. | keys must match ^[A-Za-z0-9_][A-Za-z0-9_.:/-]{0,127}$additional properties: see #lease.manifest.allow.tool_args.* | development a declaration an issuer MAY omit from the grant (SPEC 4.4); becomes a rule when a gate enforces it | |
#lease.manifest.allow.tool_args.* | object | A JSON Schema. Not validated as one here: draft-07 cannot validate a schema as data without a meta-schema $ref, which the self-contained rule forbids. | |||
#lease.manifest.deny | object | yes | What the agent must not do even if allow would otherwise permit it. Granted deny lists are the UNION of the declared and the parent's, so a child can never shed a parent's refusal. | stable | |
#lease.manifest.deny.commands | array | yes | Executable names refused outright, for example sudo, su, docker, ssh, curl, wget, launchctl, systemctl. | stable | |
#lease.manifest.deny.commands[] | string | An executable refused outright. The basename of the executable, at most 128 characters: letters, digits, ., _, +, -. No path separator (identity is the resolved executable's basename, not where it was found), no whitespace (an argument is not part of the name) and no shell operator. | pattern ^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$min length 1 | ||
#lease.manifest.deny.paths | array | yes | POSIX globs the agent must not touch: credentials, governance evidence, the lease journal itself. A denial MAY be home-relative (~/.ssh/**), because refusing a path outside the workspace is meaningful even though no allow could grant it; a leading /, any .. segment and any backslash are refused. | stable | |
#lease.manifest.deny.paths[] | string | min length 1 must not match ^/, (^|/)\.\.(/|$), \\ | |||
#lease.manifest.deny.hosts | array | yes | Hostnames refused outright. | stable | |
#lease.manifest.deny.hosts[] | string | A host refused outright. A lower-case DNS name of one or more labels, an IPv4 literal or localhost (both are DNS-name shaped), with an optional single leading *. label, or the bare *. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs. | pattern ^(\*|(\*\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$min length 1 max length 253 | ||
#lease.manifest.budget | object | yes | Ceilings. Every field is optional and non-negative; an absent field means the parent's remaining budget applies. A child's granted budget is clamped to its parent's remaining, depth is decremented per generation and fan_out is decremented on the parent as each child is issued. | stable | |
#lease.manifest.budget.steps | integer | Maximum tool calls or turns. | min 0 max 9007199254740991 | stable | |
#lease.manifest.budget.wall_clock_ms | integer | Maximum lifetime in milliseconds; the lease expires when it is spent. | min 0 max 9007199254740991 | stable | |
#lease.manifest.budget.tokens | integer | Maximum model tokens, input plus output. | min 0 max 9007199254740991 | stable | |
#lease.manifest.budget.cost_usd | number | Maximum spend in US dollars. | min 0 | stable | |
#lease.manifest.budget.depth | integer | How many generations of child lease may be issued beneath this one. Zero means no children. At most 16: the reference root policy allows 4, and a declaration above the ceiling is nonsense rather than something to clamp. | min 0 max 16 | stable | |
#lease.manifest.budget.fan_out | integer | How many child leases may be live under this one at once. At most 256: the reference root policy allows 8. | min 0 max 256 | stable | |
#lease.manifest.approvals | array | yes | Action names that need a human before the agent may perform them, under the same identifier rule as tool names. Required and may be empty. | stable | |
#lease.manifest.approvals[] | string | An action that needs a human first; the same identifier rule as a tool name. At most 128 characters of letters, digits, _, ., :, / and -, so a governed cdf_ name and a <server>/<tool> pair both fit. * is refused: a lease that names every tool has not named one, and SPEC §5.2 R2 exists because a grant must say what it opens. Whitespace is refused. | pattern ^[A-Za-z0-9_][A-Za-z0-9_.:/-]{0,127}$min length 1 | ||
#lease.manifest.attestation | object | Who vouches for the declaration. Optional: an agent presenting its own manifest has nothing to sign with, and the bridge or harness signs on its behalf. | stable | ||
#lease.manifest.attestation.issuer | string | yes | Which party produced the declaration. | one of "agent", "bridge", "harness", "plugin" | stable |
#lease.manifest.attestation.signature | string | Lower-case hex signature over the canonical bytes of the manifest, when the issuer can sign: 64 hex characters, which is HMAC-SHA256 like the lease's own signature. An odd-length or longer value is not a signature this contract defines. | pattern ^[0-9a-f]{64}$ | stable | |
#lease.manifest.attestation.key_fingerprint | string | Fingerprint of the key that produced signature. Never the key. | min length 1 | stable | |
#lease.declared_hash | string | yes | SHA-256, lower-case hex, of the canonical bytes of the manifest as DECLARED, before narrowing. | pattern ^[0-9a-f]{64}$ | stable |
#lease.parent_lease_id | string | The lease this one was issued under. Absent for a root lease. | pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ | stable | |
#lease.issued_at | string | yes | When the lease was issued. One form only, because this field is inside the signed bytes: UTC with millisecond precision and a Z suffix (2026-10-05T06:00:00.000Z), which is what Date.prototype.toISOString() writes. An offset form would hash differently for the same instant, so two implementations could disagree about one signature. SPEC §6 rule L1: expires_at is after issued_at, checked by the runner because a schema cannot compare two fields. | pattern ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$ | stable |
#lease.expires_at | string | yes | When the lease stops opening anything, whatever its recorded status; fixed at issue. One form only, because this field is inside the signed bytes: UTC with millisecond precision and a Z suffix (2026-10-05T06:00:00.000Z), which is what Date.prototype.toISOString() writes. An offset form would hash differently for the same instant, so two implementations could disagree about one signature. SPEC §6 rule L1: expires_at is after issued_at, checked by the runner because a schema cannot compare two fields. | pattern ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$ | stable |
#lease.issuer_session_id | string | yes | The kernel session that issued the lease. | min length 1 | stable |
#lease.key_fingerprint | string | yes | Fingerprint of the signing key. Never the key. | min length 1 | stable |
#lease.signature | string | yes | HMAC-SHA256, lower-case hex, over the canonical bytes of every field except this one. | pattern ^[0-9a-f]{64}$ | stable |
#uuid
| Path | Type | Required | Description | Constraints | Stability |
|---|---|---|---|---|---|
#uuid | string | pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ |
#sha256
| Path | Type | Required | Description | Constraints | Stability |
|---|---|---|---|---|---|
#sha256 | string | pattern ^[0-9a-f]{64}$ |
#reasonCode
| Path | Type | Required | Description | Constraints | Stability |
|---|---|---|---|---|---|
#reasonCode | string | A refusal reason in one of three reserved forms (SPEC §5.2). R<n>: the specification's own conditions, R1 to R6 today; R7 and above are reserved for the specification to assign, and an issuer MUST NOT mint one, although the pattern admits R7 to R99. runtime_error:<name>: the issuer could not decide (no signing key, parent unknown, schema invalid), named by the issuer. vendor:<vendor>:<code>: anything else, namespaced by whoever defines it. Prose goes in reason, never here, so two implementations can compare refusals by code. | any of: (1) pattern ^R[1-9][0-9]?$; (2) pattern ^runtime_error:[a-z0-9_]{1,64}$; (3) pattern ^vendor:[a-z0-9-]{1,32}:[A-Za-z0-9_.-]{1,64}$ |
#usage
| Path | Type | Required | Description | Constraints | Stability |
|---|---|---|---|---|---|
#usage | object | What a completed lease consumed, as far as the issuer could measure. Absent fields are unmeasured, not zero. | |||
#usage.tokens | integer | min 0 max 9007199254740991 | stable | ||
#usage.cost_usd | number | min 0 | stable |
Conditional requirements
What an if/then clause makes required, one line per property and condition value; the same text appears in the Required column above.
| Property | Required | Where |
|---|---|---|
reasons | when event is "refused" | the root |
declared_hash | when event is "refused" | the root |
lease | when event is "granted" | the root |
reason | when event is "narrowed" | the root |
reason | when event is "revoked" | the root |
reason | when event is "stopped" | the root |
by | when event is "revoked" | the root |
containment | when event is "attached" | the root |
platform | when event is "attached" | the root |