Skip to content

chore(reconcile): bring main up to published npm line (0.4.4 -> 0.4.5)#9

Merged
andrei-hasna merged 9 commits into
mainfrom
reconcile/main-to-npm-0.4.4
Jul 24, 2026
Merged

chore(reconcile): bring main up to published npm line (0.4.4 -> 0.4.5)#9
andrei-hasna merged 9 commits into
mainfrom
reconcile/main-to-npm-0.4.4

Conversation

@andrei-hasna

Copy link
Copy Markdown
Contributor

Why

main had diverged behind the published npm line. Deployed/published code was not on main:

  • origin/main sat at 0.3.36 (tip 082c698, "route ALL log reads+writes to cloud API in self_hosted mode").
  • npm @hasna/logs latest was 0.4.4.
  • The published tag npm/logs/v0.4.4 was 8 commits ahead of main (Store unification / LocalStore+ApiStore, cloud /v1 data-plane parity + POST /v1/events ingest, watch --server SSE fix, FTS5 query sanitization, releases 0.4.2/0.4.3).
  • main had zero commits that were not already on the tag (git rev-list --count npm/logs/v0.4.4..origin/main = 0).

So main was a strict ancestor of the published tag — a clean fast-forward, not a true fork. No main-only commits needed re-applying and no history is lost.

What

  • Fast-forward main up to the published tag npm/logs/v0.4.4.
  • Bump 0.4.4 -> 0.4.5 so main now sits at/above the published line.
  • Add CHANGELOG entry documenting the reconcile.
  • No functional code change — the source tree is byte-identical to the published tag (git diff npm/logs/v0.4.4 HEAD = only package.json version + CHANGELOG.md).

Verification

  • git rev-list --count HEAD..origin/main = 0 (every main commit is preserved).
  • All 8 published-tag commits present on this branch.
  • git diff npm/logs/v0.4.4 HEAD --stat = only CHANGELOG + package.json version.
  • Staged secrets scan: clean (0 findings).

Note: repo-local bun install/typecheck fail on pre-existing file: dev-dep resolution (@hasna/agent-registry, @hasna/contracts subpath exports) — a fleet-wide environment issue identical at the base tag, not introduced here; the published 0.4.4 built and shipped fine.

