Files
sencho/e2e
Anso 61b25f48d2 feat(gitops): show per-target image digests and document the drift model (#1984)
* feat(gitops): show per-target image digests and document the drift model

The observed per-service digests already reached the projection, but the
approved side did not, so a reader could see that two identities differed
without ever seeing which digest each side held. The frozen artifact set's
per-service evidence now rides along with the expected identity, and each
target's card compares them per service: the approved digest beside the
running one, the approved child for the platform that node actually runs,
and matched, drifted, unverified, or not compared as the honest answer.

The comparison follows the model's own rules, so the view cannot claim a
match the projection does not, a stale or unqualified identity stays
unverified, a built service is labelled not compared rather than matched, and
a value that is not a content digest is ignored instead of compared.

Adds the drift model reference the model was missing: the seven classes and
what each is measured against, how to read an item, where each surface
shows it, the states that are progress rather than drift, what Sencho cannot
prove, and the four policy domains. The Blueprint page gains the
digest-drift section it never had, trimmed to the Enforce mechanics.

The end-to-end spec pins an approved digest, replaces the running workload
with different content under the same project, and asserts the deployment
reports the drift, the projection carries both digests, and the Drift tab
renders the comparison without adopting what the tag now points at.

* test(gitops): pin the digest drift spec to a resolvable repository

The spec pinned a locally invented repository, e2e-digest-probe:<stamp>@sha256:...,
which is not a reference any registry knows. That works or fails depending on
what the local Docker version decides to do with a tag-and-digest reference
that has no matching repo digest, and the observation filters the running
image's repo digests by the authored repository, so an invented repository can
never produce an observed digest at all. On a runner whose Docker resolves it
differently, the deploy never lands and the spec waits out its poll.

Both sides are now real upstream tags of one repository, so the approved
reference resolves from the local content store without a registry call and
the moved side is a real digest difference. The digests are read from the
daemon per repository rather than trusting Docker's ordering, and the two
sets are asserted disjoint before anything runs, so two tags that resolve to
one manifest fail by name instead of by timeout.

The spec also stopped assuming things it never checked. The baseline is
proven from the daemon, because the container that was actually created
from the approved reference is the ground truth for what is running, and
reading it costs one call where waiting for the projection's observation
costs a reconciler tick. Every poll now asserts its status, so an API error
reports itself instead of surfacing as a timeout, the apply retry is limited
to the stale-preview case the UI asks the operator to confirm again, Docker
calls are bounded, teardown clears its handle only after a confirmed delete
and then checks the containers are actually gone, and a Docker that is
installed but broken fails instead of skipping.

Verified against local dev servers across four runs at 16 to 55 seconds each,
with no leftover images, containers, or override files, and type-clean under a
strict invocation.

* test(gitops): prove Enforce repairs to the approved digest, not the moved tag

The digest drift spec proves detection. It does not prove repair, because it
runs the Blueprint in observe mode, and the Inline path is not the one that
repairs. The Git-managed loop re-observes a drifted target and applies the
configured drift mode, so that is where the repair mechanic lives, and it now
has a test of its own.

The test starts from a genuinely drifted target rather than an assumed one. The
fixture seeds a newer candidate artifact set carrying a moved digest, points
the latest pointer at it while the approved and last-known-good pointers stay
on the original, and records a runtime observation carrying the moved digest.
That starting state is asserted before repair runs, so a fixture that quietly
stopped seeding it fails instead of passing on a correct-looking end state.

Repair then has to do the one thing that matters: hand the deploy the approved
platform child digest, take it from the accepted artifact set rather than the
most recent resolution or the running image, and leave every pointer that says
what was approved exactly where it was. The observation still reads as drifted
afterwards, because repair changes what runs, not what was agreed.

The test is mutation-checked. Pointing repair at the latest artifact set instead
of the expected one makes it fail with the moved digest, which is the bug
this exists to catch.

The taxonomy page also records the settings-only reconcile decision properly:
source drift compares commits, so a change of compose path, env path, or
context directory creates no expected-versus-observed pair and no drift item. It
surfaces as reconcile required, and for a Direct application that is where you
fetch again. The page says plainly that this is a choice about what the word
drift means, not proof that the materialized content was unchanged, because
changing where Compose is read from can change the content.

The portfolio page no longer keeps its own copy of the class list. It names the
classes by reference to the drift model page, so the two cannot disagree.

Verified: backend typecheck and lint clean, 9515 backend tests passing, frontend
typecheck clean with zero lint errors and 3457 tests across 347 files, docs
config parses.

* test(gitops): pin a settings-only reconcile as attention, not drift

The decision is recorded on the taxonomy page: a change to the compose
path, env path, or context directory, with the accepted commit still
matching the ref, creates no source drift item. Source drift compares
commits, and that shape has no expected-versus-observed pair of commits.

That decision needed a test to hold it, and the coverage that existed
asserted the facet status and the offered action without ever asserting that
no source item is emitted. The behavior was therefore unpinned in both
directions: nothing would have failed if the producer started reporting it,
and nothing would have failed if it stopped.

This adds the missing half. The fixture is the decided case exactly: same
commit, different materialization fingerprint. No source drift item, the
facet reads reconcile required, and fetch is offered. It is mutation-checked,
and dropping the commit comparison from the producer makes it fail, so it
holds the line rather than merely restating it.
2026-09-24 19:19:19 -04:00
..