mikebridge commented on PR #41551:
URL: https://github.com/apache/superset/pull/41551#issuecomment-5142155920

   # Capstone review — versioning as a whole
   
   This is a different animal from the three diff reviews already posted above: 
a 10-lens panel over the **entire versioning feature as one system** — the 
merged backend (`superset/versioning/`, routes, migrations, config) together 
with this branch's UI — rather than this PR's diff. Findings against merged 
code are marked as backend follow-ups, not asks on this PR; the "Read against 
the merge plan" section at the bottom sorts every item into what belongs here 
vs what comes after.
   
   **Date:** 2026-07-31 · **Head:** `50eb210534`
   **Scope:** merged backend + open UI branch and its integration points. 
Soft-deletion/purge excluded — it has its own review track.
   **Panel:** 10 lenses run in parallel — python, sqlalchemy, react, css, 
clean-code, tidy-first, domain-modelling, continuous-delivery, 
committer-perspective, SaaS-operations. Each was instructed to verify claims 
against the actual code, skip this PR's already-known open Medium/Lows, and 
look for what diff reviews structurally miss.
   **⚑ = found independently by two or more lenses.**
   
   Per-lens verdicts: no lens issued a BLOCKER. CD: *land it*. Committer: 
*request changes at the epic level* (two HIGH code items + SIP process). React: 
*needs changes* (two HIGHs on the UI branch). Others: no blockers / polish 
first.
   
   ---
   
   ## HIGH
   
   ### H1 ⚑⚑ `/versions/` family and restore don't discriminate against 
integer-id reuse — a successor entity inherits a hard-deleted predecessor's 
history
   `superset/versioning/queries.py:157-207,336,407,482` · 
`superset/versioning/restore.py:134-143` — **python-review + 
superset-committer-review, independently**
   
   Every version lookup (`list_versions`, `_get_version_count`, 
`current_live_transaction_id`, `resolve_version`, `get_version`) and the 
restore target query filter shadow rows on bare `ver_cls.id == entity.id`. Hard 
delete does not prune shadow rows; on SQLite/MySQL id reuse, the successor 
lists the predecessor's versions, `GET /versions/<v>/` returns the 
predecessor's full snapshot to a principal with read on the **successor only** 
(SECURITY.md read-capability row; assumed principal Gamma), and `POST 
…/restore` writes the predecessor's content — including its versioned `uuid` 
column, which no `__versioned__` exclude covers — onto the successor, 
corrupting its API identity. The activity layer already pins `(id, uuid)` for 
exactly this hazard (`activity/queries.py:94-115,546-614`), so the backend 
disagrees with itself. **Fix:** add the same uuid predicate to the `/versions/` 
family and restore's target lookup. Backend — needs its own PR against merged 
code.
   
   ### H2 Version payloads expose `username`
   `superset/versioning/schemas.py:31-37` · `queries.py:125,142` — 
**superset-committer-review**
   
   `VersionChangedBySchema` includes `username` in every `/versions/` row and 
