devrites 5.12.0 → 5.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +5 -3
- package/docs/architecture.md +2 -2
- package/docs/command-map.md +3 -1
- package/docs/engine/commands.md +21 -0
- package/docs/flow.md +2 -1
- package/docs/skills.md +13 -4
- package/engine/help.go +4 -1
- package/engine/help_test.go +2 -0
- package/engine/internal/overhaul/admit/admit_test.go +170 -0
- package/engine/internal/overhaul/admit/run.go +416 -0
- package/engine/internal/overhaul/bench/run.go +248 -0
- package/engine/internal/overhaul/bench/run_test.go +139 -0
- package/engine/internal/overhaul/overhaul.go +64 -0
- package/engine/internal/overhaul/ovio/ovio.go +158 -0
- package/engine/internal/overhaul/records/check.go +834 -0
- package/engine/internal/overhaul/records/records_test.go +577 -0
- package/engine/internal/overhaul/records/run.go +392 -0
- package/engine/internal/overhaul/render/run.go +776 -0
- package/engine/internal/overhaul/render/run_test.go +293 -0
- package/engine/internal/overhaul/score/run.go +600 -0
- package/engine/internal/overhaul/score/run_test.go +405 -0
- package/engine/internal/overhaul/snapshot/run.go +563 -0
- package/engine/internal/overhaul/snapshot/run_test.go +360 -0
- package/engine/main.go +4 -0
- package/engine/root_routing_test.go +2 -1
- package/pack/.claude/skills/overhaul/SKILL.md +201 -0
- package/pack/.claude/skills/overhaul/references/audit-domains.md +263 -0
- package/pack/.claude/skills/overhaul/references/lifecycle.md +308 -0
- package/pack/.claude/skills/overhaul/references/orchestration.md +264 -0
- package/pack/.claude/skills/overhaul/references/records.md +137 -0
- package/pack/.claude/skills/overhaul/references/repair-and-measurement.md +207 -0
- package/pack/.claude/skills/overhaul/references/review-and-report.md +137 -0
- package/pack/.claude/skills/overhaul/references/scope-and-safety.md +191 -0
- package/pack/.claude/skills/overhaul/references/scoring.md +222 -0
- package/pack/.claude/skills/overhaul/references/stack-profiles.md +264 -0
- package/pack/.claude/skills/overhaul/roles/backend-engineer.md +61 -0
- package/pack/.claude/skills/overhaul/roles/boundary.md +56 -0
- package/pack/.claude/skills/overhaul/roles/craft.md +77 -0
- package/pack/.claude/skills/overhaul/roles/frontend-engineer.md +63 -0
- package/pack/.claude/skills/overhaul/roles/implementer.md +59 -0
- package/pack/.claude/skills/overhaul/roles/recon.md +60 -0
- package/pack/.claude/skills/overhaul/roles/specialist.md +56 -0
- package/pack/.claude/skills/overhaul/roles/verifier.md +60 -0
- package/pack/generated/claude/skills/overhaul/SKILL.md +201 -0
- package/pack/generated/claude/skills/overhaul/references/audit-domains.md +263 -0
- package/pack/generated/claude/skills/overhaul/references/lifecycle.md +308 -0
- package/pack/generated/claude/skills/overhaul/references/orchestration.md +264 -0
- package/pack/generated/claude/skills/overhaul/references/records.md +137 -0
- package/pack/generated/claude/skills/overhaul/references/repair-and-measurement.md +207 -0
- package/pack/generated/claude/skills/overhaul/references/review-and-report.md +137 -0
- package/pack/generated/claude/skills/overhaul/references/scope-and-safety.md +191 -0
- package/pack/generated/claude/skills/overhaul/references/scoring.md +222 -0
- package/pack/generated/claude/skills/overhaul/references/stack-profiles.md +264 -0
- package/pack/generated/claude/skills/overhaul/roles/backend-engineer.md +61 -0
- package/pack/generated/claude/skills/overhaul/roles/boundary.md +56 -0
- package/pack/generated/claude/skills/overhaul/roles/craft.md +77 -0
- package/pack/generated/claude/skills/overhaul/roles/frontend-engineer.md +63 -0
- package/pack/generated/claude/skills/overhaul/roles/implementer.md +59 -0
- package/pack/generated/claude/skills/overhaul/roles/recon.md +60 -0
- package/pack/generated/claude/skills/overhaul/roles/specialist.md +56 -0
- package/pack/generated/claude/skills/overhaul/roles/verifier.md +60 -0
- package/pack/generated/codex/skills/overhaul/SKILL.md +201 -0
- package/pack/generated/codex/skills/overhaul/agents/openai.yaml +2 -0
- package/pack/generated/codex/skills/overhaul/references/audit-domains.md +263 -0
- package/pack/generated/codex/skills/overhaul/references/lifecycle.md +308 -0
- package/pack/generated/codex/skills/overhaul/references/orchestration.md +264 -0
- package/pack/generated/codex/skills/overhaul/references/records.md +137 -0
- package/pack/generated/codex/skills/overhaul/references/repair-and-measurement.md +207 -0
- package/pack/generated/codex/skills/overhaul/references/review-and-report.md +137 -0
- package/pack/generated/codex/skills/overhaul/references/scope-and-safety.md +191 -0
- package/pack/generated/codex/skills/overhaul/references/scoring.md +222 -0
- package/pack/generated/codex/skills/overhaul/references/stack-profiles.md +264 -0
- package/pack/generated/codex/skills/overhaul/roles/backend-engineer.md +61 -0
- package/pack/generated/codex/skills/overhaul/roles/boundary.md +56 -0
- package/pack/generated/codex/skills/overhaul/roles/craft.md +77 -0
- package/pack/generated/codex/skills/overhaul/roles/frontend-engineer.md +63 -0
- package/pack/generated/codex/skills/overhaul/roles/implementer.md +59 -0
- package/pack/generated/codex/skills/overhaul/roles/recon.md +60 -0
- package/pack/generated/codex/skills/overhaul/roles/specialist.md +56 -0
- package/pack/generated/codex/skills/overhaul/roles/verifier.md +60 -0
- package/pack/generated/devin/skills/overhaul/SKILL.md +201 -0
- package/pack/generated/devin/skills/overhaul/references/audit-domains.md +263 -0
- package/pack/generated/devin/skills/overhaul/references/lifecycle.md +308 -0
- package/pack/generated/devin/skills/overhaul/references/orchestration.md +264 -0
- package/pack/generated/devin/skills/overhaul/references/records.md +137 -0
- package/pack/generated/devin/skills/overhaul/references/repair-and-measurement.md +207 -0
- package/pack/generated/devin/skills/overhaul/references/review-and-report.md +137 -0
- package/pack/generated/devin/skills/overhaul/references/scope-and-safety.md +191 -0
- package/pack/generated/devin/skills/overhaul/references/scoring.md +222 -0
- package/pack/generated/devin/skills/overhaul/references/stack-profiles.md +264 -0
- package/pack/generated/devin/skills/overhaul/roles/backend-engineer.md +61 -0
- package/pack/generated/devin/skills/overhaul/roles/boundary.md +56 -0
- package/pack/generated/devin/skills/overhaul/roles/craft.md +77 -0
- package/pack/generated/devin/skills/overhaul/roles/frontend-engineer.md +63 -0
- package/pack/generated/devin/skills/overhaul/roles/implementer.md +59 -0
- package/pack/generated/devin/skills/overhaul/roles/recon.md +60 -0
- package/pack/generated/devin/skills/overhaul/roles/specialist.md +56 -0
- package/pack/generated/devin/skills/overhaul/roles/verifier.md +60 -0
- package/pack/generated/omp/skills/overhaul/SKILL.md +201 -0
- package/pack/generated/omp/skills/overhaul/references/audit-domains.md +263 -0
- package/pack/generated/omp/skills/overhaul/references/lifecycle.md +308 -0
- package/pack/generated/omp/skills/overhaul/references/orchestration.md +264 -0
- package/pack/generated/omp/skills/overhaul/references/records.md +137 -0
- package/pack/generated/omp/skills/overhaul/references/repair-and-measurement.md +207 -0
- package/pack/generated/omp/skills/overhaul/references/review-and-report.md +137 -0
- package/pack/generated/omp/skills/overhaul/references/scope-and-safety.md +191 -0
- package/pack/generated/omp/skills/overhaul/references/scoring.md +222 -0
- package/pack/generated/omp/skills/overhaul/references/stack-profiles.md +264 -0
- package/pack/generated/omp/skills/overhaul/roles/backend-engineer.md +61 -0
- package/pack/generated/omp/skills/overhaul/roles/boundary.md +56 -0
- package/pack/generated/omp/skills/overhaul/roles/craft.md +77 -0
- package/pack/generated/omp/skills/overhaul/roles/frontend-engineer.md +63 -0
- package/pack/generated/omp/skills/overhaul/roles/implementer.md +59 -0
- package/pack/generated/omp/skills/overhaul/roles/recon.md +60 -0
- package/pack/generated/omp/skills/overhaul/roles/specialist.md +56 -0
- package/pack/generated/omp/skills/overhaul/roles/verifier.md +60 -0
- package/pack/generated/pi/skills/overhaul/SKILL.md +201 -0
- package/pack/generated/pi/skills/overhaul/references/audit-domains.md +263 -0
- package/pack/generated/pi/skills/overhaul/references/lifecycle.md +308 -0
- package/pack/generated/pi/skills/overhaul/references/orchestration.md +264 -0
- package/pack/generated/pi/skills/overhaul/references/records.md +137 -0
- package/pack/generated/pi/skills/overhaul/references/repair-and-measurement.md +207 -0
- package/pack/generated/pi/skills/overhaul/references/review-and-report.md +137 -0
- package/pack/generated/pi/skills/overhaul/references/scope-and-safety.md +191 -0
- package/pack/generated/pi/skills/overhaul/references/scoring.md +222 -0
- package/pack/generated/pi/skills/overhaul/references/stack-profiles.md +264 -0
- package/pack/generated/pi/skills/overhaul/roles/backend-engineer.md +61 -0
- package/pack/generated/pi/skills/overhaul/roles/boundary.md +56 -0
- package/pack/generated/pi/skills/overhaul/roles/craft.md +77 -0
- package/pack/generated/pi/skills/overhaul/roles/frontend-engineer.md +63 -0
- package/pack/generated/pi/skills/overhaul/roles/implementer.md +59 -0
- package/pack/generated/pi/skills/overhaul/roles/recon.md +60 -0
- package/pack/generated/pi/skills/overhaul/roles/specialist.md +56 -0
- package/pack/generated/pi/skills/overhaul/roles/verifier.md +60 -0
- package/package.json +1 -1
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
// Package records implements the /overhaul records tool: generation staging
|
|
2
|
+
// and publishing plus cross-record validation of a run area.
|
|
3
|
+
//
|
|
4
|
+
// digest FILE sha256 of exact bytes
|
|
5
|
+
// stage RUN copy current generation to RUN/g<N+1>.tmp (prints path)
|
|
6
|
+
// publish RUN validate staged generation, write manifest, swap CURRENT last
|
|
7
|
+
// validate RUN verify CURRENT generation integrity + cross-record rules
|
|
8
|
+
//
|
|
9
|
+
// Exit: 0 ok, 1 rule violations (listed), 2 usage/IO error.
|
|
10
|
+
package records
|
|
11
|
+
|
|
12
|
+
import (
|
|
13
|
+
"fmt"
|
|
14
|
+
"io"
|
|
15
|
+
"io/fs"
|
|
16
|
+
"os"
|
|
17
|
+
"path/filepath"
|
|
18
|
+
"regexp"
|
|
19
|
+
"sort"
|
|
20
|
+
"strconv"
|
|
21
|
+
"strings"
|
|
22
|
+
|
|
23
|
+
"github.com/devrites/devrites/internal/overhaul/ovio"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
const usage = `usage: overhaul records <command> ...
|
|
27
|
+
|
|
28
|
+
digest FILE sha256 of exact bytes
|
|
29
|
+
stage RUN copy current generation to RUN/g<N+1>.tmp (prints path)
|
|
30
|
+
publish RUN validate staged generation, write manifest, swap CURRENT last
|
|
31
|
+
validate RUN verify CURRENT generation integrity + cross-record rules
|
|
32
|
+
Exit: 0 ok, 1 rule violations (listed), 2 usage/IO error.
|
|
33
|
+
`
|
|
34
|
+
|
|
35
|
+
// Run executes the tool with its arguments (tool name excluded).
|
|
36
|
+
func Run(args []string, stdout, stderr io.Writer) int {
|
|
37
|
+
if len(args) < 2 {
|
|
38
|
+
fmt.Fprint(stderr, usage)
|
|
39
|
+
return 2
|
|
40
|
+
}
|
|
41
|
+
var code int
|
|
42
|
+
var err error
|
|
43
|
+
switch args[0] {
|
|
44
|
+
case "digest":
|
|
45
|
+
var d string
|
|
46
|
+
if d, err = sha(args[1]); err == nil {
|
|
47
|
+
fmt.Fprintln(stdout, d)
|
|
48
|
+
}
|
|
49
|
+
case "stage":
|
|
50
|
+
code, err = stage(args[1], stdout, stderr)
|
|
51
|
+
case "publish":
|
|
52
|
+
code, err = publish(args[1], stdout, stderr)
|
|
53
|
+
case "validate":
|
|
54
|
+
code, err = validate(args[1], stdout)
|
|
55
|
+
default:
|
|
56
|
+
fmt.Fprint(stderr, usage)
|
|
57
|
+
return 2
|
|
58
|
+
}
|
|
59
|
+
if err != nil {
|
|
60
|
+
fmt.Fprintf(stderr, "ERROR: %v\n", err)
|
|
61
|
+
return 2
|
|
62
|
+
}
|
|
63
|
+
return code
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// current returns the CURRENT generation name; ok is false when there is none.
|
|
67
|
+
func current(run string) (string, bool, error) {
|
|
68
|
+
p := join(run, "CURRENT")
|
|
69
|
+
if !isFile(p) {
|
|
70
|
+
return "", false, nil
|
|
71
|
+
}
|
|
72
|
+
b, err := os.ReadFile(p) // #nosec G304 -- path is an explicit operator argument
|
|
73
|
+
return strings.TrimSpace(string(b)), err == nil, err
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func genNumber(gen string) (int, error) {
|
|
77
|
+
if gen == "" {
|
|
78
|
+
return 0, fmt.Errorf("invalid generation name %q", gen)
|
|
79
|
+
}
|
|
80
|
+
n, err := strconv.Atoi(gen[1:])
|
|
81
|
+
if err != nil {
|
|
82
|
+
return 0, fmt.Errorf("invalid generation name %q", gen)
|
|
83
|
+
}
|
|
84
|
+
return n, nil
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func sortedKeys(m map[string]any) []string {
|
|
88
|
+
keys := make([]string, 0, len(m))
|
|
89
|
+
for k := range m {
|
|
90
|
+
keys = append(keys, k)
|
|
91
|
+
}
|
|
92
|
+
sort.Strings(keys)
|
|
93
|
+
return keys
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func manifestOK(run, genDir string) ([]string, map[string]any, error) {
|
|
97
|
+
m, err := ovio.LoadObject(join(genDir, "manifest.json"))
|
|
98
|
+
if err != nil {
|
|
99
|
+
return nil, nil, err
|
|
100
|
+
}
|
|
101
|
+
var errs []string
|
|
102
|
+
files, revs := ovio.Obj(m["files"]), ovio.Obj(m["revisions"])
|
|
103
|
+
check := func(src map[string]any) error {
|
|
104
|
+
for _, rel := range sortedKeys(src) {
|
|
105
|
+
base := run
|
|
106
|
+
if _, ok := files[rel]; ok {
|
|
107
|
+
base = genDir
|
|
108
|
+
}
|
|
109
|
+
p := join(base, rel)
|
|
110
|
+
ok := isFile(p)
|
|
111
|
+
if ok {
|
|
112
|
+
d, err := sha(p)
|
|
113
|
+
if err != nil {
|
|
114
|
+
return err
|
|
115
|
+
}
|
|
116
|
+
ok = eq(d, src[rel])
|
|
117
|
+
}
|
|
118
|
+
if !ok {
|
|
119
|
+
errs = append(errs, fmt.Sprintf("mixed or modified generation: %s does not match manifest", rel))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return nil
|
|
123
|
+
}
|
|
124
|
+
if err := check(files); err != nil {
|
|
125
|
+
return nil, nil, err
|
|
126
|
+
}
|
|
127
|
+
if err := check(revs); err != nil {
|
|
128
|
+
return nil, nil, err
|
|
129
|
+
}
|
|
130
|
+
return errs, m, nil
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// copyTree is shutil.copytree(src, dst, ignore=ignore_patterns("manifest.json", "views")):
|
|
134
|
+
// symlinks are followed and the two names are skipped at every level.
|
|
135
|
+
func copyTree(src, dst string) error {
|
|
136
|
+
fi, err := os.Stat(src)
|
|
137
|
+
if err != nil {
|
|
138
|
+
return err
|
|
139
|
+
}
|
|
140
|
+
if err := os.MkdirAll(dst, fi.Mode().Perm()); err != nil {
|
|
141
|
+
return err
|
|
142
|
+
}
|
|
143
|
+
entries, err := os.ReadDir(src)
|
|
144
|
+
if err != nil {
|
|
145
|
+
return err
|
|
146
|
+
}
|
|
147
|
+
for _, e := range entries {
|
|
148
|
+
if e.Name() == "manifest.json" || e.Name() == "views" {
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
s, d := filepath.Join(src, e.Name()), filepath.Join(dst, e.Name())
|
|
152
|
+
st, err := os.Stat(s)
|
|
153
|
+
if err != nil {
|
|
154
|
+
return err
|
|
155
|
+
}
|
|
156
|
+
if st.IsDir() {
|
|
157
|
+
err = copyTree(s, d)
|
|
158
|
+
} else {
|
|
159
|
+
// #nosec G304 G703 -- copies a record between directories inside the operator-selected run area
|
|
160
|
+
b, rerr := os.ReadFile(s)
|
|
161
|
+
if err = rerr; err == nil {
|
|
162
|
+
// #nosec G703 -- destination is the staged generation inside the run area
|
|
163
|
+
err = os.WriteFile(d, b, st.Mode().Perm())
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if err != nil {
|
|
167
|
+
return err
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return nil
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
func stage(run string, stdout, stderr io.Writer) (int, error) {
|
|
174
|
+
if err := os.MkdirAll(join(run, "revisions"), 0o700); err != nil {
|
|
175
|
+
return 0, err
|
|
176
|
+
}
|
|
177
|
+
cur, has, err := current(run)
|
|
178
|
+
if err != nil {
|
|
179
|
+
return 0, err
|
|
180
|
+
}
|
|
181
|
+
n, curName := 1, "None"
|
|
182
|
+
if has {
|
|
183
|
+
curName = cur
|
|
184
|
+
}
|
|
185
|
+
if cur != "" {
|
|
186
|
+
g, err := genNumber(cur)
|
|
187
|
+
if err != nil {
|
|
188
|
+
return 0, err
|
|
189
|
+
}
|
|
190
|
+
n = g + 1
|
|
191
|
+
}
|
|
192
|
+
final := join(run, fmt.Sprintf("g%04d", n))
|
|
193
|
+
tmp := final + ".tmp"
|
|
194
|
+
if _, err := os.Stat(final); err == nil {
|
|
195
|
+
fmt.Fprintf(stderr, "g%04d exists but CURRENT is %s: interrupted publish; inspect it, then repair CURRENT or move it aside\n", n, curName)
|
|
196
|
+
return 2, nil
|
|
197
|
+
}
|
|
198
|
+
if _, err := os.Stat(tmp); err == nil {
|
|
199
|
+
fmt.Fprintf(stderr, "staged generation already exists: %s\n", tmp)
|
|
200
|
+
return 2, nil
|
|
201
|
+
}
|
|
202
|
+
base := "none"
|
|
203
|
+
if cur != "" {
|
|
204
|
+
base = cur
|
|
205
|
+
err = copyTree(join(run, cur), tmp)
|
|
206
|
+
} else {
|
|
207
|
+
err = os.MkdirAll(tmp, 0o700)
|
|
208
|
+
}
|
|
209
|
+
if err != nil {
|
|
210
|
+
return 0, err
|
|
211
|
+
}
|
|
212
|
+
if err := os.WriteFile(join(tmp, ".base"), []byte(base), 0o600); err != nil {
|
|
213
|
+
return 0, err
|
|
214
|
+
}
|
|
215
|
+
fmt.Fprintln(stdout, tmp)
|
|
216
|
+
return 0, nil
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
var stagedName = regexp.MustCompile(`^g[0-9]{4}\.tmp$`)
|
|
220
|
+
|
|
221
|
+
func publish(run string, stdout, stderr io.Writer) (int, error) {
|
|
222
|
+
entries, err := os.ReadDir(run)
|
|
223
|
+
if err != nil {
|
|
224
|
+
return 0, err
|
|
225
|
+
}
|
|
226
|
+
var staged []string
|
|
227
|
+
for _, e := range entries {
|
|
228
|
+
if stagedName.MatchString(e.Name()) {
|
|
229
|
+
staged = append(staged, e.Name())
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if len(staged) != 1 {
|
|
233
|
+
fmt.Fprintf(stderr, "expected exactly one staged generation, found %s\n", Repr(staged))
|
|
234
|
+
return 2, nil
|
|
235
|
+
}
|
|
236
|
+
tmp := join(run, staged[0])
|
|
237
|
+
prev, hasPrev, err := current(run)
|
|
238
|
+
if err != nil {
|
|
239
|
+
return 0, err
|
|
240
|
+
}
|
|
241
|
+
b, err := os.ReadFile(join(tmp, ".base"))
|
|
242
|
+
if err != nil {
|
|
243
|
+
return 0, err
|
|
244
|
+
}
|
|
245
|
+
want, prevName := "none", "None"
|
|
246
|
+
if hasPrev {
|
|
247
|
+
prevName = prev
|
|
248
|
+
}
|
|
249
|
+
if prev != "" {
|
|
250
|
+
want = prev
|
|
251
|
+
}
|
|
252
|
+
if base := strings.TrimSpace(string(b)); base != want {
|
|
253
|
+
fmt.Fprintf(stderr, "stale stage: CURRENT moved from %s to %s; re-stage and reapply\n", base, prevName)
|
|
254
|
+
return 1, nil
|
|
255
|
+
}
|
|
256
|
+
prevDir := ""
|
|
257
|
+
if prev != "" {
|
|
258
|
+
prevDir = join(run, prev)
|
|
259
|
+
}
|
|
260
|
+
errs, err := Check(run, tmp, prevDir)
|
|
261
|
+
if err != nil {
|
|
262
|
+
return 0, err
|
|
263
|
+
}
|
|
264
|
+
revs := map[string]any{}
|
|
265
|
+
if prevDir != "" {
|
|
266
|
+
pmErrs, pm, err := manifestOK(run, prevDir)
|
|
267
|
+
if err != nil {
|
|
268
|
+
return 0, err
|
|
269
|
+
}
|
|
270
|
+
errs = append(errs, pmErrs...)
|
|
271
|
+
prevRevs := ovio.Obj(pm["revisions"])
|
|
272
|
+
for _, rel := range sortedKeys(prevRevs) {
|
|
273
|
+
p := join(run, rel)
|
|
274
|
+
ok := isFile(p)
|
|
275
|
+
if ok {
|
|
276
|
+
d, err := sha(p)
|
|
277
|
+
if err != nil {
|
|
278
|
+
return 0, err
|
|
279
|
+
}
|
|
280
|
+
ok = eq(d, prevRevs[rel])
|
|
281
|
+
}
|
|
282
|
+
if !ok {
|
|
283
|
+
errs = append(errs, "immutable revision changed: "+rel)
|
|
284
|
+
}
|
|
285
|
+
revs[rel] = prevRevs[rel]
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
revEntries, err := os.ReadDir(join(run, "revisions"))
|
|
289
|
+
if err != nil {
|
|
290
|
+
return 0, err
|
|
291
|
+
}
|
|
292
|
+
for _, e := range revEntries {
|
|
293
|
+
d, err := sha(join(run, "revisions", e.Name()))
|
|
294
|
+
if err != nil {
|
|
295
|
+
return 0, err
|
|
296
|
+
}
|
|
297
|
+
revs["revisions/"+e.Name()] = d
|
|
298
|
+
}
|
|
299
|
+
if len(errs) > 0 {
|
|
300
|
+
for _, e := range errs {
|
|
301
|
+
fmt.Fprintln(stdout, "VIOLATION: "+e)
|
|
302
|
+
}
|
|
303
|
+
return 1, nil
|
|
304
|
+
}
|
|
305
|
+
if err := os.Remove(join(tmp, ".base")); err != nil {
|
|
306
|
+
return 0, err
|
|
307
|
+
}
|
|
308
|
+
files := map[string]any{}
|
|
309
|
+
err = filepath.WalkDir(tmp, func(p string, d fs.DirEntry, err error) error {
|
|
310
|
+
if err != nil || d.IsDir() {
|
|
311
|
+
return err
|
|
312
|
+
}
|
|
313
|
+
if d.Type()&fs.ModeSymlink != 0 && isDir(p) {
|
|
314
|
+
return nil // os.walk lists a symlinked directory but never descends into it
|
|
315
|
+
}
|
|
316
|
+
rel, err := filepath.Rel(tmp, p)
|
|
317
|
+
if err != nil {
|
|
318
|
+
return err
|
|
319
|
+
}
|
|
320
|
+
digest, err := sha(p)
|
|
321
|
+
files[filepath.ToSlash(rel)] = digest
|
|
322
|
+
return err
|
|
323
|
+
})
|
|
324
|
+
if err != nil {
|
|
325
|
+
return 0, err
|
|
326
|
+
}
|
|
327
|
+
gen := strings.TrimSuffix(staged[0], ".tmp")
|
|
328
|
+
var previous any
|
|
329
|
+
if hasPrev {
|
|
330
|
+
previous = prev
|
|
331
|
+
}
|
|
332
|
+
manifest := map[string]any{"schema": "overhaul.manifest/1", "generation": gen, "previous": previous,
|
|
333
|
+
"files": files, "revisions": revs}
|
|
334
|
+
if err := ovio.WriteJSON(join(tmp, "manifest.json"), manifest, 0o666); err != nil {
|
|
335
|
+
return 0, err
|
|
336
|
+
}
|
|
337
|
+
if err := os.Rename(tmp, join(run, gen)); err != nil {
|
|
338
|
+
return 0, err
|
|
339
|
+
}
|
|
340
|
+
if err := os.WriteFile(join(run, "CURRENT.tmp"), []byte(gen+"\n"), 0o600); err != nil {
|
|
341
|
+
return 0, err
|
|
342
|
+
}
|
|
343
|
+
if err := os.Rename(join(run, "CURRENT.tmp"), join(run, "CURRENT")); err != nil {
|
|
344
|
+
return 0, err
|
|
345
|
+
}
|
|
346
|
+
fmt.Fprintln(stdout, "published "+gen)
|
|
347
|
+
return 0, nil
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
func validate(run string, stdout io.Writer) (int, error) {
|
|
351
|
+
b, err := os.ReadFile(join(run, "CURRENT"))
|
|
352
|
+
if err != nil {
|
|
353
|
+
return 0, err
|
|
354
|
+
}
|
|
355
|
+
gen := strings.TrimSpace(string(b))
|
|
356
|
+
genDir := join(run, gen)
|
|
357
|
+
errs, m, err := manifestOK(run, genDir)
|
|
358
|
+
if err != nil {
|
|
359
|
+
return 0, err
|
|
360
|
+
}
|
|
361
|
+
prevDir := ""
|
|
362
|
+
if p := m["previous"]; ovio.Truthy(p) {
|
|
363
|
+
prevDir = join(run, PyStr(p))
|
|
364
|
+
}
|
|
365
|
+
more, err := Check(run, genDir, prevDir)
|
|
366
|
+
if err != nil {
|
|
367
|
+
return 0, err
|
|
368
|
+
}
|
|
369
|
+
errs = append(errs, more...)
|
|
370
|
+
n, err := genNumber(gen)
|
|
371
|
+
if err != nil {
|
|
372
|
+
return 0, err
|
|
373
|
+
}
|
|
374
|
+
entries, err := os.ReadDir(run)
|
|
375
|
+
if err != nil {
|
|
376
|
+
return 0, err
|
|
377
|
+
}
|
|
378
|
+
next := fmt.Sprintf("g%04d", n+1)
|
|
379
|
+
for _, e := range entries {
|
|
380
|
+
if d := e.Name(); strings.HasSuffix(d, ".tmp") || d == next {
|
|
381
|
+
fmt.Fprintf(stdout, "NOTE: %s is newer than CURRENT (crash or in-progress publish)\n", d)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if len(errs) > 0 {
|
|
385
|
+
for _, e := range errs {
|
|
386
|
+
fmt.Fprintln(stdout, "VIOLATION: "+e)
|
|
387
|
+
}
|
|
388
|
+
return 1, nil
|
|
389
|
+
}
|
|
390
|
+
fmt.Fprintln(stdout, "valid "+gen)
|
|
391
|
+
return 0, nil
|
|
392
|
+
}
|