carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "errors"
5 "fmt"
6 "os"
7 "testing"
8 "time"
9)
10
11// Final review finding 1. T7 used to pass whenever live/<host> named the
12// pinned version: no authority, no proof policy, no look at the bytes.
13// VerifyHeldLive is its reading now: the link names the version, D3 admits
14// the release under the policy in force, and bind accepts the bytes the
15// link names, read under the lock the publication holds across its swap,
16// re-hash and rollback.
17
18// passLock is a lock that is not one: the bytes check alone.
19func passLock(read func() error) error { return read() }
20
21// chanLock is one lock shared by a publication's guard and a T7 reading,
22// as the per-host flock is in production. waiting is signalled each time a
23// reading starts to wait for it.
24type chanLock struct {
25 mu chan struct{}
26 waiting chan struct{}
27}
28
29func newChanLock() *chanLock {
30 return &chanLock{mu: make(chan struct{}, 1), waiting: make(chan struct{}, 8)}
31}
32
33func (l *chanLock) guard(publish func() error) error {
34 l.mu <- struct{}{}
35 defer func() { <-l.mu }()
36 return publish()
37}
38
39func (l *chanLock) read(fn func() error) error {
40 l.waiting <- struct{}{}
41 l.mu <- struct{}{}
42 defer func() { <-l.mu }()
43 return fn()
44}
45
46// The review's regression: T7 is read between the swap and a failed
47// post-swap verification. The publication holds the lock from before the
48// swap until after its rollback, so the reading waits it out and then sees
49// the link back on v0: not published, never a pass.
50func TestVerifyHeldLiveWaitsOutAFailedPublication(t *testing.T) {
51 e := newHeldProofEnv(t)
52 e.require(t, "console")
53 e.prove(t, e.pinnedSHA)
54 lock := newChanLock()
55 agent := NewProofGate(e.policy, e.bake) // the agent's own gate
56 prev := heldAfterSwap
57 t.Cleanup(func() { heldAfterSwap = prev })
58 res := make(chan error, 1)
59 var during error = errors.New("the seam never ran")
60 heldAfterSwap = func() {
61 // The link names v1 now. Its bytes are replaced, so the
62 // publication's second bind will refuse and roll back.
63 if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("replaced after the swap")); err != nil {
64 t.Fatal(err)
65 }
66 if v := e.live(); v != "v1" {
67 t.Fatalf("live = %q inside the swap window, want v1", v)
68 }
69 go func() { res <- VerifyHeldLive(e.ctx, e.store, e.opt, heldRoute(), "v1", agent, lock.read) }()
70 select {
71 case <-lock.waiting:
72 case err := <-res:
73 during = fmt.Errorf("T7 answered %v without waiting for the lock", err)
74 return
75 case <-time.After(5 * time.Second):
76 during = errors.New("T7 never reached the lock")
77 return
78 }
79 select {
80 case err := <-res:
81 during = fmt.Errorf("T7 answered %v while the publication held the lock", err)
82 case <-time.After(200 * time.Millisecond):
83 during = nil
84 }
85 }
86 _, err := MaterializeHeld(e.ctx, e.store, heldPub, nr(t), e.opt, heldRoute(), "v1", e.gate, lock.guard)
87 if !errors.Is(err, ErrHeldNotProven) || e.live() != "v0" {
88 t.Fatalf("the publication: %v, live %q; want ErrHeldNotProven and v0", err, e.live())
89 }
90 if during != nil {
91 t.Fatal(during)
92 }
93 select {
94 case got := <-res:
95 if !errors.Is(got, ErrHeldNotLive) {
96 t.Fatalf("T7 after the rollback = %v, want ErrHeldNotLive", got)
97 }
98 case <-time.After(5 * time.Second):
99 t.Fatal("T7 never answered")
100 }
101}
102
103// Even with no lock, the reading never passes on the swapped link: it
104// hashes the bytes the link names, as the publication's second bind does.
105func TestVerifyHeldLiveRefusesTheSwappedBytes(t *testing.T) {
106 e := newHeldProofEnv(t)
107 e.require(t, "console")
108 e.prove(t, e.pinnedSHA)
109 agent := NewProofGate(e.policy, e.bake)
110 prev := heldAfterSwap
111 t.Cleanup(func() { heldAfterSwap = prev })
112 var during error = errors.New("the seam never ran")
113 heldAfterSwap = func() {
114 if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("replaced after the swap")); err != nil {
115 t.Fatal(err)
116 }
117 during = VerifyHeldLive(e.ctx, e.store, e.opt, heldRoute(), "v1", agent, passLock)
118 }
119 if _, err := MaterializeHeld(e.ctx, e.store, heldPub, nr(t), e.opt, heldRoute(), "v1", e.gate, openGuard); !errors.Is(err, ErrHeldNotProven) {
120 t.Fatalf("the publication: %v", err)
121 }
122 if !errors.Is(during, ErrHeldNotProven) {
123 t.Fatalf("T7 inside the swap window = %v, want ErrHeldNotProven", during)
124 }
125}
126
127// An existing link is no evidence by itself: T7 re-checks the policy and
128// the bytes every time. Replaced bytes, a rotated prover key and a policy
129// that does not load each fail it; a link on another version is not live;
130// an app the policy does not list passes as adoption would.
131func TestVerifyHeldLiveRechecksAnExistingLink(t *testing.T) {
132 e := newHeldProofEnv(t)
133 e.require(t, "console")
134 e.prove(t, e.pinnedSHA)
135 if _, err := MaterializeHeld(e.ctx, e.store, heldPub, nr(t), e.opt, heldRoute(), "v1", e.gate, openGuard); err != nil || e.live() != "v1" {
136 t.Fatalf("publish v1: %v, live %q", err, e.live())
137 }
138 verify := func() error {
139 return VerifyHeldLive(e.ctx, e.store, e.opt, heldRoute(), "v1", NewProofGate(e.policy, e.bake), passLock)
140 }
141 if err := verify(); err != nil {
142 t.Fatalf("published and proven: %v", err)
143 }
144
145 // The artifact is replaced after publication.
146 if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("replaced later")); err != nil {
147 t.Fatal(err)
148 }
149 if err := verify(); !errors.Is(err, ErrHeldNotProven) {
150 t.Fatalf("replaced bytes: %v, want ErrHeldNotProven", err)
151 }
152 if r, _ := ReadRefusal(HeldRefusalBase(e.bake), heldTestHost); r != nil {
153 t.Fatalf("a reading recorded a refusal: %+v", r)
154 }
155 if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("pinned build")); err != nil {
156 t.Fatal(err)
157 }
158 if err := verify(); err != nil {
159 t.Fatalf("bytes restored: %v", err)
160 }
161
162 // The proof policy changes: another prover key.
163 _, _, otherPub := proverKey(t)
164 e.writePolicy(t, fmt.Sprintf(`{"schema":1,"require":[{"app":"console","prover":"dev","key":%q}]}`, otherPub))
165 if err := verify(); !errors.Is(err, ErrHeldNotProven) {
166 t.Fatalf("rotated key: %v, want ErrHeldNotProven", err)
167 }
168 // A policy that does not load.
169 e.writePolicy(t, "{not json")
170 if err := verify(); !errors.Is(err, ErrHeldNotProven) {
171 t.Fatalf("broken policy: %v, want ErrHeldNotProven", err)
172 }
173 // Uncovered: D3 has nothing to say about these bytes, as for adoption.
174 e.require(t, "other")
175 if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("replaced later")); err != nil {
176 t.Fatal(err)
177 }
178 if err := verify(); err != nil {
179 t.Fatalf("uncovered: %v", err)
180 }
181
182 // Another version live is not published.
183 if err := PointLive(e.opt, heldTestHost, "bab", "v0", "console"); err != nil {
184 t.Fatal(err)
185 }
186 if err := verify(); !errors.Is(err, ErrHeldNotLive) {
187 t.Fatalf("on v0: %v, want ErrHeldNotLive", err)
188 }
189 if err := os.Remove(LiveLink(e.opt, heldTestHost)); err != nil {
190 t.Fatal(err)
191 }
192 if err := verify(); !errors.Is(err, ErrHeldNotLive) {
193 t.Fatalf("no link: %v, want ErrHeldNotLive", err)
194 }
195
196 // No lock, no gate: refused.
197 if err := VerifyHeldLive(e.ctx, e.store, e.opt, heldRoute(), "v1", e.gate, nil); err == nil {
198 t.Fatal("a reading without the lock passed")
199 }
200 if err := VerifyHeldLive(e.ctx, e.store, e.opt, heldRoute(), "v1", nil, passLock); err == nil {
201 t.Fatal("a reading without a gate passed")
202 }
203}
204
205// Region HA Task 8c: the claim path's reading. VerifyHeldLiveUnder reads
206// under CARLOS_HOST_CLAIM, and still refuses an unproven link, a link on
207// another version, and a reading without the lock. VerifyHeldLive writes
208// nothing, so it reads under the claim too (moves Task 7, Paul's T7 ruling
209// of 2026-10-12).
210func TestVerifyHeldLiveUnderReadsUnderTheClaim(t *testing.T) {
211 e := newHeldProofEnv(t)
212 e.require(t, "console")
213 e.prove(t, e.pinnedSHA)
214 if _, err := MaterializeHeld(e.ctx, e.store, heldPub, nr(t), e.opt, heldRoute(), "v1", e.gate, openGuard); err != nil || e.live() != "v1" {
215 t.Fatalf("publish v1: %v, live %q", err, e.live())
216 }
217 t.Setenv("CARLOS_HOST_CLAIM", "1")
218 verify := func(lock func(func() error) error) error {
219 return VerifyHeldLiveUnder(e.ctx, e.store, e.opt, heldRoute(), "v1", NewProofGate(e.policy, e.bake), lock)
220 }
221 if err := VerifyHeldLive(e.ctx, e.store, e.opt, heldRoute(), "v1", NewProofGate(e.policy, e.bake), passLock); err != nil {
222 t.Fatalf("VerifyHeldLive under the claim: %v", err)
223 }
224 if err := verify(passLock); err != nil {
225 t.Fatalf("published and proven, under the claim: %v", err)
226 }
227 if err := verify(nil); err == nil {
228 t.Fatal("a reading without the lock passed")
229 }
230 if err := StageArtifact(e.opt, "bab", "v1", "console", []byte("replaced later")); err != nil {
231 t.Fatal(err)
232 }
233 if err := verify(passLock); !errors.Is(err, ErrHeldNotProven) {
234 t.Fatalf("replaced bytes: %v, want ErrHeldNotProven", err)
235 }
236 if err := PointLive(e.opt, heldTestHost, "bab", "v0", "console"); err != nil {
237 t.Fatal(err)
238 }
239 if err := verify(passLock); !errors.Is(err, ErrHeldNotLive) {
240 t.Fatalf("on v0: %v, want ErrHeldNotLive", err)
241 }
242}
243