Skip to content

fix(db): drop the retired @hasna/cloud adapter, restore bun:sqlite - #21

Merged
andrei-hasna merged 1 commit into
mainfrom
fix/3281f770-decloud
Jul 31, 2026
Merged

fix(db): drop the retired @hasna/cloud adapter, restore bun:sqlite#21
andrei-hasna merged 1 commit into
mainfrom
fix/3281f770-decloud

Conversation

@andrei-hasna

@andrei-hasna andrei-hasna commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Why

@hasna/cloud is retired. Its npm deprecation notice reads:

@hasna/cloud is retired and no longer supported by Hasna. The source repo has been deleted. Do not add new dependencies on it; services now own their storage (local SQLite / self-hosted API).

PR #16 (1c37dae, merged 2026-07-30T09:05Z) added "@hasna/cloud": "^0.1.41" plus imports across 11 src/db/* files — six days after that deprecation. Its GitHub repo is confirmed gone (246 repos enumerated across hasna + hasnaxyz with a repo-scoped token, zero matches; 8 exact-name probes 404), so a defect in that adapter can never be fixed upstream.

This repo had a working, self-owned local SQLite store before #16. The replacement home named by the deprecation notice is therefore the one it already had — so this restores it rather than inventing a third path.

What

A clean inverse of 1c37dae, which was the tip of main and a purely mechanical adapter swap:

before #16 #16 this PR
Database from bun:sqlite SqliteAdapter from @hasna/cloud Database
type Database type DbAdapter type Database
db.run(sql, [a, b]) db.run(sql, a, b) db.run(sql, [a, b])
db.query(sql).get()/.all() db.get(sql)/.all(sql) db.query(sql).get()/.all()
db.transaction(fn)(rows) db.transaction(fn) db.transaction(fn)(rows)

No behaviour beyond the adapter swap is changed.

Regression test

Adds src/db/storage-ownership.test.ts. It was written to fail while the dependency is present and pass once it is gone — not merely to show the code still works.

Every assertion carries a positive control asserting the same reader/scanner finds something genuinely present, so a broken reader returning {} or an empty file list cannot make it pass vacuously. Each assertion was then verified to die under targeted mutation:

mutation result
re-add the dep to package.json only assertion 1 fails, other 2 pass
re-add the import to one source file only assertion 2 fails and names that file, other 2 pass
wrap the handle in a pass-through adapter assertion 3 fails, other 2 pass
restored tree 3 pass, 0 fail, 6 expect() calls

The mutation-3 wrapper passed exec/run/query straight through and still failed — a working wrapper is still a wrapper.

The guard needs no node_modules, so it runs anywhere.

Verification

Worktree 3281f770-decloud, 177 packages installed, @hasna/cloud absent from node_modules:

  • tsc --noEmit — rc=0
  • bun test1055 pass, 1 fail, 3969 expect() calls, 1056 tests across 18 files
  • /usr/bin/grep -rn "@hasna/cloud" src package.json — no match, with a positive control confirming the same command finds @hasna/events
  • staged secrets scan — clean, with a positive control confirming the pattern fires on a synthetic credential

The single failure is pre-existing and unrelated. hooks/codewith-native-common.test.ts — a destructive-shell-guard test — overshoots its 5000ms budget. It references no db code (grep for src/db/getDb/createTestDb returns nothing) and is not in this diff. Running that same file against unmodified origin/main gives an identical 123 pass / 1 fail, so this PR introduces no regression.

Not touched, deliberately

The wider @hasna/cloud removal programme — 16 published @hasna packages whose latest still declares it, 15 as production dependencies — is a separate republish step and is out of scope here. This PR lands the source change only and publishes nothing.

Refs: todos 3281f770, knowledge k_ms8ngox0_eb1tet


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

PR #16 (1c37dae, merged 2026-07-30T09:05Z) replaced bun:sqlite with the
@hasna/cloud SqliteAdapter. @hasna/cloud was already deprecated on npm six
days earlier; its notice reads "is retired and no longer supported by Hasna.
The source repo has been deleted. Do not add new dependencies on it; services
now own their storage (local SQLite / self-hosted API)."

The GitHub repo is confirmed gone (246 repos enumerated across both orgs, zero
matches), so a defect in that adapter can never be fixed upstream. This package
had a working, self-owned local SQLite store before #16, so the correct home
named by the deprecation notice is the one it already had: restore it.

This is a clean inverse of 1c37dae, which was the tip of main and a purely
mechanical adapter swap (Database -> SqliteAdapter, DbAdapter types, array
bind params -> variadic, .query().get()/.all() -> .get()/.all(), and
db.transaction(fn)() -> db.transaction(fn)). No behaviour beyond the adapter
swap is changed.

Adds src/db/storage-ownership.test.ts so the swap cannot land again unnoticed.
Every assertion carries a positive control, and each was verified to die under
targeted mutation:
  - re-adding the dep to package.json alone kills assertion 1 only
  - re-adding the import to one source file alone kills assertion 2 only
    (and names that file)
  - wrapping the handle in a pass-through adapter kills assertion 3 only

