Skip to content

feat(libraries): includeWithdrawn listing to discover withdrawn masters - #441

Merged
thewrz merged 3 commits into
mainfrom
feat/issue-416
Jul 10, 2026
Merged

feat(libraries): includeWithdrawn listing to discover withdrawn masters#441
thewrz merged 3 commits into
mainfrom
feat/issue-416

Conversation

@thewrz

@thewrz thewrz commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Why

Withdrawn library masters (ADR-030 soft-delete/tombstone) were undiscoverable through the API and MCP. GET /libraries/:id/specs and the list_library_specs tool hid them with no opt-in, while POST /specs/:id/restore and restore_spec require the spec UUID — which nothing could surface once the master was hidden. Net effect: a withdrawn master was effectively unrestorable unless its UUID had been recorded beforehand. A live MCP chat session hit exactly this ("I also don't have a tool here to list withdrawn library specs directly by section before restoring").

PR #417 already fixed the most common recovery path (re-upload revives the master). This closes the remaining deliberate browse-and-restore discoverability gap.

What

  • GET /libraries/:id/specs?includeWithdrawn=true returns withdrawn masters. Every row now carries withdrawnAt (null for active, ISO-8601 when withdrawn). The default listing still hides withdrawn masters — unchanged behaviour.
  • list_library_specs gains a parity includeWithdrawn boolean param (still read tier), so an agent can find the UUID restore_spec needs.
  • listLibrarySpecs takes includeWithdrawn (default false); the withdrawn filter becomes ($2 OR withdrawn_at IS NULL), always selecting withdrawn_at.
  • openapi.yaml documents the query param and the withdrawnAt field (required, nullable date-time), keeping the REST contract gate green; the MCP contract gate stays green (mapping unchanged, list_library_specs remains an INV5_READ_PENDING read tool).

Design decisions

  • withdrawnAt is always present on every row (not only when includeWithdrawn=true), null for active masters. This mirrors GET /specs/:id, which already surfaces withdrawnAt: null for active masters, and lets one LibrarySpec schema cover both cases (no discriminated response shape). "Default behaviour unchanged" is read as the set of rows returned is unchanged — the extra withdrawnAt: null field is additive and non-breaking.
  • Positional includeWithdrawn param on listLibrarySpecs(libraryId, includeWithdrawn, db), matching the house convention where db = pool stays last (e.g. removeSectionFromProject(projectId, specId, force, pool)), rather than an options object.
  • Dedicated ListLibrarySpecsShape in the MCP layer rather than extending the shared LibraryIdShape (which rename_library also spreads), so the new param is scoped to the one tool that uses it.
  • Demo Library view (point 3 of the issue) deferred to follow-up. The REST + MCP discoverability that unblocks restore is the core scope; no web_ui_demo/ is present in this worktree, and a greyed-out/Restore render is an independent, UI-only change. Filing as follow-up keeps this PR one demonstrable change.

Testing

  • Unit tests pass (pnpm test — 1616 passed)
  • Lint passes (pnpm lint — eslint + tsc + prettier)
  • Integration tests for the change pass (REST libraries + MCP library-management + REST & MCP contract gates — 51 passed in isolation)
  • Manual verification: default listing hides withdrawn + surfaces withdrawnAt: null; includeWithdrawn=true returns the withdrawn master with a timestamp and the exact UUID restore needs
  • CI green

Note: the full local integration suite shows pre-existing flaky failures in unrelated files (parser/onboarding/parse/specs paragraph-count tests) caused by shared-Postgres cross-file races — the failing set rotates between runs and never includes the files this PR touches. This change only adds a read-query param and has no causal path to those tests.

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

Summary by CodeRabbit

  • New Features
    • Added an optional includeWithdrawn=true option to library specification listings.
    • Withdrawn specifications remain hidden by default and are included only when explicitly requested.
    • Library specifications now report a withdrawnAt timestamp for withdrawn entries, or null for active entries.
    • Added the same withdrawn-specification filtering option to the library management tool.

