claim-prev-rehearsal fix round 2: always finish carlos.prev's judgement, Windows test build, approved copy
- The --policy already-installed path always runs carlos.prev's judgement again and fails on its error; completePrev writes the record, then judges, so a retry finishes whichever step was cut off. - The Linux-only policy-open test moves to installchannel_policy_linux_test.go. - The unfinished-update reason drops guard-arm's floor prefix. - The census-box root test checks the verdict names the replaced release. - Paul approved all nine strings (2026-10-18). The docs paragraph is applied to cli.md, and RHB-C19's row carries the re-approved usage, pinned whole by the 2b gate. Census overlay claim-prev-rehearsal-fix2 (envelope only) PROPOSED.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 files changed,
+207
−40
approved_region_ha_2b_copy_test.go+9 −1docs/site/cli.md+2 −0docs/superpowers/notes/census-proposal-claim-prev-rehearsal.md+12 −0docs/superpowers/plans/2026-10-17-region-ha-2b-copy.md+1 −1internal/host/installchannel.go+26 −7internal/host/installchannel_policy_linux_test.go+46 −0internal/host/installchannel_policy_rootrunner_linux_test.go+7 −1internal/host/installchannel_policy_test.go+93 −30internal/provision/plan14_root_reads_test.go+8 −0internal/provision/testdata/plan14-root-reads-claim-prev-rehearsal-fix2.tsv+3 −0
diff --git a/approved_region_ha_2b_copy_test.go b/approved_region_ha_2b_copy_test.go| index c36232651..d49cf5638 100644 |
| --- a/approved_region_ha_2b_copy_test.go |
| +++ b/approved_region_ha_2b_copy_test.go |
| @@ -5,6 +5,7 @@ import ( |
| "os" |
| "regexp" |
| "strconv" |
| + "strings" |
| "testing" |
| ) |
| @@ -66,5 +67,12 @@ func TestRegionHA2bBuiltCopyIsApproved(t *testing.T) { |
| } |
| func TestRegionHA2bBuiltCopyIsApplied(t *testing.T) { |
| - reportAll(t, checkRunApplied(builtRows(mustParseRegionHA2bRecord(t), regionHA2bCopyBuilt), ".")) |
| + rows := builtRows(mustParseRegionHA2bRecord(t), regionHA2bCopyBuilt) |
| + // A table cell holds one line, so in this record a row's text writes a |
| + // newline as \n: RHB-C19, the install-channel usage Paul re-approved on |
| + // 2026-10-18 (claim-prev-rehearsal), is two lines and a blank one. |
| + for i := range rows { |
| + rows[i].Text = strings.ReplaceAll(rows[i].Text, `\n`, "\n") |
| + } |
| + reportAll(t, checkRunApplied(rows, ".")) |
| } |
diff --git a/docs/site/cli.md b/docs/site/cli.md| index b477aeeaa..8638a2d39 100644 |
| --- a/docs/site/cli.md |
| +++ b/docs/site/cli.md |
| @@ -2286,6 +2286,8 @@ carlos system host-env write builds /etc/carlos/host.env from the deployment's p |
| carlos system install-channel installs the release the box's channel names, as both carlos and carlos.prev, once it passes the published host floor. A box that already runs a release is refused: carlos system update moves it. |
| +With --policy FILE, the armed policy carlos system claims run sign-policy wrote, it installs before the host floor is published. The policy's floor stands in for the published one, and once a floor is published the two must be the same. It also arms a box that carlos system update has already moved to the channel's release: carlos is left as it is, and carlos.prev, the release before, is replaced by it, so guard-arm finds the release in both. A box on another release, or with an armed guard, is still refused. |
| + |
| ```sh |
| carlos system host-env write --deployment prod --region ie |
| carlos system install-channel |
diff --git a/docs/superpowers/notes/census-proposal-claim-prev-rehearsal.md b/docs/superpowers/notes/census-proposal-claim-prev-rehearsal.md| index 8eea106d9..72e3bb247 100644 |
| --- a/docs/superpowers/notes/census-proposal-claim-prev-rehearsal.md |
| +++ b/docs/superpowers/notes/census-proposal-claim-prev-rehearsal.md |
| @@ -52,3 +52,15 @@ What changed: |
| | `completePrev`, `prevRecorded`, `newChannelInstall`, `install` | no read site of their own | envelope | the same root shell | |
| The internal/host envelope is re-pinned. The seal stands. No 9B8 entry: no new bucket writer. (On a census box, verifyPointer remembers the channel identity in root's guarded identities, as the update already does; that is a local root-state write, not a bucket write.) |
| + |
| +## Fix round 2 (PROPOSED) |
| + |
| +Overlay `internal/provision/testdata/plan14-root-reads-claim-prev-rehearsal-fix2.tsv`, loaded after fix1. Envelope only: the internal/host envelope is re-pinned, and no root-read unit's digest or read sites change. |
| + |
| +What changed, all in the `--policy` path: |
| + |
| +- "Already installed" always runs carlos.prev's judgement again: `channelPrevJudge`, which is `notePrev` without a replace. It fails on the judgement's error, so a judgement that was cut off or failed is finished or reported, never answered over. `completePrev` ends with the same judgement, after the version record. |
| +- The unfinished-update refusal's reason drops guard-arm's floor prefix. |
| +- `installChannelUsage` is now one literal, of the same value, so the re-approved RHB-C19 can be pinned whole. |
| + |
| +The seal stands. No 9B8 entry. |
diff --git a/docs/superpowers/plans/2026-10-17-region-ha-2b-copy.md b/docs/superpowers/plans/2026-10-17-region-ha-2b-copy.md| index fb4ca8457..7284ee8f6 100644 |
| --- a/docs/superpowers/plans/2026-10-17-region-ha-2b-copy.md |
| +++ b/docs/superpowers/plans/2026-10-17-region-ha-2b-copy.md |
| @@ -79,7 +79,7 @@ as stable; `<platform>` such as linux-arm64; `<box>` a box's reference. |
| | id | file | text | status | |
| |---|---|---|---| |
| -| RHB-C19 | internal/host/installchannel.go (a fragment of the usage) | Install the release this box's channel names, as root, on a box that has none yet. | approved | |
| +| RHB-C19 | internal/host/installchannel.go (a fragment of the usage, re-approved by Paul 2026-10-18 (claim-prev-rehearsal)) | usage: carlos system install-channel [--policy FILE]\n\nInstall the release this box's channel names, as root, on a box that has none yet. | approved | |
| | RHB-C20 | internal/host/installchannel.go | carlos system install-channel runs as root: it installs the platform binary | approved | |
| | RHB-C21 | internal/host/installchannel.go | <variable>=<value> is not a channel, so nothing was installed | approved | |
| | RHB-C22 | internal/host/installchannel.go | <version> (<tag>) from the <channel> channel is already installed | approved | |
diff --git a/internal/host/installchannel.go b/internal/host/installchannel.go| index c7860a94f..4e0aa52d4 100644 |
| --- a/internal/host/installchannel.go |
| +++ b/internal/host/installchannel.go |
| @@ -11,6 +11,7 @@ import ( |
| "os" |
| "path/filepath" |
| "runtime" |
| + "strings" |
| "github.com/carlosframework/platform/internal/cliflag" |
| "github.com/carlosframework/platform/internal/hostfloor" |
| @@ -53,8 +54,7 @@ import ( |
| // (refreshPrev), because guard-arm requires both slots to be the |
| // identity's release. Without --policy nothing here changes. |
| -const installChannelUsage = "usage: carlos system install-channel [--policy FILE]\n\n" + |
| - "Install the release this box's channel names, as root, on a box that has none yet.\n" |
| +const installChannelUsage = "usage: carlos system install-channel [--policy FILE]\n\nInstall the release this box's channel names, as root, on a box that has none yet.\n" |
| const installChannelPolicyFlag = "the armed policy carlos system claims run sign-policy wrote, used as the host floor until one is published" |
| @@ -271,6 +271,11 @@ func (in channelInstall) install(ctx context.Context) (channelInstalled, error) |
| // refuses it: recovery owns carlos.prev until then (fix round 1, I1). |
| if policy != nil { |
| if err := guardArmIdle(systemGuardDir(a.optDir), systemRootDir(a.optDir)); err != nil { |
| + // The reason is the journal or marker guard-arm names, without |
| + // guard-arm's floor prefix: it is an unfinished update. |
| + if errors.Is(err, hostfloor.ErrFloor) { |
| + err = errors.New(strings.TrimPrefix(err.Error(), hostfloor.ErrFloor.Error()+": ")) |
| + } |
| return got, fmt.Errorf("an update on this box has not finished, so nothing was installed: %w", err) |
| } |
| } |
| @@ -282,10 +287,19 @@ func (in channelInstall) install(ctx context.Context) (channelInstalled, error) |
| // managed: its releases come through carlos system update. |
| if already { |
| // With --policy, "already installed" also needs carlos.prev's |
| - // version record to name this release; one a cut-off run left |
| - // missing or stale is completed first, or the run fails. |
| - if policy != nil && !prevRecorded(a.optDir, p.Current.Version) { |
| - if err := in.completePrev(ctx, p.Current.Version, false); err != nil { |
| + // version record to name this release, and its judgement to have |
| + // run. A record a cut-off run left missing or stale is completed |
| + // first (which judges); otherwise the judgement runs again, as it |
| + // is idempotent, so one a run left unfinished is finished. Either |
| + // failing fails the run (fix round 2). |
| + if policy != nil { |
| + var err error |
| + if !prevRecorded(a.optDir, p.Current.Version) { |
| + err = in.completePrev(ctx, p.Current.Version, false) |
| + } else { |
| + err = channelPrevJudge(a, ctx) |
| + } |
| + if err != nil { |
| return got, err |
| } |
| } |
| @@ -473,9 +487,14 @@ func (in channelInstall) completePrev(ctx context.Context, version string, copyC |
| if err != nil { |
| return fmt.Errorf("the record of the release in carlos.prev could not be written, so the install is not complete. run it again: %w", err) |
| } |
| - return a.notePrev(ctx, false, "") |
| + return channelPrevJudge(a, ctx) |
| } |
| +// channelPrevJudge is the floor's judgement of carlos.prev, as an update |
| +// makes it (notePrev without a replace); a seam for the tests of a |
| +// judgement that fails or is cut off. |
| +var channelPrevJudge = func(a *systemApplier, ctx context.Context) error { return a.notePrev(ctx, false, "") } |
| + |
| // refreshPrev is --policy's one admission of an updated box (applied.json |
| // names the channel release, and the slots and the identity are not yet |
| // install-channel's): carlos must already hold the channel release's |
diff --git a/internal/host/installchannel_policy_linux_test.go b/internal/host/installchannel_policy_linux_test.go| new file mode 100644 |
| index 000000000..a81b329e2 |
| --- /dev/null |
| +++ b/internal/host/installchannel_policy_linux_test.go |
| @@ -0,0 +1,46 @@ |
| +//go:build linux |
| + |
| +package host |
| + |
| +import ( |
| + "bytes" |
| + "os" |
| + "path/filepath" |
| + "strings" |
| + "syscall" |
| + "testing" |
| +) |
| + |
| +// M3, Linux: a symlink, a FIFO, a directory and an oversized --policy file |
| +// are refused before anything is read; the FIFO's open does not wait for a |
| +// writer. |
| +func TestInstallChannelPolicyOpenRefusesWhatIsNotAPolicyFile(t *testing.T) { |
| + dir := t.TempDir() |
| + // M3: a symlink, a FIFO, a directory and an oversized file are refused |
| + // before anything is read; the FIFO's open does not wait for a writer. |
| + raw0 := signedPolicyBytes(t) |
| + real := filepath.Join(dir, "real.json") |
| + if err := os.WriteFile(real, raw0, 0o600); err != nil { |
| + t.Fatal(err) |
| + } |
| + link := filepath.Join(dir, "link.json") |
| + if err := os.Symlink(real, link); err != nil { |
| + t.Fatal(err) |
| + } |
| + fifo := filepath.Join(dir, "fifo.json") |
| + if err := syscall.Mkfifo(fifo, 0o600); err != nil { |
| + t.Fatal(err) |
| + } |
| + big := filepath.Join(dir, "big.json") |
| + if err := os.WriteFile(big, append(raw0, bytes.Repeat([]byte(" "), channelPolicyLimit)...), 0o600); err != nil { |
| + t.Fatal(err) |
| + } |
| + for _, path := range []string{link, fifo, dir, big} { |
| + if _, err := readChannelPolicy(path); err == nil || !strings.HasPrefix(err.Error(), "the policy "+path+" could not be read, so nothing was installed: ") { |
| + t.Fatalf("%s: %v", path, err) |
| + } |
| + } |
| + if got, err := readChannelPolicy(real); err != nil || !bytes.Equal(got, raw0) { |
| + t.Fatalf("the real file: %v", err) |
| + } |
| +} |
diff --git a/internal/host/installchannel_policy_rootrunner_linux_test.go b/internal/host/installchannel_policy_rootrunner_linux_test.go| index b19a094d0..719b7cd4b 100644 |
| --- a/internal/host/installchannel_policy_rootrunner_linux_test.go |
| +++ b/internal/host/installchannel_policy_rootrunner_linux_test.go |
| @@ -6,6 +6,7 @@ import ( |
| "bytes" |
| "context" |
| "encoding/base64" |
| + "encoding/json" |
| "os" |
| "path/filepath" |
| "testing" |
| @@ -67,9 +68,14 @@ func TestRootInstallChannelPolicyCensusBox(t *testing.T) { |
| t.Fatal("the census box's root floor state", err) |
| } |
| defer floors.close() |
| - if verdict, err := floors.public.Read("prev.json"); err != nil || len(verdict) == 0 { |
| + raw, err := floors.public.Read("prev.json") |
| + if err != nil { |
| t.Fatal("the root moves floor did not judge the new carlos.prev", err) |
| } |
| + var verdict rootFloorVerdict |
| + if err := json.Unmarshal(raw, &verdict); err != nil || verdict.Identity != id || verdict.PrevAbsent || verdict.Prev.Version != "v1.4.0" || verdict.Prev.Digest != hostfloor.Digest(f.body) { |
| + t.Fatalf("the judgement is not of the replaced carlos.prev (v1.4.0): %s %v", raw, err) |
| + } |
| if again, err := f.in.run(context.Background()); err != nil || !again.Already { |
| t.Fatalf("rerun %+v %v", again, err) |
| } |
diff --git a/internal/host/installchannel_policy_test.go b/internal/host/installchannel_policy_test.go| index 607e67814..3cad35a6f 100644 |
| --- a/internal/host/installchannel_policy_test.go |
| +++ b/internal/host/installchannel_policy_test.go |
| @@ -9,7 +9,6 @@ import ( |
| "os" |
| "path/filepath" |
| "strings" |
| - "syscall" |
| "testing" |
| "github.com/carlosframework/platform/internal/hostfloor" |
| @@ -341,7 +340,7 @@ func TestInstallChannelPolicyRefusals(t *testing.T) { |
| if err := os.WriteFile(f.a.file("journal.json"), []byte("{}"), 0o600); err != nil { |
| t.Fatal(err) |
| } |
| - }, "an update on this box has not finished, so nothing was installed: hostfloor: authenticated floor required: %opt%/system/router/journal.json is open; let the update finish or recover first"}, |
| + }, "an update on this box has not finished, so nothing was installed: %opt%/system/router/journal.json is open; let the update finish or recover first"}, |
| {"an update-begun marker", func(t *testing.T, f *channelFixture) { |
| f.in.policy = signedPolicy(t, f.priv, "bucket", hostfloor.Floor{Min: 1, MinRelease: "v1.4.0"}) |
| if err := os.MkdirAll(systemGuardDir(f.a.optDir), 0o755); err != nil { |
| @@ -350,7 +349,7 @@ func TestInstallChannelPolicyRefusals(t *testing.T) { |
| if err := os.WriteFile(filepath.Join(systemGuardDir(f.a.optDir), updateBegunFile), []byte("{}"), 0o644); err != nil { |
| t.Fatal(err) |
| } |
| - }, "an update on this box has not finished, so nothing was installed: hostfloor: authenticated floor required: %opt%/system/guard/" + updateBegunFile + " is present; let the update finish or recover first"}, |
| + }, "an update on this box has not finished, so nothing was installed: %opt%/system/guard/" + updateBegunFile + " is present; let the update finish or recover first"}, |
| {"a census that cannot be read", func(t *testing.T, f *channelFixture) { |
| f.in.policy = signedPolicy(t, f.priv, "bucket", hostfloor.Floor{Min: 1, MinRelease: "v1.4.0"}) |
| f.a.rootMoves = func() (rootFloorIdentity, bool, error) { |
| @@ -540,33 +539,6 @@ func TestInstallChannelPolicyFileIsReadUpFront(t *testing.T) { |
| t.Fatalf("%q: %v", body, err) |
| } |
| } |
| - // M3: a symlink, a FIFO, a directory and an oversized file are refused |
| - // before anything is read; the FIFO's open does not wait for a writer. |
| - raw0 := signedPolicyBytes(t) |
| - real := filepath.Join(dir, "real.json") |
| - if err := os.WriteFile(real, raw0, 0o600); err != nil { |
| - t.Fatal(err) |
| - } |
| - link := filepath.Join(dir, "link.json") |
| - if err := os.Symlink(real, link); err != nil { |
| - t.Fatal(err) |
| - } |
| - fifo := filepath.Join(dir, "fifo.json") |
| - if err := syscall.Mkfifo(fifo, 0o600); err != nil { |
| - t.Fatal(err) |
| - } |
| - big := filepath.Join(dir, "big.json") |
| - if err := os.WriteFile(big, append(raw0, bytes.Repeat([]byte(" "), channelPolicyLimit)...), 0o600); err != nil { |
| - t.Fatal(err) |
| - } |
| - for _, path := range []string{link, fifo, dir, big} { |
| - if _, err := readChannelPolicy(path); err == nil || !strings.HasPrefix(err.Error(), "the policy "+path+" could not be read, so nothing was installed: ") { |
| - t.Fatalf("%s: %v", path, err) |
| - } |
| - } |
| - if got, err := readChannelPolicy(real); err != nil || !bytes.Equal(got, raw0) { |
| - t.Fatalf("the real file: %v", err) |
| - } |
| _, key, _ := ed25519.GenerateKey(nil) |
| raw := signedPolicy(t, key, "bucket", hostfloor.Floor{Min: 1, MinRelease: "v1.4.0"}) |
| if err := os.WriteFile(missing, raw, 0o600); err != nil { |
| @@ -633,6 +605,97 @@ func TestInstallChannelPolicyTakesTheCensusBranch(t *testing.T) { |
| } |
| } |
| +// abaReturn is the fix round 2 box: installed at A (v1.4.0) with --policy, |
| +// updated to B and back to A, so A's channel identity is still held, with |
| +// a moves floor armed at A on the box so that judging carlos.prev writes a |
| +// verdict. |
| +func abaReturn(t *testing.T) *channelFixture { |
| + t.Helper() |
| + f := newChannelFixture(t, "") |
| + f.in.policy = signedPolicy(t, f.priv, "bucket", hostfloor.Floor{Min: 1, MinRelease: "v1.4.0"}) |
| + if got, err := f.in.run(context.Background()); err != nil || got.Already || got.Replaced { |
| + t.Fatalf("install A: %+v %v", got, err) |
| + } |
| + applyChannelUpdate(t, f, "v1.5.0", []byte("#!/bin/sh\necho carlos v1.5.0\n"), true) |
| + applyChannelUpdate(t, f, "v1.4.0", f.body, false) |
| + armLocalFloor(t, f.a.dataDir, "v1.4.0") |
| + return f |
| +} |
| + |
| +// failJudge makes the floor's judgement of carlos.prev fail (as a cut-off |
| +// or failing judgement) until restored; calls counts every judgement. |
| +func failJudge(t *testing.T, calls *int) func() { |
| + t.Helper() |
| + real := channelPrevJudge |
| + channelPrevJudge = func(a *systemApplier, ctx context.Context) error { |
| + *calls++ |
| + return errors.New("judgement cut off") |
| + } |
| + restore := func() { |
| + channelPrevJudge = func(a *systemApplier, ctx context.Context) error { |
| + *calls++ |
| + return real(a, ctx) |
| + } |
| + } |
| + t.Cleanup(func() { channelPrevJudge = real }) |
| + return restore |
| +} |
| + |
| +// Fix round 2: the refresh cut off after carlos.prev's record is written |
| +// and before its judgement finishes, over a matching identity. Binaries, |
| +// identity and record then all match A; the retry still judges, and only |
| +// then answers "already installed". |
| +func TestInstallChannelPolicyFinishesAJudgementCutOffAfterTheRecord(t *testing.T) { |
| + f := abaReturn(t) |
| + ctx := context.Background() |
| + calls := 0 |
| + restore := failJudge(t, &calls) |
| + if got, err := f.in.run(ctx); err == nil || err.Error() != "judgement cut off" || got.Already || got.Replaced { |
| + t.Fatalf("the cut-off refresh: %+v %v", got, err) |
| + } |
| + if !prevRecorded(f.a.optDir, "v1.4.0") { |
| + t.Fatal("the cut-off refresh did not write the record first") |
| + } |
| + if _, err := os.Stat(prevAllowedPath(f.a.optDir)); !os.IsNotExist(err) { |
| + t.Fatalf("a verdict before the judgement: %v", err) |
| + } |
| + if cur, prev := f.slots(t); !bytes.Equal(cur, f.body) || !bytes.Equal(prev, f.body) { |
| + t.Fatal("slots") |
| + } |
| + restore() |
| + got, err := f.in.run(ctx) |
| + if err != nil || !got.Already || calls != 2 { |
| + t.Fatalf("the retry: %+v %v after %d judgements", got, err, calls) |
| + } |
| + if v, ok := readLine(prevAllowedPath(f.a.optDir)); !ok || v != "v1.4.0" { |
| + t.Fatalf("the retry did not finish the judgement: %q %v", v, ok) |
| + } |
| + // Every --policy "already installed" judges again. |
| + if got, err := f.in.run(ctx); err != nil || !got.Already || calls != 3 { |
| + t.Fatalf("a rerun: %+v %v after %d judgements", got, err, calls) |
| + } |
| +} |
| + |
| +// Fix round 2: a judgement that keeps failing is reported every time, over |
| +// a record and an identity that match; never "already installed". |
| +func TestInstallChannelPolicyReportsAFailingJudgement(t *testing.T) { |
| + f := abaReturn(t) |
| + ctx := context.Background() |
| + calls := 0 |
| + failJudge(t, &calls) |
| + for i := 0; i < 3; i++ { |
| + if got, err := f.in.run(ctx); err == nil || err.Error() != "judgement cut off" || got.Already { |
| + t.Fatalf("run %d: %+v %v", i, got, err) |
| + } |
| + } |
| + if calls != 3 || !prevRecorded(f.a.optDir, "v1.4.0") { |
| + t.Fatalf("%d judgements", calls) |
| + } |
| + if _, err := os.Stat(prevAllowedPath(f.a.optDir)); !os.IsNotExist(err) { |
| + t.Fatalf("a verdict from a failed judgement: %v", err) |
| + } |
| +} |
| + |
| // floorUnreadable fails every read of the host floor's two objects. |
| type floorUnreadable struct{ release.Store } |
diff --git a/internal/provision/plan14_root_reads_test.go b/internal/provision/plan14_root_reads_test.go| index f070466ef..d8c0abf63 100644 |
| --- a/internal/provision/plan14_root_reads_test.go |
| +++ b/internal/provision/plan14_root_reads_test.go |
| @@ -875,6 +875,14 @@ func TestPlan14RootReadCensus(t *testing.T) { |
| if err = loadRootReadInventory(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-claim-prev-rehearsal-fix1.tsv"), &inv, true); err != nil { |
| t.Fatal(err) |
| } |
| + // Census overlay claim-prev-rehearsal-fix2 (fix round 2: carlos.prev's |
| + // judgement always finished on --policy's already-installed path), |
| + // loaded last. Envelope only. PROPOSED |
| + // (docs/superpowers/notes/census-proposal-claim-prev-rehearsal.md, |
| + // "Fix round 2"). The seal stands. |
| + if err = loadRootReadInventory(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-claim-prev-rehearsal-fix2.tsv"), &inv, true); err != nil { |
| + t.Fatal(err) |
| + } |
| errs := append(compareRootReads(got, inv), checkRootReadLegacy(inv, seal)...) |
| errs = append(errs, checkRootReadDeclarations(got, inv)...) |
| if len(errs) > 0 { |
diff --git a/internal/provision/testdata/plan14-root-reads-claim-prev-rehearsal-fix2.tsv b/internal/provision/testdata/plan14-root-reads-claim-prev-rehearsal-fix2.tsv| new file mode 100644 |
| index 000000000..e7c547211 |
| --- /dev/null |
| +++ b/internal/provision/testdata/plan14-root-reads-claim-prev-rehearsal-fix2.tsv |
| @@ -0,0 +1,3 @@ |
| +# Census overlay claim-prev-rehearsal-fix2 (fix round 2: Astra's re-review and platform-00's minors, as ruled by the controller), loaded LAST, after claim-prev-rehearsal-fix1. PROPOSED, not ruled: docs/superpowers/notes/census-proposal-claim-prev-rehearsal.md, section "Fix round 2". |
| +# Envelope only. In the install-channel --policy path, "already installed" always runs carlos.prev's judgement again (channelPrevJudge, notePrev without a replace) and fails on its error; completePrev ends with the same judgement; the unfinished-update refusal's reason drops guard-arm's floor prefix; installChannelUsage is one literal of the same value (RHB-C19, re-approved). No root-read unit's digest or sites change: channelInstall.run, channelInstall.refreshPrev and openChannelPolicy stand at fix1's rows. The seal stands; the 9B8 inventory is unchanged. |
| +@coverage internal/host 33968e7c537bbb72b15c1c9641515921341507d38d4150589f27370e919ba36c |