Skip to content

fix(db): re-import revives a withdrawn master instead of hiding the fresh parse - #417

Merged
thewrz merged 2 commits into
mainfrom
fix/issue-415
Jul 8, 2026
Merged

fix(db): re-import revives a withdrawn master instead of hiding the fresh parse#417
thewrz merged 2 commits into
mainfrom
fix/issue-415

Conversation

@thewrz

@thewrz thewrz commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Why

Withdraw a library master, then upload the same section into the same library again — the import reports success but the spec never reappears. The upsert in persistParsedSpec rebuilt paragraphs and bumped content_version inside the tombstoned row without clearing withdrawn_at, so the fresh parse landed invisibly: hidden from GET /libraries/:id/specs, project source resolution, and MCP list_library_specs, with no discoverable UUID to restore (#416 tracks that gap separately). Live repro on the demo DB: ABC Fab's 08 11 13 reached content_version 4 while invisible; an MCP chat agent trying to restore it found "no active specs at all".

What

  • ON CONFLICT DO UPDATE now also sets withdrawn_at = NULL — a deliberate re-import is an unambiguous "this master lives again". onboarding_status stays untouched on re-import (a prior finalize still stands), and POST /specs/:id/restore remains the content-preserving restore.
  • The row upsert moves into a focused upsertParsedSpecRow helper (max-lines-per-function cap).
  • openapi.yaml: revival documented on importLibraryMaster and withdrawSpec description prose.
  • Regression test named for the symptom: re-import after withdraw: upload landed hidden in the tombstoned row — revives instead (#415).

Testing

  • Unit tests pass — 1431/1431
  • Integration tests pass — full suite on an isolated Postgres (5434): 983 passed, 126 skipped; new regression red→green
  • Manual verification: reproduced on the live demo DB, fix semantics confirmed via the regression test; the stranded master was restored via POST /specs/:id/restore
  • CI green

🤖 Co-authored by Claude Fable 5. Closes #415.

Summary by CodeRabbit

  • Documentation

    • Clarified spec withdrawal and library import behavior, including restore steps, idempotent re-withdrawal, and how imported sections are handled.
  • Bug Fixes

    • Re-importing a withdrawn library master now restores it automatically instead of creating a duplicate.
    • Re-importing an existing section now updates its content in place and preserves a single record.
    • Withdrawal now applies only to library masters; project copies are rejected consistently.

…resh parse

Withdrawing a library master (ADR-030 tombstone) and then re-uploading the
same section made the upload vanish: persistParsedSpec's upsert rebuilt the
paragraphs and bumped content_version inside the tombstoned row but never
cleared withdrawn_at, so the master stayed hidden from listings, project
source resolution, and MCP list_library_specs — with no discoverable UUID
to restore. Live repro: a master reached content_version 4 while invisible.

The conflict update now sets withdrawn_at = NULL: a deliberate re-import is
an unambiguous "this master lives again". onboarding_status is still
intentionally untouched (a prior finalize stands). The row upsert moves to
its own helper (max-lines-per-function); openapi.yaml documents the revival
on importLibraryMaster and withdrawSpec.

Regression test pins the symptom: 're-import after withdraw: upload landed
hidden in the tombstoned row — revives instead (#415)'.

Closes #415

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e63dff98-6494-4d78-afaa-edaf3d167b75

📥 Commits

Reviewing files that changed from the base of the PR and between e581e9e and 43627aa.

📒 Files selected for processing (2)
  • openapi.yaml
  • src/db/queries/specs.integration.test.ts
📝 Walkthrough

Walkthrough

The specs upsert logic in persistParsedSpec now clears withdrawn_at on conflict, reviving previously withdrawn library masters when a matching section is re-imported. A new integration test validates this behavior, and openapi.yaml descriptions for the withdraw and import endpoints are updated to document it.

Changes

Revive-on-reimport behavior

Layer / File(s) Summary
Upsert helper clears withdrawn_at on re-import
src/db/queries/specs.ts
New internal upsertParsedSpecRow helper performs the insert/conflict-update for parsed imports, additionally setting withdrawn_at = NULL on conflict; persistParsedSpec now calls this helper instead of inline SQL.
Regression test for revival on re-import
src/db/queries/specs.integration.test.ts
New test withdraws a persisted spec, re-imports the same lineage, and asserts the same row is reused with withdrawn_at cleared and content_version incremented.
API documentation for revive behavior
openapi.yaml
Updated DELETE /specs/{id} and POST /libraries/{id}/import descriptions to document restore endpoint, re-import revival/content replacement, idempotent re-withdraw, and library-master-only scoping.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

  • wrzonance/SpecR#164: Both PRs modify the persistParsedSpec upsert/conflict logic in src/db/queries/specs.ts.
  • wrzonance/SpecR#165: Both PRs modify the same persistParsedSpec conflict-path upsert logic in src/db/queries/specs.ts.
  • wrzonance/SpecR#313: Both PRs extend the soft-withdraw/restore model by changing the persistParsedSpec upsert to clear withdrawn_at on conflict.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states that re-importing revives withdrawn masters instead of hiding the fresh parse.
Linked Issues check ✅ Passed The fix clears withdrawn_at on import conflicts, adds a regression test, and updates docs as required by #415.
Out of Scope Changes check ✅ Passed All changes support the withdraw/re-import fix; no unrelated code paths or features were added.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-415

Comment @coderabbitai help to get the list of available commands.

@thewrz
thewrz marked this pull request as ready for review July 8, 2026 00:12

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openapi.yaml`:
- Around line 223-224: The API contract wording is too broad about
restore/upsert identity and should match the actual conflict key. Update the
relevant OpenAPI descriptions in openapi.yaml to say revival/replacement happens
only for the same section/source within the same library, not section alone. Use
the existing restore/import text around the `POST /specs/{id}/restore` and the
section import wording so the contract no longer promises replacement when the
parsed source differs.

In `@src/db/queries/specs.integration.test.ts`:
- Around line 306-316: Harden the regression test in specs.integration.test.ts
by asserting the withdrawn precondition before the re-import path runs. In the
`re-import after withdraw` test, verify `withdrawSpec` actually tombstones the
row by checking its result or querying `withdrawn_at` on the existing spec
before calling `persistParsedSpec` again; then keep the revival assertions
against `persistParsedSpec`, `withdrawSpec`, and the final `SELECT` on `specs`.
Also ensure the test name clearly states the symptom so the regression is pinned
to the tombstoned-row revival behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 085519ce-260e-47ca-a6fe-e2bf9f0820f9

📥 Commits

Reviewing files that changed from the base of the PR and between cae7521 and e581e9e.

📒 Files selected for processing (3)
  • openapi.yaml
  • src/db/queries/specs.integration.test.ts
  • src/db/queries/specs.ts

Comment thread openapi.yaml Outdated
Comment thread src/db/queries/specs.integration.test.ts
#415 test

CodeRabbit review on the #415 fix:

- The library re-import upsert revives on the (section, source, library_id)
  conflict key, not section alone. The restore and import descriptions
  promised replacement for "the same section", overstating the contract when
  the parsed source differs. Qualify both as "same section/source" so the
  authoritative openapi.yaml matches the actual upsert identity.
- Harden the #415 regression test: assert withdrawSpec tombstones the row
  (result shape + withdrawn_at NOT NULL) before re-import, so the test cannot
  pass via an ordinary same-row reimport if withdrawal ever stops tombstoning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thewrz

thewrz commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Codex (GPT-5.5, xhigh) ran as a second adversarial reviewer against origin/main on commit 43627aa (additional eyes; CodeRabbit reviewed normally). Verdict: no discrete correctness issues introduced by the changes — clean. Both CodeRabbit threads (openapi contract wording, #415 test hardening) fixed in 43627aa and resolved.

@thewrz
thewrz merged commit eeffd25 into main Jul 8, 2026
13 checks passed
@thewrz
thewrz deleted the fix/issue-415 branch July 8, 2026 05:08
thewrz added a commit that referenced this pull request Jul 8, 2026
Resolves openapi.yaml import-description conflict as the union of #417's
re-import revival prose and #418's hierarchy-confidence prose.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

re-import of a withdrawn master lands hidden in the tombstoned row — upload never resurfaces

1 participant