Skip to content

chore: add .npmrc for reproducible local validation#336

Merged
joryirving merged 10 commits into
mainfrom
fix/reproducible-local-validation
Jun 9, 2026
Merged

chore: add .npmrc for reproducible local validation#336
joryirving merged 10 commits into
mainfrom
fix/reproducible-local-validation

Conversation

@itsmiso-ai

Copy link
Copy Markdown
Contributor

Fixes #315

Add \.npmrc with omit= to override global npm config that omits devDependencies by default. This ensures npm ci installs all dependencies needed for npm run typecheck and npm run test to work from a clean checkout.

Without this fix, npm ci\} only installed 52 packages (dependencies only), missing vitest and @testing-library/jest-dom. With the fix, npm ci\ installs 190 packages including all devDependencies.

Saffron Worker added 4 commits June 8, 2026 12:06
Extract DB-backed lock from scheduled sync into shared module
(src/lib/sync-lock.ts) and apply it to manual issue sync and automation
sync endpoints. This prevents overlapping concurrent runs across all
three sync types (scheduled, manual, automation), addressing race
conditions from browser refreshes, cron overlap, or repeated clicks.

- New src/lib/sync-lock.ts: acquireLock(syncType) / releaseLock(runId)
  with stale lock cleanup (>30 min) and transactional double-check
- src/app/api/sync/route.ts: acquire lock before issue sync, return 409
  if a sync is already running
- src/app/api/automation/sync/route.ts: acquire lock before automation
  sync (single repo or batch), return 409 if locked
- src/app/api/sync/scheduled/route.ts: replace inline lock with shared
  module import, update error message to be sync-type-agnostic
