release 1.1.0: ship the v2 endpoint fix to installed users, unwrap context envelopes, make deletes settle - #10
Conversation
…x license drift The manifest has said 1.0.0 since the initial commit, so everything merged since then ships to fresh marketplace installs under a version string that never moved. Bump to 1.1.0 across all three version sites. Minor, not patch: PR #9 added three user-facing skills (/doctor, /ingest, /query) alongside the migration onto @hydradb/sdk. Not major: the legacy HYDRADB_TENANT_ID / HYDRADB_SUB_TENANT_ID env names still resolve, with a one-shot deprecation warning pointing at HYDRADB_DATABASE / HYDRADB_COLLECTION. Also corrects a license drift that predates this bump. d9185b9 switched the project from MIT to Apache 2.0 but only updated package.json and LICENSE; plugin.json and marketplace.json still advertised MIT. Those two manifests are what the marketplace and the plugin UI actually show users, so the wrong license was the one on display. Both now read Apache-2.0, matching LICENSE. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SR9ddwF4t2s5ubMs7GZEzw Signed-off-by: tushar-hydradb <tushar@hydradb.com>
Greptile SummaryThe PR releases version 1.1.0 and aligns the marketplace manifests with the Apache-2.0 license, while refining retrieval-envelope handling and deletion reconciliation.
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains in the eligible follow-up review scope. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| scripts/lib/hydra/index.mjs | Refines per-ID deletion classification so already-absent sources are considered reconciled before evaluating batch-level failure. |
| scripts/lib/state.mjs | Adds explicit file-path removals to the state merge so confirmed deletions are not resurrected from disk. |
| scripts/lib/workspace-sync.mjs | Includes absolute file paths in confirmed-deletion summaries for durable state cleanup. |
| scripts/plugin.mjs | Propagates confirmed workspace removals into state writes for automatic and manual full-sync paths. |
| scripts/lib/sanitize.mjs | Adds parsing of structured application-knowledge envelopes to recover their prose content. |
| scripts/lib/hydra-client.mjs | Applies envelope unwrapping while normalizing recalled chunks and additional context. |
| scripts/lib/context-format.mjs | Applies normalized envelope content when formatting bounded Claude-facing retrieval sections. |
| conformance/tests.mjs | Adds coverage for already-absent and mixed per-ID deletion responses. |
| scripts/check.mjs | Adds checks for envelope normalization and explicit state-file removal semantics. |
| .claude-plugin/plugin.json | Updates the plugin version and displayed license. |
| .claude-plugin/marketplace.json | Updates marketplace release metadata and displayed license. |
| package.json | Bumps the package version to 1.1.0. |
| package-lock.json | Synchronizes lockfile package metadata with version 1.1.0. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Workspace sync] --> B[HydraDB delete]
B --> C{Per-ID result}
C -->|Deleted or already absent| D[Confirmed deletion]
C -->|Other failure| E[Retain for retry]
D --> F[Remove local tracking path]
E --> G[Keep local tracking path]
H[HydraDB recall] --> I[Normalize response]
I --> J[Unwrap appKnowledge envelope]
J --> K[Bounded Claude context]
Reviews (2): Last reviewed commit: "fix: unwrap knowledge envelopes into con..." | Re-trigger Greptile
⏚ Security Scan Complete✅ No vulnerabilities detected! This pull request has been scanned and no security issues were found. |
…y settle Found by installing 1.1.0 from the marketplace and exercising it against prod. Context injection carried the envelope, not the prose. Knowledge is ingested through `appKnowledge`, and the server indexes that envelope verbatim, so recall returns the whole JSON object as the chunk's content: internal ids, empty html_base64/csv_base64/markdown/files/layout slots, and tenant_metadata duplicated as app_metadata, with the actual text buried in `content.text`. Every prompt paid for that scaffolding, and one chunk was truncated mid-JSON at the 7000-char budget — the envelope ate the text it was supposed to deliver. Unwrapped at the normalization seam in sanitize.mjs, so query, last-recall and the injected block are all fixed at once; anything that is not one of our envelopes passes through untouched. `buildSection` is renamed to `buildContextString` and exported, matching the name and shape of HydraDB's documented reference formatter, which is what the plugin was already approximating. Deleting a synced file never settled, for two independent reasons. classifyDeletion checked the batch-level `success` rollup before the per-item results, so a batch reported success:false discarded the per-item reasons. Those reasons are what distinguish a real failure from an id the server has already removed, which comes back as deleted:false / "Source not found". Deletion is idempotent: the caller's postcondition holds, so that is a terminal success, not something to retry. Per-item detail now outranks the rollup, and an already-absent id is confirmed — a genuine failure alongside it is still retained. That alone changed nothing user-visible, because writeState could not express a removal. It merges `files` with what is on disk so a concurrent single-file sync keeps its addition, but a spread only adds and overwrites — it can never drop a key. A file deleted from in-memory state was resurrected from disk on every write. Removals are now named explicitly via `removedFilePaths`, which the two reconciling sync call sites pass from the summary. state.mjs is untouched by PR #9, so this predates 1.0.0. Verified end to end against prod, not just in tests: injected context is now clean prose with graph relations intact, and a deleted file drops from state on the first full pass and stays gone on the second. Each fix is mutation-tested — reverting it fails the suite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SR9ddwF4t2s5ubMs7GZEzw Signed-off-by: tushar-hydradb <tushar@hydradb.com>
🔍 Security Scan Results
Findings🟡 Symlink-following arbitrary file read and exfiltration in workspace ingestion (candidateSummary)Severity: The PostToolUse hook (hooks.json matcher Write|Edit|MultiEdit|NotebookEdit) invokes plugin.mjs post-tool-use, which calls extractPathsFromToolInput(input.tool_input, input.cwd) with LLM/attacker-influenced paths, then syncWorkspace -> candidateSummary (scripts/lib/workspace-sync.mjs). candidateSummary validates that the path is inside projectRoot via path.relative() and rejects ../ prefixes, but uses fs.stat and fs.readFile, which follow symlinks. A symlink placed inside the project (or present in a cloned repo) pointing to an arbitrary file outside the project root (e.g. ~/.ssh/id_rsa, ~/.aws/credentials, /etc/passwd) passes the lexical ../ check because the symlink's own path is inside the project, and its target content is read and uploaded to the third-party HydraDB service. This is inconsistent with the full-workspace walk() path, which uses Dirent.isFile()/isDirectory() and correctly skips symlinks. The likelySensitive() blocklist is also incomplete (no .ssh/, id_rsa, .npmrc, .gitconfig, aws/credentials), so sensitive files reached via symlink are not filtered. Combined with prompt injection in repo content steering the LLM to read a path, this enables exfiltration of arbitrary local files. Location: |
Why this is urgent
Installed 1.0.0 snapshots call the removed v1 REST surface and 404 on every hook. Confirmed against prod:
POST /recall/recall_preferencesPOST /memories/add_memoryPOST /query(v2, used by 1.1.0)The SDK wrapper on
mainalready moves everything to/query+/context/*+/databases/*. But installs are keyed by manifest version (plugins/cache/hydradb/hydradb/<version>/), and.claude-plugin/plugin.jsonhas said1.0.0since the initial commit — soclaude plugin updatereports "already latest" and users have to uninstall/reinstall by hand.The version bump is the delivery mechanism for a fix that already exists. That is what this PR is for.
What ships as 1.1.0
Everything on
mainsinced9185b9, plus three defects found while validating this release.Minor, not patch — three new skills (
/doctor,/ingest,/query). Not major —HYDRADB_TENANT_ID/HYDRADB_SUB_TENANT_IDstill resolve with a one-shot deprecation warning.Defects found by actually installing it
I installed the plugin from the marketplace into an isolated config dir and exercised it against live prod. Three things surfaced.
1. Injected context carried the envelope, not the prose.
Knowledge is ingested through
appKnowledge, and the server indexes that envelope verbatim — so recall returned the whole JSON object as chunk content: internal ids, emptyhtml_base64/csv_base64/markdown/files/layoutslots,tenant_metadataduplicated asapp_metadata, and the real text buried incontent.text. Every prompt paid for that, and one chunk was truncated mid-JSON at the 7000-char budget — the envelope ate the text it was meant to deliver.Unwrapped at the normalization seam in
sanitize.mjs, soquery,last-recalland the injected block are fixed together. Non-envelope content passes through untouched.Before / after, same workspace:
buildSectionis renamed tobuildContextStringand exported, matching the name and shape of HydraDB's documented reference formatter (docs: essentials/v2/api-results) that the plugin was already approximating.2.
classifyDeletiontreated "already gone" as retryable. (new in #9)It checked the batch-level
successrollup before the per-itemresults, so asuccess:falsebatch discarded the per-item reasons — which are exactly what separates a real failure from an id the server already removed (deleted:false,error:"Source not found"). Deletion is idempotent; the postcondition holds, so that is terminal success. Per-item detail now outranks the rollup, and a genuine failure alongside an already-absent id is still retained.3.
writeStatecould not express a removal. (pre-existing —state.mjsis untouched by #9)The merge re-reads disk so a concurrent single-file sync keeps its addition, but a spread only adds and overwrites — it can never drop a key. A file deleted from in-memory state was resurrected on every write. This is why fixing #2 alone reported
deleted: 1and changed nothing. Removals are now named explicitly viaremovedFilePaths, passed by the two reconciling sync call sites.Together, #2 and #3 are why a deleted file stayed tracked forever and re-attempted its delete on every sync.
Verification
Not just tests — installed and driven against prod:
claude plugin installreports 1.1.0, 14 skills, 4 hooks, pinned to the commit. Runtime imports onlyscripts/vendor/hydradb-sdk.mjs; nothing underscripts/references@hydradb/sdk, so the plugin works whether or not the npm step succeeds.remember→searchreturns the codeword. All four hooks exit 0.deleted:1, state drops the file,errors:[]; pass 2deleted:0,errors:[]— converged, no wedge. Previously this reportedknowledge delete incomplete … retained for retryforever.API-Version: 2verified live, per CONTRACT.md §6.npm run checkandpack:dry-rungreen.Also here: license drift
d9185b9relicensed MIT → Apache 2.0 but only updatedpackage.jsonandLICENSE.plugin.jsonandmarketplace.jsonstill advertised MIT — and those are what the marketplace and plugin UI show users. Both now readApache-2.0.Not addressed
repositoryin both manifests still points atusecortex/…, which 301s tohydra-db/…. Harmless for install; it does breakgh pr createon fresh clones. Left out to keep this PR scoped.