Skip to content

Commit 008f06d

Browse files
committed
docs(qa): close out D1 in the platform checklist + codify run-record home
D1 (saved-report schedule routes) was fixed in #6683, so this reconciles the checklist and the follow-up register with the fixed state: - dashboards.saved-report-ownership: flip the known-gap probe clause from an expected-FAIL probe to a positive deny-as-404 assertion (revision 1->2, history entry ref #6683); drop the expected-FAIL language in negative/source. - FOLLOW-UPS.md: mark D1 fixed in #6683 and remove the exploit detail. - runs/README.md: name the canonical home for run records — one "[sweep] vN release test sweep" tracking issue per release (resolves the open archival question in #6681). Docs only; check:platform-checklist green (178 items, 27 mapped / 2 waived). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L8aEBrJVxRnA5XVRkeVft9
1 parent ea1d916 commit 008f06d

3 files changed

Lines changed: 30 additions & 11 deletions

File tree

docs/qa/platform-checklist/FOLLOW-UPS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ decide on, and docs that promise retired capabilities.
1111
These are real runtime/UI defects the gap hunters hit while grounding items. Each is
1212
captured inside the relevant checklist item as an **expected-fail probe** (so a run
1313
records the actual behavior instead of ticking green), but they are defects, not test
14-
gaps. Security-sensitive ones were deliberately NOT filed publicly — your call.
14+
gaps. The one security-sensitive finding (D1) has since been fixed in #6683.
1515

1616
| # | defect | evidence | captured in | sensitivity |
1717
|---|---|---|---|---|
18-
| D1 | **Saved-report schedule routes lack an owner check**`report-service.ts` `unscheduleReport`/`listSchedules` ignore `_context`, so user B can delete user A's report schedule (cross-owner destructive access). The read/run/delete routes ARE owner-gated (deny-as-404); only the schedule routes leak. | packages/plugins/plugin-reports/src/report-service.ts (unscheduleReport/listSchedules) | dashboards.saved-report-ownership (known-gap probe clause) | **SECURITY — not filed publicly; awaiting your decision** |
18+
| D1 | **Saved-report schedule routes now owner-gated**the schedule delete/list routes were brought under the same parent-report owner check as the other report routes (deny-as-404). | packages/plugins/plugin-reports/src/report-service.ts | dashboards.saved-report-ownership (positive assertion since rev 2) | **FIXED in #6683** |
1919
| D2 | **AppManagementPage enable/disable/set-default/delete are client-only stubs** — the handlers call `toast.success()` with a `TODO: Replace with real API call` and issue no request; an admin sees "success" while nothing changes. | objectui apps/console/src/pages/system/AppManagementPage.tsx | platform-core.app-management-toggle (expected-fail probe) | UX-integrity — safe to file |
2020
| D3 | **`useGlobalUndo.executeOp` issues a bare `ds.update` with no `ifMatch`** — record undo can silently clobber a concurrent edit (no OCC guard on the undo path). | objectui react/src/hooks/useGlobalUndo.ts | records-forms.record-edit-undo (observe-and-flag clause) | correctness — safe to file |
2121
| D4 | **`SharedViewLink` builds dead `/share/<object>/<view>?token=` URLs** — client-generated token, no matching console route (only `/s/:token`), no server persistence. Registered but unused. | objectui plugin-view/src/SharedViewLink.tsx | — (not an item; demo-grade) | low — file a cleanup issue |

