Bound cairn prime cost on payload size and freshness-check count#57
Merged
Conversation
…ness) Prime now returns a structured PrimeResult (deterministically ordered by hit_count desc, created_at desc, id asc) instead of a preformatted string, itemized up to a caller-supplied byte budget while always computing fresh/stale/unknown counts over the entire visible set regardless of truncation. Freshness checks that need a git shell-out are capped per call (maxFreshnessChecksPerPrime) and fail toward unknown past the cap, never fabricating fresh. RenderPrimeText produces the human-readable text `cairn prime` prints by default; cmd/prime.go gains a --budget-bytes flag. In-scope side fix: Check()'s branch order now checks anchor shape, then never-verified, before the expensive ComputeFingerprint call, so a never-verified files anchor no longer pays for a git shell-out whose answer doesn't depend on the result. Benefits all four Check() callers, not just Prime. Status()'s SELECT is extended to also read title/summary/hit_count -- already-indexed columns at zero marginal query cost -- so Prime can render entries without a body read. This is a deliberate, reviewed exception per crn-0vqk.1's design, not a precedent for further ad hoc columns. Implements crn-0vqk.2 per the crn-0vqk.1 design.
…RenderPrimeText signature) Addresses cairn/reviewer's REQUEST-CHANGES verdict on crn-0vqk.2: - Finding 1 (blocking): Prime()'s truncation loop used continue instead of stopping, so a later lower-priority-but-cheaper entry could slip into Items past a pricier higher-priority one that got skipped. Latches a truncating flag on the first over-budget item so itemization stops there while freshness classification (and its aggregate counts) still runs for every visible entry. New regression test uses varying-cost fixtures across the priority order, verified to fail under the old continue-only behavior and pass under the fix. - Finding 2: corrected orderByRelevance's doc comment, which claimed CreatedAt is RFC3339 -- the only production stamping call site (remember.go) uses time.DateOnly. - Finding 3 (blocking addendum): dropped RenderPrimeText's redundant store/identity parameters (store was already dead, discarded via `_ = store`) in favor of reading r.Store/r.Identity directly, per the reviewer's resolution of the crn-0vqk.1 prose-vs-ER-diagram spec disagreement. Updated the cmd/prime.go call site and all test call sites. refs crn-0vqk.2
Post-rebase reconciliation onto origin/main (PR #53 added cairn.UntopicedLabel as the shared "no topic" sentinel for map/get/list; this branch's Prime() rewrite predates that and had its own literal "(untopiced)" string in RenderPrimeText). Same string value, no behavior change -- keeps prime consistent with the other three commands so they can't drift on this sentinel independently.
…e reconciliation)
quad341
added a commit
that referenced
this pull request
Jul 25, 2026
…eshness checks (#54) * test(cairn): RED — cover git-invocation-failure vs confirmed-negative (crn-fdjc.1.1) Add Incomplete status constant and new tests at the stable Check/Sweep entry points asserting the FR-5 failure-class distinction from crn-fdjc.1's design: a genuine git invocation failure (PATH unreachable, canceled context) must classify as Incomplete, never collapse into the same Unknown verdict as a confirmed negative (missing repo, no commits yet, unmatched glob, staged-but-uncommitted path). New tests fail against the current implementation, which still folds every failure into Unknown/"?" — expected RED. Confirmed-negative regression tests pass unmodified, confirming FR-2 (today's behavior for those classes is preserved). ComputeFingerprint/objectHash/expand/ untrackedPaths keep their current signatures for now; direct tests of their error-returning form land in the GREEN commit since they can't compile until the signatures change. * cairn: propagate git-invocation-failure vs confirmed-negative (GREEN) ComputeFingerprint/objectHash/expand now return an explicit error for a genuine git invocation failure, distinct from a confirmed negative (untracked path, no anchor, etc.) which still returns ("", nil). Check surfaces invocation failures as a new Incomplete status; Sweep's untrackedPaths enrichment leaves status/detail untouched when the underlying check was itself incomplete, instead of misreporting it as a confirmed Unknown. cmd/commands.go: freshnessCmd/verifyCmd now hard-error on Incomplete/invocation failure; statusCmd adds an "!X " flag; getCmd needed no change (already prints any status generically). Implements crn-fdjc.1.1 per architect design in crn-fdjc.1. * chore: release gate PASS for freshness-invocation-incomplete-status * fix: reorder Check() so Incomplete wins over never-verified, fix budget gate Rebasing crn-fdjc.1.1 (git-invocation-failure -> Incomplete propagation) onto crn-0vqk.2's already-merged Check() reorder (never-verified short-circuit before the git shell-out, for performance) produced a textual, mechanically-resolvable conflict -- but the obvious resolution (keep the never-verified check first) silently defeated crn-fdjc.1.1's own pinning tests: a files anchor that has never been fingerprinted yet still needs its git invocation attempted, so a genuine failure (broken PATH, canceled context) surfaces as Incomplete instead of being masked behind "never verified". Reordered Check() to attempt ComputeFingerprint (surfacing Incomplete on error) before the never-verified check, which now runs only after a successful call -- preserving crn-0vqk.2's original fix against a false Fresh/Stale null-comparison for a never-verified anchor, while letting crn-fdjc.1.1's error propagation win first. freshnessBudget.classify (prime.go) gated its shell-out cap on `a.Fingerprint != ""`, which was only ever correct under the old ordering where a never-verified anchor short-circuited for free. Now that Check unconditionally shells out for any shape-verifiable files anchor, that clause under-metered the exact cost FR-5's budget cap exists to bound -- dropped it so every files anchor that will actually invoke git counts against the cap, never-verified or not. Also fixes a stale call site in prime_test.go: ComputeFingerprint's pre-crn-fdjc.1.1 signature returned a single value; the two-return form landed via this same rebase but prime_test.go (merged to main earlier, unrelated to this branch) wasn't touched by the rebased commits, so the break was invisible to git's textual merge. * chore: release gate PASS for freshness-invocation-incomplete-status (post-rebase reconciliation) Supersedes this branch's earlier gate evaluation (eef42ec), which assessed criterion 6 as a trivial clean merge before origin/main advanced again (PR #57) and produced a real feature-interaction conflict. This document reflects the actual final reconciled state.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Reworks
cairn primeto bound its cost on two independent axes regardless ofstore size: response payload bytes and freshness-check git shell-outs.
Primenow returns a structured result (deterministically ordered by hitcount, then recency, then ID) with itemized entries truncated once they
exceed a byte budget — but aggregate freshness counts (fresh/stale/unknown)
always cover the entire visible set, not just the truncated slice that made
it into the itemized output. Freshness checks that require a git shell-out
are capped per call (past the cap, remaining entries are reported as
"unknown" rather than skipped silently or fabricated as fresh). Rendering to
human-readable text is now a separate step over the structured result rather
than being baked into the computation itself.
Also brings prime's "no topic" display in line with the shared label used by
map,get, andlist, so the four commands can't drift out of sync onthat string independently.
Why
primeis meant to be injected automatically at session start. Without abyte budget, a large store could bloat every session's starting context;
without a cap on freshness checks, a large store could make session start
slow. Both bounds needed to hold regardless of how big the store grows.
What to review
itemization stops entirely rather than skipping ahead to smaller,
lower-priority entries — this preserves priority order in what gets shown.
Freshness aggregate counts are computed before truncation is applied, so
they stay accurate regardless of what made it into the itemized list.
shells out to git is metered: every cheaper classification is free and
unaffected by the cap.
Test plan
go test ./... -race -count=1— all packages green.gofmt -l .,go build ./...,go vet ./...,golangci-lint run— all clean.cairn primeagainst a store with more entries than the bytebudget allows and confirmed the truncation note and freshness totals both
read correctly.