Skip to content

docs: post-arc sweep + fix the prod-drift false positive that filed #176 - #179

Merged
bejranonda merged 2 commits into
mainfrom
fix/prod-drift-false-positives
Jul 31, 2026
Merged

docs: post-arc sweep + fix the prod-drift false positive that filed #176#179
bejranonda merged 2 commits into
mainfrom
fix/prod-drift-false-positives

Conversation

@bejranonda

@bejranonda bejranonda commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Sweeps the doc set after the v2.3.1 → v2.5.1 arc, and fixes the watchdog that produced the only open issue.

fix(ci): complete prod-drift's not-app-served exclusion set

prod-drift opened #176 for v2.4.0 — a docs + benchmark release that was correctly not deployed, per the standing rule to skip redeploys for changes touching nothing app-served.

The workflow already had a docs-only carve-out (#140). Two paths survived its filter:

.env.example
packages/core/generation-uplift/suite-2/**

Neither is app-served. .env.example is a template — the container reads the real .env. generation-uplift/ holds operator-run benchmark artifacts deliberately kept outside src/ so neither the package tsconfig nor vitest picks them up (GUIDELINES §4).

So the repo's own rule and its own watchdog were guaranteed to conflict. Completed the exclusion set (also .github/, which never enters an image) and verified both directions:

Range Content Result
v2.3.1..v2.4.0 docs + benchmark filters to empty → correctly in-sync
v2.4.0..v2.5.0 real kra.ts/oracle.ts change still reports drift → detector not blinded

The negative test matters more than the positive one: it's the check that I didn't just silence the alarm.

Docs brought in line with what shipped

  • KNOWLEDGE — the scope table said user = "this user's work". That described intent, not behaviour, until scope: "user" knowledge is invisible outside its capture project (117 rows, ~half the corpus) #174. Now documents real cross-project reach, pinned to ownerUserId, plus why §12.19 retrieval is deliberately wider than listing (a rule taught once should apply everywhere you work; a listing is a browsing surface where project focus is the point).
  • GUIDELINES — invariant 3 now states that project reach and user reach are separate axes, and that the cross-project disjunct carries its own ownerUserId predicate because it sits inside an OR where an outer AND wouldn't constrain siblings. Rate-limit section rewritten for the atomic Store contract, with the measurement (50 concurrent clients moved the old counter to 2) and the REDIS_URL requirement.
  • KNOWN_ISSUES — Redis row closed and verified; prod-drift row added; new row for the two-checkout hazard: /root/BrainPlatform and /root/ExternalBrain resolve to the same compose project deploy, so deploying from the wrong one targets live prod with a 4-line stub .env.
  • DEPLOY_CHECKLISTREDIS_URL was listed as multi-replica-only, "single replica — in-memory is correct". Correct but misleading: state resets on every deploy, silently clearing daily caps.
  • README — stack table gains rate-limit state; VALIDATION row carries both uplift reads and the finding that matters more than either number.
  • APPROACH §5bf — three defects that hid behind their own descriptions, and what generalises.

Test plan

  • Both drift filter directions verified against real tags (table above) — the false positive disappears and a genuine runtime change is still caught.
  • Workflow YAML re-parsed after edit; confirmed no new ${{ }} interpolation in run: blocks (the healthz value was already injection-hardened).
  • check-doc-refs.sh passes against the live ceiling (feat(core): suite-2 second reading — uncurated corpus after the #174 fix #178), not a stale hardcoded one.
  • Redis verified live end-to-end: one REDIS_URL line in .env, present in the running container, "redis ready" logged, 8 requests through the limiter with no fallback or error.
  • Grepped the doc set for stale Redis / scope / "operator action pending" claims; the one real hit (DEPLOY_CHECKLIST) is fixed in this PR.
  • Relying on CI — docs + workflow only, no product code.

Closes #176.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rqj5y9fT3XPUqnqKyustp9

Summary by CodeRabbit

  • Bug Fixes

    • Improved production drift checks to distinguish deployment-impacting changes from documentation and configuration-only updates.
    • Added safeguards to confirm deployed versions before evaluating drift.
    • Documented reliable rate-limit behavior across server replicas and reloads when Redis is configured.
  • Documentation

    • Clarified project and user data boundaries for retrieval.
    • Updated deployment guidance for Redis-backed rate limiting.
    • Expanded guidance on known issues, incident lessons, validation results, and cross-project retrieval behavior.

…tive

Sweeps the doc set after the v2.3.1 -> v2.5.1 arc and fixes the
watchdog that produced the only open issue.

fix(ci): complete prod-drift's not-app-served exclusion set. The
watchdog opened #176 for v2.4.0, a docs + benchmark release that was
CORRECTLY not deployed per the standing "don't redeploy changes that
touch nothing app-served" rule. It already had a docs-only carve-out;
two paths survived it — .env.example (a template; the container reads
the real .env) and packages/core/generation-uplift/** (operator-run
artifacts kept outside src/ precisely so nothing builds them). So the
repo's own rule and its watchdog were guaranteed to conflict. Added
those plus .github/, and verified BOTH directions: v2.3.1..v2.4.0 now
filters to empty, while v2.4.0..v2.5.0 (a real kra/oracle change) still
reports drift — the detector is not blinded.

docs updated for what actually shipped:
- KNOWLEDGE: the scope table said `user` means "this user's work"; that
  described intent, not behaviour, until #174. Now documents real
  cross-project reach, pinned to ownerUserId, and why §12.19 retrieval
  is deliberately wider than listing.
- GUIDELINES: invariant 3 now states project reach and user reach are
  separate axes and the cross-project disjunct carries its own
  ownerUserId predicate (it sits inside an OR, where an outer AND would
  not constrain siblings). Rate-limit section rewritten for the atomic
  Store contract — the old get-then-set let 50 concurrent clients move
  the counter to 2 — plus the REDIS_URL requirement.
- KNOWN_ISSUES: Redis row closed (operator set it; "redis ready"
  verified under load), prod-drift row added, and a new row for the
  two-checkout hazard — /root/BrainPlatform and /root/ExternalBrain
  resolve to the SAME compose project `deploy`, so deploying from the
  wrong one targets live prod with a 4-line stub .env.
- DEPLOY_CHECKLIST: REDIS_URL was listed as multi-replica-only with
  "single replica — in-memory is correct". Correct but misleading: the
  state resets on every deploy, silently clearing daily caps.
- README: stack table gains rate-limit state; VALIDATION row carries
  both uplift reads and the finding that matters more than either
  number.
- APPROACH §5bf: three defects that hid behind their own descriptions,
  and what generalises — read the whole helper including branches your
  case doesn't hit; green tests can't detect a boundary no test
  asserts; widening recall isn't free and the pass rate won't show it;
  publish the correction as loudly as the claim.

Closes #176.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqj5y9fT3XPUqnqKyustp9
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@bejranonda, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a28a1268-148a-428b-9daf-74d17a64941b

📥 Commits

Reviewing files that changed from the base of the PR and between 9f88d40 and 06fe3e7.

📒 Files selected for processing (2)
  • docs/APPROACH.md
  • docs/GUIDELINES.md
📝 Walkthrough

Walkthrough

The pull request updates production drift detection and documents Redis-backed rate limiting, cross-project knowledge retrieval boundaries, live validation results, and corrected production deployment findings.

Changes

Production guidance

Layer / File(s) Summary
Production drift filtering
.github/workflows/prod-drift.yml, docs/KNOWN_ISSUES.md
The drift workflow validates the deployed commit and ignores non-application changes such as documentation, CI files, .env.example, and generation-uplift artifacts.
Rate-limit state and deployment guidance
docs/GUIDELINES.md, README.md, docs/DEPLOY_CHECKLIST.md, docs/KNOWN_ISSUES.md
The documentation defines atomic in-memory and Redis limiter behavior, Redis deployment requirements, fallback behavior, and production verification.
Scope boundaries and retrieval behavior
docs/GUIDELINES.md, docs/KNOWLEDGE.md
The documentation separates project and user reach. KRA and Oracle may retrieve the caller’s own user and global knowledge across projects.
Production findings and validation records
docs/APPROACH.md, README.md, docs/KNOWN_ISSUES.md
The documentation records corrected production defects, deployment hazards, and an additional live-retrieval validation result.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the documentation sweep and the prod-drift false-positive fix addressed by the pull request.
Description check ✅ Passed The description clearly explains the changes and provides detailed verification results, but it omits the template's separate Gates section.
Linked Issues check ✅ Passed The workflow fix addresses [#176] by ignoring non-app-served drift while preserving detection of genuine runtime changes.
Out of Scope Changes check ✅ Passed The workflow and documentation changes match the stated objectives, with no unrelated code changes identified.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/prod-drift-false-positives

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
.github/workflows/prod-drift.yml (1)

67-80: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Align the benchmark exclusion with the verified non-runtime scope.

The workflow and issue record exclude the whole packages/core/generation-uplift/ subtree, while the PR objective identifies packages/core/generation-uplift/suite-2/**. Narrow the pattern, or prove that every descendant is outside package, Docker, and runtime inputs before treating the whole tree as safe.

  • .github/workflows/prod-drift.yml#L67-L80: change the regex or add the repository check before excluding the whole directory.
  • docs/KNOWN_ISSUES.md#L406-L406: document the actual exclusion path and its evidence.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/prod-drift.yml around lines 67 - 80, Narrow the benchmark
exclusion in the drift-check condition around the git diff regex to the verified
packages/core/generation-uplift/suite-2/** path, unless repository checks prove
the entire subtree is outside package, Docker, and runtime inputs. Update
docs/KNOWN_ISSUES.md at line 406 to document the same actual exclusion path and
supporting evidence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/prod-drift.yml:
- Line 67: Normalize the deployed revision before the git diff check in the
workflow, using git rev-parse --verify "$DEPLOYED^{commit}" so annotated tags
resolve to their actual commit. Ensure the resulting commit SHA is used as the
left-hand revision in the existing git diff comparison while preserving the
current file exclusions.

In `@docs/APPROACH.md`:
- Around line 1617-1628: Update the “A soft cap on LLM cost” section in
APPROACH.md to qualify the issue as an application-level rate-limit bypass, not
an unbounded end-to-end bypass. State that deploy/Caddyfile applies a
10-events-per-IP-per-second rate_limit to /api/* before application requests,
while preserving the explanation that the application’s rateLimitCheck affected
authentication endpoints.
- Around line 1630-1642: Update the documented retrieval mechanism to identify
buildRawProjectFilterV2 and describe the defect as the missing user-scope
disjunct, replacing the claim that matching used project ownership alone.
Clarify whether the 101 Default and 100 real-project figures are total project
counts; otherwise replace them with distinct affected-row counts that reconcile
with the stated 117 affected rows.

In `@docs/GUIDELINES.md`:
- Around line 420-422: Update the Redis failure documentation near the Store
contract to state that the per-process fallback allows clients in multi-replica
deployments to obtain approximately one limit per process. Define the required
alerting or fail-closed policy specifically for authentication endpoints,
including voucher redemption, registration, and forgot-password.
- Line 418: Update the rate-limiter description in GUIDELINES.md to call the
window fixed-window rather than sliding-window, while preserving the existing
endpoint classification, environment configuration, store selection, and header
verification guidance.

---

Nitpick comments:
In @.github/workflows/prod-drift.yml:
- Around line 67-80: Narrow the benchmark exclusion in the drift-check condition
around the git diff regex to the verified
packages/core/generation-uplift/suite-2/** path, unless repository checks prove
the entire subtree is outside package, Docker, and runtime inputs. Update
docs/KNOWN_ISSUES.md at line 406 to document the same actual exclusion path and
supporting evidence.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f29606d-49bd-408d-aac5-b013a3ed8f6b

📥 Commits

Reviewing files that changed from the base of the PR and between 61f3d16 and 9f88d40.

📒 Files selected for processing (7)
  • .github/workflows/prod-drift.yml
  • README.md
  • docs/APPROACH.md
  • docs/DEPLOY_CHECKLIST.md
  • docs/GUIDELINES.md
  • docs/KNOWLEDGE.md
  • docs/KNOWN_ISSUES.md

Comment thread .github/workflows/prod-drift.yml
Comment thread docs/APPROACH.md Outdated
Comment thread docs/APPROACH.md
Comment thread docs/GUIDELINES.md Outdated
Comment thread docs/GUIDELINES.md
Four of five findings on #179 were right. Applied:

- APPROACH: "unbounded auth bypass" overstated it. deploy/Caddyfile
  rate-limits /api/* at the edge to 10 events/IP/s, ordered before
  reverse_proxy, so an attacker was never wholly unbounded. Rescoped to
  an APPLICATION-level bypass — while noting the edge limit is 3+
  orders of magnitude looser than the control it masked (10/second
  against a voucher gate meant to allow 10/hour) and, being per-IP,
  does nothing against a distributed caller. The finding stands; the
  word did not. Fitting catch on a section about framing.

- APPROACH: "matched on project ownership alone" repeated the vague
  mechanism this arc already had to correct once. Now states that
  buildRawProjectFilterV2 resolves visibility from Knowledge.visibility
  plus ownerProjectId and never consults scope. Also disambiguated the
  counts, which read as contradictory: 117 is the repo-wide affected
  set (scope='user' with a non-null ownerProjectId); 101/100 are total
  active rows per project and NOT a partition of it.

- GUIDELINES: the limiter is FIXED-window, not sliding — check() opens
  a fresh bucket once resetAt passes. The old wording gave wrong
  burst-boundary guarantees for auth limits; the real worst case across
  a boundary is ~2x max, which is what you must size against.

- GUIDELINES: documented what the Redis-failure fallback costs. Per-
  process state is right over 500s, but on multi-replica each replica
  counts independently, so a caller spreading load gets ~Nx the
  allowance while Redis is down. Added the log line to alarm on.

Rejected, with evidence: the annotated-tag concern on prod-drift.yml.
Our tags are annotated (git cat-file -t v2.4.0 -> tag), but git diff
peels them — the diff computed from tag names is byte-identical to the
one from `git rev-parse <tag>^{commit}`. No change needed.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqj5y9fT3XPUqnqKyustp9
@bejranonda
bejranonda merged commit f171ccd into main Jul 31, 2026
6 checks passed
@bejranonda
bejranonda deleted the fix/prod-drift-false-positives branch July 31, 2026 19:32
bejranonda added a commit that referenced this pull request Jul 31, 2026
Review pass over the v2.3.0 -> v2.5.2 arc turned up three gaps, all
introduced by that arc.

1. buildKnowledgeWhereV2 and buildRawProjectFilterV2 DISAGREED on the
   no-activeProjectId case. The raw helper admits scope IN
   ('user','global') unconditionally there — added 2026-05-12, on the
   reasoning that with no active project there is no boundary to
   enforce. The Prisma twin gated it on the new opt-in flag. They back
   the same visibility rule on different query surfaces, so a caller's
   results depended on which one their code path happened to hit. Same
   inconsistency class CodeRabbit caught inside the raw helper on #179
   — fixed there, and introduced between the two.

2. buildKnowledgeWhereV2 had ZERO test coverage despite backing the
   knowledge listing route and action-items, and #177 added an
   unreachable branch to it (no caller passes the flag; kra/oracle use
   the raw helper). Six tests now pin: closed by default under an
   active project, opened when opted in, applied to scope="all",
   unconditional with no active project — asserted against the raw
   helper in the same test so they cannot drift apart again — and the
   disjunct pinned to ownerUserId.

3. Documented the behaviour change the parity fix causes at a
   security-reviewed call site. action-items.ts passes projectId as
   string | null; with null it now admits the caller's own user-scoped
   rows. Acceptable — accessibleProjectIds stays [], the query ANDs
   type: action_item, it is pinned to ownerUserId, and action items are
   project-scoped by contract so the set is empty — but it is a real
   change at a spot with an explicit isolation note, so it is stated
   rather than left to be rediscovered.

Also notes on Limit.max that 0 blocks everything and is not a
"disabled" sentinel. Before the atomic rewrite the fresh-bucket path
returned ok:true unconditionally, so max:0 leaked one request per
window; that inconsistency is gone and nothing depended on it.


Claude-Session: https://claude.ai/code/session_01Rqj5y9fT3XPUqnqKyustp9

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

prod-drift: production is behind main

1 participant