feat(admin): migrate Users with safe recovery and action state

This commit is contained in:
cesnimda
2026-10-20 15:10:04 +02:00
parent 8af9168014
commit 7ecc0d6605
10 changed files with 669 additions and 69 deletions
+2
View File
@@ -2,6 +2,8 @@
## Continuation — 20 October 2026
Remaining P7-09 data presentation **8af9168014631633e38e8ba7cd22899fdf1092e2** was normally pushed and directly verified **07:58:49 UTC**, with clean HEAD=origin=remote development. P7-09 implemented scope is CONDITIONAL GO; qualifications remain. [P7-10 Users](p7-10/USERS.md) now has18/5 affected frontend,25backend and finalnative3/type/lint/build PASS; final hygiene/publication follows. Six new frontend cases derive861/99, three native cases derive342 browser tests; these are inventories, not fresh whole-suite results. Audit/System remain pending.
Import prerequisite **3028d8172c78d3d86b16d621cbdc418b9b318dc2** was normally pushed and directly verified **07:30:08 UTC**; development remote matched HEAD. Release repairs were merged normally and pushed as **36fc5d54e781dcad077c2064edc5c5adef1d4e24**, directly verified **07:50:40 UTC**, preserving all working-file hashes. Main is separately owner-authorized `cc7977131a4f991578f29d83eec17a242a858fca`, with corrected CI queued and deployment unverified. Remaining [data presentation](p7-09/DATA_PRIVACY_BACKUP.md) has47/6 affected frontend,22backendcontracts, final native9+4+1+ordinaryP2-D01 and types/lint/build PASS; final consumer/hygiene/publication receipt follows. Qualifications remain unchanged.
Company compatibility repair **5addcde1e22e9625d1b39df037a05b24c69c9cde** is pushed, directly verified 07:16:14 UTC. It preserves owner-scoped exact Norwegian names and ASCII case reuse; targeted backend 3, full backend 1057 and native Companies six pass. Separate [import completion](p7-09/IMPORT_COMPATIBILITY.md) has full frontend 837/97, native 25, types/lint/build and final hygiene PASS; its publication receipt follows this commit. Original candidate/RED evidence remains retained.
+2
View File
@@ -1,5 +1,7 @@
# Phase 7 — Initial preservation matrix
P7-10 [Users](p7-10/USERS.md) adds bounded PRES-A01/E09 proof without changing any group disposition: native administrator create/role persistence, target-session revocation, non-admin denial, last-admin conflict and deletion-disabled response, plus retained self-demotion/exact-email guards. Failed reads are distinct from empty; committed-write recovery does not replay writes; submitted form and immutable session disposal are tested. Desktop grid scrolling remains intentional and all action rows fit. Seven scans report zero violations with28incomplete nodes retained. Audit/System, shared accessibility qualification and all human/provider/production requirements remain open;57groups/33routes unchanged.
## Import continuation — 20 October 2026
The remaining [privacy/usage/backup presentation](p7-09/DATA_PRIVACY_BACKUP.md) adds S03/S04/S05/J10/E09/W11 evidence without changing any full-group disposition: two-boolean privacy persistence and newer-draft acknowledgement, GET-only independent recovery, disabled billing/deletion capability truth, actual protected ZIP response/download byte identity, session disposal and existing exact confirmation/recent-auth guards. Quick Capture's manual code selection and all eight finite width samples pass. No native deletion/restore/provider/model/billing action is executed. 37 scans have zero violations with 141 incomplete nodes retained. Human/device/output and production requirements remain open.
+13
View File
@@ -0,0 +1,13 @@
# P7-10 — Administration
Entry: P7-09 **8af9168014631633e38e8ba7cd22899fdf1092e2**, normally pushed and directly verified **20 October 2026 07:58:49 UTC**, clean development HEAD=origin=remote. It completes the implemented Settings scope with 47/6 affected frontend, 22 existing backend contracts and 23 final native cases; manual/provider/output/production qualifications remain.
Follow the [approved handoff](../../phase-6/p6-05/PHASE7_HANDOFF.md): exactly three separately validated and pushed boundaries, **Users → Audit → System**. Reuse existing routes, MUI/DataGrid, read/mutation owners and shared page/state components. No aggregate Admin dashboard or new API/schema contract.
| Boundary | State |
|---|---|
| [Users](USERS.md) | Implemented CONDITIONAL GO; final hygiene/publication receipt follows |
| Audit | Pending; preserve the exact supported Undo allowlist and ordinary owner-filtered Restore distinction |
| System | Pending; preserve independent SMTP/probe/test/maintenance scopes, write-only secret semantics and truthful diagnostic availability |
All 57 preservation groups retain **56 PARTIAL / C11 DEFERRED**, with 33 routes. P5-13 is withheld; followMode remains deferred. No Phase 8 implementation. Production readiness remains NO-GO; the independently owner-authorized main release is still unverified after run831 failed with browser crashes/malformed Playwright JavaScript. Do not infer admin recent-auth enforcement: the current Users/Audit/System actions do not implement that separate guard. Preserve actual account-lifecycle/export recent-auth checks and record this gap rather than inventing a security protocol during presentation migration.
+25
View File
@@ -0,0 +1,25 @@
# P7-10 Users
**Implemented CONDITIONAL GO; final hygiene and publication pending.** This bounded change preserves `/admin/users` and the existing authenticated administrator API contract. The shell retains the route h1; the owner uses the shared h2 header, h3 create section and existing desktop DataGrid/mobile cards.
## Findings and changes
The previous list catch converted every error to an empty list. Creation serialized a ProblemDetails object as `[object Object]`, and pending actions had no synchronous admission or disposal guard. A successful create could clear newer form values or start another private list read after unmount. Five new ordinary regressions failed against the prior owner while all four original cases passed.
The page now uses existing `useViewResource`/`ViewStateNotice` for distinct loading, unavailable/denied and actual empty states. Retry only reloads the list. A native create form retains the exact email/password/Admin payload. All mutations share synchronous admission and current-session/lifetime checks, current-language feedback and a named pending focus target. Creation clears only submitted fields that have not changed; a committed create followed by failed refresh offers read recovery without replay. Accepted deletion HTTP202 is labelled queued, not completed. Server last-admin/self-demotion/exact-email/delete availability remain authoritative.
Shared section/surface/density tokens replace owner-specific decorative surfaces. Mobile identities wrap. Desktop two-dimensional grid scrolling is intentional, named and keyboard reachable, with root overflow still checked. Final visual review exposed clipped wrapped action buttons; a strengthened browser assertion failed ordinarily before adding content-sized DataGrid rows. It now verifies every row action fits vertically and a horizontally scrolled action receives visible keyboard focus. Sorting, pagination and row identity remain unchanged.
## Evidence
- Frontend **18/18 cases in five suites PASS**: ten Users cases, including six additions, plus eight shared confirmation/dialog cases. Original self-demotion warning, other-admin warning, last-admin disable and exact-email confirmation remain ordinary passing cases. Queued-deletion feedback is tested with a mocked202, not an actual deletion.
- Existing backend **25/25 PASS**, zero skipped, covering Users controllers, API administrator authorization and session-context middleware with the unchanged Release binary.
- Final native **3/3 PASS**, zero skips/flaky/retries, **13:07:56–13:08:20 UTC**, export **`f6H1YeLZWstdE-yoN5W8B`**. Real isolated HTTP verifies administrator creation/role readback, revoked target sessions, non-admin403, last-admin409, deletion-disabled503 with retained account, GET-only503 recovery and keyboard creation. No reset email or actual account deletion runs.
- English/Bokmål light/dark samples at320,390,768,1024,1440,1920; seven all-enabled axe scans, zero violations,28 incomplete nodes. These are finite paired samples, not the full Cartesian product.
- App/browser TypeScript, lint and production build PASS. The intermediate compiler-memoization lint failure and its correction remain retained. [Portable results](evidence/users-results.json) record command/result hashes and explicitly post-run source identities.
Both final320px Bokmål dark and1440px English light PNGs were actually inspected after the row correction. The other five images are retained automated evidence. Earlier candidate images and clipped-row RED remain historical evidence. Incompletes include invisible badges/overlapping backgrounds, the inherited roleless shell version badge and MUI DataGrid sort-tooltip wrapper. These are retained accessibility qualifications for the shared-owner integration; no axe rule or application assertion is disabled. Human screen-reader, physical touch/software keyboard, actual zoom and production output checks remain pending.
## Preservation and remaining work
PRES-A01/E09 and affected shared interaction contracts gain bounded proof without changing the 57-group disposition or33routes. No backend authorization, tenant data, lifecycle flag, CV renderer/PDF/revision or AI safeguard changes. Current admin actions lack a separate recent-auth guard; this change does not claim otherwise. Actual SMTP/reset delivery, accepted deletion completion, provider/model/system operations and production remain unverified. Audit and System follow as separate commits. Phase 8 remains outside scope.
@@ -0,0 +1,259 @@
{
"observedUtc": "2026-10-20T13:08:49.381408+00:00",
"sourceScope": "Post-run observations, not pre-run captures",
"sources": {
"src/views/AdminUsersPage.tsx": "fc02891601b6dd5593210f872db86d18447e848955d60019350e1cda622761f0",
"src/admin-users-page.test.tsx": "ad167fc0f457ac082335edd23f729bce35d2246217337891f1f07fb9ccae9f71",
"src/i18n/translations.ts": "69ef9e733534731206a98b717e586a61ce1b78f6ed7b1b847b46b60b71ce0c32",
"e2e/phase7-admin-users.spec.ts": "53e2f7daa55627b66c20fc969ce553a6a73ea027da644be2531bcd467063c8f8"
},
"gates": {
"typecheck": {
"path": ".artifacts/phase7/current/ui/typecheck-command.json",
"sha256": "92b1a8a58e4f2a5b49dbcc17bb1f7a164b437b75a77ce29dbd06a6ed5d00ffaf",
"name": "typecheck",
"command": [
"rtk",
"proxy",
"npm.cmd",
"run",
"typecheck"
],
"startedUtc": "2026-10-20T13:07:13.238238+00:00",
"finishedUtc": "2026-10-20T13:07:23.292112+00:00",
"exitCode": 0
},
"lint": {
"path": ".artifacts/phase7/current/ui/lint-command.json",
"sha256": "e380e1fe5db37416bfae32f67fed85bb4827973220144fd7abdff63d842f012d",
"name": "lint",
"command": [
"rtk",
"proxy",
"npm.cmd",
"run",
"lint"
],
"startedUtc": "2026-10-20T13:07:28.787691+00:00",
"finishedUtc": "2026-10-20T13:07:48.996027+00:00",
"exitCode": 0
},
"browser-typecheck": {
"path": ".artifacts/phase7/current/ui/browser-typecheck-command.json",
"sha256": "1916ff75a302812d544bb7d0095efe82000d3b192d890dabdfa0e8708d742a87",
"name": "browser-typecheck",
"command": [
"rtk",
"proxy",
"npm.cmd",
"exec",
"tsc",
"--",
"--project",
"../.artifacts/phase7/e2e-tsconfig.json",
"--noEmit"
],
"startedUtc": "2026-10-20T13:07:23.294714+00:00",
"finishedUtc": "2026-10-20T13:07:28.785676+00:00",
"exitCode": 0
},
"build": {
"path": ".artifacts/phase7/current/ui/build-command.json",
"sha256": "fd1802d73fe5c992f3e2adac0e0f35fe28221efbc4e3b7f61a793e6dcd5e70d6",
"name": "build",
"command": [
"rtk",
"proxy",
"npm.cmd",
"run",
"build"
],
"startedUtc": "2026-10-20T13:07:48.997996+00:00",
"finishedUtc": "2026-10-20T13:07:56.404637+00:00",
"exitCode": 0
},
"admin-users": {
"path": ".artifacts/phase7/current/ui/admin-users-command.json",
"sha256": "7fed5787b7d48faf52b0cf3a1fb65e0f8244177678f8738fa023cf50aefe6473",
"name": "admin-users",
"command": [
"rtk",
"proxy",
"node",
"node_modules/@playwright/test/cli.js",
"test",
"e2e/phase7-admin-users.spec.ts",
"--retries=0",
"--reporter=list,json",
"--output=E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\ui\\admin-users-results"
],
"startedUtc": "2026-10-20T13:07:56.410189+00:00",
"finishedUtc": "2026-10-20T13:08:20.400526+00:00",
"exitCode": 0
}
},
"images": [
{
"path": ".artifacts/phase7/p7-10/users-final-visuals/users-actual-get-recovery-en-light-390.png",
"sha256": "cbb227fbd4691b63f9f972ba868244456cc635895b3a5c9a2695f170e1449912"
},
{
"path": ".artifacts/phase7/p7-10/users-final-visuals/users-long-identity-320-nb-dark.png",
"sha256": "42ebc78505ec0a0e937188b65bb23f6a15959a19ba307c4f9bce68b0a1b51249"
},
{
"path": ".artifacts/phase7/p7-10/users-final-visuals/users-long-identity-390-en-light.png",
"sha256": "9d2d8c5c52f877c17d1e61cecfc9324865a02b00fde225768d9cca6cb470b26d"
},
{
"path": ".artifacts/phase7/p7-10/users-final-visuals/users-long-identity-768-nb-light.png",
"sha256": "e7e47a6095da25a23902bf202e802f5f3ba89e4f8c7770b3a729b44ee5b681b1"
},
{
"path": ".artifacts/phase7/p7-10/users-final-visuals/users-long-identity-1024-en-dark.png",
"sha256": "ac5b41db31027a8873c825a91363ad11114e674573606ad8b7a1a386165c19de"
},
{
"path": ".artifacts/phase7/p7-10/users-final-visuals/users-long-identity-1440-en-light.png",
"sha256": "d80d2b1aa402d53429e1dc71c87a244cfb1d1af1d11fbf19916ab64523fb3c91"
},
{
"path": ".artifacts/phase7/p7-10/users-final-visuals/users-long-identity-1920-nb-dark.png",
"sha256": "84a780ab684ebb9c6530e845bbc5cf70711d388881db12a4280af5214fdd05d1"
}
],
"scans": [
{
"path": ".artifacts/phase7/p7-10/users-final-visuals/users-actual-get-recovery-en-light-390-axe.json",
"violations": 0,
"incompleteNodes": 3,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-10/users-final-visuals/users-long-identity-320-nb-dark-axe.json",
"violations": 0,
"incompleteNodes": 3,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-10/users-final-visuals/users-long-identity-390-en-light-axe.json",
"violations": 0,
"incompleteNodes": 3,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-10/users-final-visuals/users-long-identity-768-nb-light-axe.json",
"violations": 0,
"incompleteNodes": 4,
"incompleteRules": [
"aria-prohibited-attr",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-10/users-final-visuals/users-long-identity-1024-en-dark-axe.json",
"violations": 0,
"incompleteNodes": 5,
"incompleteRules": [
"aria-prohibited-attr",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-10/users-final-visuals/users-long-identity-1440-en-light-axe.json",
"violations": 0,
"incompleteNodes": 5,
"incompleteRules": [
"aria-prohibited-attr",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-10/users-final-visuals/users-long-identity-1920-nb-dark-axe.json",
"violations": 0,
"incompleteNodes": 5,
"incompleteRules": [
"aria-prohibited-attr",
"color-contrast"
]
}
],
"frontend": {
"path": ".artifacts/phase7/current/unit/admin-users-final-20261020T130341Z-command.json",
"sha256": "4b5c069699c156d6a49fdc2b42cb0d6478f02950b1a1d6414bee0be1ba5157d8",
"command": [
"rtk",
"proxy",
"npm.cmd",
"test",
"--",
"--runInBand",
"--watchAll=false",
"--json",
"--outputFile=E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\unit\\admin-users-final-20261020T130341Z.json",
"src/admin-users-page.test.tsx",
"src/confirm.test.tsx",
"src/confirm-flow.test.tsx",
"src/dialog-flow.test.tsx",
"src/modal-state.test.tsx"
],
"startedUtc": "2026-10-20T13:03:41.037736+00:00",
"finishedUtc": "2026-10-20T13:03:51.672970+00:00",
"exitCode": 0,
"counts": {
"numPassedTests": 18,
"numFailedTests": 0,
"numPendingTests": 0,
"numTodoTests": 0,
"numPassedTestSuites": 5,
"numFailedTestSuites": 0
}
},
"backend": {
"path": ".artifacts/phase7/current/backend-career/p710-users-entry-20261020T125635Z/command.json",
"sha256": "70d503a6ff1c27f6b1361d388020ad13406c87b5e8079b96a428dddd436d3b2f",
"command": [
"rtk",
"proxy",
"C:/Users/Connor/.dotnet/dotnet.exe",
"test",
"JobTrackerApi.Tests/JobTrackerApi.Tests.csproj",
"--configuration",
"Release",
"--no-build",
"--filter",
"FullyQualifiedName~UsersControllerTests|FullyQualifiedName~ApiAuthorizationPostureTests|FullyQualifiedName~SessionContextMiddlewareTests",
"--logger",
"trx;LogFileName=career.trx",
"--results-directory",
"E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\backend-career\\p710-users-entry-20261020T125635Z"
],
"startedUtc": "2026-10-20T12:56:35.309094+00:00",
"finishedUtc": "2026-10-20T12:56:38.915911+00:00",
"exitCode": 0,
"logSha256": "d9e8e89d9b46ba4575d4f2fd52a63ff317897e698356cd499dad7d8616c893f5",
"counts": {
"total": 25,
"passed": 25,
"failed": 0,
"notExecuted": 0
},
"resultSha256": "677e93e67549b1076d5a0dc804d8f0c86779d1a845cd09c79b4958edd8afc7ef"
},
"buildId": "f6H1YeLZWstdE-yoN5W8B",
"nativeStats": {
"startTime": "2026-10-20T13:07:56.934Z",
"duration": 23435.509000000002,
"expected": 3,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"nativeResultSha256": "7afbc369f3a9e628074fefaf75eab31c45139adba59f03d78687ec74541c3654"
}
@@ -0,0 +1,104 @@
{
"schemaVersion": 1,
"checkpoint": "p7-10",
"status": "PASS",
"checkedUtc": "2026-10-20T13:09:54.281127+00:00",
"commands": [
{
"name": "secret-scan",
"command": [
"rtk",
"proxy",
"C:\\Users\\Connor\\AppData\\Local\\Programs\\Python\\Python312\\python.exe",
"-X",
"utf8",
"scripts/supply-chain.py",
"scan-secrets"
],
"startedUtc": "2026-10-20T13:09:50.288246+00:00",
"finishedUtc": "2026-10-20T13:09:53.446671+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261020T130950Z/secret-scan.log",
"logSha256Raw": "be5119e6057287d397e87759f855c91e634e3bfac61c8be7584d05592ba2dfcd"
},
{
"name": "working-whitespace",
"command": [
"rtk",
"proxy",
"git",
"diff",
"--check"
],
"startedUtc": "2026-10-20T13:09:53.447162+00:00",
"finishedUtc": "2026-10-20T13:09:53.545286+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261020T130950Z/working-whitespace.log",
"logSha256Raw": "fc361bb530dd5610a4744d4fd10476a3ff92609526be469ce0c32eb4ac9ea3bb"
},
{
"name": "staged-whitespace",
"command": [
"rtk",
"proxy",
"git",
"diff",
"--cached",
"--check"
],
"startedUtc": "2026-10-20T13:09:53.545789+00:00",
"finishedUtc": "2026-10-20T13:09:53.621382+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261020T130950Z/staged-whitespace.log",
"logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "phase7-whitespace",
"command": [
"rtk",
"proxy",
"git",
"diff",
"--check",
"b5319981ad3d1bd635daff861453d23c0de5320b"
],
"startedUtc": "2026-10-20T13:09:53.621382+00:00",
"finishedUtc": "2026-10-20T13:09:53.864111+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261020T130950Z/phase7-whitespace.log",
"logSha256Raw": "296a8d67450c089ffaa00fc6935bdd1b2009396bc7edd24a6f693de0fd20e51e"
}
],
"untrackedSecretScan": {
"files": 4,
"findings": 0
},
"documentation": {
"files": 32,
"relativeLinks": 626,
"errors": []
},
"preservation": {
"groups": 57,
"statuses": {
"PARTIAL": 56,
"PASS": 0,
"FAIL": 0,
"DEFERRED": 1
},
"originalRoutesPreserved": true,
"currentConcreteRoutes": 33
},
"production": {
"main": "cc7977131a4f991578f29d83eec17a242a858fca",
"originMain": "cc7977131a4f991578f29d83eec17a242a858fca",
"status": "NO-GO",
"qualification": "Local fetched refs; direct push observation recorded separately."
},
"hashPolicy": "Portable JSON UTF8 LF; raw log hashes measure exact saved bytes.",
"limits": [
"Pattern scanner skips binary and files over2MiB; not historical Git/production secrets.",
"Local links check target existence, not URL/heading anchors.",
"Static evidence is not runtime/manual/device/provider/production acceptance."
]
}