snapshot `_version` block, against the project posture (`dashboards/schemas.py` 
excludes it; this epic's own `ActivityChangedBySchema` omits it "by design"). 
Any read-capable principal — including embedded guest tokens per H12 — can 
enumerate past editors' login names. Breaking to remove after release; cheap 
now. Coordinate with `types.ts:72`. Backend PR.
   
   ### H3 Chart preview never leaves the "applying" state — Restore never 
appears for chart previews **[CONFIRMED]**
   `ChartVersionPreview.tsx:115-205` · `reducer.ts:176` · 
`PreviewBanner.tsx:82-101` — **react-review**; confirmed by grep: 
`versionPreviewApplied` is dispatched from exactly one place, the dashboard hook
   
   `SET_VERSION_PREVIEW` unconditionally enters `isPreviewApplying`; only 
`useDashboardVersionPreview.ts:341` ever dispatches `versionPreviewApplied`. 
The chart path tracks loading in local state and never clears the store flag, 
so every chart preview's banner reads "Loading historical version" forever and 
the Restore button (gated on `!isApplying`) never renders. Introduced by the 
loading-state fix (`6f1771cff9`); tests mask it by hand-crafting 
`isPreviewApplying: false` instead of driving the real action. **This breaks 
the primary chart restore flow on #41551. Verify and fix on the branch 
immediately.**
   
   ### H4 Restore lacks the unsaved-changes guard that preview has
   `useVersionActions.tsx:110-153` · `DashboardVersionHistory.tsx:213-224` · 
`ExploreVersionHistory.tsx:258-269` — **react-review**
   
   `handlePreview` blocks on `hasUnsavedChanges` precisely because rehydration 
wipes in-progress work; the kebab restore path has no such guard on either 
page, and post-restore both hooks unconditionally refetch + rehydrate — 
silently destroying unsaved layout/control edits and undo history. 
`RestoreConfirmModal` says "your existing version history will not be lost" and 
never mentions unsaved changes. Fix on #41551: apply the same gate (or an 
explicit discard step) to `requestRestore`/`confirmRestore`.
   
   ### H5 ⚑ The backend's `truncated` contract dies at the frontend boundary
   `types.ts:66-69` · `useVersionActivity.ts:219` vs `schemas.py:470-478` — 
**clean-code-review + domain-driven-design-review, independently**
   
   The backend carefully makes truncation honest (per-kind ceilings, clean cut, 
"count is a floor", a `truncated` response field); the frontend type omits the 
field and computes `hasMore` from `count` alone, so clamped history renders as 
complete with no indicator. Fix on #41551: consume the flag (terminal "history 
truncated" row) — or remove it from the wire.
   
   ### H6 Per-workspace retention lever is missing
   `superset/tasks/version_history_retention.py:536-538` · 
`config.py:1696-1722` — **preset-review**
   
   Retention reads only the env-seeded config; the sibling soft-delete purge 
already established the pattern this was designed to follow (`SharedKey` read 
live per run + CLI setter + config fallback — a follow-up already designed in 
the planning docs but not yet implemented). Support cannot adjust one 
workspace's window (legal hold, disk pressure) without config change + worker 
restart. Known deferred; now confirmed as a fleet-rollout gate, not a 
nice-to-have. Backend PR.
   
   ### H7 Activity pagination is O(full visible history) per page request
   `orchestrator.py:300-318` · `queries.py:543` · `useVersionActivity.ts:36` — 
**preset-review**
   
   Every `/activity/` page re-materializes up to 5000 rows/kind × 3 kinds 
through the full pipeline, then slices 25 in Python; server-side `q` search 
rescans the set per debounced keystroke; no response caching, no ETag on 
`/activity/` (wired only to `/versions/`). The client compounds it: 
`MAX_CHAINED_PAGES = 8` per "Load more" click, plus refresh-on-save while the 
panel is open. The code's own note records 4s p95 on rich dashboards for one 
pass. Backend; needs a cost-model fix (or at least ceilings-as-config, M-13) 
before busy-workspace rollout.
   
   ### H8 Entity-kind taxonomy is a shotgun-surgery hotspot
   `kinds.py:73-115,198-209` · `visibility.py:136-163` · `orchestrator.py:421` 
· `changes/table.py:86` · `changes/state.py:86-90,183-189` — 
**clean-code-review**
   
   "A versioned entity kind" is smeared across six parallel dicts and four 
dispatch tables keyed on the same three class names; `visibility.py` even ships 
a fail-closed guard for "updated one table, forgot the other." Adding a fourth 
kind (SavedQuery) means ~10 edits in 5 modules. Fix: one `EntityKindSpec` 
registry. Post-merge refactor, but do it before the fourth kind.
   
   ### H9 ⚑ The two page containers are ~120-line copy-pastes that have already 
drifted
   `ExploreVersionHistory.tsx:108-123,218-282` vs 
