Take a key or list member the host may have written itself as the mesh's, so undeclaring gives the file back (hq ADR 0102)

This commit is contained in:
2026-09-22 19:57:15 +02:00
parent 0bd22e50f2
commit 7beb752be0
2 changed files with 59 additions and 9 deletions
+19 -7
View File
@@ -93,7 +93,7 @@ func applyInto(r *declaration.File, previous store.Applied) (Outcome, error) {
if !tracked(k) && !had {
rec.Absent = append(rec.Absent, k)
}
merged, added, err := addMembers(current, declared[k], rec.Added[k])
merged, added, err := addMembers(current, declared[k], rec.Added[k], previous.Into == nil)
if err != nil {
return out, fmt.Errorf("%s: %q: %w", r.Path, k, err)
}
@@ -102,7 +102,14 @@ func applyInto(r *declaration.File, previous store.Applied) (Outcome, error) {
continue
}
if !tracked(k) {
if v, had := object[k]; had {
v, had := object[k]
// **What the host may have written itself is not the machine's.** With no record of
// this file — the first apply, or a host that wrote and died before saving its state —
// a key already holding exactly what the mesh declares cannot be told from one the
// mesh set a moment ago. Remembered as the machine's, it would never be given back:
// undeclaring would leave the mesh's own value behind for ever. So it is the mesh's,
// and undeclaring takes it out (novox/hq ADR 0102).
if had && !(previous.Into == nil && canonical(v) == canonical(declared[k])) {
rec.Before[k] = v
} else {
rec.Absent = append(rec.Absent, k)
@@ -320,8 +327,11 @@ func listOf(members []json.RawMessage) json.RawMessage {
// addMembers adds the declared members to the machine's list, dropping only members the mesh
// added before and no longer declares. It returns the list and exactly which members the mesh
// added — a declared member the machine already had is the machine's, and is never recorded.
func addMembers(current, declared json.RawMessage, addedBefore []json.RawMessage) (json.RawMessage,
[]json.RawMessage, error) {
// unrecorded says there is no record of this file yet, in which case a declared member already in
// the list may be one the host itself wrote before it could save its state, and is taken as the
// mesh's.
func addMembers(current, declared json.RawMessage, addedBefore []json.RawMessage,
unrecorded bool) (json.RawMessage, []json.RawMessage, error) {
have, err := membersOf(current)
if err != nil {
return nil, nil, err
@@ -341,9 +351,11 @@ func addMembers(current, declared json.RawMessage, addedBefore []json.RawMessage
for _, m := range want {
if !hasMember(have, m) {
have = append(have, m)
if !hasMember(added, m) {
added = append(added, m)
}
} else if !unrecorded {
continue // the machine's own, and never the mesh's to take out
}
if !hasMember(added, m) {
added = append(added, m)
}
}
return listOf(have), added, nil