Skip to content

[PROJ-1431] Ship isolated shadow-governance reviewer demo backend - #330

Merged
AndrewNordstrom merged 8 commits into
mainfrom
dev/PROJ-1431-recsys-reviewer-demo-path-tells-the-corgi-loop-end-to-end
Jul 10, 2026
Merged

AndrewNordstrom merged 8 commits into
mainfrom
dev/PROJ-1431-recsys-reviewer-demo-path-tells-the-corgi-loop-end-to-end

Conversation

@AndrewNordstrom

Copy link
Copy Markdown
Collaborator

Summary

Builds the isolated backend contract for Corgi's no-login reviewer demo. A visitor can create a shadow session from a production-scored snapshot, cast a demo-only vote, run 24 deterministic synthetic community voters, advance up to ten shadow epochs, inspect the reordered feed, and read post-level receipt math without mutating production governance or the live feed.

Linear: PROJ-1431

Changes

  • Adds the public, rate-limited /api/demo/* Fastify route family with Redis-only sessions, corpora, locks, and idempotency records.
  • Uses production score decompositions and production-faithful trimmed-mean aggregation for one reviewer plus 24 deterministic, history-aware synthetic voters.
  • Freezes each session corpus so rank movement is attributable to policy changes; AppView hydration and public-label filtering fail closed.
  • Adds inspectable shadow receipts, prior/counterfactual rank movement, topic-intent reweighting, five guided epochs, and a ten-epoch cap.
  • Adds a disabled/private Birders feed registry, materializer, and scout without changing the public community-gov feed or publishing a new feed rkey.
  • Moves admin Swagger from /docs to /api/docs so the static public product documentation can own /docs/.
  • Adds integration, route, store, public-view, isolation, community-routing, and contract coverage plus dated lab/evidence notes.

Safety Boundaries

  • Demo state is confined to demo:* Redis keys.
  • No demo writes reach governance_votes, governance_epochs, governance_audit_log, research exports, feed:current, or production snapshot keys.
  • Reviewer actions never call production epoch transitions or the production scoring pipeline.
  • birders-who-code remains disabled and is not advertised by describeFeedGenerator.

Verification

  • npm run verify passed on current origin/main: 118 Vitest files / 1,065 tests, TypeScript, CLI, SDK, fixture, legacy web lint/build, and Next static build.
  • npm run docs:verify passed: 14 tracked docs / 35 Markdown files.
  • git diff --check passed.
  • Same-origin local browser flow completed through epoch 3 at desktop, tablet, and mobile widths with no console errors or horizontal overflow.
  • Local scoped CodeRabbit passes found no unresolved HIGH or MEDIUM findings.

Checklist

  • Tests added/updated and npm run verify passes locally
  • CHANGELOG.md updated under ## [Unreleased]
  • Docs updated and npm run docs:verify passes
  • Single-purpose change; no unrelated churn
  • No secrets, internal infrastructure, or private URLs introduced

@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • New Features

    • Added a rate-limited shadow-governance demo with production-sourced scoring, deterministic synthetic voters, reranked feeds, and inspectable receipts.
    • Added AppView-backed hydration and public-view filtering for demo content, safely redacting hidden/invalid posts.
    • Added Birders feed readiness scouting with an optional materialize mode (disabled/private by default), plus a new CLI script to run the scout.
    • Generalized feed routing/discovery and feed skeleton snapshots to support multiple community feeds with private-feed protections.
  • Documentation

    • Updated Swagger UI guidance to /api/docs and expanded demo/readiness/governance contract docs.
  • Tests

    • Added extensive coverage for demo isolation, routes, weights/math, Birders scouting/materialization, and feed/community registry behavior.

Walkthrough

This pull request adds an isolated /api/demo/* shadow-governance backend with production-scored corpus hydration, deterministic voters, Redis-backed sessions, reranking, and receipts. It also adds a disabled Birders feed registry, namespaced scouting/materialization workflows, CLI support, documentation, and validation coverage.

Changes

Shadow governance demo

Layer / File(s) Summary
Demo contracts and governance math
src/demo/types.ts, src/demo/weights.ts, src/demo/topic-intent.ts, src/governance/aggregation-math.ts, src/scoring/components/relevance.ts, web-next/app/demo/*
Defines backend and web contracts, validates weights and topic intent, implements deterministic trimmed-mean aggregation and shadow scoring, and verifies backend/web contract parity.
Corpus loading and public-view hydration
src/demo/corpus.ts, src/demo/appview.ts, src/demo/public-view.ts, tests/demo-shadow-public-view.test.ts
Loads scored Open Science candidates, hydrates AppView posts in batches, filters hidden content, and supplies degraded fixture data when hydration is insufficient.
Persistence, voters, and service orchestration
src/demo/store.ts, src/demo/synthetic-voters.ts, src/demo/service.ts, tests/demo-shadow-store.test.ts, tests/demo-shadow-weights.test.ts
Adds validated Redis and memory stores, deterministic synthetic voter generation, session locking and idempotency, epoch advancement, reranking, and receipt construction.
Demo routes and server integration
src/demo/routes.ts, src/feed/server.ts, src/feed/rate-limit-config.ts, tests/demo-shadow-routes.test.ts, tests/rate-limit-config.test.ts
Registers validated demo endpoints with envelopes, error mapping, idempotency headers, route-specific limits, dependency injection, Swagger tagging, and lifecycle/concurrency tests.
Demo documentation and evidence
CHANGELOG.md, README.md, docs/lab/*demo*, docs/RECSYS_VALIDATION_EVIDENCE.md, docs/dev-journal.md
Documents the contract, isolation rules, corpus provenance, readiness results, API documentation path, verification evidence, and implementation journal entries.

Birders feed readiness

Layer / File(s) Summary
Community registry and snapshot isolation
src/feed/community-registry.ts, src/feed/snapshot-cache.ts, src/feed/routes/describe-generator.ts, src/feed/routes/feed-skeleton.ts, tests/feed-community-registry.test.ts, tests/feed-skeleton-*.test.ts
Adds enabled/public and disabled/private community entries, community-scoped Redis keys and snapshots, dynamic feed discovery, disabled-feed rejection, private-feed authorization, and registry/snapshot tests.
Birders scout and materialization workflow
src/feed/community-materializer.ts, src/feed/birders-scout-command.ts, scripts/birders-feed-scout.ts, package.json, tests/birders-*.test.ts
Queries candidate and bridge terms, computes readiness metrics, materializes Birders-only Redis state transactionally, and adds read-only/default CLI scouting with JSON and formatted output.
Birders readiness evidence
docs/lab/birders-feed-readiness.md, docs/RECSYS_VALIDATION_EVIDENCE.md, docs/dev-journal.md
Records thresholds, production read-only measurements, safety boundaries, verification receipts, and the decision to defer publication.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

  • PROJ-1431: Directly implements the isolated /api/demo/* shadow-governance reviewer demo backend and its corpus, voting, ranking, receipt, and isolation flow.

Suggested labels: documentation, javascript

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.19% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: an isolated shadow-governance reviewer demo backend.
Description check ✅ Passed The description is directly aligned with the changeset and describes the demo backend, isolation, and tests.
Linked Issues check ✅ Passed The PR implements the required /api/demo flow, Redis isolation, deterministic voters, public-view hydration, reranked feeds, receipts, and tests.
Out of Scope Changes check ✅ Passed The added docs, feed registry, and tests appear to support the demo and isolation work, with no clearly unrelated feature churn.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev/PROJ-1431-recsys-reviewer-demo-path-tells-the-corgi-loop-end-to-end
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch dev/PROJ-1431-recsys-reviewer-demo-path-tells-the-corgi-loop-end-to-end

Warning

Review ran into problems

🔥 Problems

These MCP integrations need to be re-authenticated in the Integrations settings: Notion


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

@coderabbitai coderabbitai Bot added documentation Improvements or additions to documentation javascript Pull requests that update javascript code labels Jul 10, 2026

@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: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/feed/routes/feed-skeleton.ts (1)

269-301: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Enforce community.public in the serving path.

publicFeedUris() only hides private feeds from discovery. This handler still serves any enabled community whenever FEED_PRIVATE_MODE is off, so public: false has no request-time effect. A future enabled + public:false community would be publicly fetchable unless this gate also checks !community.public.

🔒 Proposed fix
-      if (config.FEED_PRIVATE_MODE) {
+      if (config.FEED_PRIVATE_MODE || !community.public) {
         const viewerDid = await verifyFeedRequesterDid(authHeader);
         if (!viewerDid) return reply.send({ feed: [] });
         const approved = await isParticipantApproved(viewerDid);
         if (!approved) return reply.send({ feed: [] });
         precomputedViewerDid = viewerDid;
       }

Add coverage for:

  • enabled/public:false with FEED_PRIVATE_MODE=false → empty feed for unauthenticated/unapproved callers
  • enabled/public:true with FEED_PRIVATE_MODE=false → normal feed response
🤖 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 `@src/feed/routes/feed-skeleton.ts` around lines 269 - 301, Enforce the
community’s public visibility in the serving path: update the gate after
isFeedCommunityServable in the feed handler to treat enabled communities with
public === false as inaccessible when FEED_PRIVATE_MODE is disabled, returning
an empty feed for unauthenticated or unapproved callers. Preserve normal
responses for enabled, public communities, and add coverage for both scenarios.
🤖 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 `@scripts/birders-feed-scout.ts`:
- Around line 47-50: Update the finally cleanup in the script’s main execution
flow so failures from redis.disconnect() or db.end() cannot replace the original
try-block error; perform cleanup defensively by catching and logging cleanup
errors while preserving the initial failure, and ensure both cleanup operations
are still attempted.

In `@src/demo/corpus.ts`:
- Around line 232-264: Sequential score reads in buildScoredCorpusItems increase
demo latency; parallelize them while respecting backend capacity. Refactor the
loop to issue readScore calls concurrently, preferably with Promise.all over
bounded chunks of roughly 10–20 rows if the Redis/DB pool cannot safely handle
all candidates at once, then preserve the existing filtering and item
construction behavior for missing or non-finite scores.

In `@src/demo/service.ts`:
- Around line 143-158: Session creation currently persists the full corpus
inside ShadowDemoSessionState, causing each anonymous session write to duplicate
substantial Redis data. In the session state construction and store.writeSession
flow, replace the embedded corpus with a reference to a shared corpus key or
otherwise minimize the persisted record, and ensure reads/mutations resolve that
reference; also add monitoring for demo:* key count/memory and enforce a per-IP
concurrent-session limit.

In `@src/demo/store.ts`:
- Around line 198-213: Remove the corpusKey setex operation from writeSession,
leaving only the sessionKey persistence in the Redis transaction. Update or add
tests for writeSession to verify only the session blob is written and no
demo:corpus entry is created.

In `@src/demo/weights.ts`:
- Around line 34-58: Replace the manual checks in validateShadowWeights with a
Zod schema for the five SHADOW_DEMO_SIGNAL_KEYS, requiring finite non-negative
numbers and a sum within SUM_TOLERANCE of 1. Use safeParse or parse at the
external-input boundary in routes.ts, map validation failures to the existing
error behavior, and have validateShadowWeights return the parsed typed result so
the schema is the single source of truth.

In `@src/feed/community-materializer.ts`:
- Around line 119-137: Handle partial MULTI/EXEC failures explicitly in the
materialization transaction around the ranked-candidate writes and snapshot
metadata updates. When results contain a failed command, identify the affected
command and perform best-effort cleanup or reconciliation of the Birders keys
before throwing; alternatively, include which writes may have committed in the
error. Preserve the existing queue-abort handling and use the transaction
construction/materialization symbols to keep the mitigation scoped to this
namespace.
- Around line 491-505: Prevent nullableFiniteInteger() from converting null into
0: explicitly return null for null input before calling finiteNumber(), while
preserving validation for strings and numbers. Add a regression test covering
readCommunitySnapshot() with active_epoch_id: null, asserting reportStatus()
returns unavailable and reportWarnings() includes the missing active production
epoch warning.

In `@tests/birders-feed-materializer.test.ts`:
- Around line 161-204: Add a separate test for a genuinely missing active epoch,
distinct from the existing zero-candidate case: configure the mocked database
row with active_epoch_id: null, invoke scoutCommunityFeed, and assert the report
status is 'unavailable' and warnings include 'No active production epoch was
available for Birders materialization.'. Ensure the nullable epoch coercion used
by scoutCommunityFeed/materialization preserves null rather than converting it
to 0.

In `@tests/birders-scout-command.test.ts`:
- Around line 29-35: Add coverage in the existing invalid-options test for the
missing-value branches of parseBirdersScoutArgs by asserting --limit and
--window-hours throw their respective “requires a value” messages, and verify
the -h alias throws BirdersScoutHelpRequested.
- Around line 1-9: Add direct test coverage for the exported
renderBirdersMaterializeResult function in the birders scout command tests,
using a representative MaterializedCommunityFeedResult and asserting the
rendered output contains “Materialized ranked posts:” and “Redis keys written:”.

In `@tests/demo-shadow-isolation.test.ts`:
- Around line 23-42: Pair the static checks in the tests covering
demoSourceText() with a behavioral isolation test, such as in the demo
shadow-store tests, that exercises the demo store using a mocked or spied
production database client and asserts no writes occur to governance, feed,
audit, or export tables. Keep these regex assertions as a cheap first-line
guard, but ensure the behavioral test detects indirect calls through renamed,
wrapped, or re-exported helpers.

In `@tests/demo-shadow-public-view.test.ts`:
- Around line 31-220: Extend the existing shadow public-view Vitest suite with
focused cases for empty and exactly 25 URI inputs to the batching/URL helper,
null and undefined inputs to publicPostFromAppView, malformed JSON from the
AppView fetch response, and abort/timeout behavior that verifies the fetch mock
receives an AbortSignal. Use the existing hydration and URL-builder symbols,
avoid duplicating the malformed-post and HTTP-503 coverage, and assert the
expected fail-closed or transport-error results.

In `@tests/demo-shadow-routes.test.ts`:
- Around line 568-604: Add edge-case tests in the existing shadow route suite:
exercise two overlapping vote requests for one session and assert one returns
409 with “session is busy”; submit all-zero weights and assert the documented
validation error status; and use a store whose readSession returns malformed
JSON, asserting a 500 response with the generic demo error body. Reuse the
existing app setup and route symbols, and ensure concurrency is genuinely
overlapping rather than sequential.

In `@tests/demo-shadow-store.test.ts`:
- Around line 40-52: Update RedisDemoStore.writeSession to handle partial
MULTI/EXEC failures: when the corpus SETEX result fails after the session SETEX
succeeds, delete or invalidate the demo:session:* key before rethrowing the
transaction error. Ensure the existing “Redis transaction failed” error still
includes the underlying failure, and keep the partial-failure test asserting the
cleanup behavior.

In `@tests/demo-shadow-weights.test.ts`:
- Around line 44-232: Add tests in the existing “shadow demo weight math” suite
for both uncovered edge paths: assert aggregateShadowVotes([]) throws an error
matching “zero shadow demo votes”, and call scoreFromRawWeights with an empty
topicWeights object, asserting effectiveRawScores.relevance remains equal to
rawScores.relevance.

In `@tests/feed-community-registry.test.ts`:
- Around line 14-51: Expand the “feed community registry” tests beyond happy
paths: add assertions that resolveFeedCommunityByRkey and
resolveFeedCommunityByUri return null for unknown values, and that
publicFeedUris and the resolver handle an empty communities array. Add coverage
for feedUriForRkey and directly verify isFeedCommunityServable returns true for
enabled and false for disabled communities, using the existing registry fixtures
and publisher DID.

In `@tests/feed-skeleton-validation.test.ts`:
- Around line 258-323: Extend tests in the existing feed skeleton suite to cover
cursor-based pagination for a non-community-gov community, verifying the
subsequent page uses getCommunityFeedSnapshotById and the namespaced
snapshot-by-id Redis key. Add a test for an enabled but non-public Birders
configuration using feedUriForCommunity, asserting the unauthenticated request
returns the expected empty feed or auth challenge after public access is
enforced; include empty or boundary pagination behavior where appropriate.

---

Outside diff comments:
In `@src/feed/routes/feed-skeleton.ts`:
- Around line 269-301: Enforce the community’s public visibility in the serving
path: update the gate after isFeedCommunityServable in the feed handler to treat
enabled communities with public === false as inaccessible when FEED_PRIVATE_MODE
is disabled, returning an empty feed for unauthenticated or unapproved callers.
Preserve normal responses for enabled, public communities, and add coverage for
both scenarios.
🪄 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: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 840777fc-1f1a-465d-8a42-19d7826a37ef

📥 Commits

Reviewing files that changed from the base of the PR and between ac0d57c and 6d433f8.

📒 Files selected for processing (43)
  • CHANGELOG.md
  • README.md
  • docs/RECSYS_VALIDATION_EVIDENCE.md
  • docs/dev-journal.md
  • docs/lab/birders-feed-readiness.md
  • docs/lab/demo-shadow-governance-contract.md
  • docs/lab/open-science-demo-readiness.md
  • package.json
  • scripts/birders-feed-scout.ts
  • src/demo/appview.ts
  • src/demo/corpus.ts
  • src/demo/public-view.ts
  • src/demo/routes.ts
  • src/demo/service.ts
  • src/demo/store.ts
  • src/demo/synthetic-voters.ts
  • src/demo/topic-intent.ts
  • src/demo/types.ts
  • src/demo/weights.ts
  • src/feed/birders-scout-command.ts
  • src/feed/community-materializer.ts
  • src/feed/community-registry.ts
  • src/feed/rate-limit-config.ts
  • src/feed/routes/describe-generator.ts
  • src/feed/routes/feed-skeleton.ts
  • src/feed/server.ts
  • src/feed/snapshot-cache.ts
  • src/governance/aggregation-math.ts
  • src/governance/aggregation.ts
  • src/scoring/components/relevance.ts
  • tests/birders-feed-materializer.test.ts
  • tests/birders-scout-command.test.ts
  • tests/demo-shadow-isolation.test.ts
  • tests/demo-shadow-public-view.test.ts
  • tests/demo-shadow-routes.test.ts
  • tests/demo-shadow-store.test.ts
  • tests/demo-shadow-weights.test.ts
  • tests/feed-community-registry.test.ts
  • tests/feed-skeleton-validation.test.ts
  • tests/rate-limit-config.test.ts
  • tests/web-next-shadow-demo-contract.test.ts
  • web-next/app/demo/shadow-demo-contract.ts
  • web-next/app/demo/shadow-demo-contract.type-test.ts

Comment thread scripts/birders-feed-scout.ts
Comment thread src/demo/corpus.ts
Comment thread src/demo/service.ts
Comment thread src/demo/store.ts
Comment thread src/demo/weights.ts
Comment thread tests/demo-shadow-routes.test.ts
Comment thread tests/demo-shadow-store.test.ts Outdated
Comment thread tests/demo-shadow-weights.test.ts
Comment thread tests/feed-community-registry.test.ts
Comment thread tests/feed-skeleton-validation.test.ts
@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai coderabbitai Bot added the github_actions Pull requests that update GitHub Actions code label Jul 10, 2026

@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: 2

♻️ Duplicate comments (1)
tests/demo-shadow-weights.test.ts (1)

216-242: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Still missing: scoreFromRawWeights empty-topicWeights fallback test.

The past review flagged two untested branches — zero votes (now fixed at lines 74-76) and scoreFromRawWeights falling back to unchanged rawScores.relevance when topicIntent.topicWeights is empty. Only the first was added; this fallback branch is still only exercised via the non-empty TOPIC_INTENT fixture.

🧪 Suggested addition
it('falls back to raw relevance when topic intent has no weights', () => {
  const scored = scoreFromRawWeights(
    { recency: 0.5, engagement: 0.8, bridging: 0.25, source_diversity: 1, relevance: 0.4 },
    { recency: 0.2, engagement: 0.3, bridging: 0.1, source_diversity: 0.1, relevance: 0.3 },
    { 'science-research': 0.8 },
    { topicWeights: {} }
  );
  expect(scored.effectiveRawScores.relevance).toBe(0.4);
});

As per path instructions, "Check for tests that only cover the happy path. Suggest edge cases: empty inputs, boundary values, null/undefined... and error conditions."

🤖 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 `@tests/demo-shadow-weights.test.ts` around lines 216 - 242, Add a test
covering the empty-topicWeights fallback in scoreFromRawWeights, passing a topic
intent with topicWeights: {} and asserting effectiveRawScores.relevance remains
equal to the raw relevance value; keep the existing non-empty topic intent
contribution test unchanged.

Source: Path instructions

🤖 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 `@src/demo/store.ts`:
- Around line 198-199: Refactor ShadowDemoSessionState and the persistence flow
in writeSession and its corresponding read method so Redis sessions store only a
corpusId or content-hash/version reference, while the frozen ShadowDemoCorpus is
written once to a shared record and hydrated on reads to preserve the existing
returned payload shape. Update session creation and vote/epoch update paths to
reuse the shared corpus record, and add tests verifying multiple sessions share
one corpus blob and updates do not rewrite corpus bytes.

In `@tests/feed-skeleton-auth.test.ts`:
- Around line 158-181: Add private-community authorization tests alongside the
existing anonymous case: configure a valid requester DID with
isParticipantApprovedMock returning false, then assert a 200 empty feed, no
redisMock.zrevrange call, and approval verification is invoked; add a
DID-verification-null case asserting an empty feed, no Redis access, and
isParticipantApprovedMock is not called. Ensure verifyFeedRequesterDidMock and
related mocks reflect the behavior implemented in jwt-verifier.ts.

---

Duplicate comments:
In `@tests/demo-shadow-weights.test.ts`:
- Around line 216-242: Add a test covering the empty-topicWeights fallback in
scoreFromRawWeights, passing a topic intent with topicWeights: {} and asserting
effectiveRawScores.relevance remains equal to the raw relevance value; keep the
existing non-empty topic intent contribution test unchanged.
🪄 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: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5c3715ba-4976-4bf1-98e0-01ca07d6fecb

📥 Commits

Reviewing files that changed from the base of the PR and between 6d433f8 and 766210c.

📒 Files selected for processing (10)
  • scripts/birders-feed-scout.ts
  • src/demo/corpus.ts
  • src/demo/store.ts
  • src/feed/community-materializer.ts
  • src/feed/routes/feed-skeleton.ts
  • tests/birders-feed-materializer.test.ts
  • tests/demo-shadow-store.test.ts
  • tests/demo-shadow-weights.test.ts
  • tests/feed-community-registry.test.ts
  • tests/feed-skeleton-auth.test.ts

Comment thread src/demo/store.ts Outdated
Comment thread tests/feed-skeleton-auth.test.ts
@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai coderabbitai Bot removed the github_actions Pull requests that update GitHub Actions code label Jul 10, 2026
@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@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: 2

🤖 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 `@src/feed/snapshot-cache.ts`:
- Around line 240-259: Protect the fallback zrevrange in createCurrentSnapshot
with error handling equivalent to the metric write: catch Redis read failures,
log the degraded fallback outcome, and return null instead of allowing the
exception to escape. First verify the caller’s handling, but ensure this
function itself safely handles failures while reading spec.fallbackSortedSetKey.
- Around line 8-9: The fallback Redis keys are hard-coded global values instead
of being scoped per community. Remove FEED_LAST_KNOWN_GOOD_KEY and
FEED_LAST_KNOWN_GOOD_FALLBACK_TOTAL_KEY, add corresponding last-known-good key
fields to each community’s redis configuration (such as lastKnownGood and
lastKnownGoodFallbackTotal), and update FeedSnapshotSpec and all references
around the fallback logic to read those configured values.
🪄 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: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3d97ee4f-ab07-4fa4-b6d3-45f04d9d9212

📥 Commits

Reviewing files that changed from the base of the PR and between 766210c and cf66a0e.

📒 Files selected for processing (2)
  • docs/dev-journal.md
  • src/feed/snapshot-cache.ts

Comment thread src/feed/snapshot-cache.ts Outdated
Comment thread src/feed/snapshot-cache.ts
@cursor

cursor Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/demo-shadow-weights.test.ts (2)

45-72: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a negative-weight boundary case.

The suite validates sum≠1, NaN, and all-zero, but not a negative component that still sums to 1.0 (e.g. recency: -0.5, engagement: 1.5). The test name implies "non-negative" is an enforced property, but no test proves validateShadowWeights rejects it.

it('rejects negative weights even when they sum to one', () => {
  expect(() =>
    validateShadowWeights({
      recency: -0.5,
      engagement: 1.5,
      bridging: 0,
      source_diversity: 0,
      relevance: 0,
    })
  ).toThrow(/non-negative|negative/);
});

As per path instructions, **/*.test.ts files should cover boundary values and error conditions.

