chore: archive v0.2 phase directories to milestones/v0.2-phases/
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>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
e008bf7dae
commit
123ae5b29b
+228
@@ -0,0 +1,228 @@
|
||||
---
|
||||
phase: 08-stack-upgrade-backend-decomposition
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: [08-01]
|
||||
files_modified:
|
||||
- frontend/src/stores/toast.js
|
||||
- frontend/src/components/settings/SettingsAccountTab.vue
|
||||
- frontend/src/components/auth/TotpEnrollment.vue
|
||||
- backend/tests/test_auth.py
|
||||
autonomous: true
|
||||
requirements: [CR-01, CR-02, CR-03]
|
||||
tags: [phase-7.1, toast-stub, session-revocation, frontend]
|
||||
must_haves:
|
||||
truths:
|
||||
- "frontend/src/stores/toast.js exists, exports useToastStore, exposes show(message, type, duration)"
|
||||
- "show() is a silent no-op in this phase (Phase 10 will render); calling it never throws"
|
||||
- "SettingsAccountTab.vue calls toastStore.show('Other sessions have been terminated.', 'success') when data.sessions_revoked > 0 in both changePassword and disableTotp handlers"
|
||||
- "TotpEnrollment.vue calls toastStore.show('Other sessions have been terminated.', 'success') when data.sessions_revoked > 0 in confirmEnrollment handler"
|
||||
- "Inline sessionRevokedToast ref + setTimeout + inline toast template blocks are removed from both components"
|
||||
- "CR-01, CR-02, CR-03 tests run as plain passing tests (xfail decorator removed)"
|
||||
artifacts:
|
||||
- path: "frontend/src/stores/toast.js"
|
||||
provides: "Pinia toast store stub with the show() contract Phase 10 must honor"
|
||||
contains: "export const useToastStore"
|
||||
- path: "frontend/src/components/settings/SettingsAccountTab.vue"
|
||||
provides: "Refactored to use toastStore.show() in changePassword + disableTotp handlers"
|
||||
contains: "toastStore.show"
|
||||
- path: "frontend/src/components/auth/TotpEnrollment.vue"
|
||||
provides: "Refactored to use toastStore.show() in confirmEnrollment handler"
|
||||
contains: "toastStore.show"
|
||||
- path: "backend/tests/test_auth.py"
|
||||
provides: "Three session-revocation tests promoted from xfail to passing"
|
||||
contains: "test_change_password_revokes_other_sessions"
|
||||
key_links:
|
||||
- from: "SettingsAccountTab.vue changePassword handler"
|
||||
to: "useToastStore.show"
|
||||
via: "function call when data.sessions_revoked > 0"
|
||||
pattern: "toastStore\\.show\\(['\"]Other sessions have been terminated"
|
||||
- from: "TotpEnrollment.vue confirmEnrollment handler"
|
||||
to: "useToastStore.show"
|
||||
via: "function call when data.sessions_revoked > 0"
|
||||
pattern: "toastStore\\.show\\(['\"]Other sessions have been terminated"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Complete the absorbed Phase 7.1 work. The backend code for CR-01/CR-02/CR-03 is already implemented in `backend/api/auth.py` (RESEARCH.md confirmed lines 518, 615, 662). This plan: (1) creates the `useToastStore` Pinia stub per the UI-SPEC.md contract, (2) replaces the inline `sessionRevokedToast` ref + `setTimeout` pattern in `SettingsAccountTab.vue` and `TotpEnrollment.vue` with `toastStore.show(...)` calls, (3) removes the inline toast HTML blocks from both components, and (4) promotes the three CR xfail stubs from plan 08-01 to passing tests.
|
||||
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<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
|
||||
|
||||
<interfaces>
|
||||
<!-- The locked store API contract from UI-SPEC.md §"useToastStore API Contract": -->
|
||||
toastStore.show(message: string, type: 'success'|'error'|'info' = 'success', duration: number = 4000): void
|
||||
|
||||
<!-- Trigger map from UI-SPEC.md §"Sessions-Revoked Notification — Interaction Contract": -->
|
||||
SettingsAccountTab.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')
|
||||
|
||||
<!-- Existing inline state to remove (from grep): -->
|
||||
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)
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Create useToastStore Pinia stub at frontend/src/stores/toast.js</name>
|
||||
<files>frontend/src/stores/toast.js</files>
|
||||
<read_first>
|
||||
- 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)
|
||||
</read_first>
|
||||
<behavior>
|
||||
- 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)
|
||||
</behavior>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>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</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 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
|
||||
</acceptance_criteria>
|
||||
<done>toast.js exists with the exact UI-SPEC signature; the module imports cleanly; show() is a silent no-op.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Refactor SettingsAccountTab.vue and TotpEnrollment.vue to use toastStore</name>
|
||||
<files>frontend/src/components/settings/SettingsAccountTab.vue, frontend/src/components/auth/TotpEnrollment.vue</files>
|
||||
<read_first>
|
||||
- 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.')
|
||||
</read_first>
|
||||
<action>
|
||||
For `frontend/src/components/settings/SettingsAccountTab.vue`:
|
||||
1. Delete the inline `<div v-if="sessionRevokedToast" ...>...</div>` template block (lines ~5-25 — the fixed-position toast).
|
||||
2. Delete the line `const sessionRevokedToast = ref(false)` (~line 211).
|
||||
3. In the `changePassword` handler (~lines 220-230), replace `sessionRevokedToast.value = true; setTimeout(() => { sessionRevokedToast.value = false }, 5000)` with `toastStore.show('Other sessions have been terminated.', 'success')`. Keep the surrounding `if (data.sessions_revoked > 0) { ... }` guard.
|
||||
4. In the `disableTotp` handler (~lines 258-267), apply the same replacement.
|
||||
5. Add `import { useToastStore } from '../../stores/toast.js'` at the top with the other imports.
|
||||
6. Add `const toastStore = useToastStore()` near the other store/ref declarations in `<script setup>`.
|
||||
7. If `ref` is no longer used anywhere else in the file, remove it from the `vue` import; otherwise leave the import untouched.
|
||||
|
||||
For `frontend/src/components/auth/TotpEnrollment.vue`:
|
||||
1. Delete the inline `<div v-if="sessionRevokedToast" ...>...</div>` template block (lines ~5-23).
|
||||
2. Delete the line `const sessionRevokedToast = ref(false)` (~line 149).
|
||||
3. In the `confirmEnrollment` handler (~lines 170-180), replace the `sessionRevokedToast.value = true; setTimeout(...)` block with `toastStore.show('Other sessions have been terminated.', 'success')`. Keep the surrounding `if (data.sessions_revoked > 0) { ... }` guard.
|
||||
4. Add `import { useToastStore } from '../../stores/toast.js'` at the top.
|
||||
5. Add `const toastStore = useToastStore()` near the other store/ref declarations.
|
||||
6. Same `ref` cleanup 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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd frontend && npm test 2>&1 | tail -30</automated>
|
||||
</verify>
|
||||
<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>
|
||||
<done>Both components removed the inline ref/setTimeout/HTML, both wire to toastStore.show with the locked copy, frontend test suite passes.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Promote three CR test stubs in test_auth.py from xfail to passing</name>
|
||||
<files>backend/tests/test_auth.py</files>
|
||||
<read_first>
|
||||
- backend/tests/test_auth.py (find the three xfail-decorated tests added in plan 08-01: test_change_password_revokes_other_sessions, test_enable_totp_revokes_other_sessions, test_disable_totp_revokes_other_sessions)
|
||||
</read_first>
|
||||
<action>
|
||||
For each of the three tests added in plan 08-01, remove the `@pytest.mark.xfail(reason="Wave 0 stub — promoted to passing in 08-03", strict=False)` decorator line. Leave the function bodies untouched (the assertion logic was already complete in plan 08-01). Run the three tests in strict mode to confirm they pass against the existing backend implementation. Do not modify any other test or any production code.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd backend && pytest tests/test_auth.py::test_change_password_revokes_other_sessions tests/test_auth.py::test_enable_totp_revokes_other_sessions tests/test_auth.py::test_disable_totp_revokes_other_sessions -x -v</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -B1 "def test_change_password_revokes_other_sessions" backend/tests/test_auth.py | grep -c "xfail"` returns 0
|
||||
- `grep -B1 "def test_enable_totp_revokes_other_sessions" backend/tests/test_auth.py | grep -c "xfail"` returns 0
|
||||
- `grep -B1 "def test_disable_totp_revokes_other_sessions" backend/tests/test_auth.py | grep -c "xfail"` returns 0
|
||||
- Pytest output shows all three test IDs with status `PASSED` (not XFAIL, not XPASS, not SKIPPED)
|
||||
- `cd backend && pytest -v` exits 0 with no new failures vs. baseline
|
||||
</acceptance_criteria>
|
||||
<done>Three xfail decorators removed; three tests pass strictly; full backend suite green.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<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>
|
||||
|
||||
<verification>
|
||||
- `cd frontend && npm test` — zero failures
|
||||
- `cd backend && pytest tests/test_auth.py -v` — three new tests show PASSED (not XPASS / XFAIL)
|
||||
- Visual smoke (recommended for executor, not gating): `cd frontend && npm run dev`, change password while logged in on two browsers; the second browser session should be revoked (this is backend behavior — Phase 10 will make the toast visible)
|
||||
- `grep -rn "sessionRevokedToast" frontend/src/` returns no matches
|
||||
</verification>
|
||||
|
||||
<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>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/08-stack-upgrade-backend-decomposition/08-03-SUMMARY.md` when done. Include: exact final test output for the three CR tests, confirmation that no other components were touched, and a flagged forward-reference to Phase 10 (UX-10) that the toast store contract is locked.
|
||||
</output>
|
||||
Reference in New Issue
Block a user