Commit 042b220
committed
fix(plugin-reports): stop
`DELETE /api/v1/reports/schedules/:scheduleId` answered `404 REPORT_NOT_FOUND`
for another owner's schedule but `204 No Content` for a schedule id that does not
exist. The caller can delete neither, yet still reads which of the two they hit
straight off the status code — an enumeration oracle over other owners' report
schedules.
This is the defect #7523 closed on the sibling `DELETE /reports/:id`, in the
costume that card explicitly warned about: there the split was 500-vs-204 and
loud, here 404-vs-204 and quiet. The route was in fact cited by #7523's
investigation as the example of the RIGHT shape, because it does route its catch
through `handleValidation` — which is why the cross-owner arm is a clean 404
rather than a 500. Only that arm was ever probed (QA run #7515); the unknown-id
arm was not, so the surviving half went unseen and `rest.test.ts:1651` pinned its
204 green.
`unscheduleReport()` carried the intent — "others get a not-found so the delete
neither fires nor reveals the schedule's existence" — and a hole one line wide
above it: `if (!schedule) return; // idempotent`. Idempotence is only harmless
where every caller may see the row; with a cross-owner arm that throws, resolving
quietly IS the tell.
Both deny arms are now one decision, taken before the delete fires, by the
predicate already blind to the difference between them: `canAccessReport` is
false for a schedule that does not exist, for one whose report is gone, and for
one owned by somebody else alike. A single throw site means a single message, so
the route's single `handleValidation` call emits a single response — status and
body cannot drift apart.
Unlike `deleteReport`, this could NOT be pre-empted in the route. That one
collapses its arms with `getReport()`, already blind to the same difference
(#2980); the caller here presents a scheduleId and `IReportService` exposes no
by-id schedule read to be blind with (`listSchedules` is keyed by reportId). The
blinding therefore lives in the service, and `IReportService.unscheduleReport`
now states it as a contract obligation rather than leaving each implementation to
rediscover it. The route keeps its half of the composition: ONE emitter for
whatever the service throws.
Deleting a schedule you own still answers 204. Deleting one you cannot see is now
404 instead of a silent 204 — the cost of closing the oracle, and in line with
the cross-owner GET / run / upsert-overwrite / delete arms, which all already
answer 404. A system context deleting an id with no row now gets
REPORT_NOT_FOUND too; the route is the only production caller.
Two pins asserted the leaking arm and are superseded IN PLACE, same input,
opposite assertion — `rest.test.ts`'s "DELETE /reports/schedules/:scheduleId
returns 204" and `report-service.test.ts`'s "an unknown schedule id is
idempotent, not a leak", whose title stated the conclusion backwards.
Tests assert the two deny arms' whole responses are EQUAL rather than pinning
each arm's status separately: a mutation answering both arms 404 with different
bodies leaves every per-arm status assertion green and turns the equality
assertions red.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4Zy4fY7Kj8t27QAEzr9G1DELETE /reports/schedules/:scheduleId revealing whether a schedule id exists (#7603)1 parent 245d1dc commit 042b220
7 files changed
Lines changed: 474 additions & 7 deletions
File tree
- .changeset
- packages
- plugins/plugin-reports/src
- rest/src
- spec/src/contracts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
Lines changed: 55 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
454 | 454 | | |
455 | 455 | | |
456 | 456 | | |
457 | | - | |
458 | | - | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
459 | 512 | | |
460 | 513 | | |
461 | 514 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
598 | 598 | | |
599 | 599 | | |
600 | 600 | | |
601 | | - | |
602 | | - | |
603 | 601 | | |
604 | 602 | | |
605 | 603 | | |
606 | 604 | | |
607 | | - | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
608 | 629 | | |
609 | 630 | | |
610 | 631 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9533 | 9533 | | |
9534 | 9534 | | |
9535 | 9535 | | |
| 9536 | + | |
| 9537 | + | |
| 9538 | + | |
| 9539 | + | |
| 9540 | + | |
| 9541 | + | |
| 9542 | + | |
| 9543 | + | |
| 9544 | + | |
| 9545 | + | |
| 9546 | + | |
| 9547 | + | |
| 9548 | + | |
| 9549 | + | |
| 9550 | + | |
| 9551 | + | |
| 9552 | + | |
| 9553 | + | |
9536 | 9554 | | |
9537 | 9555 | | |
9538 | 9556 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1648 | 1648 | | |
1649 | 1649 | | |
1650 | 1650 | | |
1651 | | - | |
| 1651 | + | |
| 1652 | + | |
| 1653 | + | |
| 1654 | + | |
| 1655 | + | |
| 1656 | + | |
| 1657 | + | |
| 1658 | + | |
| 1659 | + | |
| 1660 | + | |
| 1661 | + | |
| 1662 | + | |
| 1663 | + | |
| 1664 | + | |
| 1665 | + | |
| 1666 | + | |
| 1667 | + | |
| 1668 | + | |
| 1669 | + | |
| 1670 | + | |
| 1671 | + | |
| 1672 | + | |
| 1673 | + | |
| 1674 | + | |
| 1675 | + | |
| 1676 | + | |
| 1677 | + | |
| 1678 | + | |
| 1679 | + | |
| 1680 | + | |
| 1681 | + | |
| 1682 | + | |
1652 | 1683 | | |
1653 | 1684 | | |
1654 | 1685 | | |
| |||
0 commit comments