…rs (#416)

Withdrawn library masters (ADR-030) were undiscoverable through the API/MCP:
`GET /libraries/:id/specs` and `list_library_specs` hid them with no opt-in,
and `POST /specs/:id/restore` / `restore_spec` need a spec UUID that nothing
could surface once the master was hidden. A withdrawn master was effectively
unrestorable unless its UUID was recorded beforehand.

Add an opt-in that closes the browse-and-restore loop, surfacing the UUID
`restore` needs:

- `GET /libraries/:id/specs?includeWithdrawn=true` returns withdrawn masters;
  every row now carries `withdrawnAt` (null for active). Default behaviour
  (withdrawn hidden) is unchanged.
- `list_library_specs` gains a parity `includeWithdrawn` param (read tier).
- `listLibrarySpecs` takes `includeWithdrawn` (default false); the withdrawn
  filter becomes `($2 OR withdrawn_at IS NULL)`.
- openapi.yaml documents the query param and the `withdrawnAt` field (required,
  nullable date-time) — keeps both contract gates green.

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

coderabbitai Bot commented Jul 10, 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: b8055826-b43a-4877-9632-71d505c10a8f

📥 Commits

Reviewing files that changed from the base of the PR and between 3ec6e48 and 9ee8341.

📒 Files selected for processing (7)
  • openapi.yaml
  • src/api/libraries.integration.test.ts
  • src/api/libraries.ts
  • src/db/queries/libraries.ts
  • src/mcp/library-management-handlers.ts
  • src/mcp/library-management-tools.ts
  • src/mcp/library-management.integration.test.ts
📝 Walkthrough

Walkthrough

The library spec listing API and MCP tool now accept includeWithdrawn, expose nullable withdrawnAt timestamps, hide withdrawn masters by default, and include integration tests for both behaviors.

Changes

Withdrawn spec discovery

Layer / File(s) Summary
Query contract and withdrawn mapping
src/db/queries/libraries.ts
LibrarySpec includes withdrawnAt, and listLibrarySpecs conditionally filters withdrawn masters while mapping withdrawal timestamps.
HTTP listing contract and behavior
openapi.yaml, src/api/libraries.ts, src/api/libraries.integration.test.ts
The HTTP endpoint documents and forwards includeWithdrawn; tests cover default filtering, active rows, and opted-in withdrawn rows.
MCP listing contract and behavior
src/mcp/library-management-handlers.ts, src/mcp/library-management-tools.ts, src/mcp/library-management.integration.test.ts
The MCP tool validates, documents, and forwards includeWithdrawn, with tests for active and withdrawn results.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant listLibrarySpecsHandler
  participant listLibrarySpecs
  participant Database
  Client->>listLibrarySpecsHandler: Request specs with includeWithdrawn
  listLibrarySpecsHandler->>listLibrarySpecs: Forward libraryId and flag
  listLibrarySpecs->>Database: Query specs with conditional withdrawal filter
  Database-->>listLibrarySpecs: Return withdrawn_at data
  listLibrarySpecs-->>Client: Return specs with withdrawnAt
Loading

Possibly related PRs

  • wrzonance/SpecR#227: Introduced the library spec listing flow extended by this change.
  • wrzonance/SpecR#313: Added withdrawn-master semantics and withdrawnAt handling used by this listing behavior.
  • wrzonance/SpecR#346: Introduced the MCP list_library_specs flow extended with includeWithdrawn.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements the requested REST and MCP includeWithdrawn support, returns withdrawnAt, and preserves default hidden behavior.
Out of Scope Changes check ✅ Passed The changes stay focused on withdrawn-master discoverability, documentation, and tests, with no unrelated scope added.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding includeWithdrawn support to library spec listings to surface withdrawn masters.

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

The sonarjs/no-alphabetical-sort rule rejects a bare .sort() on the
string arrays in the includeWithdrawn assertion; supply an explicit
localeCompare comparator so ordering is well-defined.

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

thewrz commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@thewrz
thewrz marked this pull request as ready for review July 10, 2026 20:47

@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: 1

🧹 Nitpick comments (1)
src/api/libraries.ts (1)

46-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider validating includeWithdrawn with Zod instead of a raw string comparison.

The rest of the handler follows the established pattern correctly (404 on missing library, then call listLibrarySpecs). However, includeWithdrawn is derived from req.query via a bare === 'true' check rather than through a Zod schema.

As per coding guidelines: "Validate external input, including request bodies, environment variables, and parsed XML/OOXML, with Zod."

♻️ Proposed refactor
-  const includeWithdrawn = req.query['includeWithdrawn'] === 'true';
+  const includeWithdrawn = z
+    .enum(['true', 'false'])
+    .optional()
+    .default('false')
+    .transform((v) => v === 'true')
+    .parse(req.query['includeWithdrawn']);
🤖 Prompt for 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.

In `@src/api/libraries.ts` around lines 46 - 55, Validate the includeWithdrawn
query parameter in the handler using a Zod schema rather than directly comparing
req.query values to 'true'. Define or reuse a schema that parses the query input
into a boolean, handle validation failures consistently with the handler’s
existing error response pattern, and pass the parsed value to listLibrarySpecs.

Source: Coding guidelines

🤖 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 `@src/mcp/library-management-handlers.ts`:
- Around line 60-64: Update the validation failure branch in the list library
specs handler to use the parsed Zod error via issues(parsed.error), rather than
the fixed “libraryId must be a UUID” message, so failures identify whether
libraryId or includeWithdrawn is invalid.

---

Nitpick comments:
In `@src/api/libraries.ts`:
- Around line 46-55: Validate the includeWithdrawn query parameter in the
handler using a Zod schema rather than directly comparing req.query values to
'true'. Define or reuse a schema that parses the query input into a boolean,
handle validation failures consistently with the handler’s existing error
response pattern, and pass the parsed value to listLibrarySpecs.
🪄 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: bb00b017-6222-41b9-bf55-ee76d68d2deb

📥 Commits

Reviewing files that changed from the base of the PR and between 3ec6e48 and 37cc35f.

📒 Files selected for processing (7)
  • openapi.yaml
  • src/api/libraries.integration.test.ts
  • src/api/libraries.ts
  • src/db/queries/libraries.ts
  • src/mcp/library-management-handlers.ts
  • src/mcp/library-management-tools.ts
  • src/mcp/library-management.integration.test.ts

Comment thread src/mcp/library-management-handlers.ts
The handler hard-coded 'libraryId must be a UUID', but the schema now
also validates the optional includeWithdrawn boolean; a bad
includeWithdrawn reported the wrong field. Use the existing issues()
helper so the message names the actual failing field, matching the
rename_library and create_client_library handlers in this file.

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

thewrz commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Codex (GPT-5.5, xhigh) adversarial review — 2 P2 findings, 0 P1. Outcomes:

[P2] src/mcp/library-management-handlers.ts — misleading validation error → FIXED in 9ee8341.
The handler hard-coded 'invalid list_library_specs input: libraryId must be a UUID', but the schema now also validates the optional includeWithdrawn boolean, so a bad includeWithdrawn reported the wrong field. Switched to the existing issues(parsed.error) helper already used by rename_library and create_client_library in the same file, so the message names the actual failing field.

[P2] src/api/libraries.ts:48includeWithdrawn query parsed as === 'true' rather than Zod-validated with a 400 on malformed input → DECLINED.
This is the established house pattern for boolean query params: src/api/projects.ts:173 parses force identically (req.query['force'] === 'true'). Introducing strict Zod validation + 400 for includeWithdrawn alone would diverge from force and create inconsistency before a cross-module decision to validate all boolean query params. The shorthand is also safe — Express array/object query values evaluate to false, which is the safe default (withdrawn stay hidden); there is no crash or injection path. If we want strict query-param validation repo-wide, that belongs in its own cross-cutting change, not this PR.

Codex verdict: approve after the two small cleanups — the SQL uses bound params, the boolean predicate and GROUP BY s.withdrawn_at are correct, default listing still hides withdrawn specs, withdrawnAt is always mapped, OpenAPI documents the new query/field, and the tool remains read-tier.

Comment thread src/api/libraries.ts
@thewrz

thewrz commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@thewrz

thewrz commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 24 minutes.

@thewrz
thewrz merged commit d1b0300 into main Jul 10, 2026
15 checks passed
@thewrz
thewrz deleted the feat/issue-416 branch July 10, 2026 21:51
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.

withdrawn masters are undiscoverable — no includeWithdrawn listing, MCP restore requires an unfindable UUID

1 participant