kept second-drive copies (D25: R-1175, R-1181, R-1179); .fab import keeps the family gate (R-1191); stale restore temp (R-1188 c); the overcommit sentence made true (R-1192); escrow before the floor (R-1176); unreleased
gates / gates (push) Successful in 1m20s

This commit is contained in:
admin committed 2026-10-21 15:46:10 +02:00
1 parent 8f5bea0148
commit 5929b8e345
34 files changed
+2152 -44

No files matched your search

+28 -16
View File
@@ -1167,6 +1167,11 @@ func main() {
// Hand it to the updater and reconcile — if the box is below the floor it auto-updates to
// the floor (reusing the Phase 1 swap). This rides the existing report cycle; no new timer.
// latest_version stays informational (the customer's opt-in button), NOT the auto-target.
// SLICE 3: run the escrow auto-confirm on the same ACK. R-1176 (2026-10-21): it runs BEFORE SetFloor, because
// the off-site apply-bridge's settle gate GOes the moment the floor is known — and a rebuilt box must already
// have recorded „the hub holds the household's package" by then, or its mint guard (R-241) cannot stop a
// fresh repository password. Pinned by TestR1176_EscrowIsRecordedBeforeTheFloorIsKnown.
escrowConfirmer.Reconcile(resp.Escrow)
if updater != nil {
updater.SetFloor(resp.MinControllerVersion)
updater.MaybeAutoUpdate()
@@ -1196,9 +1201,6 @@ func main() {
Logger: logger,
}
cr.Reconcile(resp.ConfigVersion)
// SLICE 3: run the escrow auto-confirm on the same ACK (after the config refresh decision —
// a refresh-restart re-enters here anyway on the next cycle).
escrowConfirmer.Reconcile(resp.Escrow)
// v0.111.0: operator log-tail requests ride the same ACK (pull pattern — the hub
// never reaches in). The NEXT report cycle collects + ships the tails; the hub
// clears its pending request on receipt. An empty list clears any stale local set.
@@ -3380,6 +3382,9 @@ func (a *geoStackAdapter) GetDeployedHostnames() map[string]string {
type exportAdapter struct {
mgr *stacks.Manager
encKey []byte
// R-1191: whether the app had no record before the import (SaveEncryptedAppConfig → PrepareImportedApp).
importFreshMu sync.Mutex
importFresh map[string]bool
}
func (a *exportAdapter) GetStackDir(name string) (string, bool) {
@@ -3522,20 +3527,27 @@ func (a exportStopGuard) End() { a.g.End() }
func (a exportStopGuard) ReleaseFailed(stacks ...string) { a.g.ReleaseFailed(stacks...) }
func (a *exportAdapter) SaveEncryptedAppConfig(stackDir string, env map[string]string) error {
meta := stacks.LoadMetadata(stackDir)
sensitiveVars := stacks.SensitiveEnvVars(&meta)
cfg := &stacks.AppConfig{
Deployed: true,
DeployedAt: time.Now().Format(time.RFC3339),
Env: env,
// R-166 — a CUSTOMER-INTENT POINT, and the one that is not the API action switch. Importing
// a `.fab` bundle is the customer installing that app on this box, and the import path starts
// it (appexport/restore.go). Without this the app would come back from a restore with NO
// recorded intent and fall to legacy boot behaviour — meaning a power cut days later would
// strand it, which is exactly the failure this release exists to remove.
DesiredState: stacks.DesiredStateRunning,
// R-1191: the record the unit restore would build (locked fields, the prior record's life records, desired state
// running — R-166), not a bare one; the gates follow in PrepareImportedApp, once the compose file is in place.
fresh, err := a.mgr.SaveImportedAppConfig(stackDir, env)
if err != nil {
return err
}
return stacks.SaveAppConfig(stackDir, cfg, a.encKey, sensitiveVars)
a.importFreshMu.Lock()
if a.importFresh == nil {
a.importFresh = map[string]bool{}
}
a.importFresh[stackDir] = fresh
a.importFreshMu.Unlock()
return nil
}
func (a *exportAdapter) PrepareImportedApp(stackDir string, env map[string]string) error {
a.importFreshMu.Lock()
fresh := a.importFresh[stackDir]
delete(a.importFresh, stackDir)
a.importFreshMu.Unlock()
return a.mgr.PrepareImportedApp(stackDir, env, fresh)
}
func (a *exportAdapter) RefreshStacks() error {