devrites 5.12.0 → 5.13.1
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 +12 -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/install/binary.go +4 -0
- package/engine/internal/install/install_test.go +22 -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,293 @@
|
|
|
1
|
+
package render
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"crypto/sha256"
|
|
6
|
+
"encoding/base64"
|
|
7
|
+
"encoding/json"
|
|
8
|
+
"os"
|
|
9
|
+
"path/filepath"
|
|
10
|
+
"strings"
|
|
11
|
+
"testing"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
const (
|
|
15
|
+
hostile = "\"><img src=x onerror=alert(1)><script>alert(2)</script>|`rm -rf /`"
|
|
16
|
+
hostileEsc = ""><img src=x onerror=alert(1)><script>alert(2)</script>|`rm -rf /`"
|
|
17
|
+
fakeHome = "/Users/tester"
|
|
18
|
+
badLane = `x"><a href="#p">y</a>`
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
func writeJSON(t *testing.T, path string, v any) {
|
|
22
|
+
t.Helper()
|
|
23
|
+
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
24
|
+
t.Fatal(err)
|
|
25
|
+
}
|
|
26
|
+
b, err := json.Marshal(v)
|
|
27
|
+
if err != nil {
|
|
28
|
+
t.Fatal(err)
|
|
29
|
+
}
|
|
30
|
+
if err := os.WriteFile(path, b, 0o644); err != nil {
|
|
31
|
+
t.Fatal(err)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type obj = map[string]any
|
|
36
|
+
|
|
37
|
+
// fixture builds a run dir and generation; withPlan=false makes an
|
|
38
|
+
// assessment-only audit that has a rubric revision but no plan.
|
|
39
|
+
func fixture(t *testing.T, withPlan bool) (run, gen string) {
|
|
40
|
+
t.Helper()
|
|
41
|
+
t.Setenv("HOME", fakeHome)
|
|
42
|
+
t.Setenv("USERPROFILE", fakeHome) // os.UserHomeDir on Windows
|
|
43
|
+
run = t.TempDir()
|
|
44
|
+
gen = filepath.Join(run, "generations", "g0003.tmp")
|
|
45
|
+
writeJSON(t, filepath.Join(run, "revisions", "rubric-r1.json"), obj{
|
|
46
|
+
"rev": 1,
|
|
47
|
+
"domains": obj{"correctness": 1, "security": 1},
|
|
48
|
+
"lanes": []any{"backend"},
|
|
49
|
+
"controls": []any{
|
|
50
|
+
obj{"id": "C-1", "domain": "correctness", "lanes": []any{"backend"}, "weight": 0.1, "hard_gate": true},
|
|
51
|
+
obj{"id": "C-2", "domain": "security", "lanes": []any{"backend"}, "weight": 0.2},
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
revisions := obj{"rubric": obj{"rev": 1, "file": "revisions/rubric-r1.json"}}
|
|
55
|
+
if withPlan {
|
|
56
|
+
writeJSON(t, filepath.Join(run, "revisions", "plan-r1.json"), obj{
|
|
57
|
+
"rev": 1,
|
|
58
|
+
"rubric": obj{"file": "revisions/rubric-r1.json"},
|
|
59
|
+
"tasks": []any{
|
|
60
|
+
obj{"id": "T-1", "lane": "backend", "findings": []any{"F-1"}, "oracle": "go test ./x", "risk": "low"},
|
|
61
|
+
obj{"id": "T-2", "lane": "frontend"},
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
revisions["plan"] = obj{"rev": 1, "file": "revisions/plan-r1.json"}
|
|
65
|
+
}
|
|
66
|
+
writeJSON(t, filepath.Join(gen, "run.json"), obj{
|
|
67
|
+
"run_id": "R1", "mode": "repair", "assessment_only": !withPlan, "phase": "review",
|
|
68
|
+
"readiness_verdict": "NOT_READY", "execution_outcome": "AWAITING_APPROVAL", "revisions": revisions,
|
|
69
|
+
})
|
|
70
|
+
writeJSON(t, filepath.Join(gen, "coverage.json"), obj{"files": []any{
|
|
71
|
+
obj{"path": fakeHome + "/proj/a.go", "eligible": true, "lanes": []any{"backend"},
|
|
72
|
+
"ranges": []any{obj{"start": 1, "end": 9, "state": "verified"}}},
|
|
73
|
+
obj{"path": "vendor/x.js", "eligible": false, "exclusion": obj{"reason": hostile}},
|
|
74
|
+
}})
|
|
75
|
+
writeJSON(t, filepath.Join(gen, "findings.json"), obj{"findings": []any{
|
|
76
|
+
obj{"id": "F-1", "title": hostile, "kind": "defect", "severity": "high", "status": "confirmed",
|
|
77
|
+
"failing_scenario": "nil deref"},
|
|
78
|
+
obj{"id": "F-2", "title": "OPP-TITLE", "kind": "opportunity", "status": "confirmed"},
|
|
79
|
+
}})
|
|
80
|
+
writeJSON(t, filepath.Join(gen, "evidence.json"), obj{"items": []any{}})
|
|
81
|
+
writeJSON(t, filepath.Join(gen, "dispatch.json"), obj{"attempts": []any{
|
|
82
|
+
obj{"task_id": "T-1", "attempt_id": 1, "lane": "backend", "role": "implementer", "status": "admitted",
|
|
83
|
+
"changed_paths": []any{"x/a.go"}},
|
|
84
|
+
obj{"task_id": "T-9", "attempt_id": 1, "lane": badLane, "role": "reviewer", "status": "admitted"},
|
|
85
|
+
}})
|
|
86
|
+
writeJSON(t, filepath.Join(gen, "approval.json"), obj{"events": []any{
|
|
87
|
+
obj{"id": "A-1", "kind": "plan", "status": "recorded", "quote": "QUOTE-approve r1"},
|
|
88
|
+
}})
|
|
89
|
+
writeJSON(t, filepath.Join(gen, "cycles.json"), obj{"cycles": []any{}})
|
|
90
|
+
writeJSON(t, filepath.Join(gen, "scorecard.json"), obj{
|
|
91
|
+
"readiness_verdict": "NOT_READY",
|
|
92
|
+
"status_by_control": obj{"C-1": "FAIL"},
|
|
93
|
+
"thresholds": []any{obj{"scope": "global", "value": obj{"exact": "1/2", "decimal": "0.5"}}},
|
|
94
|
+
})
|
|
95
|
+
writeJSON(t, filepath.Join(gen, "scorecard-baseline.json"), obj{
|
|
96
|
+
"thresholds": []any{obj{"scope": "global", "value": obj{"exact": "1/4"}}},
|
|
97
|
+
})
|
|
98
|
+
return run, gen
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
func renderKind(t *testing.T, run, gen, kind string) (string, string) {
|
|
102
|
+
t.Helper()
|
|
103
|
+
var out, errb bytes.Buffer
|
|
104
|
+
if code := Run([]string{run, gen, kind}, &out, &errb); code != 0 {
|
|
105
|
+
t.Fatalf("exit %d: %s", code, errb.String())
|
|
106
|
+
}
|
|
107
|
+
want := filepath.Join(gen, "views", kind+".html")
|
|
108
|
+
if strings.TrimSpace(out.String()) != want {
|
|
109
|
+
t.Fatalf("printed %q, want %q", out.String(), want)
|
|
110
|
+
}
|
|
111
|
+
h, err := os.ReadFile(want)
|
|
112
|
+
if err != nil {
|
|
113
|
+
t.Fatal(err)
|
|
114
|
+
}
|
|
115
|
+
m, err := os.ReadFile(filepath.Join(gen, "views", kind+".md"))
|
|
116
|
+
if err != nil {
|
|
117
|
+
t.Fatal(err)
|
|
118
|
+
}
|
|
119
|
+
return string(h), string(m)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// sectionHTML returns the HTML of one <section id="...">.
|
|
123
|
+
func sectionHTML(t *testing.T, page, id string) string {
|
|
124
|
+
t.Helper()
|
|
125
|
+
i := strings.Index(page, `<section id="`+id+`">`)
|
|
126
|
+
if i < 0 {
|
|
127
|
+
t.Fatalf("section %q missing", id)
|
|
128
|
+
}
|
|
129
|
+
s := page[i:]
|
|
130
|
+
return s[:strings.Index(s, "</section>")]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// tags returns every markup tag; escaped text never contains a raw '<'.
|
|
134
|
+
func tags(page string) []string {
|
|
135
|
+
var out []string
|
|
136
|
+
for _, part := range strings.Split(page, "<")[1:] {
|
|
137
|
+
out = append(out, part[:strings.IndexByte(part, '>')])
|
|
138
|
+
}
|
|
139
|
+
return out
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
func TestHostileTextIsInert(t *testing.T) {
|
|
143
|
+
run, gen := fixture(t, true)
|
|
144
|
+
h, _ := renderKind(t, run, gen, "review")
|
|
145
|
+
for _, tag := range tags(h) {
|
|
146
|
+
low := strings.ToLower(tag)
|
|
147
|
+
if strings.HasPrefix(low, "script") || strings.HasPrefix(low, "img") || strings.Contains(low, "onerror") {
|
|
148
|
+
t.Fatalf("live markup injected: <%s>", tag)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for _, id := range []string{"findings", "coverage"} {
|
|
152
|
+
if !strings.Contains(sectionHTML(t, h, id), hostileEsc) {
|
|
153
|
+
t.Errorf("escaped hostile text missing in %s", id)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
func TestCSPPinsStylesheetAndNoRemoteURLs(t *testing.T) {
|
|
159
|
+
run, gen := fixture(t, true)
|
|
160
|
+
h, _ := renderKind(t, run, gen, "review")
|
|
161
|
+
style := h[strings.Index(h, "<style>")+len("<style>") : strings.Index(h, "</style>")]
|
|
162
|
+
sum := sha256.Sum256([]byte(style))
|
|
163
|
+
want := `content="default-src 'none'; style-src 'sha256-` + base64.StdEncoding.EncodeToString(sum[:]) +
|
|
164
|
+
`'; img-src data:; base-uri 'none'; form-action 'none'"`
|
|
165
|
+
if !strings.Contains(h, want) {
|
|
166
|
+
t.Fatal("CSP missing or hash does not match the emitted stylesheet")
|
|
167
|
+
}
|
|
168
|
+
if strings.Contains(h, "http://") || strings.Contains(h, "https://") || strings.Contains(h, "<script") {
|
|
169
|
+
t.Fatal("remote URL or script present")
|
|
170
|
+
}
|
|
171
|
+
if nt, nc := strings.Count(h, "<table>"), strings.Count(h, "<caption>"); nt == 0 || nt != nc {
|
|
172
|
+
t.Fatalf("tables %d captions %d", nt, nc)
|
|
173
|
+
}
|
|
174
|
+
if strings.Count(h, `<div class="scroll" role="region" tabindex="0"`) != strings.Count(h, "<table>") {
|
|
175
|
+
t.Fatal("table outside a focusable scroll region")
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
func TestMarkdownEscapesAndHomeRedacted(t *testing.T) {
|
|
180
|
+
run, gen := fixture(t, true)
|
|
181
|
+
h, md := renderKind(t, run, gen, "review")
|
|
182
|
+
if strings.Contains(md, "<script>") || !strings.Contains(md, "</script>\\|`rm -rf /`") {
|
|
183
|
+
t.Fatal("markdown cell not escaped")
|
|
184
|
+
}
|
|
185
|
+
for _, page := range []string{h, md} {
|
|
186
|
+
if strings.Contains(page, fakeHome) || !strings.Contains(page, "~/proj/a.go") {
|
|
187
|
+
t.Fatal("home path not redacted")
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
func TestRowIDsDecisionAndStamp(t *testing.T) {
|
|
193
|
+
run, gen := fixture(t, true)
|
|
194
|
+
h, md := renderKind(t, run, gen, "review")
|
|
195
|
+
if !strings.Contains(h, `<tr id="F-1">`) {
|
|
196
|
+
t.Fatal("row id F-1 missing")
|
|
197
|
+
}
|
|
198
|
+
if strings.Contains(h, `<tr id="nil deref"`) || strings.Contains(h, `<tr id="run_id"`) {
|
|
199
|
+
t.Fatal("row id on a non-ID first cell")
|
|
200
|
+
}
|
|
201
|
+
d, f := strings.Index(h, `<section id="decide">`), strings.Index(h, `<section id="findings">`)
|
|
202
|
+
if d < 0 || d > f {
|
|
203
|
+
t.Fatal("decision section missing or after findings")
|
|
204
|
+
}
|
|
205
|
+
if !strings.Contains(sectionHTML(t, h, "decide"), `<textarea id="ap" readonly>Approve overhaul R1 plan revision 1, digest `) {
|
|
206
|
+
t.Fatal("approval textarea missing")
|
|
207
|
+
}
|
|
208
|
+
if !strings.HasPrefix(md, "<!-- overhaul-stamp run=R1 generation=g0003 plan=r1:") ||
|
|
209
|
+
!strings.Contains(h, "<!-- overhaul-stamp run=R1 generation=g0003 plan=r1:") {
|
|
210
|
+
t.Fatal("stamp missing")
|
|
211
|
+
}
|
|
212
|
+
if !strings.Contains(h, `<p role="status"><strong>Outcome AWAITING_APPROVAL · verdict NOT_READY · phase review</strong></p>`) {
|
|
213
|
+
t.Fatal("status summary missing")
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
func TestHostileLaneCannotInject(t *testing.T) {
|
|
218
|
+
run, gen := fixture(t, true)
|
|
219
|
+
h, md := renderKind(t, run, gen, "review")
|
|
220
|
+
if strings.Contains(h, `<a href="#p">`) || strings.Contains(md, `<a href="#p">`) {
|
|
221
|
+
t.Fatal("lane name injected an anchor")
|
|
222
|
+
}
|
|
223
|
+
if !strings.Contains(h, `<section id="lane-x---a-href---p--y--a-">`) {
|
|
224
|
+
t.Fatal("lane anchor not sanitized")
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
func TestSectionContents(t *testing.T) {
|
|
229
|
+
run, gen := fixture(t, true)
|
|
230
|
+
h, md := renderKind(t, run, gen, "review")
|
|
231
|
+
for _, id := range []string{"findings", "leads", "rejected", "repairs"} {
|
|
232
|
+
if strings.Contains(sectionHTML(t, h, id), "OPP-TITLE") {
|
|
233
|
+
t.Fatalf("opportunity leaked into %s", id)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if !strings.Contains(sectionHTML(t, h, "opportunities"), "OPP-TITLE") {
|
|
237
|
+
t.Fatal("opportunity missing")
|
|
238
|
+
}
|
|
239
|
+
if !strings.Contains(md, "| F-1 / T-1 | high, not recorded | nil deref | x/a.go | go test ./x |") {
|
|
240
|
+
t.Fatal("repairs join missing")
|
|
241
|
+
}
|
|
242
|
+
card := sectionHTML(t, h, "scorecard")
|
|
243
|
+
if !strings.Contains(card, "<td>global</td><td><span class=\"muted\">not recorded</span></td><td>3/10</td><td>1/4</td><td>1/2</td><td>C-2</td><td>C-1</td>") {
|
|
244
|
+
t.Fatalf("transparent scorecard global row wrong: %s", card)
|
|
245
|
+
}
|
|
246
|
+
if !strings.Contains(sectionHTML(t, h, "approvals"), "QUOTE-approve r1") {
|
|
247
|
+
t.Fatal("approval quote missing")
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
func TestPlanMarkdown(t *testing.T) {
|
|
252
|
+
run, gen := fixture(t, true)
|
|
253
|
+
renderKind(t, run, gen, "review")
|
|
254
|
+
b, err := os.ReadFile(filepath.Join(gen, "views", "plan.md"))
|
|
255
|
+
if err != nil {
|
|
256
|
+
t.Fatal(err)
|
|
257
|
+
}
|
|
258
|
+
p := string(b)
|
|
259
|
+
if !strings.HasPrefix(p, "<!-- overhaul-stamp run=R1 generation=g0003 plan=r1:") ||
|
|
260
|
+
!strings.Contains(p, "| T-1 | backend | F-1 |") ||
|
|
261
|
+
!strings.Contains(p, "```text\nApprove overhaul R1 plan revision 1, digest ") {
|
|
262
|
+
t.Fatalf("plan.md incomplete:\n%s", p)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
func TestAssessmentOnlyAudit(t *testing.T) {
|
|
267
|
+
run, gen := fixture(t, false)
|
|
268
|
+
for _, kind := range []string{"review", "report"} {
|
|
269
|
+
h, md := renderKind(t, run, gen, kind)
|
|
270
|
+
ctl := sectionHTML(t, h, "controls")
|
|
271
|
+
if !strings.Contains(ctl, `<tr id="C-1">`) || !strings.Contains(ctl, `<tr id="C-2">`) {
|
|
272
|
+
t.Fatal("rubric controls not listed from run.json revisions")
|
|
273
|
+
}
|
|
274
|
+
if strings.Contains(h+md, "Approve overhaul") || strings.Contains(h, `id="decide"`) {
|
|
275
|
+
t.Fatal("assessment-only run shows an approval message")
|
|
276
|
+
}
|
|
277
|
+
if !strings.Contains(h, "plan=none") {
|
|
278
|
+
t.Fatal("stamp should record plan=none")
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if _, err := os.Stat(filepath.Join(gen, "views", "plan.md")); !os.IsNotExist(err) {
|
|
282
|
+
t.Fatal("plan.md written without a plan")
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
func TestUsage(t *testing.T) {
|
|
287
|
+
var out, errb bytes.Buffer
|
|
288
|
+
for _, args := range [][]string{{"run"}, {"run", "gen", "bogus"}} {
|
|
289
|
+
if Run(args, &out, &errb) != 2 {
|
|
290
|
+
t.Fatalf("%v: want exit 2", args)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|