Moves phases 08–11 execution artifacts from .planning/phases/ to .planning/milestones/v0.2-phases/ to keep .planning/phases/ clean for the next milestone. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
16 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, tags, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | tags | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 08-stack-upgrade-backend-decomposition | 03 | execute | 1 |
|
|
true |
|
|
|
Purpose: Ship CR-01/CR-02/CR-03 to the user-visible behavior contract that the UI-SPEC.md locks (toast on sessions_revoked > 0), with the call signature Phase 10 must honor without modifying any Phase 8 call site.
Output: New toast store, refactored two Vue components, promoted three backend tests.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/08-stack-upgrade-backend-decomposition/08-CONTEXT.md @.planning/phases/08-stack-upgrade-backend-decomposition/08-RESEARCH.md @.planning/phases/08-stack-upgrade-backend-decomposition/08-PATTERNS.md @.planning/phases/08-stack-upgrade-backend-decomposition/08-UI-SPEC.md @frontend/src/stores/topics.js @frontend/src/components/settings/SettingsAccountTab.vue @frontend/src/components/auth/TotpEnrollment.vue @backend/tests/test_auth.py toastStore.show(message: string, type: 'success'|'error'|'info' = 'success', duration: number = 4000): voidSettingsAccountTab.vue / changePassword() → on data.sessions_revoked > 0 → toastStore.show('Other sessions have been terminated.', 'success') SettingsAccountTab.vue / disableTotp() → on data.sessions_revoked > 0 → toastStore.show('Other sessions have been terminated.', 'success') TotpEnrollment.vue / confirmEnrollment() → on data.sessions_revoked > 0 → toastStore.show('Other sessions have been terminated.', 'success')
SettingsAccountTab.vue: lines 6, 17, 211, 226-227, 263-264 (sessionRevokedToast ref + setTimeout + inline HTML block) TotpEnrollment.vue: lines 6, 15, 149, 175-176 (sessionRevokedToast ref + setTimeout + inline HTML block)
Task 1: Create useToastStore Pinia stub at frontend/src/stores/toast.js frontend/src/stores/toast.js - frontend/src/stores/topics.js (simplest existing Pinia store — copy the setup-store style with defineStore + named function exports) - .planning/phases/08-stack-upgrade-backend-decomposition/08-UI-SPEC.md §"useToastStore API Contract" (exact signature, default values, "MUST NOT" rules) - .planning/phases/08-stack-upgrade-backend-decomposition/08-PATTERNS.md §"frontend/src/stores/toast.js" (full stub template) - Importing `useToastStore` from `'../stores/toast.js'` resolves successfully - Calling `useToastStore().show('hello')` returns `undefined` and does not throw - Calling `useToastStore().show('hello', 'error')` returns `undefined` and does not throw - Calling `useToastStore().show('hello', 'success', 8000)` returns `undefined` and does not throw - The store does not render any DOM element (verified visually — Phase 10 implements rendering) Create `frontend/src/stores/toast.js`. Use the setup-store style (`defineStore('toast', () => { ... })`). Define a single function `show(message, type = 'success', duration = 4000)` with positional parameters only (the UI-SPEC explicitly forbids object-argument shape `show({message, type})`). The function body is empty (no-op stub). Return `{ show }` so the store exposes the method. Add a top-of-file docstring (JS comment block) stating: Phase 7.1 STUB — Phase 10 (UX-10) fills in the full implementation; the signature `show(message, type, duration)` is locked and Phase 10 must honor it without modifying Phase 8 call sites. Default values match UI-SPEC.md: `type = 'success'` (NOT `'info'`), `duration = 4000`. Export as named export: `export const useToastStore = defineStore(...)`. Do NOT import or depend on any component. cd frontend && node -e "import('./src/stores/toast.js').then(m => { const s = m.useToastStore; if (typeof s !== 'function') throw new Error('useToastStore not a function'); console.log('ok'); })" 2>&1 | tail -5 - File `frontend/src/stores/toast.js` exists - `grep -c "export const useToastStore" frontend/src/stores/toast.js` returns 1 - `grep -c "defineStore('toast'" frontend/src/stores/toast.js` returns 1 - `grep -c "function show(message, type = 'success', duration = 4000)" frontend/src/stores/toast.js` returns 1 - `cd frontend && npm test -- --run stores/toast 2>/dev/null || true` does not throw an import error - The file contains NO `import` of any Vue component or DOM API toast.js exists with the exact UI-SPEC signature; the module imports cleanly; show() is a silent no-op. Task 2: Refactor SettingsAccountTab.vue and TotpEnrollment.vue to use toastStore frontend/src/components/settings/SettingsAccountTab.vue, frontend/src/components/auth/TotpEnrollment.vue - frontend/src/components/settings/SettingsAccountTab.vue (full file — current inline toast HTML at lines 5-25, `sessionRevokedToast` ref at line 211, setTimeouts in changePassword at 225-228 and disableTotp at 261-264) - frontend/src/components/auth/TotpEnrollment.vue (full file — current inline toast HTML at lines 5-23, `sessionRevokedToast` ref at line 149, setTimeout in confirmEnrollment at 174-177) - frontend/src/stores/toast.js (the stub created in task 1) - .planning/phases/08-stack-upgrade-backend-decomposition/08-UI-SPEC.md §"Sessions-Revoked Notification — Interaction Contract" (message copy locked exactly: 'Other sessions have been terminated.') For `frontend/src/components/settings/SettingsAccountTab.vue`: 1. Delete the inline `For frontend/src/components/auth/TotpEnrollment.vue:
- Delete the inline
<div v-if="sessionRevokedToast" ...>...</div>template block (lines ~5-23). - Delete the line
const sessionRevokedToast = ref(false)(~line 149). - In the
confirmEnrollmenthandler (~lines 170-180), replace thesessionRevokedToast.value = true; setTimeout(...)block withtoastStore.show('Other sessions have been terminated.', 'success'). Keep the surroundingif (data.sessions_revoked > 0) { ... }guard. - Add
import { useToastStore } from '../../stores/toast.js'at the top. - Add
const toastStore = useToastStore()near the other store/ref declarations. - Same
refcleanup rule as above.
The message string MUST be exactly 'Other sessions have been terminated.' (matches UI-SPEC.md and the previously displayed inline copy). The type argument MUST be 'success' (not 'info'). Do NOT pass a duration argument — let the default 4000ms apply.
Update any existing Vitest tests (SettingsAccountTab.test.js, TotpEnrollment.test.js) ONLY if they explicitly assert on sessionRevokedToast or the inline DOM block. Mock useToastStore via vi.mock('../../stores/toast.js', () => ({ useToastStore: () => ({ show: vi.fn() }) })) and assert the mock was called with the locked message string. If tests already pass after the refactor without changes, do not touch them.
cd frontend && npm test 2>&1 | tail -30
<acceptance_criteria>
- grep -c "sessionRevokedToast" frontend/src/components/settings/SettingsAccountTab.vue returns 0
- grep -c "sessionRevokedToast" frontend/src/components/auth/TotpEnrollment.vue returns 0
- grep -c "toastStore.show('Other sessions have been terminated\\.', 'success')" frontend/src/components/settings/SettingsAccountTab.vue returns 2 (changePassword + disableTotp)
- grep -c "toastStore.show('Other sessions have been terminated\\.', 'success')" frontend/src/components/auth/TotpEnrollment.vue returns 1 (confirmEnrollment)
- grep -c "from '../../stores/toast.js'" frontend/src/components/settings/SettingsAccountTab.vue returns 1
- grep -c "from '../../stores/toast.js'" frontend/src/components/auth/TotpEnrollment.vue returns 1
- grep -c "setTimeout" frontend/src/components/settings/SettingsAccountTab.vue returns 0 (the only setTimeouts in this file were for the toast)
- cd frontend && npm test exits 0
</acceptance_criteria>
Both components removed the inline ref/setTimeout/HTML, both wire to toastStore.show with the locked copy, frontend test suite passes.
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| Vue component → Pinia toast store | In-process function call; no untrusted input crosses |
| Backend response (sessions_revoked) → frontend toast trigger | Backend value is already validated server-side; frontend only uses the boolean test > 0 |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-08-03-01 | Spoofing | Toast message injection | mitigate | Message string is a literal in the call sites, not user-controlled; Phase 10 contract specifies plain text only (no HTML) |
| T-08-03-02 | Repudiation | Audit log for revoked sessions | mitigate | Backend (api/auth.py) already writes audit log with metadata_={"sessions_revoked": revoked} for all three handlers; this plan does not alter audit behavior |
| T-08-03-03 | Information Disclosure | Toast leaks session count | accept | UI shows generic "Other sessions have been terminated."; the exact count is not revealed to the UI |
| T-08-03-04 | Tampering | xfail strict=False masks regression | mitigate | Decorator removed in task 3; subsequent pytest runs are strict |
| T-08-03-SC | Supply Chain | pinia, vue, pytest unchanged | accept | No new packages added |
| </threat_model> |
<success_criteria>
- toast.js stub exists with the UI-SPEC signature
- Both components wire to toastStore.show with the locked message
- All three CR tests pass strictly (no xfail)
- Full backend + frontend test suites pass </success_criteria>