1214bae0a2
An absent off-site object has four meanings — never configured, mid-restart, a transient config read failure, and rebuilt-and-stranded — and the hub cannot tell them apart. The box can, from two local facts it holds with certainty, so it says so instead of leaving the hub to deduce it from a silence (operator ruling). The ACK's identity_blob_present is now recorded on EVERY ACK, before the gates that used to discard it: on a box with no off-site target the auto-confirm returns immediately, which is exactly a rebuilt box, so the one fact distinguishing it from a box that never had off-site backups was thrown away every cycle. The declaration needs BOTH halves — a fresh data area AND a hub-held recovery package. Freshness alone is a box that never had off-site backups; dropping that condition makes the whole fleet ask for credentials, which is what the Scenario B test exists to catch. The object carries enabled:false and zero sizes, which is what makes it inert to the hub's existing fill and staleness checkers and to a pre-upgrade hub. A configured box's JSON is byte-identical to v0.198.0's.
69 lines
2.3 KiB
Go
69 lines
2.3 KiB
Go
package report
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"testing"
|
|
)
|
|
|
|
// TestMainWiresRecordPresence — the seam-discipline test (§9 rule 6).
|
|
//
|
|
// `RecordPresence` is a nil-able field: an unwired confirmer compiles, every test in this package
|
|
// passes, the fleet reports nothing new, and the whole of R-204 item 4 is inert. That is this
|
|
// project's most-repeated failure shape — six features built and never wired, one of them an off-site
|
|
// restage event that existed and never fired once.
|
|
//
|
|
// It walks the AST of main.go rather than grepping the file, because a commented-out field still
|
|
// contains the string (the lesson from the lifecycle-gate wiring test next door), and it parses with
|
|
// comments DROPPED so a commented assignment cannot satisfy it.
|
|
func TestMainWiresRecordPresence(t *testing.T) {
|
|
const mainPath = "../../cmd/controller/main.go"
|
|
fset := token.NewFileSet()
|
|
f, err := parser.ParseFile(fset, mainPath, nil, 0) // comments dropped on purpose
|
|
if err != nil {
|
|
t.Fatalf("parse %s: %v — the wiring of RecordPresence is now unasserted", mainPath, err)
|
|
}
|
|
|
|
found := false
|
|
sawConfirmerLiteral := false
|
|
ast.Inspect(f, func(n ast.Node) bool {
|
|
lit, ok := n.(*ast.CompositeLit)
|
|
if !ok {
|
|
return true
|
|
}
|
|
// Match `report.EscrowAutoConfirmer{...}` (and a bare `EscrowAutoConfirmer{...}`).
|
|
name := ""
|
|
switch t := lit.Type.(type) {
|
|
case *ast.SelectorExpr:
|
|
name = t.Sel.Name
|
|
case *ast.Ident:
|
|
name = t.Name
|
|
}
|
|
if name != "EscrowAutoConfirmer" {
|
|
return true
|
|
}
|
|
sawConfirmerLiteral = true
|
|
for _, el := range lit.Elts {
|
|
kv, ok := el.(*ast.KeyValueExpr)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if k, ok := kv.Key.(*ast.Ident); ok && k.Name == "RecordPresence" {
|
|
found = true
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
// Distinguish "the literal moved" from "the field was dropped" — otherwise a refactor that
|
|
// relocated the confirmer would read as a passing test over nothing (the §12 rule: an absent
|
|
// thing is not evidence).
|
|
if !sawConfirmerLiteral {
|
|
t.Fatalf("no EscrowAutoConfirmer composite literal found in %s — did the wiring move? This test can no longer see it", mainPath)
|
|
}
|
|
if !found {
|
|
t.Fatal("EscrowAutoConfirmer is constructed WITHOUT RecordPresence — the box will never learn the hub holds its recovery package, and R-204 item 4 ships inert")
|
|
}
|
|
}
|