Skip to content

fix(web): fail closed when CLI linking has no database to write to - #349

Draft
DevQwinB wants to merge 1 commit into
blockchain-maxis:mainfrom
DevQwinB:fix/cli-link-requires-database
Draft

fix(web): fail closed when CLI linking has no database to write to#349
DevQwinB wants to merge 1 commit into
blockchain-maxis:mainfrom
DevQwinB:fix/cli-link-requires-database

Conversation

@DevQwinB

Copy link
Copy Markdown

closes #277

Draft, deliberately. /api/cli/pair/complete does not exist on main — it is #268, still open, and the four open CLI PRs each choose a different route layout. This lands the precondition the issue is actually about, in a shape that survives whichever pairing PR wins. See Where this fits.

The bug

Wallet links are Wallet rows in Postgres. The read path degrades gracefully with no DATABASE_URLsafeDbProfile / safeDbOperations in lib/profiles.ts return null and the caller falls through to a live chain read, then to the curated demo profiles. That is right for reads; a preview deployment with nothing provisioned still renders /p/{handle}.

It is exactly wrong for writes. The same fall-through makes a link appear to succeed and persist nothing: the CLI prints success, the developer believes they are linked, and the failure surfaces later from some unrelated command that needed the binding. A link that silently persists nothing is worse than a refusal.

Change

apps/web/lib/profiles.ts — the write-path counterpart

Deliberately in the same file as the read-path fall-through, right below it, so the asymmetry is visible where someone would otherwise copy the wrong pattern. A comment block says which half is which and why.

  • isDatabaseConfigured()
  • DatabaseRequiredError — carries code: 'database_required' and isConfigurationError: true
  • requireDatabase(operation)

It checks configuration, not reachability. An unreachable database is a different failure with a different fix, and reporting it as "not configured" sends an operator to the wrong runbook.

POST /api/cli/pair/complete — fails closed, first

The precondition is checked before the body is parsed and before any signature is looked at. If the result cannot be stored, nothing else about the request matters, and that ordering is the point of the issue.

{
  "error": "database_required",
  "isConfigurationError": true,
  "message": "CLI wallet linking requires a database. … see docs/CLI.md and issue #191.",
  "docs": "…/docs/CLI.md#linking-requires-a-database"
}

503, not a 4xx. Nothing the caller sent is wrong; the service is correctly configured to refuse rather than broken; and nothing the developer does to their own account changes it. The isConfigurationError flag is what lets the CLI report a deployment problem without string-matching prose — telling someone their wallet or signature was bad would send them to debug the one thing that is working. cache-control: no-store, since nothing changes until an operator acts.

Once the precondition passes the route returns 501, not 200: pairing verification is #268's, and this will not pretend to complete a pairing it has not verified.

GET on the same route reports {"available":…} so the page can ask before approval.

/link — says so before you approve

Reads the same signal server-side, so the page and the API cannot disagree, and disables approval with an explanation. Approving a link that gets refused seconds later is the worst version of this: the developer has already signed something. The notice states plainly that it is a deployment problem, not their account, and links #191 and the docs — because the person who can fix it is not the person reading the page.

Docs

New docs/CLI.md with a #linking-requires-a-database section: why the read path degrades and the write path cannot, what each surface reports, and the operator's fix (provision, migrate, check /api/health). Cross-referenced from #191 and from the DATABASE_URL row in ENVIRONMENT.md, which previously said only that web "degrades honestly" — true for reads, and now qualified for linking.

Verification

  • 8 new tests, all passing: isDatabaseConfigured tracks the var; requireDatabase throws a typed error whose message names DATABASE_URL and #191; it is a no-op once configured; the read path still returns null rather than throwing (a regression there would break every preview deployment, so it is pinned); POST → 503 with the right body and no-store; the refusal is >= 500 and never a 4xx; POST reaches 501 once configured; GET flips.
  • node --test "lib/**/*.test.ts" in apps/web215 tests passing, no regressions.
  • pnpm --filter @signet/web typecheck clean, lint clean.
  • prettier --check clean on every file this PR adds. lib/profiles.ts and docs/ENVIRONMENT.md already fail prettier --check on main; I left that alone rather than bundling a reformat into this diff.

Where this fits

The precondition is the part of #277 that does not depend on how pairing is laid out: it is about DATABASE_URL and the write path, and lib/profiles.ts is where the issue itself points. The route is the thinnest possible carrier for it and is explicitly marked as #268's seam.

Happy to rebase onto whichever pairing PR lands first and move the requireDatabase call into its handler — that is a one-line move, and the guard, the page and the docs are unaffected.

Wallet links are Wallet rows in Postgres. The read path degrades
gracefully with no DATABASE_URL — safeDbProfile and safeDbOperations
return null and the caller falls through to a chain read, then to the
curated demo profiles — and that is right for reads. It is exactly wrong
for writes: the same fall-through makes a link appear to succeed while
persisting nothing. The developer believes they are linked, the CLI
believes it, and the failure surfaces later from some unrelated command
that needed the binding.

Add the write-path counterpart next to those helpers in lib/profiles.ts:
isDatabaseConfigured, a typed DatabaseRequiredError carrying
isConfigurationError, and requireDatabase. Only configuration is
checked, not reachability — an unreachable database is a different
failure with a different fix, and calling it "not configured" sends an
operator to the wrong runbook.

POST /api/cli/pair/complete checks the precondition first, before the
body and before any signature: if the result cannot be stored, nothing
else about the request matters. It answers 503 rather than a 4xx because
nothing the caller sent is wrong and nothing they can do to their own
account changes it, and carries isConfigurationError so a client can
classify it without string-matching prose. Pairing verification itself
is blockchain-maxis#268's; this route returns 501 once the precondition passes rather
than pretending to complete a pairing it has not verified.

/link reads the same signal server-side, so the page and the API cannot
disagree, and disables approval with an explanation before the developer
signs something that cannot be stored.

docs/CLI.md documents the dependency, what each surface reports, and how
an operator fixes it, cross-referenced from ENVIRONMENT.md and blockchain-maxis#191.
@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@DevQwinB Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@netlify

netlify Bot commented Aug 30, 2026

Copy link
Copy Markdown

Deploy Preview for stellar-signet ready!

Name Link
🔨 Latest commit ea6dc72
🔍 Latest deploy log https://app.netlify.com/projects/stellar-signet/deploys/6a9463209c53a60008bcdeb7
😎 Deploy Preview https://deploy-preview-349--stellar-signet.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

@DevQwinB is attempting to deploy a commit to the blockchainmaxis-8449's projects Team on Vercel.

A member of the Team first needs to authorize it.

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.

CLI linking is unavailable without a provisioned database

1 participant