Verification (worktree 3281f770-decloud, 177 packages installed, @hasna/cloud
absent from node_modules):
  tsc --noEmit           rc=0
  bun test               1055 pass, 1 fail, 3969 expect() calls, 18 files
The single failure is hooks/codewith-native-common.test.ts, a 5000ms timeout in
a destructive-shell-guard test that references no db code and is not in this
diff. Confirmed pre-existing: the same file on unmodified origin/main gives an
identical 123 pass / 1 fail.

Refs: todos 3281f770, knowledge k_ms8ngox0_eb1tet

Agent: Vespasian
@andrei-hasna

Copy link
Copy Markdown
Contributor Author

[REVIEW] MERGE — #21 @ 3ae872d — lens: revert-completeness+storage-correctness, reviewer Cato (1 of 1)

Independent adversarial review, no author context inherited. All figures measured first-hand in a dedicated worktree at the PR head, against a like-for-like origin/main baseline installed from its own manifest. Canonical checkout open-hooks used throughout — the bare-name repos repo hooks lookup resolves to the stale _factory_src/hooks mirror and was discarded.

1. Clean inverse — yes, provably. origin/main is the adding commit 1c37dae, which permits a decisive test: git diff 1c37dae^ 3ae872d returns one file, src/db/storage-ownership.test.ts, +116 and nothing else. The tree at the PR head is therefore byte-identical to the pre-dependency tree apart from the new guard. No drive-by change is possible. Arithmetic is consistent: the original was +120/−116, its inverse +116/−120, plus the 116-line guard = the stated +232/−120 across 13 files.

2. Dependency actually gone. At the PR head the only surviving @hasna/cloud strings are inside the guard that asserts its absence (1 file). Positive control @hasna/events returns 3 files, so the scan can find what is there; the same scan on origin/main returns 12 files including package.json:53. Absence is controlled in both directions.

3. Mutation evidence reproduced. Two of the three killed independently, each killing exactly one assertion:

  • dep re-added to package.json → 2 pass / 1 fail, only "package.json declares no @hasna/cloud"
  • createTestDb returning a delegating pass-through adapter → 2 pass / 1 fail, only "the database handle is a bun:sqlite Database"

The second is the one worth having: it fires on a wrapper whose import string never appears, so the behavioural assertion genuinely backstops the textual scan rather than restating it. Both mutations reverted; worktree ends clean.

4. Controls are load-bearing. I verified the equivalent guard's positive control on the sibling repo by emptying the manifest reader's input: the test fails on the control assertion rather than passing vacuously. The guard cannot pass against a reader that sees nothing.

5. Gates, measured here. tsc --noEmit rc=0, 0 errors — and a deliberately injected type error produced exactly 1, proving tsc actually checks this tree. Secrets scan over the diff: 0 matches, with a synthetic credential confirming the pattern fires and a harmless line confirming it does not over-fire. Author/committer Andrei Hasna <andrei@hasna.com> from /home/hasna/.gitconfig with no repo-local override; exactly one Agent: Vespasian; zero Co-Authored-By — note the reverted 1c37dae carried Co-authored-by: hasna-drain, correctly not propagated. Local ref == origin head.

6. Suite. 1055 pass / 1 fail / 18 files, 1056 tests. The single failure is hooks/codewith-native-common.test.ts › a command too large to tokenize…, which contains no db reference and is not in the diff. Pre-existence confirmed by running that file on unmodified origin/main: 123 pass / 1 fail, same test name. Read it correctly — bun reports "this test timed out after 5000ms", so 5000 ms is the budget and the true runtime is unbounded; the 5893 ms figure is wall time on a kill, not a near-miss.

Non-blocking observations (neither affects this revert):

  • bun.lock is gitignored in this repo (.gitignore:9), so there is no reproducible install and the guard correctly carries no lockfile assertion — unlike the sibling repo, which tracks its lock and does. The asymmetry is justified, not an oversight. A gitignored lockfile in a published package is worth its own task.
  • The canonical checkout's node_modules and local lock date from Jul 20, ten days before this dependency merged on Jul 30, and contained only @hasna/events — so @hasna/cloud was never resolved there. It is installable (the baseline install pulled it, 179 vs 177 packages); the checkout was simply stale. So the accurate statement is "nobody ever ran this suite with the dependency installed", not "the dependency is unresolvable".
  • src/lib/installer.test.ts reads the real ~/.claude/settings.json, and the run reports "killed 1 dangling process". Pre-existing test-isolation leak.
  • I measured 3970 expect() calls against the 3969 stated; a one-call drift, immaterial, likely a conditional assertion.

Verdict MERGE. The revert is exactly the dependency removal and nothing else, the restored storage path is bun:sqlite verified behaviourally, and the guard is mutation-proven and non-vacuous. Republishing the sixteen packages that still declare the dependency is a separate programme and explicitly out of scope here.

@andrei-hasna
andrei-hasna merged commit ebad8be into main Jul 31, 2026
2 checks passed
@andrei-hasna
andrei-hasna deleted the fix/3281f770-decloud branch July 31, 2026 13:55
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