Skip to content

fix(api): save IPNS record to DB before delegated routing publish#151

Merged
FSM1 merged 1 commit into
mainfrom
fix/ipns-publish-db-save-order
Feb 19, 2026
Merged

fix(api): save IPNS record to DB before delegated routing publish#151
FSM1 merged 1 commit into
mainfrom
fix/ipns-publish-db-save-order

Conversation

@FSM1

@FSM1 FSM1 commented Feb 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • Root cause: publishRecord() called publishToDelegatedRouting() before upsertFolderIpns(). If delegated routing failed (429 rate limit, network error, DHT issues), the DB save never ran — leaving per-file IPNS names unresolvable.
  • Impact: Files uploaded from the desktop FUSE mount under load returned 404 on IPNS resolve, breaking downloads and file details in the web app.
  • Fix: Save to DB first, then attempt delegated routing as best-effort. Both publishRecord() and publishBatch() are fixed.

Test plan

  • All 104 IPNS tests pass (unit, integration, security)
  • Deploy to staging and verify that newly-uploaded files from desktop are downloadable in web app
  • Verify that delegated routing failures are logged as warnings (not thrown)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced resilience: delegated routing failures no longer block operations. Data is now persisted to storage even if routing encounters errors.
    • Routing failures are logged as warnings instead of throwing critical errors, improving system stability.
  • Tests

    • Updated test coverage to validate non-fatal routing failure scenarios and ensure data persistence during error conditions.

When publishToDelegatedRouting() failed (e.g. rate-limited 429 or
network error), upsertFolderIpns() never ran, so per-file IPNS
names were never stored in the DB. This caused resolve to return
404 for files uploaded from the desktop FUSE mount under load,
since both the DHT and DB fallback had no record.

Fix: save to DB first, then attempt delegated routing as
best-effort. If routing fails, log a warning but return success
since the DB cache ensures resolve will find the record.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Feb 19, 2026

Copy link
Copy Markdown

Walkthrough

The changes modify IPNS publishing to prioritize database persistence over delegated routing publication. Delegated routing failures transition from fatal exceptions to non-fatal logged warnings, enabling the service to save state even if external routing publication fails. Test cases are updated to validate successful database persistence occurs regardless of routing outcomes.

Changes

Cohort / File(s) Summary
IPNS Service Implementation & Tests
apps/api/src/ipns/ipns.service.ts, apps/api/src/ipns/ipns.service.spec.ts
Refactored delegated routing failures from fatal to non-fatal. Service now persists IPNS state to database before attempting publication, with routing failures logged as warnings rather than thrown. Tests updated to assert successful DB saves occur regardless of routing publication outcomes; error handling scenarios renamed to reflect non-fatal routing failures.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: reordering DB persistence before delegated routing publication to prevent data loss on routing failures.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/ipns-publish-db-save-order

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 and usage tips.

@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 (1)
apps/api/src/ipns/ipns.service.ts (1)

56-73: ⚠️ Potential issue | 🟡 Minor

success: true semantics silently changed — document the new contract

Before this PR: success: true ← DHT publication succeeded and DB was saved.
After this PR: success: true ← DB was saved; DHT publication may have silently failed.

Any caller that uses success: true as a signal that the record is immediately resolvable from delegated routing (e.g., waiting to share a link) will now receive a false positive when routing is down. The DB-fallback in resolveRecord() provides eventual consistency, but not the immediate DHT reachability that success previously implied.

Consider adding a field to PublishIpnsResponseDto to distinguish the two outcomes:

💡 Proposed DTO addition
 export interface PublishIpnsResponseDto {
   success: boolean;
   ipnsName: string;
   sequenceNumber: string;
+  /** true when the record was also published to the delegated routing DHT */
+  routingPublished?: boolean;
 }

Then at the call site:

+    let routingPublished = false;
     try {
       await this.publishToDelegatedRouting(dto.ipnsName, recordBytes);
+      routingPublished = true;
     } catch (error) {
       this.logger.warn(...);
     }

-    return { success: true, ipnsName: dto.ipnsName, sequenceNumber: folder.sequenceNumber };
+    return { success: true, ipnsName: dto.ipnsName, sequenceNumber: folder.sequenceNumber, routingPublished };

If enriching the response is too noisy for now, at minimum update the JSDoc on publishRecord and PublishIpnsResponseDto to state that success reflects DB persistence only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/ipns/ipns.service.ts` around lines 56 - 73, The PR changed
success semantics so PublishIpnsResponseDto now may return success: true even if
delegated routing publish failed; update the contract by either adding a boolean
field (e.g., delegatedRoutingPublished or routedToDelegatedRouting) to
PublishIpnsResponseDto and set it from the result of publishToDelegatedRouting
(called in ipns.service.ts after upsertFolderIpns), or at minimum update the
JSDoc/comments on publishRecord (or the method that returns
PublishIpnsResponseDto) and the PublishIpnsResponseDto declaration to explicitly
state that success indicates DB persistence only and does not guarantee
immediate delegated routing/DHT publication.
🧹 Nitpick comments (2)
apps/api/src/ipns/ipns.service.ts (1)

111-128: Batch fix is consistent with single-record fix — but lacks test coverage

The same DB-first / best-effort-routing pattern is correctly applied inside the Promise.allSettled map. upsertFolderIpns failures still surface as rejected promises and are handled by the settled loop (resulting in success: false per entry). Routing failures are swallowed and only warn-logged, returning success: true per entry.

However, the new "delegated routing failures are non-fatal" describe block in the spec file covers only publishRecord. There are no corresponding tests for publishBatch with non-fatal routing failures (500, network error, string rejection). Given that the fix applies to both paths and publishBatch is independently callable, this is a test-coverage gap for code changed in this PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/ipns/ipns.service.ts` around lines 111 - 128, Add unit tests for
publishBatch mirroring the existing "delegated routing failures are non-fatal"
spec for publishRecord: exercise publishBatch with entries where
upsertFolderIpns succeeds but publishToDelegatedRouting rejects with (a) an HTTP
500-like error, (b) a network/error object, and (c) a string rejection, and
assert that each batch entry returns success: true while the logger.warn is
invoked (i.e., routing failures are non-fatal); target the publishBatch method
and stub/mocks for upsertFolderIpns and publishToDelegatedRouting to simulate
the failure modes used in publishRecord tests.
apps/api/src/ipns/ipns.service.spec.ts (1)