`DashboardVersionHistory.tsx:92-107,165-237` — **clean-code-review + 
tidy-first-review, independently**
   
   URL-param effect, unmount-close, search/debounce, refresh effect and six 
handlers duplicated; drift already present (initial-hydration sentinel 
`undefined` vs `'|'`); co-change already demonstrated on this branch. The 
save-signal selector is a third verbatim copy (M-24). Fix: extract a shared 
container hook. Post-merge cleanup batch, first item.
   
   ### H10 Headline rendering is split across the boundary with two 
vocabularies, one unlocalizable
   `render.py:67-80,251-271` vs `display.ts:206-301` — 
**domain-driven-design-review**
   
   Related-record headlines are server-composed raw English (no i18n); 
self-record headlines are client-composed, differently worded, `t()`-wrapped. 
The same domain event gets different sentences depending on which side renders 
it, and server English surfaces untranslated in a localized UI. 
`schemas.py:40-46` documents the opposite of what happens. Decide one owner for 
prose (client, which owns i18n) and make the machine-readable record the 
contract. Cross-layer; post-merge but before translations are cut.
   
   ### H11 "One logical save" is re-derived in the UI with a 60-second 
wall-clock heuristic
   `grouping.ts:84-118,129-165` — **domain-driven-design-review**
   
   `mergeAdjacentRelatedEntries` (60s window) and 
`rollupSameTransactionRelated` approximate transaction semantics the backend 
owns; two genuine saves 30s apart render as one in an audit-style timeline. 
Both carry `TODO(version-history)` backend-workaround markers — the fix belongs 
upstream (one transaction per logical save; dataset-level impact record). Known 
compromise; schedule the upstream fix.
   
   ### H12 Meta text uses `colorTextQuaternary` — fails WCAG contrast
   `SaveGroupItem.tsx:97` · `ActionRow.tsx:94` · `RelatedUpdateRow.tsx:95` · 
`CurrentVersionSection.tsx:100` — **css-review**
   
   Disabled-level token (~1.9:1) on informative content at small size; needs 
4.5:1. Codebase convention for meta text is 
`colorTextTertiary`/`colorTextSecondary`; nothing else in `src/` uses 
quaternary for content. One-token fix on #41551.
   
   ### H13 SIP-210 gates upstreaming: still "Open for Voting", UI explicitly 
out of its scope, follow-up UI SIP not filed
   apache/superset#39492 — **superset-committer-review**
   
   Also: the `/activity/` endpoint family is outside SIP-210's declared API 
surface but freezes as public contract on release (M-11), and the SIP text has 
drifted from the implementation (restore relations, kill-switch posture — 
L-16). None of this gates the fork merge; all of it gates upstream release. 
Action: amend SIP-210 (or file the UI SIP) covering the UI, `/activity/`, and 
the implemented restore semantics, then drive the dev@ vote.
   
   ---
   
   ## MEDIUM
   
   - **M1 ⚑ `get_version` re-fetches by positional OFFSET after computing the 
stable `transaction_id`** — a prune committing between the two queries returns 
the wrong version's snapshot; the module's own docstring forbids positional 
addressing. Filter on the tx id it already resolved. (`queries.py:468-487`; 
**clean-code + sqlalchemy**, independently)
   - **M2 `list_change_records_batch` doesn't chunk its `transaction_id IN` 
clause** — ~990+ versions overflows SQLite's bind floor, and the resulting 
OperationalError is swallowed by the missing-table catch, silently blanking 
`changes` for every listed version. Chunk + narrow the except. 
(`queries.py:268-281`; **sqlalchemy**)
   - **M3 Backdated baseline `issued_at` makes retention prune fresh baselines 
almost immediately** — baselines copy the entity's pre-versioning `changed_on`; 
the prune cuts on `issued_at < cutoff`, so a legacy entity's baseline captured 
today is prunable at the next 03:00 run — pre-edit state unrestorable within 
hours, not 30 days. Anchor retention on capture time or exempt op=0 rows for N 
days after insertion. (`baseline/insertion.py:120` + 
`version_history_retention.py:198-207`; **python**)
   - **M4 Naive-local and naive-UTC timestamps mixed in 
