EVD-9 window 20260905-evd9 — independent operator review

Prepared 2026-09-05 by a second live-deploy operator. This is a REVIEW of a window another lane is executing, not a competing plan and not authorization to run anything.

The window is owned and executed by the SPRINT_20260824_010 P8-8 lane, which set SPRINT_20260722_011 EVD-9 to DOING in 0ac6d5e237 on the owner’s authorization. This reviewer executed nothing: no container, database, route, grant, volume, network or image was created, changed, stopped or recreated, and neither the runbook nor the sprint row was edited. Every measurement below is read-only.

Step authority remains evidence-evd9-data-move.md. Where this review and that runbook disagree, the runbook is what the operator runs; the findings here are proposed corrections for its owner to fold in. The executing lane owns that file in-window.

Scope, as commissioned: an independent read of the same receipts, the runbook defects found offline, the corrected scope mapping and negative control, the ROA-1 registration inventory for the Evidence family, and a pass over runbook sections 8-13 for steps that cannot be performed as written.


1. Window state at review time

Reconstructed from receipt mtimes under the gitignored tmp/evd9-20260905-evd9/ (host clock UTC+03:00; UTC is local minus three hours). The window had not reached its point of no return.

LocalPhaseEvidence
09:18:42Row claimed DOINGcommit 0ac6d5e237
09:22:54-09:24:45Preflight A: repo status, build-boundary, P19 transition, live topology, rollback capture, database and source-count censuspreflight/{repository-status,build-boundary,evidence-p19-transition,containers-before,volumes-before,databases-before,source-real-counts-nonzero}, rollback/*.ABSENT.txt, rollback/stellaops-{attestor-tileproxy,tsa}.*
09:41:23-09:47:26Preflight B: HEAD pin, digest-pin override, window approval, Authority descriptors/tests/token/feed, source grant census, both grant planspreflight/{repository-head,evidence-digest-pin.override.yml,window-approval,authority-evidence-*,source-grants-before,grants-forward.sql,grants-rollback.sql}
09:49:32Gateway rollback capturerollback/stellaops-router-gateway.*
09:57:04Three-schema full dump superseded (see D-G)backup/source-domain-full.dump.three-schemas-superseded
09:58:25Writer fence, absent-predecessor variantpreflight/writer-fence-stop.txt
09:58:45 / 09:59:16Source frozen: two count snapshots 31 s apart, byte-identicalparity/source-counts-{a,b}.tsv, 32 tables
09:59:37-09:59:58Fenced data-only dump, sha256, list, circular-FK gatebackup/source-domain-fenced-data.*, backup/evidence-bundles-supersession-gate.txt
10:01:50-10:02:17Corrected full dump, isolated restore test, restore proofbackup/{source-domain-full.dump,restore-test.log,restoretest-counts.tsv}, preflight/backup-restore-proof.txt

Not reached at review time: runbook section 5 (provision and converge the target) and section 6 (the copy). Confirmed independently: stellaops_evidence does not appear in pg_database on stellaops-postgres, and neither stellaops-evidence-web nor stellaops-evidence-worker exists.

Frozen source, as measured by the window itself

32 domain tables (migration ledgers excluded). Non-empty: attestor.entries 26, attestor.local_transparency_entries 3, evidence_locker.decision_capsules 4, evidence_locker.evidence_bundle_signatures 8, evidence_locker.evidence_bundles 8, evidence_locker.evidence_schema_version 5, evidence_locker.verdict_attestations 11, proofchain.graph_edges 9, proofchain.graph_nodes 8, proofchain.predicate_type_registry 35. Empty and load-bearing for sections 7 and 9: attestor.verdict_ledger 0, proofchain.spines 0, evidence_locker.evidence_artifacts 0.

The parity denominator has moved since the rehearsal. The 2026-09-03 receipt records parity 31/31; this window’s frozen snapshot holds 32 tables. 31/31 is therefore not the expected value for this window and must not be used as the acceptance number.

2. Preflight gate, evaluated independently against the runbook’s section 2 step 4 list

Receipt required by :283-296State when reviewed
caller-repoints.tsvpresent
console-evd6.txtabsent — the one open row; preflight/console-build.log was in flight
backup-restore-proof.txtpresent
authority-evidence-descriptors.txtpresent
authority-evidence-tests.txtpresent
authority-evidence-token-claims.txtpresent
authority-evidence-feed.txtpresent
evidence-web-image.txt / evidence-worker-image.txtpresent, digest-form (see D-F)
grants-forward.sql / grants-rollback.sqlpresent
window-approval.txtpresent

Independently re-derived, agreeing with the lane’s receipts: build-boundary -Check OK with 0 unpinned, 0 stale, 0 grown, 0 activation mismatches, 0 impure client SDKs; check-evidence-p19-transition.ps1 -RequirePost reports the coherent post-state (0 target pairs, 0 active predecessor hosts); stellaops_evidence absent; both target containers absent; the three predecessor writers absent.

Two observations the receipts do not resolve, recorded as observations rather than defects:

3. Point of no return, and rollback per phase

The point of no return is runbook section 6, the pg_restore into stellaops_evidence. Before it the target does not exist and abandoning the window costs nothing on this estate, because the absent-predecessor variant means there is nothing to restart. After it the target holds a copy of production data whose provenance is one specific frozen instant of the source; a later rollback cannot reuse it, and a second attempt needs a fresh converge and a fresh freeze.

PhaseRollbackArtefact it depends onCost
Sections 1-2 preflightnothing to undononezero
Section 3 backupdelete artifactsnonezero
Section 4 fencenothing to restart (absent-predecessor variant)rollback/*.ABSENT.txtzero
Section 5 convergeDROP DATABASE stellaops_evidence and re-provision, or leave it converged and empty; nothing reaches itnoneminutes
Section 6 restorethe target becomes evidence: retain unchanged, do not dropnonewindow re-runs end to end
Section 7 parity failskeep the fence, change no grant or config, retain the target as the primary evidenceparity/*as above
Section 8 start/repointstop both successors; restore the captured gateway/Console blobs and recreate only those on their captured config_files listsrollback/stellaops-router-gateway.*, Console blobs~15 min
Section 10 revokegrants-rollback.sqlthat file, reviewed pre-windowminutes

On this estate rollback reduces to: stop both successors, restore the gateway and Console blobs, leave stellaops_platform and stellaops_evidence intact. No predecessor restart is possible, and none is needed for parity with the pre-window state, because the predecessors served nothing.


4. Defects in the runbook

Each was verified against src/, the compose overlay, or the live estate on 2026-09-05. Line references are to evidence-evd9-data-move.md unless another file is named.

D-A — two unbounded health-wait loops

:516-518 (section 5 converge) and :772-774 (section 8 step 1) are until docker inspect -f '{{.State.Health.Status}}' stellaops-evidence-web | grep -qx healthy; do sleep 2; done. There is no deadline and no failure branch. until treats a failing docker inspect as “not yet”, so a container that exits, never becomes healthy, or does not exist spins forever while writing stderr every two seconds.

The successor has a documented route into exactly that state: docker-compose.evidence.yml:175-179 records that a missing Authority__ResourceServer__Authority starts cleanly and then answers 500 to every request including the anonymous /health/ready, which the EVD-8 rehearsal saw as Up (unhealthy) with no startup error anywhere in the log.

Proposed fix: bound both loops with a deadline, dump docker logs on expiry, and exit non-zero.

D-B — section 9 names no scopes, and the policy labels are not the claims

Section 9 lists three forcing functions and never states which scopes their endpoints require. The obvious source for them is the route inventory (src/Evidence/__Tests/StellaOps.Evidence.WebService.Tests/Fixtures/consolidated-route-inventory.tsv, 95 routes), which labels routes with the policy names attestor:write, attestor:verify, attestor:read. Those are internal labels. The claims the policies test are different strings: attest:create and attest:read (src/Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOpsScopes.cs:803,429, consumed at src/Evidence/__Libraries/StellaOps.Attestor.Application/AttestorWebServiceComposition.cs:368-385, whose own comment states the split). A token minted as attestor:write satisfies none of the 52 attestor-policy routes.

Corrected mapping:

Forcing functionEndpoint policy labelClaim the token must carry
FF-1 attestation create, then verifyattestor:write, then attestor:verifyattest:create, then attest:read or attest:create
FF-2 capsule store, then retrieveevidence:create, then evidence:readevidence:create, then evidence:read
FF-3 proof chain read and renderattestor:readattest:read or attest:create
FF-4 anonymous repeatany of the abovenone, by construction

No new identity is required on this estate, which was checked rather than assumed. The stellaops-cli client’s allowed_scopes already contains attest:create, attest:read, evidence:create, evidence:hold and evidence:read; the admin user’s role grants all of them plus attest:admin and ops.health; admin is not locked out (failed_login_attempts 0, locked_until null). stella-ops-ui carries attest:read and evidence:read, covering the Console half of FF-3. This is the one place the AAI-9 precedent does not repeat: that window needed a purpose-scoped identity provisioned before its FF-2 could run at all.

What needs care is the request. The 2026-09-03 rehearsal minted its operator token with evidence:read evidence:create evidence:hold ops.health and no attest:*. FF-1 and FF-3 would have returned 403 under that token, and the rehearsal never ran them.

D-C — the anonymous control is vacuous (corrected)

A first version of this finding claimed the probe would return 200 on an attestor route and trigger a needless rollback. That is withdrawn: through the gateway it will not. The endpoints attach their policy by name (.RequireAuthorization("attestor:read") and siblings, 29 sites in AttestorWebServiceEndpoints.cs), so the discovery mapper places the policy name in Policies, HasAuthorization is true (AspNetEndpointDescriptor.cs:300-305), and RequiresAuthentication=true is published (AspNetCoreEndpointDiscoveryProvider.cs:255-270). The gateway rejects an anonymous caller before the service is reached, and the service-side bypass only fires when the Authorization header is absent (AttestorWebServiceComposition.cs:718-730). Section 9 requires every probe to cross the gateway, so section 9 step 4 stays green as written and must not be changed.

What survives is the vacuity, and it is the more important half. For all 52 attestor-policy routes the mapper publishes RequiringClaims = []: AssertionRequirement falls through ExtractPolicyClaims’s default: branch, TryExtractScopeClaims does not match it, and zero claims are returned (DefaultAuthorizationClaimMapper.cs:260-319). Consequences:

  1. Section 9 step 4’s anonymous 401 measures the gateway’s authentication gate. It is not evidence about scope enforcement on those routes, which exists only in-process.
  2. A token-bearing but under-scoped caller is still rejected — by the service assertion, not the gateway. A negative control against an attestor route therefore proves the service enforces, and should be recorded in those words.
  3. The tokenless in-range path is real but narrow: reachable only by a caller that skips the gateway, container-to-container on the estate networks or from the Docker host against a published port. It is the in-estate service-call posture the overlay documents, not a window blocker.

Proposed negative control, three probes with stated expectations, replacing the single one:

ProbeSourceTokenExpected
N-1through the gatewaynone401 at the gateway (proves the gateway’s authentication gate)
N-2through the gatewayvalid, lacking the route’s claim403 from the service (proves in-process scope enforcement)
N-3direct to the container from an in-range address, no Authorization header, attestor routenoneauthorized by design; record as the measured bypass posture, not as a failure

Only N-2 distinguishes a working authorization posture from a token that merely authenticates, and it is the probe the runbook does not currently contain.

D-D — “not a widening” is true against the predecessors and false against this estate

docker-compose.evidence.yml:181-191 justifies the bypass set as carried verbatim from both predecessors, concluding “Not a widening: no network is added that either predecessor did not already bypass.” That is sound against the predecessor baseline.

It is not sound against the current baseline. On this estate the three predecessor writers are absent — containers, images and both carried volumes — which the rehearsal recorded (D-EVD9-5) and this window’s own rollback/*.ABSENT.txt re-confirms. Nothing serves those routes today. Relative to the live baseline the window therefore moves the tokenless in-network writable attestor surface from 0 routes to 52, including POST /api/evidence/v1/attestations:sign, POST /anchors, DELETE /anchors/{anchorId} and POST /internal/api/v1/attestations/verdict.

This is very likely the intended restoration of capability. The finding is that it belongs in the window closeout record as an explicit line the owner can see, rather than behind a comment asserting the opposite.

Supporting live measurement, 2026-09-05: stellaops is 172.19.0.0/16 and stellaops_frontdoor is 172.18.0.0/16, both exactly matching docker-compose.evidence.yml:196-199.

D-E — an invalid enum value in the shared environment file (for the ROA-1 lane, not this window)

devops/compose/.env:458 sets ROUTER_ON_MISSING_AUTHORIZATION=Fail. Fail is not a member of MissingAuthorizationBehavior, whose only values are RequireExplicit, AllowAuthenticated and WarnAndAllow (src/Router/__Libraries/StellaOps.Microservice.AspNetCore/StellaRouterBridgeOptions.cs:198-217). All 27 running services carry Router__OnMissingAuthorization=Fail and are healthy, so the value is inert or silently defaulting estate-wide rather than crashing. The Evidence overlay consumes the same variable at docker-compose.evidence.yml:218, so the successor inherits whatever it resolves to. UNVERIFIED: which of the three behaviours is in force at runtime; determining that needs a binding test or an instrumented boot, neither of which this review performed.

RESOLVED 2026-09-07 by ROA-1(b), which this review handed it to. The answer is WarnAndAllow, and not because anything parsed Fail: the key was never bound at all. Hosts bind the Router: section into StellaRouterOptionsBase, which has no OnMissingAuthorization property, and TryAddStellaRouter never copies one, so StellaRouterOptions kept its own default. That is why 27 services carried an unparseable value and stayed healthy. The key was deleted from every compose file, both env examples and the release bundle in the same change; the property remains a code-level option on AddStellaRouter. Compose_NeverSetsTheUnboundOnMissingAuthorizationKey fails if it returns.

D-F — the image-receipt gate cannot fail on missing provenance

The required-input table demands “web/worker image digests, provenance receipts, runtime render, authenticated rehearsal”. The only executable check for it, :294-295, is grep -Eq '@sha256:[0-9a-f]{64}$' against each receipt. A file containing nothing but a digest line passes. Both receipts in this window contain exactly that one line and carry no gitSha, worktreeState or dirtyFileCount, so the provenance half of a required input is unrecorded while its gate reads green.

Proposed fix: have each image receipt carry the image’s buildinfo.json triple and assert gitSha equals the intended commit. Per the live-deploy playbook the acceptance check is gitSha == the commit you intended to pin, never worktreeState == clean on its own, because a build mis-invoked against a clean main clone produces an honest clean stamp of the wrong commit.

D-G — section 3’s dump omits a schema its own restore needs (found and fixed by the executing lane)

Recorded for completeness because it is the highest-value defect of the window and belongs in the runbook’s history. Section 3 dumps only attestor, proofchain and evidence_locker. Every evidence_locker RLS policy calls evidence_locker_app.require_current_tenant(), so a standalone restore of that artifact fails at the first policy with schema "evidence_locker_app" does not exist. Data loads before policies, so per-table counts still match, which is how the gap stays hidden. The 2026-09-03 rehearsal did not hit it because it restore-tested the fenced data-only dump into an already-converged target that had the functions.

The executing lane found this independently, re-dumped with the fourth schema included, kept the superseded artifact as backup/source-domain-full.dump.three-schemas-superseded, and is folding the missing --schema into section 3 in this window’s change set.


5. Sections 8-13: steps that cannot be performed as written

The 2026-09-04 route-leg retirement banner amended the forward path (section 7 step 3 and section 8 step 3). It did not amend the rollback section or section 8 step 5, and the absent-predecessor variant note in the rehearsal section does not reach section 10. The result is that retired premises survive in the places least likely to be read before they are needed.

RefStep as writtenWhy it cannot be performedConsequence
Rollback step 2“Verify the fifteen predecessor routes … before restarting writers”All fifteen rows are gone from both route tables; the tables hold 22 routes total (banner). There is nothing to verifyA void gate on the rollback path. An operator entering rollback under pressure cannot satisfy it and has no stated alternative
Rollback step 4“Restart stellaops-attestor, stellaops-evidence-locker-web, stellaops-evidence-locker-worker using captured image IDs”All three containers, their images and both carried volumes are absentThe rehearsal section says this; the rollback section does not. Rollback here is: stop both successors, restore the gateway and Console blobs, leave both databases intact
Rollback step 5“Repeat predecessor health, authorization, capsule digest, attestation/timestamp, and Console proof checks”No predecessor exists to checkOnly the final clause, “confirm source counts still equal source-counts-b.tsv”, is performable
Section 8 step 5“Verify first-match routing for the consolidated group, both Policy false friends, and the Platform /api/v2/evidence route”“The consolidated group” was the retired fifteen-to-one route groupThe false-friend and /api/v2/evidence checks remain valid and should be kept; the group check should be replaced by the banner’s own instruction to confirm the auto-published layer serves /api/evidence/v1/**
Section 9 step 3“Open a copied proof spine in deployed Console, render its chain, and run proof verification”proofchain.spines holds 0 rows in the frozen sourceFF-3 cannot be discharged by carried data at all. Same shape as the capsule case the runbook already flags, and not flagged for spines
Section 10 step 4“Keep predecessor writers stopped but present. Retain their images, compose definitions, source schemas, and volumes through rollback retention”Nothing to keep, retain or stopOnly the source-schema retention clause is live, and it is the one that matters

The FF-3 finding, stated precisely

The runbook already records that the 8 copied bundle headers have no payloads and that FF-2 is therefore “discharged only by a NEW capsule stored through the successor, never by carried data”. The identical argument applies to FF-3 and is not made: with proofchain.spines at 0 there is no copied spine to open. What the carried data does contain is proofchain.graph_nodes 8 and proofchain.graph_edges 9.

Two honest ways to close FF-3, either acceptable, neither silent:

  1. Create a spine through the successor during the window and render that in Console, mirroring the FF-2 treatment. This exercises the write path as well as the read path.
  2. Redefine FF-3 against the graph rows that do exist, and state in the closeout record that the spine-rendering half is not exercised because the source holds no spines.

What must not happen is FF-3 being recorded green on a Console page that rendered nothing — which the runbook’s own wording (“a direct API 200 without Console rendering does not satisfy this gate”) shows it was already alert to in the other direction.

A third vacuous gate in section 7, unflagged

The rehearsal section flags 7.2 (verdict chain) and 7.3 (spine recomputation) as vacuous on this estate, both 0 rows. Section 7.4 is vacuous for the same reason and is not flagged: evidence_locker.evidence_artifacts holds 0 rows, so the dangling-bundle_id count is trivially 0 and the gate cannot fail. The fk_artifacts_bundle constraint that 7.4 then adds is still correct and worth adding — it constrains future inserts — but adding it to an empty table proves nothing about the copy. Record the row count beside the verdict, exactly as the runbook already instructs for 7.2 and 7.3.


6. ROA-1 registration inventory for the Evidence family

Commissioned as “6 Evidence + 1 EvidenceLocker RequireAssertion registrations”. The inventory does not confirm that split.

The family has SIX production RequireAssertion registrations, all in one file. The seventh is not a production registration.

#RegistrationPolicyRequires authenticated userScopes checkedRoutes guarded
1AttestorWebServiceComposition.cs:368-372attestor:writenoattest:create, OR trusted-network bypass22 (16 prefixed, 6 unprefixed)
2AttestorWebServiceComposition.cs:374-378attestor:verifynoattest:read, attest:create, OR bypass2, plus 2 shared with attestor:read
3AttestorWebServiceComposition.cs:380-385attestor:readnoattest:read, attest:create, OR bypass26 (13 prefixed, 13 unprefixed)
4AttestorWebServiceComposition.cs:387-401watchlist:readyestrust:read, trust:write, trust:admin, watchlist:{read,write,admin}, watchlist.{read,write,admin}4
5AttestorWebServiceComposition.cs:403-414watchlist:writeyestrust:write, trust:admin, watchlist:{write,admin}, watchlist.{write,admin}3 (paired with watchlist:read)
6AttestorWebServiceComposition.cs:416-424watchlist:adminyestrust:admin, watchlist:admin, watchlist.admin0 — registered, guards no route

The seventh is a test double, not a live fail-open. src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.Tests/EvidenceLockerWebApplicationFactory.cs:267 is .RequireAssertion(_ => true) inside the retiring predecessor’s test project. It has no production counterpart and is not gateway-reachable. Its sibling in the consolidated host’s own suite is src/Evidence/__Tests/StellaOps.Evidence.WebService.Tests/EvidenceHostCompositionTests.cs:469, also a test. A sweep of src/ finds no other RequireAssertion in the Evidence, EvidenceLocker or Attestor families outside test projects, and none in src/__Obsoleted/.

Gateway enforceability — the same for all six. RequiringClaims is empty for every route behind them, because AssertionRequirement is unmappable at DefaultAuthorizationClaimMapper.cs:260-319. RequiresAuthentication is published as true for all of them, since each endpoint attaches its policy by name. The gateway therefore requires a token and can never require a scope; scope enforcement for 59 routes lives solely in-process.

UNVERIFIED: whether the 18 unprefixed carried Attestor routes are gateway-reachable in practice. The consolidated host was not running during this review, so this could not be probed. The prefixed /api/evidence/v1/** surface is served by the auto-published layer per the runbook banner.

The design note ROA-1 needs before it touches this family

Registrations 1-3 are not a like-for-like conversion the way AdvisoryAI’s were. They differ from 4-6 in two ways that both matter:

Converting them to a typed StellaOpsScopeRequirement would make them gateway-enforceable and would remove the bypass, because a scope requirement has no second branch. That bypass is a deliberate, tested capability, not an oversight: AttestorBypassTenantHeaderTests pins that a tokenless bypass POST /api/evidence/v1/rekor/entries carrying X-StellaOps-TenantId returns 200 and persists the entry under the caller-supplied tenant, that the same call without the header still returns 400 tenant_required, and that an authenticated principal cannot spoof a tenant through that header. The header exception is Attestor-specific and does not generalise.

ROA-1’s remediation for this family is therefore a design decision with a named consumer to re-home first — the keyless in-estate submit path — rather than a mechanical swap. Registrations 4-6 have no bypass and are a clean mechanical conversion. Registration 6 guards no route and can be deleted or wired.

A precedent for the target shape exists: RemediationAuthTests (src/__Obsoleted/Remediation/__Tests/StellaOps.Remediation.WebService.Tests/RemediationAuthTests.cs:98,132) asserts that each policy must enforce a StellaOpsScopeRequirement and not RequireAssertion(_ => true).


7. Estate finding: compose chains that resolve through tmp/

Found while checking whether this window’s digest-pin override, which lives at tmp/evd9-20260905-evd9/preflight/evidence-digest-pin.override.yml, would become a permanent dependency of the live service.

It would, and the estate already carries the problem at scale. Measured 2026-09-05 by reading com.docker.compose.project.config_files from every running container:

This is pre-existing and estate-wide, not introduced by EVD-9, and the immediately preceding AAI-9 window used the same pattern (stellaops-advisoryai-web resolves through four files under tmp/aai9-20260904-aai9/compose/). It is recorded here because the recommendation follows directly:

Whatever this window pins, put the pin somewhere durable, or copy it into the window’s rollback/ directory and re-point the chain at the copy before the window closes. A pin that is the last entry in a live service’s chain is not a receipt, it is a load-bearing part of the deployment. The two dead pins above are what that looks like a fortnight later, and they deserve their own row on someone’s board.


8. Failure modes

#Failure modeDetectionResponse
F-01A forcing function passes vacuouslyEach FF has a named vacuity trap: FF-1 green proves the data path, not authorization posture (D-C); FF-2 green on carried data proves nothing because the 8 bundle headers have no payloads; FF-3 has no spine to render at all (section 5)Before accepting any green, state what would have made it red. Where nothing could, label it a belt and record it as one
F-02The converge boot never reaches healthy and the window hangsD-A: no deadline on either wait loopBound the loop; on expiry dump docker logs and check Authority__ResourceServer__Authority first, per docker-compose.evidence.yml:175-179
F-03A forcing function fails on a scope that was never requested403 on FF-1 or FF-3 with a token minted from the inventory’s policy labelsD-B: request attest:create and attest:read, not attestor:*
F-04Rollback is entered and its own steps cannot be executedSection 5: three of the six rollback clauses have void premisesUse the absent-predecessor rollback: stop both successors, restore the gateway and Console blobs, leave both databases intact
F-05The window’s image pin is lost and the service cannot be recreatedSection 7: two live containers are already in this stateCopy the pin somewhere durable and assert the copy, not the original
F-06A parity verdict is read against the wrong denominatorThe rehearsal recorded 31/31; this window’s frozen source holds 32 tablesUse 32. Record the three zero-row tables (verdict_ledger, spines, evidence_artifacts) beside the verdict so the vacuous gates stay visible
F-07Scheduled host maintenance interrupts the windowTwo Windows tasks run every two days at 01:00 and 02:00 UTC; the estate does not restart itself (measured gaps 2 h 58 m and 7 h 04 m)Check Get-ScheduledTask StellaOps-WslVhdxOptimize | Get-ScheduledTaskInfo and clear NextRunTime by the full window duration
F-08A recreate lands on a pruned image recorddocker image inspect on a running container’s image id says “No such image” while the container runsSweep before section 8 by comparing docker inspect --format '{{.Image}}' across all containers against docker image ls -a --no-trunc --format '{{.ID}}'

9. What this review did not determine