fix: restore legacy training data hidden by new visibility schema (migrate-on-read)

Records written before the visibility field existed carry none; the new
fail-closed authorization treated missing visibility as invalid, making every
legacy group unlistable and unreadable. Add resolved_visibility(group) that
derives effective visibility for legacy records only (owner present => private,
absent => public), leaves explicit-malformed visibility fail-closed (None), and
never derives demo/hidden. Apply it at every list, authorization, chat, and
analytics boundary while keeping demo and hidden-preview paths raw and
owner_user_id-based private isolation intact. No persisted data is rewritten.

Backend full suite passes 517; frontend 26/26; production build passes.
This commit is contained in:
Macky
2026-08-25 08:59:22 +07:00
parent 3c22d88bcd
commit 6a2e6a326f
7 changed files with 222 additions and 12 deletions

View File

@@ -404,3 +404,44 @@ verification, production runtime check, stage, commit, push, deploy, reset, stas
credential, or permission action is claimed. Branch remains `main`; HEAD remains
`8a632b5e6a3b67c9acd2787e1a686ac17ae69486`; the index is empty and the broad
existing working tree remains intentionally dirty.
## 2026-08-25 legacy-visibility migrate-on-read remediation
After the eight-scope security gate passed and the work was pushed, the operator
reported that pre-existing training data appeared to have disappeared from the UI
while the underlying records were still present. Root cause was a
backward-incompatible visibility schema introduction: records written before the
`visibility` field existed carry none, and the new fail-closed authorization
treated a missing visibility as invalid, so every legacy record became
unlistable and unreadable.
The old schema encoded sharing entirely through `owner_user_id` (owner present =>
own private group; absent => shared public group). The fix is a migrate-on-read
helper `resolved_visibility(group)` that derives the effective visibility for
such legacy records only (missing `visibility`): owner present => `private`,
absent => `public`. Records with an explicit but malformed `visibility` stay
fail-closed (None); `demo`/`hidden` are never derived from legacy records. No
persisted data is rewritten.
The resolved visibility is now used at every authorization/list boundary that
previously blocked legacy records — `GroupStore.list_visible_to` (user, admin,
super-admin shared branches), `group_routes._authorize_group`,
`group_routes._get_owned_group`, `group_routes.list_groups` serialization,
`chat_routes._get_ready_group` (non-demo), and `analytics_routes._is_shared_group`
— while `demo` and hidden-preview paths keep using the raw visibility so
legacy records are never promoted to demo/hidden.
Evidence after remediation:
| Check | Result |
|---|---|
| legacy/resolved_visibility regression suite | **13 passed** |
| blocker test file | **55 passed in 10.10s** |
| backend full suite | **517 passed in 80.97s** |
| frontend unit suite | **26 passed (6 files)** |
| frontend production build | **passed** |
| compileall / `git diff --check` | **passed / passed** |
Returning to migration-on-read behaves purely at runtime; no data migration,
backfill, or permission change was performed. Stage/commit/push requires operator
direction as before.

View File

@@ -146,6 +146,14 @@ smoke test.
frontend and **81/81** focused backend checks; final cross-cutting scope also
**passed**. Both exact verdicts had empty security and logic arrays.
- Independent local code/security gate: **8/8 scopes passed**.
- Post-push operator report of missing legacy training data traced to the new
fail-closed visibility schema. Legacy records lacking `visibility` were rejected
by authorization. Fixed with migrate-on-read `resolved_visibility` (owner present
=> private, absent => public; malformed explicit stays fail-closed; demo/hidden
never derived). Applied at every list/authorization/chat/analytics boundary.
- Post-fix evidence: legacy/resolved regression **13 passed**; blocker file **55
passed**; full backend **517 passed in 80.97s**; frontend unit **26 passed**;
production build **passed**; compileall and `git diff --check` passed.
- No live OAuth provider, production runtime, deployment, stage, commit, push,
reset, or stash verification is claimed.