`version_transaction.issued_at`** — Continuum stamps naive UTC; baselines copy 
`AuditMixinNullable.changed_on` (naive local). On non-UTC servers baseline 
ordering and since/until filters shift by the zone delta. 
(`baseline/insertion.py:120`; **python**)
   - **M5 No index on `table_columns_version.table_id` / 
`sql_metrics_version.table_id`** — recurring FK-filtered queries (every dataset 
snapshot, every dataset-save commit) degrade to full scans as shadow tables 
grow. Add `(table_id, transaction_id)` composite indexes. (migration 
`56cd24c07170:404-457,487-501`; **python**)
   - **M6 `cap_records` doesn't bound layout records, contradicting its 
docstring** — layout records get `path=[<component-id>]`, so every node is its 
own group of 1 and the cap never fires; a large tab-restructure writes one row 
per node, unbounded. Group under a synthetic bucket or add a per-transaction 
total ceiling. (`diff.py:244-274,829-877`; **python**)
   - **M7 `VERSION_RESTORED` is an unscoped global broadcast** — no entity 
identity on the action; a restore resolving after navigation rehydrates the 
*current* page's entity (clearing filters, wiping unsaved edits) for a restore 
that happened elsewhere. Carry `entityUuid`/type and have consumers filter. 
Related: `useDashboardVersionPreview` never checks `preview.entityUuid` against 
the page's uuid. (`reducer.ts:181`; **react**) — #41551
   - **M8 Session log records programmatic `SET_FIELD_VALUE` dispatches as user 
edits** — transferred-control rewrites after load produce phantom "unsaved 
changes" entries on untouched charts, which would also misfire any future 
dirty-gate (H4's fix). Mark programmatic dispatches or log only 
user-interaction paths. (`sessionLogMiddleware.ts:66-78`; **react**) — #41551
   - **M9 Timeline rows unmemoized behind a container-level controlled search 
input** — typing is O(total records) per keystroke once pages accumulate. 
`memo()` the row components; handlers are already stable. 
(`VersionHistoryPanel.tsx:261-290`; **react**) — #41551
   - **M10 Embedded guest tokens can read full edit history + editor 
identities** — guest short-circuit in `raise_for_access` passes the versioning 
surface for the authorized dashboard; whether history metadata is inside the 
guest entitlement is a question the SECURITY.md matrix doesn't answer. Filed as 
a question per policy; recommend explicit `is_guest_user()` rejection or a 
documented decision + pinning test. No guest-token test exists. 
(`security/manager.py:4229-4237`; **committer**)
   - **M11 `GET /versions/` is unpaginated** — bounded only by retention, which 
is operator-configurable to 100 years; sibling `/activity/` already defines the 
envelope. Retrofitting pagination post-release breaks clients. 
(`api_helpers.py:222`; **committer**)
   - **M12 Partial-restore warning rides a magic-string protocol** — client 
compares the response to the literal `'OK'` (`message !== 'OK'`); any backend 
rewording silently flips toast behaviour both ways. Add a structured 
`skipped_count` field. (`useVersionActions.tsx:137` + `api_helpers.py:316-321`; 
**tidy-first**)
   - **M13 Fetch/page ceilings hardcoded** — 
`_MAX_FETCHED_RECORDS`/`_MAX_PAGE_SIZE`/`_DEFAULT_PAGE_SIZE` have no config 
override; support's only lever on a hot activity endpoint is the global capture 
kill-switch, which doesn't touch read cost. (`queries.py:543`, 
`orchestrator.py:71-72`; **preset**)
   - **M14 ⚑ Write-path capture has no latency instrumentation** — the 
kill-switch's documented trigger is "operator observes save-path slowdown", but 
capture emits only error counters; the decision signal doesn't exist. Add a 
timer around the listener body before the capture-on rollout step. 
(`changes/listener.py:178`; **CD + preset**, independently)
   - **M15 `ENABLE_VERSIONING_CAPTURE` opens the restore write API, not just 
