carlos / platform Public

moves: JournalFile and DecodeJournal, so root can judge a journal it read through an anchored open (Task 7b)

ReadJournal's decoding is now DecodeJournal over bytes the caller read,
unchanged in what it accepts. Root's held release reads the render journal
through safefile, never by name (#267).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 27b83074c34225e7dff60deafb2fd2787399bbbc parent f8997c3
1 file changed, +18 −1
  • internal/moves/journal.go +18 −1
diff --git a/internal/moves/journal.go b/internal/moves/journal.go
index c8bb22ea7..2e4d23b1b 100644
--- a/internal/moves/journal.go
+++ b/internal/moves/journal.go
@@ -144,6 +144,14 @@ func journalNames(host, op string) error {
return journalName("op", op)
}
+// JournalFile is the journal's file for (host, op):
+// <JournalDir>/journal.json. Root reads it only through an anchored,
+// no-symlink open of a regular file (internal/host's held release), never
+// by name with ReadJournal.
+func JournalFile(dataDir, host, op string) string {
+ return filepath.Join(JournalDir(dataDir, host, op), journalFile)
+}
+
// ReadJournal loads the journal for (host, op). A journal that does not
// exist yet is an empty one. A file that exists but cannot be trusted
// (unreadable, not valid JSON, an unknown version, or naming a different
@@ -153,13 +161,22 @@ func ReadJournal(dataDir, host, op string) (*Journal, error) {
if err := journalNames(host, op); err != nil {
return nil, err
}
- b, err := os.ReadFile(filepath.Join(JournalDir(dataDir, host, op), journalFile))
+ b, err := os.ReadFile(JournalFile(dataDir, host, op))
if errors.Is(err, fs.ErrNotExist) {
return &Journal{V: journalVersion, Host: host, Op: op, Steps: map[string]string{}}, nil
}
if err != nil {
return nil, fmt.Errorf("moves journal %s/%s: %w", host, op, err)
}
+ return DecodeJournal(b, host, op)
+}
+
+// DecodeJournal is ReadJournal's judgement of a journal's bytes b, read by
+// the caller: one JSON object of this version naming exactly (host, op).
+func DecodeJournal(b []byte, host, op string) (*Journal, error) {
+ if err := journalNames(host, op); err != nil {
+ return nil, err
+ }
var j Journal
dec := json.NewDecoder(bytes.NewReader(b))
if err := dec.Decode(&j); err != nil {