🤖 Prompt for AI Agents
In `tests/demo-shadow-weights.test.ts`, inside the `'validates finite
non-negative weights that sum to one'` test (around lines 45-72), add a case
asserting validateShadowWeights throws for a weights object containing a
negative component whose total still sums to 1.0. Confirm the thrown message
pattern against the actual guard in src/demo/weights.ts.
🤖 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 `@tests/demo-shadow-weights.test.ts` around lines 45 - 72, Add a boundary
assertion in the “validates finite non-negative weights that sum to one” test
for validateShadowWeights, using a weights object with recency -0.5 and
engagement 1.5 while the total remains 1.0. Assert that it throws using the
actual negative/non-negative error message pattern from validateShadowWeights.

Source: Path instructions


89-124: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover the exact trimming boundary (9 vs. 10 votes).

Small-electorate coverage uses 2 votes, and trimming coverage jumps straight to 10. The off-by-one boundary at 9 votes (should still be trimmed_mean_no_trim_under_10 with trimCount: 0) is untested, leaving the < 10 threshold unverified for the value closest to the switch.

it('does not trim exactly nine votes', () => {
  const summary = aggregateShadowVotes(Array(9).fill(RECENCY_ONLY));
  expect(summary.aggregateMethod).toBe('trimmed_mean_no_trim_under_10');
  expect(summary.trimCount).toBe(0);
});