accrual** — capture-on day makes `POST …/restore` and `/activity/` live 
production surface with no UI involved. Not a hole (editorship-gated, tested) 
but the post-merge plan must treat them as live, not dark. 
(`commands/version_restore.py:143`; **CD**)
   - **M16 ⚑ `USER_FACING_KIND` duplicates `API_KIND_TO_TABLE`; `api_kind` 
names the internal form** — the pipeline round-trips `entity_kind` through two 
mapping tables back to its stored value; module docs claim the class-name form 
reaches the DTO, contradicted by `render.py:145`. Merge the mappings into the 
H8 registry; rename. (`kinds.py:94-98`; **clean-code + DDD**, independently)
   - **M17 Restore headline persists `version_number`, an identifier the model 
documents as unstable** — the immutable `__meta__` record stamps a display 
index that drifts under pruning; "Restored to version 5" can later point at a 
different position. `version_uuid` is already in the payload; resolve the 
display number at read time. (`version_restore.py:119-126` + 
`display.ts:309-314`; **DDD**)
   - **M18 `kind='column'` means two different concepts** — dashboard layout 
column vs dataset column, disambiguated only by entity kind; latent until a 
dataset history panel reuses `buildTimeline`, at which point dataset column 
edits get suppressed as layout scaffolding. Rename one side. (`diff.py:733-742` 
vs `1052-1063` + `grouping.ts:174`; **DDD**)
   - **M19 API strips the record's natural identity, forcing the client to hash 
values** — `(transaction_id, entity_kind, entity_id, sequence)` is a real 
unique key; decoration pops `entity_id` and `sequence`, so client dedup 
reconstructs identity from eight fields including `JSON.stringify` of values. 
Expose `sequence`. (`render.py:196-205` + `grouping.ts:31-47`; **DDD**)
   - **M20 `__meta__` bends the record model's documented dimensional purity** 
— `kind='__meta__'`/`path=['__meta__']` are sentinels inside namespaces 
documented as pure; the deferral of a separate event stream is known — at 
minimum document the exception in the `ChangeRecord` docstring. 
(`diff.py:192-209`; **DDD**)
   - **M21 ⚑ Presentation policy split across the boundary with drifted key 
sets** — layout-node labels probed twice (backend knows `label`, frontend knows 
`code`); kind→phrase vocabulary implemented on both sides with the seam 
source-dependent. Pick one owner. (`display.ts:74-87` vs `diff.py:751-764`; 
**clean-code + DDD**)
   - **M22 `apply_record_decoration` does six jobs, one of which is a security 
