Skip to content

feat(registry): push/pull + run agent/bundle from the registry (typed-artifact schema v2, mounts, filesets)#61

Draft
msa0311 wants to merge 25 commits into
mainfrom
feat/registry-login-policy-artifacts
Draft

feat(registry): push/pull + run agent/bundle from the registry (typed-artifact schema v2, mounts, filesets)#61
msa0311 wants to merge 25 commits into
mainfrom
feat/registry-login-policy-artifacts

Conversation

@msa0311

@msa0311 msa0311 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Client integration between lns and the lns-registry: push/pull every artifact family and container images through one Docker-like verb pair, and run an agent or bundle artifact directly from the registry — including mounting application-layer artifacts (filesets) into the agent. (Registry login/logout is provided by #66, already on main; this PR builds on it.)

Commands

  • lns push <source> <ref> [--family X] [--content <dir|file>]source is a local file → pushed as the typed artifact for the family (inferred from the ref's …/<segment>/… path or --family); source is an image ref → re-pushes an image from lns's cache. --content packs a local dir/file into the artifact's OCI layer (fileset authoring).
  • lns pull <ref> [-o file] — the registry's artifactType/config mediaType decides: a typed artifact is written out (file or stdout); an OCI image is pulled into the cache.
  • lns run <agent-ref> / lns run <bundle-ref> [--mount <ref>[:/path]] — resolve a typed agent or bundle artifact into a concrete run and boot it; --mount attaches an application-layer artifact at the path it declares (see Running artifacts).
  • lns run / image pull now authenticate to the registry too (stored credential + loopback-HTTP).

Generic, file-based: the registry is the schema authority (validates the config blob, MANIFEST_INVALID on failure). All 10 families ride one code path; no per-family Rust models. lns policy allow/deny/list/remove (local editing) unchanged.

Running artifacts

lns run <ref> accepts a typed agent or bundle artifact, not just an OCI image:

  • agent → resolves to its image, command, and full run config — user, ports, volumes — plus its declared credentials.
  • bundle (kind: AgentSystem) → resolves its single agent component (host-qualifying the ref against the bundle's registry), applies the sandbox profile (cpus/memory), and materializes the bundle's egress policy into a run-scoped ephemeral file, leaving the project's lns-policy.yaml untouched.
  • --mount <ref>[:/path] → attaches an application-layer artifact (fileset/model/tool/knowledge) at the path the artifact declares in its mount (or an explicit :/path override). Now an optional override: a fileset the agent needs can ride in the bundle via components.filesets (below), so lns run <bundle> is self-contained. Explicit mounts coexist with bundle-resolved ones.
  • The resolved run config overlays onto RunArgs with explicit CLI flags always winning (--cpus, -p, -v, --cmd, --policy, …).
  • Required credentials with no usable local entry produce a non-fatal lns connect … warning (the sandbox still asks at the boundary) — not a hard block.
  • A plain image or imageless run passes through unchanged; a non-runnable family (policy, tool, …) is refused with a clear message.

Typed-artifact schema v2 (two-class model + mounts)

Adopts lns-registry PR #5's redesigned schema (clean break, v1alpha1). Every family is runtime-layer (applied around the agent) or application-layer (mounted into the agent filesystem). Shared envelope {apiVersion, kind, metadata, mount?, spec} — all family content nests under spec.

  • Families (10): added model + fileset; agent drops isolation (sandbox owns it); refs become ArtifactRef {ref, digest?}; bundle components gains model and filesets.
  • Fileset authoring (push side). lns push --content walks a local dir/file into a single gzip-tar layer and threads it through RegistryClient::push_artifact → the PushArtifact IPC frame → the service's ArtifactRegistry → the OCI manifest, so a fileset's content lands in the registry as real image layers.
  • Mounting (consume side). Application-layer artifacts are materialized into the guest at boot via the existing RuntimeFileSpec→composefs rail. model writes its envelope spec ({provider, model, parameters?}) at /etc/agent/model per the ARTIFACTS.md consume contract (mirrors the policy unwrap); tool/knowledge/fileset have their OCI layers fetched (pull_artifact_layers), gz-sniffed, tar-walked (tar-slip guarded by safe_rel_path), and injected under /etc/agent/tools|knowledge/<name> (or the fileset's explicit mount.path). The thin CLI resolver computes the mount path from each component's config blob and emits RunImageArgs.artifact_mounts {reference, path, read_only}; the service does the pull + untar at boot. read_only is advisory today (the seed lands in the read-only composefs base; per-file RO + overlay-CoW-shadow enforcement is future work).
  • Policy normalization. materialize_policy unwraps the policy envelope's spec and rewrites each spec.integrations ArtifactRef ({ref}) to the bare integration id the supervisor reads, so a bundle policy that references integrations loads instead of failing on a type mismatch.

Architecture

  • lns-policy artifact: family taxonomy (slug / media types / path-segment / inference, two-class is_application_layer) + YAML-or-JSON → JSON to_config_blob; typed deserializers (the registry-contract crate owns the schemas).
  • lns-ipc: PushArtifact {…, layers} / PushImage / Pull; ArtifactMount {reference, path, read_only} on RunImageArgs/RunConfig.
  • lns-service artifact: auth resolution + loopback/LNS_REGISTRY_PLAIN_HTTP protocol; artifact-vs-image decided from the manifest; image push reconstructs from the persisted image record; materialize_mounts pulls + expands application-layer layers into RuntimeFileSpecs folded into the runtime layer.
  • lns-cli registry + run::resolve: thin IPC client; the resolver is a pure, injectable unit driven through the RegistryClient port (fully host-tested with a fake registry, no real I/O).

CLI stays a thin IPC client; the daemon owns the oci-client. Loopback registries (localhost/127.0.0.1/::1) auto-use plain HTTP, like Docker.

Tests / gate

Full local gate green (make lint && make complexity && make coverage, all touched crates 100%, 0 failures). Layer 2: registry.feature round-trips artifacts + image push + family inference; run_agent_ref.feature + run_bundle_ref.feature pin resolution/credential gate/ephemeral policy; run_mount_flag.feature pins --mount landing an ArtifactMount at the fileset's declared path (and an explicit override). Layer 3 covers the family contract, deserializers, blob conversion, the resolver (mapping + overlay precedence + every error path), the content-layer packer, the integration-ref normalizer, service materialization (gz-sniff, tar-walk, tar-slip guard), and the IPC codec. Coverage IGNOREs: the thin IPC leaves (registry/real.rs) and the OCI leaf in image/real.rs.

Live end-to-end verification (PR#5 registry + microVM)

Migrated the hermes demo (hermes-agent/lens/) to schema v2 and a config fileset: hermes is non-compliant (reads only $HERMES_HOME/config.yaml, never /etc/agent/*), so its config.yaml ships as a fileset mounted at /opt/data, replacing the old cp/sed config shim. Verified against the live registry + a real microVM:

  • lns push fileset-config.yaml …/filesets/hermes-config:v1 --content filesets/hermes-config → accepted as 142 bytes + 1 layer; the registry manifest carries the gzip-tar layer; pulled back, it contains config.yaml.
  • lns run …/bundles/hermes-system:v1 --mount …/filesets/hermes-config:v1 → resolved the agent, applied the sandbox (2 vCPU / 3072 MiB) and the materialized egress policy (16 allow rules, integration refs normalized), mounted the fileset at /opt/data, booted, and hermes came up reading the fileset config — its banner showed claude-sonnet-4-6 (the fileset's value, not the image default claude-opus-4.6), and api.anthropic.com traffic showed the anthropic integration injecting its credential.

Digest-pin fix (pull_inner). Digest-pinned pulls previously failed on a phantom mismatch: pull_inner re-hashed a serde re-serialization of the manifest and compared it to the requested digest, which (a) never matches for a multi-arch image — the pin is the index, which the client follows to a platform manifest — and (b) doesn't reproduce the registry's stored bytes, so the computed digest was fictional. The oci-client already verifies the requested digest against the raw bytes on fetch (index + resolved platform manifest), so the redundant re-check was dropped. Digest-pinned pulls, the sandbox baseImage pin, and lns run @sha256:… on multi-arch images now work.

Integrated with current main ✅

Merged current main and reconciled the divergence: dropped our duplicate login in favour of #66 (deleted auth/mod.rs + our colliding registry_auth.rs; adopted main's RegistryAuthStore/credential_for and rewired the service-side auth helpers to it), and ported push/pull/run --mount into main's CommandSpec registry (each is now a *_SPEC with the socket-wiring entry points in the real leaf, mirroring login). Reconciled main's host-bind feature (RunImageArgs.name/binds, the unified mounts: Vec<MountSpec> run flag) alongside our artifact_mounts, and kept our pull_inner digest fix (main still carried the bug). Full gate green (lint + complexity + 100% coverage across all crates); the branch is MERGEABLE.

Deferred (TODO)

  • Local pre-push schema validation against GET /ext/v1/types (today: rely on server MANIFEST_INVALID).
  • Multi-agent bundles.
  • OS keyring; OIDC device/browser login; pushing images straight from the Docker daemon (not just lns's cache).

Phase 1 of the lns-registry client integration: authenticate to any OCI
registry and push/pull a policy as a typed OCI artifact.

- `lns login`/`logout`/`auth list`: store registry credentials in a new
  per-machine 0600 JSON store (`~/.lns-registry-auth.json`) behind a
  `RegistryCredentialStore` seam, kept separate from workload credentials so
  registry tokens never reach a sandboxed workload. Tokens are only accepted
  via `--password-stdin` and are never printed.
- `lns policy push <file> <ref>` / `pull <ref>`: encode a `Policy` as the
  registry's typed config blob (mediaType
  `application/vnd.lens.policy.config.v1+json`, manifest artifactType
  `application/vnd.lens.policy.v1+json`), schema-valid by construction. The
  CLI stays a thin IPC client; lns-service owns the oci-client push/pull and
  resolves `RegistryAuth` from the stored credential by registry host.
- New IPC messages PolicyPush/PolicyPull; Registry auth threaded per-call.

Tests: Layer 2 behaviours for auth and a policy push→pull round-trip; Layer 3
units for artifact encode/decode (incl. JSON-schema validity), the credential
store, the service-side orchestration, and the IPC codec. Full gate green.
@msa0311 msa0311 marked this pull request as draft June 15, 2026 02:02
…IN_HTTP override)

oci-client defaults to HTTPS, so a local/dev registry served over plain HTTP
(e.g. a port-forwarded in-cluster registry on localhost:5000) is unreachable.
Mirror Docker/containerd: a loopback registry (localhost / 127.0.0.1 / ::1) is
spoken to over plain HTTP automatically, with no flag or env var. For the rarer
non-loopback HTTP dev registry, `LNS_REGISTRY_PLAIN_HTTP` is a comma-separated
host[:port] allowlist.

The protocol is derived from the target reference's registry host in the daemon
(which owns the OCI client); `registry_protocol`/`is_loopback_registry` are pure
host-tested helpers, the ref parse + env read live in the IGNORES'd wiring.
@msa0311 msa0311 force-pushed the feat/registry-login-policy-artifacts branch from add2230 to 6442702 Compare June 15, 2026 02:34
@jansav

jansav commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Login part was already implemented in #66

msa0311 added 4 commits June 15, 2026 07:52
…r all families

Replace the policy-only `lns policy push`/`pull` with a single Docker-like verb
pair that works for every typed artifact family (agent, policy, tool, workflow,
sandbox, knowledge, integration, bundle) and, on pull, transparently caches
container images.

- `lns push <file> <ref> [--family X]`: uploads the file (YAML or JSON) as the
  family's config blob; family is inferred from the reference's `…/<segment>/…`
  path or `--family`. The registry validates the blob against its schema.
- `lns pull <ref> [-o file]`: the registry's config mediaType decides — a typed
  artifact is written out (file or stdout); an OCI image is pulled into the cache.
- lns-policy `artifact` module is now the family taxonomy (slug / media types /
  path-segment / inference) + a YAML-or-JSON → JSON `to_config_blob`; the
  policy-specific encoder is gone.
- Generic IPC `PushArtifact`/`Pull` (→ `Pushed`/`PulledArtifact`/`PulledImage`)
  replaces `PolicyPush`/`PolicyPull`; the service `artifact` module generalizes
  the push/pull orchestration; image-vs-artifact is decided from the manifest.

`lns policy allow/deny/list/remove` (local editing) are unchanged. Image push and
threading registry auth into `lns run` image pull are the next stages. Full gate
green; Layer 2 `registry.feature` round-trips policy and agent files.
… + loopback HTTP

Image pulls (lns pull <image>, lns run <image>) now build the OCI client for the
target reference: loopback / LNS_REGISTRY_PLAIN_HTTP protocol selection and the
stored credential for that registry (anonymous if none). Previously image pull was
hardcoded to anonymous HTTPS, so private and localhost registries were unreachable.

- artifact::resolve_auth(reference): best-effort stored-credential lookup for a
  reference's registry, anonymous on any parse/load failure.
- RealRegistry::for_reference(reference): protocol + auth wired from the reference;
  used by image::pull. RealRegistry::new() retired.
…s's cache

`lns push <image-ref> <target-ref>` (a source that isn't a local file) now
re-pushes an image lns has already pulled: the daemon reconstructs it from the
manifest + layer caches and uploads it to the target with the target's stored
credential and loopback/HTTP protocol.

- IPC PushImage{source_reference,target_reference}; service artifact::push_image
  orchestration; RealRegistry::push_image_from_cache reads CachedManifest + layer
  blobs and oci-client `push`es them.
- CLI push() routes a non-file source to push_image. Layer 2 covers it.
@msa0311 msa0311 changed the title feat(registry): lns login + policy push/pull as typed OCI artifacts feat(registry): lns login/auth + unified lns push/pull for all artifacts & images Jun 15, 2026
msa0311 added 3 commits June 16, 2026 15:45
`lns push <cached-image>` only worked for digest-pinned pulls because it
reconstructed the push from the manifest cache, which intentionally skips
mutable tags. Tag-pulled images had no manifest-cache entry, so push failed
with "not in the local image cache" even though the image was cached.

Persist the raw manifest + config blob on PulledImage and in ImageRecord
(both Option + serde default for back-compat; an old record reads as None
and asks for a re-pull), then reconstruct the push from the record plus the
layer cache instead of the manifest cache.
`lns run <ref>` now accepts a typed agent or bundle artifact, not just an
OCI image. An agent reference resolves to its image + command; a bundle
(kind: AgentSystem) resolves its single agent component and materializes the
bundle's egress policy into a run-scoped ephemeral file, leaving the project's
lns-policy.yaml untouched. Required credentials that are not yet provisioned
locally surface a non-fatal connect warning (the sandbox still asks at the
boundary). A plain image or a non-runnable family (policy, tool, …) is
unchanged or refused with a clear message.

Artifact deserializers (AgentArtifact, BundleArtifact) live in lns-policy, the
registry-contract crate; the CLI-side resolver is a pure, injectable unit
driven through the RegistryClient port so it is fully host-tested.
…ifact

The agent artifact now also encodes resources (cpus/memoryMib), sandbox user,
published ports, and volumes; the resolver maps them onto RunArgs (loopback
TCP for ports, MiB memory), with explicit CLI flags still winning. This lets
`lns run <bundle-ref>` reproduce a full run — dashboard port, persistent
volume, and resource sizing — instead of just image + command + credentials.
@msa0311 msa0311 changed the title feat(registry): lns login/auth + unified lns push/pull for all artifacts & images feat(registry): lns login/auth, unified push/pull, and run agent/bundle artifacts Jun 22, 2026
CPU/memory now live solely on the sandbox artifact (the runtime envelope),
not the agent. The bundle resolver pulls the sandbox component and applies its
`resources` (cpu/memory, number or string per the registry schema); the agent
artifact no longer carries resources. A bare agent-ref run, or a bundle with no
sandbox component, falls back to CLI flags or defaults. Explicit --cpus/--mem
still win.

Adds a SandboxArtifact deserializer to lns-policy and Quantity coercion
(reusing cli::parse_mem_arg for string memory). isolation/capabilities/baseImage
are parsed but not yet applied (no RunArgs knob / digest-pinned image pending the
pull_inner fix).
msa0311 added 6 commits June 24, 2026 13:09
…ent isolation

Adopt the lns-registry two-class schema in the artifact contract: add the Model
and Fileset families (now 10), the optional envelope `mount {path, readOnly?}`
(application-layer only), and the ModelArtifact/FilesetArtifact/MountedArtifact
deserializers plus Family::{default_mount_path, is_application_layer}. References
become ArtifactRef {ref, digest?}; bundle components gain a single `model`. The
agent artifact drops `isolation` (now sandbox-only); the sandbox gains tolerant
supervisorVersion/capabilities/baseImage. Consume/mount side follows.
Add ArtifactMount {reference, path, read_only} to RunImageArgs/RunConfig. The
CLI resolver turns a bundle's application-layer components into mounts: it pulls
each component's config blob to read its family + metadata.name + envelope mount,
computes the guest path (canonical default or explicit override; fileset requires
an explicit path), rejects runtime-layer refs and images, and emits an
ArtifactMount. The bundle resolver wires the `model` component through this; the
service-side materialization at boot follows.
artifact::materialize_mounts resolves each ArtifactMount and turns it into a
RuntimeFileSpec: a model artifact's config blob is written at its mount path
(default /etc/agent/model). runtime_layer::for_run takes the extra specs and
folds them into the composefs runtime layer, so the files land read-only in the
guest at boot. Duplicate mount paths are rejected; layer-content families
(tool/knowledge/fileset) are deferred to the next stage with a clear error.
…t root

ArtifactRegistry gains pull_artifact_layers (real impl pulls the artifact
manifest and each layer blob via oci_client). materialize_mounts now handles the
layer-content families: tool/knowledge/fileset layers are gz-sniffed, walked with
a tar-slip guard (safe_rel_path rejects ../, absolute, and non-UTF8 components),
and emitted as RuntimeFileSpecs rooted at the mount path. Empty layer sets warn
and no-op; runtime-layer families are rejected.
resolve_bundle_ref now resolves the model, tool, and knowledge components into
artifact mounts (canonical paths /etc/agent/{model,tools/<name>,knowledge/<name>}
unless the artifact overrides via its envelope mount). Drops the interim
note_skipped_components — these components are applied now, not skipped.
Round-trip against a PR#5 registry surfaced that every family nests its
family-specific fields under spec (the registry rejected a top-level-components
bundle with MANIFEST_INVALID "spec is a required property"). Move BundleArtifact
to spec.components and SandboxArtifact to spec.{isolation,resources,…}; the
resolver reads them accordingly. materialize_policy now unwraps the policy
artifact envelope's spec so the supervisor still gets a flat {network,
integrations} file (falls back to a flat blob for back-compat).
@msa0311 msa0311 changed the title feat(registry): lns login/auth, unified push/pull, and run agent/bundle artifacts feat(registry): login/auth, push/pull, run agent/bundle, and typed-artifact schema v2 Jun 24, 2026

@msa0311 msa0311 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against the lns-registry PR #5 contract (registry-side author here). Overall: this is a faithful, careful adoption of the v2 schema — I'm happy with it. The schema mapping is exact and the mount/untar rail is genuinely well-built. A few points below; none are blockers.

Strong points

  • Schema adoption is exact (lns-policy/src/artifact.rs): all 10 families, correct runtime/application split (is_application_layer), /etc/agent/* defaults matching the registry, ArtifactRef {ref,digest?}, model+fileset, bundle components.model, agent dropped isolation. The generic MountedArtifact reader (just {metadata, mount?}) is a nice way to mount any application-layer artifact without modelling every spec.
  • The untar rail is secure, with defense in depth. Two independent gates — safe_rel_path (lns-service/src/artifact.rs) rejects ../absolute/non-UTF8 tar entries, and normalize_guest_path (runtime_layer/mod.rs) rejects .. again at build time. gz magic-byte sniff, duplicate-mount-path detection, family-gated dispatch (non-mountable family → hard error). This is the part I scrutinized hardest and it's right.
  • Tests are thorough (behaviour features + unit coverage of deserializers, blob conversion, gzip layer expansion, the traversal guard).

Points worth addressing

1. Model mount format — the one real contract decision (let's align).
For model the code mounts the full envelope ({apiVersion,kind,metadata,spec}) at /etc/agent/model, whereas tool/knowledge/fileset mount raw layer files and policy is unwrapped to spec for the supervisor. What a compliant agent reads at /etc/agent/model is the standard, so I've now pinned it in the registry's docs/ARTIFACTS.md ("Mount file format — the consume contract"):

  • model mounts its spec ({provider, model, parameters?}), not the full envelope — so the agent reads config directly without peeling a k8s-style wrapper (mirrors the policy unwrap; identity is already implied by the mount path).
  • tool/knowledge/fileset mount layer files verbatim (no envelope).

PRISM hasn't implemented the layout yet, so we get to define it — proposing spec-only. This would be a ~one-line change here (unwrap spec like materialize_policy does). Happy to discuss if you/PRISM prefer the full envelope.

2. mount.path is now constrained registry-side. I added a schema rule on PR #5: mount.path must be absolute + traversal-free (leading /, no ..). So a bad fileset path now fails at push with a clear message rather than late in normalize_guest_path mid-boot. No change needed here — just a heads-up that the surface tightened.

3. read_only — parsed and threaded, but is it enforced? It reaches RunImageArgs.artifact_mounts{read_only}, but I didn't see it affect the composefs injection (everything goes in at mode & 0o7777). Worth confirming a readOnly: true mount is actually non-writable in the guest, or documenting it as advisory for now.

4. Symlink targets in layers are unsanitized (expand_layer_to_specs) — but they only ever resolve inside the guest's own FS, so not a host escape. Fine to leave; a one-line comment noting it's intentional would help the next reader.

The integrations ArtifactRef-vs-string mismatch you flagged is real and correctly deferred — that's the next thing to land before integrations work end-to-end.

msa0311 added 3 commits June 24, 2026 16:39
…ring)

lns push --content <dir|file> packs the local content tree into a single
gzip tar layer and forwards it alongside the config blob. The layer rail
threads through RegistryClient::push_artifact, the PushArtifact IPC frame,
and the service-side ArtifactRegistry down to the OCI manifest, so a
fileset's content lands in the registry as real image layers.
…r artifact

Mounts a fileset/model/tool/knowledge artifact into the guest at the path
the artifact declares (or an explicit override after :/). Explicit mounts
ride alongside any bundle-resolved ones. This is the interim attach for
filesets until the registry bundle schema grows a filesets component key.
The registry stores policy.spec.integrations as ArtifactRef maps ({ref}),
but the supervisor reads a flat policy with integrations as bare id strings.
materialize_policy now rewrites each {ref} to the integration id (last path
segment, tag stripped) when unwrapping the envelope spec, so a bundle policy
that references integrations loads instead of failing with a type mismatch.
@msa0311

msa0311 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up on the deferred "bundle filesets component key (registry gap) + the paired lns run --mount flag" from this PR's description.

I've closed the registry gap on lns-registry PR #5: the bundle schema now has components.filesets: [ArtifactRef] (it was the only application-layer family missing from bundle composition — model/tools/knowledge were already there). GET /ext/v1/types reflects it, and a fileset-in-bundle validates.

So --mount no longer has to be required — a fileset the agent needs to boot can live in the bundle like everything else. Consume side here:

  • Add filesets: Vec<ComponentRef> to Components (lns-policy/src/artifact.rs) — mirrors tools/knowledge.
  • In the bundle resolver (lns-cli/src/run/resolve.rs), resolve components.filesets[] into RunImageArgs.artifact_mounts the same way the explicit --mount flag does — each fileset mounts at its envelope mount.path (already validated absolute + traversal-free registry-side now).
  • Keep --mount as an optional override for ad-hoc/local use (a fileset not in the bundle, or lns run <agent> without one), with explicit flags winning over bundle-resolved mounts — consistent with how --cpus/-p/-v already overlay.

Net: lns run <bundle> becomes self-contained — no required --mount. Happy to coordinate ordering (registry PR #5 is ready to merge whenever).

msa0311 added 2 commits June 24, 2026 17:52
…pulls

pull_inner re-hashed a re-serialized copy of the manifest and compared it
to the requested digest. For a multi-arch image the pinned digest is the
index, which the client follows to a platform manifest, so the comparison
could never match; worse, serde re-serialization does not reproduce the
registry's stored bytes, so the computed digest was fictional and pinned
pulls failed with a phantom mismatch.

The oci-client already validates the requested digest against the raw
manifest bytes on fetch (index and resolved platform manifest both), so
the redundant re-check only introduced the bug. Drop it and the orphaned
manifest_bytes helper. Fixes digest-pinned pulls, the sandbox baseImage
pin, and lns run @sha256:… on multi-arch images.
…iew feedback)

Addresses lns-registry-author review on PR #61:
- model mounts unwrap the envelope spec, delivering {provider, model,
  parameters?} at /etc/agent/model per the pinned ARTIFACTS.md consume
  contract (mirrors the policy spec unwrap); falls back to the full blob
  when there is no spec.
- bundle resolution consumes the new components.filesets key so a fileset
  the agent needs can live in the bundle; lns run --mount stays an optional
  override. resolve_mount already handles the fileset family.
- document that mount read_only is advisory today (immutable composefs base;
  per-file RO + overlay-CoW-shadow enforcement is future work).
- note that layer symlink targets are intentionally left verbatim — they
  resolve only inside the guest FS and the entry path is safe_rel_path-guarded.
@msa0311

msa0311 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Thanks both — addressed in 8821dd5, with one item tracked as a deliberate follow-up.

@msa0311 (review points)

  1. Model mount → spec-only. Done. Model mounts now unwrap the envelope spec, so /etc/agent/model carries {provider, model, parameters?} directly (mirrors the policy unwrap), per the pinned ARTIFACTS.md consume contract. Falls back to the full blob when there's no spec.
  2. mount.path constraint — noted, no change needed here; thanks for tightening it registry-side.
  3. read_only — confirmed it is not enforced today: RuntimeFileSpec has no RO/mode option and everything injects at mode & 0o7777. Documented as advisory at the materialize site (the seed lands in the read-only composefs base, but per-file RO mode + overlay-CoW-shadow enforcement is future work). Left the field threaded for when we do enforce it.
  4. Symlink targets — added a one-line note that targets are intentionally verbatim: they resolve only inside the guest FS (never the host), and the entry path is already safe_rel_path-guarded.
  5. Integrations ArtifactRef→string — already landed earlier in 1049127 (normalize_policy_integrations rewrites spec.integrations {ref} → the bare id the supervisor reads).

Bundle filesets (your follow-up): done. Components gained filesets: Vec<ComponentRef> and the bundle resolver mounts them at their declared mount.path, so lns run <bundle> is self-contained. --mount stays an optional override — explicit mounts coexist with bundle-resolved ones.

@jansav

You're right — #66 already ships lns login. Our branch carries a duplicate (auth/mod.rs + a colliding registry_auth.rs), and it's also 107 commits behind main, which has since reworked the CLI into the CommandSpec registry (no push/pull module yet). Dropping our login and porting our push/pull/run-resolve commands into that system is a sizable integration, so I'm tracking it as a dedicated follow-up rather than folding it into this round of review fixes. A pre-integration backup of the branch is kept locally.

msa0311 added 2 commits June 25, 2026 13:25
…olicy-artifacts

# Conflicts:
#	crates/lns-cli/src/cli.rs
#	crates/lns-cli/src/lib.rs
#	crates/lns-cli/src/service/orchestrator.rs
#	crates/lns-cli/tests/behaviours/world.rs
#	crates/lns-ipc/src/lib.rs
#	crates/lns-ipc/src/protocol.rs
#	crates/lns-policy/src/credentials.rs
#	crates/lns-policy/src/lib.rs
#	crates/lns-policy/src/registry_auth.rs
#	crates/lns-service/src/image/mod.rs
#	crates/lns-service/src/image/real.rs
#	crates/lns-service/src/ipc/mod.rs
#	scripts/coverage-floor.sh
…rge gaps

Integration follow-ups after merging main:
- run_push/run_pull (socket-wiring command entry points) move to
  registry/real.rs alongside RealRegistryClient (IGNORE'd leaf), matching
  how login/real.rs holds run_login/run_logout; registry/mod.rs keeps the
  pure push/pull + augment/SPEC wiring at 100%.
- add tests for the absolute --content path, the push/pull SPEC registration,
  the model spec-unwrap, and an empty-rel tar entry; restructure the model
  test to drop an unreachable let-else panic arm.
@msa0311

msa0311 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Integrated current main and the branch is now MERGEABLE (was 130 commits behind).

@jansav — our duplicate login is gone: dropped auth/mod.rs + our colliding registry_auth.rs, adopted #66's RegistryAuthStore/credential_for, and rewired the service-side auth helpers to it. lns login/logout are now solely #66's.

Also ported our push/pull/run --mount into main's new CommandSpec registry (socket-wiring entry points live in each module's real leaf, mirroring login), and reconciled the host-bind feature (RunImageArgs.name/binds + the unified mounts: Vec<MountSpec> flag) alongside artifact_mounts. Kept our pull_inner digest-pin fix (main still carried that bug — worth a separate look).

Full local gate is green (lint + complexity + 100% coverage, all crates); CI is running. Ready for another look. 🙏

@msa0311 msa0311 changed the title feat(registry): login/auth, push/pull, run agent/bundle, and typed-artifact schema v2 feat(registry): push/pull + run agent/bundle from the registry (typed-artifact schema v2, mounts, filesets) Jun 25, 2026
msa0311 added 2 commits June 25, 2026 19:48
…olicy-artifacts

# Conflicts:
#	crates/lns-cli/src/cli.rs
…latform coverage

The multi-line matches! in materialize_mounts_writes_a_model_spec_at_its_path
source-mapped to an unexecuted line on Linux (covered on macOS), leaving
artifact.rs at 99.84% in CI. Importing RuntimeSource shortens it to a single
executed line that registers as hit on both platforms.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants