Files
NODEDC_MISSION_CORE/docs/adr/0048-independent-installed-lab-packages-and-portable-result-lifecycle.md
T
DCCONSTRUCTIONS a945d665dd feat(observatory): add installed package dispatch and durable publication
Checkpoint existing backend lifecycle changes. Focused verification found nine legacy fixture failures in portable LAB V1 executor/runtime tests; repair follows separately without rewriting this snapshot. ADR date retains its intentional Markdown hard break.
2026-09-02 00:58:37 +03:00

7.0 KiB

ADR 0048: Independent installed LAB packages and portable result lifecycle

Date: 2026-09-01
Status: accepted and implemented as source contracts; no Worker deployment or image migration

Context

The first portable Observatory path still composed M4.9 and LAB V1 through profile-specific builders and a combined Worker release. A Worker could claim a job whose exact executor was not installed, execution success could become terminal before catalog publication, and the UI could open only one hard-coded legacy replay family. Adding another LAB therefore still risked changes across the Worker coordinator, backend publication and frontend viewer.

The target remains observation-only. This decision does not authorize a build, deployment, Docker installation, Worker 006 mutation, K1 command or safety use.

Decision

Installed package boundary

A Worker-local InstalledLabPackageRegistry binds one independently installed package to the exact RunDefinition, RuntimeCandidate and four-digest executor identity. Its manifest contains only reviewed container images, argv, dependency topology, fixed in-container mount targets and immutable asset IDs. It cannot contain a host path, secret, environment, Docker socket or job-provided command.

Every package uses one stable container I/O contract:

  • read-only source at /missioncore/input/source;
  • read-only canonical plan at /missioncore/input/run-plan.json;
  • result package at /missioncore/output;
  • ephemeral work at /missioncore/work.

single-container and fixed-stack are package properties. Exactly one container owns the portable result. All images must already be admitted by the bound RuntimeCandidate, and the package asset inventory must exactly equal that candidate's reusable asset inventory.

Worker composition now exposes one generic package executor factory. A Worker may install any non-empty subset of server-ready definitions. It advertises only the resulting four-digest identities; it is not required to implement every LAB known by the backend. Existing profile-specific builders remain a compatibility path until their images adopt the common package I/O contract.

The generic executor runs a deterministic dependency graph of hardened one-shot containers through the local Docker Engine API. Every container is pinned by image SHA-256, has no network, a read-only root filesystem, no Linux capabilities or privilege escalation, and receives only the declared read-only inputs plus the single shared result output. Memory, CPU, PID, shared-memory, tmpfs, GPU-count and timeout limits are explicit package fields. Exactly one result-writer step must transitively depend on every compute step, after which the Worker verifies the declared result manifest, file lengths, digests and the absence of links or undeclared files before publication can begin.

Worker-local asset bindings translate reviewed controller paths to Docker-host paths. Those host paths never enter the package manifest or queued job. The current package contract intentionally admits ordered offline steps only; a future LAB that genuinely requires simultaneous services needs an explicit new contract instead of silently weakening the isolation boundary.

Capability-aware dispatch

Worker claim protocol v2 sends a bounded, canonical snapshot of installed four-digest executor identities. The queue selects the oldest queued job that matches one of those identities inside the same transaction that creates the claim. An empty snapshot claims nothing. The snapshot is included in the claim request digest, so an idempotency key cannot be replayed with different Worker capabilities.

Claim v1 remains accepted only as a rolling compatibility path and cannot send a capability field. New Worker code always uses v2.

Execution and publication are separate durable lifecycles

Verified upload completion atomically seals execution as succeeded and creates a publication outbox entry. Publication has its own state:

  • not-required for the compatibility path;
  • pending after verified execution completion;
  • failed with bounded error evidence and attempt count;
  • published with timestamp.

A publisher failure no longer rewrites or loses the successful execution. The authenticated Worker API returns the durable job with HTTP 202 and exposes an idempotent publication retry endpoint. Exact retries reuse content-addressed artifacts and the immutable SessionStore projection; they do not rerun the model.

Contract-driven result viewing

Portable publications receive an explicit v2 viewer capability portable-result-review / portable-result / result-defined. The public viewer service resolves that capability, immutable publication provenance and central artifact manifest, then rechecks the JSON result document's length and SHA-256. The Control Station selects either the legacy canonical replay adapter or the portable result adapter from the typed capability. No portable setup ID, LAB ID, source session ID or result-name family selects the viewer.

The UI polls while execution or publication is pending, refreshes the catalog after publication, and keeps legacy and portable setup catalogs independently usable if either endpoint is temporarily unavailable. Catalog merge identity is setup_id, never a display name.

Validator extension

Result validators are registered and selected by exact result-contract SHA-256. The built-in LAB V1 and M4.9 functions remain compatibility registrations. The composition core no longer requires both setup IDs or branches on them, so a future contract can supply another server-owned registration without changing the queue or Worker router.

Compatibility and migration boundary

This source increment does not claim that the currently installed M4.9 or LAB V1 images implement the new common container I/O surface. No synthetic package manifest is checked in for an image that has not been rebuilt and smoked against that surface. The old builders continue to work, and M4.9 startup no longer requires LAB V1 receipt/release environment values.

The next physical migration is deliberately per profile:

  1. make one image emit the existing portable result package through the common I/O contract;
  2. seal its independent installed package manifest and local asset bindings;
  3. run contract composition and a short offline smoke;
  4. advertise only that executor identity in claim v2;
  5. perform a reversible canary without changing another profile.

Consequences

  • A Worker cannot take an unsupported job merely because it is earlier in the queue.
  • Adding a conforming LAB changes its definition, runtime candidate, validator registration and installed package, not the queue protocol or frontend routing.
  • Successful compute and successful catalog publication are both visible and recoverable facts.
  • Portable result review is generic JSON/artifact evidence; richer visual viewers can be added as new typed capabilities without adding setup-name conditionals.
  • The generic package boundary is implemented, while image conformance and physical Worker 006 acceptance remain explicit, unclaimed work.