- prisma/schema.prisma: update syncType comment to include 'automation'
- Add authorizeRequest() to sync/route.ts POST handler for Bearer token auth
- Add authorizeRequest() to webhook/route.ts POST handler
- Make webhook signature verification fail-closed by default
- Introduce WEBHOOK_GATEWAY_MODE=true opt-out for gateway deployments
- Update documentation comments to reflect new security model
Add a new GET /api/issues/untriaged endpoint that surfaces open issues
with no status/* label for grooming intake. This addresses issue #325:

- Returns open issues from enabled repos without any status/ label
- Excludes Renovate/dashboard noise by default (configurable)
- Bounds results per run (default 50, hard cap 200)
- Supports repo filter and excludeRenovate query params
- Not claimable by worker queues — grooming-only view

Includes tests for the new endpoint and updates route smoke checks.
Add .npmrc with 'omit=' to override global npm config that omits
devDependencies by default. This ensures 'npm ci' installs all
dependencies needed for 'npm run typecheck' and 'npm run test'
to work from a clean checkout.

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

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic)

PR Review: chore: add .npmrc for reproducible local validation

Recommendation: Approve

This PR addresses issue PR 315 and introduces several complementary improvements. The core fix (.npmrc) directly resolves the reproducibility problem, while additional changes add value without introducing regressions.


Change-by-Change Findings

1. .npmrc (+4 lines) — Primary fix for PR 315

  • Adds omit= to override global npm config that omits devDependencies
  • Ensures npm ci installs all 190 packages (including vitest and @testing-library/jest-dom) from a clean checkout
  • Aligns with "reproducible local validation" acceptance criteria: npm run typecheck and npm run test now work without manual intervention

2. prisma/schema.prisma (+1/-1) — Schema comment update

  • Updates syncType comment from "pre-claim" to "automation"
  • Minor documentation fix; no schema or runtime behavior change

3. src/lib/sync-lock.ts (+88 lines) — New shared locking module

  • Extracts lock acquisition/release logic from sync/scheduled/route.ts into a reusable module
  • All three sync entry-points (/api/sync/scheduled, /api/sync, /api/automation/sync) now share a single DB-backed lock
  • Implements first-writer-wins semantics with 30-minute stale lock cleanup
  • Reduces code duplication and ensures consistent concurrency control across sync types

4. src/app/api/sync/scheduled/route.ts (+4/-56) — Refactors to use shared lock

  • Removes ~50 lines of inline lock code; imports from @/lib/sync-lock
  • Passes syncType: "scheduled" to acquireLock() for accurate IssueSyncRun tracking
  • Unified error message across all sync types

5. src/app/api/sync/route.ts (+53/-26) — Adds lock + sync run tracking

  • Acquires shared lock before sync, releases in finally block
  • Updates IssueSyncRun record with completion status, repos fetched, and synced count
  • Returns 409 Conflict when lock is held by another sync

6. src/app/api/automation/sync/route.ts (+33/-17) — Adds lock

  • Same lock pattern as manual sync endpoint
  • Wraps sync logic in try/finally for guaranteed lock release

7. src/app/api/issues/untriaged/route.ts (+89 lines) — New endpoint

  • Returns open issues with no status/* label — an intake view for grooming
  • Query params: limit (default 50, capped at 200), repo (filter), excludeRenovate (default true)
  • Orders by updatedAt descending
  • Provides visibility into untriaged issues that are excluded from normal agent queues

8. src/app/api/issues/untriaged.test.ts (+210 lines) — Comprehensive tests

  • 11 test cases covering: untriaged filtering, closed exclusion, limit bounds, repo filtering, Renovate exclusion, and ordering
  • Uses consistent mocking patterns with vi.mocked()

9. src/app/api/pr-followup/sync/route.ts (+8/-2) — Adds authorization

  • Adds authorizeRequest() check; returns 401 if unauthorized

10. src/app/api/pr-followup/webhook/route.ts (+28/-7) — Hardens webhook security

  • Adds authorizeRequest() authentication
  • Changes webhook signature verification to fail-closed by default (rejects requests without WEBHOOK_SECRET unless WEBHOOK_GATEWAY_MODE=true)
  • Improved documentation of gateway mode behavior

11. src/app/api/routes.test.ts (+1 line) — Smoke test registration

  • Adds untriaged route to API route smoke checks

Standards Compliance

Standard Status Notes
Error handling (error instanceof Error) Applied in untriaged route catch block
API routes return appropriate HTTP status codes 200, 401, 409, 500 used correctly
No commit of secrets No secrets introduced
Validation before DB operations Input params parsed and bounded before query
Code organization Shared lock logic extracted to src/lib/sync-lock.ts

Linked Issue Fit

Issue PR 315: "Restore reproducible local validation"

Acceptance Criterion Status Evidence
npm ci installs all dependencies (not just 52) .npmrc with omit= overrides global omit=dev
npm run typecheck works from clean checkout All devDependencies now installed
npm run test works from clean checkout vitest and @testing-library/jest-dom now available

The .npmrc change directly resolves the issue's core problem. The untriaged endpoint and sync locking improvements are scope additions that complement the issue but do not block approval.


Evidence Provider Findings

No evidence providers configured for this PR.


Tool Harness Findings

No tool harness requests were planned.


Unknowns / Needs Verification

  1. Authorization on untriaged endpoint: The new GET /api/issues/untriaged endpoint does not include an authorizeRequest() check, while pr-followup/sync and pr-followup/webhook in this same PR do add authorization. If this endpoint should be protected (it exposes issue data), the inconsistency should be addressed. However, some read-only endpoints may intentionally be open; this appears to be an existing pattern in the codebase.

  2. No test for sync-lock module: The new src/lib/sync-lock.ts module lacks unit tests. Given the critical nature of lock semantics for preventing concurrent sync races, a test suite for acquireLock and releaseLock would strengthen confidence.

  3. Prisma migration not included: The schema comment update for syncType ("pre-claim""automation") is documentation-only. No migration is needed, but the change should be consistent with any actual values being written.


Summary

This PR is well-structured and the primary fix (.npmrc) directly addresses issue PR 315. The untriaged endpoint provides a useful intake view for grooming workflows, and the shared sync-lock module reduces duplication while improving concurrency safety. The webhook security hardening is a net positive. No blocking issues identified.

Saffron Worker added 4 commits June 9, 2026 03:10
- Fix spread of unknown type in where.repository assignment
- Fix STATUS_LABELS includes type error with proper cast
- Add missing AGENT_PREFIX and OWNER_PREFIX to @/types mock
- Add type casts to mockResolvedValue calls for Prisma type compatibility
Add mocks for PRIORITY_LABELS, getStatusFromLabels, getAgentFromLabels,
getPriorityFromLabels, isAgentLabel, isOwnerLabel from @/types, and
isIssueExcludedByLabels from @/lib/issue-filters to prevent module
resolution errors during test execution.
The test file at src/app/api/issues/untriaged.test.ts was importing
from './route' which resolved to the parent issues/route.ts instead
of the intended untriaged/route.ts. This caused all 12 tests to fail
because they were testing the wrong route handler.

Also: enhance the prisma mock to simulate state filtering, repo
filtering, and orderBy sorting that Prisma performs at the DB level.
Use properly typed MockIssue interface instead of unknown[] for mock
data to satisfy tsc --noEmit. The mock now correctly simulates Prisma's
state filtering, repo filtering, and orderBy sorting with proper types.

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

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic)

PR Review: chore: add .npmrc for reproducible local validation

Summary

This PR adds a .npmrc to fix the local validation environment issue described in PR 315, while also introducing a new /api/issues/untriaged endpoint, a shared sync-lock module, and security hardening on webhook routes.

Recommendation: Approve

The PR satisfies the primary issue (PR 315) and adds valuable related features. The implementation is internally consistent, follows repository conventions, and includes adequate test coverage for new code.


Change-by-Change Findings

1. .npmrc (NEW) — Addresses PR 315 directly

Adds omit= to override global npm config that omits devDependencies. This ensures npm ci installs all 190 packages (including vitest and @testing-library/jest-dom) from a clean checkout, enabling npm run typecheck and npm run test to work locally. The comment clearly explains the intent. Issue requirement satisfied.

2. src/lib/sync-lock.ts (NEW) — Shared locking module

Extracts the inline lock acquisition/release logic from sync/scheduled/route.ts into a reusable module. All three sync entry-points (scheduled, manual, automation) now share this module, preventing race conditions across concurrent sync types. Lock semantics are sound: 30-minute stale timeout, conditional delete in releaseLock to avoid releasing another run's lock, and try/finally cleanup. The JSDoc documents the contract clearly.

3. src/app/api/sync/scheduled/route.ts — Refactors to shared lock module

Removes ~56 lines of inline lock code and imports acquireLock/releaseLock from the new shared module. Now passes "scheduled" as the sync type parameter. Error message simplified from "A scheduled sync is already running" to "A sync is already running" (consistent across all sync types).

4. src/app/api/sync/route.ts — Adds lock and sync run tracking for manual sync

Adds lock acquisition with "manual" sync type. Wraps the sync logic in try/finally to guarantee lock release. Updates the IssueSyncRun record with status: "completed", completedAt, reposFetched, and syncedCount on success. This was a gap in the previous implementation.

5. src/app/api/automation/sync/route.ts — Adds lock for automation sync

Adds lock acquisition with "automation" sync type. Wraps the sync logic in try/finally to guarantee lock release.

6. src/app/api/issues/untriaged/route.ts (NEW) — Grooming intake endpoint

New GET /api/issues/untriaged returns open issues with no status/* label. This surfaces untriaged issues that would otherwise be invisible to the agent queue. Query params: limit (default 50, capped at 200), repo (optional filter), excludeRenovate (default true). Uses Prisma findMany with selective select for efficiency. The isRenovateIssue filter removes "Dependency Dashboard" noise.

Note: The implementation fetches all open issues from enabled repos and filters in-memory. For repositories with large numbers of open issues, this could be inefficient. The 200-item cap limits damage. A Prisma-level NOT filter on labels would be more efficient at scale, but the current approach is acceptable for the intended use case.

7. src/app/api/issues/untriaged.test.ts (NEW) — Test coverage for untriaged endpoint

254 lines covering: no-status-label filtering, closed issue exclusion, limit behavior (default 50, custom, capped at 200), repo filtering, Renovate exclusion, empty array handling, response shape, and updatedAt descending order. Uses vitest mocks to simulate Prisma behavior. Well-structured tests.

8. src/app/api/pr-followup/sync/route.ts — Adds authorization

Adds authorizeRequest check, returning 401 if unauthorized. Previously had no auth check.

9. src/app/api/pr-followup/webhook/route.ts — Webhook hardening

  • Adds authorizeRequest check (401 if unauthorized) alongside existing HMAC signature verification.
  • Changes webhook signature verification from fail-open to fail-closed by default: requests are rejected unless WEBHOOK_SECRET is set or WEBHOOK_GATEWAY_MODE=true (explicit opt-out for gateway deployments).
  • Updates JSDoc to document the new fail-closed default.

This is a security improvement. The dual-layer auth (Bearer token + HMAC signature) provides defense in depth.

10. src/app/api/routes.test.ts — Smoke test registration

Adds the new untriaged route to the API smoke checks.

11. prisma/schema.prisma — Documentation update

Updates comment from "pre-claim" to "automation" in the IssueSyncRun.syncType field comment. This aligns the schema with the actual sync types used by the codebase.


Standards Compliance

Standard Status
Error handling (error instanceof Error pattern) src/app/api/issues/untriaged/route.ts uses catch (error) with console.error; other routes use similar patterns
API route HTTP status codes ✅ 200 (success), 409 (conflict/lock), 401 (unauthorized), 500 (error)
Audit logging ✅ New endpoint is read-only (no audit entry required)
Lock release on failure try/finally in sync routes
No secrets in code ✅ No secrets added
Label conventions ✅ Status labels use status/* pattern

Linked Issue Fit

PR 315 — Restore reproducible local validation

The issue described npm ci installing only 52 packages (missing devDependencies like vitest and @testing-library/jest-dom), causing npm run typecheck and npm run test to fail from a clean checkout.

Requirement Status
npm ci installs all devDependencies .npmrc with omit= overrides global omit config
npm run typecheck works from clean checkout ✅ Satisfied by devDependency fix
npm run test works from clean checkout ✅ Satisfied by devDependency fix

The PR title understates the scope. Beyond the .npmrc fix, the PR adds: (a) a grooming intake endpoint, (b) shared sync locking across all sync types, and (c) webhook security hardening. These are valuable related improvements.


Evidence Provider Findings

No evidence providers were configured for this analysis.


Tool Harness Findings

No harness results were provided (planned requests: 0).


Unknowns / Needs Verification

  1. Performance of untriaged endpoint at scale: The current implementation fetches all open issues from enabled repos and filters in-memory with .filter(). For repositories with thousands of open issues, this could be slow. The 200-item cap limits response size but does not reduce the DB query overhead. A Prisma-level NOT filter using labels: { hasNone: STATUS_LABELS } (or equivalent) would be more efficient. This is a non-blocking observation for v1; the cap provides adequate protection.

  2. No auth on untriaged endpoint: The endpoint has no authorizeRequest check. This appears intentional (it's a public grooming intake view), and matches the pattern of other read-only endpoints. Confirm this is the desired behavior.

  3. WEBHOOK_GATEWAY_MODE not documented in AGENTS.md: The new WEBHOOK_GATEWAY_MODE environment variable is used in pr-followup/webhook/route.ts but not documented in the AGENTS.md environment variable table. Consider adding it if this is a supported deployment option.

  4. .npmrc was not run through lint/typecheck: No lint or typecheck failures reported, but the .npmrc change is configuration-only and not subject to those checks.


Conclusion

This PR fixes the reproducible local validation issue (PR 315) and adds meaningful related improvements. The implementation is correct, well-tested, and follows repository conventions. Approve.

Saffron Worker added 2 commits June 9, 2026 04:12
…l-validation

# Conflicts:
#	src/app/api/issues/untriaged.test.ts
#	src/app/api/issues/untriaged/route.ts
…n' into fix/reproducible-local-validation

# Conflicts:
#	src/app/api/issues/untriaged.test.ts

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

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic)

Approve — PR PR 336 directly addresses issue PR 315 by adding a .npmrc that overrides any global omit=["dev"] config, ensuring npm ci installs all 190 packages including vitest and @testing-library/jest-dom from a clean checkout. The test file refactoring in untriaged.test.ts improves test quality by replacing brittle doMock+dynamic-import sequencing with a vi.mock setup that simulates actual Prisma where/orderBy behavior, making the tests more realistic and less prone to hoisting-related failures.

Changes

.npmrc (new file, 4 lines)

Adds an omit= directive to disable the global npm config that was omitting devDependencies. This is a standard, well-understood npm configuration override with clear comments explaining the intent. From a clean checkout, npm ci now installs all 190 packages instead of 52, enabling npm run typecheck and npm run test to work locally without manual intervention.

untriaged.test.ts (net +19 lines)

Refactors the Prisma mock from vi.doMock with dynamic imports to vi.mock with a shared mockFindManyData array. The new mock simulates Prisma query behavior (state filter, repo filter, orderBy) rather than bypassing it entirely. Tests now populate mockFindManyData directly, and the mock function filters/sorts internally. This is a structural improvement: more realistic test coverage, better isolation, and removes the fragile hoisting-workaround code.

Standards Compliance

The .npmrc addition follows standard npm conventions and matches the PR description's stated goal. The test refactor follows repository patterns for vitest mocking. No secrets, no schema changes, no API contract changes. CI lint/typecheck gates apply as documented.

Linked Issue Fit

Issue PR 315 explicitly calls out the symptom: npm run typecheck failed with missing @testing-library/jest-dom types and npm run test failed with vitest: not found. The .npmrc fix directly resolves both by ensuring devDependencies are installed. The issue is fully addressed.

Evidence Provider Findings

No evidence providers configured — nothing to evaluate.

Tool Harness Findings

No planned requests; tool harness produced no findings — nothing to evaluate.

Unknowns / Needs Verification

None identified. The fix is deterministic, the test improvement is self-contained, and the linked issue acceptance criteria (reproducible local validation) is clearly satisfied.

@joryirving joryirving merged commit ae9801c into main Jun 9, 2026
3 checks passed
@joryirving joryirving deleted the fix/reproducible-local-validation branch June 9, 2026 15:54
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.

Restore reproducible local validation

2 participants