◐ Off-By-One · answer catalog

go-audit-idle-full-audit-window

1 answer(s)godocker

go-audit-idle-full-audit-window

📦 Source in repository (JSON)

Answer

Root cause (go-audit-idle-full-audit-window): the FULL-audit due window was anchored to the last audit of any kind. LIGHT verification runs on every non-FULL tick and bumps that anchor, so while the system is idle the window slides forward forever and the FULL audit is NEVER executed — the recurring rows stay pending forever and the first tick of the due window (#220 after FULL at #217) never fires.

Fix: anchor the window to lastFullTick only; LIGHT ticks bump only lastLightTick. Window opens at lastFullTick + fullInterval and stays open (>=), so a missed first tick (downtime/restart) is caught up on the first tick actually executed inside the window.

// fullDue reports whether the current tick is inside the full-audit window.
func (s *Scheduler) fullDue() bool {
    if s.buggy { // PRE-FIX: anchor = last audit of ANY kind; LIGHT ticks slide
        // the window forever => FULL is NEVER done.
        return s.tick-s.lastLightTick >= s.fullInterval
    }
    // FIX: anchor = last FULL audit only. LIGHT verification never
    // postpones the FULL audit.
    return s.tick-s.lastFullTick >= s.fullInterval
}

func (s *Scheduler) Tick() {
    s.tick++
    if s.fullDue() {
        s.executions = append(s.executions,
            Execution{Tick: s.tick, Kind: KindFull, Rows: len(s.pending)})
        s.lastFullTick = s.tick // only FULL moves the window
        return
    }
    s.executions = append(s.executions, Execution{Tick: s.tick, Kind: KindLight})
    s.lastLightTick = s.tick
}

State is seeded from persisted state (SeedLastFull(217) also restores the tick counter, since a FULL at #217 implies ticks through #217 were processed).

Secondary fix (d765c5d, /namespaces/{id}): invalid-numeric id returned 500 on the live pre-fix binary; it's a client error → 400. Also rejects empty/negative/overflow ids — never a 500:

func ParseNamespaceID(path string) (int, error) {
    const prefix = "/namespaces/"
    if !strings.HasPrefix(path, prefix) { return 0, fmt.Errorf("path %q does not match %s{id}", path, prefix) }
    id := strings.TrimPrefix(path, prefix)
    if id == "" { return 0, fmt.Errorf("missing namespace id") }
    n, err := strconv.Atoi(id)
    if err != nil { return 0, fmt.Errorf("invalid numeric namespace id %q", id) } // FIX: was 500
    if n < 0 { return 0, fmt.Errorf("namespace id must be non-negative, got %q", id) }
    return n, nil
}

INFRA-012 (deploy): d765c5d is verified on scratch but a mid-tick restart marks in-flight ticks as timeout. Deploy at a tick boundary via TickerGate: CloseForDrain() → wait Inflight()==0 → restart (window survives via persisted lastFullTick) → Reopen().

Evidence & signatures

Module: `~/auditfix` (Go 1.26, `go vet`/`gofmt` clean, `go test -race` clean). **11/11 gates green:**

| Gate | Check | Result |
|---|---|---|
| 01 | FULL at first tick of due window (#220, then #223 after FULL at #217) | ✅ `FULL ticks=[220 223]` |
| 02 | No FULL before window (#218/#219 LIGHT) | ✅ |
| 03 | Recurring rows stay pending forever, each FULL covers all 3 | ✅ |
| 04 | **Regression:** pre-fix anchor over 60 ticks → 0 FULLs (NEVER-DONE); fixed → 20 | ✅ |
| 05 | Missed window start (restart) → catch-up FULL at first tick | ✅ |
| 06 | Long-run cadence, spacing exactly `fullInterval` | ✅ |
| 07 | `/namespaces/abc` → 400 (was 500) | ✅ |
| 08 | `/namespaces/42` → 200, body `namespace 42` | ✅ |
| 09 | Table: `12x`, `//`, `-5`, overflow, `12/34`, `other/12` → 4xx, never 500; `0`,`007` → 200 | ✅ |
| 10 | E2E-001: full battery every ~5 ticks (fulls at 5,10,…,30); pre-fix runs 0 | ✅ |
| 11 | INFRA-012: close→drain→reopen; in-flight ticks complete, window survives restart | ✅ |

Trace of the ticket scenario (full at #217, interval 3): `#218 LIGHT, #219 LIGHT, #220 FULL(3 rows), #221 LIGHT, #222 LIGHT, #223 FULL(3 rows), #224 LIGHT, #225 LIGHT`.

Edge cases tested: downtime catch-up, interval clamp, negative/empty/overflow ids, E2E-001 cadence, and drain/restart not losing the window or timing out in-flight ticks.
{"model": "deepseek-v4-flash", "problem_class": "go-audit-idle-full-audit-window", "result": "passed", "tests": 11}
Generated from the verified corpus · MIT licensedBack to the catalog