docs/qa/platform-checklist/areas/dashboards.json

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,7 @@
768768
"title": "Saved reports are owner-isolated: cross-owner read/run/delete deny as 404, schedules included",
769769
"since": "v15.1",
770770
"status": "active",
771-
"revision": 1,
771+
"revision": 2,
772772
"priority": "P1",
773773
"surface": "api",
774774
"personas": [
@@ -786,7 +786,7 @@
786786
"as A: POST /api/v1/reports (201) and POST /api/v1/reports/:id/schedule; record both ids",
787787
"as B: GET /api/v1/reports/:idA, DELETE /api/v1/reports/:idA, POST /api/v1/reports/:idA/run — capture each",
788788
"as B: GET /api/v1/reports (list) with and without ?ownerId=A — capture row sets",
789-
"as B: GET /api/v1/reports/:idA/schedules and DELETE /api/v1/reports/schedules/:scheduleIdA — capture each (see the known-gap clause)",
789+
"as B: GET /api/v1/reports/:idA/schedules and DELETE /api/v1/reports/schedules/:scheduleIdA — capture each (both owner-gated since #6683)",
790790
"anonymous: GET /api/v1/reports",
791791
"as A afterwards: re-read the report and schedule to prove nothing was destroyed"
792792
],
@@ -810,9 +810,9 @@
810810
"evidence": "the response"
811811
},
812812
{
813-
"clause": "KNOWN-GAP PROBE — schedule routes: unscheduleReport and listSchedules currently ignore the caller context (report-service.ts), so B deleting A's schedule succeeds today; the contract this item asserts is deny-as-404, so record the actual outcome and treat a 2xx as a FAIL with a privately-raised finding (do NOT file publicly without maintainer decision — cross-owner destructive access)",
813+
"clause": "schedule routes are owner-gated too (#6683): B's GET /reports/:idA/schedules returns an empty list (never A's recipients/cron), and B's DELETE /reports/schedules/:scheduleIdA answers 404 REPORT_NOT_FOUND — never a cross-owner 2xx",
814814
"oracle": "api",
815-
"verify": "the two schedule-route responses + A's schedule surviving (re-read as A)",
815+
"verify": "the two schedule-route responses' status+body + A's schedule surviving (re-read as A)",
816816
"evidence": "responses + the survival read"
817817
},
818818
{
@@ -823,14 +823,14 @@
823823
}
824824
],
825825
"negative": [
826-
"any cross-owner 2xx anywhere on /api/v1/reports* is a FAIL; the schedule-route clause documents the one place a FAIL is expected TODAY — a run must not tick it green until the owner check lands"
826+
"any cross-owner 2xx anywhere on /api/v1/reports* is a FAIL — including the schedule routes, which #6683 brought under the same canAccessReport gate (they leaked before)"
827827
],
828828
"traps": [
829829
"wrong-persona"
830830
],
831831
"source": [
832832
"packages/rest/src/rest-route-ledger.ts (reports family)",
833-
"packages/plugins/plugin-reports/src/report-service.ts (canAccessReport #2980; the unchecked unscheduleReport/listSchedules)",
833+
"packages/plugins/plugin-reports/src/report-service.ts (canAccessReport #2980; unscheduleReport/listSchedules owner-gated in #6683)",
834834
"packages/platform-objects/src/audit/sys-saved-report.object.ts",
835835
"docs/plans/release-15.1-test-plan.md §A10 (#2980/#2981/#2975)"
836836
],
@@ -840,6 +840,12 @@
840840
"date": "2026-08-07",
841841
"change": "initial — 15.1 §A10 was never imported; the sweep also surfaced the unchecked schedule routes, recorded here as an expected-FAIL probe",
842842
"ref": "claude/platform-test-checklist-ocwugl"
843+
},
844+
{
845+
"revision": 2,
846+
"date": "2026-08-08",
847+
"change": "D1 fixed in #6683 — unscheduleReport/listSchedules now owner-gated; flipped the known-gap probe clause to a positive deny-as-404 assertion and dropped the expected-FAIL language in negative/source",
848+
"ref": "#6683"
843849
}
844850
]
845851
},

docs/qa/platform-checklist/runs/README.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,22 @@ land in the repo (`.gitignore` here tracks only this README). The durable source
88
the checklist itself under `../areas/`; a run is a snapshot that goes stale the moment
99
the build moves.
1010

11-
**Where results go instead:** the executing environment — a CI artifact, the runner's
12-
own workspace, the sweep's tracking issue, or an external QA store. Keep them there;
13-
do not commit them.
11+
**Where results go instead — the canonical home is one tracking issue per release
12+
sweep** (a `[sweep] vN release test sweep` issue, successor to the #3358 model):
13+
14+
- the issue **body** hosts the human-readable summary — the per-item verdict table
15+
(pass / partial / fail / blocked) and the filter that selected them (`since:vN` ∪ all
16+
`P0` ∪ items whose `source` cites a release PR);
17+
- the machine **run-record JSON(s)** (shape below) attach to that issue — pasted in a
18+
comment or linked as a CI artifact from the sweep job;
19+
- every `fail` becomes its **own linked issue** (RUNNER.md makes a filed issue part of a
20+
completed `fail` verdict), cross-referenced from the sweep issue.
21+
22+
A raw CI artifact or an external QA store is a fine substitute where one exists, but the
23+
per-release tracking issue is the default so a sweep is never lost. What NEVER lands in
24+
the repo is the record itself — only the durable ledger under `../areas/` accumulates
25+
here, through each item's `revision`/`history`. A verdict is interpretable only next to
26+
the `revision` it names, so the run record stays with its build's artifacts, not in git.
1427

1528
## Record shape (write to `YYYY-MM-DD-<slug>.json`, kept out of git)
1629

0 commit comments

Comments
 (0)