feat: CLI audit + local/api parity: open-hooks - #14
Closed
andrei-hasna wants to merge 5 commits into
Closed
Conversation
…posure Addresses three P1 review findings on the local/api parity work. 1. Hook event ingestion was never ported to API mode, so `hooks log` read the remote authority while every hook kept writing to local SQLite that no api- mode command could see. `writeHookEvent` now resolves the same router the read path uses and POSTs to `/v1/log/events`; on an unreachable or incompletely configured authority the event is spooled to local SQLite (and reported on stderr) instead of dropped, and `hooks storage push` drains that spool idempotently. Adds the `POST /v1/log/events` route and a shared `buildHookEventRow`/`insertHookEvent` pair so both write paths persist the identical row. 2. `apiConfigPresent()` treated a bare API key as "the user wants HTTP", so a stray `HOOKS_API_KEY` hijacked the PostgreSQL remote/hybrid path and then failed with REMOTE_API_URL_MISSING. Routing now keys on the API URL alone, and a remote/hybrid environment carrying both a database URL and an API URL emits an explicit precedence warning instead of silently misrouting. 3. `hooks mcp --http` mounted the destructive `/v1` data API unconditionally and accepted the client-side `HASNA_HOOKS_API_KEY` as its admin credential. The mount is now behind an explicit `--api` flag that defaults to off, and the server authenticates against the separate `HASNA_HOOKS_API_SERVER_KEY` so one secret no longer serves both trust roles. Each fix is covered by a test that fails when the fix is reverted.
Two reviewer findings on the new HTTP storage/ingestion transport. schema_migrations transport (P1): `hooks storage push` sent the client's migration ledger to the authority, so a machine on a newer release could mark a migration as applied on an authority that never ran its DDL — permanently suppressing it, including the CHECK-constraint rebuilds that admit SessionStart/SessionEnd/UserPromptSubmit. The /v1 transport now carries a DATA_SYNC_TABLES allowlist (hook_events, feedback): storageExportRows defaults to it and refuses an explicitly named bookkeeping table, and storageImportRows returns an error in the SyncResult instead of upserting schema_migrations or _meta. The PostgreSQL sync path is unchanged. Unbounded fetch (P1): no request carried a deadline, so an authority that accepted the connection and never answered blocked writeHookEvent forever — every agent tool call stalled until the agent's own hook timeout killed the process and the event was dropped, contradicting the documented spool-rather-than-drop guarantee. Every /v1 request now carries an AbortSignal.timeout: 3s on the hook write path (HASNA_HOOKS_API_WRITE_TIMEOUT_MS) and 30s for interactive CLI commands (HASNA_HOOKS_API_TIMEOUT_MS). The abort surfaces as the existing REMOTE_API_UNREACHABLE classification, so db-writer's catch spools as before; a malformed override falls back to the default rather than disabling the deadline. Regression tests, each verified to fail with its fix reverted: the authority refuses a schema_migrations payload and its ledger is unchanged, the default push payload carries data tables only, and writeHookEvent resolves within the deadline against a never-responding Bun.serve with the row landing in local SQLite.
Under an API authority the local SQLite file is a spool and a pull mirror, not a second source of truth: `hooks storage pull` writes authority rows into it and `hooks storage push` uploads everything it still holds. `log clear` deleted only on the authority, so the documented drain workflow (`hooks storage push`) re-uploaded the events an operator had just purged and `hooks log list` showed them again. `hooks log list` in local mode also kept showing rows the operator believed were gone. Clear the mirror after the remote DELETE succeeds, scoped by `--hook` the same way the remote delete is. The purge runs even when the authority reported nothing cleared: rows spooled while it was unreachable exist only locally and would otherwise be pushed straight after the purge. `clearLocalHookEventMirror` returns 0 without touching the filesystem when no local database exists — `getDb()` would create the file and its schema, and an API-mode client with no spool must not grow one just to empty it. Regression coverage in src/cli/cli.test.ts drives the real CLI against a loopback authority: pull -> clear -> push -> list returns [], an unpushed spool is not pushed after a clear, `--hook` leaves other hooks' rows alone, and a clear with no mirror creates no local database.
…through the API authority `hooks log clear --yes` in API mode discarded the return value of `clearLocalHookEventMirror()`, so purging an unpushed local spool the authority never held printed "Nothing to clear." / `cleared: 0` while deleting every local row — audit history destroyed behind a message saying nothing happened. The command now reports `cleared_remote` and `cleared_local` alongside a headline `cleared` that is the larger of the two, not their sum: `storage pull` mirrors authority rows into the same local table, so adding the counts would report one pulled event twice. The four MCP log tools (`hooks_log_list`, `hooks_log_tail`, `hooks_log_errors`, `hooks_log_summary`) still read local SQLite, which the API write path no longer writes to, so they answered "no events" for work that had just landed on the authority. They now route through the same client as `hooks log …` and fail closed with the `REMOTE_*` message instead of serving an empty local result set. Supporting parity: `since` filtering on `GET /v1/log/events`, and a new `GET /v1/log/summary` route backed by the existing `summarizeHookEvents()`. `send_feedback` deliberately keeps writing local SQLite — `feedback` is a DATA_SYNC_TABLE, so the row spools and drains on the next `storage push`.
Contributor
Author
|
[REVIEW] NO_GO — #14 @ e77a40e — lens: correctness+security+gates, reviewer Augustus (1 of 1) What I read:
What I ran:
Blocking P0/P1 findings:
Non-blocking follow-ups:
Disposition:
|
Contributor
Author
|
[DISPOSITION] CLOSED by appius (lineage agent-ceo) — conflicting-PR triage, 2026-07-31. Reason: this PR carries a Nothing is lost and nothing is deleted:
Closing is the decision; a stale open PR that cannot merge is debt. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Objective
CLI audit + local/api parity: open-hooks
Repo: hasna/hooks (local: /home/hasna/workspace/hasna/opensource/open-hooks).
Audit this repo's CLI: (1) Find every DEAD command — commands that are broken, error out, reference removed code, or are declared but non-functional — and fix them or remove them with justification. (2) Find every command that only works in 'local' mode (on-box SQLite/files) and does NOT work against the 'api' (self-hosted/remote) path; implement full api-mode support for each so every command works identically against local AND api backends (route through the cloud-router/stage-A/HTTP path per this repo's existing pattern). Ensure both modes are fully implemented and tested. Add regression tests for the fixed commands and for local-vs-api parity. Open a PR; do not merge.
Verification
Run
run_c6bbd99b9b6c· backendcodewith· task632dc176-187e-4220-b7bb-6d72c3bf8263🏭 Generated by @hasnaxyz/factory
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.