Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
158 commits
Select commit Hold shift + click to select a range
78999db
test(experiment): define directory format surface
xormania Aug 2, 2026
8e4f67c
feat(experiment): accept authored directory snapshots
xormania Aug 2, 2026
b146298
test(experiment): reject unsafe authored files
xormania Aug 2, 2026
dae2984
feat(experiment): enforce authored file invariants
xormania Aug 2, 2026
eb47d05
test(experiment): harden directory intake boundaries
xormania Aug 2, 2026
1d3b936
test(experiment): define bundled image resolution
xormania Aug 2, 2026
6d59d74
feat(experiment): resolve bundled image selectors
xormania Aug 2, 2026
74c6bb6
docs(experiment): publish authored format contract
xormania Aug 2, 2026
8b29cf0
fix(experiment): keep legacy boundary executable
xormania Aug 2, 2026
7e5c93c
test(experiment): define local bundle manifest
xormania Aug 2, 2026
55adf88
feat(experiment): define local runtime bundle
xormania Aug 2, 2026
058ec65
feat(experiment): expose local installer surface
xormania Aug 2, 2026
1d92642
test(experiment): require installed bundle independence
xormania Aug 2, 2026
a335f6a
feat(experiment): install a user-local Agent Lab bundle
xormania Aug 2, 2026
65e1603
test(experiment): bind initialized home configuration
xormania Aug 2, 2026
a581431
feat(experiment): verify initialized home authority
xormania Aug 2, 2026
942ffe6
test(experiment): require installed Experiment checks
xormania Aug 2, 2026
901d3fd
feat(experiment): run installed checks from the bundle
xormania Aug 2, 2026
f808182
test(experiment): define explicit tool provisioning
xormania Aug 2, 2026
47b49f6
feat(experiment): provision pinned user tools explicitly
xormania Aug 2, 2026
304b4ed
test(experiment): reject symlinked install prefixes
xormania Aug 2, 2026
109e5d1
feat(experiment): contain local installation paths
xormania Aug 2, 2026
d86e2e4
test(experiment): lock local lifecycle routing
xormania Aug 2, 2026
3d10179
test(experiment): define local image catalog lifecycle
xormania Aug 2, 2026
788fc6a
feat(experiment): add the shared local image catalog
xormania Aug 2, 2026
5710a92
test(experiment): bind local catalog resolution
xormania Aug 2, 2026
5aaaf95
feat(experiment): resolve active local image bindings
xormania Aug 2, 2026
ed56b3e
test(experiment): harden catalog tombstone recovery
xormania Aug 2, 2026
d2a34b5
docs(experiment): describe local image mappings
xormania Aug 2, 2026
cee3200
test(experiment): define catalog authority boundaries
xormania Aug 2, 2026
3837125
test(experiment): require installed catalog resolution
xormania Aug 2, 2026
5465c44
feat(experiment): verify durable local image catalogs
xormania Aug 2, 2026
5e57944
docs(experiment): define local catalog authority
xormania Aug 2, 2026
d03fc45
test(experiment): forbid unproven recovery deletion
xormania Aug 2, 2026
54d62da
test(experiment): expose catalog recovery gaps
xormania Aug 2, 2026
e021e8f
test(experiment): cover catalog crash boundaries
xormania Aug 2, 2026
78ebdcc
test(ci): reject summaryless lifecycle subcases
xormania Aug 2, 2026
bc2a1b4
test(experiment): reject impossible bootstrap phases
xormania Aug 2, 2026
5c8e05d
test(experiment): expose catalog recovery races
xormania Aug 2, 2026
87c4439
test(experiment): require restartable catalog cleanup
xormania Aug 2, 2026
6323045
feat(experiment): harden local image catalog
xormania Aug 2, 2026
0971fb1
test(experiment): enforce catalog evidence
xormania Aug 2, 2026
17561e6
docs(experiment): explain catalog recovery
xormania Aug 2, 2026
92766af
test(experiment): define install store contract
xormania Aug 2, 2026
d03e446
feat(experiment): add permit-gated store
xormania Aug 2, 2026
5d9540e
test(experiment): expose store recovery gaps
xormania Aug 2, 2026
0b1cab0
test(experiment): correct selected-entry oracle
xormania Aug 2, 2026
a405ffe
test(experiment): preserve layout drift injection
xormania Aug 2, 2026
2949edb
test(experiment): align drift probe helper contract
xormania Aug 2, 2026
cf80ae3
fix(experiment): recover store publication
xormania Aug 2, 2026
1da3493
test(experiment): verify installed store runtime
xormania Aug 2, 2026
e1e2f0f
test(experiment): kill store contract mutations
xormania Aug 2, 2026
0471a47
test(experiment): prove prepublication durability
xormania Aug 2, 2026
06571e3
test(experiment): require plan identity domain
xormania Aug 2, 2026
9cfe453
test(experiment): expose store boundary failures
xormania Aug 2, 2026
c2e955c
test(experiment): expose store integrity gaps
xormania Aug 2, 2026
1c6a832
test(experiment): route integrity evidence
xormania Aug 2, 2026
08fe3c8
fix(experiment): harden trusted input reads
xormania Aug 2, 2026
319273b
test(experiment): retain safe preflight race
xormania Aug 2, 2026
b0aedf3
test(experiment): bind fixture plan identities
xormania Aug 2, 2026
7c34240
fix(experiment): contain store cleanup
xormania Aug 2, 2026
945eee5
fix(experiment): bind store trust evidence
xormania Aug 2, 2026
9aa9cd0
test(experiment): expose cleanup metadata race
xormania Aug 2, 2026
b46cc09
fix(experiment): reject cleanup metadata drift
xormania Aug 2, 2026
83f447f
docs(experiment): explain installed store
xormania Aug 2, 2026
e69621b
test(experiment): expose authority replacement races
xormania Aug 2, 2026
65f877f
test(experiment): expose durability path substitution
xormania Aug 2, 2026
0feef99
fix(experiment): bind snapshot and store authorities
xormania Aug 2, 2026
5eb440b
test(experiment): bind durability oracle paths
xormania Aug 2, 2026
5fef8a5
test(experiment): expose late store replacement
xormania Aug 2, 2026
8f84674
fix(experiment): revalidate authority before publish
xormania Aug 2, 2026
43ee2e8
test(experiment): require bounded lifecycle overlap
xormania Aug 2, 2026
e242206
fix(experiment): bound lifecycle wall time
xormania Aug 2, 2026
6a761d4
test(experiment): expose lifecycle cancellation leaks
xormania Aug 2, 2026
5506c44
test(experiment): preserve signal-owned evidence
xormania Aug 2, 2026
1d2b43b
fix(experiment): harden lifecycle supervision
xormania Aug 2, 2026
f7ca731
test(experiment): require bounded catalog overlap
xormania Aug 2, 2026
83f99af
fix(experiment): bound catalog wall time
xormania Aug 2, 2026
9020b1a
test(experiment): expose catalog supervision gaps
xormania Aug 2, 2026
cbf1af1
fix(experiment): supervise catalog lanes
xormania Aug 2, 2026
f3bc189
test(experiment): reject trailing status bytes
xormania Aug 2, 2026
f9ad706
fix(experiment): authenticate catalog statuses
xormania Aug 2, 2026
77765b8
test(experiment): require complete catalog cancellation
xormania Aug 2, 2026
6579897
fix(experiment): close catalog cancellation races
xormania Aug 2, 2026
d653fa7
test(experiment): expose threaded cancellation escape
xormania Aug 2, 2026
ed2183c
fix(experiment): traverse threaded descendants
xormania Aug 2, 2026
925f56e
fix(experiment): rescan catalog descendants
xormania Aug 2, 2026
38ce590
fix(experiment): freeze catalog trees top down
xormania Aug 2, 2026
6cb0b84
test(experiment): define zip intake surface
xormania Aug 2, 2026
9911682
test(experiment): preserve source suite ownership
xormania Aug 2, 2026
ffb3e1d
feat(experiment): accept zip checks
xormania Aug 2, 2026
afc8be4
test(experiment): define hostile zip boundaries
xormania Aug 2, 2026
f26bb97
fix(experiment): bound zip archive decoding
xormania Aug 2, 2026
89574fa
test(experiment): require common zip authorization and install
xormania Aug 2, 2026
60de363
feat(experiment): install zip snapshots through the common store
xormania Aug 2, 2026
dfe7356
test(experiment): expose zip output uncertainty
xormania Aug 2, 2026
504d9d2
test(experiment): keep output fault evidence quiet
xormania Aug 2, 2026
398b02e
fix(experiment): fail closed on partial check output
xormania Aug 2, 2026
59dd2df
test(experiment): harden zip intake adversarial evidence
xormania Aug 2, 2026
d9f98b6
docs(experiment): document bounded zip intake
xormania Aug 2, 2026
a83fa18
test(experiment): cover benign zip metadata uncertainty
xormania Aug 2, 2026
effa402
fix(experiment): accept benign zip creator metadata
xormania Aug 2, 2026
7cf6393
test(experiment): require accepted zip mutant outcomes
xormania Aug 2, 2026
1e64c89
test(experiment): use viable zip mutation fixtures
xormania Aug 2, 2026
a1851b1
test(experiment): close zip parser edge contracts
xormania Aug 2, 2026
dc87fb4
fix(experiment): close zip parser edge cases
xormania Aug 2, 2026
b0f2aeb
test(experiment): harden zip evidence harnesses
xormania Aug 2, 2026
588bde1
test(experiment): accept DOS-compatible zip creators
xormania Aug 2, 2026
e18f973
fix(experiment): accept DOS-compatible zip creators
xormania Aug 2, 2026
6a25f72
test(experiment): verify source adapter aggregation
xormania Aug 2, 2026
8ec2e0f
test(experiment): strengthen zip mutation proof
xormania Aug 2, 2026
91fc871
test(experiment): reject Windows zip special types
xormania Aug 2, 2026
33c3d9e
fix(experiment): reject Windows zip special types
xormania Aug 2, 2026
62e654b
test(experiment): isolate zip decoder mutation
xormania Aug 2, 2026
6884eda
test(experiment): reject reserved watchdog status
xormania Aug 2, 2026
3281398
test(experiment): preserve watchdog infrastructure status
xormania Aug 2, 2026
5e82e6f
test(image): bound crash matrix orchestration
xormania Aug 2, 2026
98aa826
test(image): reduce crash matrix contention
xormania Aug 2, 2026
4eec0f0
test(image): reject unsafe crash oracle records
xormania Aug 2, 2026
53c6f2f
test(image): safe-read crash oracle records
xormania Aug 2, 2026
acbcff5
test(image): match catalog snapshot bound
xormania Aug 2, 2026
19c3d8c
test(image): preserve matrix infrastructure status
xormania Aug 2, 2026
c8265e9
test(image): propagate matrix infrastructure status
xormania Aug 2, 2026
6da44b2
test(experiment): define pinned Git CLI surface
xormania Aug 2, 2026
72e3819
feat(experiment): route pinned Git previews
xormania Aug 2, 2026
8a3c77e
test(experiment): define pinned Git object fixture
xormania Aug 2, 2026
af26d9c
feat(experiment): normalize pinned Git objects
xormania Aug 2, 2026
ec998fa
test(experiment): harden pinned Git object graph
xormania Aug 2, 2026
edbce27
fix(experiment): validate GitHub blob wrapping
xormania Aug 2, 2026
24acd34
test(experiment): bound Git provider acquisition
xormania Aug 2, 2026
f9116c4
feat(experiment): isolate Git provider requests
xormania Aug 2, 2026
266716f
test(experiment): distinguish bound Git object loss
xormania Aug 2, 2026
09292de
fix(experiment): classify bound Git object loss
xormania Aug 2, 2026
b3038b6
test(experiment): preserve signals after Git cleanup
xormania Aug 2, 2026
87bdac4
fix(experiment): preserve Git worker signals
xormania Aug 2, 2026
ef49350
test(experiment): require common Git intake pipeline
xormania Aug 2, 2026
af5574b
feat(experiment): install pinned Git snapshots
xormania Aug 2, 2026
1211fed
test(experiment): harden Git provider worker
xormania Aug 2, 2026
5873177
fix(experiment): close Git worker cleanup
xormania Aug 2, 2026
9d0e847
test(experiment): kill Git intake mutants
xormania Aug 2, 2026
53c1956
docs(experiment): define pinned Git boundary
xormania Aug 2, 2026
3d4f6c5
test(experiment): require Git source aggregation
xormania Aug 2, 2026
43f041d
test(experiment): aggregate Git source evidence
xormania Aug 2, 2026
892f00d
test(experiment): close public Git source grammar
xormania Aug 2, 2026
9900d45
fix(experiment): mirror Git provenance grammar
xormania Aug 2, 2026
95e91db
test(experiment): bound Git provider failure paths
xormania Aug 2, 2026
68c0aae
test(experiment): retain Git worker identity through cleanup
xormania Aug 2, 2026
2267304
test(experiment): observe Git workers without reaping
xormania Aug 2, 2026
9ffeb00
test(experiment): prove Git worker ownership lifecycle
xormania Aug 2, 2026
df02b09
fix(experiment): retain Git worker identity through cleanup
xormania Aug 2, 2026
94ace68
test(experiment): require fixed Git worker diagnostics
xormania Aug 2, 2026
9ffe2cf
test(experiment): detect Git tree-entry substitution
xormania Aug 2, 2026
e56ff97
test(experiment): detect Git blob substitution
xormania Aug 2, 2026
82e2573
docs(experiment): state Git provider trust binding
xormania Aug 2, 2026
80ede76
test(experiment): disambiguate signal-state marker
xormania Aug 2, 2026
7decef2
test(image): preserve catalog replay assertions
xormania Aug 2, 2026
2c0285b
test(experiment): classify reserved replay status
xormania Aug 2, 2026
efe86e4
test(experiment): preserve Git lifecycle replay
xormania Aug 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions authorization/experiment/v0alpha1/operator.cedar
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ when
principal.source == "fixed-local-cli" &&
context.bindingVersion == "v0alpha1" &&
resource.planDigest == context.planDigest &&
resource.sourceDigest == context.sourceDigest &&
resource.contractDigest == context.contractDigest &&
resource.contractVersion == "v0alpha1"
};
2 changes: 2 additions & 0 deletions authorization/experiment/v0alpha1/schema.cedarschema
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace AgentLab {

entity RequestedExperimentPlan = {
planDigest: String,
sourceDigest: String,
contractDigest: String,
contractVersion: String,
requestedName: String,
Expand All @@ -20,6 +21,7 @@ namespace AgentLab {
context: {
bindingVersion: String,
planDigest: String,
sourceDigest: String,
contractDigest: String,
},
};
Expand Down
1 change: 1 addition & 0 deletions catalog/experiment-images/v0alpha1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"apiVersion":"agent-lab.experiment-images/v0alpha1","entries":[]}
8 changes: 4 additions & 4 deletions contracts/experiment/v0alpha1/plan.cue
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ contractDigest: string & =~"^sha256:[0-9a-f]{64}$" @tag(contractDigest)
y: _
less: x.name < y.name
}) {
name: member.name
image: member.image
command: member.command
resourceClass: member.resourceClass
name: member.name
requestedSelector: member.image
command: member.command
resourceClass: member.resourceClass
}]
})
})
7 changes: 6 additions & 1 deletion contracts/experiment/v0alpha1/schema.cue
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ import (
#Name: string & strings.MaxRunes(63) &
=~"^[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$"

#Image: string & strings.MinRunes(1) & strings.MaxRunes(255) &
#DigestRef: string & strings.MinRunes(1) & strings.MaxRunes(255) &
=~"^([a-z0-9]+([.-][a-z0-9]+)*(:(?:[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?/)?[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*@sha256:[0-9a-f]{64}$"

#CatalogName: string & strings.MaxRunes(63) &
=~"^[a-z][a-z0-9]*(?:-[a-z0-9]+)*\\.[a-z][a-z0-9]*(?:-[a-z0-9]+)*$"

#Image: close({digestRef: #DigestRef}) | close({catalogName: #CatalogName})

#Argument: string & strings.MaxRunes(1024) & !~"[\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}]"

#Member: close({
Expand Down
43 changes: 32 additions & 11 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,41 @@ capabilities their documented task requires.

See [Development](development.md) and [development-agent configuration](agent-config.md).

## Experiment request preflight
## Experiment planning and installed evidence

`scripts/experiment check [--] MANIFEST` validates the closed `agent-lab/v0alpha1` request with the
repository-pinned CUE contract and emits one canonical, digest-bound `RequestedExperimentPlan`.
`scripts/experiment authorize install [--] MANIFEST` reads the manifest once, derives that same plan
`scripts/agent-lab experiment check` snapshots and validates one closed authored source from a sole
local `experiment.cue`, a bounded sole-member ZIP, or an exact supported public GitHub commit. The
repository-pinned CUE contract emits one canonical, digest-bound `RequestedExperimentPlan`.
`scripts/agent-lab experiment authorize install` reads that snapshot once, derives the same plan
in-process, and asks the repository-pinned Cedar policy whether the fixed local compatibility
principal may submit the exact plan digest.

Both commands are no-effect preflights: they make no container-engine, network, registration, or
runtime changes. A requested name is correlation data, not an installed Experiment identity or
scope. Start, stop, and remove authorization require a future Broker-minted Experiment identity and
Broker-owned ledger, so they are deliberately outside this requested-plan seam. An install permit
only clears the exact requested intent for a future Broker-controlled install path; it neither
actuates the plan nor waives Agent Lab's containment envelope.
For public Git, the fixed TLS-authenticated GitHub API binds the requested commit ID to its returned
root-tree ID. The adapter requires the response to echo that commit and independently recomputes the
returned tree and blob Git object IDs before the authored bytes enter the common planning path.

The preview forms create no durable Agent Lab state; Git previews have only their bounded public
acquisition effect. `experiment install` instead repeats snapshotting, planning, and Cedar
evaluation, then stores the exact permitted evidence in the initialized home. Directory, ZIP, and
Git sources carrying identical bytes converge on the same source, plan, authorization, installation,
and artifact identities; only their closed transport provenance differs. No caller-supplied decision
is accepted. For a local image name, install rechecks the selected entry under the shared catalog
lock before taking the Experiment store lock exclusively; both remain held through durable
no-replace publication. Direct and bundled selectors do not open local catalog state.

The installed envelope contains the exact artifact plus closed plan, decision, provenance, and
receipt records. Its installation key binds source, domain-separated plan, contract, authorization,
and selected-entry identities. The receipt binds the artifact and every other evidence record; its
returned receipt digest binds the receipt itself. An exact retry verifies the envelope and returns
the same identity; a different identity under the same requested name never overwrites it.
`experiment inspect NAME` takes the store lock shared and verifies the complete envelope without
repairing staging.

Installation is persistent onboarding evidence only. It makes no container-engine, network,
registration, image-acquisition, admission, or runtime change and does not waive the containment
envelope. The requested name identifies this stored envelope, not a running Broker identity or
runtime scope. Experiment start, stop, and runtime removal remain future Broker operations; stored
artifact uninstall is also not implemented.

## Workload launch sequence

Expand Down Expand Up @@ -236,7 +257,7 @@ For formal assumptions and limits, read [Security](../SECURITY.md) and the
| Squid and test service | `compose.egress.yaml`, `gateway/squid/` |
| workload container | `compose.agent.yaml` and HOME overlays |
| workload orchestration | `scripts/agent` |
| Experiment request planning and authorization | `scripts/experiment`, `contracts/experiment/`, `authorization/experiment/` |
| Experiment installed evidence | `scripts/agent-lab`, `scripts/experiment.py`, `scripts/experiment_store.py`, contracts, and authorization policy |
| config parsing and validation | `scripts/lib/config.sh` |
| project and secret guards | `scripts/lib/guard.sh` |
| recipe publication | `scripts/lib/allowlist.sh` |
Expand Down
121 changes: 121 additions & 0 deletions docs/experiments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Experiments

An Experiment is authored as data in a directory containing exactly one file, `experiment.cue`, in a
bounded ZIP archive containing that exact sole member, or at one exact commit of a supported public
GitHub repository whose root tree contains that exact sole blob. The file defines one concrete value
named `experiment` in package `experiment`. Agent Lab snapshots the exact authored bytes privately
before evaluating them; extra entries, links, special files, suspicious modes, changing sources,
malformed CUE, and unknown schema fields are refused.

```cue
package experiment

experiment: {
apiVersion: "agent-lab/v0alpha1"
kind: "Experiment"
metadata: name: "example"
spec: members: [{
name: "worker"
image: digestRef: "registry.example/team/worker@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
command: ["serve"]
}]
}
```

Check the artifact or preview its install authorization from the repository:

```bash
./scripts/agent-lab experiment check ./my-experiment
./scripts/agent-lab experiment authorize install ./my-experiment
./scripts/agent-lab experiment check --zip ./my-experiment.zip
./scripts/agent-lab experiment authorize install --zip ./my-experiment.zip
./scripts/agent-lab experiment check --git https://github.com/owner/repository.git --commit <40 lowercase hex>
./scripts/agent-lab experiment authorize install --git https://github.com/owner/repository.git --commit <40 lowercase hex>
```

The check and authorization forms are previews. They create no durable Agent Lab state and do not
invoke Docker or run Experiment content. Git previews do perform the bounded public acquisition
described below. `authorize install` freshly checks the same held source and emits decision evidence
bound to its source, plan, contract, and authorization identities. The decision is not an
installation capability.

Install a freshly checked and permitted artifact, then inspect its stored identity:

```bash
agent-lab [--home /absolute/private/home] experiment install ./my-experiment
agent-lab [--home /absolute/private/home] experiment install --zip ./my-experiment.zip
agent-lab [--home /absolute/private/home] experiment install --git https://github.com/owner/repository.git --commit <40 lowercase hex>
agent-lab [--home /absolute/private/home] experiment inspect example
```

`install` takes one held source snapshot, derives the plan, and evaluates Cedar again in the same
operation without reopening the caller path. It does not accept a saved plan, decision, destination,
or name override. A permit is evidence for that exact candidate only; installation stores the
source, plan, decision, provenance, and receipt without running content, invoking Docker, acquiring
image bytes, or claiming runtime admission. The decision and receipt bind the same domain-separated
plan identity rather than an unframed hash of the JSON bytes.

ZIP intake reads one stable archive into at most 1,048,576 bytes and accepts only stored or deflated
`experiment.cue` data that expands to at most 262,144 bytes. ZIP64, multidisk archives, encryption,
comments, extra fields, alternate paths, extra members, special file types, inconsistent headers,
bad CRC or lengths, truncated streams, and trailing bytes are rejected before CUE evaluation. Agent
Lab never extracts the archive or chooses a destination from caller data. The normalized source
digest is identical to directory intake for identical authored bytes; installation provenance also
records the raw archive byte count and SHA-256 digest. Archive identity does not affect the plan,
authorization binding, installation key, or idempotent cross-transport retry.
Regular-file attributes are interpreted only for Unix and DOS-compatible FAT, NTFS, and VFAT
creator systems; other creator systems are rejected when their member type cannot be proven.

Git intake is Linux-only in this version. It accepts only a normalized, unauthenticated
`https://github.com/<owner>/<repository>.git` URL and one exact lowercase 40-hex SHA-1 commit object
ID. A fixed credential-free GitHub Git Data API client reads that commit, its exact root tree, and
the bound blob under one five-second deadline and a 1,048,576-byte aggregate response cap. It uses
explicit system trust, identity encoding, fixed headers, a private process group, and zero temporary
files. The TLS-authenticated fixed GitHub API is the trust binding from the requested commit ID to
the returned root-tree ID: the response must echo the requested commit, and Agent Lab independently
recomputes the returned tree and blob Git object IDs before accepting their bytes. Redirects,
credentials, mutable refs, alternate protocols or authorities, extra tree entries, and changed bound
objects fail closed. Agent Lab never runs Git, creates a repository, checks out content, follows
submodules, or executes repository data. Provenance records the canonical URL, provider-bound commit
ID, independently verified tree/blob IDs, bounded acquisition facts, and the independent framed
SHA-256 source digest. Git object identity does not replace source identity or change cross-transport
retry.

An exact retry freshly validates and authorizes again, verifies the complete installed envelope,
and returns `changed:false` with the same `installationKey` and `receiptDigest`. The same requested
name with a different installation identity conflicts without overwrite. `inspect` is read-only: it
verifies and reports one installed identity, but never reconciles staging or repairs state. A later
effectful install may recover only recognized, bounded staging left by an interrupted publication;
unknown or ambiguous residue remains in place and returns infrastructure uncertainty.

These preview, install, and inspect commands work from a local installation after `agent-lab init`
and explicit `agent-lab tools provision`. Installed execution verifies and uses its release bundle
and the effective home's pinned tool cache; it does not depend on a source checkout.

Each member selects either an exact digest-pinned OCI reference with `digestRef` or a shared name
with `catalogName`. Shared names have exactly two bounded lowercase components, `<vendor>.<image>`.
The `agent-lab.*` namespace belongs to the release-owned bundled catalog; the catalog is initially
empty. Other valid namespaces belong to the operator-local catalog shared by every Experiment using
the same effective home:

```bash
agent-lab image add vendor.image registry.example/team/image@sha256:<64 lowercase hex>
agent-lab image inspect vendor.image
agent-lab image list [--all]
agent-lab image remove vendor.image --expect sha256:<entry digest>
```

Add records a mapping only. Same-subject add is idempotent; a different subject never overwrites.
Remove uses the active entry digest as a compare-and-swap token, creates a generation-two tombstone,
and makes the name non-reusable in v0. A local name is resolved from one held, verified catalog
snapshot. The plan binds only the selected entry digest, generation, and immutable subject; checked
evidence separately records the held snapshot revision and digest. An unrelated catalog change
therefore changes catalog evidence without changing the selected plan identity.

After a fresh install permit, every selected local entry is checked again under the stable shared
catalog lock. That lock remains held while the Experiment store lock is acquired and through durable
publication. Removal therefore cannot make the selected entry stale during installation. Direct
digest selectors and release-owned bundled names do not open the local catalog on this path.

Catalog membership is naming only, not image presence, admission, safety, or runnable status. See
[`images.md`](images.md) for the exact namespace, mutation, persistence, and failure contract.
78 changes: 78 additions & 0 deletions docs/images.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Local image names

Agent Lab can assign one operator-local name to an already immutable OCI subject:

```bash
agent-lab [--home ABSOLUTE_HOME] image add VENDOR.IMAGE DIGEST_REF
agent-lab [--home ABSOLUTE_HOME] image inspect VENDOR.IMAGE
agent-lab [--home ABSOLUTE_HOME] image list [--all]
agent-lab [--home ABSOLUTE_HOME] image remove VENDOR.IMAGE --expect ENTRY_DIGEST
```

This catalog is shared by every Experiment using the same initialized Agent Lab home. It stores
names and digest-pinned references only. These commands do not call Docker, contact a registry,
download or inspect image bytes, perform admission, or assert that a subject is runnable.

## Names and subjects

A local name has exactly two lowercase ASCII components, `<vendor>.<image>`. Each component is 1–31
bytes, begins with a letter, and may contain digits or single hyphen-separated segments. The whole
name is at most 63 bytes. `agent-lab.*` is reserved for the release-owned bundled catalog and cannot
be added, removed, or shadowed locally.

`DIGEST_REF` uses the same bounded parser as an authored Experiment `digestRef`. It must contain one
exact lowercase `sha256` digest; mutable tags, bare digests, credentials, paths, and ambiguous
references are refused.

## Mutation rules

The first add publishes generation 1. Repeating the same name and subject is an idempotent
`changed:false` success. A different subject conflicts and never overwrites.

Removal requires the active entry digest from add or inspect. An exact compare-and-swap publishes a
generation-2 tombstone. Retrying with the original active digest is idempotent; every other token
conflicts. A tombstoned name cannot be reused or restored in v0alpha1.

`image remove` removes only the local name from future selection. It does not call Docker, remove
runtime image bytes, stop a workload, or delete an installed Experiment envelope. A retained
installed envelope remains inspectable because it binds the exact selected entry identity; a new or
exact-retry install using the tombstoned name fails its liveness check. Runtime image removal and
stored-artifact uninstall are separate future operations, and neither is implemented by this
command.

During `experiment install`, a selected local entry is rechecked after the fresh permit under the
stable shared catalog lock. The lock remains held through Experiment publication, so a concurrent
catalog removal cannot invalidate the selected entry mid-install. This proves naming liveness only;
it does not perform image acquisition or admission.

## Stored authority

The configured images component contains immutable entry and snapshot histories plus one current
pointer:

```text
images/catalog/
|-- current.json
|-- entries/<entry-digest>.json
`-- snapshots/<snapshot-digest>.json
```

Every read holds the stable catalog lock and verifies canonical schemas, record digests, the
reachable transition chain, all physical history, ownership and modes, fixed counts, and byte
bounds. Unsafe, missing initialized, changing, or corrupt authority returns `125`; an unknown or
removed logical name returns `1`.

The initialized-home receipt binds the catalog lock's device, inode, path, and schema. The lock
starts with its schema line and appends exactly one `initialized` line only after the first complete
staged catalog is durable and immediately before its no-replace commit. A matching bounded intent
distinguishes that pre-commit recovery window; replacement or any other bytes fail closed.

Mutations prepare one bounded intent beneath `images/.staging/`. A first catalog uses Linux
no-replace directory publication. Later changes durably publish immutable records before atomically
advancing and syncing the current pointer. The next mutation reconciles a recognized interrupted
intent against the observed pointer. After it proves that cleanup is safe, it durably renames the
operation to one bounded cleanup wrapper before removing any contents, so a crash during cleanup is
restartable. Unknown or unsafe residue is preserved and returns `125`.

This is tamper-evident state for a cooperative local account, not protection from a hostile process
running as the same user.
Loading
Loading