Skip to content

fix: close the two bugs deferred from the engine hunt - #112

Merged
ElxMaj merged 2 commits into
mainfrom
fix/deferred-hunt-bugs
Jul 19, 2026
Merged

fix: close the two bugs deferred from the engine hunt#112
ElxMaj merged 2 commits into
mainfrom
fix/deferred-hunt-bugs

Conversation

@ElxMaj

@ElxMaj ElxMaj commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Completes the two defects deferred from #111 (the engine bug hunt), each with a regression test.

1. Distill embedding reconcile (data integrity)

A node's row and its embedding vector are written in separate transactions. A transient embedding failure after the row commits left a real node with no vector — and the idempotent re-distill skip (seen.has(key)) made the miss permanent, so the fact was invisible to semantic search forever (mitigated only by the keyword fallback and graph neighbors).

Fix: at the start of a distill pass, re-embed any existing node for that evidence that is missing its vector, via a new store.hasEmbedding and an embedTextForNode that mirrors each create path's embed text. It's a no-op once everything is embedded, and a re-distill of the same source now repairs a past failure.

2. API timestamp validation (security / info disclosure)

The error classifier echoes messages matching /invalid / verbatim. A malformed since/until/before query param reached Postgres as an uncastable timestamp, whose raw error (invalid input syntax for type timestamp: "..." — engine, column type, reflected input) then got reflected to the client.

Fix: validate those params as ISO-8601 at the boundary (parseIsoParam), rejecting a bad value with a fixed 400 and a canonical ISO string on success, so the raw DB error is never produced. The classifier is left untouched (core's own product-voice messages still surface correctly).

Verification

Two new tests: a vector-less node gets re-embedded on the next distill; a bad timestamp param is a fixed 400 with no leaked DB error. pnpm typecheck, pnpm lint, root pnpm test (ℹ fail 0; core 361, web 117), pnpm smoke:packed, and check-benchmark-drift all green. Patch changeset (core + web).

With this, every finding from the engine hunt (#111 plus these two) is fixed.

🤖 Generated with Claude Code

Distill embedding reconcile (data integrity). A node's row and its embedding are
written in separate transactions, so a transient embedding failure after the row
commits left a node with no vector; the idempotent re-distill skip then made the
miss permanent, hiding a real fact from semantic search. Distill now re-embeds
any existing node missing its vector at the start of the pass (a no-op once all
are embedded), through a new store.hasEmbedding and an embedTextForNode that
mirrors each create path's embed text.

API timestamp validation (security). The since/until/before query params are now
validated as ISO-8601 at the boundary and rejected with a fixed 400, so a
malformed value never reaches Postgres as an uncastable timestamp whose raw error
(engine, column type, reflected input) the classifier would echo to the client.

Two new tests (reconcile re-embeds a vector-less node; a bad timestamp param is a
fixed 400 with no leaked DB error). typecheck, lint, root test (fail 0),
smoke:packed, and benchmark-drift all green. Patch changeset (core + web).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8022d9cc67

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/web/src/api.ts
Comment on lines +356 to +358
const t = Date.parse(raw);
if (Number.isNaN(t)) throw new ApiError(400, `${name} must be an ISO-8601 timestamp`);
return new Date(t).toISOString();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-ISO timestamps rather than relying on Date.parse

Date.parse is deliberately permissive, so malformed/non-ISO inputs such as since=June%201,%202024 are accepted, and impossible dates such as since=2024-02-30 are silently normalized to March 1 instead of receiving the documented fixed 400. This means the new boundary does not actually enforce the advertised ISO-8601 contract for /api/metrics or /api/runs; use a strict ISO shape and calendar-date validation before canonicalizing.

Useful? React with 👍 / 👎.

Comment thread packages/core/src/marrow.ts Outdated
Comment on lines +584 to +585
if (!(await this.store.hasEmbedding(node.id, node.kind))) {
await this.embedNode(node.id, node.kind, this.embedTextForNode(node));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make reconciliation embedding writes atomic

When two requests or server instances re-distill the same evidence while a node has no vector, both can observe hasEmbedding as false and then insert an embedding. The embedding schema has no unique (node_id, node_kind) constraint, so both writes succeed; nearestNodes applies its limit before deduplicating IDs, allowing duplicate rows to consume semantic-search candidate slots indefinitely. Enforce uniqueness and use an atomic insert/upsert (or otherwise serialize this check/write).

Useful? React with 👍 / 👎.

# Conflicts:
#	packages/core/src/distill.test.ts
#	packages/core/src/marrow.ts
#	packages/web/src/server.test.ts
@ElxMaj
ElxMaj merged commit 9142574 into main Jul 19, 2026
2 checks passed
@ElxMaj
ElxMaj deleted the fix/deferred-hunt-bugs branch July 19, 2026 09:09
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