T22: validate checkpoint completeness inside sheal retro - #49
Conversation
sheal retro now runs a completeness assessment before analysis: assessCompleteness (src/retro/completeness.ts) reports input gaps — no sessions, no transcript entries, no assistant messages, empty entry content, tool results without recorded calls, no filesTouched — as an inputGaps field in the JSON report and a yellow 'Input Gaps — Analysis Degraded' section in pretty output. Checkpoint loading is validated at the boundary (isCheckpoint guard): corrupt JSON or missing structure produces a clean error naming the checkpoint instead of a stack trace. Report-only by design: gaps do not change the exit code (deliberate scope decision; revisit alongside Q8 strict semantics if wanted). Implemented by Codex (codex-rescue), reviewed by Claude; 7 new tests red-first in test/retro-completeness.test.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…CLI tests Codex's independent review (step 4) found the completeness heuristics imprecise and the Done-when CLI test missing. Applied: - Tool pairing now flags BOTH directions — a call without a recorded result (the actual truncation case, previously missed) and a result without a call. Balanced-orphan pairs remain undetectable (entries carry no correlation ids) — dismissed as out of scope. - Blank content only counts for conversational entries; Gemini-shaped tool entries legitimately carry empty content with tool data. - Empty filesTouched is a gap only when file-modifying tools ran; read-only sessions are no longer falsely marked degraded (deliberate behavior change from the original assertion). - Batch retro (--last/--today) wraps candidate loading in the same clean-skip crash contract as the single path. - assessCompleteness now runs before the analyzers in runRetrospective. - Loader error strings bound to their first line. - CLI-driving tests added per the task's Done-when: truncated fixture (gaps in pretty + JSON, exit 0) and corrupt fixture (no stack trace), via a HOME-override native-session plant. Dismissed with reasons: full-schema validation beyond the crash contract (bounded validator + clean errors cover the threat); free-text truncation detection (not reliably detectable from transcripts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#49 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🧑🏫 PR Tutor — human-review pass1. TL;DR
2. The problem it solvesT22 (Validate checkpoint completeness inside 3. Picture itMap — where the new pieces sit in the retro pipeline: The validator guards the disk boundary; the completeness check lives in the engine, so every path that builds a report (single, Decision tree — what loading a checkpoint can now yield: Every failure is named after the checkpoint, bounded to one line, and never a stack trace. Before → After — the behavior change for thin input: The report is still produced (degraded, not suppressed) — the reader just can't miss that the input was thin. 4. Toy example — the tool-pairing heuristicThe least obvious of the five gap checks. Transcripts record a tool call and its result as two separate entries (some runtimes emit one self-contained entry carrying both). Entries have no correlation IDs, so the check counts each side and compares: The other four checks are simpler existence tests: no sessions / no transcript entries; no assistant messages; blank content on conversational entries (tool entries may legitimately be blank — Gemini carries data in 5. WalkthroughDetection — Wiring — Boundary + display — Tests — Docs — Reviewed only the delta over PR #48 (base 6. Glossary (as this repo uses the terms)
7. Does it meet "Done when"?From
Note: the task's "What we need" section has a third bullet — the retro extractor should branch "product feedback vs. learning" so bug reports about sheal don't enter the learnings store — which is not implemented and not covered by Done-when. It's arguably the deeper lesson of the LEARN-019/020 reclassification. See follow-ups. 8. Loose ends & red flags
9. Suggested follow-up tasksProvisional numbers (current max across all branches is T23; (The load-error exit-code question is better appended to Q8 (Strict-mode semantics: how should Say "file these" (or "file task 1") and the main session will create them via the opentasks skill. 10. Verify by hand
11. Check your understanding (optional — skip freely, or say "quiz me")
Tutoring pass, not an approval or a safety claim. It surfaces what changed and what to check by hand; the human reviewer decides. |
…s, test type narrowing pr-tutor findings on PR #49: the batch path had the crash guard but not the isCheckpoint structure guard, and JSON-mode batch skips were silent — a batch could under-report with no trace. Skip notices now go to stderr in JSON mode (stdout stays parseable, CLI test added) and malformed structures skip cleanly. Also narrowed the load-result union in the tests so the file survives typechecking if tests ever join the tsc gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…onstant); append load-error exit semantics to Q8 All three from the pr-tutor pass on PR #49. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Tutor-pass follow-up — findings triaged and applied at 8a087e3 (T22: tutor-pass fixes):
Suite green at the new head; the qa-personas run in flight tested the prior head — its oracle probes (dry-run/gap/exit-code invariants) are unaffected by these additions. 🤖 Generated with Claude Code |
qa-personas results — PR #49 (T22: validate checkpoint completeness inside
|
| Probe | Assertion | Result |
|---|---|---|
| P1a | Truncated session (user-only, ≥3 prompts) → pretty "Input Gaps — Analysis Degraded" + exit 0 | PASS |
| P1b | Same session -f json → inputGaps[] present, JSON parseable, exit 0 |
PASS |
| P2a | Complete session → analyzed, not marked degraded, exit 0 | PASS |
| P2b | Complete session -f json → no inputGaps key |
PASS |
| P3a | Corrupt JSONL → clean handling, no stack trace, non-empty message, exit unchanged (0) | PASS |
| P3b | Corrupt JSONL -f json → no stack trace, exit unchanged |
PASS |
| P4a | Gaps never alter exit code (truncated == dangling == complete, all 0) | PASS |
| P5a | Dangling tool call (truncation) reported as a gap | PASS |
| P6a | Batch path (--last) over mixed/corrupt fixtures → no stack trace, clean skip |
PASS |
All four mandated invariants (gap report on truncation & exit 0; complete not degraded; corrupt ⇒ no stack trace, exit unchanged; gaps never change exit code) hold. PR's own suite test/retro-completeness.test.ts also green (13/13).
Persona findings (soft signal — advisory, severity-tagged)
Scope note: every item below reproduces on the changed CLI surface but lives in code unchanged by this diff (the loader's join(dir, "${sessionId}.jsonl") and the effort/render analyzers both predate PR #49). None is a regression from T22. Listed so they're on record; a human decides whether to spin off follow-ups.
- [High · pre-existing] Path traversal / arbitrary
.jsonlread via-c(misuse persona; independently reproduced by the orchestrator). A crafted checkpoint id escapes the project's slug dir and reads any.jsonlthe user can access.
Repro: with a session file at/tmp/tmpleak.jsonl,
node dist/index.js retro -p <proj> -c "$(python3 -c 'print("../"*20+"tmp/tmpleak")')" -f json→ fully analyzes the out-of-tree file (checkpointIdechoes the../…/tmp/tmpleakpath). Also reaches sibling project dirs and$HOME. Root cause is the unsanitized${sessionId}.jsonljoin in@liwala/agent-sessions(present on the base branch). Suggest a dedicated follow-up task to sanitize/confine checkpoint ids to the projects dir. - [Med · pre-existing] Out-of-range token values corrupt machine-readable output.
input_tokens: 1e400→ JSON"inputTokens": null; negative tokens pass through as negative totals — breaks downstream cost/accounting consumers. Repro:-c <bignum-fixture> -f json. - [Med · pre-existing] Prototype-chain tool names corrupt
toolCounts. A tool namedconstructoryieldstoolCounts.constructor = "function Object() { [native code] }1"(string where a number is contractual); a tool named__proto__is silently dropped and mutates the counts object's prototype. - [Low/Med · pre-existing] ANSI-escape injection in pretty output. Attacker-controlled tool names / file paths are printed to the terminal without stripping control sequences (cursor/color/title spoofing). Repro:
-c <ansi-fixture> | cat -vshows raw^[[31m…. - [Low · pre-existing] Invalid
createdAtreflected verbatim into JSON (e.g.-2026-07-17T…). - [UX · this PR's surface] Degraded inputs still report
Health Score: 100/100and the batch summary hides degradation prevalence. Not a break (gaps are report-only by design per the PR), but the perfect score sits next to the "Analysis Degraded" banner, which a skimming user may misread. Batch--lastaverages degraded + complete into oneAverage health: 100/100with no degraded count. Worth considering as a UX polish, consistent with T22's intent of flagging low-trust analysis. - [UX] Unknown
--formatsilently falls back to pretty (-f xml→ pretty, exit 0) instead of rejecting. - [UX]
-c ''selects latest;--today -c <id>silently ignores-c— selector precedence is undocumented.
Correctly handled (no divergence): empty/blank/deeply-nested/NUL/bad-UTF-8/wrong-shape/NaN-literal fixtures (no crash), giant transcript at normal memory, all gap-suppression attempts (truncated stayed degraded, complete stayed complete), JSON stayed parseable throughout, absolute/suffix -c forms rejected.
Suggested regression seeds (for the conductor/human to land)
- The 4 mandated invariants are already covered by
test/retro-completeness.test.ts; the oracle mirrors them — no gap there. - If the [High] traversal is accepted as a bug: a CLI test asserting
-c "../../../x"(and an absolute-escape form) does not read outside the resolved projects dir — landed against the loader in@liwala/agent-sessions, not this PR.
Method: cross-vendor black-box personas + an out-of-persona deterministic oracle. Advisory only — a human owns the merge.
…he PR #49 qa-personas [High] advisory Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
Implements T22 (Validate checkpoint completeness inside
sheal retro), stacked on #48. Extracted from the consolidation pass's reclassification of LEARN-019/020: retro used to analyze whatever it loaded, producing hollow analysis from truncated session data.assessCompleteness(src/retro/completeness.ts) runs before the analyzers and reports gaps: no sessions / no transcript entries / no assistant messages / blank conversational entries / tool calls without recorded results (the truncation case) and results without calls / emptyfilesToucheddespite file-modifying tool activity.inputGaps: string[]in JSON reports and a yellow "Input Gaps — Analysis Degraded" section in pretty output. Report-only by design — gaps never change the exit code (deliberate scope decision; revisit with Q8 (Strict-mode semantics) if strict wiring is wanted later).isCheckpointguard): corrupt JSON or missing structure yields a clean, checkpoint-named error (first-line bounded), no stack trace. The batch path (--last/--today) has the same clean-skip crash contract.Two-review trail
codex exec, cross-vendor) from a TDD brief; 7 tests red-first.filesTouchedfalse positive, batch-path guard, the Done-when CLI tests), 1 trivial reorder (assess before analyzers), 2 dismissed with reasons in the commit trail (full-schema validation beyond the crash contract; free-text truncation detection isn't reliably detectable).Done when status
tscclean, lint 0 errors (37 pre-existing warnings).🤖 Generated with Claude Code