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.
This commit is contained in:
@@ -5,6 +5,11 @@ Status: accepted; portable LAB V1 admission and Worker pull foundations implemen
|
||||
production executor, claim lease, result publication, Worker deployment and
|
||||
live-trigger wiring pending
|
||||
|
||||
Amended on 2026-09-01 by ADR 0048. Capability-aware claims, renewable leases,
|
||||
verified result transport and the durable publication lifecycle are now
|
||||
implemented; physical Worker installation and live-trigger wiring remain outside
|
||||
that source increment.
|
||||
|
||||
## Context
|
||||
|
||||
Observatory must let an operator apply an admitted laboratory setup to a saved
|
||||
|
||||
@@ -4,6 +4,11 @@ Date: 2026-08-31
|
||||
Status: accepted as a backend foundation; production validators, transport and
|
||||
executor wiring remain blocked
|
||||
|
||||
Amended on 2026-09-01 by ADR 0048. Exact built-in validators, package transport,
|
||||
application publication wiring, durable publication status and a typed portable
|
||||
result viewer are now implemented. Installed generic package executors and a
|
||||
physical Worker 006 cutover remain separate acceptance work.
|
||||
|
||||
## Context
|
||||
|
||||
The durable Observatory queue deliberately treats a Worker `succeed` call as a
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user