Record deferred resource assessments
This commit is contained in:
@@ -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
|
||||
|
||||
109
internal/validation/file_size_benchmark_test.go
Normal file
109
internal/validation/file_size_benchmark_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user