Skip to content

PRO-1298: migrate plugin onto @hydradb/sdk (bundled wrapper) + fix DX-G-002 and delete silent no-op - #9

Merged
SohamRatnaparkhi merged 5 commits into
mainfrom
tushar/pro-1298-plugin-sdk-wrapper
Jul 27, 2026
Merged

SohamRatnaparkhi merged 5 commits into
mainfrom
tushar/pro-1298-plugin-sdk-wrapper

Conversation

@tushar-hydradb

@tushar-hydradb tushar-hydradb commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

PRO-1298 — Claude Code plugin (highest blast radius)

Migrates the plugin's hand-rolled v1 HTTP client (scripts/lib/hydra-client.mjs) onto the generated @hydradb/sdk behind a thin canonical wrapper, fixes the never-worked knowledge sync (DX-G-002) plus a second silent delete bug, and aligns user-facing names to the shared contract — without breaking installed copies.

Verified end-to-end, not just unit-mocked. The recall round-trip test feeds a real v2 wire envelope through the actual bundled SDK (which deserializes it to camelCase) and asserts the normalizer still surfaces the chunk. This caught a silent empty-recall regression the migration would otherwise have shipped — exactly the failure class this ticket exists to kill.

The runtime constraint that shaped the approach

The plugin ships via a public marketplace = git clone, no npm install; node_modules/ is gitignored and hooks run on bare node. A bare import "@hydradb/sdk" throws ERR_MODULE_NOT_FOUND at load, before any guard — crashing every hook (reproduced). So the SDK is bundled, not depended on at runtime.

Vendored SDK (Option A: bundle)

  • scripts/build-vendor.mjs esbuilds the exact-pinned @hydradb/sdk@2.1.2 (+ form-data, node-fetch) into one committed self-contained ESM file, scripts/vendor/hydradb-sdk.mjs, imported by relative path → runtime stays zero-dependency (esbuild is a devDependency only). package-lock.json pins transitive versions for reproducible bundles.
  • check.mjs gates: (1) a fresh build:vendor must be byte-identical to the committed bundle (drift guard — an SDK bump without a re-vendor fails CI); (2) the bundle loads and constructs on bare node from a dir with no node_modules (the exact marketplace runtime). Bare-node gate: green.

Canonical wrapper (scripts/lib/hydra/, CONTRACT §2)

Maps the canonical surface onto the vendored SDK, unwraps HandlerEnvelope by shape, translates SDK errors to plain Errors, caps retries at 0 (under the 15s/20s hook budgets), exposes tenantId/subTenantId as properties. HydraClient is now a thin adapter that keeps its method names + normalized output shape + null-when-unconfigured, so plugin.mjs/workspace-sync.mjs are otherwise unchanged and unconfigured installs stay inert.

DX-G-002 — three stacked defects, all fixed

Knowledge ingest now routes through the SDK's multipart context.ingest:

  • multipart/form-data (not application/json),
  • top-level tenant_id,
  • sources in app_knowledge (a JSON string) carrying each item's client-assigned id (claude-file:<sha1>) verbatim — never app_knowledge-as-JSON-body, never the v1-only app_sources.

Second silent bug — delete no-op

DELETE /context answers a zero-match delete with 200 {success:false, deleted_count:0}; the caller counted that as success and dropped tracked state. The wrapper now inspects the body (success/deleted_count/user_memory_deleted) for both kinds and raises; workspace-sync surfaces it into summary.errors and retains state for retry. Ingest and delete send the same collection so scopes match (server filters delete on database+collection).

Preserved behaviour (all load-bearing)

Redaction on both legs; Promise.allSettled silent-failure recall; client=null + guarded SDK construction when unconfigured; retry wall-time under hook budgets; dual read/write timeouts; the 340-line normalizer (kept verbatim — it now snake-cases input keys once at entry, since the SDK deserializes v2 responses to camelCase, so recall still surfaces chunks); the persisted state.json shape; and the --json output shapes (golden-filed).

Names (CONTRACT §3)

Canonical /hydradb:query|ingest|doctor (slash + CLI + skills); every prior name (search, recall, status, remember, save-session, sync-workspace, reindex) keeps working with one stderr deprecation warning (stderr, so --json stdout is untouched). Env reads add the HYDRA_DB_* aliases (canonical HYDRADB_*, one warning); HYDRA_OPENCLAW_* is not read (per-client scoping, CONTRACT §1).

Tests (the repo had ZERO real HTTP-level coverage)

  • HTTP-level wire tests (stubbed fetch): assert knowledge ingest is multipart + top-level tenant_id + app_knowledge (not application/json, not app_sources) with the client id preserved; delete-by-kind routing; the zero-match "deleted nothing" surfacing; and a recall round-trip through the camelCase SDK response.
  • Conformance runner drives conformance/vectors.json through the wrapper against the real bundled SDK with a capturing fetch (16/16).
  • Golden --json shape snapshots for query/doctor/last-recall lock the marketplace-parsed shapes.
  • npm run check and npm pack --dry-run: green.

Notes / follow-ups

  • Docs/README + setup skill env examples still teach the (working, deprecated) HYDRADB_TENANT_ID spelling — left to the Phase 3 docs pass per the plan.
  • CONTRACT.md + conformance/vectors.json are the centrally-maintained masters, committed verbatim.

🤖 Generated with Claude Code

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

2 participants