Record deferred resource assessments

This commit is contained in:
2026-11-02 03:36:53 +00:00
parent d9c97fd915
commit 9dc87ae996
2 changed files with 156 additions and 1 deletions

View File

@@ -53,7 +53,7 @@ the agent policy; an intermediate compilation dependency is recorded accurately.
| 15 | A13 | Complete — validation ordering, scope, tree lifecycle, diagnostics, and public callback checks passed. |
| 16 | A15 | Complete — the full CLI command suite passed after workflow extraction. |
| 17 | Remaining A19, A20 | Complete — Git, identity, and Markdown tests passed; delivery benchmark ran for fresh uploads and matching retries. |
| 18 | A11, A17: assessment only | Pending |
| 18 | A11, A17: assessment only | Assessment complete; production JSON limits and exclusion indexing remain deferred. Evidence follows below. |
| 19 | Integrated verification and current documentation | Pending |
## Stage 1: Correct the owned tag push
@@ -536,6 +536,52 @@ production behavior remains as defined in [audit.md](audit.md).
Evidence: both deferred items have a concrete disposition and useful evidence
without inventing unapproved protocol limits or performance thresholds.
### Assessment evidence
**A11 — observed local sizes, not limits.** The Gitea tests construct repository,
release, tag, and attachment responses in code; the public delivery fixture also
constructs release and attachment lists with variable URLs, note bodies, and
asset counts. Representative encoded fixture responses, including the JSON
encoder's trailing newline, measured 81 bytes for repository identity, 136
bytes with permissions, 125 bytes for a sparse release response, 144 bytes for
a populated release response, 73 bytes for a one-item attachment list, and 62
bytes for an attachment upload response. The four literal release-ID fixtures
in `internal/gitea/backend_test.go` are 21, 28, 29, and 29 bytes without a
newline. Empty, malformed, and missing-field responses are also represented;
generated list and note lengths vary with the fixture inputs.
The eight checked-in release notes measured: `v0.1.0` 3,400 bytes, `v0.2.0`
6,086, `v0.3.0` 6,006, `v0.4.0` 7,899, `v0.4.1` 1,235, `v0.4.2` 1,826,
`v0.5.0` 3,179, and `v0.6.0` 7,297. These local examples do not establish
deployment or protocol maxima. JSON size/time caps and new configuration remain
deferred; existing cancellation and ambiguous-write handling remain in force.
**A17 — benchmark-only index.** On Linux/amd64, Go 1.26.8, Intel Core
i5-7260U, the following command ran 64 combinations, three samples each:
```sh
PATH=/home/eric/sdk/go1.26.8/bin:$PATH TMPDIR=/var/tmp \
GOCACHE=/tmp/releasekit-go-cache GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off \
go test ./internal/validation -run '^$' -bench '^BenchmarkFileSizeExclusions$' \
-benchtime=100ms -count=3
```
Each operation normalizes exclusions and, for the prototype, builds one index.
Paths and fixture strings are prepared outside the timed interval. Median
times and allocations for representative cases were:
| Paths / exclusions / case | Scan | Index |
| --- | --- | --- |
| 1,000 / 1 / shallow early match | 24.5 µs, 16 B, 1 allocation | 44.0 µs, 16 B, 1 allocation |
| 1,000 / 1 / deep nonmatch | 25.7 µs, 16 B, 1 allocation | 119.0 µs, 16 B, 1 allocation |
| 10,000 / 1,000 / shallow early match | 270 µs, 16,384 B, 1 allocation | 564 µs, 70,992 B, 6 allocations |
| 10,000 / 1,000 / shallow nonmatch | 227.0 ms, 16,384 B, 1 allocation | 521 µs, 70,992 B, 6 allocations |
| 10,000 / 1,000 / deep nonmatch | 233.4 ms, 16,384 B, 1 allocation | 1.36 ms, 70,992 B, 6 allocations |
The prototype is slower for small lists and early matches but substantially
faster for the large nonmatching list, at a higher allocation cost. Production
matching remains unchanged; these observations support a later adoption decision.
## Stage 19: Verify the complete target and update current documentation
1. Inspect the combined implementation against all A01–A20 outcomes and the

View File

@@ -0,0 +1,109 @@
package validation
import (
"fmt"
"strings"
"testing"
)
// benchmarkExclusionIndex is an assessment-only alternative, not production logic.
func benchmarkExclusionIndex(exclusions []string) map[string]struct{} {
index := make(map[string]struct{}, len(exclusions))
for _, exclusion := range normalizedExclusions(exclusions) {
index[exclusion] = struct{}{}
}
return index
}
func benchmarkExcluded(filePath string, index map[string]struct{}) bool {
if _, ok := index["."]; ok {
return true
}
if _, ok := index[filePath]; ok {
return true
}
for pos := 0; pos < len(filePath); pos++ {
if filePath[pos] == '/' {
if _, ok := index[filePath[:pos]]; ok {
return true
}
}
}
return false
}
var benchmarkExclusionMatches int
func BenchmarkFileSizeExclusions(b *testing.B) {
for _, pathCount := range []int{1000, 10000} {
for _, exclusionCount := range []int{1, 10, 100, 1000} {
for _, depth := range []string{"shallow", "deep"} {
for _, match := range []bool{true, false} {
exclusions := make([]string, exclusionCount)
for i := range exclusions {
exclusions[i] = fmt.Sprintf("excluded-%04d", i)
}
paths := make([]string, pathCount)
prefix := "other"
if match {
prefix = exclusions[0]
}
if depth == "deep" {
prefix += "/nested/one/two/three/four"
}
for i := range paths {
paths[i] = fmt.Sprintf("%s/file-%05d", prefix, i)
}
name := fmt.Sprintf("paths=%d/exclusions=%d/%s/match=%t", pathCount, exclusionCount, depth, match)
for _, indexed := range []bool{false, true} {
method := "scan"
if indexed {
method = "index"
}
b.Run(name+"/"+method, func(b *testing.B) {
normalized := normalizedExclusions(exclusions)
index := benchmarkExclusionIndex(exclusions)
for _, filePath := range paths {
if excludedByPath(filePath, normalized) != benchmarkExcluded(filePath, index) {
b.Fatalf("index disagrees for %q", filePath)
}
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
if indexed {
index := benchmarkExclusionIndex(exclusions)
for _, filePath := range paths {
if benchmarkExcluded(filePath, index) {
count++
}
}
} else {
normalized := normalizedExclusions(exclusions)
for _, filePath := range paths {
if excludedByPath(filePath, normalized) {
count++
}
}
}
benchmarkExclusionMatches = count
}
})
}
}
}
}
}
}
func TestBenchmarkExclusionIndexPreservesLiteralMatching(t *testing.T) {
for _, exclusions := range [][]string{{"."}, {"/"}, {"a//b", "../x", "", "a/b"}, {"a", "a/b"}} {
index := benchmarkExclusionIndex(exclusions)
for _, filePath := range []string{"a", "a/b", "a/b/c", "ab/c", "/", "//file", "../x/file", "other"} {
if got, want := benchmarkExcluded(filePath, index), excludedByPath(filePath, normalizedExclusions(exclusions)); got != want {
t.Errorf("path %q exclusions %s: indexed %t, scan %t", filePath, strings.Join(exclusions, ","), got, want)
}
}
}
}