devrites 5.14.0 → 5.14.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 +11 -0
- package/README.md +6 -3
- package/docs/adr/0033-rite-fast-own-agents.md +45 -0
- package/docs/adr/README.md +2 -1
- package/docs/architecture.md +6 -4
- package/docs/command-map.md +2 -1
- package/docs/flow.md +3 -2
- package/docs/orchestration.md +3 -0
- package/docs/quick-reference.md +2 -1
- package/docs/skills.md +13 -8
- package/docs/usage.md +2 -2
- package/engine/internal/install/apply.go +5 -3
- package/engine/internal/install/binary.go +3 -1
- package/engine/internal/install/install.go +0 -25
- package/engine/internal/lib/claims.go +15 -0
- package/engine/internal/lib/claims_test.go +28 -0
- package/engine/internal/lib/context_next_test.go +53 -0
- package/engine/internal/lib/dispatch.go +28 -7
- package/engine/internal/lib/manifest_union.go +14 -0
- package/engine/internal/overhaul/admit/admit_test.go +9 -0
- package/engine/internal/overhaul/admit/run.go +9 -0
- package/engine/internal/overhaul/bench/run.go +327 -38
- package/engine/internal/overhaul/bench/run_test.go +131 -0
- package/engine/internal/overhaul/bench/stats.go +154 -0
- package/engine/internal/overhaul/ovio/ovio.go +4 -0
- package/engine/internal/overhaul/records/check.go +3 -0
- package/engine/internal/overhaul/records/consistency.go +217 -0
- package/engine/internal/overhaul/records/records_test.go +81 -0
- package/engine/internal/overhaul/records/run.go +1 -1
- package/engine/internal/overhaul/render/run.go +2 -0
- package/engine/internal/overhaul/render/run_test.go +5 -1
- package/engine/internal/overhaul/score/run.go +58 -9
- package/engine/internal/overhaul/score/run_test.go +84 -8
- package/engine/internal/parallel/ops.go +5 -0
- package/engine/internal/state/write.go +8 -2
- package/engine/internal/version/version_test.go +27 -0
- package/engine/main.go +2 -2
- package/pack/.claude/agents/fast-builder.md +90 -0
- package/pack/.claude/agents/fast-checker.md +60 -0
- package/pack/.claude/agents/fast-critic.md +70 -0
- package/pack/.claude/agents/fast-planner.md +95 -0
- package/pack/.claude/agents/overhaul-backend-engineer.md +5 -4
- package/pack/.claude/agents/overhaul-boundary.md +2 -2
- package/pack/.claude/agents/overhaul-craft.md +1 -1
- package/pack/.claude/agents/overhaul-frontend-engineer.md +5 -3
- package/pack/.claude/agents/overhaul-implementer.md +9 -5
- package/pack/.claude/agents/overhaul-specialist.md +6 -3
- package/pack/.claude/agents/overhaul-verifier.md +3 -3
- package/pack/.claude/settings.json +1 -1
- package/pack/.claude/skills/devrites-lib/reference/intent-map.md +2 -0
- package/pack/.claude/skills/overhaul/SKILL.md +2 -1
- package/pack/.claude/skills/overhaul/references/audit-domains.md +139 -12
- package/pack/.claude/skills/overhaul/references/lifecycle.md +2 -0
- package/pack/.claude/skills/overhaul/references/orchestration.md +13 -5
- package/pack/.claude/skills/overhaul/references/records.md +12 -3
- package/pack/.claude/skills/overhaul/references/repair-and-measurement.md +108 -5
- package/pack/.claude/skills/overhaul/references/scope-and-safety.md +17 -2
- package/pack/.claude/skills/overhaul/references/scoring.md +36 -7
- package/pack/.claude/skills/overhaul/references/stack-profiles.md +52 -0
- package/pack/.claude/skills/rite/SKILL.md +4 -1
- package/pack/.claude/skills/rite-doctor/SKILL.md +2 -0
- package/pack/.claude/skills/rite-fast/SKILL.md +183 -0
- package/pack/.claude/skills/rite-fast/reference/artifacts.md +107 -0
- package/pack/.claude/skills/rite-fast/reference/check-and-triage.md +51 -0
- package/pack/.claude/skills/rite-quick/SKILL.md +2 -0
- package/pack/generated/claude/agents/fast-builder.md +90 -0
- package/pack/generated/claude/agents/fast-checker.md +60 -0
- package/pack/generated/claude/agents/fast-critic.md +70 -0
- package/pack/generated/claude/agents/fast-planner.md +95 -0
- package/pack/generated/claude/agents/overhaul-backend-engineer.md +5 -4
- package/pack/generated/claude/agents/overhaul-boundary.md +2 -2
- package/pack/generated/claude/agents/overhaul-craft.md +1 -1
- package/pack/generated/claude/agents/overhaul-frontend-engineer.md +5 -3
- package/pack/generated/claude/agents/overhaul-implementer.md +9 -5
- package/pack/generated/claude/agents/overhaul-specialist.md +6 -3
- package/pack/generated/claude/agents/overhaul-verifier.md +3 -3
- package/pack/generated/claude/settings.json +1 -1
- package/pack/generated/claude/skills/devrites-lib/reference/intent-map.md +2 -0
- package/pack/generated/claude/skills/overhaul/SKILL.md +2 -1
- package/pack/generated/claude/skills/overhaul/references/audit-domains.md +139 -12
- package/pack/generated/claude/skills/overhaul/references/lifecycle.md +2 -0
- package/pack/generated/claude/skills/overhaul/references/orchestration.md +13 -5
- package/pack/generated/claude/skills/overhaul/references/records.md +12 -3
- package/pack/generated/claude/skills/overhaul/references/repair-and-measurement.md +108 -5
- package/pack/generated/claude/skills/overhaul/references/scope-and-safety.md +17 -2
- package/pack/generated/claude/skills/overhaul/references/scoring.md +36 -7
- package/pack/generated/claude/skills/overhaul/references/stack-profiles.md +52 -0
- package/pack/generated/claude/skills/rite/SKILL.md +4 -1
- package/pack/generated/claude/skills/rite-doctor/SKILL.md +2 -0
- package/pack/generated/claude/skills/rite-fast/SKILL.md +183 -0
- package/pack/generated/claude/skills/rite-fast/reference/artifacts.md +107 -0
- package/pack/generated/claude/skills/rite-fast/reference/check-and-triage.md +51 -0
- package/pack/generated/claude/skills/rite-quick/SKILL.md +2 -0
- package/pack/generated/codex/AGENTS.md +1 -0
- package/pack/generated/codex/agents/fast-builder.toml +90 -0
- package/pack/generated/codex/agents/fast-checker.toml +60 -0
- package/pack/generated/codex/agents/fast-critic.toml +70 -0
- package/pack/generated/codex/agents/fast-planner.toml +95 -0
- package/pack/generated/codex/agents/overhaul-backend-engineer.toml +5 -4
- package/pack/generated/codex/agents/overhaul-boundary.toml +2 -2
- package/pack/generated/codex/agents/overhaul-craft.toml +1 -1
- package/pack/generated/codex/agents/overhaul-frontend-engineer.toml +5 -3
- package/pack/generated/codex/agents/overhaul-implementer.toml +9 -5
- package/pack/generated/codex/agents/overhaul-specialist.toml +6 -3
- package/pack/generated/codex/agents/overhaul-verifier.toml +3 -3
- package/pack/generated/codex/skills/devrites-lib/reference/intent-map.md +2 -0
- package/pack/generated/codex/skills/overhaul/SKILL.md +2 -1
- package/pack/generated/codex/skills/overhaul/references/audit-domains.md +139 -12
- package/pack/generated/codex/skills/overhaul/references/lifecycle.md +2 -0
- package/pack/generated/codex/skills/overhaul/references/orchestration.md +13 -5
- package/pack/generated/codex/skills/overhaul/references/records.md +12 -3
- package/pack/generated/codex/skills/overhaul/references/repair-and-measurement.md +108 -5
- package/pack/generated/codex/skills/overhaul/references/scope-and-safety.md +17 -2
- package/pack/generated/codex/skills/overhaul/references/scoring.md +36 -7
- package/pack/generated/codex/skills/overhaul/references/stack-profiles.md +52 -0
- package/pack/generated/codex/skills/rite/SKILL.md +4 -1
- package/pack/generated/codex/skills/rite-doctor/SKILL.md +2 -0
- package/pack/generated/codex/skills/rite-fast/SKILL.md +183 -0
- package/pack/generated/codex/skills/rite-fast/agents/openai.yaml +2 -0
- package/pack/generated/codex/skills/rite-fast/reference/artifacts.md +107 -0
- package/pack/generated/codex/skills/rite-fast/reference/check-and-triage.md +51 -0
- package/pack/generated/codex/skills/rite-quick/SKILL.md +2 -0
- package/pack/generated/devin/AGENTS.md +1 -0
- package/pack/generated/devin/agents/fast-builder.md +96 -0
- package/pack/generated/devin/agents/fast-checker.md +63 -0
- package/pack/generated/devin/agents/fast-critic.md +73 -0
- package/pack/generated/devin/agents/fast-planner.md +98 -0
- package/pack/generated/devin/agents/overhaul-backend-engineer.md +5 -4
- package/pack/generated/devin/agents/overhaul-boundary.md +2 -2
- package/pack/generated/devin/agents/overhaul-craft.md +1 -1
- package/pack/generated/devin/agents/overhaul-frontend-engineer.md +5 -3
- package/pack/generated/devin/agents/overhaul-implementer.md +9 -5
- package/pack/generated/devin/agents/overhaul-specialist.md +6 -3
- package/pack/generated/devin/agents/overhaul-verifier.md +3 -3
- package/pack/generated/devin/skills/devrites-lib/reference/intent-map.md +2 -0
- package/pack/generated/devin/skills/overhaul/SKILL.md +2 -1
- package/pack/generated/devin/skills/overhaul/references/audit-domains.md +139 -12
- package/pack/generated/devin/skills/overhaul/references/lifecycle.md +2 -0
- package/pack/generated/devin/skills/overhaul/references/orchestration.md +13 -5
- package/pack/generated/devin/skills/overhaul/references/records.md +12 -3
- package/pack/generated/devin/skills/overhaul/references/repair-and-measurement.md +108 -5
- package/pack/generated/devin/skills/overhaul/references/scope-and-safety.md +17 -2
- package/pack/generated/devin/skills/overhaul/references/scoring.md +36 -7
- package/pack/generated/devin/skills/overhaul/references/stack-profiles.md +52 -0
- package/pack/generated/devin/skills/rite/SKILL.md +4 -1
- package/pack/generated/devin/skills/rite-doctor/SKILL.md +2 -0
- package/pack/generated/devin/skills/rite-fast/SKILL.md +183 -0
- package/pack/generated/devin/skills/rite-fast/reference/artifacts.md +107 -0
- package/pack/generated/devin/skills/rite-fast/reference/check-and-triage.md +51 -0
- package/pack/generated/devin/skills/rite-quick/SKILL.md +2 -0
- package/pack/generated/omp/agents/fast-builder.md +89 -0
- package/pack/generated/omp/agents/fast-checker.md +59 -0
- package/pack/generated/omp/agents/fast-critic.md +69 -0
- package/pack/generated/omp/agents/fast-planner.md +94 -0
- package/pack/generated/omp/agents/overhaul-backend-engineer.md +5 -4
- package/pack/generated/omp/agents/overhaul-boundary.md +2 -2
- package/pack/generated/omp/agents/overhaul-craft.md +1 -1
- package/pack/generated/omp/agents/overhaul-frontend-engineer.md +5 -3
- package/pack/generated/omp/agents/overhaul-implementer.md +9 -5
- package/pack/generated/omp/agents/overhaul-specialist.md +6 -3
- package/pack/generated/omp/agents/overhaul-verifier.md +3 -3
- package/pack/generated/omp/commands/rite-fast.md +5 -0
- package/pack/generated/omp/skills/devrites-lib/reference/intent-map.md +2 -0
- package/pack/generated/omp/skills/overhaul/SKILL.md +2 -1
- package/pack/generated/omp/skills/overhaul/references/audit-domains.md +139 -12
- package/pack/generated/omp/skills/overhaul/references/lifecycle.md +2 -0
- package/pack/generated/omp/skills/overhaul/references/orchestration.md +13 -5
- package/pack/generated/omp/skills/overhaul/references/records.md +12 -3
- package/pack/generated/omp/skills/overhaul/references/repair-and-measurement.md +108 -5
- package/pack/generated/omp/skills/overhaul/references/scope-and-safety.md +17 -2
- package/pack/generated/omp/skills/overhaul/references/scoring.md +36 -7
- package/pack/generated/omp/skills/overhaul/references/stack-profiles.md +52 -0
- package/pack/generated/omp/skills/rite/SKILL.md +4 -1
- package/pack/generated/omp/skills/rite-doctor/SKILL.md +2 -0
- package/pack/generated/omp/skills/rite-fast/SKILL.md +183 -0
- package/pack/generated/omp/skills/rite-fast/reference/artifacts.md +107 -0
- package/pack/generated/omp/skills/rite-fast/reference/check-and-triage.md +51 -0
- package/pack/generated/omp/skills/rite-quick/SKILL.md +2 -0
- package/pack/generated/pi/AGENTS.md +1 -0
- package/pack/generated/pi/agents/fast-builder.md +90 -0
- package/pack/generated/pi/agents/fast-checker.md +60 -0
- package/pack/generated/pi/agents/fast-critic.md +70 -0
- package/pack/generated/pi/agents/fast-planner.md +95 -0
- package/pack/generated/pi/agents/overhaul-backend-engineer.md +5 -4
- package/pack/generated/pi/agents/overhaul-boundary.md +2 -2
- package/pack/generated/pi/agents/overhaul-craft.md +1 -1
- package/pack/generated/pi/agents/overhaul-frontend-engineer.md +5 -3
- package/pack/generated/pi/agents/overhaul-implementer.md +9 -5
- package/pack/generated/pi/agents/overhaul-specialist.md +6 -3
- package/pack/generated/pi/agents/overhaul-verifier.md +3 -3
- package/pack/generated/pi/prompts/rite-fast.md +5 -0
- package/pack/generated/pi/skills/devrites-lib/reference/intent-map.md +2 -0
- package/pack/generated/pi/skills/overhaul/SKILL.md +2 -1
- package/pack/generated/pi/skills/overhaul/references/audit-domains.md +139 -12
- package/pack/generated/pi/skills/overhaul/references/lifecycle.md +2 -0
- package/pack/generated/pi/skills/overhaul/references/orchestration.md +13 -5
- package/pack/generated/pi/skills/overhaul/references/records.md +12 -3
- package/pack/generated/pi/skills/overhaul/references/repair-and-measurement.md +108 -5
- package/pack/generated/pi/skills/overhaul/references/scope-and-safety.md +17 -2
- package/pack/generated/pi/skills/overhaul/references/scoring.md +36 -7
- package/pack/generated/pi/skills/overhaul/references/stack-profiles.md +52 -0
- package/pack/generated/pi/skills/rite/SKILL.md +4 -1
- package/pack/generated/pi/skills/rite-doctor/SKILL.md +2 -0
- package/pack/generated/pi/skills/rite-fast/SKILL.md +183 -0
- package/pack/generated/pi/skills/rite-fast/reference/artifacts.md +107 -0
- package/pack/generated/pi/skills/rite-fast/reference/check-and-triage.md +51 -0
- package/pack/generated/pi/skills/rite-quick/SKILL.md +2 -0
- package/package.json +1 -1
- package/scripts/codex-generate.sh +2 -1
- package/scripts/devin-generate.sh +4 -3
- package/scripts/omp-generate.sh +3 -3
- package/scripts/pi-generate.sh +4 -3
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
package bench
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"math"
|
|
5
|
+
"sort"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// uNull returns P(U = u) for u = 0..n*m under the Mann-Whitney null with arm
|
|
9
|
+
// sizes n and m, from the generating function prod_i (1-q^(m+i))/(1-q^i).
|
|
10
|
+
// Cancellation leaves the upper half inaccurate; only the lower tail is read.
|
|
11
|
+
func uNull(n, m int) []float64 {
|
|
12
|
+
c := make([]float64, n*m+1)
|
|
13
|
+
c[0] = 1
|
|
14
|
+
for i := 1; i <= n; i++ {
|
|
15
|
+
for u := len(c) - 1; u >= m+i; u-- {
|
|
16
|
+
c[u] -= c[u-m-i]
|
|
17
|
+
}
|
|
18
|
+
for u := i; u < len(c); u++ {
|
|
19
|
+
c[u] += c[u-i]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
total := 0.0
|
|
23
|
+
for _, v := range c {
|
|
24
|
+
total += v
|
|
25
|
+
}
|
|
26
|
+
for i := range c {
|
|
27
|
+
c[i] /= total
|
|
28
|
+
}
|
|
29
|
+
return c
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// exactLimit bounds the arm sizes for the exact null distribution, well inside
|
|
33
|
+
// float64 range and cheap to compute; beyond it the (slightly conservative)
|
|
34
|
+
// normal approximation takes over.
|
|
35
|
+
const exactLimit = 400
|
|
36
|
+
|
|
37
|
+
// maxPairs caps n*m for the pairwise differences, which are held in memory.
|
|
38
|
+
const maxPairs = 4_000_000
|
|
39
|
+
|
|
40
|
+
// tailCount returns the largest k with P(U <= k-1) <= alpha/2: the pairwise
|
|
41
|
+
// differences D_(1..k-1) and D_(nm-k+2..nm) fall outside a (1-alpha) interval,
|
|
42
|
+
// whose endpoints are D_(k) and D_(nm+1-k). Zero means no finite interval.
|
|
43
|
+
func tailCount(n, m int, alpha float64) int {
|
|
44
|
+
if n+m <= exactLimit {
|
|
45
|
+
p, cum, k := uNull(n, m), 0.0, 0
|
|
46
|
+
for u := range p {
|
|
47
|
+
if cum += p[u]; cum > alpha/2 {
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
k = u + 1
|
|
51
|
+
}
|
|
52
|
+
return k
|
|
53
|
+
}
|
|
54
|
+
mean := float64(n*m) / 2
|
|
55
|
+
sd := math.Sqrt(float64(n*m*(n+m+1)) / 12)
|
|
56
|
+
return max(0, int(math.Floor(mean-normalQuantile(1-alpha/2)*sd-0.5))+1)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// hlRatio is the Hodges-Lehmann ratio estimate exp(median(ln x_i - ln y_j))
|
|
60
|
+
// with its distribution-free (1-alpha) bounds. ok is false when a sample is
|
|
61
|
+
// not positive or the arms are too small for a finite interval.
|
|
62
|
+
func hlRatio(x, y []float64, alpha float64) (est, lo, hi float64, ok bool) {
|
|
63
|
+
if len(x)*len(y) > maxPairs {
|
|
64
|
+
return 0, 0, 0, false
|
|
65
|
+
}
|
|
66
|
+
z := make([]float64, 0, len(x)*len(y))
|
|
67
|
+
for _, a := range x {
|
|
68
|
+
for _, b := range y {
|
|
69
|
+
if a <= 0 || b <= 0 {
|
|
70
|
+
return 0, 0, 0, false
|
|
71
|
+
}
|
|
72
|
+
z = append(z, math.Log(a)-math.Log(b))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
k := tailCount(len(x), len(y), alpha)
|
|
76
|
+
if k == 0 {
|
|
77
|
+
return 0, 0, 0, false
|
|
78
|
+
}
|
|
79
|
+
sort.Float64s(z)
|
|
80
|
+
return math.Exp(median(z)), math.Exp(z[k-1]), math.Exp(z[len(z)-k]), true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// quantileBounds returns one-sided (1-alpha) distribution-free bounds for the
|
|
84
|
+
// p-quantile of xs from its order statistics; a missing bound is ±Inf. With
|
|
85
|
+
// F(j) = P(Bin(n, p) <= j), the upper bound is x_(k) for the smallest k with
|
|
86
|
+
// F(k-1) >= 1-alpha and the lower bound x_(j) for the largest j with
|
|
87
|
+
// F(j-1) <= alpha.
|
|
88
|
+
func quantileBounds(xs []float64, p, alpha float64) (lcb, ucb float64) {
|
|
89
|
+
s := append([]float64(nil), xs...)
|
|
90
|
+
sort.Float64s(s)
|
|
91
|
+
n := len(s)
|
|
92
|
+
lcb, ucb = math.Inf(-1), math.Inf(1)
|
|
93
|
+
lg := func(k int) float64 { v, _ := math.Lgamma(float64(k) + 1); return v }
|
|
94
|
+
cdf := 0.0
|
|
95
|
+
for i := 0; i < n; i++ { // cdf = F(i) after this step
|
|
96
|
+
cdf += math.Exp(lg(n) - lg(i) - lg(n-i) + float64(i)*math.Log(p) + float64(n-i)*math.Log1p(-p))
|
|
97
|
+
if cdf <= alpha {
|
|
98
|
+
lcb = s[i] // j = i+1
|
|
99
|
+
}
|
|
100
|
+
if cdf >= 1-alpha {
|
|
101
|
+
ucb = s[i] // k = i+1
|
|
102
|
+
break
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return lcb, ucb
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// quantile7 is the linearly interpolated sample quantile (Hyndman-Fan type 7).
|
|
109
|
+
func quantile7(xs []float64, p float64) float64 {
|
|
110
|
+
s := append([]float64(nil), xs...)
|
|
111
|
+
sort.Float64s(s)
|
|
112
|
+
h := p * float64(len(s)-1)
|
|
113
|
+
i := int(math.Floor(h))
|
|
114
|
+
if i+1 >= len(s) {
|
|
115
|
+
return s[len(s)-1]
|
|
116
|
+
}
|
|
117
|
+
return s[i] + (h-float64(i))*(s[i+1]-s[i])
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// wilson returns the Wilson score interval for e events in n trials.
|
|
121
|
+
func wilson(p float64, n, z float64) (lo, hi float64) {
|
|
122
|
+
d := 1 + z*z/n
|
|
123
|
+
c := (p + z*z/(2*n)) / d
|
|
124
|
+
h := z * math.Sqrt(p*(1-p)/n+z*z/(4*n*n)) / d
|
|
125
|
+
return max(0, c-h), min(1, c+h)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// newcombe returns the hybrid score interval (Newcombe 1998, method 10) for
|
|
129
|
+
// p1 - p2 from proportions over n1 and n2 trials.
|
|
130
|
+
func newcombe(p1, n1, p2, n2, z float64) (lo, hi float64) {
|
|
131
|
+
l1, u1 := wilson(p1, n1, z)
|
|
132
|
+
l2, u2 := wilson(p2, n2, z)
|
|
133
|
+
d := p1 - p2
|
|
134
|
+
return d - math.Sqrt((p1-l1)*(p1-l1)+(u2-p2)*(u2-p2)), d + math.Sqrt((u1-p1)*(u1-p1)+(p2-l2)*(p2-l2))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// normalQuantile is the standard normal inverse CDF (Acklam's rational
|
|
138
|
+
// approximation, relative error below 1.2e-9).
|
|
139
|
+
func normalQuantile(p float64) float64 {
|
|
140
|
+
a := []float64{-3.969683028665376e+01, 2.209460984245205e+02, -2.759285104469687e+02, 1.383577518672690e+02, -3.066479806614716e+01, 2.506628277459239e+00}
|
|
141
|
+
b := []float64{-5.447609879822406e+01, 1.615858368580409e+02, -1.556989798598866e+02, 6.680131188771972e+01, -1.328068155288572e+01}
|
|
142
|
+
c := []float64{-7.784894002430293e-03, -3.223964580411365e-01, -2.400758277161838e+00, -2.549732539343734e+00, 4.374664141464968e+00, 2.938163982698783e+00}
|
|
143
|
+
d := []float64{7.784695709041462e-03, 3.224671290700398e-01, 2.445134137142996e+00, 3.754408661907416e+00}
|
|
144
|
+
switch {
|
|
145
|
+
case p < 0.02425:
|
|
146
|
+
q := math.Sqrt(-2 * math.Log(p))
|
|
147
|
+
return (((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q + c[5]) / ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q + 1)
|
|
148
|
+
case p > 1-0.02425:
|
|
149
|
+
return -normalQuantile(1 - p)
|
|
150
|
+
}
|
|
151
|
+
q := p - 0.5
|
|
152
|
+
r := q * q
|
|
153
|
+
return (((((a[0]*r+a[1])*r+a[2])*r+a[3])*r+a[4])*r + a[5]) * q / (((((b[0]*r+b[1])*r+b[2])*r+b[3])*r+b[4])*r + 1)
|
|
154
|
+
}
|
|
@@ -156,3 +156,7 @@ func Truthy(v any) bool {
|
|
|
156
156
|
}
|
|
157
157
|
return true
|
|
158
158
|
}
|
|
159
|
+
|
|
160
|
+
// Domains is the fixed set of score domains in their documented order. Findings,
|
|
161
|
+
// receipts, the applicability matrix and the rubric all use these keys.
|
|
162
|
+
var Domains = []string{"correctness", "security", "reliability", "performance", "tests", "architecture", "ux", "operations"}
|
|
@@ -580,6 +580,9 @@ func Check(run, genDir, prevDir string) ([]string, error) {
|
|
|
580
580
|
add("run.json: readiness_verdict differs from scorecard.json")
|
|
581
581
|
}
|
|
582
582
|
}
|
|
583
|
+
if err := consistency(run, genDir, R, add); err != nil {
|
|
584
|
+
return nil, err
|
|
585
|
+
}
|
|
583
586
|
ap := ovio.List(R["approval.json"]["events"])
|
|
584
587
|
uniq(ap, idKey, "approval", add)
|
|
585
588
|
stopAt := -1
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
package records
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"slices"
|
|
6
|
+
|
|
7
|
+
"github.com/devrites/devrites/internal/overhaul/ovio"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
var (
|
|
11
|
+
jsonZero = any(json.Number("0"))
|
|
12
|
+
domains = set(ovio.Domains...)
|
|
13
|
+
cellStates = set("applicable", "not-applicable", "blocked")
|
|
14
|
+
rangeStates = set("inventoried", "tool-scanned", "semantically-reviewed", "cross-boundary-reviewed", "verified", "excluded", "blocked")
|
|
15
|
+
reviewedRange = set("semantically-reviewed", "cross-boundary-reviewed", "verified", "excluded")
|
|
16
|
+
openFinding = set("confirmed", "approved-for-fix", "in-progress", "fixed-unverified", "deferred", "accepted-risk")
|
|
17
|
+
serious = set("critical", "high")
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
// consistency checks that findings, the coverage ledger, the applicability
|
|
21
|
+
// matrix, the control results and the recorded gates agree with each other, so
|
|
22
|
+
// a gate or control cannot claim PASS against the run's own records.
|
|
23
|
+
func consistency(run, genDir string, R map[string]map[string]any, add func(string, ...any)) error {
|
|
24
|
+
controls, err := rubricControls(run, R["run.json"])
|
|
25
|
+
if err != nil {
|
|
26
|
+
return err
|
|
27
|
+
}
|
|
28
|
+
fs := ovio.List(R["findings.json"]["findings"])
|
|
29
|
+
for _, it := range fs {
|
|
30
|
+
f := ovio.Obj(it)
|
|
31
|
+
fid := PyStr(f["id"])
|
|
32
|
+
if !domains[str(f, "domain")] {
|
|
33
|
+
add("finding %s: domain must be one of %s", fid, Repr(ovio.Domains))
|
|
34
|
+
}
|
|
35
|
+
if controls == nil {
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
cs := ovio.List(f["controls"])
|
|
39
|
+
if len(cs) == 0 && severityOK[str(f, "status")] {
|
|
40
|
+
add("finding %s: a confirmed finding names the rubric controls it fails", fid)
|
|
41
|
+
}
|
|
42
|
+
for _, c := range cs {
|
|
43
|
+
if !controls.has(c) {
|
|
44
|
+
add("finding %s: unknown control %s", fid, PyStr(c))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
cov := R["coverage.json"]
|
|
50
|
+
cells := ovio.List(cov["applicability"])
|
|
51
|
+
seen := vset{}
|
|
52
|
+
for _, it := range cells {
|
|
53
|
+
c := ovio.Obj(it)
|
|
54
|
+
key := Repr([]any{c["domain"], c["component"], c["lane"]})
|
|
55
|
+
if seen.has(key) {
|
|
56
|
+
add("applicability %s: duplicate cell", key)
|
|
57
|
+
}
|
|
58
|
+
seen.add(key)
|
|
59
|
+
if !domains[str(c, "domain")] || str(c, "component") == "" || str(c, "lane") == "" {
|
|
60
|
+
add("applicability %s: needs a known domain, a component and a lane", key)
|
|
61
|
+
}
|
|
62
|
+
st := str(c, "state")
|
|
63
|
+
if !cellStates[st] {
|
|
64
|
+
add("applicability %s: state must be applicable, not-applicable or blocked", key)
|
|
65
|
+
}
|
|
66
|
+
if st != "applicable" && !ovio.Truthy(c["reason"]) {
|
|
67
|
+
add("applicability %s: %s needs a reason", key, st)
|
|
68
|
+
}
|
|
69
|
+
for _, r := range ovio.List(c["receipts"]) {
|
|
70
|
+
if p := ovio.Str(r); p == "" || !inside(run, p) || !isFile(join(run, p)) {
|
|
71
|
+
add("applicability %s: receipt %s missing or outside the run dir", key, PyStr(r))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for _, it := range ovio.List(cov["files"]) {
|
|
76
|
+
for _, r := range ovio.List(ovio.Obj(it)["ranges"]) {
|
|
77
|
+
if st := str(ovio.Obj(r), "state"); !rangeStates[st] {
|
|
78
|
+
add("coverage %s: range state %s is not a coverage state", PyStr(ovio.Obj(it)["path"]), Repr(st))
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
results := ""
|
|
84
|
+
for _, name := range []string{"results-candidate.json", "results-baseline.json"} {
|
|
85
|
+
if isFile(join(genDir, name)) {
|
|
86
|
+
results = name
|
|
87
|
+
break
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if results != "" {
|
|
91
|
+
doc, err := ovio.LoadObject(join(genDir, results))
|
|
92
|
+
if err != nil {
|
|
93
|
+
return err
|
|
94
|
+
}
|
|
95
|
+
res := ovio.Obj(doc["results"])
|
|
96
|
+
for _, it := range fs {
|
|
97
|
+
f := ovio.Obj(it)
|
|
98
|
+
if !openFinding[str(f, "status")] {
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
for _, c := range ovio.List(f["controls"]) {
|
|
102
|
+
if str(ovio.Obj(res[ovio.Str(c)]), "status") == "PASS" {
|
|
103
|
+
add("finding %s: control %s is PASS in %s while the finding is %s",
|
|
104
|
+
PyStr(f["id"]), PyStr(c), results, str(f, "status"))
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
scp := join(genDir, "scorecard.json")
|
|
111
|
+
if !isFile(scp) {
|
|
112
|
+
return nil
|
|
113
|
+
}
|
|
114
|
+
sc, err := ovio.LoadObject(scp)
|
|
115
|
+
if err != nil {
|
|
116
|
+
return err
|
|
117
|
+
}
|
|
118
|
+
gates := ovio.Obj(sc["gates"])
|
|
119
|
+
claims := func(g string) bool { return str(gates, g) == "PASS" }
|
|
120
|
+
for _, it := range fs {
|
|
121
|
+
f := ovio.Obj(it)
|
|
122
|
+
fid, status := PyStr(f["id"]), str(f, "status")
|
|
123
|
+
if claims("G-NO-CRITICAL-HIGH") && openFinding[status] && serious[str(f, "severity")] {
|
|
124
|
+
add("scorecard: G-NO-CRITICAL-HIGH is PASS but finding %s is %s %s", fid, status, str(f, "severity"))
|
|
125
|
+
}
|
|
126
|
+
if claims("G-NO-OPEN-SERIOUS-LEAD") && (status == "candidate" || status == "needs-validation") &&
|
|
127
|
+
serious[str(f, "potential_impact")] {
|
|
128
|
+
add("scorecard: G-NO-OPEN-SERIOUS-LEAD is PASS but lead %s is open with potential impact %s", fid, str(f, "potential_impact"))
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if !claims("G-COVERAGE") {
|
|
132
|
+
return nil
|
|
133
|
+
}
|
|
134
|
+
admitted := map[string]bool{}
|
|
135
|
+
for _, it := range ovio.List(R["dispatch.json"]["attempts"]) {
|
|
136
|
+
if x := ovio.Obj(it); str(x, "status") == "admitted" {
|
|
137
|
+
admitted[str(x, "receipt")] = true
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if len(cells) == 0 {
|
|
141
|
+
add("scorecard: G-COVERAGE is PASS without an applicability matrix in coverage.json")
|
|
142
|
+
}
|
|
143
|
+
for _, it := range cells {
|
|
144
|
+
c := ovio.Obj(it)
|
|
145
|
+
key := Repr([]any{c["domain"], c["component"], c["lane"]})
|
|
146
|
+
switch str(c, "state") {
|
|
147
|
+
case "blocked":
|
|
148
|
+
add("scorecard: G-COVERAGE is PASS but applicability %s is blocked", key)
|
|
149
|
+
case "applicable":
|
|
150
|
+
if !checkedBy(run, str(c, "domain"), ovio.List(c["receipts"]), admitted) {
|
|
151
|
+
add("scorecard: G-COVERAGE is PASS but no admitted, non-gap receipt of applicability %s lists %s in domains_checked", key, str(c, "domain"))
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
for _, it := range ovio.List(cov["files"]) {
|
|
156
|
+
f := ovio.Obj(it)
|
|
157
|
+
if !ovio.Truthy(f["eligible"]) {
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
rs := ovio.List(f["ranges"])
|
|
161
|
+
done := len(rs) > 0 || eq(f["lines"], jsonZero)
|
|
162
|
+
for _, r := range rs {
|
|
163
|
+
done = done && reviewedRange[str(ovio.Obj(r), "state")]
|
|
164
|
+
}
|
|
165
|
+
if !done {
|
|
166
|
+
add("scorecard: G-COVERAGE is PASS but eligible file %s has unreviewed ranges", PyStr(f["path"]))
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return nil
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// checkedBy reports whether any admitted, non-gap receipt records domain in
|
|
173
|
+
// domains_checked.
|
|
174
|
+
func checkedBy(run, domain string, receipts []any, admitted map[string]bool) bool {
|
|
175
|
+
for _, r := range receipts {
|
|
176
|
+
p := ovio.Str(r)
|
|
177
|
+
if !admitted[p] || !inside(run, p) || !isFile(join(run, p)) {
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
rc, err := ovio.LoadObject(join(run, p))
|
|
181
|
+
if err != nil || str(rc, "outcome") == "gap" {
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
184
|
+
if slices.ContainsFunc(ovio.List(rc["domains_checked"]), func(d any) bool { return d == any(domain) }) {
|
|
185
|
+
return true
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// rubricControls returns the control IDs of the run's rubric (revisions.rubric,
|
|
192
|
+
// else the plan's pinned rubric), or nil when no rubric is frozen yet.
|
|
193
|
+
func rubricControls(run string, rj map[string]any) (vset, error) {
|
|
194
|
+
revs := ovio.Obj(rj["revisions"])
|
|
195
|
+
rf := str(ovio.Obj(revs["rubric"]), "file")
|
|
196
|
+
if rf == "" {
|
|
197
|
+
if pf := str(ovio.Obj(revs["plan"]), "file"); pf != "" && inside(run, pf) && isFile(join(run, pf)) {
|
|
198
|
+
plan, err := ovio.LoadObject(join(run, pf))
|
|
199
|
+
if err != nil {
|
|
200
|
+
return nil, err
|
|
201
|
+
}
|
|
202
|
+
rf = str(ovio.Obj(plan["rubric"]), "file")
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if rf == "" || !inside(run, rf) || !isFile(join(run, rf)) {
|
|
206
|
+
return nil, nil
|
|
207
|
+
}
|
|
208
|
+
doc, err := ovio.LoadObject(join(run, rf))
|
|
209
|
+
if err != nil {
|
|
210
|
+
return nil, err
|
|
211
|
+
}
|
|
212
|
+
ids := vset{}
|
|
213
|
+
for _, c := range ovio.List(doc["controls"]) {
|
|
214
|
+
ids.add(ovio.Obj(c)["id"])
|
|
215
|
+
}
|
|
216
|
+
return ids, nil
|
|
217
|
+
}
|
|
@@ -80,6 +80,7 @@ func (f *fixture) records() map[string]J {
|
|
|
80
80
|
"coverage.json": {"schema": "overhaul.coverage/1", "files": L{J{"path": "src/a.py", "eligible": true}}},
|
|
81
81
|
"findings.json": {"schema": "overhaul.findings/1", "findings": L{J{"id": "F-1", "fingerprint": "fp-1",
|
|
82
82
|
"kind": "defect", "scope_origin": "pre-existing", "status": "confirmed", "severity": "high",
|
|
83
|
+
"domain": "correctness", "controls": L{"C-1"},
|
|
83
84
|
"evidence": L{"E-1"}, "locations": L{J{"path": "src/a.py", "start": 1, "end": 1},
|
|
84
85
|
J{"path": "mobile-app:src/api.ts", "external": true}},
|
|
85
86
|
"history": L{J{"status": "candidate"}, J{"status": "confirmed"}}}}},
|
|
@@ -186,6 +187,25 @@ func TestPublishThenValidate(t *testing.T) {
|
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
|
|
190
|
+
func cell(r map[string]J) J { return r["coverage.json"]["applicability"].(L)[0].(J) }
|
|
191
|
+
|
|
192
|
+
// scorecard records a scorecard with the given gates and points run.json at its rubric.
|
|
193
|
+
func scorecard(r map[string]J, gates J) {
|
|
194
|
+
r["run.json"]["revisions"].(J)["rubric"] = J{"rev": 1, "file": "revisions/rubric-r1.json"}
|
|
195
|
+
r["scorecard.json"] = J{"schema": "overhaul.scorecard/1", "gates": gates}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// reviewed records one applicability cell in state st, backed by a receipt that
|
|
199
|
+
// checked its domain, and a fully reviewed range for the fixture's file.
|
|
200
|
+
func reviewed(f *fixture, r map[string]J, st string) {
|
|
201
|
+
write(f.t, filepath.Join(f.run, "receipts/R-dom.json"), J{"outcome": "no-findings", "domains_checked": L{"correctness"}})
|
|
202
|
+
addAttempt(r, J{"task_id": "W-1", "attempt_id": "A1", "role": "reviewer", "status": "admitted", "receipt": "receipts/R-dom.json"})
|
|
203
|
+
r["coverage.json"]["applicability"] = L{J{"domain": "correctness", "component": "api", "lane": "backend",
|
|
204
|
+
"state": st, "reason": "no GUI", "receipts": L{"receipts/R-dom.json"}}}
|
|
205
|
+
r["coverage.json"]["files"] = L{J{"path": "src/a.py", "eligible": true,
|
|
206
|
+
"ranges": L{J{"start": 1, "end": 1, "state": "semantically-reviewed"}}}}
|
|
207
|
+
}
|
|
208
|
+
|
|
189
209
|
func TestSingleGenerationRules(t *testing.T) {
|
|
190
210
|
verifiedHistory := L{J{"status": "candidate"}, J{"status": "confirmed"}, J{"status": "approved-for-fix"},
|
|
191
211
|
J{"status": "in-progress"}, J{"status": "fixed-unverified"}, J{"status": "verified"}}
|
|
@@ -370,6 +390,67 @@ func TestSingleGenerationRules(t *testing.T) {
|
|
|
370
390
|
{"ineligible coverage without reason", func(f *fixture, r map[string]J) {
|
|
371
391
|
r["coverage.json"]["files"] = L{J{"path": "src/a.py", "eligible": false}}
|
|
372
392
|
}, "coverage src/a.py: ineligible without an exclusion reason"},
|
|
393
|
+
{"finding without domain", func(f *fixture, r map[string]J) { delete(finding(r), "domain") }, "finding F-1: domain must be one of"},
|
|
394
|
+
{"confirmed finding without controls", func(f *fixture, r map[string]J) { delete(finding(r), "controls") },
|
|
395
|
+
"finding F-1: a confirmed finding names the rubric controls it fails"},
|
|
396
|
+
{"finding with unknown control", func(f *fixture, r map[string]J) { finding(r)["controls"] = L{"C-9"} }, "finding F-1: unknown control C-9"},
|
|
397
|
+
{"open finding with passing control", func(f *fixture, r map[string]J) {
|
|
398
|
+
r["results-candidate.json"] = J{"schema": "overhaul.results/1", "results": J{"C-1": J{"status": "PASS"}}}
|
|
399
|
+
}, "finding F-1: control C-1 is PASS in results-candidate.json while the finding is confirmed"},
|
|
400
|
+
{"critical-high gate against open finding", func(f *fixture, r map[string]J) {
|
|
401
|
+
scorecard(r, J{"G-NO-CRITICAL-HIGH": "PASS"})
|
|
402
|
+
}, "G-NO-CRITICAL-HIGH is PASS but finding F-1 is confirmed high"},
|
|
403
|
+
{"serious-lead gate against open lead", func(f *fixture, r map[string]J) {
|
|
404
|
+
r["findings.json"]["findings"] = append(r["findings.json"]["findings"].(L), J{"id": "F-2", "fingerprint": "fp-2",
|
|
405
|
+
"kind": "defect", "scope_origin": "pre-existing", "status": "candidate", "domain": "security",
|
|
406
|
+
"potential_impact": "critical", "history": L{J{"status": "candidate"}}})
|
|
407
|
+
scorecard(r, J{"G-NO-OPEN-SERIOUS-LEAD": "PASS"})
|
|
408
|
+
}, "G-NO-OPEN-SERIOUS-LEAD is PASS but lead F-2 is open with potential impact critical"},
|
|
409
|
+
{"coverage gate without matrix", func(f *fixture, r map[string]J) { scorecard(r, J{"G-COVERAGE": "PASS"}) },
|
|
410
|
+
"G-COVERAGE is PASS without an applicability matrix"},
|
|
411
|
+
{"coverage gate with blocked cell", func(f *fixture, r map[string]J) {
|
|
412
|
+
reviewed(f, r, "blocked")
|
|
413
|
+
scorecard(r, J{"G-COVERAGE": "PASS"})
|
|
414
|
+
}, "applicability ['correctness', 'api', 'backend'] is blocked"},
|
|
415
|
+
{"coverage gate with unchecked domain", func(f *fixture, r map[string]J) {
|
|
416
|
+
reviewed(f, r, "applicable")
|
|
417
|
+
cell(r)["receipts"] = L{"receipts/R-1.json"}
|
|
418
|
+
scorecard(r, J{"G-COVERAGE": "PASS"})
|
|
419
|
+
}, "no admitted, non-gap receipt of applicability ['correctness', 'api', 'backend'] lists correctness in domains_checked"},
|
|
420
|
+
{"coverage gate with unreviewed range", func(f *fixture, r map[string]J) {
|
|
421
|
+
reviewed(f, r, "applicable")
|
|
422
|
+
r["coverage.json"]["files"] = L{J{"path": "src/a.py", "eligible": true, "ranges": L{J{"start": 1, "end": 1, "state": "tool-scanned"}}}}
|
|
423
|
+
scorecard(r, J{"G-COVERAGE": "PASS"})
|
|
424
|
+
}, "eligible file src/a.py has unreviewed ranges"},
|
|
425
|
+
{"coverage gate with rejected receipt", func(f *fixture, r map[string]J) {
|
|
426
|
+
reviewed(f, r, "applicable")
|
|
427
|
+
attempts(r)[len(attempts(r))-1].(J)["status"] = "rejected"
|
|
428
|
+
scorecard(r, J{"G-COVERAGE": "PASS"})
|
|
429
|
+
}, "no admitted, non-gap receipt of applicability"},
|
|
430
|
+
{"coverage gate with gap receipt", func(f *fixture, r map[string]J) {
|
|
431
|
+
reviewed(f, r, "applicable")
|
|
432
|
+
write(f.t, filepath.Join(f.run, "receipts/R-dom.json"), J{"outcome": "gap", "domains_checked": L{"correctness"}})
|
|
433
|
+
scorecard(r, J{"G-COVERAGE": "PASS"})
|
|
434
|
+
}, "no admitted, non-gap receipt of applicability"},
|
|
435
|
+
{"coverage gate counts empty files", func(f *fixture, r map[string]J) {
|
|
436
|
+
reviewed(f, r, "applicable")
|
|
437
|
+
r["coverage.json"]["files"] = L{J{"path": "src/a.py", "eligible": true, "lines": 0}}
|
|
438
|
+
scorecard(r, J{"G-COVERAGE": "PASS"})
|
|
439
|
+
}, ""},
|
|
440
|
+
{"coverage gate backed by records", func(f *fixture, r map[string]J) {
|
|
441
|
+
reviewed(f, r, "applicable")
|
|
442
|
+
scorecard(r, J{"G-COVERAGE": "PASS"})
|
|
443
|
+
}, ""},
|
|
444
|
+
{"unknown applicability state", func(f *fixture, r map[string]J) {
|
|
445
|
+
reviewed(f, r, "maybe")
|
|
446
|
+
}, "state must be applicable, not-applicable or blocked"},
|
|
447
|
+
{"not-applicable cell without reason", func(f *fixture, r map[string]J) {
|
|
448
|
+
reviewed(f, r, "not-applicable")
|
|
449
|
+
delete(cell(r), "reason")
|
|
450
|
+
}, "not-applicable needs a reason"},
|
|
451
|
+
{"unknown range state", func(f *fixture, r map[string]J) {
|
|
452
|
+
r["coverage.json"]["files"] = L{J{"path": "src/a.py", "eligible": true, "ranges": L{J{"start": 1, "end": 1, "state": "done"}}}}
|
|
453
|
+
}, "range state 'done' is not a coverage state"},
|
|
373
454
|
}
|
|
374
455
|
for _, c := range cases {
|
|
375
456
|
t.Run(c.name, func(t *testing.T) {
|
|
@@ -339,7 +339,7 @@ func publish(run string, stdout, stderr io.Writer) (int, error) {
|
|
|
339
339
|
}
|
|
340
340
|
manifest := map[string]any{"schema": "overhaul.manifest/1", "generation": gen, "previous": previous,
|
|
341
341
|
"files": files, "revisions": revs}
|
|
342
|
-
if err := ovio.WriteJSON(join(tmp, "manifest.json"), manifest,
|
|
342
|
+
if err := ovio.WriteJSON(join(tmp, "manifest.json"), manifest, 0o644); err != nil {
|
|
343
343
|
return 0, err
|
|
344
344
|
}
|
|
345
345
|
if err := os.Rename(tmp, join(run, gen)); err != nil {
|
|
@@ -731,6 +731,8 @@ func build(run, gen string) (*built, error) {
|
|
|
731
731
|
}
|
|
732
732
|
S = append(S,
|
|
733
733
|
section{"coverage", "Coverage ledger", []string{"path", "eligible", "lanes", "ranges (state)", "exclusion"}, covRows},
|
|
734
|
+
section{"applicability", "Applicability matrix (a cell without a receipt is not checked)", []string{"domain", "component", "lane", "state", "reason", "receipts"},
|
|
735
|
+
rowsOf(objs(R["coverage"]["applicability"]), "domain", "component", "lane", "state", "reason", "receipts")},
|
|
734
736
|
section{"profiles", "Stack profiles", []string{"id", "revision", "status", "file", "sha256"},
|
|
735
737
|
rowsOf(objs(R["stack-profiles"]["profiles"]), "id", "rev", "status", "file", "sha256")},
|
|
736
738
|
section{"contracts", "Contracts and boundaries", []string{"id", "producer", "consumers", "version", "evidence", "open questions"},
|
|
@@ -73,7 +73,8 @@ func fixture(t *testing.T, withPlan bool) (run, gen string) {
|
|
|
73
73
|
obj{"path": fakeHome + "/proj/a.go", "eligible": true, "lanes": []any{"backend"},
|
|
74
74
|
"ranges": []any{obj{"start": 1, "end": 9, "state": "verified"}}},
|
|
75
75
|
obj{"path": "vendor/x.js", "eligible": false, "exclusion": obj{"reason": hostile}},
|
|
76
|
-
}
|
|
76
|
+
}, "applicability": []any{obj{"domain": "operations", "component": "api", "lane": "backend", "state": "blocked",
|
|
77
|
+
"reason": "no container runtime"}}})
|
|
77
78
|
writeJSON(t, filepath.Join(gen, "findings.json"), obj{"findings": []any{
|
|
78
79
|
obj{"id": "F-1", "title": hostile, "kind": "defect", "severity": "high", "status": "confirmed",
|
|
79
80
|
"failing_scenario": "nil deref"},
|
|
@@ -259,6 +260,9 @@ func TestSectionContents(t *testing.T) {
|
|
|
259
260
|
if !strings.Contains(sectionHTML(t, h, "approvals"), "QUOTE-approve r1") {
|
|
260
261
|
t.Fatal("approval quote missing")
|
|
261
262
|
}
|
|
263
|
+
if !strings.Contains(sectionHTML(t, h, "applicability"), "no container runtime") {
|
|
264
|
+
t.Fatal("blocked applicability cell missing from the report")
|
|
265
|
+
}
|
|
262
266
|
}
|
|
263
267
|
|
|
264
268
|
func TestPlanMarkdown(t *testing.T) {
|
|
@@ -31,11 +31,16 @@ var (
|
|
|
31
31
|
repairGates = []string{"G-APPROVAL", "G-TASKS-COMPLETE", "G-ORACLES", "G-NO-REGRESSION", "G-PERF-TARGETS",
|
|
32
32
|
"G-PRESERVATION", "G-FINGERPRINTS", "G-OWNERSHIP", "G-FINAL-CHALLENGE"}
|
|
33
33
|
mayBeNA = set("G-PERF-TARGETS", "G-LANE-RECEIPTS", "G-CONCURRENCY")
|
|
34
|
-
// domainOrder
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
// domainOrder also orders the per-domain threshold rows.
|
|
35
|
+
domainOrder = ovio.Domains
|
|
36
|
+
// defaultDomainWeights is the documented default weight table; a rubric
|
|
37
|
+
// that deviates records the reason and the approval in weight_deviations.
|
|
38
|
+
defaultDomainWeights = map[string]int64{"correctness": 20, "security": 20, "reliability": 15, "performance": 15,
|
|
39
|
+
"tests": 10, "architecture": 10, "ux": 5, "operations": 5}
|
|
40
|
+
controlWeights = set("1", "3", "5")
|
|
41
|
+
evaluations = set("executed", "static", "source-proof", "human")
|
|
42
|
+
defaultPolicy = [][2]string{{"global_min", "9.7"}, {"lane_min", "9.7"}, {"domain_min", "9.0"}, {"lane_domain_min", "9.0"}}
|
|
43
|
+
transitions = map[[2]string]string{
|
|
39
44
|
{"FAIL", "PASS"}: "fixed-or-newly-proven",
|
|
40
45
|
{"UNKNOWN", "PASS"}: "evidence-only",
|
|
41
46
|
{"PASS", "FAIL"}: "regression-or-new-failure",
|
|
@@ -69,10 +74,15 @@ func run(args []string) (string, error) {
|
|
|
69
74
|
if err != nil {
|
|
70
75
|
return "", err
|
|
71
76
|
}
|
|
72
|
-
docs,
|
|
77
|
+
docs, digs, err := load(o["--rubric"], o["--baseline"], o["--candidate"])
|
|
73
78
|
if err != nil {
|
|
74
79
|
return "", err
|
|
75
80
|
}
|
|
81
|
+
for i, arm := range []string{"baseline", "candidate"} {
|
|
82
|
+
if err := sameRubric(docs[i+1], digs[0], arm); err != nil {
|
|
83
|
+
return "", err
|
|
84
|
+
}
|
|
85
|
+
}
|
|
76
86
|
res, err := compare(docs[0], docs[1], docs[2])
|
|
77
87
|
if err != nil {
|
|
78
88
|
return "", err
|
|
@@ -88,8 +98,8 @@ func run(args []string) (string, error) {
|
|
|
88
98
|
if err != nil {
|
|
89
99
|
return "", err
|
|
90
100
|
}
|
|
91
|
-
if
|
|
92
|
-
return "",
|
|
101
|
+
if err := sameRubric(docs[1], digs[0], "results"); err != nil {
|
|
102
|
+
return "", err
|
|
93
103
|
}
|
|
94
104
|
out, err := score(docs[0], docs[1], docs[2])
|
|
95
105
|
if err != nil {
|
|
@@ -110,6 +120,18 @@ func run(args []string) (string, error) {
|
|
|
110
120
|
return fmt.Sprintf("%s -> %s\n", out["readiness_verdict"], path), nil
|
|
111
121
|
}
|
|
112
122
|
|
|
123
|
+
// sameRubric requires results to name the digest of the rubric they were
|
|
124
|
+
// produced for, so two scorecards are only comparable on one rubric revision.
|
|
125
|
+
func sameRubric(results map[string]any, digest, what string) error {
|
|
126
|
+
switch d := results["rubric_digest"]; {
|
|
127
|
+
case d == nil:
|
|
128
|
+
return fmt.Errorf("%s: rubric_digest is required", what)
|
|
129
|
+
case d != any(digest):
|
|
130
|
+
return fmt.Errorf("%s were produced for a different rubric digest", what)
|
|
131
|
+
}
|
|
132
|
+
return nil
|
|
133
|
+
}
|
|
134
|
+
|
|
113
135
|
// flags parses "--name value" pairs; every required flag must be present and
|
|
114
136
|
// no other flag is accepted.
|
|
115
137
|
func flags(args, required, optional []string) (map[string]string, error) {
|
|
@@ -181,6 +203,7 @@ func needReason(v any, what string) error {
|
|
|
181
203
|
|
|
182
204
|
type control struct {
|
|
183
205
|
id, domain string
|
|
206
|
+
evaluation string
|
|
184
207
|
lanes []string
|
|
185
208
|
weight *big.Rat
|
|
186
209
|
na, hard, indep bool
|
|
@@ -224,12 +247,27 @@ func checkRubric(r map[string]any) (*rubricSpec, error) {
|
|
|
224
247
|
return nil, errPolicy
|
|
225
248
|
}
|
|
226
249
|
}
|
|
250
|
+
deviations := map[string]bool{}
|
|
251
|
+
for _, e := range ovio.List(r["weight_deviations"]) {
|
|
252
|
+
m := ovio.Obj(e)
|
|
253
|
+
d, _ := m["domain"].(string)
|
|
254
|
+
if d == "" || !ovio.Truthy(m["reason"]) || !ovio.Truthy(m["approval"]) {
|
|
255
|
+
return nil, fmt.Errorf("weight deviation %q needs a domain, a reason and an approval reference", d)
|
|
256
|
+
}
|
|
257
|
+
if _, known := defaultDomainWeights[d]; !known || deviations[d] {
|
|
258
|
+
return nil, fmt.Errorf("weight deviation %q names an unknown or repeated domain", d)
|
|
259
|
+
}
|
|
260
|
+
deviations[d] = true
|
|
261
|
+
}
|
|
227
262
|
doms := ovio.Obj(r["domains"])
|
|
228
263
|
for _, d := range slices.Sorted(maps.Keys(doms)) {
|
|
229
264
|
w, err := num(doms[d], "domain weight "+d)
|
|
230
265
|
if err != nil {
|
|
231
266
|
return nil, err
|
|
232
267
|
}
|
|
268
|
+
if def, ok := defaultDomainWeights[d]; ok && w.Cmp(big.NewRat(def, 1)) != 0 && !deviations[d] {
|
|
269
|
+
return nil, fmt.Errorf("domain weight %s is %s, not the default %d, without a weight_deviations entry", d, w.RatString(), def)
|
|
270
|
+
}
|
|
233
271
|
sp.dw[d] = w
|
|
234
272
|
}
|
|
235
273
|
for _, e := range ovio.List(r["domain_exclusions"]) {
|
|
@@ -310,8 +348,15 @@ func checkRubric(r map[string]any) (*rubricSpec, error) {
|
|
|
310
348
|
if err != nil {
|
|
311
349
|
return nil, err
|
|
312
350
|
}
|
|
351
|
+
if !controlWeights[w.RatString()] {
|
|
352
|
+
return nil, fmt.Errorf("%s: weight must be 1, 3 or 5, got %s", id, w.RatString())
|
|
353
|
+
}
|
|
354
|
+
ev, _ := c["evaluation"].(string)
|
|
355
|
+
if !evaluations[ev] {
|
|
356
|
+
return nil, fmt.Errorf("%s: evaluation must be executed, static, source-proof or human, got %v", id, c["evaluation"])
|
|
357
|
+
}
|
|
313
358
|
sp.controls = append(sp.controls, control{id: id, domain: dom, lanes: lanes, weight: w, na: na != nil,
|
|
314
|
-
hard: ovio.Truthy(c["hard_gate"]), indep: ovio.Truthy(c["independent_verification"])})
|
|
359
|
+
hard: ovio.Truthy(c["hard_gate"]), indep: ovio.Truthy(c["independent_verification"]), evaluation: ev})
|
|
315
360
|
}
|
|
316
361
|
return sp, nil
|
|
317
362
|
}
|
|
@@ -331,6 +376,10 @@ func credit(c control, res map[string]any, notes *[]any) (string, error) {
|
|
|
331
376
|
*notes = append(*notes, map[string]any{"control": c.id, "from": "PASS", "to": "UNKNOWN", "reason": "no evidence"})
|
|
332
377
|
st = "UNKNOWN"
|
|
333
378
|
}
|
|
379
|
+
if st == "PASS" && c.domain == "performance" && c.evaluation == "static" {
|
|
380
|
+
*notes = append(*notes, map[string]any{"control": c.id, "from": "PASS", "to": "UNKNOWN", "reason": "static inspection establishes no performance metric"})
|
|
381
|
+
st = "UNKNOWN"
|
|
382
|
+
}
|
|
334
383
|
if st == "PASS" && c.indep && !ovio.Truthy(res["verified_by"]) {
|
|
335
384
|
*notes = append(*notes, map[string]any{"control": c.id, "from": "PASS", "to": "UNKNOWN", "reason": "no independent verification"})
|
|
336
385
|
st = "UNKNOWN"
|