901-960: New suite validates non-fatal behavior for publishRecord — add parallel coverage for publishBatch

The three cases (string rejection, 400, 503) are well-chosen and cover the distinct code paths in publishToDelegatedRouting (network-error backoff path, non-retryable HTTP path). However, publishBatch has the identical try/catch wrapping added in this PR (lines 121–128 of the service) and is not exercised in any routing-failure scenario.

Suggested additions to the describe block (or a sibling "publishBatch — delegated routing failures are non-fatal" block):

it('should return success for all entries even when delegated routing fails (500)', async () => {
  mockFetch.mockResolvedValue({
    ok: false,
    status: 500,
    text: () => Promise.resolve('Internal Server Error'),
  });
  mockFolderIpnsRepo.create.mockReturnValue({ ...mockFolderEntity, sequenceNumber: '0' });

  const result = await service.publishBatch(testUserId, {
    records: [
      { ipnsName: testIpnsName, record: testRecord, metadataCid: testMetadataCid },
    ],
  });

  expect(result.totalSucceeded).toBe(1);
  expect(result.totalFailed).toBe(0);
  expect(result.results[0].success).toBe(true);
  expect(mockFolderIpnsRepo.save).toHaveBeenCalled();
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/ipns/ipns.service.spec.ts` around lines 901 - 960, Add tests
mirroring the delegated-routing failure cases already covering publishRecord to
also exercise publishBatch: create a new describe/it block that stubs mockFetch
to reject with a string or return non-OK responses (e.g., status 400, 500/503),
mockFolderIpnsRepo.create to return an entity (e.g., sequenceNumber: '0'), call
service.publishBatch(...) with one or more records, and assert the batch
response reports successes (totalSucceeded === number of records, totalFailed
=== 0, each results[i].success === true) and that mockFolderIpnsRepo.save was
called; this exercises the same try/catch in publishBatch and validates
delegated routing failures are non-fatal (referencing publishBatch,
publishRecord, publishToDelegatedRouting, mockFetch, mockFolderIpnsRepo.create,
and mockFolderIpnsRepo.save).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/api/src/ipns/ipns.service.ts`:
- Around line 56-73: The PR changed success semantics so PublishIpnsResponseDto
now may return success: true even if delegated routing publish failed; update
the contract by either adding a boolean field (e.g., delegatedRoutingPublished
or routedToDelegatedRouting) to PublishIpnsResponseDto and set it from the
result of publishToDelegatedRouting (called in ipns.service.ts after
upsertFolderIpns), or at minimum update the JSDoc/comments on publishRecord (or
the method that returns PublishIpnsResponseDto) and the PublishIpnsResponseDto
declaration to explicitly state that success indicates DB persistence only and
does not guarantee immediate delegated routing/DHT publication.

---

Nitpick comments:
In `@apps/api/src/ipns/ipns.service.spec.ts`:
- Around line 901-960: Add tests mirroring the delegated-routing failure cases
already covering publishRecord to also exercise publishBatch: create a new
describe/it block that stubs mockFetch to reject with a string or return non-OK
responses (e.g., status 400, 500/503), mockFolderIpnsRepo.create to return an
entity (e.g., sequenceNumber: '0'), call service.publishBatch(...) with one or
more records, and assert the batch response reports successes (totalSucceeded
=== number of records, totalFailed === 0, each results[i].success === true) and
that mockFolderIpnsRepo.save was called; this exercises the same try/catch in
publishBatch and validates delegated routing failures are non-fatal (referencing
publishBatch, publishRecord, publishToDelegatedRouting, mockFetch,
mockFolderIpnsRepo.create, and mockFolderIpnsRepo.save).

In `@apps/api/src/ipns/ipns.service.ts`:
- Around line 111-128: Add unit tests for publishBatch mirroring the existing
"delegated routing failures are non-fatal" spec for publishRecord: exercise
publishBatch with entries where upsertFolderIpns succeeds but
publishToDelegatedRouting rejects with (a) an HTTP 500-like error, (b) a
network/error object, and (c) a string rejection, and assert that each batch
entry returns success: true while the logger.warn is invoked (i.e., routing
failures are non-fatal); target the publishBatch method and stub/mocks for
upsertFolderIpns and publishToDelegatedRouting to simulate the failure modes
used in publishRecord tests.

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.

1 participant