Skip to content

fix(estimate): read-only estimation + collision-proof story-ID prefix#78

Merged
tzone85 merged 1 commit into
mainfrom
fix/estimate-readonly-collision
Jun 23, 2026
Merged

fix(estimate): read-only estimation + collision-proof story-ID prefix#78
tzone85 merged 1 commit into
mainfrom
fix/estimate-readonly-collision

Conversation

@tzone85

@tzone85 tzone85 commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Ports the VXD fix (vortex-dispatch #82) — the identical bug exists in NXD's planner/estimator.

Problem

nxd estimate crashes on the second run with UNIQUE constraint failed: stories.id:

  1. Estimation ran the real planner, which persists STORY_CREATED into the project DB (an estimate should be read-only).
  2. Story IDs were namespaced by truncating the reqID to prefix[:8] — estimate reqIDs (est-YYYYMMDD-…) all collapse to the constant est-2026; ULID reqIDs collide within ~256ms.

Fix

  • Planner.PlanEphemeral — same decomposition, all event-store/projection writes gated off. Estimator uses it.
  • storyIDPrefix(reqID)≤8 chars verbatim; longer → sha256(reqID) first 8 hex.

Scope / offline-first

Planner + estimator only. No cloud providers, Codex, or Anthropic model-ID changes — those VXD changes are cloud-specific and deliberately not ported, keeping NXD's Ollama offline-first core intact.

Tests

storyIDPrefix uniqueness/determinism; PlanEphemeral persists nothing; estimator survives repeated submissions with zero stories leaked; 2 integration tests updated. Full suite green.

…refix

Ports the VXD fix (vortex-dispatch #82) — the same bug exists here.

`nxd estimate` would crash on the second run of the year with "UNIQUE
constraint failed: stories.id". Two causes:

1. Estimation ran the real planner, which persists STORY_CREATED into the
   project DB. An estimate is a read-only quote and must not write
   requirement/story rows. New Planner.PlanEphemeral runs the same
   decomposition with every event-store/projection write gated off;
   Estimator uses it.
2. Story IDs were namespaced by truncating the reqID to its first 8 chars.
   Estimate reqIDs ("est-YYYYMMDD-...") all collapsed to the constant
   "est-2026"; ULID reqIDs collided within ~256ms (a ULID's entropy is in
   its trailing chars). storyIDPrefix now hashes the full reqID (sha256,
   first 8 hex) for IDs >8 chars; short IDs stay verbatim.

Offline-first core untouched — this is the planner/estimator only; no cloud
providers or model-ID changes (those VXD changes are not applicable to NXD).

Tests: storyIDPrefix uniqueness/determinism, PlanEphemeral persists nothing,
estimator survives repeated submissions with zero stories leaked. Two
integration tests updated to the new prefix scheme. Full suite green.
@tzone85 tzone85 merged commit 081679f into main Jun 23, 2026
10 checks passed
@tzone85 tzone85 deleted the fix/estimate-readonly-collision branch June 23, 2026 11:37
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