control** — the deleted-related-entity redaction ("deletion must not widen what 
a requester can see") is a branch inside a cosmetically-named function; extract 
it as a named function so a display refactor can't silently weaken it. 
(`render.py:83-205,172-195`; **clean-code**)
   - **M23 ⚑ `_resolve_uuids_for_kind` is a ~50-line copy of 
`_resolve_names_for_kind`** — and the two near-identical shadow scans run per 
request; selecting name + uuid in one statement halves them. 
(`queries.py:620-732`; **clean-code + sqlalchemy**)
   - **M24 The dashboard save-signal selector is duplicated, comment and all** 
— third copy of the two-field join; a third save path bumping a different field 
goes stale in one consumer silently. Export one selector. 
(`DashboardVersionHistory.tsx:136-141` + 
`useDashboardVersionPreview.ts:178-184`; **tidy-first**) — #41551
   - **M25 The preview effect multiplexes three state machines** — 
restore-reload / apply / exit coordinated through five refs in one ~160-line 
effect; extract named flows so the ref protocol is testable per flow. 
(`useDashboardVersionPreview.ts:218-380`; **clean-code**)
   - **M26 `NAME_COLUMN` is the taxonomy-membership predicate** — a 
display-column lookup gates security-relevant fail-closed behaviour in four 
places; membership should test the kind registry. (`kinds.py:111` et al.; 
**clean-code**)
   - **M27 `VersionHistoryColumn` `height: 100vh` clips the panel bottom under 
the navbar** — "Load more" can be permanently unreachable on short dashboards; 
the flag-on empty column also forces a viewport-height grid minimum. 
(`DashboardBuilder.tsx:158-167`; **css**) — #41551
   - **M28 No responsive strategy for the dashboard-side panel** — explore 
overlays below `screenXL`; the dashboard host pins 320px at every viewport. 
Port the overlay treatment. (`VersionHistoryPanel.tsx:45-50`; **css**) — #41551
   - **M29 ⚑ Timeline visual primitives and the kebab menu are built 2-4× 
each** — `Meta`/`IconWrapper`/`ChevronWrapper`/`KebabButton`/`itemStyle` 
duplicated across four components (CloseButton has already drifted); the 
two-item restore/fork menu is constructed twice with the open-as-new label 
ternary a third time. Extract a shared `styles.ts` + one `VersionActionsKebab`. 
(**css + tidy-first**, independently) — #41551 / cleanup
   - **M30 The 60s heuristic aside, `mergeActivityPages` reset UX**: every 
debounced search/include change discards all chained pages and flashes the 
skeleton; stale-while-revalidate would keep results visible. 
(`useVersionActivity.ts:144-149`; **react**, LOW-rated there but user-visible)
   
   ## LOW (condensed)
   
   - ⚑ **Two live `useVersionActions` instances per page** carry independent 
in-flight locks — banner + panel can each launch a fork/restore concurrently 
(tidy-first + react). Store-level in-flight flag, or hoist the hook when H9's 
dedup happens.
   - ⚑ **`resolve_version` derives UUIDv5 per row in Python** — O(lifetime 
edits) per snapshot/restore when retention is disabled, which is a supported 
operator choice (preset M + sqlalchemy + python). The docstring names itself 
the revisit point.
   - ⚑ **`shadow_row_count` runs `count(*)` + SAVEPOINT on every flush of every 
versioned parent forever** — `EXISTS`/`LIMIT 1` or a per-process baselined 
cache (preset + sqlalchemy).
   - ⚑ **`path` segment typing disagrees across surfaces** — engine emits ints, 
`ActivityRecordSchema` stringifies, `/versions/` preserves; use `fields.Raw` 
(python + DDD).
   - ⚑ **`EntityWindows` stays a positional tuple** next to the 
deliberately-promoted `Window` dataclass (clean-code + DDD).
   - **Check-then-insert baseline race** can produce duplicate op=0 rows under 
concurrent first-saves; partial unique index or `INSERT … WHERE NOT EXISTS` 
(python).
   - **Chart restore can re-point `datasource_id` without a datasource-access 
check** — consistent with PUT posture, but worth an explicit decision comment 
(python).
   - **`_matches_previous_version` probes without a SAVEPOINT**, unlike every 
other defensive probe in the package — on PG a failure breaks the documented 
fail-open fallback (sqlalchemy).
   - **SQLite BigInteger PK autoincrement depends on Continuum's global 
compiler shim** — `with_variant(sa.Integer(), 'sqlite')` would make the DDL 
self-sufficient (sqlalchemy).
   - **`q` search JSON-serialises every record's values per request** — 
cheap-DoS shaped; pre-serialise or cap searched length (python).
   - **`current_entity_version_info` runs the live-tx SELECT twice per write 
response** (python).
   - **Flag-on/capture-off empty state says "Saved changes will appear here"** 
— untrue while capture is off; API exposes no capture state (CD).
   - **Kill-switch off-window creates silent unattributed gaps in history** — 
worth a line in the docs page (CD).
   - **CI runs flag-on globally; no e2e asserts the panel is absent flag-off** 
— one dark-mode Playwright assertion closes it (CD).
   - **Retention beat entry lost by custom CeleryConfig** — warning exists; put 
it in the rollout runbook (CD).
   - **Retention drain loop has no per-run window cap or pacing** — first run 
on a deep backlog contends all night (preset).
   - **Capture switch is process-global** — per-workspace toggling depends on 
process isolation; state it in the config comment (preset).
   - **`?version_history=true` is never consumed** — a late `canRestore` flip 
can re-open a panel the user closed (react).
   - **`newestGroup` recomputed to null under `include='related'`** — "Current" 
tag and restore notice vanish until refetch (react).
   - **Post-restore, the save-signal effect discards the cache the restore 
branch just warmed** (react).
   - **`is_managed_externally` restore gate is client-side only** — matches PUT 
posture, but the new docs page overstates it; either add the one-line server 
check in `validate()` or soften the docs (committer). *Note: the docs page is 
ours (`50eb210534`) — action owed either way.*
   - **OpenAPI omits `version_number`'s instability under pruning** 
(committer). **No UPDATING.md entry** for tables/dependency/knobs (committer). 
**SIP text drift** on restore relations and kill-switch posture (committer).
   - **Doc rot:** `changes/__init__` describes listener events that no longer 
exist; `etag.py` docstring points at a nonexistent function; `diff.py:34` 
self-referencing docstring; dead `_SUMMARY_VERBS` entries for removed kinds; 
`find_active_by_uuid` promises filtering it doesn't do; `_MAX_FETCHED_RECORDS` 
defined 200 lines after first use; magic `2` for Continuum DELETE at four 
sites; `orchestrator.py` bundles four module responsibilities; pure layout 
transforms live in `api.ts`; three hand-rolled fetch-guard shapes 
(`useStaleGuard` candidate); CloseButton hover de-emphasizes; physical CSS 
properties break RTL in four spots; undocumented z-indexes (99 ties the header; 
magic 20 in explore); hover-only row highlight lacks `:focus-visible`; icon 
buttons under touch-target size.
   
   ---
   
   ## Read against the merge plan
   
   **Fix on #41551 before merge** (branch-local, cheap, real): H3 (chart 
preview stuck applying — verify first, then fix + make the tests drive the real 
action), H4 (unsaved-changes gate on restore), H5 (consume `truncated`), H12 
(contrast token), M7 (scope `VERSION_RESTORED`), M8 (phantom session-log 
entries — prerequisite for H4's gate being trustworthy), M27 (100vh clipping). 
M9/M28/M29 if time allows.
   
   **Backend PRs against merged code** (post-merge is fine; before broad 
capture-on): H1 (uuid-pin `/versions/` + restore — security-grade, two-lens), 
H2 (drop `username` — before the payload freezes), M1-M6 (OFFSET race, 
IN-chunking, baseline retention/timezone, indexes, layout cap), M10 
(guest-token decision), M11 (paginate `/versions/`), M12 (structured 
partial-restore field), M14 (capture latency metric — gate for the capture-on 
step), H6 (per-workspace retention), H7/M13 (activity cost model / ceilings).
   
   **Upstream/process**: H13 + M11's SIP-scope point + SIP text drift — amend 
SIP-210 or file the UI SIP, then the dev@ vote. Gates upstreaming, not this 
fork.
   
   **Post-merge cleanup batch** (the "fix later" the plan explicitly budgets): 
H8 (kind registry), H9 (container hook), H10 (headline ownership), M16-M26 
structure items, the LOW pile. Tidy-first's sequencing advice stands: normalize 
the three fetch-guard shapes before extracting a shared hook.
   
   **Known-and-confirmed**: H6 was already a known deferred follow-up; H11's 
60s heuristic already carries backend-workaround TODOs; M15 restates that 
capture is the real switch (documented in `capture_enabled()`); the 
`canOverwriteSlice` out-of-flag change is already named in the PR description, 
which is exactly what the CD lens asked for.
   
   ---
   
   _This capstone review was generated by Claude (AI) on behalf of @mikebridge 
— a 10-lens parallel review panel with findings verified against the code at 
`50eb210534` and aggregated/de-duplicated by hand._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to