After merge, the 4 open PRs (#8 security, #2, #3, #1) should be rebased onto reconciled main and re-triaged against the correct (published) line.

The MCP server used the local SQLite store unconditionally (getDb/ingestLog),
so with the fleet flip env set (HASNA_LOGS_API_URL + HASNA_LOGS_API_KEY) the
CLI routed to cloud but the MCP kept reading/writing local — the installed HTTP
MCP service (dist/mcp) never carried the cloud path because it never imported
the resolver (tree-shaken out).

Mirror the CLI's logsCloud() in the MCP: log_push, log_push_batch, log_search,
log_tail, log_count and log_recent_errors now resolve resolveLogsCloudStore()
per call and hit the cloud HTTP API when the flip resolves, else fall back to
the local store (fully reversible: unset the env vars -> local). Contracts is
already inlined into every entry bundle, so adding the import pulls the cloud
client into dist/mcp. Bump to 0.3.37-cloud2 so fixed vs stale is grep-distinct.

Verified end-to-end over the streamable-HTTP MCP against https://logs.hasna.xyz/v1:
push returns a cloud UUID, search reads it back, zero local-sqlite leak, on
spark01 + station02/03/05. Tests: 25 pass / 0 fail.
Introduce a single Store interface with two transports resolved from the
environment: LocalStore (on-box SQLite, sole owner of getDb) and ApiStore
(HTTP /v1 + bearer key; self_hosted and cloud both use it). Route every CLI
command and MCP tool data-plane operation through resolveStore(), removing
the per-command cloud?:local split-brain branches and handler-level getDb.

- Add src/store/{types,local,api,index}.ts; export via @hasna/logs/storage.
- Add /v1/logs/count, /v1/logs/summary, /v1/health cloud endpoints (+ cloud
  store aggregates) for the ApiStore data-plane. Requires ECS redeploy.
- Local-only infra/compute commands+tools (run, import-jsonl, scan, watch,
  events push/export, diagnose, event-store repair, alerts, issues, perf,
  session-context) refuse cleanly in api mode via requireLocalMode.
- ApiStore surfaces a clear local-only error for features with no cloud data
  model (events catalog, test reports, pages, scan jobs).
- Add top-level CLI error boundary; delete dead cloud-store.ts + test; prune
  dead imports; bump 0.3.37-cloud2 -> 0.4.0.
…the Store

- Delete forbidden DSN-on-client storage push/pull/sync: drop src/lib/storage-sync.ts
  and src/lib/remote-storage.ts (pg Pool from raw RDS DSN), the CLI 'logs storage'
  command group, and the MCP storage_* tools. The raw RDS DSN is never distributed
  to clients (CLAUDE.md §2).
- Remove the forbidden 'hybrid'/'remote' StorageMode type (lived in storage-sync).
- Confine getDb()/bun:sqlite to the store implementation: all CLI commands and MCP
  tools now route through Store. Local-only maintenance/compute ops (import-jsonl,
  run, db doctor, events push/export, scan, diagnose, watch, log_* analytics,
  alerts, issues, perf, feedback, event_watch) become LocalStore methods reached
  via requireLocalStore(); MCP self-telemetry uses a best-effort local store.
- Extract event-catalog watch queries into src/lib/event-watch.ts (db-parameterized).
- Preserve the table inventory in src/lib/storage-tables.ts (no connection code).
…ch to a transport

- watch called requireLocalStore("watch") before the --server branch, so
  'logs watch --server <url>' threw the local-only error in self_hosted/cloud
  mode. Handle --server first (out-of-band operator-named SSE tail), resolving
  the project id best-effort via the mode-resolved store; require the local
  store only for the on-box event-catalog/log-row paths.
- Move the raw SSE fetch out of the CLI command body into a documented
  event-stream transport (src/lib/event-stream-client.ts); no command handler
  performs raw fetch to the app's own event data.
- Add an api-mode regression test for watch --server.
Route the entire data plane through the Store in self_hosted/cloud mode so
the fleet flip keeps parity with the local SQLite store, and delete orphaned
modules flagged by the adversarial review.

ApiStore: replace the unsupported() throwers (events, test reports, pages,
scan jobs) with real /v1-backed implementations, and add the operations that
were previously forced local via requireLocalStore — perf snapshots/trend,
issues list/update, alert rules, feedback, session context, log-context-by-id,
events export, and the diagnose/compare analytics. These now sit on the Store
interface (types.ts) and are implemented by both LocalStore (async) and ApiStore.

Cloud server (src/server/cloud): add the matching /v1 routes and CloudLogStore
methods backed by the existing shared Postgres tables (all authed with
logs:read / logs:write scope guards). Raw event envelopes stay null in cloud
(they live in local append-only segment files).

CLI/MCP: stop shadowing the resolved store with requireLocalStore for these
data operations; route through the unified store instead.

Dead code: delete src/lib/lighthouse.ts (orphaned) and src/lib/rotate.ts +
its test (superseded by retention.ts). Drop the now-dead --external lighthouse
build flag.

Local-only compute that legitimately has no cloud data model (universal-event
push, event_watch tail, log watch/follow, subprocess capture, db repair,
browser scan) remains on the concrete LocalStore via requireLocalStore.

Tests: add cloud /v1 parity coverage. bun test green (396), build + biome clean.
…ease 0.4.2

Close the last cloud data-plane 404: the ApiStore posts telemetry to
POST /v1/events (used by `logs run` capture, `events push`, and the SDK/MCP)
but the cloud Hono app only served GET /v1/events, so every event write 404'd
in api mode. Add v1.post("/events") (single + `{events:[...]}` batch, same
response shapes as the local /api/events route) backed by a new
CloudLogStore.createEvent that validates+normalizes+redacts through the shared
DB-agnostic pass (identical redaction to local; secrets never reach Postgres),
dedupes on event_id, and persists to event_records (raw envelope stays
local-only, so raw is null in cloud).

Extract normalizeAndRedactUniversalEvent from universal-ingest so local SQLite
and cloud Postgres ingest share one redaction pipeline.

Safety: `logs doctor rebuild-index` clears + replays SQLite projections
(logs/issues/spans/test reports/...) from raw segments via DELETE FROM logs.
It now snapshots the DB first (backupLogsDb, WAL-checkpointed copy) and prints
the backup path, so a pruned/damaged segment store can never silently drop
rows without recourse.

Tests: cloud POST /v1/events round-trip + idempotency + 422/403; full suite green.
NOTE: the cloud endpoints require an ECS redeploy of this server build to take
effect on the live https://logs.hasna.xyz service.
…literal

Local-mode `logs list --text`/`log_search` passed raw user input straight
to the SQLite FTS5 MATCH expression, so any query-syntax character (e.g. a
hyphen) was parsed as an operator: 'mcp-qa' -> 'mcp NOT column:qa' -> throws
"no such column: qa" (exit 1). Cloud/Postgres (ILIKE) was unaffected.

Add toFtsMatchQuery() to tokenize on whitespace and wrap each token in a
quoted literal phrase (doubling embedded quotes), neutralizing FTS5 operators
while preserving implicit AND-of-terms for multi-word queries. Regression
tests cover hyphens, multi-hyphen ids, embedded quotes, and multi-word AND.
main had diverged behind the registry: it sat at 0.3.36 (082c698) while npm
latest was 0.4.4. The published tag npm/logs/v0.4.4 was 8 commits ahead of main
(Store unification, cloud /v1 data-plane parity + POST /v1/events ingest,
watch --server SSE fix, FTS5 query sanitization, releases 0.4.2/0.4.3) and main
had zero commits absent from the tag. main was a strict ancestor of the tag, so
this reconcile is a clean fast-forward; no main-only commits to re-apply and no
history lost. Bump 0.4.4 -> 0.4.5 + CHANGELOG; no functional code change.
@andrei-hasna
andrei-hasna merged commit 53589b2 into main Jul 24, 2026
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