As per path instructions, suggest boundary-value edge cases for **/*.test.ts files.

🤖 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 `@tests/demo-shadow-weights.test.ts` around lines 89 - 124, Add a
boundary-value test in the aggregateShadowVotes test suite for exactly nine
votes, using Array(9).fill(RECENCY_ONLY); assert aggregateMethod is
trimmed_mean_no_trim_under_10 and trimCount is 0 to verify the threshold remains
exclusive below ten votes.

Source: Path instructions

♻️ Duplicate comments (1)
tests/feed-skeleton-auth.test.ts (1)

158-182: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Missing assertion that approval check is short-circuited for anonymous callers.

The prior review specifically requested asserting isParticipantApprovedMock is not called when DID verification returns null. That assertion is still absent here — only zrevrange and verifyFeedRequesterDidMock are checked. Without it, a regression that calls isParticipantApproved(null) (or skips the null check entirely) would pass this test silently.

🤖 Prompt for AI Agents
In `tests/feed-skeleton-auth.test.ts` in the test `'does not serve an enabled
private community to an anonymous caller'` (around lines 158-182), add:
expect(isParticipantApprovedMock).not.toHaveBeenCalled();
right after the existing `redisMock.zrevrange` assertion, to confirm the
fail-closed path never reaches participant approval when DID is null.

Also worth confirming: this test doesn't override config.FEED_PRIVATE_MODE, so it runs with whatever originalPrivateMode defaults to. If the private-community gate in feed-skeleton.ts is conditioned on the global FEED_PRIVATE_MODE flag rather than purely on community.public, this test (and the unapproved-caller test below) may not actually exercise the fail-closed path when the flag is off in the default environment — which is precisely the gap flagged in the linked security review of feed-skeleton.ts.

#!/bin/bash
set -euo pipefail
fd config.ts src --exec cat -n {} \; | rg -n "FEED_PRIVATE_MODE" -A3 -B3
fd feed-skeleton.ts src --exec cat -n {} \;
🤖 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 `@tests/feed-skeleton-auth.test.ts` around lines 158 - 182, In the test “does
not serve an enabled private community to an anonymous caller,” add an
expectation immediately after the existing redisMock.zrevrange assertion that
isParticipantApprovedMock was not called. Also ensure the test explicitly
enables the relevant FEED_PRIVATE_MODE configuration when required so the
private-community authorization path is exercised reliably.
🤖 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.

Outside diff comments:
In `@tests/demo-shadow-weights.test.ts`:
- Around line 45-72: Add a boundary assertion in the “validates finite
non-negative weights that sum to one” test for validateShadowWeights, using a
weights object with recency -0.5 and engagement 1.5 while the total remains 1.0.
Assert that it throws using the actual negative/non-negative error message
pattern from validateShadowWeights.
- Around line 89-124: Add a boundary-value test in the aggregateShadowVotes test
suite for exactly nine votes, using Array(9).fill(RECENCY_ONLY); assert
aggregateMethod is trimmed_mean_no_trim_under_10 and trimCount is 0 to verify
the threshold remains exclusive below ten votes.

---

Duplicate comments:
In `@tests/feed-skeleton-auth.test.ts`:
- Around line 158-182: In the test “does not serve an enabled private community
to an anonymous caller,” add an expectation immediately after the existing
redisMock.zrevrange assertion that isParticipantApprovedMock was not called.
Also ensure the test explicitly enables the relevant FEED_PRIVATE_MODE
configuration when required so the private-community authorization path is
exercised reliably.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1a0a6b9d-529c-4921-a08b-6585a426c807

📥 Commits

Reviewing files that changed from the base of the PR and between cf66a0e and b56d047.

📒 Files selected for processing (6)
  • src/demo/store.ts
  • src/feed/community-registry.ts
  • src/feed/snapshot-cache.ts
  • tests/demo-shadow-store.test.ts
  • tests/demo-shadow-weights.test.ts
  • tests/feed-skeleton-auth.test.ts

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/demo-shadow-weights.test.ts (2)

158-196: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Verify seed and epoch sensitivity, not only repeatability.

The test proves identical inputs produce identical votes, but an implementation that ignores seed and epochId would still pass. Generate votes with a different seed and epoch, then assert the generated vote payloads differ to protect session and epoch isolation.

AI agent prompt
Keep the existing identical-input repeatability assertion. Add one generated
vote set with a different seed and another with a different epochId, then assert
each differs from the baseline payload.
🤖 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 `@tests/demo-shadow-weights.test.ts` around lines 158 - 196, Extend the test
for createSyntheticVoterVotes by generating one additional vote set with a
different seed and another with a different epochId, while keeping the existing
repeated-input equality assertion. Assert that each altered-input result differs
from syntheticVotes, comparing the generated vote payloads directly.

Source: Path instructions


45-81: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add malformed runtime-value cases to the validator suite.

The test covers NaN, negative values, and invalid sums, but not Infinity, -Infinity, or missing/null components. These can appear after deserialization despite the TypeScript type; add explicit runtime-invalid cases and assert the intended validation errors.

AI agent prompt
Extend validateShadowWeights tests with Infinity, -Infinity, undefined, and null
component values. Cast malformed objects only at the test boundary and assert
the validator rejects them with the appropriate finite/type error.
🤖 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 `@tests/demo-shadow-weights.test.ts` around lines 45 - 81, Add runtime-invalid
cases to the validateShadowWeights test in the “validates finite non-negative
weights that sum to one” block for Infinity, -Infinity, undefined, and null
component values. Cast malformed objects only at the test boundary, and assert
Infinity values fail with the finite error while undefined/null values fail with
the intended type or validation error.

Source: Path instructions

🤖 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.

Outside diff comments:
In `@tests/demo-shadow-weights.test.ts`:
- Around line 158-196: Extend the test for createSyntheticVoterVotes by
generating one additional vote set with a different seed and another with a
different epochId, while keeping the existing repeated-input equality assertion.
Assert that each altered-input result differs from syntheticVotes, comparing the
generated vote payloads directly.
- Around line 45-81: Add runtime-invalid cases to the validateShadowWeights test
in the “validates finite non-negative weights that sum to one” block for
Infinity, -Infinity, undefined, and null component values. Cast malformed
objects only at the test boundary, and assert Infinity values fail with the
finite error while undefined/null values fail with the intended type or
validation error.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c9b3239d-88b6-47b9-8370-fb97e358dd78

📥 Commits

Reviewing files that changed from the base of the PR and between b56d047 and 8f696cc.

📒 Files selected for processing (2)
  • tests/demo-shadow-weights.test.ts
  • tests/feed-skeleton-auth.test.ts

@AndrewNordstrom AndrewNordstrom added the coderabbit:exempt Temporary CodeRabbit exemption with audit note label Jul 10, 2026
@AndrewNordstrom

Copy link
Copy Markdown
Collaborator Author

Applying the audited coderabbit:exempt path only to clear superseded review residue. CodeRabbit reviewed current head 8f696cc successfully; the repo wrapper reports zero current-head findings and 11 historical threads whose original commits are no longer on the current head. No current-head review is being bypassed.

@AndrewNordstrom
AndrewNordstrom merged commit 69b0b69 into main Jul 10, 2026
21 of 25 checks passed
@AndrewNordstrom
AndrewNordstrom deleted the dev/PROJ-1431-recsys-reviewer-demo-path-tells-the-corgi-loop-end-to-end branch July 10, 2026 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

coderabbit:exempt Temporary CodeRabbit exemption with audit note documentation Improvements or additions to documentation javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant