From 29cfeccc871b218cd9b732847b33bc2dfd152ceb Mon Sep 17 00:00:00 2001 From: cesnimda Date: Sat, 11 Jul 2026 12:50:55 +0200 Subject: [PATCH] chore: stop tracking .gsd planning tooling Per project rule that tooling (claude, gsd, etc.) should not live in the repo. Local files are untouched; .gsd/ is now gitignored. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 1 + .gsd/DECISIONS.md | 29 - .gsd/KNOWLEDGE.md | 15 - .gsd/OVERRIDES.md | 20 - .gsd/PROJECT.md | 28 - .gsd/REQUIREMENTS.md | 249 - .gsd/event-log.jsonl | 58 - .gsd/milestones/M001/M001-CONTEXT.md | 113 - .gsd/milestones/M001/M001-DISCUSSION.md | 40 - .gsd/milestones/M001/M001-META.json | 3 - .gsd/milestones/M001/M001-ROADMAP.md | 15 - .gsd/milestones/M001/M001-VALIDATION.md | 50 - .gsd/milestones/M001/slices/S01/S01-PLAN.md | 12 - .../M001/slices/S01/S01-RESEARCH.md | 110 - .../milestones/M001/slices/S01/S01-SUMMARY.md | 170 - .gsd/milestones/M001/slices/S01/S01-UAT.md | 183 - .../M001/slices/S01/tasks/T01-PLAN.md | 54 - .../M001/slices/S01/tasks/T01-SUMMARY.md | 22 - .../M001/slices/S01/tasks/T01-VERIFY.json | 9 - .../M001/slices/S01/tasks/T02-PLAN.md | 55 - .../M001/slices/S01/tasks/T02-SUMMARY.md | 22 - .../M001/slices/S01/tasks/T03-PLAN.md | 54 - .../M001/slices/S01/tasks/T03-SUMMARY.md | 64 - .gsd/milestones/M001/slices/S02/S02-PLAN.md | 12 - .../milestones/M001/slices/S02/S02-SUMMARY.md | 101 - .gsd/milestones/M001/slices/S02/S02-UAT.md | 91 - .../M001/slices/S02/tasks/T01-PLAN.md | 51 - .../M001/slices/S02/tasks/T01-SUMMARY.md | 22 - .../M001/slices/S02/tasks/T02-PLAN.md | 53 - .../M001/slices/S02/tasks/T02-SUMMARY.md | 22 - .gsd/milestones/M001/slices/S03/S03-PLAN.md | 12 - .../milestones/M001/slices/S03/S03-SUMMARY.md | 107 - .gsd/milestones/M001/slices/S03/S03-UAT.md | 92 - .../M001/slices/S03/tasks/T01-PLAN.md | 50 - .../M001/slices/S03/tasks/T01-SUMMARY.md | 22 - .../M001/slices/S03/tasks/T02-PLAN.md | 52 - .../M001/slices/S03/tasks/T02-SUMMARY.md | 22 - .gsd/milestones/M001/slices/S04/S04-PLAN.md | 12 - .../milestones/M001/slices/S04/S04-SUMMARY.md | 27 - .gsd/milestones/M001/slices/S04/S04-UAT.md | 27 - .../M001/slices/S04/tasks/T01-PLAN.md | 46 - .../M001/slices/S04/tasks/T01-SUMMARY.md | 22 - .../M001/slices/S04/tasks/T02-PLAN.md | 50 - .../M001/slices/S04/tasks/T02-SUMMARY.md | 22 - .../M001/slices/S04/tasks/T02-VERIFY.json | 19 - .gsd/milestones/M001/slices/S05/S05-PLAN.md | 12 - .../M001/slices/S05/S05-RESEARCH.md | 119 - .../milestones/M001/slices/S05/S05-SUMMARY.md | 118 - .gsd/milestones/M001/slices/S05/S05-UAT.md | 246 - .../M001/slices/S05/tasks/T01-PLAN.md | 60 - .../M001/slices/S05/tasks/T01-SUMMARY.md | 22 - .../M001/slices/S05/tasks/T01-VERIFY.json | 9 - .../M001/slices/S05/tasks/T02-PLAN.md | 60 - .../M001/slices/S05/tasks/T02-SUMMARY.md | 22 - .../M001/slices/S05/tasks/T02-VERIFY.json | 25 - .gsd/milestones/M001/slices/S06/S06-PLAN.md | 15 - .../M001/slices/S06/S06-RESEARCH.md | 261 - .../milestones/M001/slices/S06/S06-SUMMARY.md | 98 - .gsd/milestones/M001/slices/S06/S06-UAT.md | 209 - .../M001/slices/S06/tasks/T01-PLAN.md | 40 - .../M001/slices/S06/tasks/T01-SUMMARY.md | 22 - .../M001/slices/S06/tasks/T01-VERIFY.json | 26 - .../M001/slices/S06/tasks/T02-PLAN.md | 40 - .../M001/slices/S06/tasks/T02-SUMMARY.md | 22 - .../M001/slices/S06/tasks/T02-VERIFY.json | 26 - .../M001/slices/S06/tasks/T03-PLAN.md | 41 - .../M001/slices/S06/tasks/T03-SUMMARY.md | 22 - .../M001/slices/S06/tasks/T03-VERIFY.json | 22 - .gsd/milestones/M001/slices/S07/S07-PLAN.md | 15 - .../M001/slices/S07/S07-RESEARCH.md | 122 - .../milestones/M001/slices/S07/S07-SUMMARY.md | 105 - .gsd/milestones/M001/slices/S07/S07-UAT.md | 68 - .../M001/slices/S07/tasks/T01-PLAN.md | 22 - .../M001/slices/S07/tasks/T01-SUMMARY.md | 22 - .../M001/slices/S07/tasks/T01-VERIFY.json | 22 - .../M001/slices/S07/tasks/T02-PLAN.md | 25 - .../M001/slices/S07/tasks/T02-SUMMARY.md | 22 - .../M001/slices/S07/tasks/T02-VERIFY.json | 34 - .../M001/slices/S07/tasks/T03-PLAN.md | 23 - .../M001/slices/S07/tasks/T03-SUMMARY.md | 22 - .../M001/slices/S07/tasks/T03-VERIFY.json | 22 - .gsd/milestones/M002/M002-CONTEXT-DRAFT.md | 42 - .gsd/milestones/M003/M003-CONTEXT-DRAFT.md | 41 - .gsd/milestones/M004/M004-CONTEXT-DRAFT.md | 41 - .gsd/state-manifest.json | 4248 ----------------- 85 files changed, 1 insertion(+), 8742 deletions(-) delete mode 100644 .gsd/DECISIONS.md delete mode 100644 .gsd/KNOWLEDGE.md delete mode 100644 .gsd/OVERRIDES.md delete mode 100644 .gsd/PROJECT.md delete mode 100644 .gsd/REQUIREMENTS.md delete mode 100644 .gsd/event-log.jsonl delete mode 100644 .gsd/milestones/M001/M001-CONTEXT.md delete mode 100644 .gsd/milestones/M001/M001-DISCUSSION.md delete mode 100644 .gsd/milestones/M001/M001-META.json delete mode 100644 .gsd/milestones/M001/M001-ROADMAP.md delete mode 100644 .gsd/milestones/M001/M001-VALIDATION.md delete mode 100644 .gsd/milestones/M001/slices/S01/S01-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S01/S01-RESEARCH.md delete mode 100644 .gsd/milestones/M001/slices/S01/S01-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S01/S01-UAT.md delete mode 100644 .gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S01/tasks/T01-VERIFY.json delete mode 100644 .gsd/milestones/M001/slices/S01/tasks/T02-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S01/tasks/T02-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S01/tasks/T03-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S01/tasks/T03-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S02/S02-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S02/S02-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S02/S02-UAT.md delete mode 100644 .gsd/milestones/M001/slices/S02/tasks/T01-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S02/tasks/T01-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S02/tasks/T02-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S02/tasks/T02-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S03/S03-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S03/S03-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S03/S03-UAT.md delete mode 100644 .gsd/milestones/M001/slices/S03/tasks/T01-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S03/tasks/T01-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S03/tasks/T02-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S03/tasks/T02-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S04/S04-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S04/S04-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S04/S04-UAT.md delete mode 100644 .gsd/milestones/M001/slices/S04/tasks/T01-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S04/tasks/T01-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S04/tasks/T02-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S04/tasks/T02-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S04/tasks/T02-VERIFY.json delete mode 100644 .gsd/milestones/M001/slices/S05/S05-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S05/S05-RESEARCH.md delete mode 100644 .gsd/milestones/M001/slices/S05/S05-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S05/S05-UAT.md delete mode 100644 .gsd/milestones/M001/slices/S05/tasks/T01-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S05/tasks/T01-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S05/tasks/T01-VERIFY.json delete mode 100644 .gsd/milestones/M001/slices/S05/tasks/T02-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S05/tasks/T02-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S05/tasks/T02-VERIFY.json delete mode 100644 .gsd/milestones/M001/slices/S06/S06-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S06/S06-RESEARCH.md delete mode 100644 .gsd/milestones/M001/slices/S06/S06-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S06/S06-UAT.md delete mode 100644 .gsd/milestones/M001/slices/S06/tasks/T01-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S06/tasks/T01-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S06/tasks/T01-VERIFY.json delete mode 100644 .gsd/milestones/M001/slices/S06/tasks/T02-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S06/tasks/T02-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S06/tasks/T02-VERIFY.json delete mode 100644 .gsd/milestones/M001/slices/S06/tasks/T03-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S06/tasks/T03-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S06/tasks/T03-VERIFY.json delete mode 100644 .gsd/milestones/M001/slices/S07/S07-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S07/S07-RESEARCH.md delete mode 100644 .gsd/milestones/M001/slices/S07/S07-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S07/S07-UAT.md delete mode 100644 .gsd/milestones/M001/slices/S07/tasks/T01-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S07/tasks/T01-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S07/tasks/T01-VERIFY.json delete mode 100644 .gsd/milestones/M001/slices/S07/tasks/T02-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S07/tasks/T02-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S07/tasks/T02-VERIFY.json delete mode 100644 .gsd/milestones/M001/slices/S07/tasks/T03-PLAN.md delete mode 100644 .gsd/milestones/M001/slices/S07/tasks/T03-SUMMARY.md delete mode 100644 .gsd/milestones/M001/slices/S07/tasks/T03-VERIFY.json delete mode 100644 .gsd/milestones/M002/M002-CONTEXT-DRAFT.md delete mode 100644 .gsd/milestones/M003/M003-CONTEXT-DRAFT.md delete mode 100644 .gsd/milestones/M004/M004-CONTEXT-DRAFT.md delete mode 100644 .gsd/state-manifest.json diff --git a/.gitignore b/.gitignore index 117a76c..f6bb9c6 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,4 @@ target/ # ── GSD baseline (auto-generated) ── .gsd-id +.gsd/ diff --git a/.gsd/DECISIONS.md b/.gsd/DECISIONS.md deleted file mode 100644 index 6f1e9f4..0000000 --- a/.gsd/DECISIONS.md +++ /dev/null @@ -1,29 +0,0 @@ -# Decisions Register - - - -| # | When | Scope | Decision | Choice | Rationale | Revisable? | Made By | -|---|------|-------|----------|--------|-----------|------------|---------| -| D001 | M001 | scope | Product entry point | External job discovery, then import into the app | The user finds jobs on job sites first; the app begins when a role is imported. | No | collaborative | -| D002 | M001 | pattern | AI action model | Assistive drafting and analysis only; no autonomous sending | The user wants AI help but explicitly does not want auto-send or auto-apply behavior. | No | collaborative | -| D003 | M001 | scope | Primary user | Individual job seeker | The product is designed for individuals managing their own search, not recruiter or team workflows. | Yes — if product direction changes later | collaborative | -| D004 | M001 | pattern | Daily navigation hierarchy | Job table first, then follow-up/dashboard, then individual job workspace | The user explicitly described this as the intended control flow for daily use. | Yes — if real usage disproves the hierarchy | collaborative | -| D005 | M001 | roadmap | First milestone focus | Prioritize Gmail import quality and AI draft quality before broader expansion | The user identified Gmail import and AI drafts as the weakest current areas and the first bar for daily use. | Yes — if execution proves another blocker is more fundamental | collaborative | -| D006 | M001/S02 | workspace-persistence | How the saved application answer draft should persist inside the job workspace before a dedicated field exists | Store the application answer draft in a replaceable notes block and make SaveApplicationDrafts overwrite notes when notes are explicitly provided | The existing append-only notes behavior made the Tailored CV workspace untrustworthy because repeated saves duplicated the application answer indefinitely. A replaceable notes block preserves current schema compatibility while giving the workspace a stable saved/read-back loop for later slices. | Yes | agent | -| D007 | M001/S01 | gmail-continuity | What “good Gmail import” now means for M001 | Treat Gmail import as full-thread continuity: the user must be able to import the whole relevant thread, and already-linked Gmail threads must refresh automatically so later inbound messages and user-sent replies appear on the job without manual re-import. This supersedes the narrower one-time-import interpretation inside D005’s Gmail-import focus. | The user explicitly asked whether the app can bring the whole email thread and automatically show their reply later without re-pulling/importing again. That changes the trust bar from “find and import the right message” to “keep the linked thread current over time,” while still preserving the no-auto-send boundary from D002. | Yes — if Gmail API or product constraints later require a different sync model | human | -| D008 | M001/S01 planning | gmail-sync | How linked Gmail threads stay current in S01 | Use a job-scoped refresh flow over already-imported Gmail thread IDs, triggered from the job workspace/API, instead of building inbox-wide Gmail watch/history cursor infrastructure in M001. | The codebase already persists ExternalThreadId per correspondence and lacks Gmail history/watch infrastructure. Fetching known linked threads for one job keeps scope bounded, fits the single-user workspace model, supports duplicate-safe import of new inbound and sent replies, and creates a trustworthy continuity loop without adding brittle webhook/cursor state before the milestone proves value. | Yes — if real usage shows job-scoped pull refresh is too slow or misses important continuity cases. | agent | -| D009 | M001/S01 closure | gmail-matching | Where Gmail candidate aggregation and ranking logic should live for job-aware import | Keep Gmail query-hit aggregation, dedupe, matched-query traces, and ranking reasons in the backend contract instead of recreating that logic in the React workspace. | The correspondence workspace needs explanatory candidate ranking plus duplicate visibility, and putting the logic in the API keeps one source of truth for scoring/import state while preventing browser-side heuristic drift. | Yes | agent | -| D010 | M001/S03 closeout | followup-drafting | How follow-up grounding should be exposed to the workspace | Return explicit follow-up grounding fields (`contextSummary`, `contextSignals`, `threadSubject`, and last-correspondence metadata) from the backend DTO instead of making the React workspace infer them client-side. | The slice needed draft trust, not just draft text. Putting grounding signals in the API contract gives the UI a durable explanation surface, keeps thread/package inference in one place with generation logic, and makes backend/frontend tests assert the same source of truth. | Yes | agent | -| D011 | M001/S05 planning | workflow-trust | How S05 should represent daily-loop readiness and next-action state across overview surfaces | Introduce explicit workflow trust/action signals from the backend/UI contract and reuse them across the table, dashboard, reminders, and shared workspace instead of continuing to infer behavior from free-form `followUpReason` strings or raw `notes` presence in each component. | S05 is an end-to-end polish slice where the remaining risk is fragmented trust, not missing subsystems. Centralizing workflow signals avoids heuristic drift between overview surfaces, respects the saved application-answer notes-block constraint, and gives the final integrated regression one source of truth for R010 while preserving the explicit manual-send boundary required by R008. | Yes | agent | -| D012 | M001/S05 | workspace-observability | Where linked Gmail thread continuity status should be exposed in the final trust loop | Show linked-thread refresh state directly in the correspondence workspace, including linked thread count and last refresh outcome, instead of hiding continuity feedback inside the Gmail import modal only. | The end-to-end trust loop depends on users being able to verify that already-linked Gmail threads stay current without re-importing. Surfacing continuity state in the main workspace keeps the job timeline trustworthy, supports final UAT, and avoids making thread refresh feel like a hidden one-off import behavior. | Yes | agent | -| D013 | M001/S06/T02 | seeding | How acceptance-ready job data is created for S06 live reruns | Seed the acceptance fixture through the live companies/jobapplications/correspondence API plus the dedicated tailored-cv, application-drafts, and followup endpoints, using deterministic company/title/thread/message identifiers for idempotent reruns. | The slice goal is a repeatable live environment check, so seeding through the same HTTP contract the UI uses proves the real backend surface, keeps package/readiness behavior aligned with production code paths, and avoids brittle direct DB mutations or duplicate correspondence on reruns. | Yes | agent | -| D014 | M001/S06/T03 | acceptance-run | How the S06 live acceptance runner should authenticate seeding and protected UI verification without requiring manual token export every rerun. | Allow the S06 acceptance runner to mint a localhost-only admin JWT from the checked-in dev JWT settings plus the local SQLite admin record when AUTH_TOKEN is missing. | The current DB snapshot contains an admin user but the placeholder development password is not reliable, and the task’s verification command must stay repeatable. A localhost-only signed JWT fallback keeps the run fully local, avoids secret prompts, does not print token material, and still exercises the real protected API/UI paths. | Yes | agent | -| D015 | M001/S06 | environment | S06 preflight auth handling | Treat /api/auth/config reachability plus an auth-limited /api/admin/system probe as a guided partial-pass, and never echo bearer tokens in preflight output. | S06 needs a repeatable go/no-go gate before browser UAT. The live stack can be healthy even when admin-only diagnostics require an extra token, so the preflight should fail hard only for unreachable/malformed API responses while still surfacing clear AUTH_TOKEN guidance and protecting secrets on shared terminals. | Yes | agent | -| D016 | M001/S07 | uat-artifact | How S07 daily-loop closure should capture acceptance evidence | Keep docs/s06-acceptance-run.md as the canonical execution log and use S07 closure artifacts to summarize/import the cross-surface proof rather than duplicating raw runner output. | S07's job is to prove one seeded job stays coherent across /jobs, workspace, /reminders, and /dashboard while preserving the manual-send boundary. Reusing the S06 runner output as the canonical source keeps reruns idempotent, prevents drift between generated logs and human summary text, and gives downstream slices one stable place for detailed evidence plus one concise dependency summary. | Yes | agent | -| D017 | M005 planning | delivery | How M005 execution should be staged and published | Execute M005 one slice at a time, verify each slice independently, push each slice on its own git branch, then continue to the next slice only after the prior slice is stable. | The CV intelligence/export milestone is high-risk and multi-layered. Slice-by-slice branching and push discipline will keep extraction, tailored draft, and PDF rendering changes reviewable and reduce regression blast radius. | Yes | human | -| D018 | M005 planning | verification | What document corpus should drive universal CV extraction verification | Use the real CV files placed in /home/pi/cvs as a regression corpus for universal extractor work, alongside synthetic/unit fixtures. | A universal CV extractor cannot be validated only against synthetic fixtures. Real CVs with different layouts, OCR quality, and structure are required to test extraction, review UX, and rendering assumptions. | Yes | human | -| D019 | M011/S01 | frontend-platform | How to handle frontend build-tool risk during the initial platform hardening slice | Remediate the direct critical frontend dependency immediately, keep the CRA baseline for the next hardening slice, and defer the broader frontend build-tool migration to a later dedicated implementation step. | The audit showed one critical direct dependency issue (`axios`) and a large remaining body of transitive risk concentrated behind `react-scripts`. Upgrading the direct dependency removed the critical finding with low change surface, restored a reproducible local and Docker build baseline, and avoids coupling S02 auth/session work to a framework migration. The remaining CRA transitive debt is still real, but it is now a contained follow-on migration concern rather than an immediate blocker. | Yes | agent | -| D020 | M011/S02 | authentication | What session transport should replace browser-stored bearer tokens in the frontend and API | Use an HttpOnly cookie-backed app session for the primary local auth path, have the API read the local app JWT from a secure cookie instead of browser storage, keep Google credential exchange server-side, and add CSRF protection for state-changing requests. | The current design stores the app bearer token in localStorage/sessionStorage and attaches it via an Authorization header on every request, which leaves the primary local auth path exposed to XSS-driven token theft. A cookie-backed session keeps the app token out of browser storage, lets the API enforce the local auth path centrally, preserves existing JWT-based authorization semantics on the server, and gives the frontend a cleaner source of truth through `/auth/me` and explicit unauthorized responses. Adding CSRF protection alongside the cookie keeps state-changing requests safe under the new transport. | Yes | agent | -| D021 | M011/S03/T01 | frontend-architecture | How to centralize degraded-state handling for the core frontend views in S03. | Use a lightweight shared frontend async-view-state pattern for S03 instead of introducing a new global data-fetching framework in this slice. | The current risk is not lack of a full query library; it is that core views swallow request failures into empty arrays or nulls and then render normal empty states. A small shared abstraction for loading/empty/error/retry state can retire that product risk quickly across the highest-traffic views without broadening S03 into a framework migration or destabilizing the existing app. | Yes | agent | diff --git a/.gsd/KNOWLEDGE.md b/.gsd/KNOWLEDGE.md deleted file mode 100644 index 5adfa66..0000000 --- a/.gsd/KNOWLEDGE.md +++ /dev/null @@ -1,15 +0,0 @@ -# Project Knowledge - -- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` still compiles the entire `JobTrackerApi.Tests` project before filtering execution. If unrelated controller tests drift from production signatures, the Gmail slice verification command will fail at compile time even when `GmailControllerTests` itself is correct. -- The correspondence workspace auto-refreshes linked Gmail threads once per `jobId + ExternalThreadId set` using `POST /api/gmail/refresh-linked-threads`; if you need another pull in the same UI session without changing linked threads, use the explicit "Refresh linked threads" action. -- The S02 package workspace persists the application-answer draft inside `JobApplication.Notes` using the marker block `<<>> ... <<>>`; downstream slices should replace or parse that block instead of appending free-form notes. -- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests` is now a trustworthy direct verification command in this worktree for package-generation and notes-replacement behavior; prefer it over older isolated-harness guidance when checking S02 regressions. -- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsFollowUpDraftTests` is now trustworthy again in this worktree after restoring the missing ASP.NET Core / Identity / xUnit test-project references in `JobTrackerApi.Tests/JobTrackerApi.Tests.csproj`; older task notes that require an isolated Docker harness are stale. -- Running `npm --prefix job-tracker-ui start` alone is not enough for browser UAT in this worktree: the frontend calls `http://localhost:5202/api/...`, so without the backend (or a matching CORS/proxy setup) the UI loads but shows empty-state surfaces with `net::ERR_FAILED`/CORS errors instead of real job data. -- In this CRA frontend, `react-scripts` resolves the app directory from the current working directory. Run UI tests/builds from `job-tracker-ui/` (for example `cd job-tracker-ui && CI=true ./node_modules/.bin/react-scripts ...`) instead of invoking `npm --prefix job-tracker-ui ...` from the repo root, or `react-scripts` may fail looking for a root-level `package.json`. -- The S06 acceptance seed must backdate both `JobApplication.FollowUpAt` and the latest correspondence timestamp past the user’s `AppliedFollowUpDays` threshold; `RulesEngine` computes `Waiting` follow-up from the most recent activity (`DateApplied`, `ResponseDate`, `FollowUpAt`, `FeedbackRequestedAt`, or last correspondence), so a recent reminder date can suppress the intended `workflowSignal.actionKey = "follow-up"` fixture. -- In this M001 worktree, the local SQLite DB contains `admin@example.com` with the `Admin` role even when the placeholder `Auth:AdminPassword` from `appsettings.Development.json` no longer authenticates. For repeatable localhost acceptance reruns, `scripts/s06-acceptance-run.sh` can mint a dev-only local JWT from the checked-in JWT settings instead of depending on a manual bearer-token export. -- `scripts/s06-preflight.sh` intentionally exits 0 on the auth-limited path where `/api/auth/config` is reachable but `/api/admin/system` returns 401/403. Treat that as a guided partial pass for browser/UAT prep; only unreachable API, malformed JSON, or non-auth admin failures should block the slice. -- In this M001 worktree, the focused CRA regression command `CI=true npm --prefix /home/pi/development/JobTracker/.gsd/worktrees/M001/job-tracker-ui test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/workflow-trust-signals.test.tsx` can fail with `react-scripts: not found` even when `job-tracker-ui/node_modules` already exists from an older install state; rerun `npm --prefix /home/pi/development/JobTracker/.gsd/worktrees/M001/job-tracker-ui install` first, then retry the exact test command. -- In the S07 localhost acceptance pass, opening the follow-up draft tab did not emit a fresh captured network request by itself. To verify the R008 manual-send boundary without clicking the send action, use the live UI evidence (`Copy Draft` and `Send And Log Email` both visible), then confirm `GET /api/jobapplications/3/followup-draft` succeeds from the authenticated browser context and that no `POST /api/jobapplications/3/send-followup` request appears during the observed pass. -- In this GSD worktree alias, running frontend installs from the symlinked path can corrupt `job-tracker-ui/package-lock.json` by writing package keys like `../../../../../../.gsd/projects/.../worktrees/M001/job-tracker-ui/node_modules/...`. That lockfile can still work locally but breaks CI `npm ci` on a different checkout path. Before pushing frontend lockfile changes, verify the lock uses plain `node_modules/...` package keys and test it from a different directory. diff --git a/.gsd/OVERRIDES.md b/.gsd/OVERRIDES.md deleted file mode 100644 index 8289ea6..0000000 --- a/.gsd/OVERRIDES.md +++ /dev/null @@ -1,20 +0,0 @@ -# GSD Overrides - -User-issued overrides that supersede plan document content. - ---- -## Override: 2026-03-24T10:57:41.499Z - -**Change:** can the gmail import bring the while tread of emails though? and also update automatically so if i reply to an email itll ahow my responce automaticslly without havjng to repull/kmport the emaila -**Scope:** resolved -**Applied-at:** M001/S01/T01 - ---- - -## Override: 2026-04-10T16:46:22.130Z - -**Change:** use next.js -**Scope:** active -**Applied-at:** M001/none/none - ---- diff --git a/.gsd/PROJECT.md b/.gsd/PROJECT.md deleted file mode 100644 index e596b12..0000000 --- a/.gsd/PROJECT.md +++ /dev/null @@ -1,28 +0,0 @@ -# Project - -## What This Is - -Job Tracker is a personal job-application workspace for an individual user. The user finds jobs elsewhere, imports them into the app, uses AI to improve CVs, cover letters, replies, and follow-ups, and keeps the real-world application process organized through tracking, correspondence, and manual updates. - -## Core Value - -The product must let one person run a real job search without losing the thread: import a role, prepare stronger application material, track what happened, keep Gmail correspondence tied to the right job, and know exactly what needs follow-up next. - -## Current State - -A substantial brownfield app already exists. The repo has a React frontend, an ASP.NET Core API, and a local FastAPI AI service. Current capabilities already include job tracking, companies, attachments, correspondence, reminders, job import preview, Gmail connection/import, profile CV upload/parsing/rewrite flows, AI-assisted tailored CV and cover-letter generation, candidate-fit/focus-plan/interview-prep/readiness endpoints, and dashboard/system surfaces. M001 is now complete across S01-S07: the Gmail workspace is job-aware, backend ranking happens server-side, Gmail imports persist thread/from/to metadata, duplicate-safe single-message and thread imports are explicit, already-linked Gmail threads refresh back into the same job automatically via a bounded `ExternalThreadId` pull, the Tailored CV workspace persists reusable package material, follow-up drafting reuses imported correspondence plus saved package context, and `/jobs`, `/dashboard`, and `/reminders` now share one workflow-signal contract that routes into the same job workspace semantics. S05 finished the trust-loop polish by centralizing workflow trust/action metadata, surfacing linked-thread continuity state directly in the workspace, and adding an integrated regression that proves overview entry → package reuse → Gmail continuity → grounded follow-up drafting without crossing the manual-send boundary. S06 then stabilized the live localhost environment with a repeatable preflight gate, idempotent acceptance-data seeding through the real API, and a rerunnable acceptance-run artifact that re-proves `/jobs` → workspace → reminders/dashboard plus the manual-send boundary in the actual stack. S07 closes that loop with a dedicated daily-loop UAT artifact that traces one seeded job coherently across `/jobs`, the job workspace, `/reminders`, and `/dashboard`, while explicitly preserving the manual-send boundary and calling out that Gmail-connected continuity still requires a genuinely configured Gmail session to be proven live. - -## Architecture / Key Patterns - -The frontend lives in `job-tracker-ui/` and is a React + TypeScript app using MUI. The backend lives in `JobTrackerApi/` and is an ASP.NET Core API with EF Core, Identity, background hosted services, and controller-based endpoints. The local AI service lives in `tools/summarizer/` and provides summarization plus OCR/text extraction. Existing patterns already center around per-job workspaces, Gmail-backed correspondence import, profile-CV-as-source-of-truth, attachment-level AI inclusion controls, and server-side generation endpoints that return drafts for user review rather than autonomous sending. With the override, Gmail-backed correspondence is no longer just a one-time import pattern; it is a linked-thread continuity pattern that must keep the job timeline aligned with the user’s real Gmail thread history. - -## Capability Contract - -See `.gsd/REQUIREMENTS.md` for the explicit capability contract, requirement status, and coverage mapping. - -## Milestone Sequence - -- [x] M001: Gmail and draft quality loop — Make Gmail import and AI drafting strong enough to trust as a daily workflow, including whole-thread Gmail continuity after import. -- [ ] M002: Tracking control center — Strengthen the job table, follow-up surfaces, and tracking rhythm into a clearer control center. -- [ ] M003: Deeper inbox-aware assistance — Extend correspondence awareness and context-driven assistance beyond the first Gmail improvements. -- [ ] M004: Trust, launchability, and hardening — Polish validation, clarity, performance, and operational trust surfaces for sustained daily use. diff --git a/.gsd/REQUIREMENTS.md b/.gsd/REQUIREMENTS.md deleted file mode 100644 index eef527b..0000000 --- a/.gsd/REQUIREMENTS.md +++ /dev/null @@ -1,249 +0,0 @@ -# Requirements - -This file is the explicit capability and coverage contract for the project. - -## Active - -### R008 — The app may draft application, reply, and follow-up content, but it must not auto-send emails or auto-apply to jobs. -- Class: constraint -- Status: active -- Description: The app may draft application, reply, and follow-up content, but it must not auto-send emails or auto-apply to jobs. -- Why it matters: The user called auto-sending dangerous and explicitly does not want that behavior. -- Source: user -- Primary owning slice: M001/S03 -- Supporting slices: M001/S05 -- Validation: Constrained by S03 follow-up tests and workspace UX: focused backend/frontend verification proves drafting uses context while outbound follow-up still requires an explicit manual send/log action. -- Notes: S03 re-checked the manual-send boundary inside the follow-up workspace: users edit drafts locally and `POST /api/jobapplications/{id}/send-followup` fires only from the explicit send/log action. The broader anti-autonomy constraint still remains active across later milestone validation. - -### R009 — Core UX, data model emphasis, and roadmap decisions should optimize for one person managing their own search. -- Class: constraint -- Status: active -- Description: Core UX, data model emphasis, and roadmap decisions should optimize for one person managing their own search. -- Why it matters: Individual-first scope keeps product decisions sharp and prevents premature recruiter/CRM drift. -- Source: user -- Primary owning slice: M001/S04 -- Supporting slices: M002/S01, M004/S01 -- Validation: mapped -- Notes: Shared/team workflows are not the current product target. - -### R018 — Run an adversarial security assessment against the application across input validation, authentication, authorization, API exposure, file uploads, and data exposure. -- Class: operational -- Status: active -- Description: Run an adversarial security assessment against the application across input validation, authentication, authorization, API exposure, file uploads, and data exposure. -- Why it matters: The next milestone is explicitly a hostile security-testing pass intended to find vulnerabilities before attackers do. -- Source: user-security-milestone -- Primary owning slice: M013 -- Validation: Produce verified findings or an explicit no-finding result for each requested attack category. -- Notes: Assessment should assume weak protections and behave like an aggressive tester, not a happy-path reviewer. - -### R019 — For each security issue found, record the vulnerability description, an example exploit input, risk level, and a clear remediation recommendation. -- Class: functional -- Status: active -- Description: For each security issue found, record the vulnerability description, an example exploit input, risk level, and a clear remediation recommendation. -- Why it matters: Security testing is only useful if the output is actionable for remediation and triage. -- Source: user-security-milestone -- Primary owning slice: M013 -- Validation: Each finding includes description, exploit example, risk rating, and fix guidance. -- Notes: If no issue is found in a category, the milestone should still document what was tested and the observed boundary. - -## Validated - -### R001 — The user finds a job outside the app, imports it into the app, and starts the application workflow from that imported role. -- Class: primary-user-loop -- Status: validated -- Description: The user finds a job outside the app, imports it into the app, and starts the application workflow from that imported role. -- Why it matters: The product is not a job board replacement; the import step is the real start of the user loop. -- Source: user -- Primary owning slice: M001/S01 -- Supporting slices: M001/S05 -- Validation: S01 completed with a job-scoped Gmail import loop wired into the job workspace: backend `GET /api/gmail/job-candidates` uses the owned job as context, imports target that job directly, and focused UI verification passed in `job-tracker-ui/src/correspondence-gmail-import.test.tsx`. -- Notes: Validation is contract/UI-level plus workspace integration. Live user UAT of the broader milestone loop still remains for later slices. - -### R002 — Gmail connection, message retrieval, single-message/thread import, and linked-thread refresh must help the user pull real correspondence into the right job, preserve full thread continuity, and automatically surface later inbound or user-sent replies without requiring manual re-import of the thread. -- Class: integration -- Status: validated -- Description: Gmail connection, message retrieval, single-message/thread import, and linked-thread refresh must help the user pull real correspondence into the right job, preserve full thread continuity, and automatically surface later inbound or user-sent replies without requiring manual re-import of the thread. -- Why it matters: Gmail import is one of the two clearest current weaknesses and a major trust surface for daily use; one-time import alone is not enough if the thread immediately goes stale. -- Source: user -- Primary owning slice: M001/S01 -- Supporting slices: M001/S03, M001/S05 -- Validation: Validated by M001/S01: backend `POST /api/gmail/refresh-linked-threads` now refreshes already-linked Gmail thread ids for one owned job, imports only unseen replies into the same job with duplicate-safe counts, focused `GmailControllerTests` pass via `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests`, and `job-tracker-ui/src/correspondence-gmail-import.test.tsx` proves the workspace auto-refresh path shows a later Gmail reply without manual re-import. -- Notes: S01 now covers job-aware Gmail candidate ranking, duplicate-safe single-message/thread import, persisted Gmail thread/from/to metadata, and automatic linked-thread continuity inside the correspondence workspace. - -### R003 — Tailored CV and cover-letter drafts must feel specific, credible, and good enough that the user wants to start from them. -- Class: differentiator -- Status: validated -- Description: Tailored CV and cover-letter drafts must feel specific, credible, and good enough that the user wants to start from them. -- Why it matters: Draft generation exists already, but the milestone bar is actual usefulness rather than feature presence. -- Source: user -- Primary owning slice: M001/S02 -- Supporting slices: M001/S05 -- Validation: Validated by M001/S02: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests` now passes with package generation using recruiter/job/profile/attachment/imported-correspondence context, and `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-generated-drafts.test.tsx` proves generation, editing, save, and saved-state redisplay behavior in the job workspace. -- Notes: S02 also proved the saved application-answer loop by replacing the marker-delimited notes block instead of appending indefinitely. - -### R004 — The app must generate follow-up and reply drafts from the imported job, saved application material, and correspondence context tied to that job. -- Class: primary-user-loop -- Status: validated -- Description: The app must generate follow-up and reply drafts from the imported job, saved application material, and correspondence context tied to that job. -- Why it matters: Follow-through is part of the core value, not an optional afterthought. -- Source: user -- Primary owning slice: M001/S03 -- Supporting slices: M001/S01, M001/S02, M001/S05 -- Validation: Validated by M001/S03: follow-up draft generation now consumes imported correspondence plus saved application package material, focused backend follow-up tests pass in an isolated harness, the focused React follow-up test passes, and browser verification on the built branch UI proved the grounded follow-up draft plus manual send/log flow back into correspondence. -- Notes: S03 validated the follow-up half of the requirement. Explicit reply drafting may still be deepened later, but the requirement-level milestone bar is now met for job-grounded follow-up/reply assistance without autonomous sending. - -### R005 — The first page should give the user a clear overview of jobs, status, readiness, and what needs attention. -- Class: continuity -- Status: validated -- Description: The first page should give the user a clear overview of jobs, status, readiness, and what needs attention. -- Why it matters: The user explicitly wants to start from the job table each day. -- Source: user -- Primary owning slice: M001/S04 -- Supporting slices: M001/S05 -- Validation: Validated by M001/S04: the job table now exposes actionable urgency chips that route directly into the relevant job workspace tab, focused daily-loop UI tests pass, and browser verification confirmed the table/dashboard/reminders flow routes into the same workspace model. -- Notes: S04 made the table a practical first-stop overview for daily use rather than a passive list. - -### R006 — The dashboard and follow-up surfaces must clearly show next actions, neglected threads, and jobs that need attention now. -- Class: continuity -- Status: validated -- Description: The dashboard and follow-up surfaces must clearly show next actions, neglected threads, and jobs that need attention now. -- Why it matters: Tracking is only valuable if it turns state into action. -- Source: user -- Primary owning slice: M001/S04 -- Supporting slices: M001/S05 -- Validation: Validated by M001/S04: dashboard and reminders now expose actionable attention items routed into the existing job workspace, focused daily-loop UI tests pass, and browser verification confirmed those surfaces open the correct job workspace state. -- Notes: S04 made the follow-up/dashboard surfaces show actionable urgency instead of dead-end summaries. - -### R007 — Each job needs a workspace where the user can update status, review/import correspondence, edit drafts, and prepare follow-ups. -- Class: core-capability -- Status: validated -- Description: Each job needs a workspace where the user can update status, review/import correspondence, edit drafts, and prepare follow-ups. -- Why it matters: The user’s third step in the daily flow is to drop into a specific job and do focused work. -- Source: inferred -- Primary owning slice: M001/S03 -- Supporting slices: M001/S02, M001/S04 -- Validation: Validated by M001/S03 and M001/S04: the per-job workspace now supports imported correspondence review, package drafting, follow-up drafting, and routed entry from overview surfaces, with focused tests and browser verification covering package and follow-up loops. -- Notes: S01-S04 now make the individual job workspace the real execution surface for import, drafting, and follow-up work. The reopened Gmail continuity work extends what that correspondence review surface must keep current over time. - -### R010 — The app must preserve a coherent history across manual status changes, imported Gmail correspondence, linked-thread updates, reminders, and follow-up work. -- Class: continuity -- Status: validated -- Description: The app must preserve a coherent history across manual status changes, imported Gmail correspondence, linked-thread updates, reminders, and follow-up work. -- Why it matters: The product promise is to keep the thread of the job search intact over time, including new Gmail replies that happen after the first import. -- Source: user -- Primary owning slice: M001/S04 -- Supporting slices: M001/S01, M001/S03, M001/S05 -- Validation: Validated by M001/S05: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsWorkflowSignalsTests` proves backend reminders/readiness return normalized workflow trust signals, `CI=true ./node_modules/.bin/react-scripts test --watch=false --runTestsByPath src/workflow-trust-signals.test.tsx src/end-to-end-trust-loop.test.tsx src/correspondence-gmail-import.test.tsx src/job-details-generated-drafts.test.tsx src/job-details-followup-drafts.test.tsx src/daily-control-loop.test.tsx` proves `/jobs`, `/dashboard`, and `/reminders` route into the same workspace semantics while preserving saved package reuse, linked-thread continuity, and grounded follow-up drafting, and `CI=true ./node_modules/.bin/react-scripts build` confirms the integrated UI ships as one coherent loop. -- Notes: S05 centralized workflow signals in `job-tracker-ui/src/jobWorkflowSignals.ts`, re-proved linked Gmail continuity in the shared workspace, and added an integrated trust-loop regression so coherence no longer depends on per-surface string heuristics. - -## Deferred - -### R011 — The app should later expand overview analytics, saved views, and clearer strategy readouts beyond the core daily loop. -- Class: operability -- Status: deferred -- Description: The app should later expand overview analytics, saved views, and clearer strategy readouts beyond the core daily loop. -- Why it matters: This can improve search strategy, but it is not the first trust gap to close. -- Source: inferred -- Primary owning slice: M002/S02 -- Supporting slices: none -- Validation: unmapped -- Notes: Deferred because Gmail import and draft quality are higher-value first fixes. - -### R012 — The app may later add richer message understanding, smarter thread handling, and broader inbox-aware assistance after the first Gmail milestone. -- Class: integration -- Status: deferred -- Description: The app may later add richer message understanding, smarter thread handling, and broader inbox-aware assistance after the first Gmail milestone. -- Why it matters: This extends the correspondence workflow, but it depends on getting the initial import/matching loop right first. -- Source: inferred -- Primary owning slice: M003/S01 -- Supporting slices: none -- Validation: unmapped -- Notes: This is the natural next step after M001 proves the core Gmail path, including linked-thread continuity. - -### R013 — The app may later add broader strategic coaching and more advanced guidance beyond application package and follow-up/reply drafting. -- Class: differentiator -- Status: deferred -- Description: The app may later add broader strategic coaching and more advanced guidance beyond application package and follow-up/reply drafting. -- Why it matters: There is room to deepen the assistant, but the current product bar is a stronger core workflow. -- Source: inferred -- Primary owning slice: M003/S02 -- Supporting slices: none -- Validation: unmapped -- Notes: Deferred to avoid scattering the first milestone. - -## Out of Scope - -### R014 — The app will not automatically submit applications to external job sites. -- Class: anti-feature -- Status: out-of-scope -- Description: The app will not automatically submit applications to external job sites. -- Why it matters: This prevents product drift into risky, low-trust automation the user explicitly does not want. -- Source: user -- Primary owning slice: none -- Supporting slices: none -- Validation: n/a -- Notes: The app starts after discovery/import, not at job search submission. - -### R015 — The app will not send replies, follow-ups, or other communication autonomously. -- Class: anti-feature -- Status: out-of-scope -- Description: The app will not send replies, follow-ups, or other communication autonomously. -- Why it matters: Manual control over outbound communication is a hard trust requirement. -- Source: user -- Primary owning slice: none -- Supporting slices: none -- Validation: n/a -- Notes: Drafting is allowed; autonomous sending is not. Automatic thread refresh/import of already-sent Gmail replies is allowed because it reflects history after the user sends manually. - -### R016 — The product will not optimize for shared pipelines, recruiter operations, or multi-user coaching workflows right now. -- Class: out-of-scope -- Status: out-of-scope -- Description: The product will not optimize for shared pipelines, recruiter operations, or multi-user coaching workflows right now. -- Why it matters: This protects the individual-first product shape. -- Source: user -- Primary owning slice: none -- Supporting slices: none -- Validation: n/a -- Notes: Multi-user admin surfaces may exist technically, but they are not the roadmap center. - -### R017 — The app will not try to replace external job boards as the main discovery surface. -- Class: out-of-scope -- Status: out-of-scope -- Description: The app will not try to replace external job boards as the main discovery surface. -- Why it matters: The user explicitly described a workflow that starts after finding the job elsewhere. -- Source: user -- Primary owning slice: none -- Supporting slices: none -- Validation: n/a -- Notes: Job import is the bridge from external discovery into the app. - -## Traceability - -| ID | Class | Status | Primary owner | Supporting | Proof | -|---|---|---|---|---|---| -| R001 | primary-user-loop | validated | M001/S01 | M001/S05 | S01 completed with a job-scoped Gmail import loop wired into the job workspace: backend `GET /api/gmail/job-candidates` uses the owned job as context, imports target that job directly, and focused UI verification passed in `job-tracker-ui/src/correspondence-gmail-import.test.tsx`. | -| R002 | integration | validated | M001/S01 | M001/S03, M001/S05 | Validated by M001/S01: backend `POST /api/gmail/refresh-linked-threads` now refreshes already-linked Gmail thread ids for one owned job, imports only unseen replies into the same job with duplicate-safe counts, focused `GmailControllerTests` pass via `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests`, and `job-tracker-ui/src/correspondence-gmail-import.test.tsx` proves the workspace auto-refresh path shows a later Gmail reply without manual re-import. | -| R003 | differentiator | validated | M001/S02 | M001/S05 | Validated by M001/S02: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests` now passes with package generation using recruiter/job/profile/attachment/imported-correspondence context, and `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-generated-drafts.test.tsx` proves generation, editing, save, and saved-state redisplay behavior in the job workspace. | -| R004 | primary-user-loop | validated | M001/S03 | M001/S01, M001/S02, M001/S05 | Validated by M001/S03: follow-up draft generation now consumes imported correspondence plus saved application package material, focused backend follow-up tests pass in an isolated harness, the focused React follow-up test passes, and browser verification on the built branch UI proved the grounded follow-up draft plus manual send/log flow back into correspondence. | -| R005 | continuity | validated | M001/S04 | M001/S05 | Validated by M001/S04: the job table now exposes actionable urgency chips that route directly into the relevant job workspace tab, focused daily-loop UI tests pass, and browser verification confirmed the table/dashboard/reminders flow routes into the same workspace model. | -| R006 | continuity | validated | M001/S04 | M001/S05 | Validated by M001/S04: dashboard and reminders now expose actionable attention items routed into the existing job workspace, focused daily-loop UI tests pass, and browser verification confirmed those surfaces open the correct job workspace state. | -| R007 | core-capability | validated | M001/S03 | M001/S02, M001/S04 | Validated by M001/S03 and M001/S04: the per-job workspace now supports imported correspondence review, package drafting, follow-up drafting, and routed entry from overview surfaces, with focused tests and browser verification covering package and follow-up loops. | -| R008 | constraint | active | M001/S03 | M001/S05 | Constrained by S03 follow-up tests and workspace UX: focused backend/frontend verification proves drafting uses context while outbound follow-up still requires an explicit manual send/log action. | -| R009 | constraint | active | M001/S04 | M002/S01, M004/S01 | mapped | -| R010 | continuity | validated | M001/S04 | M001/S01, M001/S03, M001/S05 | Validated by M001/S05: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsWorkflowSignalsTests` proves backend reminders/readiness return normalized workflow trust signals, `CI=true ./node_modules/.bin/react-scripts test --watch=false --runTestsByPath src/workflow-trust-signals.test.tsx src/end-to-end-trust-loop.test.tsx src/correspondence-gmail-import.test.tsx src/job-details-generated-drafts.test.tsx src/job-details-followup-drafts.test.tsx src/daily-control-loop.test.tsx` proves `/jobs`, `/dashboard`, and `/reminders` route into the same workspace semantics while preserving saved package reuse, linked-thread continuity, and grounded follow-up drafting, and `CI=true ./node_modules/.bin/react-scripts build` confirms the integrated UI ships as one coherent loop. | -| R011 | operability | deferred | M002/S02 | none | unmapped | -| R012 | integration | deferred | M003/S01 | none | unmapped | -| R013 | differentiator | deferred | M003/S02 | none | unmapped | -| R014 | anti-feature | out-of-scope | none | none | n/a | -| R015 | anti-feature | out-of-scope | none | none | n/a | -| R016 | out-of-scope | out-of-scope | none | none | n/a | -| R017 | out-of-scope | out-of-scope | none | none | n/a | -| R018 | operational | active | M013 | none | Produce verified findings or an explicit no-finding result for each requested attack category. | -| R019 | functional | active | M013 | none | Each finding includes description, exploit example, risk rating, and fix guidance. | - -## Coverage Summary - -- Active requirements: 4 -- Mapped to slices: 4 -- Validated: 8 (R001, R002, R003, R004, R005, R006, R007, R010) -- Unmapped active requirements: 0 diff --git a/.gsd/event-log.jsonl b/.gsd/event-log.jsonl deleted file mode 100644 index 1e77415..0000000 --- a/.gsd/event-log.jsonl +++ /dev/null @@ -1,58 +0,0 @@ -{"cmd":"plan-slice","params":{"milestoneId":"M001","sliceId":"S06"},"ts":"2026-03-27T07:47:00.102Z","actor":"agent","hash":"ad7ae36d97e9c851","session_id":"96f47087-e006-4aa2-8147-1cc42da4374d"} -{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S06","taskId":"T01"},"ts":"2026-03-27T07:57:14.999Z","actor":"agent","hash":"7206faf86461a4cd","session_id":"96f47087-e006-4aa2-8147-1cc42da4374d"} -{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S06","taskId":"T02"},"ts":"2026-03-27T08:09:46.080Z","actor":"agent","hash":"08f3c9c34195dd48","session_id":"96f47087-e006-4aa2-8147-1cc42da4374d"} -{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S06","taskId":"T03"},"ts":"2026-03-27T08:24:16.617Z","actor":"agent","hash":"df80cd5e7e3c84ad","session_id":"96f47087-e006-4aa2-8147-1cc42da4374d"} -{"cmd":"complete-slice","params":{"milestoneId":"M001","sliceId":"S06"},"ts":"2026-03-27T08:29:02.349Z","actor":"agent","hash":"fedfb0239925e215","session_id":"96f47087-e006-4aa2-8147-1cc42da4374d"} -{"cmd":"plan-slice","params":{"milestoneId":"M001","sliceId":"S07"},"ts":"2026-03-27T08:34:48.119Z","actor":"agent","hash":"ece1adcb6dd214ed","session_id":"96f47087-e006-4aa2-8147-1cc42da4374d"} -{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S07","taskId":"T01"},"ts":"2026-03-27T08:36:36.314Z","actor":"agent","hash":"0aa4019d4a27538a","session_id":"96f47087-e006-4aa2-8147-1cc42da4374d"} -{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S07","taskId":"T02"},"ts":"2026-03-27T08:51:21.876Z","actor":"agent","hash":"7f6dfb093ecf298e","session_id":"96f47087-e006-4aa2-8147-1cc42da4374d"} -{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S07","taskId":"T03"},"ts":"2026-03-27T08:55:15.935Z","actor":"agent","hash":"0b8928a7f97d0d42","session_id":"96f47087-e006-4aa2-8147-1cc42da4374d"} -{"cmd":"plan-milestone","params":{"milestoneId":"M005"},"ts":"2026-03-28T22:04:42.705Z","actor":"agent","hash":"9f92dc9597f6bcca","session_id":"14376f9c-a697-450d-ba63-4e6522e8f68d"} -{"cmd":"plan-slice","params":{"milestoneId":"M005","sliceId":"S01"},"ts":"2026-03-28T22:05:00.001Z","actor":"agent","hash":"94d3ace67d51aaad","session_id":"14376f9c-a697-450d-ba63-4e6522e8f68d"} -{"cmd":"plan-slice","params":{"milestoneId":"M005","sliceId":"S02"},"ts":"2026-03-28T22:05:16.424Z","actor":"agent","hash":"cc2907fae86cc252","session_id":"14376f9c-a697-450d-ba63-4e6522e8f68d"} -{"cmd":"plan-slice","params":{"milestoneId":"M005","sliceId":"S03"},"ts":"2026-03-28T22:05:32.786Z","actor":"agent","hash":"ae2f80720d601a48","session_id":"14376f9c-a697-450d-ba63-4e6522e8f68d"} -{"cmd":"plan-slice","params":{"milestoneId":"M005","sliceId":"S04"},"ts":"2026-03-28T22:05:48.342Z","actor":"agent","hash":"38e10b5bfc9e49e6","session_id":"14376f9c-a697-450d-ba63-4e6522e8f68d"} -{"cmd":"plan-slice","params":{"milestoneId":"M005","sliceId":"S05"},"ts":"2026-03-28T22:06:02.267Z","actor":"agent","hash":"a4cdfef1b0f97af3","session_id":"14376f9c-a697-450d-ba63-4e6522e8f68d"} -{"cmd":"plan-milestone","params":{"milestoneId":"M006"},"ts":"2026-04-01T13:42:13.507Z","actor":"agent","hash":"4e6e2177aea2c247","session_id":"4611175a-96ec-432d-832a-0269486cb6ff"} -{"cmd":"plan-milestone","params":{"milestoneId":"M007"},"ts":"2026-04-01T13:45:43.599Z","actor":"agent","hash":"f74c11f87b160d5e","session_id":"4611175a-96ec-432d-832a-0269486cb6ff"} -{"cmd":"plan-milestone","params":{"milestoneId":"M010"},"ts":"2026-04-01T13:45:43.608Z","actor":"agent","hash":"0767a15a4163e364","session_id":"4611175a-96ec-432d-832a-0269486cb6ff"} -{"cmd":"plan-milestone","params":{"milestoneId":"M009"},"ts":"2026-04-01T13:45:43.609Z","actor":"agent","hash":"868651dc3e9840ba","session_id":"4611175a-96ec-432d-832a-0269486cb6ff"} -{"cmd":"plan-milestone","params":{"milestoneId":"M008"},"ts":"2026-04-01T13:45:43.611Z","actor":"agent","hash":"a17e013ae4c6fbc7","session_id":"4611175a-96ec-432d-832a-0269486cb6ff"} -{"cmd":"plan-slice","params":{"milestoneId":"M006","sliceId":"S01"},"ts":"2026-04-01T13:46:55.228Z","actor":"agent","hash":"53e13651ee21608e","session_id":"4611175a-96ec-432d-832a-0269486cb6ff"} -{"v":2,"cmd":"plan-milestone","params":{"milestoneId":"M011"},"ts":"2026-04-10T16:33:49.574Z","actor":"agent","hash":"8da5bd1f6d8be219","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"plan-slice","params":{"milestoneId":"M011","sliceId":"S01"},"ts":"2026-04-10T16:36:01.325Z","actor":"agent","hash":"1b39eb81745f79cb","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S01","taskId":"T01"},"ts":"2026-04-10T16:45:13.023Z","actor":"agent","hash":"df43e89bf0ef508a","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S01","taskId":"T02"},"ts":"2026-04-10T16:46:52.982Z","actor":"agent","hash":"fc183a287cf7e0ec","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S01","taskId":"T03"},"ts":"2026-04-10T16:47:07.060Z","actor":"agent","hash":"96dbf0b722260441","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-slice","params":{"milestoneId":"M011","sliceId":"S01"},"ts":"2026-04-10T16:47:38.406Z","actor":"agent","hash":"e8b7e8fcc07292af","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"reassess-roadmap","params":{"milestoneId":"M011","completedSliceId":"S01"},"ts":"2026-04-10T16:47:48.162Z","actor":"agent","hash":"e8d28553a74cd045","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"plan-slice","params":{"milestoneId":"M011","sliceId":"S02"},"ts":"2026-04-10T16:48:16.316Z","actor":"agent","hash":"c6f7c425cd77c100","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S02","taskId":"T01"},"ts":"2026-04-10T16:49:40.607Z","actor":"agent","hash":"1e247d4737f232b4","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S02","taskId":"T02"},"ts":"2026-04-10T19:57:16.264Z","actor":"agent","hash":"02eb6bc1686244e9","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S02","taskId":"T03"},"ts":"2026-04-10T19:57:41.031Z","actor":"agent","hash":"85c32d040f9631aa","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-slice","params":{"milestoneId":"M011","sliceId":"S02"},"ts":"2026-04-10T19:58:17.389Z","actor":"agent","hash":"3115b597816bc8cb","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"reassess-roadmap","params":{"milestoneId":"M011","completedSliceId":"S02"},"ts":"2026-04-10T19:58:21.945Z","actor":"agent","hash":"51ed90ab022e6ae9","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"plan-slice","params":{"milestoneId":"M011","sliceId":"S03"},"ts":"2026-04-10T22:04:32.223Z","actor":"agent","hash":"10a79a238ead7007","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S03","taskId":"T01"},"ts":"2026-04-10T22:05:25.953Z","actor":"agent","hash":"4d7f978e674fb278","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S03","taskId":"T02"},"ts":"2026-04-10T22:19:14.274Z","actor":"agent","hash":"94e0f7a9b24dd246","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S03","taskId":"T03"},"ts":"2026-04-10T22:19:33.234Z","actor":"agent","hash":"31c5bb74a3280df0","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-slice","params":{"milestoneId":"M011","sliceId":"S03"},"ts":"2026-04-10T22:20:05.975Z","actor":"agent","hash":"7a76f48b67c6a4fa","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"reassess-roadmap","params":{"milestoneId":"M011","completedSliceId":"S03"},"ts":"2026-04-10T22:20:18.782Z","actor":"agent","hash":"0bdf677f91c94f7b","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"plan-slice","params":{"milestoneId":"M011","sliceId":"S04"},"ts":"2026-04-10T22:32:19.950Z","actor":"agent","hash":"ad5a195d23e3979e","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S04","taskId":"T01"},"ts":"2026-04-10T22:33:06.567Z","actor":"agent","hash":"dff04d446600fb9c","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S04","taskId":"T02"},"ts":"2026-04-10T22:44:02.977Z","actor":"agent","hash":"0a94bd5f4e0d3c90","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S04","taskId":"T03"},"ts":"2026-04-10T22:44:23.671Z","actor":"agent","hash":"6148706a46d32f7b","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-slice","params":{"milestoneId":"M011","sliceId":"S04"},"ts":"2026-04-10T22:44:54.430Z","actor":"agent","hash":"e68c20060f20dd34","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"reassess-roadmap","params":{"milestoneId":"M011","completedSliceId":"S04"},"ts":"2026-04-10T22:45:07.836Z","actor":"agent","hash":"f92571f10029d5e9","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"plan-slice","params":{"milestoneId":"M011","sliceId":"S05"},"ts":"2026-04-10T22:55:56.643Z","actor":"agent","hash":"39e7d7ed3cc34612","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S05","taskId":"T01"},"ts":"2026-04-10T22:56:09.946Z","actor":"agent","hash":"c133fd6bf6b26629","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S05","taskId":"T02"},"ts":"2026-04-10T22:59:48.954Z","actor":"agent","hash":"f603df2d0e5cd772","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S05","taskId":"T03"},"ts":"2026-04-10T23:00:30.352Z","actor":"agent","hash":"96ecf88ce819d73b","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-slice","params":{"milestoneId":"M011","sliceId":"S05"},"ts":"2026-04-10T23:00:57.810Z","actor":"agent","hash":"31a2aca44265f192","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"reassess-roadmap","params":{"milestoneId":"M011","completedSliceId":"S05"},"ts":"2026-04-10T23:01:02.519Z","actor":"agent","hash":"fe0bd7ec6ab8df21","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"plan-slice","params":{"milestoneId":"M011","sliceId":"S06"},"ts":"2026-04-10T23:01:49.394Z","actor":"agent","hash":"f2b438884ca52230","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S06","taskId":"T01"},"ts":"2026-04-10T23:20:01.968Z","actor":"agent","hash":"406e0f3c172d1161","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S06","taskId":"T02"},"ts":"2026-04-10T23:24:03.823Z","actor":"agent","hash":"1a2544dcd9f4f925","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-task","params":{"milestoneId":"M011","sliceId":"S06","taskId":"T03"},"ts":"2026-04-10T23:24:23.101Z","actor":"agent","hash":"f583516649531d4c","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-slice","params":{"milestoneId":"M011","sliceId":"S06"},"ts":"2026-04-10T23:24:52.479Z","actor":"agent","hash":"b2c2dc564fb09dfe","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} -{"v":2,"cmd":"complete-milestone","params":{"milestoneId":"M011"},"ts":"2026-04-10T23:25:36.547Z","actor":"agent","hash":"10b42cbd47fe0d4a","session_id":"f90d26d1-ea3a-48a7-b5ff-50c99ba96644"} diff --git a/.gsd/milestones/M001/M001-CONTEXT.md b/.gsd/milestones/M001/M001-CONTEXT.md deleted file mode 100644 index 467cca5..0000000 --- a/.gsd/milestones/M001/M001-CONTEXT.md +++ /dev/null @@ -1,113 +0,0 @@ -# M001: Gmail and draft quality loop - -**Gathered:** 2026-03-24 -**Status:** Ready for planning - -## Project Description - -This milestone upgrades an existing personal job-tracking app into a workflow the user can trust every day. The user still finds jobs outside the app and applies outside the app, but once a job is imported, the app should become the place where the user prepares stronger application material, imports relevant Gmail correspondence, generates follow-up and reply drafts, and keeps the process organized without losing the thread. - -## Why This Milestone - -The codebase already has job import, Gmail import, CV tooling, AI-assisted draft generation, readiness analysis, and per-job workspaces. The problem is not total feature absence; it is that the two most important weak points in the current product are Gmail import and AI draft quality. Those are foundational trust surfaces. If Gmail import feels dumb or the AI drafts feel weak, the app does not earn a place in the user’s daily job-search loop. This milestone focuses on making the existing workflow materially better before broadening the product outward. - -## User-Visible Outcome - -### When this milestone is complete, the user can: - -- import a job found on an external job site, generate a tailored application package, and use it as the basis for a real application outside the app -- connect Gmail, import the right correspondence into the right job with less cleanup, and generate follow-up or reply drafts from real context - -### Entry point / environment - -- Entry point: browser UI starting from the job table and per-job workspace -- Environment: browser + local/dev or deployed web app backed by API, database, Gmail OAuth, and local AI service -- Live dependencies involved: database, Gmail OAuth/import, local AI service, optional SMTP for reminder/follow-up workflows - -## Completion Class - -- Contract complete means: the relevant API endpoints, UI flows, persisted job/correspondence state, and draft-generation paths are wired and verified with tests and artifact checks -- Integration complete means: real Gmail import, job-linked correspondence, and AI draft generation work together across frontend, API, and AI service boundaries -- Operational complete means: the workflow survives real auth/config/service conditions well enough that a user can use it repeatedly without hidden setup traps or dangerous outbound automation - -## Final Integrated Acceptance - -To call this milestone complete, we must prove: - -- a user can import a real job, generate a stronger tailored CV and cover-letter package, and save/edit that material in the job workspace -- a user can connect Gmail, import the correct message or thread into a job, and then generate a context-aware follow-up or reply draft from that imported correspondence -- the full loop from job table → follow-up/dashboard → individual job workspace works cleanly enough for real repeated use, and no part of the milestone relies on auto-send or auto-apply behavior - -## Risks and Unknowns - -- Gmail matching quality may still be noisy — if message-to-job association is weak, the correspondence workflow will not feel trustworthy -- Draft quality may plateau even with better prompts — if outputs still feel generic, the main value promise remains unproven -- Existing capability may be present but fragmented — if the workflow still feels scattered across tabs and screens, the product will not feel like one coherent workspace -- The daily overview may still under-signal urgency — if the table and dashboard do not turn state into action, tracking value stays abstract - -## Existing Codebase / Prior Art - -- `JobTrackerApi/Controllers/GmailController.cs` — existing Gmail OAuth, message listing, and import endpoints -- `JobTrackerApi/Services/GmailOAuthService.cs` — Gmail token handling and Gmail API access -- `JobTrackerApi/Controllers/JobImportController.cs` — existing external job import preview flow -- `JobTrackerApi/Controllers/JobApplicationsController.cs` — current candidate-fit, focus-plan, interview-prep, readiness, tailored-CV, and follow-up draft surfaces -- `JobTrackerApi/Controllers/ProfileCvController.cs` — profile CV upload, extraction, parse, rebuild, and improve flows -- `job-tracker-ui/src/components/Correspondence.tsx` — current Gmail connection/import UI inside job correspondence -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — current per-job AI tabs, draft editing, attachment context selection, and readiness UI -- `job-tracker-ui/src/components/JobTable.tsx` — job table surface that should remain the primary daily control view -- `job-tracker-ui/src/components/DashboardView.tsx` — dashboard/follow-up surface that should become a clearer urgency view -- `tools/summarizer/app.py` — local AI service boundary for summarization and extraction - -> See `.gsd/DECISIONS.md` for all architectural and pattern decisions — it is an append-only register; read it during planning, append to it during execution. - -## Relevant Requirements - -- R001 — establishes the import-first workflow around jobs discovered elsewhere -- R002 — improves Gmail import into something the user can trust as part of the daily workflow -- R003 — raises AI application draft quality from present to genuinely useful -- R004 — turns imported job and correspondence context into better reply/follow-up drafting -- R005 — preserves the job table as the first control surface -- R006 — makes follow-up/dashboard views better at surfacing urgency and next actions -- R007 — strengthens the individual job workspace as the place to do focused work -- R008 — keeps all outbound communication manual and user-controlled -- R009 — keeps milestone scope optimized for an individual user, not a recruiter workflow -- R010 — ensures tracking continuity across manual updates and imported correspondence - -## Scope - -### In Scope - -- smarter Gmail message/thread import and job matching quality -- better use of imported correspondence as context for reply and follow-up drafting -- stronger tailored CV and cover-letter generation quality -- tighter daily-use flow across job table, follow-up/dashboard, and individual job workspace -- coherence improvements that make existing AI and tracking capability feel like one product loop - -### Out of Scope / Non-Goals - -- automatic job application submission -- automatic sending of follow-up or reply emails -- turning the app into a job-discovery product that replaces external job boards -- recruiter CRM, shared pipelines, or team collaboration workflows -- broad product expansion unrelated to Gmail import quality, draft quality, or the daily control loop - -## Technical Constraints - -- preserve the no-auto-send trust boundary across all AI-assisted communication flows -- build on the existing React + ASP.NET Core + local FastAPI service architecture rather than replacing it -- respect existing Gmail OAuth and job-linked correspondence patterns already in the codebase -- keep draft generation grounded in real profile CV, job, attachment, and correspondence context rather than generic prompts alone -- maintain an individual-first UX even where admin/multi-user code exists in the repo - -## Integration Points - -- Gmail API — used for message listing, thread retrieval, and import into job correspondence -- local AI service — used for summarization, extraction, and draft generation context building -- ASP.NET Core API + EF Core database — persists jobs, correspondence, CV/profile state, and draft outputs -- browser UI — presents the table/dashboard/job-workspace control flow that must feel coherent after this milestone - -## Open Questions - -- How far matching confidence and message-to-job suggestion logic can be pushed with the current Gmail data model — current thinking: prove it through real import UX in S01 rather than abstract matching rules -- Whether prompt/workflow changes alone are enough to make drafts feel strong, or whether UI/context shaping is the bigger lever — current thinking: test both because “quality” here is partly generation and partly surrounding workflow -- Which existing dashboard/table elements should be simplified versus enriched — current thinking: prioritize action clarity over adding more analytics first diff --git a/.gsd/milestones/M001/M001-DISCUSSION.md b/.gsd/milestones/M001/M001-DISCUSSION.md deleted file mode 100644 index 7be79a7..0000000 --- a/.gsd/milestones/M001/M001-DISCUSSION.md +++ /dev/null @@ -1,40 +0,0 @@ -# M001 Discussion Log - -## Exchange — 2026-03-24T08:14:04.663Z - -### M003 Gate - -For M003 — deeper external awareness and automation like smarter inbox handling beyond the first Gmail improvements — should I write a draft for later? - -- **Write draft for later (Recommended)** — Keep the current intent as a draft now, then do a focused discussion when this milestone comes up. -- **Discuss now** — Use this session to deepen M003 enough for a full context file. -- **Just queue it** — Record the milestone only and leave discussion for a future session from scratch. - -**Selected:** Write draft for later (Recommended) - ---- -## Exchange — 2026-03-24T08:14:27.657Z - -### M004 Gate - -For M004 — launch hardening, validation, trust, clarity, and operational polish — should I write a draft for later? - -- **Write draft for later (Recommended)** — Capture the current intent as a draft now and refine it when the product is closer to launch hardening. -- **Discuss now** — Use this session to deepen M004 enough for a full context file. -- **Just queue it** — Record the milestone only and leave discussion for a future session from scratch. - -**Selected:** Write draft for later (Recommended) - ---- -## Exchange — 2026-03-24T08:14:59.499Z - -### M001 Check - -I verified the current code has job import, Gmail import, CV tooling, and AI draft surfaces already present; should M001 be written as a coherence-and-quality milestone around those real capabilities? - -- **Yes, write it (Recommended)** — Write M001 around smarter Gmail import, stronger drafts, and the daily control loop. -- **Not quite — adjust it** — The milestone emphasis is off and needs correction before writing. - -**Selected:** Yes, write it (Recommended) - ---- diff --git a/.gsd/milestones/M001/M001-META.json b/.gsd/milestones/M001/M001-META.json deleted file mode 100644 index b657e91..0000000 --- a/.gsd/milestones/M001/M001-META.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "integrationBranch": "main" -} diff --git a/.gsd/milestones/M001/M001-ROADMAP.md b/.gsd/milestones/M001/M001-ROADMAP.md deleted file mode 100644 index 141b7b1..0000000 --- a/.gsd/milestones/M001/M001-ROADMAP.md +++ /dev/null @@ -1,15 +0,0 @@ -# M001: M001: M001: Gmail and draft quality loop - -## Vision -M001: M001: Gmail and draft quality loop - -## Slice Overview -| ID | Slice | Risk | Depends | Done | After this | -|----|-------|------|---------|------|------------| -| S01 | Smarter Gmail import and matching | high | — | ✅ | TBD | -| S02 | Stronger AI application package drafting | high | S01 | ✅ | TBD | -| S03 | Reply and follow-up drafting from real thread context | medium | S01, S02 | ✅ | TBD | -| S04 | Daily control loop surfaces | medium | S01, S03 | ✅ | TBD | -| S05 | End-to-end trust and workflow polish | low | S01, S02, S03, S04 | ✅ | TBD | -| S06 | Live environment stabilization and integrated acceptance rerun | high | S05 | ✅ | TBD | -| S07 | Daily-loop UAT artifact closure | medium | S06 | ✅ | TBD | diff --git a/.gsd/milestones/M001/M001-VALIDATION.md b/.gsd/milestones/M001/M001-VALIDATION.md deleted file mode 100644 index d6f69ec..0000000 --- a/.gsd/milestones/M001/M001-VALIDATION.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -verdict: needs-remediation -remediation_round: 0 ---- - -# Milestone Validation: M001 - -## Success Criteria Checklist -- [x] Criterion 1 — evidence: S02 wired `POST /api/jobapplications/{id}/generate-application-package` to imported correspondence, recruiter/job/profile context, and persisted package workspace state in `JobDetailsDialog.tsx`; focused backend/frontend tests prove generate/edit/save/reload behavior for tailored CV, cover letter, recruiter message, and application-answer drafts. -- [x] Criterion 2 — evidence: S01 delivered job-scoped Gmail candidate ranking, single-message and full-thread import, persisted Gmail metadata, and correspondence workspace rendering; focused backend/frontend tests substantiate lower-cleanup import behavior and timeline/workspace reflection. -- [x] Criterion 3 — evidence: S01 added `POST /api/gmail/refresh-linked-threads` plus persisted `ExternalThreadId`/`ExternalMessageId`, and both S01 and S05 report duplicate-safe linked-thread refresh that imports later replies into the same job without manual re-import. -- [x] Criterion 4 — evidence: S03 added context-grounded follow-up drafting from imported correspondence plus saved package material and kept the explicit manual-send boundary; focused backend/frontend tests and follow-up workspace behavior substantiate the drafting loop. -- [x] Criterion 5 — evidence: S04 and S05 align `/jobs`, `/dashboard`, `/reminders`, and the per-job workspace around one routed control loop, with daily-loop and integrated trust-loop tests covering the shared workflow path. -- [x] Criterion 6 — evidence: S03 and S05 preserve the manual-send boundary; drafting/regeneration stays separate from `send-followup`, and requirement R008 remains constrained/active. -- [ ] Integrated live re-check against real behavior — gap: milestone definition of done requires success criteria to be re-checked against live behavior and final integrated acceptance scenarios to pass, but the available evidence remains mostly contract/test-based. S01 explicitly says live Gmail UAT was not executed, S05 says full live UAT still depends on resolving the backend CORS/runtime mismatch on `http://localhost:5202`, and the milestone does not yet contain executed end-to-end acceptance results for the full real loop. - -## Slice Delivery Audit -| Slice | Claimed | Delivered | Status | -|-------|---------|-----------|--------| -| S01 | User can connect Gmail, review likely messages/threads for a job, import a message or full thread, and trust linked Gmail threads to stay current without manual re-import. | Summary substantiates ranked job-aware Gmail candidates, duplicate-safe single-message/thread import, persisted Gmail metadata, and linked-thread refresh on known job-linked threads. | pass | -| S02 | Imported job plus profile/CV context generates materially better tailored CV and cover-letter drafts that feel specific and usable. | Summary substantiates stronger package-context assembly plus persisted generate/edit/save/reset workspace for package artifacts. | pass | -| S03 | Inside a job, the user can generate follow-up and reply drafts grounded in imported/auto-refreshed correspondence plus saved application context, then edit before sending manually. | Summary substantiates grounded follow-up drafting, exposed context metadata, editable draft flow, and explicit manual-send/log boundary. | pass | -| S04 | Job table becomes primary overview and dashboard/follow-up surfaces clearly show what needs attention next. | Summary substantiates routed dashboard/reminders/job-table actions and focused UI/browser proof, but the checked-in `S04-UAT.md` is still a doctor-created placeholder rather than a real executed UAT artifact. | needs-attention | -| S05 | Full loop works cleanly in a real environment: import job → generate package → apply externally → import/update correspondence automatically → draft follow-up/reply → track progress confidently. | Summary substantiates shared workflow-signal contract, visible package/continuity trust state, and integrated regression coverage, but also states full live UAT is still blocked by backend CORS/runtime mismatch, so the “real environment” claim is not yet fully closed. | fail | - -## Cross-Slice Integration -- S01 → S02: aligned. S02 explicitly consumes imported correspondence and recruiter/thread context from S01 in package generation. -- S01 → S03: aligned. S03 builds follow-up context from persisted correspondence and linked-thread metadata instead of transient Gmail candidates. -- S02 → S03: aligned. S03 reuses saved package fields and the marker-delimited application-answer block established in S02. -- S03 → S04: aligned. S04 routes users into the Follow-up and Tailored CV workspace tabs rather than inventing a second compose loop. -- S04 → S05: partially aligned. S05 centralizes workflow signals and integrated routing as planned, but the boundary-map expectation of final live integration proof is not yet satisfied because the available evidence stops at tests plus limited browser shell verification. - -## Requirement Coverage -- Coverage is mapped for all active requirements: R008 is addressed by S03/S05 and R009 is addressed by S04 (with later supporting slices planned outside M001). -- Validated requirements R001, R002, R003, R004, R005, R006, R007, and R010 all have at least one substantiating slice. -- No active requirement is completely unaddressed. -- Remaining concern is validation depth, not mapping breadth: milestone-level live proof is still missing for the integrated loop, and S04’s UAT artifact is incomplete. - -## Verdict Rationale -`needs-remediation` because the milestone has strong implementation and regression-test evidence, but it does not yet meet its own definition of done for live integrated acceptance. The most material gaps are: - -1. **No executed full-loop live acceptance evidence.** S01 deferred live Gmail UAT, and S05 explicitly reports that full browser UAT is still blocked by backend CORS/runtime mismatch. -2. **S05’s claimed “real environment” outcome is not fully substantiated.** The summary itself narrows proof to integrated tests plus limited browser shell verification. -3. **S04’s UAT artifact is still a placeholder.** Even though S04 summary references browser verification, the required human-verification artifact was not properly closed. - -These are milestone-sealing gaps rather than cosmetic documentation issues, because M001 explicitly requires live behavior re-checks and final integrated acceptance scenarios before completion. - -## Remediation Plan -- **S06: Live environment stabilization and end-to-end acceptance rerun** — fix the backend/frontend runtime/CORS mismatch for the M001 environment, then execute and record a real browser-based acceptance pass covering `/jobs` → workspace package state → Gmail linked-thread refresh/continuity → grounded follow-up draft → `/dashboard` and `/reminders` entry consistency, without violating the manual-send boundary. -- **S07: Daily-loop UAT artifact closure** — replace the placeholder `S04-UAT.md` with a real executed UAT record that confirms the overview surfaces and job workspace behave coherently for the same job, using the stabilized environment from S06. diff --git a/.gsd/milestones/M001/slices/S01/S01-PLAN.md b/.gsd/milestones/M001/slices/S01/S01-PLAN.md deleted file mode 100644 index bdc742d..0000000 --- a/.gsd/milestones/M001/slices/S01/S01-PLAN.md +++ /dev/null @@ -1,12 +0,0 @@ -# S01: Smarter Gmail import and matching - -**Goal:** Finish S01 by turning the existing job-aware Gmail import flow into a live linked-thread continuity loop for one job workspace. -**Demo:** After this: TBD - -## Tasks -- [x] **T01: Add linked Gmail thread refresh to the backend contract** — - - Files: JobTrackerApi/Controllers/GmailController.cs, JobTrackerApi/Services/GmailOAuthService.cs, JobTrackerApi.Tests/GmailControllerTests.cs, JobTrackerApi/Program.cs - - Verify: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` -- [x] **T02: Surface live Gmail thread continuity in the job workspace** — - - Files: job-tracker-ui/src/components/Correspondence.tsx, job-tracker-ui/src/types.ts, job-tracker-ui/src/correspondence-gmail-import.test.tsx, job-tracker-ui/src/components/JobDetailsDialog.tsx - - Verify: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` diff --git a/.gsd/milestones/M001/slices/S01/S01-RESEARCH.md b/.gsd/milestones/M001/slices/S01/S01-RESEARCH.md deleted file mode 100644 index 615c440..0000000 --- a/.gsd/milestones/M001/slices/S01/S01-RESEARCH.md +++ /dev/null @@ -1,110 +0,0 @@ -# S01 — Research - -**Date:** 2026-03-24 - -## Summary - -S01 owns **R001** and **R002** directly, and it materially supports **R010** because imported correspondence becomes part of the job timeline and later follow-up context. The codebase already has a complete Gmail OAuth path, a Gmail message listing endpoint, single-message and thread import endpoints, and a per-job correspondence UI. The gap is not missing Gmail plumbing; it is that matching is still mostly manual. The backend returns raw Gmail search results, while the frontend (`job-tracker-ui/src/components/Correspondence.tsx`) applies a lightweight client-side score based only on the freeform query, snippet text, and already-imported subjects. That does not use the actual job/company context strongly enough to satisfy the “less manual cleanup” bar in R002. - -The best approach is to keep the existing OAuth/import flow and add a **job-aware matching layer** rather than replacing the Gmail integration. In practice that means: enrich backend candidate discovery around a specific `JobApplication`, return grouped thread/message suggestions with explicit match reasons/confidence inputs, and then update the correspondence dialog to present those suggestions first. This follows the current architecture cleanly and preserves the no-auto-send boundary from D002. The React side should keep async work consolidated instead of scattering additional fetches across effects; the loaded `react-best-practices` skill is relevant here, especially `async-parallel` and `client-event-listeners`. - -## Recommendation - -Add a dedicated **job-scoped Gmail matching surface** on top of the existing endpoints instead of trying to make the current generic `/api/gmail/messages` search UI smarter only in the browser. - -Recommended shape: -- Backend: add a job-aware endpoint in `JobTrackerApi/Controllers/GmailController.cs` that accepts `jobApplicationId` and optional overrides, loads the job + company context, builds Gmail queries from `JobTitle`, `Company.Name`, `Company.RecruiterEmail`, recruiter name, and recent imported correspondence, then returns ranked message/thread candidates with **match reasons** and enough metadata for import decisions. -- Persistence: if the planner wants durable thread-aware behavior, extend `Models/Correspondence.cs` beyond `ExternalMessageId` to also persist at least `ExternalThreadId` and raw sender/recipient metadata. This is the cleanest way to support downstream S03 reply/follow-up context without re-deriving it later. -- Frontend: refactor `job-tracker-ui/src/components/Correspondence.tsx` so the Gmail tab consumes enriched API data instead of doing primary ranking locally. `JobDetailsDialog.tsx` already loads the full job; passing the job or a reduced job-context prop into `Correspondence` is cheaper than forcing the Gmail tab to rediscover job facts. - -Why this approach: -- It uses the existing Gmail OAuth/token flow unchanged. -- It moves matching logic to the backend where job context, dedupe checks, and future heuristics are easier to test. -- It avoids over-investing in fragile client-only heuristics. -- It creates a natural seam for S03, where better thread metadata and message provenance will matter again. - -## Implementation Landscape - -### Key Files - -- `JobTrackerApi/Controllers/GmailController.cs` — current Gmail API surface. Has `/status`, `/connect-url`, `/messages`, `/import`, and `/import-thread`. Import endpoints already attach messages to `JobApplication` and dedupe by `Correspondence.ExternalMessageId`, but discovery is still generic and not job-aware. -- `JobTrackerApi/Services/GmailOAuthService.cs` — Gmail OAuth/token refresh and Gmail API access. `ListMessagesAsync` currently calls the Gmail list endpoint and then does an **N+1** sequence of `GetMessageAsync` calls to hydrate summaries. There is no thread-specific fetch API, no job-aware query builder, and no ranking/match-reason contract here yet. -- `job-tracker-ui/src/components/Correspondence.tsx` — the main S01 frontend surface. It opens the Gmail dialog, loads `/gmail/status`, loads `/gmail/messages`, groups by `threadId`, and sorts using `scoreMessage(...)`. Current suggestions come from existing correspondence subjects, not from job/company/recruiter context. -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — already loads the full `JobApplication` record and hosts the Correspondence tab. This is the easiest place to pass job context into `Correspondence` instead of refetching it inside the Gmail tab. -- `job-tracker-ui/src/types.ts` — frontend contracts for `GmailStatus`, `GmailMessageSummary`, and `CorrespondenceMessage`. Any enriched matching response or persisted metadata expansion needs updates here. -- `Models/Correspondence.cs` — currently stores `From`, `Subject`, `Channel`, `ExternalMessageId`, `Content`, and `Date`. No `ThreadId`, no original Gmail sender/recipient fields, and no match/debug metadata. -- `Data/JobTrackerContext.cs` — EF relationships and ownership filters. `JobApplication`, `Company`, and `GmailConnection` are user-scoped via query filters; new Gmail-matching endpoints should continue loading jobs through this context rather than bypassing ownership. -- `Models/Company.cs` and `Models/JobApplication.cs` — hold the matching signals that the current Gmail UI ignores: `Company.Name`, `RecruiterEmail`, `RecruiterName`, `JobTitle`, `JobUrl`, `ShortSummary`, and existing correspondence/timeline relationships. -- `JobTrackerApi/Controllers/CorrespondenceController.cs` — current create/list/delete API for job-linked messages. If S01 persists extra Gmail metadata, this contract may need to expose it to the UI and timeline. -- `JobTrackerApi/Controllers/JobApplicationsController.cs` — downstream dependency surface. It already reads `Correspondences` for follow-up drafting and timeline assembly, so better message/thread metadata here directly helps S03. -- `JobTrackerApi/Program.cs` — important migration/backfill guardrail. The app manually ensures legacy SQLite/MySQL columns such as `Correspondences.Subject`, `Channel`, and `ExternalMessageId`. If S01 adds new persistence columns, these compatibility blocks must be updated alongside the EF migration. -- `JobTrackerApi.Tests/GmailControllerTests.cs` — only covers the empty-thread import validation case today. Good starting point, but far below the verification level needed for S01. -- `job-tracker-ui/src/job-details-generated-drafts.test.tsx` — representative React test style: mock `api`, render `JobDetailsDialog`, assert visible tab behavior. Follow this pattern for new Gmail suggestion/import UI tests. - -### Build Order - -1. **Decide and lock the backend contract first.** - - Prove what a “smarter match” response looks like: message/thread grouping, rank/confidence, reasons, imported/already-linked flags, and import actions. - - This is the riskiest part and unblocks everything else. - -2. **Implement job-aware matching in `GmailController` + supporting service/helpers.** - - Load `JobApplication` with `Company`. - - Build candidate Gmail queries from job/company/recruiter data. - - Merge/dedupe results by message id or thread id. - - Compute match reasons server-side. - - Keep existing `/import` and `/import-thread` behavior unless the new contract proves they need richer return payloads. - -3. **Only after the contract is stable, refactor `Correspondence.tsx`.** - - Replace `scoreMessage(...)` as the primary ranking engine with server-provided ranking/reasons. - - Pass job context from `JobDetailsDialog.tsx` rather than introducing another job fetch in the correspondence tab. - - Keep manual query override/search available as a fallback, not the primary UX. - -4. **Then extend persistence if needed for thread continuity.** - - Add correspondence metadata only if the chosen backend contract needs it for dedupe, import clarity, or future reply context. - - If added, update model, migration, and `Program.cs` compatibility shims together. - -5. **Finish with tests.** - - Backend tests for matching/import behavior first. - - Frontend tests for the new Gmail suggestion UI second. - -### Verification Approach - -- Backend unit/integration tests: - - `dotnet test JobTrackerApi.Tests` - - Add tests for: job-aware candidate endpoint contract, dedupe behavior for already-imported messages, ownership-scoped job lookup, and thread import summary results. -- Frontend tests: - - `npm test -- --watch=false` from `job-tracker-ui` - - Add React tests covering: Gmail tab rendering ranked suggestions, showing match reasons, import button states, and fallback/manual search behavior. -- Contract/manual verification: - - Open a job in `JobDetailsDialog` → Correspondence tab. - - Confirm Gmail connection state still works. - - Confirm the Gmail tab now shows job-relevant suggestions before freeform searching. - - Import a single message and a full thread; verify the job timeline/correspondence list updates and duplicates are skipped. -- If persistence changes land: - - Verify schema startup still succeeds on existing dev DBs because `JobTrackerApi/Program.cs` legacy `EnsureColumn(...)` blocks are easy to miss. - -## Constraints - -- `Data/JobTrackerContext.cs` applies ownership query filters to `Company`, `JobApplication`, and `GmailConnection`. Any new Gmail matching endpoint must keep loading through EF-scoped entities, not raw unfiltered ids. -- `JobTrackerApi/Services/GmailOAuthService.cs` currently exposes only message list/detail methods. There is no reusable thread-fetch or search-aggregation abstraction yet. -- `JobTrackerApi/Program.cs` contains manual schema repair code for SQLite/MySQL. Adding correspondence metadata requires updating both EF migration artifacts and these runtime compatibility paths. -- `Models/Correspondence.cs` does not currently preserve Gmail thread identity or raw sender/recipient fields, which limits downstream thread-aware UX. - -## Common Pitfalls - -- **Leaving ranking in the browser** — `Correspondence.tsx` can polish server results, but if the primary intelligence stays in `scoreMessage(...)`, S01 will remain query-driven and fragile. -- **Adding columns without updating `Program.cs`** — this repo relies on startup-time `EnsureColumn(...)` logic for legacy/dev databases; migration-only changes are incomplete here. -- **Duplicating job fetches in the dialog tree** — `JobDetailsDialog.tsx` already owns the job record. Per the `react-best-practices` guidance (`async-parallel`, `client-event-listeners`), keep async fetching consolidated and avoid adding more dialog-level waterfalls or duplicated global listeners. -- **Treating thread import as enough without thread metadata** — importing all messages in a thread helps today, but without persisting thread identity the app still cannot reason clearly about thread continuity later. - -## Open Risks - -- Gmail search quality may still be noisy even after better query construction; the planner should expect one iteration on ranking heuristics once real data is exercised. -- `ListMessagesAsync` is sequential and could become noticeably slow if the new matching flow issues multiple Gmail searches per job. If that happens, batching/parallelization inside the Gmail service becomes part of S01, not a later optimization. - -## Skills Discovered - -| Technology | Skill | Status | -|------------|-------|--------| -| React | `react-best-practices` | available | -| ASP.NET Core | `openai/skills@aspnet-core` | installed | diff --git a/.gsd/milestones/M001/slices/S01/S01-SUMMARY.md b/.gsd/milestones/M001/slices/S01/S01-SUMMARY.md deleted file mode 100644 index 303df06..0000000 --- a/.gsd/milestones/M001/slices/S01/S01-SUMMARY.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -id: S01 -parent: M001 -milestone: M001 -provides: - - Job-scoped Gmail matching and import now run from the job workspace with backend-owned ranking reasons, duplicate-aware import contracts, persisted Gmail thread metadata, and linked-thread refresh that imports later replies into the same job. -affects: - - S02 - - S03 - - S04 - - S05 -key_files: - - JobTrackerApi/Controllers/GmailController.cs - - JobTrackerApi/Services/GmailOAuthService.cs - - JobTrackerApi.Tests/GmailControllerTests.cs - - Models/Correspondence.cs - - JobTrackerApi/Controllers/CorrespondenceController.cs - - job-tracker-ui/src/components/Correspondence.tsx - - job-tracker-ui/src/components/JobDetailsDialog.tsx - - job-tracker-ui/src/types.ts - - job-tracker-ui/src/correspondence-gmail-import.test.tsx -key_decisions: - - Keep Gmail continuity bounded to known `ExternalThreadId` values for one job via `POST /api/gmail/refresh-linked-threads` instead of inbox-wide Gmail watch/history infrastructure. - - Treat the backend as the source of truth for Gmail candidate ranking, duplicate visibility, and linked-thread refresh counts so the workspace UI stays explanatory without re-implementing Gmail heuristics in React. -patterns_established: - - Gmail-derived correspondence is first-class job history: imported rows persist external message/thread ids plus sender/recipient labels and can be rendered directly in the timeline/workspace. - - Linked-thread continuity is pull-based and duplicate-safe: refresh reads already-linked thread ids for one owned job, skips known external message ids, and imports only new Gmail replies into that same job. - - The workspace distinguishes ranked import suggestions from already-linked live threads, with automatic one-shot refresh per loaded job/thread-set and an explicit manual refresh action. -observability_surfaces: - - GET /api/gmail/status - - GET /api/gmail/job-candidates - - POST /api/gmail/import - - POST /api/gmail/import-thread - - POST /api/gmail/refresh-linked-threads - - persisted Correspondence.ExternalMessageId / ExternalThreadId / ExternalFrom / ExternalTo - - JobTrackerApi.Tests/GmailControllerTests.cs - - job-tracker-ui/src/correspondence-gmail-import.test.tsx -drill_down_paths: - - .gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md - - .gsd/milestones/M001/slices/S01/tasks/T02-SUMMARY.md - - .gsd/milestones/M001/slices/S01/tasks/T03-SUMMARY.md -duration: ~1 slice closure session + prior executor task work -verification_result: passed -completed_at: 2026-03-24T11:54:52+01:00 ---- - -# S01: Smarter Gmail import and matching - -## Outcome - -S01 now delivers a job-aware Gmail import loop that is materially closer to the milestone trust bar than the pre-slice state. The workspace can: - -- connect Gmail and load ranked candidate messages/threads for one owned job -- explain why each Gmail candidate matched via score/confidence/match reasons -- import either a single message or an entire thread with duplicate-safe result counts -- persist Gmail thread identity plus raw sender/recipient labels on correspondence rows -- refresh already-linked Gmail threads for that same job and import only later unseen replies -- surface that linked-thread state back in the workspace without making the user manually re-import the thread - -The biggest slice change is that Gmail import is no longer just “pick a message and save a snapshot.” It is now a bounded continuity loop around stored `ExternalThreadId` values. - -## What actually shipped - -### Backend contract - -`JobTrackerApi/Controllers/GmailController.cs` and `JobTrackerApi/Services/GmailOAuthService.cs` now cover three distinct Gmail workspace behaviors: - -1. **Job-aware candidate ranking** via `GET /api/gmail/job-candidates` - - backend aggregates Gmail hits per job query - - duplicate Gmail hits are merged server-side - - response includes weighted match reasons, matched queries, imported flags, confidence, and thread/message counts - -2. **Duplicate-safe import** via `POST /api/gmail/import` and `POST /api/gmail/import-thread` - - single-message imports return `Imported`/`Skipped` plus the imported or existing correspondence row - - thread imports report imported/skipped counts instead of silently duplicating rows - - imported correspondence persists `ExternalMessageId`, `ExternalThreadId`, `ExternalFrom`, and `ExternalTo` - -3. **Linked-thread continuity** via `POST /api/gmail/refresh-linked-threads` - - reads the owned job’s existing linked Gmail thread ids - - fetches those threads from Gmail - - skips already-known external message ids - - imports only new messages into the same job - - returns refresh counts per job and per thread - - distinguishes invalid job, disconnected Gmail, empty linked-thread set, and already-current outcomes - -### Persistence and compatibility - -`Models/Correspondence.cs`, `JobTrackerApi/Controllers/CorrespondenceController.cs`, and `JobTrackerApi/Program.cs` now treat Gmail metadata as part of the durable correspondence model, including compatibility guards for the extra columns. - -### Workspace UI - -`job-tracker-ui/src/components/Correspondence.tsx` now makes the job workspace the real Gmail import surface instead of a generic search-first flow. - -It now: - -- loads backend-ranked Gmail candidates for the current job -- shows confidence/score/match reasons/already-linked status -- preserves manual query override through the same endpoint -- renders persisted Gmail thread/from/to metadata in the correspondence list -- automatically refreshes linked Gmail threads once per loaded job/thread-set -- exposes a manual “Refresh linked threads” action for another bounded pull in the same session -- updates the workspace after message import, thread import, and linked-thread refresh - -`job-tracker-ui/src/correspondence-gmail-import.test.tsx` now covers both the ranked import path and the no-manual-reimport continuity path. - -## Verification run in this closure session - -### Automated checks - -| Command | Result | -|---|---| -| `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` | ✅ passed | -| `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` | ✅ passed | -| `dotnet build JobTrackerApi/JobTrackerApi.csproj` | ✅ passed | - -The backend verification command originally described in project knowledge was blocked by unrelated test-project drift earlier in the milestone, but that drift was fixed during this closure pass, and the exact filtered command now passes. - -### Observability confirmed - -The slice’s durable inspection surfaces are now coherent: - -- `GET /api/gmail/status` exposes Gmail connection freshness (`lastSyncedAt`) -- `GET /api/gmail/job-candidates` exposes job-scoped ranking details and duplicate visibility -- `POST /api/gmail/refresh-linked-threads` exposes refresh counts and per-thread status -- persisted correspondence rows carry external Gmail thread/message/from/to metadata -- focused backend/frontend tests encode the intended behavior for future refactors - -### Human / live Gmail UAT status - -This auto-mode session did **not** have a live Gmail account wired for a real-account browser pass, so the required human/live UAT was prepared as a concrete script in `S01-UAT.md` rather than executed here. The implementation and automated slice gates passed; the live-account trust check remains a human runbook item. - -## Requirement impact - -- **R002** moved to **validated** based on the shipped linked-thread refresh contract plus focused backend/frontend verification. -- **R010** remains active, but S01 materially advances it by making Gmail correspondence continuity part of the same job history instead of a one-off import snapshot. - -## Key downstream implications - -### For S02 - -Imported Gmail correspondence is now trustworthy enough to use as drafting context. Downstream draft generation should consume job-linked correspondence rows directly, including sender/recipient/thread metadata when useful for tone or recipient context. - -### For S03 - -Reply/follow-up drafting can now assume two important invariants: - -- Gmail correspondence is attached to a specific job -- later Gmail replies can be pulled into that same job without user re-import - -That means reply/follow-up context assembly should build from the persisted job correspondence set, not from transient Gmail candidate data. - -### For S04/S05 - -Daily-loop surfaces can now rely on a clearer distinction between: - -- jobs that only have candidate Gmail matches -- jobs with already-linked live Gmail threads -- jobs whose linked threads were refreshed and imported new correspondence - -Those are useful action/readiness signals for dashboards, follow-up surfaces, and final end-to-end trust checks. - -## Notable lessons / non-obvious details - -- The bounded sync model is deliberate: refresh is over known linked thread ids for one job, not inbox-wide Gmail watch/history state. -- The React workspace auto-refresh is intentionally one-shot per `jobId + linked thread set`; repeated pulls in the same session require the explicit refresh action. -- The filtered Gmail backend test command compiles the whole `JobTrackerApi.Tests` project before filtering, so unrelated test drift can still block slice verification if future work breaks test signatures again. - -## Slice verdict - -S01 now establishes the milestone’s Gmail foundation: smarter matching, clearer import trust signals, persisted Gmail metadata, and real linked-thread continuity in the job workspace. diff --git a/.gsd/milestones/M001/slices/S01/S01-UAT.md b/.gsd/milestones/M001/slices/S01/S01-UAT.md deleted file mode 100644 index e7ec2e6..0000000 --- a/.gsd/milestones/M001/slices/S01/S01-UAT.md +++ /dev/null @@ -1,183 +0,0 @@ -# S01 UAT: Smarter Gmail import and matching - -## Scope - -Validate that one real job workspace can: - -- connect Gmail -- show ranked Gmail message/thread suggestions for that job -- import a single message or full thread into the job -- preserve Gmail thread/sender/recipient metadata in correspondence -- refresh already-linked Gmail threads and surface later inbound or user-sent replies without manual re-import - -## Preconditions - -1. Run the app with a build that includes S01 changes. -2. Sign in as a normal local user who owns at least one job application record. -3. Choose one job with a realistic company name, recruiter email, or recruiter name so Gmail matching has meaningful context. -4. Use a Gmail account that contains at least one relevant thread for that job. -5. For the continuity test, ensure that thread can receive a new inbound reply or that you can send a reply from your Gmail account during the test. -6. Start with no browser extensions that block Google OAuth popups. - ---- - -## Test Case 1 — Connect Gmail from the job workspace - -**Goal:** Confirm Gmail connection starts from the job correspondence workspace and returns visible connection state. - -### Steps - -1. Open the chosen job in the job workspace/dialog. -2. Go to the **Correspondence** area. -3. Click **Import email**. -4. Open the **Google** tab. -5. If Gmail is not connected, click **Connect Gmail**. -6. Complete the Google OAuth flow in the popup. -7. Return to the job workspace. - -### Expected results - -- The popup closes or reports success. -- The Gmail section shows the connected Gmail address. -- The Gmail tab becomes usable without a page reload. -- A `Last synced` timestamp or connected-state indicator is visible. - ---- - -## Test Case 2 — Ranked Gmail suggestions are job-aware and explanatory - -**Goal:** Confirm the workspace suggests likely Gmail threads/messages for the current job and explains why. - -### Steps - -1. Stay in the same job’s **Correspondence → Google** tab. -2. Wait for candidate Gmail suggestions to load automatically. -3. Review the first 3 suggested threads/messages. -4. Use the manual search box with a more specific override such as the recruiter email or a subject fragment. -5. Click **Search**. - -### Expected results - -- Suggestions are scoped to the current job rather than a generic inbox list. -- Each suggested thread/message shows visible ranking context such as confidence, score, and match reasons. -- Already-linked content, if any, is marked as already linked/imported rather than presented as a fresh import with no explanation. -- Manual search override changes the candidate list without breaking the job-aware context. - -### Edge checks - -- If the job has no good recruiter/company data, the UI should still show either fallback queries or a clear empty state rather than a broken panel. -- If no Gmail matches exist, the UI should clearly say there are no matches yet for this job/search override. - ---- - -## Test Case 3 — Import a single Gmail message into the job - -**Goal:** Confirm single-message import attaches Gmail correspondence to the correct job and preserves metadata. - -### Steps - -1. In the Google tab, locate a suggested Gmail message that belongs to the chosen job. -2. Click **Import email** for that message. -3. Return focus to the correspondence list. -4. Inspect the newly imported correspondence row. -5. Without changing jobs, try importing the exact same Gmail message again if it is still visible. - -### Expected results - -- The imported message appears in the chosen job’s correspondence history immediately. -- The correspondence row shows the imported subject/body content. -- Gmail metadata is visible on that row, including thread id and sender/recipient labels when available. -- Re-importing the same message does **not** create a duplicate correspondence entry. -- The UI reports that the message was already linked or skipped on the second attempt. - ---- - -## Test Case 4 — Import an entire Gmail thread into the job - -**Goal:** Confirm thread import brings multiple related Gmail messages into the same job and stays duplicate-safe on repeat. - -### Steps - -1. In the Google tab, choose a suggested thread with at least 2 messages. -2. Click **Import thread**. -3. Inspect the correspondence list after import. -4. Count how many entries from that thread now appear on the job. -5. Trigger **Import thread** again for the same thread if it remains listed. - -### Expected results - -- Multiple correspondence entries from the selected Gmail thread are attached to the same job. -- Each imported entry preserves Gmail metadata (`Thread`, `From`, `To`) where present. -- The import result reports imported/skipped counts. -- Repeating the thread import does not duplicate messages already attached to the job. - -### Edge checks - -- If some messages from the thread were already imported before the thread import, only the missing messages should be added. -- If the thread is fully imported already, the result should show skipped-only behavior rather than silently doing nothing. - ---- - -## Test Case 5 — Automatic linked-thread refresh imports a later reply without manual re-import - -**Goal:** Validate the core slice promise: once a Gmail thread is linked to a job, later replies appear on that job through refresh rather than a fresh import action. - -### Steps - -1. Start from a job that already has at least one imported Gmail message/thread with a visible linked thread id. -2. In Gmail, send a new reply on that same thread **or** wait for a real inbound reply from the recruiter. -3. Return to the job workspace. -4. Reopen the job if needed, but do **not** use **Import email** or **Import thread** for the new reply. -5. Wait for the workspace to settle. -6. If the new reply does not appear automatically after the initial load, click **Refresh linked threads** once. -7. Inspect the correspondence list. - -### Expected results - -- The workspace recognizes that the job has linked Gmail threads. -- The new reply appears under the same job without the user manually selecting the thread again from Gmail candidates. -- The new reply preserves thread continuity and sender/recipient metadata. -- The Gmail area shows a refresh summary such as imported count, linked thread count, or already-current state. -- Running refresh again immediately after a successful import should not create duplicates. - -### Edge checks - -- Test both directions if possible: - - recruiter → user inbound reply - - user → recruiter sent reply from Gmail -- If there are no new messages, refresh should report that linked threads are already current rather than pretending new content arrived. - ---- - -## Test Case 6 — Failure visibility is explicit - -**Goal:** Confirm the slice does not fail as a silent no-op. - -### Steps - -1. Disconnect Gmail from the workspace. -2. Reopen the Gmail tab for the same job. -3. Attempt to use the Gmail area again. -4. Reconnect Gmail. -5. Open a different job with no imported Gmail thread ids. -6. Check whether linked-thread refresh controls and status remain understandable. - -### Expected results - -- Disconnected Gmail state is explicit and actionable. -- The workspace does not pretend linked-thread refresh succeeded while disconnected. -- A job with no linked Gmail threads shows a clear “no linked threads yet” state rather than a hidden failure. -- Invalid/empty states remain distinct from successful refreshes. - ---- - -## Acceptance summary - -S01 passes UAT when all of the following are true for at least one real job: - -- Gmail can be connected from the correspondence workspace. -- The workspace shows sensible ranked Gmail suggestions for that job. -- Single-message and full-thread imports attach correspondence to that job and stay duplicate-safe. -- Imported correspondence visibly preserves Gmail metadata. -- A later reply on an already-linked Gmail thread appears on the job through the linked-thread refresh path without requiring the user to manually re-import the thread. -- Empty/disconnected/already-current states are visible and understandable. diff --git a/.gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md b/.gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md deleted file mode 100644 index 243bd27..0000000 --- a/.gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -estimated_steps: 4 -estimated_files: 4 -skills_used: - - aspnet-core - - test ---- - -# T01: Add linked Gmail thread refresh to the backend contract - -**Slice:** S01 — Smarter Gmail import and matching -**Milestone:** M001 - -## Description - -Finish the backend half of S01 by making already-imported Gmail threads refreshable for a single job. The executor should build on the existing job-aware matching/import flow already present in `GmailController`, keep the scope bounded to known linked thread ids, and return a clear refresh result that distinguishes new imports from duplicate-only refreshes and disconnected/failure states. - -## Steps - -1. Inspect the current Gmail import code in `JobTrackerApi/Controllers/GmailController.cs`, the Gmail API wrapper in `JobTrackerApi/Services/GmailOAuthService.cs`, and the current `GmailControllerTests` coverage to identify the smallest thread-refresh contract that can satisfy R002. -2. Add a job-scoped linked-thread refresh path in `JobTrackerApi/Controllers/GmailController.cs` that loads one owned job, gathers distinct linked `ExternalThreadId` values from its correspondence, fetches Gmail messages for those known threads, and imports only unseen `ExternalMessageId` values into the same job. -3. Extend `JobTrackerApi/Services/GmailOAuthService.cs` with the thread-level retrieval helper(s) needed by that controller path, and keep diagnostics bounded to counts, ids, timestamps, and connection state rather than full message bodies. -4. Expand `JobTrackerApi.Tests/GmailControllerTests.cs` to cover successful refresh with a new inbound reply, successful refresh with a new user-sent reply, duplicate-only refresh, disconnected Gmail state, and invalid/inaccessible job handling. - -## Must-Haves - -- [ ] The refresh path operates on already-linked Gmail thread ids for one owned job; it does not introduce inbox-wide watch/history infrastructure for this slice. -- [ ] New Gmail replies import into the same `JobApplication` with existing duplicate protection based on `ExternalMessageId`. -- [ ] The refresh contract exposes enough status to tell whether the run imported new messages, found only duplicates, or could not run because Gmail/job state was invalid. - -## Verification - -- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` -- Confirm the updated tests assert at least one new-message refresh, one duplicate-only refresh, and one failure-path outcome. - -## Observability Impact - -- Signals added/changed: linked-thread refresh result counts, last refresh/sync timestamp updates, and explicit disconnected/invalid-job outcomes. -- How a future agent inspects this: read the refresh DTO and controller action in `JobTrackerApi/Controllers/GmailController.cs` and the focused assertions in `JobTrackerApi.Tests/GmailControllerTests.cs`. -- Failure state exposed: the API should make it obvious whether nothing happened because there were no linked threads, Gmail was disconnected, every message was already imported, or the job was not accessible. - -## Inputs - -- `JobTrackerApi/Controllers/GmailController.cs` — current job-aware Gmail matching and import endpoints. -- `JobTrackerApi/Services/GmailOAuthService.cs` — current Gmail list/detail helpers and connection status updates. -- `JobTrackerApi.Tests/GmailControllerTests.cs` — existing Gmail controller test coverage. -- `JobTrackerApi/Program.cs` — existing Gmail/correspondence runtime wiring and compatibility guards. - -## Expected Output - -- `JobTrackerApi/Controllers/GmailController.cs` — adds the linked-thread refresh contract for one job. -- `JobTrackerApi/Services/GmailOAuthService.cs` — adds thread-fetch support used by refresh. -- `JobTrackerApi.Tests/GmailControllerTests.cs` — proves refresh success, duplicate-only, and failure-path behavior. -- `JobTrackerApi/Program.cs` — updates any wiring needed for the new refresh flow or diagnostics. diff --git a/.gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md b/.gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md deleted file mode 100644 index ec77142..0000000 --- a/.gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T01 -parent: S01 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.777Z -blocker_discovered: false ---- - -# T01: Add linked Gmail thread refresh to the backend contract - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S01/tasks/T01-VERIFY.json b/.gsd/milestones/M001/slices/S01/tasks/T01-VERIFY.json deleted file mode 100644 index 6191569..0000000 --- a/.gsd/milestones/M001/slices/S01/tasks/T01-VERIFY.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "schemaVersion": 1, - "taskId": "T01", - "unitId": "M001/S01/T01", - "timestamp": 1774350445753, - "passed": true, - "discoverySource": "none", - "checks": [] -} diff --git a/.gsd/milestones/M001/slices/S01/tasks/T02-PLAN.md b/.gsd/milestones/M001/slices/S01/tasks/T02-PLAN.md deleted file mode 100644 index 18282c2..0000000 --- a/.gsd/milestones/M001/slices/S01/tasks/T02-PLAN.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -estimated_steps: 4 -estimated_files: 4 -skills_used: - - react-best-practices - - test ---- - -# T02: Surface live Gmail thread continuity in the job workspace - -**Slice:** S01 — Smarter Gmail import and matching -**Milestone:** M001 - -## Description - -Complete S01 in the UI by turning the existing ranked Gmail import tab into a live linked-thread workspace. The executor should preserve the current ranked suggestion/import flow, then layer in automatic refresh for already-linked Gmail threads so the user sees new replies appear on the same job without using the import action again. - -## Steps - -1. Inspect the current Gmail UI in `job-tracker-ui/src/components/Correspondence.tsx`, the host wiring in `job-tracker-ui/src/components/JobDetailsDialog.tsx`, and the existing component test in `job-tracker-ui/src/correspondence-gmail-import.test.tsx`. -2. Update `job-tracker-ui/src/types.ts` and `job-tracker-ui/src/components/Correspondence.tsx` to consume the new backend linked-thread refresh contract and track refresh/loading/freshness state separately from the ranked import-suggestion state. -3. Wire `job-tracker-ui/src/components/Correspondence.tsx` so already-linked threads refresh automatically at the right moment in the job workspace flow, refresh the rendered correspondence list after sync, and make the linked/live state legible in the UI. -4. Extend `job-tracker-ui/src/correspondence-gmail-import.test.tsx` to prove the continuity path: after a thread is already linked, a refresh brings in a later Gmail reply and renders it on the same job without another import action. - -## Must-Haves - -- [ ] The UI keeps ranked job-aware Gmail suggestions for first import while clearly distinguishing already-linked live threads from new import candidates. -- [ ] Linked-thread refresh happens through the new backend contract and updates the visible correspondence list without requiring the user to click an import button again. -- [ ] The React test proves the continuity path, not just the original ranked-import path. - -## Verification - -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` -- Manually inspect the Correspondence dialog to confirm linked-thread state, refresh/loading state, and the newly synced message all appear in the workspace. - -## Observability Impact - -- Signals added/changed: visible linked/live thread state, refresh progress/freshness state, and clearer no-new-messages vs failure feedback. -- How a future agent inspects this: run `job-tracker-ui/src/correspondence-gmail-import.test.tsx` and inspect the Gmail area in `job-tracker-ui/src/components/Correspondence.tsx`. -- Failure state exposed: the UI should distinguish refresh failure, disconnected Gmail, no linked threads, and successful refresh with zero new messages. - -## Inputs - -- `job-tracker-ui/src/components/Correspondence.tsx` — current ranked Gmail import UI. -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — host dialog that owns the job workspace. -- `job-tracker-ui/src/types.ts` — frontend Gmail and correspondence contracts. -- `job-tracker-ui/src/correspondence-gmail-import.test.tsx` — existing Gmail import component test. -- `JobTrackerApi/Controllers/GmailController.cs` — T01 backend refresh contract consumed by the UI. - -## Expected Output - -- `job-tracker-ui/src/components/Correspondence.tsx` — renders linked-thread refresh state and continuity behavior. -- `job-tracker-ui/src/types.ts` — matches the backend refresh contract. -- `job-tracker-ui/src/correspondence-gmail-import.test.tsx` — proves the no-manual-reimport continuity path. -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — includes any required wiring for the refreshed workspace flow. diff --git a/.gsd/milestones/M001/slices/S01/tasks/T02-SUMMARY.md b/.gsd/milestones/M001/slices/S01/tasks/T02-SUMMARY.md deleted file mode 100644 index 87c0569..0000000 --- a/.gsd/milestones/M001/slices/S01/tasks/T02-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T02 -parent: S01 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.777Z -blocker_discovered: false ---- - -# T02: Surface live Gmail thread continuity in the job workspace - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S01/tasks/T03-PLAN.md b/.gsd/milestones/M001/slices/S01/tasks/T03-PLAN.md deleted file mode 100644 index 5ab0539..0000000 --- a/.gsd/milestones/M001/slices/S01/tasks/T03-PLAN.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -estimated_steps: 4 -estimated_files: 4 -skills_used: - - react-best-practices - - test ---- - -# T03: Wire ranked Gmail suggestions into the job workspace UI - -**Slice:** S01 — Smarter Gmail import and matching -**Milestone:** M001 - -## Description - -Deliver the user-facing part of the slice in the actual job workspace. The Correspondence tab should stop acting like a generic Gmail search box and instead open with job-aware ranked suggestions from the backend, explain why each candidate is relevant, let the user override with manual search when needed, and refresh the job-linked correspondence view after import. - -## Steps - -1. Update `job-tracker-ui/src/types.ts` to reflect the new backend candidate contract and enriched correspondence metadata from T01 and T02. -2. Pass job context from `job-tracker-ui/src/components/JobDetailsDialog.tsx` into `job-tracker-ui/src/components/Correspondence.tsx` so the Gmail tab can request job-aware suggestions without duplicating the job fetch. -3. Refactor `job-tracker-ui/src/components/Correspondence.tsx` to consume the backend-ranked suggestions, show thread/message match reasons and import state, keep manual Gmail query override/search available, and refresh the rendered correspondence list after successful imports. -4. Add `job-tracker-ui/src/correspondence-gmail-import.test.tsx` covering ranked suggestion rendering, reason/confidence display, thread vs single-message import actions, and refresh after import. - -## Must-Haves - -- [ ] The Gmail tab opens on job-aware ranked suggestions instead of using `scoreMessage(...)` as the primary intelligence. -- [ ] The UI still supports manual Gmail searching as a fallback override, but it no longer depends on freeform query heuristics for the core experience. -- [ ] The React test proves the user can see ranked suggestions and that importing updates the same job’s correspondence surface. - -## Verification - -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` -- Manually inspect the dialog to confirm ranked reasons/import state are visible and the correspondence list refreshes after import. - -## Observability Impact - -- Signals added/changed: visible match reasons/confidence/import state in the Gmail tab and clearer import success/duplicate feedback toasts. -- How a future agent inspects this: read `job-tracker-ui/src/correspondence-gmail-import.test.tsx` and open the Correspondence dialog in the running app. -- Failure state exposed: the UI should distinguish no matches, already-imported candidates, loading states, and import failures instead of collapsing them into a generic empty list. - -## Inputs - -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — host dialog that already owns the job record. -- `job-tracker-ui/src/components/Correspondence.tsx` — current Gmail import UI with client-side ranking. -- `job-tracker-ui/src/types.ts` — frontend API contracts. -- `JobTrackerApi/Controllers/GmailController.cs` — T01/T02 backend Gmail candidate and import contract. - -## Expected Output - -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — passes job context into the correspondence tab. -- `job-tracker-ui/src/components/Correspondence.tsx` — renders job-aware Gmail suggestions and refreshed import behavior. -- `job-tracker-ui/src/types.ts` — matches the updated backend contract. -- `job-tracker-ui/src/correspondence-gmail-import.test.tsx` — proves the UI flow end to end at component level. diff --git a/.gsd/milestones/M001/slices/S01/tasks/T03-SUMMARY.md b/.gsd/milestones/M001/slices/S01/tasks/T03-SUMMARY.md deleted file mode 100644 index ddf89bd..0000000 --- a/.gsd/milestones/M001/slices/S01/tasks/T03-SUMMARY.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: T03 summary -status: done -files: - - job-tracker-ui/src/components/JobDetailsDialog.tsx - - job-tracker-ui/src/components/Correspondence.tsx - - job-tracker-ui/src/types.ts - - job-tracker-ui/src/correspondence-gmail-import.test.tsx -observability_surfaces: - - job-tracker-ui/src/components/Correspondence.tsx Gmail tab linked-thread state - - /gmail/job-candidates requests with queryOverride - - /gmail/refresh-linked-threads workspace refresh requests - - job-tracker-ui/src/correspondence-gmail-import.test.tsx -verification: - - npm ci (job-tracker-ui) - - CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx ---- - -Wired the job-aware Gmail matching contract into the actual job workspace UI and completed the live linked-thread refresh loop. - -## What changed - -- `job-tracker-ui/src/types.ts` - - added frontend contracts for job-aware Gmail matches, linked-thread refresh results, import results, and enriched correspondence metadata -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` - - passes the loaded `job` into `Correspondence` so the Gmail tab can stay job-aware without another job fetch -- `job-tracker-ui/src/components/Correspondence.tsx` - - replaced client-side Gmail ranking as the primary workflow - - Gmail tab now calls `/gmail/job-candidates` - - shows confidence, score, match reasons, and already-linked state - - preserves manual query override via the same job-aware endpoint - - automatically calls `/gmail/refresh-linked-threads` when the job already has linked Gmail thread ids - - refreshes correspondence plus Gmail candidate state after single-message, thread, and linked-thread refresh actions - - renders persisted Gmail metadata (`ExternalThreadId`, `ExternalFrom`, `ExternalTo`) in the correspondence view - - shows linked-thread freshness/import summary inside the Gmail area -- `job-tracker-ui/src/correspondence-gmail-import.test.tsx` - - verifies ranked Gmail suggestions render with visible reasons/confidence - - verifies single-message import refreshes the same job’s correspondence view - - verifies automatic linked-thread refresh shows a later Gmail reply without manual re-import - - verifies manual search override is sent as `queryOverride` - -## Verification - -- frontend dependencies were installed with `npm ci` in `job-tracker-ui` -- focused React test passed: - - `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` - -## Verification Evidence - -| # | Command | Exit Code | Verdict | Duration | -|---|---------|-----------|---------|----------| -| 1 | `npm ci` (job-tracker-ui) | 0 | ✅ pass | not recorded | -| 2 | `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` | 0 | ✅ pass | ~2.8s | - -## Diagnostics - -- Open a job with imported Gmail correspondence and inspect the Gmail tab chips: linked-thread count, last refresh summary, and Gmail connection `lastSyncedAt` show whether the workspace considers the thread live. -- Watch network traffic for `GET /gmail/job-candidates` and `POST /gmail/refresh-linked-threads` to confirm the workspace distinguishes ranked suggestions from already-linked thread refresh. -- Inspect rendered correspondence chips (`Thread ...`, `From ...`, `To ...`) to verify imported Gmail metadata survived the round trip from persistence to UI. -- Read `job-tracker-ui/src/correspondence-gmail-import.test.tsx` for the durable automated proof of manual query override plus no-manual-reimport continuity. - -## Notes - -The Gmail tab now treats the backend as the source of truth for ranking while keeping the manual search field as a fallback override, and it refreshes known linked threads once per loaded job/thread-set automatically to avoid re-import loops. diff --git a/.gsd/milestones/M001/slices/S02/S02-PLAN.md b/.gsd/milestones/M001/slices/S02/S02-PLAN.md deleted file mode 100644 index 84a624b..0000000 --- a/.gsd/milestones/M001/slices/S02/S02-PLAN.md +++ /dev/null @@ -1,12 +0,0 @@ -# S02: Stronger AI application package drafting - -**Goal:** Make the application package generator use imported job/correspondence context well enough that tailored CV, cover-letter, and recruiter-message drafts feel specific, credible, and worth starting from inside the job workspace. -**Demo:** After this: TBD - -## Tasks -- [x] **T01: Strengthen application-package context assembly and backend draft tests** — - - Files: JobTrackerApi/Controllers/JobApplicationsController.cs, JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs - - Verify: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests` -- [x] **T02: Make the job workspace save and present the application package as real working material** — - - Files: job-tracker-ui/src/components/JobDetailsDialog.tsx, job-tracker-ui/src/types.ts, job-tracker-ui/src/job-details-generated-drafts.test.tsx - - Verify: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-generated-drafts.test.tsx` diff --git a/.gsd/milestones/M001/slices/S02/S02-SUMMARY.md b/.gsd/milestones/M001/slices/S02/S02-SUMMARY.md deleted file mode 100644 index 687bc44..0000000 --- a/.gsd/milestones/M001/slices/S02/S02-SUMMARY.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -id: S02 -parent: M001 -milestone: M001 -provides: - - stronger application-package generation that uses imported correspondence, recruiter/job context, profile CV structure, and attachment signals - - a persisted package workspace inside the job dialog for tailored CV, cover letter, recruiter message, and application-answer draft material -requires: - - slice: S01 - provides: imported and auto-refreshed job-linked correspondence plus trusted thread metadata for package context assembly -affects: - - S03 -key_files: - - JobTrackerApi/Controllers/JobApplicationsController.cs - - JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs - - job-tracker-ui/src/components/JobDetailsDialog.tsx - - job-tracker-ui/src/job-details-generated-drafts.test.tsx -key_decisions: - - D006: persist the application-answer draft as a replaceable marker-delimited notes block until a dedicated field exists -patterns_established: - - assemble AI package context from persisted job, recruiter, correspondence, saved-draft, profile-CV, and attachment signals before prompting - - treat generated artifacts as editable workspace state with explicit saved/generated/unsaved status rather than disposable preview output -observability_surfaces: - - POST /api/jobapplications/{id}/generate-application-package - - PUT /api/jobapplications/{id}/tailored-cv - - PUT /api/jobapplications/{id}/application-drafts - - job-tracker-ui/src/job-details-generated-drafts.test.tsx - - JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs -duration: 2 tasks -verification_result: passed -completed_at: 2026-03-24 ---- - -# S02: Stronger AI application package drafting - -**Imported Gmail/job context now feeds a persisted application-package workspace that generates, edits, saves, and reloads job-specific draft material instead of one-shot generic previews.** - -## What Happened - -S02 closed the main draft-quality gap by wiring S01’s imported correspondence into application-package generation and then making the Tailored CV tab behave like a real working surface. - -On the backend, `JobTrackerApi/Controllers/JobApplicationsController.cs` now builds package context from more than the job description. Generation explicitly pulls in recruiter identity, job URL, imported correspondence, saved package material already tied to the job, profile CV structure, and selected attachment signals. That context is reused across tailored CV, cover letter, application answer, recruiter message, and package key points so the returned artifacts can react to real recruiter/thread context instead of defaulting to generic role-summary language. - -On the frontend, `job-tracker-ui/src/components/JobDetailsDialog.tsx` now treats tailored CV, cover letter, recruiter message, and application-answer text as one package workspace. Reopening the job loads the saved copy back into the editors, generation replaces the current working copy for all package artifacts, save persists the package back to the job, and reset restores the last saved state. Status chips make the persistence state legible by distinguishing `Saved to job`, `Generated only`, and `Unsaved edits`. - -S02 also established a temporary but stable persistence contract for the application-answer draft: until a dedicated field exists, it lives in a marker-delimited block inside `JobApplication.Notes`, and save replaces that block rather than appending indefinitely. That keeps the workspace trustworthy and gives S03 a reliable place to read package context back from. - -The net effect is that S01 correspondence is no longer just imported and displayed; it now materially influences package drafting, and the resulting artifacts persist as reusable job workspace material. - -## Verification - -- `$HOME/.dotnet/dotnet build JobTrackerApi/JobTrackerApi.csproj` — passed -- `$HOME/.dotnet/dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests` — passed (2 tests) -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-generated-drafts.test.tsx` — passed (2 tests) -- Observability surfaces confirmed by implementation/tests: - - `POST /api/jobapplications/{id}/generate-application-package` now emits package artifacts grounded in correspondence/recruiter/job context - - `PUT /api/jobapplications/{id}/tailored-cv` and `PUT /api/jobapplications/{id}/application-drafts` form the durable save loop the workspace depends on - - focused backend/frontend tests cover package generation specificity, notes replacement, saved-state load, edit, save, and redisplay behavior - -## New Requirements Surfaced - -- none - -## Deviations - -- The plan did not call out storage for the application-answer draft, but execution required a concrete persistence strategy before the workspace could be trustworthy. S02 therefore adopted the marker-delimited notes-block approach captured in D006 instead of introducing a new schema field mid-slice. - -## Known Limitations - -- The application-answer draft is still stored inside `JobApplication.Notes` rather than a first-class field, so downstream work must keep honoring the marker-block contract. -- Automated verification proves context wiring and persistence loops, but it does not replace human judgment on whether the generated writing feels genuinely strong enough for a real application; that still needs live UAT with real imported correspondence and AI output. -- Draft quality still depends on the quality of the imported correspondence, recruiter metadata, profile CV structure, and selected attachments available on the job. - -## Follow-ups - -- S03 should consume the saved package workspace artifacts, including the marker-delimited application-answer block, instead of reconstructing package context from scratch. -- A later milestone can promote the application-answer draft to a dedicated persisted field if the notes-block workaround starts constraining editing, analytics, or downstream composition. -- Milestone-level live UAT should include at least one job with strong imported Gmail context to judge whether the upgraded generator actually feels specific enough to start from. - -## Files Created/Modified - -- `JobTrackerApi/Controllers/JobApplicationsController.cs` — strengthened package-context assembly, notes replacement behavior, and generation/save contracts -- `JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs` — focused backend proof for correspondence-aware package output and notes replacement -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — converted the Tailored CV tab into a coherent generate/edit/save/reset package workspace -- `job-tracker-ui/src/job-details-generated-drafts.test.tsx` — verified saved-state load, generation, editing, coherent save payload, and redisplay behavior -- `.gsd/REQUIREMENTS.md` — refreshed R003 validation to reflect the direct filtered backend test plus frontend workspace proof -- `.gsd/KNOWLEDGE.md` — recorded the marker-delimited application-answer persistence contract and the authoritative direct S02 test command - -## Forward Intelligence - -### What the next slice should know -- S03 can now assume package context lives in durable job fields: `tailoredCvText`, `coverLetterText`, `recruiterMessageDraft`, and the marker-delimited application-answer block inside `notes`; use those saved values as the baseline context for reply/follow-up generation. - -### What's fragile -- Application-answer persistence via the `<<>> ... <<>>` notes block — downstream code must replace/parse that block consistently or the workspace will drift back into duplicate or stale-answer behavior. - -### Authoritative diagnostics -- `JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs` and `job-tracker-ui/src/job-details-generated-drafts.test.tsx` — these are the tightest trustworthy checks for whether package generation is context-aware and whether the workspace save/reload loop still works end to end. - -### What assumptions changed -- Earlier task execution assumed the filtered backend verification might still need an isolated harness because of broader test-project drift — in this worktree, `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests` now passes directly and should be treated as the primary S02 regression check. diff --git a/.gsd/milestones/M001/slices/S02/S02-UAT.md b/.gsd/milestones/M001/slices/S02/S02-UAT.md deleted file mode 100644 index 1012fb6..0000000 --- a/.gsd/milestones/M001/slices/S02/S02-UAT.md +++ /dev/null @@ -1,91 +0,0 @@ -# S02: Stronger AI application package drafting — UAT - -**Milestone:** M001 -**Written:** 2026-03-24 - -## UAT Type - -- UAT mode: mixed -- Why this mode is sufficient: this slice needs both artifact proof (generation/save contracts and persisted reload behavior) and human judgment that the drafts actually feel specific enough to start from. - -## Preconditions - -- The app frontend and API are running against this worktree. -- The tester can sign in as a user with a parsed profile CV already available. -- At least one job exists with: - - company + recruiter information - - imported Gmail correspondence from S01 linked to the job - - optional but preferred AI-selected attachments for extra context -- The local AI summarizer/service used by `generate-application-package` is configured and reachable. -- The tester knows which job has the strongest imported recruiter/thread context so draft specificity is easy to judge. - -## Smoke Test - -Open a job with imported correspondence, switch to the Tailored CV tab, click **Generate package**, and confirm all four editable artifacts populate without errors: tailored CV, cover letter, recruiter message, and application answer. - -## Test Cases - -### 1. Generate a package that reflects imported job and correspondence context - -1. Open a job that already has imported Gmail correspondence in the job workspace. -2. In the Tailored CV tab, confirm at least one attachment is selected for AI context if relevant. -3. Click **Generate package**. -4. Wait for generation to finish. -5. Review the tailored CV, cover letter, recruiter message, and application answer together. -6. **Expected:** the generated package mentions concrete job/company/recruiter details and reflects correspondence-specific context or tone rather than reading like a generic template. - -### 2. Save edited package material as durable job workspace output - -1. Starting from a generated package, edit all or some of these fields: tailored CV, cover letter, recruiter message, application answer. -2. Confirm the status chips change to show **Unsaved edits** for the edited artifacts. -3. Click **Save** for the package workspace. -4. Wait for the save to complete. -5. **Expected:** save succeeds without duplication, the edited values remain visible, and the status chips move to **Saved to job**. - -### 3. Reopen the job and verify the last saved package reloads - -1. Close the job details dialog after saving. -2. Reopen the same job. -3. Return to the Tailored CV tab. -4. **Expected:** the saved tailored CV, cover letter, recruiter message, and application answer reload as the current workspace state; nothing falls back to blank or a previous generated-only draft. - -### 4. Regenerate after saving and use reset-to-saved safely - -1. With a saved package already present, click **Generate package** again. -2. Confirm the editors are replaced with the new generated working copy. -3. Make one more manual edit so the status changes to **Unsaved edits**. -4. Click **Reset to saved**. -5. **Expected:** the unsaved regeneration/edit is discarded and the workspace returns exactly to the last saved job-tied material. - -## Edge Cases - -### Saved application answer does not duplicate on repeated saves - -1. Save a package with a distinct application-answer draft. -2. Edit only the application answer and save again. -3. Close and reopen the job. -4. **Expected:** only the latest application-answer draft is present; earlier answers are replaced, not appended repeatedly into the notes-backed storage. - -### Empty saved package still distinguishes generated-only state - -1. Open a job with no previously saved package material. -2. Generate the package but do not click Save. -3. **Expected:** generated text appears, but the relevant status chips show **Generated only** rather than **Saved to job**. - -## Failure Signals - -- Generate package returns an error or leaves one or more of the four artifacts empty despite available context. -- Drafts ignore obvious recruiter/company/correspondence details and read like generic boilerplate. -- Saving succeeds visually but reopening the job loses edits or restores stale values. -- The application answer repeats previous saved copies instead of replacing the prior draft. -- Status chips do not match reality, for example showing **Saved to job** before any save or failing to show **Unsaved edits** after changes. - -## Not Proven By This UAT - -- This UAT does not prove Gmail import or linked-thread refresh; that belongs to S01. -- This UAT does not prove reply/follow-up draft generation from saved package context; that belongs to S03. -- This UAT does not prove final end-to-end milestone quality across dashboard/table/job-loop navigation; later slices and final milestone UAT must cover that. - -## Notes for Tester - -Use the job with the richest imported recruiter/thread context first; this slice is about whether that context materially improves draft usefulness. If draft quality feels only marginally better, note which missing signals (recruiter identity, thread details, attachment evidence, profile CV structure) seem absent so S03/S05 can inspect the package-context assembly path. diff --git a/.gsd/milestones/M001/slices/S02/tasks/T01-PLAN.md b/.gsd/milestones/M001/slices/S02/tasks/T01-PLAN.md deleted file mode 100644 index d9dc1d5..0000000 --- a/.gsd/milestones/M001/slices/S02/tasks/T01-PLAN.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -estimated_steps: 4 -estimated_files: 2 -skills_used: - - best-practices - - test ---- - -# T01: Strengthen application-package context assembly and backend draft tests - -**Slice:** S02 — Stronger AI application package drafting -**Milestone:** M001 - -## Description - -Make the backend application-package generator use the context S01 now provides. The executor should keep the existing package endpoint, but improve how it builds prompts and selects context so the tailored CV, cover letter, recruiter message, and supporting signals reflect imported correspondence, recruiter/job details, profile CV structure, and attachment context more convincingly. - -## Steps - -1. Inspect `JobTrackerApi/Controllers/JobApplicationsController.cs` around `generate-application-package` and identify which job, recruiter, correspondence, attachment, and profile-CV signals are already available but underused. -2. Refine the package-context assembly and prompt shape so imported correspondence and recruiter/job-specific details influence the generated drafts directly without weakening the no-auto-send boundary. -3. Add a focused backend test file `JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs` that exercises the package endpoint contract with real job/correspondence/profile context and asserts the returned artifacts are specific to that context. -4. Keep the output contract stable unless a change materially improves the workspace; if it changes, make the added fields explicit and limited to what T02 will consume. - -## Must-Haves - -- [ ] Imported correspondence from S01 is deliberately consumed in package generation instead of remaining disconnected from the draft flow. -- [ ] Backend tests prove package output responds to job-specific context rather than generic fallback behavior. -- [ ] The generator still returns review-only draft material and does not cross the manual-send boundary. - -## Verification - -- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests` -- Confirm the focused test covers correspondence-aware package context and expected package artifacts. - -## Observability Impact - -- Signals added/changed: clearer package-response artifacts and stronger context assembly around job/correspondence/profile inputs. -- How a future agent inspects this: read `JobTrackerApi/Controllers/JobApplicationsController.cs` and `JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs`. -- Failure state exposed: focused backend verification should show whether weak drafts come from missing context assembly, empty correspondence state, or prompt/output contract drift. - -## Inputs - -- `JobTrackerApi/Controllers/JobApplicationsController.cs` — current application-package and related AI draft endpoints. -- `Models/Correspondence.cs` — persisted Gmail-linked correspondence metadata from S01. -- `JobTrackerApi/Controllers/ProfileCvController.cs` — profile CV structure/source-of-truth behavior. - -## Expected Output - -- `JobTrackerApi/Controllers/JobApplicationsController.cs` — stronger package-generation context and/or response contract. -- `JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs` — focused backend proof for context-aware package generation. diff --git a/.gsd/milestones/M001/slices/S02/tasks/T01-SUMMARY.md b/.gsd/milestones/M001/slices/S02/tasks/T01-SUMMARY.md deleted file mode 100644 index c9c72e0..0000000 --- a/.gsd/milestones/M001/slices/S02/tasks/T01-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T01 -parent: S02 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.777Z -blocker_discovered: false ---- - -# T01: Strengthen application-package context assembly and backend draft tests - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S02/tasks/T02-PLAN.md b/.gsd/milestones/M001/slices/S02/tasks/T02-PLAN.md deleted file mode 100644 index 3258a57..0000000 --- a/.gsd/milestones/M001/slices/S02/tasks/T02-PLAN.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -estimated_steps: 4 -estimated_files: 3 -skills_used: - - react-best-practices - - test ---- - -# T02: Make the job workspace save and present the application package as real working material - -**Slice:** S02 — Stronger AI application package drafting -**Milestone:** M001 - -## Description - -Turn the Tailored CV/application package area into a coherent working loop. The workspace should make it obvious what was generated, what was edited, what was saved to the job, and what can be reused later, instead of feeling like a temporary AI preview pane. - -## Steps - -1. Update `job-tracker-ui/src/types.ts` if T01 changes the package contract or exposes stronger saved draft/package state. -2. Refine `job-tracker-ui/src/components/JobDetailsDialog.tsx` so generation, editing, saving, and redisplay of application package material feel like one continuous workflow tied to the job. -3. Expand `job-tracker-ui/src/job-details-generated-drafts.test.tsx` to prove the stronger package flow: generate with contextual outputs, edit/save the important draft artifacts, and verify the saved material is reflected back in the dialog state. -4. Keep the UI focused on working material already tied to the job; do not introduce a second competing draft surface or outbound automation. - -## Must-Haves - -- [ ] The Tailored CV tab clearly presents generated package artifacts as editable, savable job material rather than disposable previews. -- [ ] Saved package edits update dialog state in a way the user can trust and later slices can reuse. -- [ ] The focused React test proves generation and save behavior for the package loop. - -## Verification - -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-generated-drafts.test.tsx` -- Confirm the expanded test proves generation, editing, and save-state behavior in the dialog. - -## Observability Impact - -- Signals added/changed: clearer UI state around generated vs saved package material and stronger test coverage for package-loop regressions. -- How a future agent inspects this: open the Tailored CV tab in `job-tracker-ui/src/components/JobDetailsDialog.tsx` and read `job-tracker-ui/src/job-details-generated-drafts.test.tsx`. -- Failure state exposed: the workspace and test should distinguish generation failure, unsaved edits, and saved package state instead of collapsing them into generic draft text. - -## Inputs - -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — existing package-generation and save UI. -- `job-tracker-ui/src/types.ts` — frontend package contracts. -- `JobTrackerApi/Controllers/JobApplicationsController.cs` — T01 package-generation contract. -- `job-tracker-ui/src/job-details-generated-drafts.test.tsx` — current focused dialog test. - -## Expected Output - -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — stronger package generation/edit/save flow. -- `job-tracker-ui/src/types.ts` — aligned package contract for the workspace. -- `job-tracker-ui/src/job-details-generated-drafts.test.tsx` — focused frontend proof for the improved package loop. diff --git a/.gsd/milestones/M001/slices/S02/tasks/T02-SUMMARY.md b/.gsd/milestones/M001/slices/S02/tasks/T02-SUMMARY.md deleted file mode 100644 index edf12b7..0000000 --- a/.gsd/milestones/M001/slices/S02/tasks/T02-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T02 -parent: S02 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.777Z -blocker_discovered: false ---- - -# T02: Make the job workspace save and present the application package as real working material - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S03/S03-PLAN.md b/.gsd/milestones/M001/slices/S03/S03-PLAN.md deleted file mode 100644 index 7f6a2bd..0000000 --- a/.gsd/milestones/M001/slices/S03/S03-PLAN.md +++ /dev/null @@ -1,12 +0,0 @@ -# S03: Reply and follow-up drafting from real thread context - -**Goal:** Make follow-up drafting use imported correspondence and saved application material well enough that the job workspace can produce specific, trustworthy follow-up and reply drafts without crossing the manual-send boundary. -**Demo:** After this: TBD - -## Tasks -- [x] **T01: Strengthen follow-up draft context assembly and backend reply/follow-up tests** — - - Files: JobTrackerApi/Controllers/JobApplicationsController.cs, JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs - - Verify: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsFollowUpDraftTests` -- [x] **T02: Make the follow-up workspace show thread-grounded draft state without autonomous sending** — - - Files: job-tracker-ui/src/components/JobDetailsDialog.tsx, job-tracker-ui/src/types.ts, job-tracker-ui/src/job-details-followup-drafts.test.tsx - - Verify: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-followup-drafts.test.tsx` diff --git a/.gsd/milestones/M001/slices/S03/S03-SUMMARY.md b/.gsd/milestones/M001/slices/S03/S03-SUMMARY.md deleted file mode 100644 index f43be74..0000000 --- a/.gsd/milestones/M001/slices/S03/S03-SUMMARY.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -id: S03 -parent: M001 -milestone: M001 -provides: - - Context-grounded follow-up drafting that combines imported correspondence, saved application package material, and explicit manual send/log behavior inside the job workspace. -requires: - - slice: S01 - provides: Imported correspondence records, linked Gmail thread metadata, and ongoing thread refresh continuity tied to a job. - - slice: S02 - provides: Saved tailored CV, cover letter, recruiter message, and marker-delimited application-answer draft state reused by follow-up drafting. -affects: - - S04 -key_files: - - JobTrackerApi/Controllers/JobApplicationsController.cs - - JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs - - JobTrackerApi.Tests/JobTrackerApi.Tests.csproj - - job-tracker-ui/src/components/JobDetailsDialog.tsx - - job-tracker-ui/src/types.ts - - job-tracker-ui/src/job-details-followup-drafts.test.tsx -key_decisions: - - Expose follow-up grounding (`contextSummary`, `contextSignals`, thread subject, and last-correspondence metadata) directly from the backend DTO so the workspace explains why the draft exists instead of inferring context client-side. - - Preserve the manual-send boundary by keeping follow-up generation separate from explicit send/log submission. -patterns_established: - - Assemble thread/package grounding once in the backend and return it as part of the draft contract, then render the same grounding verbatim in the workspace. - - Reuse the S02 notes marker block for saved application-answer context instead of inventing a second persistence path for follow-up drafting. -observability_surfaces: - - GET /api/jobapplications/{id}/followup-draft - - POST /api/jobapplications/{id}/send-followup - - GET /api/correspondence/{jobId} - - JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs - - job-tracker-ui/src/job-details-followup-drafts.test.tsx -drill_down_paths: - - .gsd/milestones/M001/slices/S03/tasks/T01-SUMMARY.md - - .gsd/milestones/M001/slices/S03/tasks/T02-SUMMARY.md -duration: 2-task slice; planned 8h implementation plus closeout verification -verification_result: passed -completed_at: 2026-03-24 ---- - -# S03: Reply and follow-up drafting from real thread context - -**Follow-up drafting now reuses imported thread history plus saved package material, shows that grounding in the job workspace, and keeps outbound email behind an explicit manual send/log action.** - -## What Happened - -S03 closed the gap between the correspondence workspace built in S01 and the saved application package workspace built in S02. Before this slice, follow-up generation mostly behaved like a generic prompt over job summary text. After this slice, `JobApplicationsController` assembles follow-up context from the latest imported correspondence, recruiter details, saved tailored CV / cover letter / recruiter message material, and the marker-delimited application-answer draft saved in notes. The API now returns not only a draft subject/body, but also the grounding metadata the UI needs to explain why the draft was generated and what informed it. - -On the frontend, `JobDetailsDialog.tsx` now treats follow-up drafting as part of the same per-job working loop rather than a blind compose form. The Follow-up tab shows thread subject, latest sender, context summary, and context signals; keeps the body editable before send; and makes the manual-send boundary explicit in the helper text and button flow. The UI posts the edited draft through the existing send/log endpoint instead of silently dispatching anything. - -During closeout, the slice-level backend verification command in the plan was restored as a first-class path by repairing missing framework/package references in `JobTrackerApi.Tests/JobTrackerApi.Tests.csproj`. That means future agents can use the filtered `dotnet test ... --filter JobApplicationsFollowUpDraftTests` command directly in this worktree instead of relying on the older isolated harness workaround recorded during task execution. - -Net effect: imported correspondence, saved package drafts, and the follow-up compose/send-log loop now form one coherent job-level workspace path. S04 can build overview and urgency surfaces on top of a follow-up flow that is already grounded and manually controlled. - -## Verification - -- Passed: `$HOME/.dotnet/dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsFollowUpDraftTests` -- Passed: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-followup-drafts.test.tsx` -- Passed: `$HOME/.dotnet/dotnet build JobTrackerApi/JobTrackerApi.csproj` -- Passed: `CI=true npm --prefix job-tracker-ui run build` -- Observability surfaces confirmed through code/test contracts: - - `GET /api/jobapplications/{id}/followup-draft` now exposes grounding metadata (`contextSummary`, `contextSignals`, thread subject, last-correspondence details) - - `POST /api/jobapplications/{id}/send-followup` remains the explicit manual send/log boundary - - `GET /api/correspondence/{jobId}` remains the authoritative timeline surface for confirming logged outbound follow-up entries - -## New Requirements Surfaced - -- none - -## Deviations - -The written plan expected the filtered backend command to be the slice-level proof path, but task execution had fallen back to an isolated harness because `JobTrackerApi.Tests.csproj` was missing required ASP.NET Core / Identity / xUnit references. Closeout repaired the test project so the plan-level verification command now passes directly in this worktree. - -## Known Limitations - -- The slice materially strengthened follow-up drafting, but it still does not introduce a separate rich reply-specific workflow beyond thread-aware subject/context reuse; deeper reply-mode specialization can still be expanded later if usage demands it. -- This closeout did not re-run a full live Gmail + browser UAT loop end to end; the trusted evidence for S03 in this worktree is the focused backend/frontend verification plus the production frontend build. -- Follow-up quality still depends on upstream data quality from S01 and S02: weak imported thread metadata or missing saved package material will reduce how specific the draft can be. - -## Follow-ups - -- S04 should consume the new follow-up grounding/status signals to show which jobs are ready for follow-up, missing context, or need attention from overview surfaces. -- S05 should re-run the full live loop with real Gmail continuity plus follow-up drafting to reconfirm milestone trust end to end. - -## Files Created/Modified - -- `JobTrackerApi/Controllers/JobApplicationsController.cs` — assembled correspondence-aware follow-up context, exposed draft grounding metadata, and improved fallback/reply-style subject behavior. -- `JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs` — added focused backend proof that imported thread state plus saved package material change the follow-up draft. -- `JobTrackerApi.Tests/JobTrackerApi.Tests.csproj` — restored missing framework/package references so filtered backend verification commands run directly again. -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — exposed follow-up grounding in the workspace, kept drafts editable, and clarified the manual-send boundary. -- `job-tracker-ui/src/types.ts` — added the richer `FollowUpDraft` contract used by the workspace. -- `job-tracker-ui/src/job-details-followup-drafts.test.tsx` — proved the Follow-up tab renders context grounding and only sends/logs after explicit user action. - -## Forward Intelligence - -### What the next slice should know -- S03 made the follow-up flow depend on three upstream context sources at once: imported correspondence, saved package fields, and the marker-delimited application-answer draft in `JobApplication.Notes`. If S04 wants actionable table/dashboard indicators, it should reuse the backend grounding signals instead of trying to infer readiness from scattered raw fields in the browser. - -### What's fragile -- `JobApplication.Notes` marker parsing for `<<>> ... <<>>` — if another slice starts appending free-form notes around that block incorrectly, follow-up context quality will silently degrade because saved application-answer reuse depends on parsing that exact marker format. - -### Authoritative diagnostics -- `GET /api/jobapplications/{id}/followup-draft` and `JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs` — the endpoint payload is the single source of truth for what grounding the backend believes it has, and the focused backend test is the fastest trustworthy check when prompt assembly or DTO shape changes. -- `job-tracker-ui/src/job-details-followup-drafts.test.tsx` — this is the most reliable UI-level diagnostic for the manual-send boundary and edited-draft payload because it asserts the exact send/log request body after user edits. - -### What assumptions changed -- “The slice may need an isolated backend harness because the filtered test command is not trustworthy.” — no longer true in this worktree; closeout repaired `JobTrackerApi.Tests.csproj`, so the plan-level filtered backend command now passes directly and should replace the older workaround. diff --git a/.gsd/milestones/M001/slices/S03/S03-UAT.md b/.gsd/milestones/M001/slices/S03/S03-UAT.md deleted file mode 100644 index dd2c8fb..0000000 --- a/.gsd/milestones/M001/slices/S03/S03-UAT.md +++ /dev/null @@ -1,92 +0,0 @@ -# S03: Reply and follow-up drafting from real thread context — UAT - -**Milestone:** M001 -**Written:** 2026-03-24 - -## UAT Type - -- UAT mode: mixed -- Why this mode is sufficient: S03 changes both backend draft assembly and the per-job Follow-up workspace, so the right acceptance script combines artifact-backed checks (focused backend/frontend tests and build) with a human review of draft specificity, editability, and the manual-send boundary. - -## Preconditions - -- The API and UI for this worktree are running from `/home/pi/development/JobTracker/.gsd/worktrees/M001`. -- A job exists with all of the following: - - imported Gmail correspondence tied to the job - - recruiter email populated on the company/job - - saved tailored CV, cover letter, recruiter message, and/or saved application-answer draft material -- The tester can open that job in the job workspace. -- No autonomous outbound-email automation is enabled anywhere in the environment. - -## Smoke Test - -Open one seeded job, switch to the **Follow-up** tab, and confirm the tab shows a generated draft plus a visible context panel that names the thread/package grounding rather than only a blank email form. - -## Test Cases - -### 1. Generate a thread-grounded follow-up draft - -1. Open a job that already has imported correspondence and saved application package material. -2. Open the **Follow-up** tab. -3. Wait for the draft to load. -4. Review the context panel above/alongside the draft. -5. **Expected:** - - a draft subject and body are generated - - the UI shows why the draft was generated now (for example, waiting-update timing) - - the UI shows thread/package grounding such as thread subject, latest sender, context summary, or context signals - - the draft content feels tied to the actual thread stage and saved package context instead of reading like a generic template - -### 2. Edit the draft before sending and verify manual-send behavior - -1. In the **Follow-up** tab, edit the generated subject and/or body. -2. Confirm the recipient field is editable or clearly visible before any send action. -3. Verify the helper text/button copy makes the manual-send boundary explicit. -4. Click **Send and log email**. -5. **Expected:** - - nothing is sent before the explicit button click - - the edited draft text, not the original generated text, is what gets submitted - - the workflow behaves like a manual send/log action rather than background automation - - there is a clear success state or logged result after submission - -### 3. Confirm the sent follow-up reappears in job history/correspondence - -1. After sending/logging the follow-up, switch to the job’s correspondence/history surface. -2. Refresh the job workspace if needed. -3. Find the newly logged outbound follow-up entry. -4. **Expected:** - - the sent/logged follow-up appears back in the same job timeline/correspondence record - - the entry is attached to the correct job and thread context - - the app reflects history continuity instead of treating the send as an isolated compose event - -## Edge Cases - -### Missing package or thin thread context still preserves user control - -1. Open a job that has weaker context than the happy path (for example, imported correspondence but little/no saved package material, or saved package material but only a thin thread). -2. Generate a follow-up draft. -3. **Expected:** - - the app still returns an editable draft or a clearly explained degraded draft state - - the UI reflects missing/limited grounding rather than pretending the draft is strongly informed - - the manual-send boundary remains unchanged: no autonomous send occurs, and the user still must explicitly send/log - -## Failure Signals - -- The Follow-up tab loads only a blank compose form with no visible context grounding. -- The generated draft ignores obvious thread details (subject, recruiter, recent message content) and reads like a generic reminder. -- Editing the draft does not persist into the send/log request. -- A follow-up appears to be sent or logged without an explicit user action. -- The sent follow-up does not return to the job’s correspondence/history surface. -- The tab crashes, build/test regressions appear, or the follow-up endpoint no longer returns the grounding fields expected by the UI. - -## Not Proven By This UAT - -- This UAT does not prove Gmail OAuth/import itself; that was covered by S01 and is only consumed here as prerequisite context. -- This UAT does not prove end-to-end milestone coherence across table/dashboard/control-loop surfaces; S04 and S05 still own that broader workflow validation. - -## Notes for Tester - -Use a job with real-looking imported thread content and saved package material; otherwise the quality signal will be too weak to judge whether S03 actually improved grounding. If the environment cannot support live send/log execution, fall back to the focused checks that already passed for this slice: - -- `$HOME/.dotnet/dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsFollowUpDraftTests` -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-followup-drafts.test.tsx` -- `CI=true npm --prefix job-tracker-ui run build` diff --git a/.gsd/milestones/M001/slices/S03/tasks/T01-PLAN.md b/.gsd/milestones/M001/slices/S03/tasks/T01-PLAN.md deleted file mode 100644 index 1206899..0000000 --- a/.gsd/milestones/M001/slices/S03/tasks/T01-PLAN.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -estimated_steps: 4 -estimated_files: 2 -skills_used: - - test ---- - -# T01: Strengthen follow-up draft context assembly and backend reply/follow-up tests - -**Slice:** S03 — Reply and follow-up drafting from real thread context -**Milestone:** M001 - -## Description - -Make the follow-up draft endpoint use imported correspondence, recruiter details, and saved application package material so the draft reflects the real thread stage and saved job context instead of a generic reminder template. - -## Steps - -1. Audit `GetFollowUpDraft` and nearby helpers in `JobApplicationsController` to identify which imported thread/package signals are currently ignored. -2. Add or refactor backend context assembly so follow-up drafting consumes recent correspondence, saved package material, recruiter details, and stage-specific cues without crossing the manual-send boundary. -3. Add focused backend tests proving the follow-up draft output changes in response to thread/package context and still preserves explicit manual-send behavior. -4. Verify the focused backend behavior with an isolated test path if the broader test project remains blocked by unrelated compile drift. - -## Must-Haves - -- [ ] Follow-up draft context includes imported correspondence and saved application package material deliberately. -- [ ] The generated follow-up draft reflects thread stage/recruiter context instead of generic job-only phrasing. -- [ ] Focused backend tests prove the stronger draft grounding. - -## Verification - -- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsFollowUpDraftTests` -- Focused isolated harness if needed: run only `JobApplicationsFollowUpDraftTests` against `JobTrackerApi/Controllers/JobApplicationsController.cs` - -## Observability Impact - -- Signals added/changed: richer follow-up draft reason/context surface and clearer thread-aware draft behavior. -- How a future agent inspects this: `GET /api/jobapplications/{id}/followup-draft` plus `JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs`. -- Failure state exposed: missing thread/package context should show up as weaker fallback behavior in focused backend tests rather than silent generic output. - -## Inputs - -- `JobTrackerApi/Controllers/JobApplicationsController.cs` — existing follow-up draft and send/log endpoints. -- `Models/Correspondence.cs` — imported thread/sender/recipient fields from S01. -- `JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs` — current focused test seam style for isolated job-application behavior. - -## Expected Output - -- `JobTrackerApi/Controllers/JobApplicationsController.cs` — stronger follow-up draft context assembly. -- `JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs` — focused backend proof for thread-aware follow-up drafting. diff --git a/.gsd/milestones/M001/slices/S03/tasks/T01-SUMMARY.md b/.gsd/milestones/M001/slices/S03/tasks/T01-SUMMARY.md deleted file mode 100644 index 56937c0..0000000 --- a/.gsd/milestones/M001/slices/S03/tasks/T01-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T01 -parent: S03 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.777Z -blocker_discovered: false ---- - -# T01: Strengthen follow-up draft context assembly and backend reply/follow-up tests - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S03/tasks/T02-PLAN.md b/.gsd/milestones/M001/slices/S03/tasks/T02-PLAN.md deleted file mode 100644 index 7771978..0000000 --- a/.gsd/milestones/M001/slices/S03/tasks/T02-PLAN.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -estimated_steps: 4 -estimated_files: 3 -skills_used: - - react-best-practices - - test ---- - -# T02: Make the follow-up workspace show thread-grounded draft state without autonomous sending - -**Slice:** S03 — Reply and follow-up drafting from real thread context -**Milestone:** M001 - -## Description - -Turn the Follow-up tab into a clearer workspace that shows why the draft was generated now, what thread/package context informed it, and what will happen when the user manually sends/logs it. - -## Steps - -1. Align frontend types with any stronger follow-up draft contract exposed by T01. -2. Refine the Follow-up tab in `JobDetailsDialog.tsx` so it surfaces thread/package grounding, editable draft state, and the manual-send boundary clearly. -3. Add a focused React test that proves generation, editability, and manual send/log behavior for the follow-up loop. -4. Verify the focused follow-up workspace test and make sure it covers the saved-context/thread-aware behavior instead of generic form rendering. - -## Must-Haves - -- [ ] The Follow-up tab shows why the follow-up is due and what job/thread/package context informed the draft. -- [ ] The draft remains editable before sending and the send action stays explicitly manual. -- [ ] The focused React test proves generate/edit/send-log behavior for the follow-up loop. - -## Verification - -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-followup-drafts.test.tsx` -- Confirm the test proves thread-aware draft context plus manual send/log behavior in `job-tracker-ui/src/components/JobDetailsDialog.tsx`. - -## Observability Impact - -- Signals added/changed: clearer Follow-up tab state around draft reason, informing context, and sent/logged outcome. -- How a future agent inspects this: open the Follow-up tab in `job-tracker-ui/src/components/JobDetailsDialog.tsx` and read `job-tracker-ui/src/job-details-followup-drafts.test.tsx`. -- Failure state exposed: the UI should distinguish draft-generation failure, editable draft state, and sent/logged follow-up state. - -## Inputs - -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — current follow-up UI. -- `job-tracker-ui/src/types.ts` — current frontend contracts. -- `JobTrackerApi/Controllers/JobApplicationsController.cs` — stronger follow-up draft contract from T01. - -## Expected Output - -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — follow-up workspace grounded in saved/job/thread context. -- `job-tracker-ui/src/types.ts` — aligned follow-up DTO shape if T01 adds context fields. -- `job-tracker-ui/src/job-details-followup-drafts.test.tsx` — focused frontend proof for the follow-up loop. diff --git a/.gsd/milestones/M001/slices/S03/tasks/T02-SUMMARY.md b/.gsd/milestones/M001/slices/S03/tasks/T02-SUMMARY.md deleted file mode 100644 index fd6730a..0000000 --- a/.gsd/milestones/M001/slices/S03/tasks/T02-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T02 -parent: S03 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.777Z -blocker_discovered: false ---- - -# T02: Make the follow-up workspace show thread-grounded draft state without autonomous sending - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S04/S04-PLAN.md b/.gsd/milestones/M001/slices/S04/S04-PLAN.md deleted file mode 100644 index 5006a03..0000000 --- a/.gsd/milestones/M001/slices/S04/S04-PLAN.md +++ /dev/null @@ -1,12 +0,0 @@ -# S04: Daily control loop surfaces - -**Goal:** Make the job table, reminders view, and dashboard behave like one daily control loop so the user can scan what needs attention and jump directly into the right job workspace state. -**Demo:** After this: TBD - -## Tasks -- [x] **T01: Turn reminders and dashboard into actionable entry surfaces** — - - Files: job-tracker-ui/src/components/DashboardView.tsx, job-tracker-ui/src/components/RemindersView.tsx, job-tracker-ui/src/App.tsx - - Verify: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx` -- [x] **T02: Make the job table expose the right next action and prove the daily loop** — - - Files: job-tracker-ui/src/components/JobTable.tsx, job-tracker-ui/src/daily-control-loop.test.tsx - - Verify: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx` diff --git a/.gsd/milestones/M001/slices/S04/S04-SUMMARY.md b/.gsd/milestones/M001/slices/S04/S04-SUMMARY.md deleted file mode 100644 index 1afc8b1..0000000 --- a/.gsd/milestones/M001/slices/S04/S04-SUMMARY.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: S04 summary -status: done -verification: - - CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx - - CI=true npm --prefix job-tracker-ui run build - - Browser UAT on built branch UI via localhost:3001 against localhost:5202 ---- - -S04 turned the table, dashboard, and reminders into a coherent daily control loop. - -Delivered: -- routed workspace entry helper in `job-tracker-ui/src/jobWorkspaceRoute.ts` -- actionable dashboard attention section in `job-tracker-ui/src/components/DashboardView.tsx` -- reminders flow routed into the existing `/jobs` workspace in `job-tracker-ui/src/components/RemindersView.tsx` -- clickable urgency chips in `job-tracker-ui/src/components/JobTable.tsx` that jump straight into follow-up or tailored-CV work -- focused UI proof in `job-tracker-ui/src/daily-control-loop.test.tsx` - -Browser verification: -- verified dashboard `Follow up` action opened the real job workspace on the Follow-up tab in the built branch UI -- verified reminders `Open` routed back into the same job workspace flow -- verified S03 follow-up flow remained intact when entered from the daily overview surfaces - -Net effect: -- the job table is now a clearer first-stop overview -- reminders and dashboard no longer strand the user in secondary surfaces -- overview surfaces now route into one workspace model instead of competing loops diff --git a/.gsd/milestones/M001/slices/S04/S04-UAT.md b/.gsd/milestones/M001/slices/S04/S04-UAT.md deleted file mode 100644 index cd8cb77..0000000 --- a/.gsd/milestones/M001/slices/S04/S04-UAT.md +++ /dev/null @@ -1,27 +0,0 @@ -# S04: Recovery placeholder UAT - -**Milestone:** M001 -**Written:** 2026-03-24T12:56:42.784Z - -## Preconditions -- Doctor created this placeholder because the expected UAT file was missing. - -## Smoke Test -- Re-run the slice verification from the slice plan before shipping. - -## Test Cases -### 1. Replace this placeholder -1. Read the slice plan and task summaries. -2. Write a real UAT script. -3. **Expected:** This placeholder is replaced with meaningful human checks. - -## Edge Cases -### Missing completion artifacts -1. Confirm the summary, roadmap checkbox, and state file are coherent. -2. **Expected:** GSD doctor reports no remaining completion drift for this slice. - -## Failure Signals -- Placeholder content still present when treating the slice as done - -## Notes for Tester -Doctor created this file only to restore the required artifact shape. Replace it with a real UAT script. diff --git a/.gsd/milestones/M001/slices/S04/tasks/T01-PLAN.md b/.gsd/milestones/M001/slices/S04/tasks/T01-PLAN.md deleted file mode 100644 index fe66e6d..0000000 --- a/.gsd/milestones/M001/slices/S04/tasks/T01-PLAN.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -estimated_steps: 4 -estimated_files: 3 -skills_used: - - react-best-practices - - test ---- - -# T01: Turn reminders and dashboard into actionable entry surfaces - -**Slice:** S04 — Daily control loop surfaces -**Milestone:** M001 - -## Description - -Make the dashboard and reminders page show high-priority jobs as direct entry points into the existing job workspace state instead of acting like passive summaries or separate modal loops. - -## Steps - -1. Audit the current dashboard and reminders surfaces to identify where reminder/readiness information is already available but not actionable. -2. Add direct job-open actions that route into `/jobs` with the correct workspace tab and optional follow-up mode. -3. Replace reminder-modal detours with routed job-workspace entry where it improves flow coherence. -4. Cover the new routed-entry behavior in a focused UI test. - -## Must-Haves - -- [ ] Dashboard shows actionable jobs needing attention now. -- [ ] Reminders routes into the existing job workspace state rather than creating a separate loop. -- [ ] Focused UI coverage proves routed entry from overview surfaces. - -## Verification - -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx` -- `CI=true npm --prefix job-tracker-ui run build` - -## Inputs - -- `job-tracker-ui/src/components/DashboardView.tsx` — current analytics dashboard. -- `job-tracker-ui/src/components/RemindersView.tsx` — current reminders page. -- `job-tracker-ui/src/App.tsx` — route shell and navigation structure. - -## Expected Output - -- `job-tracker-ui/src/components/DashboardView.tsx` — actionable attention cards or lists. -- `job-tracker-ui/src/components/RemindersView.tsx` — routed job-workspace entry flow. -- `job-tracker-ui/src/daily-control-loop.test.tsx` — focused proof for overview-to-workspace routing. diff --git a/.gsd/milestones/M001/slices/S04/tasks/T01-SUMMARY.md b/.gsd/milestones/M001/slices/S04/tasks/T01-SUMMARY.md deleted file mode 100644 index 2b7643d..0000000 --- a/.gsd/milestones/M001/slices/S04/tasks/T01-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T01 -parent: S04 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.778Z -blocker_discovered: false ---- - -# T01: Turn reminders and dashboard into actionable entry surfaces - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S04/tasks/T02-PLAN.md b/.gsd/milestones/M001/slices/S04/tasks/T02-PLAN.md deleted file mode 100644 index a6154fb..0000000 --- a/.gsd/milestones/M001/slices/S04/tasks/T02-PLAN.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -estimated_steps: 4 -estimated_files: 2 -skills_used: - - react-best-practices - - test ---- - -# T02: Make the job table expose the right next action and prove the daily loop - -**Slice:** S04 — Daily control loop surfaces -**Milestone:** M001 - -## Description - -Strengthen the job table so the first daily view shows what action is actually due and can jump directly into the right job workspace tab. - -## Steps - -1. Audit the existing job-table row chips and actions against the reminder/readiness data already available. -2. Add clearer action affordances for follow-up and package work that route into the same job workspace state used by dashboard/reminders. -3. Fold the table interactions into the focused daily-loop test. -4. Verify the routed-table behavior and build output. - -## Must-Haves - -- [ ] Job-table urgency signals are actionable, not just decorative. -- [ ] Table actions route into the same workspace state used by reminders/dashboard. -- [ ] The focused UI test proves the table participates in the same daily loop. - -## Verification - -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx` -- `CI=true npm --prefix job-tracker-ui run build` - -## Inputs - -- `job-tracker-ui/src/components/JobTable.tsx` — current table and row actions. -- `job-tracker-ui/src/daily-control-loop.test.tsx` — focused loop test from T01. - -## Expected Output - -- `job-tracker-ui/src/components/JobTable.tsx` — clearer next-action affordances. -- `job-tracker-ui/src/daily-control-loop.test.tsx` — proof that the table participates in the routed daily loop. - -## Observability Impact - -- Signals changed: the table's urgency chips and primary row actions should now expose the same routed follow-up and package-work intents already used by dashboard and reminders. -- How to inspect later: read `job-tracker-ui/src/components/JobTable.tsx` for the shared workspace-route usage and run `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx` to confirm table, reminders, and dashboard all land in the expected workspace state. -- Failure state made visible: if table actions drift from the shared routing contract, the focused UI test should fail with the wrong route/tab content instead of silently leaving decorative chips in place. diff --git a/.gsd/milestones/M001/slices/S04/tasks/T02-SUMMARY.md b/.gsd/milestones/M001/slices/S04/tasks/T02-SUMMARY.md deleted file mode 100644 index 81290a5..0000000 --- a/.gsd/milestones/M001/slices/S04/tasks/T02-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T02 -parent: S04 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.778Z -blocker_discovered: false ---- - -# T02: Make the job table expose the right next action and prove the daily loop - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S04/tasks/T02-VERIFY.json b/.gsd/milestones/M001/slices/S04/tasks/T02-VERIFY.json deleted file mode 100644 index 0a23add..0000000 --- a/.gsd/milestones/M001/slices/S04/tasks/T02-VERIFY.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "schemaVersion": 1, - "taskId": "T02", - "unitId": "M001/S04/T02", - "timestamp": 1774357002857, - "passed": false, - "discoverySource": "none", - "checks": [], - "retryAttempt": 1, - "maxRetries": 2, - "runtimeErrors": [ - { - "source": "bg-shell", - "severity": "crash", - "message": "[jobtracker-api] exitCode=127", - "blocking": true - } - ] -} diff --git a/.gsd/milestones/M001/slices/S05/S05-PLAN.md b/.gsd/milestones/M001/slices/S05/S05-PLAN.md deleted file mode 100644 index 9ee1048..0000000 --- a/.gsd/milestones/M001/slices/S05/S05-PLAN.md +++ /dev/null @@ -1,12 +0,0 @@ -# S05: End-to-end trust and workflow polish - -**Goal:** Prove the full daily-use loop as one trustworthy workflow by tightening shared next-action/readiness signals, then validating overview → workspace → package → Gmail continuity → follow-up behavior without weakening the manual-send boundary. -**Demo:** After this: TBD - -## Tasks -- [x] **T01: Centralize workflow trust signals across overview and readiness surfaces** — - - Files: JobTrackerApi/Controllers/JobApplicationsController.cs, JobTrackerApi.Tests/JobApplicationsWorkflowSignalsTests.cs, job-tracker-ui/src/types.ts, job-tracker-ui/src/jobWorkflowSignals.ts, job-tracker-ui/src/components/JobTable.tsx, job-tracker-ui/src/components/DashboardView.tsx, job-tracker-ui/src/components/RemindersView.tsx, job-tracker-ui/src/workflow-trust-signals.test.tsx - - Verify: `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsWorkflowSignalsTests` and `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/workflow-trust-signals.test.tsx` -- [x] **T02: Add integrated trust-loop proof and workspace polish** — - - Files: job-tracker-ui/src/components/JobDetailsDialog.tsx, job-tracker-ui/src/components/Correspondence.tsx, job-tracker-ui/src/end-to-end-trust-loop.test.tsx, job-tracker-ui/src/daily-control-loop.test.tsx, .gsd/milestones/M001/slices/S05/S05-UAT.md - - Verify: `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/end-to-end-trust-loop.test.tsx`, `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx src/job-details-generated-drafts.test.tsx src/job-details-followup-drafts.test.tsx src/daily-control-loop.test.tsx`, and `CI=true npm --prefix job-tracker-ui run build` diff --git a/.gsd/milestones/M001/slices/S05/S05-RESEARCH.md b/.gsd/milestones/M001/slices/S05/S05-RESEARCH.md deleted file mode 100644 index 082498d..0000000 --- a/.gsd/milestones/M001/slices/S05/S05-RESEARCH.md +++ /dev/null @@ -1,119 +0,0 @@ -# S05 — Research - -**Date:** 2026-03-24 - -## Summary - -S05 is an integration-and-polish slice, not a new subsystem. The codebase already has the main milestone pieces in place: job-table/dashboard routing into one workspace (`job-tracker-ui/src/jobWorkspaceRoute.ts`), Gmail import plus linked-thread refresh (`job-tracker-ui/src/components/Correspondence.tsx`, `JobTrackerApi/Controllers/GmailController.cs`), saved application-package drafting (`job-tracker-ui/src/components/JobDetailsDialog.tsx`, `JobTrackerApi/Controllers/JobApplicationsController.cs`), and grounded follow-up drafting with explicit manual send (`JobDetailsDialog.tsx`, `JobApplicationsController.cs`). What is still missing is one trustworthy proof path and a small amount of glue cleanup so the full loop feels coherent instead of slice-by-slice. - -The active requirements this slice most directly supports are **R008** and **R010**. R008 matters because `POST /api/jobapplications/{id}/send-followup` really calls `_email.SendAsync(...)`, so final verification must preserve the explicit-user-action boundary and avoid any accidental live outbound send during UAT. R010 matters because the continuity story is currently spread across several surfaces: reminders/dashboard route by `followUpReason` text, the table uses `tailoredCvText` plus `notes` as a package-readiness proxy, Gmail continuity is one-shot auto-refresh plus manual refresh, and follow-up grounding comes from a separate DTO. S05 should make those surfaces feel like one loop and prove them together. - -## Recommendation - -Treat S05 as **“integrated regression first, then polish only what the integrated proof exposes.”** Do not invent a new workflow. Reuse the existing shared `/jobs?open=...&tab=...&followMode=...` entry pattern, and keep backend DTOs as the source of truth instead of adding more browser-side heuristics. - -Two loaded skills reinforce that approach: -- **`react-best-practices`**: keep derived UI state in helpers instead of adding more mirrored effect-driven state (`rerender-derived-state-no-effect`, `rerender-dependencies`), and avoid introducing new fetch waterfalls when composing the final loop (`async-parallel`). -- **`aspnet-core`**: keep the controller/API contract as the authoritative feature seam. If S05 needs clearer trust/readiness signals, add them in `JobApplicationsController` DTOs/endpoints instead of duplicating string parsing rules in multiple React components. - -Primary recommendation: add one end-to-end UI regression that spans table/dashboard/reminders → shared workspace → package save → Gmail correspondence continuity → follow-up draft/manual-send boundary, then patch any trust gaps that test exposes. That is the fastest path to milestone-level confidence. - -## Implementation Landscape - -### Key Files - -- `job-tracker-ui/src/jobWorkspaceRoute.ts` — single shared route builder for opening a job workspace on a specific tab/mode. This is the seam S04 already established; S05 should keep using it rather than creating new navigation paths. -- `job-tracker-ui/src/components/JobTable.tsx` — primary entry surface. Important details: - - urgency/action chips route into the shared workspace - - `getActionSignals()` is the current place where “what needs attention now” is inferred - - `readinessFilter === "needs-work"` currently uses `!job.tailoredCvText || !job.notes`, which is a coarse proxy because `notes` also holds the S02 application-answer marker block and arbitrary notes -- `job-tracker-ui/src/components/DashboardView.tsx` — reminder/attention overview. It routes into Tailored CV or Follow-up based on `followUpReason` string matching. Useful for integrated proof, but fragile if more action types appear. -- `job-tracker-ui/src/components/RemindersView.tsx` — same pattern as dashboard: groups items by `followUpReason` text and routes into the shared workspace. -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — the real job workspace. It already contains the critical integrated loop pieces: - - package generation/save in tab 3 - - follow-up draft fetch + editable draft + explicit send/log in tab 4 - - readiness in tab 8 - - lazy per-tab data loading -- `job-tracker-ui/src/components/Correspondence.tsx` — Gmail import/continuity surface. Key non-obvious behavior: - - auto-refresh of linked threads is one-shot per `jobId + linkedThreadIds` set via `autoRefreshKeyRef` - - repeated pulls in the same session require the explicit **Refresh linked threads** action - - imported correspondence rows surface Gmail metadata directly in the timeline area -- `job-tracker-ui/src/daily-control-loop.test.tsx` — current best overview-surface proof. Verifies dashboard/reminders/job-table route into the shared workspace, but stops short of the full import/package/follow-up loop. -- `job-tracker-ui/src/correspondence-gmail-import.test.tsx` — current best Gmail continuity proof. Verifies ranked Gmail import and linked-thread refresh behavior, including the later reply appearing without manual re-import. -- `job-tracker-ui/src/job-details-generated-drafts.test.tsx` — best proof for package generate/edit/save/reload. -- `job-tracker-ui/src/job-details-followup-drafts.test.tsx` — best proof for follow-up grounding and the manual-send boundary. -- `JobTrackerApi/Controllers/JobApplicationsController.cs` — backend source of truth for S05 trust signals: - - `GET /api/jobapplications/{id}/followup-draft` - - `POST /api/jobapplications/{id}/send-followup` - - `GET /api/jobapplications/{id}/readiness` - - `PUT /api/jobapplications/{id}/application-drafts` - - `POST /api/jobapplications/{id}/generate-application-package` - - also owns reminders/analytics/readiness logic used by the overview surfaces -- `JobTrackerApi/Controllers/GmailController.cs` — backend source of truth for Gmail match/import/refresh continuity. -- `JobTrackerApi/Services/FollowUpReminderHostedService.cs` — sends reminder emails to the app user with a deep link into the follow-up tab. This is not recruiter auto-send, but it matters for live verification because outbound email infrastructure may be active. -- `job-tracker-ui/src/api.ts` — local UI targets `http://localhost:5202/api` on localhost; browser UAT must run the backend too. - -### Build Order - -1. **Prove the whole loop in one focused UI regression before polishing.** - - Best seam: add a new integrated React test or extend `src/daily-control-loop.test.tsx`. - - It should cover: open from an overview surface → land on workspace tab → generate/save package → confirm saved state is reused → open/import/refresh correspondence → generate follow-up with grounding → verify send remains explicit/manual. - - This gives the planner one artifact that tells it exactly what still feels fragmented. - -2. **Then fix trust/continuity heuristics exposed by that integrated proof.** - Likely candidates from current code: - - replace/centralize string-matching action routing (`followUpReason.includes('tailored cv')`) if it causes ambiguous or brittle behavior across `DashboardView.tsx` and `RemindersView.tsx` - - tighten package-readiness/action inference in `JobTable.tsx` so it reflects saved package state more directly than `!job.notes` - - if overview surfaces need richer trust signals, prefer adding explicit API fields in `JobApplicationsController.cs` over duplicating UI inference - -3. **Only after the integrated regression passes, do final browser UAT on the real app path.** - - Use the existing S04 pattern: browser entry through the real `/jobs` / `/dashboard` surfaces, not component-only proof. - - Keep live send safe: do not rely on clicking the final send button unless the environment is configured to a safe sink/stub recipient. - -### Verification Approach - -Automated regression set already worth keeping: - -- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests` -- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests` -- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsFollowUpDraftTests` -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx` -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-generated-drafts.test.tsx` -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-followup-drafts.test.tsx` -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx` -- add one new S05 integrated UI test and run it directly by path -- `dotnet build JobTrackerApi/JobTrackerApi.csproj` -- `CI=true npm --prefix job-tracker-ui run build` - -Browser/UAT proof should explicitly confirm: -- job table/dashboard/reminders all open the same workspace model -- package work saved in Tailored CV is visible when follow-up drafting runs later -- Gmail linked-thread refresh updates correspondence without manual re-import of the whole thread -- follow-up drafting shows grounding from saved package + correspondence context -- no outbound recruiter email is sent without the explicit send action - -## Constraints - -- Local browser verification requires **both** frontend and backend. `job-tracker-ui/src/api.ts` hard-codes `http://localhost:5202/api` on localhost. -- `send-followup` is a real email-sending endpoint; final verification must preserve R008 by avoiding accidental live sends. -- Gmail thread auto-refresh in `Correspondence.tsx` is intentionally one-shot per linked-thread set. A second refresh in the same session needs the manual button; that is by design, not a bug. -- The S02 application-answer draft still lives inside `JobApplication.Notes` with the `<<>>` marker block; any S05 readiness logic must not treat all notes as generic free text. - -## Common Pitfalls - -- **Treating `notes` as generic readiness state** — in this milestone, `notes` may contain the persisted application-answer draft block. If S05 wants a better package/trust signal, do not keep leaning on `!job.notes` alone. -- **Duplicating action inference in multiple components** — `DashboardView.tsx`, `RemindersView.tsx`, and `JobTable.tsx` already infer next actions separately. If S05 needs to change action logic, centralize it in a helper or API contract instead of drifting three copies. -- **Mistaking manual-send for no-send** — R008 forbids autonomous outbound communication, not explicit user-triggered send. S05 should verify the manual boundary remains explicit, not remove the send capability. -- **Running browser UAT with only the frontend** — the UI will render but fail to load real data because localhost calls go to `http://localhost:5202/api`. - -## Open Risks - -- The biggest remaining risk is not missing code, but a fragmented trust story: overview surfaces, package workspace, Gmail continuity, and follow-up grounding may all work individually while still feeling loosely connected. The integrated UI regression should be used to decide whether S05 needs a data-contract tweak or just copy/UX polish. -- If live email infrastructure is enabled, naive UAT on `send-followup` could send a real message. Prefer a safe sink/stubbed mail setup for final acceptance. - -## Skills Discovered - -| Technology | Skill | Status | -|------------|-------|--------| -| React UI workflow / performance | `react-best-practices` | available | -| ASP.NET Core controller APIs | `aspnet-core` | available | diff --git a/.gsd/milestones/M001/slices/S05/S05-SUMMARY.md b/.gsd/milestones/M001/slices/S05/S05-SUMMARY.md deleted file mode 100644 index 938e48a..0000000 --- a/.gsd/milestones/M001/slices/S05/S05-SUMMARY.md +++ /dev/null @@ -1,118 +0,0 @@ -# S05 Summary — End-to-end trust and workflow polish - -## Slice Outcome - -S05 completed the final trust-loop assembly for M001. The slice did not add a second workflow; it tightened the existing one so `/jobs`, `/dashboard`, `/reminders`, and the job workspace now describe the same next action, reuse the same saved package state, expose Gmail linked-thread continuity clearly, and keep follow-up drafting separate from any outbound send action. - -In practice, this slice turned the milestone from a set of individually working subsystems into one coherent daily-use loop: - -- overview surfaces route into the same workspace semantics -- saved package material is treated as explicit reusable workflow state -- linked Gmail thread refresh is visible in the workspace instead of hidden behind import-only UI -- grounded follow-up drafting remains available without crossing the manual-send boundary - -## What This Slice Actually Delivered - -### 1. Shared workflow trust/action model - -S05 centralized workflow trust signals across backend DTOs and frontend routing helpers so overview surfaces no longer guess from free-form `followUpReason` text or raw `notes` presence. - -Delivered pattern: - -- backend reminders/readiness return normalized `workflowSignal` metadata -- frontend consumes that contract through `job-tracker-ui/src/jobWorkflowSignals.ts` -- `JobTable`, `DashboardView`, and `RemindersView` route from the same source of truth into the existing `/jobs?open=...&tab=...` workspace entry model - -This is the main coherence pattern future slices should preserve: if a new daily-loop surface needs a next action, it should consume `workflowSignal`, not invent another heuristic. - -### 2. Explicit saved-package trust in the workspace - -The Tailored CV workspace now makes the saved-package chain obvious: - -- tailored CV, cover letter, application answer, and recruiter message each show save state -- the UI explicitly says saved package material feeds follow-up drafting -- the saved working-material panel shows what later workflow steps can trust and reuse - -This matters because S02 already established package persistence, but S05 made that persistence legible as workflow state rather than hidden implementation detail. - -### 3. Visible Gmail continuity state in the correspondence workspace - -S05 surfaced linked-thread continuity directly in `Correspondence.tsx`. - -The workspace now shows: - -- Gmail connection state -- linked-thread count -- explicit linked-thread refresh action -- last refresh outcome - -That makes the S01 continuity work inspectable in the same workspace where the user reviews correspondence, rather than requiring them to infer freshness from the Gmail import modal alone. - -### 4. Integrated trust-loop regression - -S05 added `job-tracker-ui/src/end-to-end-trust-loop.test.tsx` as the integrated proof for the slice. The test starts from an overview entry point and verifies the assembled path in one place: - -1. open the job from an overview action -2. confirm saved package material is already present -3. confirm linked-thread refresh updates correspondence without re-importing the thread -4. confirm the follow-up draft is grounded in saved package + correspondence context -5. confirm drafting/regeneration does not trigger send behavior - -This is now the best single regression to read when future work risks breaking the milestone’s core loop. - -## Patterns Established - -- **Workflow actions come from normalized workflow signals.** Do not parse `followUpReason` strings or generic `notes` content in overview surfaces. -- **Saved package state is explicit workflow state.** Future slices should continue treating tailored CV / cover letter / application answer / recruiter message as reusable job-scoped material. -- **Continuity status belongs in the main workspace.** If refresh or sync trust matters, expose its state where the user does the work. -- **Integrated proof should start from an overview surface.** Final-loop regressions should validate real entry routing, not isolated component state only. -- **Manual-send boundary must stay explicit.** Draft generation/regeneration and outbound send must remain visibly separate. - -## Verification Run - -All slice-plan command checks passed in this worktree. - -### Backend - -- `~/.gsd/agent/bin/dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsWorkflowSignalsTests` - -### Frontend tests - -- `cd job-tracker-ui && CI=true ./node_modules/.bin/react-scripts test --watch=false --runTestsByPath src/workflow-trust-signals.test.tsx` -- `cd job-tracker-ui && CI=true ./node_modules/.bin/react-scripts test --watch=false --runTestsByPath src/end-to-end-trust-loop.test.tsx` -- `cd job-tracker-ui && CI=true ./node_modules/.bin/react-scripts test --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx src/job-details-generated-drafts.test.tsx src/job-details-followup-drafts.test.tsx src/daily-control-loop.test.tsx` - -### Build - -- `cd job-tracker-ui && CI=true ./node_modules/.bin/react-scripts build` - -## Observability / Diagnostic Surfaces Confirmed - -The slice-plan observability surfaces are in place and useful: - -- `GET /api/jobapplications/reminders` -- `GET /api/jobapplications/{id}/readiness` -- `GET /api/jobapplications/{id}/followup-draft` -- `GET /api/correspondence/{jobId}` -- `POST /api/gmail/refresh-linked-threads` -- `job-tracker-ui/src/jobWorkflowSignals.ts` -- `job-tracker-ui/src/workflow-trust-signals.test.tsx` -- `job-tracker-ui/src/end-to-end-trust-loop.test.tsx` - -Backend tests proved normalized workflow-signal behavior. Frontend tests proved overview routing consistency and integrated trust-loop behavior. A live browser check also confirmed the app shell renders, but the current environment still has a CORS/runtime mismatch on `http://localhost:5202/api/...`, so full live UAT depends on running the backend with the expected CORS behavior. - -## Requirement Impact - -- **R010** is now validated. S05 proved coherent history and action continuity across overview routing, saved package reuse, linked Gmail updates, and follow-up drafting using one shared workflow contract. -- **R008** remains active by design. S05 re-proved the manual-send boundary, but the requirement stays active as an ongoing product constraint rather than a one-time feature box. - -## Decisions / Gotchas Worth Carrying Forward - -- Saved application answers should remain explicit workflow state, not inferred from generic notes. -- Linked-thread refresh state should stay visible in the main correspondence workspace. -- In this CRA frontend, run `react-scripts` from `job-tracker-ui/`; invoking via `npm --prefix ...` from repo root can mis-resolve the app directory and fail looking for a root-level `package.json`. -- Browser UAT still requires a correctly configured backend on port `5202`; otherwise the UI shell loads with CORS failures and empty data surfaces. - -## What The Next Slice / Milestone Should Know - -M001 is now assembled as one coherent single-user loop. Future work should build on the shared `workflowSignal` contract and the integrated trust-loop regression instead of adding new routing heuristics or duplicate readiness logic. If a later slice changes reminders, workspace entry, Gmail continuity, or follow-up drafting, it should update both the focused workflow-signal tests and the integrated trust-loop test together. diff --git a/.gsd/milestones/M001/slices/S05/S05-UAT.md b/.gsd/milestones/M001/slices/S05/S05-UAT.md deleted file mode 100644 index 43cf6e7..0000000 --- a/.gsd/milestones/M001/slices/S05/S05-UAT.md +++ /dev/null @@ -1,246 +0,0 @@ -# S05 UAT — End-to-end trust and workflow polish - -## Goal - -Verify that one real job can be entered from `/jobs`, `/dashboard`, and `/reminders`, then carried through the same trusted workspace loop: - -- saved package material is visible and reusable -- linked Gmail threads stay current without thread re-import -- follow-up drafting is grounded in saved package + correspondence -- no recruiter email is sent unless the human explicitly chooses the send action in a safe environment - -## Safety Guardrails - -1. **Do not click `Send and log email`** unless outbound mail is intentionally routed to a safe sink, stub mailbox, or other non-production target. -2. If safe outbound handling is not confirmed, stop after reviewing the follow-up draft and the manual-send boundary copy. -3. Do not capture or share screenshots containing sensitive recruiter or correspondence content. -4. Use one job that already has all or most of the following: - - a saved tailored CV and/or other saved package material - - at least one imported Gmail thread linked to the job - - a workflow action from the overview surfaces - - follow-up context that should produce a draft - -## Preconditions - -- API is running on the expected backend origin with working CORS for the frontend. -- UI is running and can load real API-backed job data. -- Gmail integration is authenticated for the current user. -- The selected job belongs to the current user and has real recruiter/thread context. -- If the final send step will be tested, outbound mail is pointed at a safe sink/stub. - -## Shared Expected Signals For The Same Job - -Across `/jobs`, `/dashboard`, and `/reminders`, the same job should: - -- show the same general next action -- open the same job workspace -- land on the same tab or equivalent workflow destination -- preserve the same saved package, correspondence, and follow-up state - -Inside the job workspace, expect to see: - -- package save-state chips -- a signal that saved package material feeds follow-up drafting -- linked-thread continuity state in Correspondence -- explicit manual-send boundary copy in Follow up - ---- - -## Test Case 1 — `/jobs` routes into the trusted workspace - -### Steps - -1. Open `/jobs`. -2. Find a job row with a workflow chip, readiness signal, or next-action control. -3. Open that job using the primary workflow action. - -### Expected - -- The dialog/workspace opens for the selected job. -- The opened tab matches the job’s workflow need rather than a generic default. -- The selected company and role match the job row you opened. - ---- - -## Test Case 2 — saved package material is visible and reusable - -### Steps - -1. In the opened job workspace, go to **Tailored CV**. -2. Review the save-state chips for: - - Tailored CV - - Cover letter - - Application answer - - Recruiter message -3. Review the “Saved working material” panel. -4. If the job already has saved package data, confirm those fields show as saved. - -### Expected - -- The page shows save-state chips instead of leaving package trust implicit. -- The workspace explicitly indicates that saved package material feeds follow-up drafting. -- Saved material reflects the job’s current stored state, not just the latest generated draft. -- Resetting or revisiting the tab preserves the saved package state. - -### Edge checks - -- If one package field is unsaved, only that field should look incomplete; generic notes alone should not make the job appear package-ready. -- If the application answer exists, it should come back from saved state rather than disappearing or duplicating. - ---- - -## Test Case 3 — correspondence shows linked-thread continuity in the workspace - -### Steps - -1. Open the **Correspondence** tab for the same job. -2. Confirm the Gmail connection state is visible. -3. Confirm the linked-thread panel/state is visible in the main workspace. -4. Review the linked-thread count. -5. Click **Refresh linked threads**. - -### Expected - -- The workspace shows Gmail connection status without needing the import modal to explain trust. -- If linked Gmail threads already exist, the workspace shows the linked-thread count. -- After refresh, the UI reports whether new messages were imported or whether linked threads were already current. -- The user is not forced through a full thread re-import flow for a thread that is already linked. - -### Edge checks - -- If no new Gmail messages exist, the refresh should say the linked threads are current rather than failing silently. -- If the job has no linked threads, the UI should say so clearly instead of implying a broken refresh. - ---- - -## Test Case 4 — new linked correspondence appears without thread re-import - -### Steps - -1. Use a job whose recruiter thread has changed since the last import, or create that condition safely before the test. -2. With the same job open in **Correspondence**, trigger **Refresh linked threads**. -3. Review the correspondence timeline/list after refresh. - -### Expected - -- The new inbound or user-sent Gmail message appears in the same job’s correspondence. -- The refresh uses the already-linked Gmail thread. -- The user does not need to search for and re-import the whole thread manually. - -### Edge checks - -- Duplicate refreshes should not keep importing the same message repeatedly. -- Imported message order and thread continuity should still look sensible in the correspondence list. - ---- - -## Test Case 5 — grounded follow-up drafting stays separate from send - -### Steps - -1. Open the **Follow up** tab for the same job. -2. Review the follow-up context panel. -3. Confirm it references package/correspondence grounding such as thread subject, last activity, or other grounding signals. -4. Click **Regenerate draft** if needed. -5. Review the manual-send boundary panel. -6. Edit the subject/body if desired. -7. **Stop before clicking `Send and log email`** unless outbound mail is confirmed safe. - -### Expected - -- The draft context clearly reflects saved package material and imported correspondence. -- Regenerating the draft changes/reloads draft content only; it does not send email. -- The manual-send boundary copy clearly states that generation/regeneration never sends recruiter email. -- The only outbound action remains the explicit send button. - -### Edge checks - -- If the recruiter email field is blank, drafting should still work; only the manual send step should be blocked or require completion. -- Draft review/editing should remain possible without side effects in correspondence. - ---- - -## Test Case 6 — `/dashboard` opens the same job with the same semantics - -### Steps - -1. Close the workspace. -2. Open `/dashboard`. -3. Find the same job in an attention, readiness, or reminder card. -4. Open the job from the dashboard action. - -### Expected - -- The same job workspace opens. -- The opened tab/action meaning matches the job’s workflow need. -- Saved package state, linked-thread continuity state, and follow-up context are the same as when the job was opened from `/jobs`. - -### Edge checks - -- The dashboard should not route the same job to a conflicting tab/action compared with `/jobs`. - ---- - -## Test Case 7 — `/reminders` opens the same job with the same semantics - -### Steps - -1. Close the workspace. -2. Open `/reminders`. -3. Find the same job in its reminder grouping. -4. Open the job from the reminder action. - -### Expected - -- The same job workspace opens. -- The same job lands in the same workflow area or equivalent action destination as the other entry points. -- Package state, correspondence continuity, and follow-up trust state remain unchanged. - -### Edge checks - -- Reminder grouping should reflect the same workflow classification seen elsewhere, not a conflicting heuristic. - ---- - -## Test Case 8 — optional safe-sink send verification - -> Run this only if outbound email is confirmed safe. - -### Steps - -1. Confirm the environment is using a stub mailbox, sink, or other non-production target. -2. In **Follow up**, click `Send and log email`. -3. Re-open **Correspondence** and/or refresh the job state. - -### Expected - -- The outbound action occurs only after the explicit click. -- The job history/correspondence reflects the sent follow-up appropriately. -- No autonomous send happened before this explicit action. - -### Edge checks - -- If the send endpoint is intentionally stubbed, the UI should still make the boundary clear and report the stubbed result consistently. - ---- - -## Pass Criteria - -S05 passes UAT when all of the following are true for the same job: - -- `/jobs`, `/dashboard`, and `/reminders` all open the same job workspace coherently. -- Saved package material is visible as reusable workflow state. -- Linked-thread continuity is visible in the correspondence workspace. -- Refreshing linked threads updates correspondence without requiring re-import of an already-linked thread. -- Follow-up drafting is clearly grounded in package + correspondence context. -- Draft generation/regeneration never sends email on its own. -- No recruiter email is sent unless the human explicitly chooses the send action in a safe environment. - -## Failure Clues - -- Different surfaces route the same job to conflicting next actions. -- Generic notes make the job appear package-ready when saved package material is actually missing. -- Linked-thread freshness is hidden, ambiguous, or only discoverable through import-only UI. -- Refreshing linked threads requires a new full-thread import. -- Regenerating a follow-up draft appears coupled to sending. -- Workspace state changes depending on whether the job was opened from jobs, dashboard, or reminders. diff --git a/.gsd/milestones/M001/slices/S05/tasks/T01-PLAN.md b/.gsd/milestones/M001/slices/S05/tasks/T01-PLAN.md deleted file mode 100644 index 4a08f75..0000000 --- a/.gsd/milestones/M001/slices/S05/tasks/T01-PLAN.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -estimated_steps: 4 -estimated_files: 8 -skills_used: - - aspnet-core - - react-best-practices - - test ---- - -# T01: Centralize workflow trust signals across overview and readiness surfaces - -**Slice:** S05 — End-to-end trust and workflow polish -**Milestone:** M001 - -## Description - -Replace the remaining brittle workflow heuristics with one shared trust/action model so the table, dashboard, reminders, and readiness surfaces all describe the same next step for the same job. - -## Steps - -1. Audit the current reminder/readiness/action logic in `JobApplicationsController.cs`, `JobTable.tsx`, `DashboardView.tsx`, and `RemindersView.tsx`, with special attention to `followUpReason` string parsing and the saved application-answer notes-block constraint. -2. Add explicit workflow trust/action fields or normalized route metadata to the backend DTOs and cover that behavior in a focused backend test. -3. Introduce a shared UI helper that consumes the new contract and update the overview surfaces to route from it instead of duplicating local heuristics. -4. Add focused frontend coverage proving that the same job produces the same next action across table, dashboard, and reminders. - -## Must-Haves - -- [ ] The workflow contract distinguishes package-work gaps from follow-up work without treating all `notes` text as generic readiness state. -- [ ] Table, dashboard, and reminders open the shared workspace from one trust/action source of truth. -- [ ] Backend and frontend focused tests fail if workflow signal drift reappears. - -## Verification - -- `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsWorkflowSignalsTests` -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/workflow-trust-signals.test.tsx` - -## Observability Impact - -- Signals added/changed: normalized workflow trust/action fields and readiness-derived routing metadata used by overview surfaces. -- How a future agent inspects this: read `JobTrackerApi/Controllers/JobApplicationsController.cs` and `job-tracker-ui/src/jobWorkflowSignals.ts`, then run the focused backend/frontend tests. -- Failure state exposed: mismatched overview actions, package-readiness drift, or fallback to string parsing becomes visible as deterministic test failures instead of silent UI inconsistency. - -## Inputs - -- `JobTrackerApi/Controllers/JobApplicationsController.cs` — current reminders/readiness logic and DTO shaping. -- `job-tracker-ui/src/components/JobTable.tsx` — current next-action and readiness heuristics. -- `job-tracker-ui/src/components/DashboardView.tsx` — current dashboard reminder routing. -- `job-tracker-ui/src/components/RemindersView.tsx` — current reminders grouping and routing. -- `job-tracker-ui/src/types.ts` — current DTO shapes available to the UI. - -## Expected Output - -- `JobTrackerApi/Controllers/JobApplicationsController.cs` — explicit workflow trust/action fields or normalized route metadata. -- `JobTrackerApi.Tests/JobApplicationsWorkflowSignalsTests.cs` — backend proof for the normalized workflow contract. -- `job-tracker-ui/src/types.ts` — updated UI contract for the new trust/action fields. -- `job-tracker-ui/src/jobWorkflowSignals.ts` — shared workflow helper used by overview surfaces. -- `job-tracker-ui/src/components/JobTable.tsx` — table actions driven from the shared trust/action model. -- `job-tracker-ui/src/components/DashboardView.tsx` — dashboard attention actions driven from the shared trust/action model. -- `job-tracker-ui/src/components/RemindersView.tsx` — reminders grouping/routing driven from the shared trust/action model. -- `job-tracker-ui/src/workflow-trust-signals.test.tsx` — focused UI proof that overview surfaces stay aligned. diff --git a/.gsd/milestones/M001/slices/S05/tasks/T01-SUMMARY.md b/.gsd/milestones/M001/slices/S05/tasks/T01-SUMMARY.md deleted file mode 100644 index 3d042b3..0000000 --- a/.gsd/milestones/M001/slices/S05/tasks/T01-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T01 -parent: S05 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.778Z -blocker_discovered: false ---- - -# T01: Centralize workflow trust signals across overview and readiness surfaces - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S05/tasks/T01-VERIFY.json b/.gsd/milestones/M001/slices/S05/tasks/T01-VERIFY.json deleted file mode 100644 index 9dcce78..0000000 --- a/.gsd/milestones/M001/slices/S05/tasks/T01-VERIFY.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "schemaVersion": 1, - "taskId": "T01", - "unitId": "M001/S05/T01", - "timestamp": 1774358881931, - "passed": true, - "discoverySource": "none", - "checks": [] -} diff --git a/.gsd/milestones/M001/slices/S05/tasks/T02-PLAN.md b/.gsd/milestones/M001/slices/S05/tasks/T02-PLAN.md deleted file mode 100644 index aa6113b..0000000 --- a/.gsd/milestones/M001/slices/S05/tasks/T02-PLAN.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -estimated_steps: 4 -estimated_files: 5 -skills_used: - - react-best-practices - - agent-browser - - test ---- - -# T02: Add integrated trust-loop proof and workspace polish - -**Slice:** S05 — End-to-end trust and workflow polish -**Milestone:** M001 - -## Description - -Compose the milestone’s existing package, Gmail, and follow-up flows into one integrated UI proof path, then make the smallest workspace polish changes needed so that path feels trustworthy and keeps outbound send explicitly manual. - -## Steps - -1. Build a focused integrated React test that starts from an overview entry path and exercises package reuse, linked-thread continuity, and grounded follow-up drafting inside the shared workspace. -2. Update `JobDetailsDialog.tsx` and `Correspondence.tsx` only where the integrated proof exposes unclear state, missing trust copy, or continuity ambiguity. -3. Re-run the focused S01-S04 regressions to confirm the integrated path did not break the narrower package, Gmail, follow-up, or daily-loop contracts. -4. Write a live-safe UAT runbook that tells a human how to verify the full loop against real services without triggering accidental recruiter email. - -## Must-Haves - -- [ ] A single integrated UI regression proves overview → workspace → saved package → linked Gmail thread refresh → grounded follow-up draft. -- [ ] The workspace keeps the manual-send boundary explicit and does not couple draft generation to `send-followup`. -- [ ] A human can run the final live-UAT flow safely using the documented guardrails. - -## Verification - -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/end-to-end-trust-loop.test.tsx` -- `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx src/job-details-generated-drafts.test.tsx src/job-details-followup-drafts.test.tsx src/daily-control-loop.test.tsx` -- `CI=true npm --prefix job-tracker-ui run build` - -## Observability Impact - -- Signals added/changed: clearer workspace trust state around saved package reuse, linked-thread refresh outcomes, and follow-up draft/manual-send separation. -- How a future agent inspects this: run `src/end-to-end-trust-loop.test.tsx`, inspect `JobDetailsDialog.tsx` and `Correspondence.tsx`, and follow `.gsd/milestones/M001/slices/S05/S05-UAT.md` for live verification. -- Failure state exposed: broken loop composition, stale correspondence continuity, or accidental send coupling surfaces in one integrated test instead of requiring four separate slice tests to infer the regression. - -## Inputs - -- `job-tracker-ui/src/jobWorkflowSignals.ts` — shared workflow action helper from T01. -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — package workspace, follow-up drafting, and readiness surfaces. -- `job-tracker-ui/src/components/Correspondence.tsx` — Gmail import and linked-thread continuity workspace. -- `job-tracker-ui/src/daily-control-loop.test.tsx` — current routed overview proof from S04. -- `job-tracker-ui/src/correspondence-gmail-import.test.tsx` — current Gmail continuity proof from S01. -- `job-tracker-ui/src/job-details-generated-drafts.test.tsx` — current package save/reuse proof from S02. -- `job-tracker-ui/src/job-details-followup-drafts.test.tsx` — current follow-up grounding/manual-send proof from S03. - -## Expected Output - -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` — polished workspace trust state for package reuse and follow-up/manual-send separation. -- `job-tracker-ui/src/components/Correspondence.tsx` — polished linked-thread continuity state used by the integrated loop. -- `job-tracker-ui/src/end-to-end-trust-loop.test.tsx` — integrated UI proof for the full trust loop. -- `job-tracker-ui/src/daily-control-loop.test.tsx` — updated overview proof if the shared trust-loop entry semantics change. -- `.gsd/milestones/M001/slices/S05/S05-UAT.md` — live-safe end-to-end verification runbook. diff --git a/.gsd/milestones/M001/slices/S05/tasks/T02-SUMMARY.md b/.gsd/milestones/M001/slices/S05/tasks/T02-SUMMARY.md deleted file mode 100644 index 895fb33..0000000 --- a/.gsd/milestones/M001/slices/S05/tasks/T02-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T02 -parent: S05 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.778Z -blocker_discovered: false ---- - -# T02: Add integrated trust-loop proof and workspace polish - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S05/tasks/T02-VERIFY.json b/.gsd/milestones/M001/slices/S05/tasks/T02-VERIFY.json deleted file mode 100644 index aac5d44..0000000 --- a/.gsd/milestones/M001/slices/S05/tasks/T02-VERIFY.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "schemaVersion": 1, - "taskId": "T02", - "unitId": "M001/S05/T02", - "timestamp": 1774359406536, - "passed": false, - "discoverySource": "none", - "checks": [], - "retryAttempt": 1, - "maxRetries": 2, - "runtimeErrors": [ - { - "source": "bg-shell", - "severity": "crash", - "message": "[jobtracker-api] exitCode=127", - "blocking": true - }, - { - "source": "bg-shell", - "severity": "crash", - "message": "[jobtracker-api] exitCode=134 errors: --- End of inner exception stack trace ---; --- End of inner exception stack trace ---", - "blocking": true - } - ] -} diff --git a/.gsd/milestones/M001/slices/S06/S06-PLAN.md b/.gsd/milestones/M001/slices/S06/S06-PLAN.md deleted file mode 100644 index 6d1f6f1..0000000 --- a/.gsd/milestones/M001/slices/S06/S06-PLAN.md +++ /dev/null @@ -1,15 +0,0 @@ -# S06: Live environment stabilization and integrated acceptance rerun - -**Goal:** Live environment is repeatably startable and preflighted, seeded with acceptance-ready data, and the integrated daily loop is re-verified with a recorded artifact proving the manual-send boundary and individual-first workflow. -**Demo:** After this: TBD - -## Tasks -- [x] **T01: Validated and recorded the live API/auth preflight gate, including README runbook guidance and negative-path shell coverage.** — - - Files: scripts/s06-preflight.sh, README.md, job-tracker-ui/src/api.ts, JobTrackerApi/appsettings.Development.json - - Verify: bash scripts/s06-preflight.sh -- [x] **T02: Seeded acceptance-ready job data through the live API with deterministic rerun-safe ids and readiness output.** — - - Files: scripts/s06-acceptance-data.sh, scripts/s06-preflight.sh, README.md - - Verify: bash scripts/s06-acceptance-data.sh -- [x] **T03: Added a repeatable live acceptance runner and recorded real S06 browser evidence for the manual-send boundary and daily loop.** — - - Files: scripts/s06-acceptance-run.sh, docs/s06-acceptance-run.md, scripts/s06-preflight.sh, scripts/s06-acceptance-data.sh, job-tracker-ui/src/end-to-end-trust-loop.test.tsx - - Verify: bash scripts/s06-acceptance-run.sh && test -s docs/s06-acceptance-run.md diff --git a/.gsd/milestones/M001/slices/S06/S06-RESEARCH.md b/.gsd/milestones/M001/slices/S06/S06-RESEARCH.md deleted file mode 100644 index a9bc7fc..0000000 --- a/.gsd/milestones/M001/slices/S06/S06-RESEARCH.md +++ /dev/null @@ -1,261 +0,0 @@ -# S06 Research — Live environment stabilization and integrated acceptance rerun - -## Summary - -S06 is primarily an environment-proof and acceptance-artifact slice, not a new feature slice. The product loop from S01–S05 is already implemented and mostly works against the local seeded dataset once the live backend is actually running and the browser is authenticated. The real blockers for a true live rerun are environmental: - -1. the frontend hard-calls `http://localhost:5202/api` in localhost dev (`job-tracker-ui/src/api.ts`), so the UI immediately fails with `ERR_CONNECTION_REFUSED` if the API is not already up -2. auth is required in the local dev environment (`JobTrackerApi/appsettings.Development.json`), so a live rerun also needs a valid token/session before `/jobs`, `/dashboard`, and `/reminders` are usable -3. Gmail live continuity cannot be fully re-checked in this environment because Gmail OAuth is only partially configured: `Auth:GoogleClientId` exists, but `Google:GmailClientSecret` / redirect config are absent, so `/api/gmail/status` reports `connected:false` and `/api/admin/system` reports `gmailConfigured:false` -4. the seeded local dataset is minimal (1 job / 1 company) and does not exercise the full S05 trust loop naturally: the sample job opens from reminders and the workspace/follow-up flow works, but the dashboard does not currently expose a useful job-level action card, and correspondence only has one linked external thread message plus one locally logged follow-up - -This means S06 should likely split into: **environment bring-up/diagnostics**, then **seed/data conditioning for acceptance**, then **real browser rerun + recorded artifact**. - -## Active Requirements To Target - -### R008 — manual-send boundary stays explicit -S06 must re-prove this in the live browser, especially because Follow-up draft already renders a real `Send and log email` button in `JobDetailsDialog` while generation remains separate. - -### R009 — individual-first daily loop -S06 must prove the same job can be worked from overview surfaces without needing admin-only or multi-record complexity. The current local dataset is individual-friendly but too thin to prove all overview semantics. - -## Skills Discovered - -- Existing installed skills used for approach guidance: - - `debug-like-expert` — informed the evidence-first, no-assumption investigation approach - - `agent-browser` — informed the browser verification workflow -- Newly installed during research: - - `gmail` (`odyssey4me/agent-skills@gmail`) -- Tried but not usable for this slice: - - `jezweb/claude-skills@google-workspace` search result did not expose a directly installable `google-workspace` skill name from that repo - -## What Exists Now - -### Runtime and config surfaces - -- `job-tracker-ui/src/api.ts` - - On `localhost`, defaults API traffic to `http://localhost:5202/api` - - In production/non-localhost, defaults to `/api` - - No CRA dev proxy file exists; dev depends on direct cross-origin API access -- `JobTrackerApi/Program.cs` - - CORS policy defaults to `http://localhost:3000` - - `app.UseCors("AllowReact")` is already wired - - auth + migrations + admin seeding happen at startup -- `JobTrackerApi/appsettings.Development.json` - - `Auth:Require=true` - - local CORS allowlist includes `http://localhost:3000` - - placeholder local admin + JWT values exist - - `Auth:GoogleClientId` placeholder exists, which makes Google auth look enabled even when Gmail OAuth is not actually fully configured -- `README.md` - - already documents the exact dev topology: UI on `:3000`, API on `:5202`, UI defaulting to `http://localhost:5202/api` -- `JobTrackerApi/Controllers/AdminSystemController.cs` - - best existing operational probe for S06 - - exposes database, auth, Gmail-configured, and AI-health status in one call -- `job-tracker-ui/src/pages/AdminSystemPage.tsx` - - already renders the above status in the UI - -### Auth and entry behavior - -- `JobTrackerApi/Controllers/AuthController.cs` - - `/api/auth/config` truthfully reports `requireAuth`, `googleEnabled`, `localEnabled`, `allowRegistration` - - local login is still standard username/password -- `job-tracker-ui/src/App.tsx` - - fetches `/auth/config` and redirects to `/login` when auth is required and no token exists -- `job-tracker-ui/src/pages/LoginPage.tsx` - - uses `/auth/login` or `/auth/register` - - `allowRegistration` is only exposed if backend allows it -- `job-tracker-ui/src/auth.ts` - - token key is `authToken` - -### Daily-loop/workspace surfaces already in place - -- `job-tracker-ui/src/components/JobTable.tsx` -- `job-tracker-ui/src/components/DashboardView.tsx` -- `job-tracker-ui/src/components/RemindersView.tsx` -- `job-tracker-ui/src/components/JobDetailsDialog.tsx` -- `job-tracker-ui/src/components/Correspondence.tsx` -- `job-tracker-ui/src/end-to-end-trust-loop.test.tsx` - -The S05 contract is real: the workspace tabs, follow-up generation, reminders open path, and correspondence/follow-up tabs are all present in the running UI. - -## Evidence From Live Investigation - -### 1. The original frontend blockage is real and immediate - -When the browser opened `http://localhost:3000/login` before the API was up, the first failing request was: - -- `GET http://localhost:5202/api/auth/config → net::ERR_CONNECTION_REFUSED` - -This matches the carried-forward gotcha exactly: if port `5202` is not serving, the shell UI loads but the real app loop is blocked. - -### 2. Backend works once started correctly - -Running from the API project directory with the explicit dotnet path succeeds: - -- `ASPNETCORE_ENVIRONMENT=Development ASPNETCORE_URLS=http://127.0.0.1:5202 /home/pi/.gsd/agent/bin/dotnet run --no-launch-profile` - -Observed runtime facts: - -- API listens on `http://127.0.0.1:5202` -- DB migrations are already up to date -- local SQLite DB is present and readable -- the prior bg-shell failure was a process-launch/cwd/runtimeconfig issue, not an application-code crash - -Important planning note: the generic `bg_shell start` cwd is not the user-mandated worktree by default in this harness. Absolute paths or an explicit shell `cd` are safer when scripting S06 verification. - -### 3. Admin/system status gives the clearest stabilization checklist - -`GET /api/admin/system` returned: - -- database: configured + connectable -- auth: required, JWT key present -- Google login configured: true -- Gmail configured: false -- AI healthy: true -- storage has only 1 company / 1 job - -This is the most useful pre-UAT gate to run before any browser rerun. - -### 4. Live data is too thin for a full acceptance rerun - -Current seeded API state: - -- `GET /api/jobapplications` returns exactly 1 job: `Acme Browser QA / Backend Developer` -- `workflowSignal.actionKey = review-readiness` -- `needsFollowUp = false` -- `GET /api/jobapplications/reminders` still returns the same job under reminders, but only as an “Other reminders” case -- dashboard renders analytics and aggregate cards, but with this dataset it does **not** expose the richer job-level action surface that S04/S05 were meant to re-check - -This means the current local DB is enough to prove the app runs, but not enough to strongly prove the milestone’s intended `/jobs` → workspace → Gmail continuity → follow-up → dashboard/reminders loop. - -### 5. Follow-up drafting and manual-send boundary are live - -In the running browser, opening the sample job from `/reminders` succeeded and the job workspace rendered. - -On the **Follow-up draft** tab, live UI showed: - -- generated subject/body from `/api/jobapplications/1/followup-draft` -- recruiter recipient prefilled (`maria@acme.test`) -- explicit `Send and log email` button -- separate generation/loading behavior from the send action - -This supports R008: drafting is live, but send is still an explicit separate action. - -### 6. Correspondence continuity UI is only partially exercised locally - -`Correspondence.tsx` does contain the linked-thread continuity panel and manual refresh flow, but the local sample data does not currently expose it meaningfully: - -- `GET /api/correspondence/1` has one imported-style external thread message and one locally logged sent-style message -- `GET /api/gmail/status` returns `connected:false` -- because Gmail is disconnected and the second message lacks external thread metadata, the real linked-thread refresh loop cannot be demonstrated live here - -So S06 cannot honestly claim Gmail continuity rerun in this environment until Gmail config + account linking are present. - -## Implementation Landscape - -### Natural task seams - -#### Seam 1 — environment bring-up and diagnostics -Focus files/surfaces: -- `JobTrackerApi/Program.cs` -- `JobTrackerApi/appsettings.Development.json` -- `job-tracker-ui/src/api.ts` -- `README.md` -- `JobTrackerApi/Controllers/AdminSystemController.cs` -- `job-tracker-ui/src/pages/AdminSystemPage.tsx` - -Goal: -- make the live local topology easy to start and verify before browser UAT begins -- likely produce or tighten a repeatable runbook/checklist rather than large code changes - -#### Seam 2 — acceptance seed/data conditioning -Focus surfaces: -- existing API endpoints / local DB seed path -- possibly task-local scripts or documented setup steps - -Goal: -- ensure at least one job truly exercises: - - overview action from `/jobs` - - meaningful reminder/dashboard action - - saved package state - - linked correspondence state - - follow-up draft grounding - -This is the riskiest seam because the current one-job dataset is operational but not acceptance-rich. - -#### Seam 3 — browser rerun and artifact capture -Focus surfaces: -- browser verification itself -- `.gsd` UAT/summary artifact output for S06 -- likely downstream dependency for S07 - -Goal: -- record what was actually exercised live -- distinguish clearly between: - - fully live checks - - blocked checks - - mocked/not-possible checks - -## Recommendation - -1. **Build a hard preflight gate first.** Before any browser rerun, check: - - API reachable on `:5202` - - `/api/auth/config` reachable - - admin/system status healthy for DB + AI - - Gmail-configured status truthfully known -2. **Do not start with browser fixes.** The main current failure mode is not React routing; it is environment readiness. -3. **Treat Gmail as an explicit acceptance branch.** If Gmail remains unconfigured, S06 should record that the full Gmail continuity rerun is blocked and either: - - add a safe local config/setup task, or - - scope S06 to environment stabilization plus non-Gmail integrated rerun, leaving Gmail closure to a follow-up task/slice -4. **Augment the local acceptance dataset before claiming success.** The present single sample job does not naturally prove dashboard/reminders/job-table coherence strongly enough. -5. **Use the S05 integrated regression as the contract oracle.** If live behavior diverges from `job-tracker-ui/src/end-to-end-trust-loop.test.tsx`, investigate the environment/data first before changing UI logic. - -## Risks / Constraints - -- **Gmail is the biggest live blocker.** `googleConfigured=true` does not mean Gmail import is usable; `gmailConfigured=false` in admin/system is the truer signal for S06. -- **Auth can block all browser checks.** Since dev auth is required, any UAT runbook must include token/login setup. -- **The dataset currently biases toward a low-urgency readiness case.** This can make dashboard/reminders look less actionable than they were designed to be. -- **Do not infer success from shell render.** The login page and app shell can render even while API traffic is broken. - -## Verification Plan - -### Preflight - -- Start API from `JobTrackerApi/` and verify `GET /api/auth/config` -- Verify `GET /api/admin/system` as an admin token/session -- Confirm these fields before browser UAT: - - `database.canConnect = true` - - `ai.healthy = true` - - `auth.required = true/false` understood - - `auth.gmailConfigured = true` if Gmail continuity is in scope - -### Browser rerun - -For one chosen acceptance job: - -1. `/jobs` opens the correct workspace -2. `/reminders` opens the same job/workspace semantics -3. `/dashboard` exposes and opens the same job/workspace semantics -4. **Tailored CV** shows saved package state clearly -5. **Correspondence** shows linked-thread continuity state -6. **Follow-up draft** shows grounded context and explicit manual-send boundary -7. Do **not** click send unless outbound mail is intentionally pointed at a safe sink - -### Contract spot checks - -Useful endpoints to compare against live UI: - -- `GET /api/jobapplications` -- `GET /api/jobapplications/reminders` -- `GET /api/jobapplications/{id}` -- `GET /api/correspondence/{jobId}` -- `GET /api/jobapplications/{id}/followup-draft` -- `GET /api/gmail/status` -- `GET /api/admin/system` - -## Planner Notes - -- This slice is not mainly a coding problem unless the planner finds a missing preflight/diagnostic surface. It is mostly an **environment + proof** problem. -- The first executable task should probably be a stabilization/proof task, not a product-feature task. -- If the planner wants a high-confidence S06 outcome, it should require a decision on whether Gmail live acceptance is actually achievable in this environment before promising full milestone rerun coverage. -- S07 depends on S06 producing truthful acceptance evidence. If S06 cannot execute Gmail live, that limitation must be recorded explicitly rather than papered over. diff --git a/.gsd/milestones/M001/slices/S06/S06-SUMMARY.md b/.gsd/milestones/M001/slices/S06/S06-SUMMARY.md deleted file mode 100644 index 48ad0f3..0000000 --- a/.gsd/milestones/M001/slices/S06/S06-SUMMARY.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -id: S06 -parent: M001 -milestone: M001 -provides: - - A repeatable localhost preflight + seed + acceptance-run workflow for the M001 trust loop. - - A deterministic live acceptance fixture (`S06 Acceptance Labs` / `S06 Acceptance Backend Engineer`) that surfaces saved package state, recruiter-thread correspondence, follow-up readiness, and dashboard/reminder visibility. - - A recorded live acceptance artifact that downstream closure/UAT work can reference instead of reconstructing the environment from scratch. - - Fresh live proof that the manual-send boundary still holds in the real stack. -requires: - - slice: S05 - provides: The shared workflow-signal contract, integrated trust-loop regression, and manual-send-boundary behavior that S06 re-verified in the real environment. -affects: - - S07 -key_files: - - scripts/s06-preflight.sh - - scripts/s06-acceptance-data.sh - - scripts/s06-acceptance-data.test.sh - - scripts/s06-acceptance-run.sh - - docs/s06-acceptance-run.md - - .gsd/DECISIONS.md - - .gsd/KNOWLEDGE.md - - .gsd/PROJECT.md -key_decisions: - - D013: seed the acceptance fixture through the live API contract with deterministic identifiers so reruns are idempotent and prove real code paths. - - D014: allow the acceptance runner to mint a localhost-only admin JWT from checked-in dev JWT settings plus the local SQLite admin user when `AUTH_TOKEN` is absent. - - D015: treat `/api/auth/config` reachability plus an auth-limited `/api/admin/system` probe as a guided partial-pass, and never echo bearer tokens in preflight output. -patterns_established: - - Use a preflight gate before browser UAT so backend/CORS/auth blockers fail fast with readable guidance instead of surfacing later as ambiguous frontend runtime errors. - - Seed live acceptance fixtures through the same authenticated HTTP endpoints the UI uses, with deterministic company/title/thread/message identifiers, so reruns prove the real contract and stay idempotent. - - Persist a single acceptance-run artifact (`docs/s06-acceptance-run.md`) that refreshes shell evidence without destroying the guided browser-observation section, so later slices can build on one stable handoff document. - - Record manual-send-boundary evidence as both UI observation and network evidence; for this slice, drafting may call `GET .../followup-draft` but must not trigger `POST .../send-followup` without an explicit human action. -observability_surfaces: - - `scripts/s06-preflight.sh` console output for auth/db/gmailConfigured/ai readiness signals and clear failure guidance. - - `scripts/s06-acceptance-data.sh` seed summary output (`seed.result`, job/company ids, workflow action, readiness level, reminder state). - - `docs/s06-acceptance-run.md` plus `docs/artifacts/s06-acceptance/logs/*` for shell-step evidence and blocker guidance. - - Recorded browser artifacts referenced from the acceptance doc (jobs/workspace, follow-up draft, reminders/dashboard, trace, timeline). -drill_down_paths: - - .gsd/milestones/M001/slices/S06/tasks/T01-SUMMARY.md - - .gsd/milestones/M001/slices/S06/tasks/T02-SUMMARY.md - - .gsd/milestones/M001/slices/S06/tasks/T03-SUMMARY.md -duration: "" -verification_result: passed -completed_at: 2026-03-27T08:29:02.335Z -blocker_discovered: false ---- - -# S06: Live environment stabilization and integrated acceptance rerun - -**Stabilized the live localhost stack with a repeatable preflight + seed + acceptance runner flow and re-proved the `/jobs` → workspace → reminders/dashboard loop with recorded manual-send-boundary evidence.** - -## What Happened - -S06 turned the previously fragile live environment into a repeatable acceptance target instead of a one-off debugging session. The slice added a preflight gate that checks the real API contract before UI work starts, an idempotent live-data seed that creates or refreshes a deterministic acceptance fixture through the same HTTP endpoints the app uses in normal operation, and a single acceptance runner that ties preflight, seeding, the integrated trust-loop regression, and the live evidence document together. The resulting live run now proves that the seeded job appears on `/jobs`, opens the real workspace with saved Tailored CV and package state, shows deterministic recruiter-thread correspondence, appears on `/reminders` with the expected `Follow up` / `Waiting 14d` signals, and contributes to `/dashboard` analytics without frontend runtime failures. The slice also re-proved the manual-send boundary in the actual stack: opening or regenerating the follow-up draft issued `GET /api/jobapplications/3/followup-draft` but no `POST /api/jobapplications/3/send-followup`, so drafting stayed assistive and did not cross into autonomous sending. The main remaining live gap is Gmail-connected continuity: the seeded correspondence is visible, but this localhost run did not have a connected Gmail session and therefore did not execute a linked-thread refresh request. That gap is now explicit in the acceptance artifact instead of being mistaken for a fully proven live Gmail refresh. - -## Verification - -Verified the assembled slice in the live worktree with the real backend and frontend running on the expected localhost origins. Commands run: `bash scripts/s06-preflight.sh` (exit 0, expected auth-limited partial-pass when `/api/admin/system` requires a token), `TEST_AUTH_TOKEN="$TOKEN" AUTH_TOKEN="$TOKEN" bash scripts/s06-acceptance-data.test.sh` (exit 0; missing-token, bad-token, and double-rerun cases all passed), `AUTH_TOKEN="$TOKEN" bash scripts/s06-acceptance-data.sh` (exit 0; `seed.result=success`, stable company/job fixture, `seed.workflow.action=follow-up`, `seed.readiness.level=Ready`, `seed.reminders=Waiting 14d`), and `bash scripts/s06-acceptance-run.sh && test -s docs/s06-acceptance-run.md` (exit 0). The recorded live browser evidence in `docs/s06-acceptance-run.md` confirms `/jobs`, workspace, `/reminders`, and `/dashboard` behavior plus the manual-send boundary and a clean dashboard reload with no console errors or failed requests. - -## Requirements Advanced - -- R008 — S06 re-proved the manual-send boundary in the live stack: follow-up drafting remained assistive, and the recorded network evidence showed no send endpoint call during draft review/regeneration. -- R009 — S06 re-checked the individual-first loop in the real environment by proving one seeded job behaves coherently across `/jobs`, the per-job workspace, `/reminders`, and `/dashboard`. - -## Requirements Validated - -None. - -## New Requirements Surfaced - -None. - -## Requirements Invalidated or Re-scoped - -None. - -## Deviations - -Added two bounded implementation choices beyond the original plan to keep reruns repeatable in this real environment: a localhost-only JWT fallback inside the acceptance runner because the checked-in dev password no longer authenticates against the current SQLite snapshot, and an explicit preflight partial-pass path for the auth-limited `/api/admin/system` case so browser/UAT work is blocked only by true environment failures. Gmail continuity was recorded as not configured/not refreshed in this run rather than being overstated as a proven live success. - -## Known Limitations - -This slice does not prove a real Gmail-connected linked-thread refresh in the local environment. The seeded recruiter correspondence is present and the workspace loop is otherwise live, but the browser run did not expose connected Gmail state or issue `POST /api/gmail/refresh-linked-threads`. The acceptance data fixture is intentionally deterministic and single-user; it is suitable for localhost reruns, not for multi-user or production seeding. - -## Follow-ups - -S07 should turn this rerunnable acceptance flow into the final daily-loop UAT closure artifact, ideally in an environment where Gmail is actually connected so linked-thread refresh can be observed live. If the Gmail account remains unavailable locally, S07 should keep the limitation explicit rather than weakening the trust claim. If future slices rely on the S06 fixture, preserve the deterministic identifiers and rerun-safe update behavior instead of adding duplicate seed records. - -## Files Created/Modified - -- `scripts/s06-preflight.sh` — Provides the live API/auth/CORS preflight gate with readable failure guidance and an auth-limited partial-pass path. -- `scripts/s06-acceptance-data.sh` — Seeds or refreshes the deterministic acceptance fixture through the live API and prints readiness/reminder output. -- `scripts/s06-acceptance-data.test.sh` — Exercises missing-token, bad-token, and idempotent rerun cases for the acceptance seeding flow. -- `scripts/s06-acceptance-run.sh` — Orchestrates preflight, seeding, the integrated trust-loop regression, log capture, and the acceptance document refresh. -- `docs/s06-acceptance-run.md` — Records the live S06 acceptance rerun, browser observations, manual-send-boundary evidence, and the remaining Gmail continuity gap. -- `.gsd/DECISIONS.md` — Captured S06 environment decisions, including live-API seeding, local JWT fallback, and preflight auth handling. -- `.gsd/KNOWLEDGE.md` — Captured the auth-limited preflight behavior and the local admin/JWT rerun pattern for future agents. -- `.gsd/PROJECT.md` — Updated project state to reflect S06 completion and the remaining S07 closure focus. diff --git a/.gsd/milestones/M001/slices/S06/S06-UAT.md b/.gsd/milestones/M001/slices/S06/S06-UAT.md deleted file mode 100644 index 52c9fa3..0000000 --- a/.gsd/milestones/M001/slices/S06/S06-UAT.md +++ /dev/null @@ -1,209 +0,0 @@ -# S06: Live environment stabilization and integrated acceptance rerun — UAT - -**Milestone:** M001 -**Written:** 2026-03-27T08:29:02.335Z - -# S06 UAT — Live environment stabilization and integrated acceptance rerun - -## Goal - -Prove that the real localhost environment can be started, preflighted, seeded with acceptance-ready data, and used to rerun the integrated daily loop for the deterministic acceptance fixture without crossing the manual-send boundary. - -The concrete fixture for this slice is: - -- Company: `S06 Acceptance Labs` -- Job: `S06 Acceptance Backend Engineer` -- Expected workflow state: `Follow up`, `Ready`, `Waiting 14d` - -## Preconditions - -1. Backend is running at `http://localhost:5202` and frontend is running at `http://localhost:3000`. -2. The worktree contains the S06 scripts and `docs/s06-acceptance-run.md`. -3. If running the seed script directly, a valid bearer token is available in `AUTH_TOKEN`. -4. If running only `bash scripts/s06-acceptance-run.sh`, the default localhost dev JWT fallback is allowed to mint a local token from the checked-in dev JWT settings plus the local SQLite admin user. -5. Do **not** click `Send And Log Email` unless outbound mail is intentionally pointed at a safe sink. - ---- - -## Test Case 1 — Preflight catches the real environment state before browser work - -### Steps - -1. Run `bash scripts/s06-preflight.sh`. -2. Review the printed readiness lines. - -### Expected - -- The script prints `Preflight target: http://localhost:5202/api`. -- The script prints the required origin pair `UI http://localhost:3000 -> API http://localhost:5202/api`. -- The script reports `auth.requireAuth=true` and the auth config surface is reachable. -- If `/api/admin/system` is not called with an admin token, the script still exits successfully with the guided partial-pass message instead of failing ambiguously. - -### Edge checks - -- If the API is down or `API_BASE` is wrong, the script exits non-zero with a clear start-the-API hint. -- If the API returns malformed JSON, the script prints the raw body and fails. - ---- - -## Test Case 2 — Acceptance seeding produces the deterministic live fixture - -### Steps - -1. Export a valid token to `AUTH_TOKEN`. -2. Run `bash scripts/s06-acceptance-data.sh`. -3. Review the seed summary output. -4. Run the script a second time. - -### Expected - -- The script prints `seed.result=success`. -- The script prints stable `seed.company.id` / `seed.job.id` values and does not create duplicate jobs on rerun. -- The script prints `seed.workflow.action=follow-up`. -- The script prints `seed.readiness.level=Ready`. -- The script prints `seed.reminders=Waiting 14d`. -- The job has saved Tailored CV / package state and recruiter-thread correspondence prepared for later browser verification. - -### Edge checks - -- Running without `AUTH_TOKEN` fails immediately with guidance. -- Running with a bad token fails clearly as an auth issue. -- Re-running the seed does not create duplicate deterministic correspondence for the same external message id. - ---- - -## Test Case 3 — The acceptance runner refreshes the artifact and keeps evidence on disk - -### Steps - -1. Run `bash scripts/s06-acceptance-run.sh`. -2. Confirm `docs/s06-acceptance-run.md` exists and is non-empty. -3. Open the generated markdown and review the shell summary table. - -### Expected - -- The runner reports `acceptance.result=pass`. -- The runner records `Preflight`, `Seed acceptance data`, and `UI trust-loop test` as pass states. -- The markdown contains a current run id, timestamp, auth-token-source category, and log paths under `docs/artifacts/s06-acceptance/logs/`. -- The guided browser section remains present after rerun and is not overwritten by the shell refresh. - -### Edge checks - -- If preflight fails because the backend is unreachable, the runner exits non-zero and records the failure instead of proceeding. -- If Gmail is not configured, the document calls that out as a live limitation rather than pretending Gmail continuity was proven. - ---- - -## Test Case 4 — `/jobs` opens the seeded workspace with saved package state - -### Steps - -1. Open `http://localhost:3000/jobs` in the live app. -2. Locate the row for `S06 Acceptance Labs • S06 Acceptance Backend Engineer`. -3. Confirm the row shows `Follow up`, `CV ready`, and `Waiting` style signals. -4. Open the job workspace from that row. -5. Go to **Tailored CV**. - -### Expected - -- The workspace opens for the seeded job, not a different row. -- The Tailored CV area contains the saved seeded text beginning `Saved acceptance tailored CV highlighting ASP.NET Core delivery...`. -- The saved package state is visible in the real workspace rather than only in test data or logs. - -### Edge checks - -- Re-opening the same job should show the same saved package state; it should not disappear or duplicate on revisit. - ---- - -## Test Case 5 — Follow-up drafting stays manual and does not auto-send - -### Steps - -1. From the same seeded job workspace, open **Follow up**. -2. Wait for the draft to load or regenerate it if needed. -3. Review the available actions. -4. Inspect the network log if available. -5. Stop **before** clicking `Send And Log Email`. - -### Expected - -- The follow-up draft loads successfully from the live backend. -- The UI shows separate `Copy Draft` and `Send And Log Email` actions. -- Draft loading/regeneration calls `GET /api/jobapplications/{id}/followup-draft`. -- No `POST /api/jobapplications/{id}/send-followup` request occurs during draft review/regeneration alone. -- The manual-send boundary remains explicit and intact. - -### Edge checks - -- Editing or regenerating the draft should not add a sent item to correspondence by itself. -- If recruiter contact data is incomplete, drafting may still work while send remains the explicit guarded action. - ---- - -## Test Case 6 — `/reminders` and `/dashboard` reflect the same seeded job coherently - -### Steps - -1. Open `http://localhost:3000/reminders`. -2. Find the seeded job under the follow-up grouping. -3. Confirm the job shows `Follow up`, `Waiting 14d`, and `Follow-up: 10/03/2026`. -4. Open `http://localhost:3000/dashboard`. -5. Confirm the dashboard analytics include the seeded job/company state. -6. Reload `/dashboard` and review browser diagnostics. - -### Expected - -- `/reminders` shows the same seeded job that was opened from `/jobs`. -- `/dashboard` includes `S06 Acceptance Labs` in the activity/company view. -- The dashboard reload completes without console errors or failed network requests. -- The app behaves as one coherent single-user loop across all three surfaces. - -### Edge checks - -- If the job is missing from `/reminders`, the seed dates may no longer be beyond the active follow-up threshold and should be rechecked. -- If `/dashboard` loads but records failed requests, treat the slice as not stabilized. - ---- - -## Test Case 7 — Gmail continuity status is reported honestly - -### Steps - -1. In the seeded job workspace, open **Correspondence**. -2. Confirm the deterministic recruiter-thread message is present. -3. Check whether the workspace exposes connected Gmail state and whether any linked-thread refresh request runs. -4. Compare the observation with `docs/s06-acceptance-run.md`. - -### Expected - -- The recruiter-thread message seeded by S06 is visible. -- If Gmail is connected in the environment, a linked-thread refresh can be observed and reported. -- If Gmail is **not** connected, the acceptance artifact explicitly records that Gmail continuity was not proven live in this run. - -### Edge checks - -- Do not claim success for Gmail continuity merely because seeded correspondence is present. -- Absence of `POST /api/gmail/refresh-linked-threads` in the live run means the slice only proved seeded correspondence visibility, not live Gmail refresh. - ---- - -## Pass Criteria - -S06 passes when all of the following are true: - -- The localhost stack is startable on the expected UI/API origins. -- Preflight provides a readable go/no-go result instead of failing with opaque CORS/runtime symptoms. -- The deterministic acceptance fixture can be seeded repeatably without duplicate drift. -- The seeded job appears coherently across `/jobs`, the workspace, `/reminders`, and `/dashboard`. -- Follow-up drafting remains manual and does not auto-send. -- `docs/s06-acceptance-run.md` contains current shell evidence and honest browser observations, including any remaining Gmail continuity limitation. - -## Failure Clues - -- `scripts/s06-preflight.sh` fails because the API is unreachable or returns malformed JSON. -- Seeding creates duplicate fixture data or no longer lands in `follow-up` / `Waiting 14d` state. -- `/jobs`, workspace, `/reminders`, and `/dashboard` disagree about the seeded job’s next action. -- Opening or regenerating a follow-up draft triggers a send request. -- The acceptance artifact hides Gmail configuration limitations instead of recording them. -- Dashboard reload still produces console errors or failed requests. diff --git a/.gsd/milestones/M001/slices/S06/tasks/T01-PLAN.md b/.gsd/milestones/M001/slices/S06/tasks/T01-PLAN.md deleted file mode 100644 index ae03284..0000000 --- a/.gsd/milestones/M001/slices/S06/tasks/T01-PLAN.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -estimated_steps: 12 -estimated_files: 4 -skills_used: [] ---- - -# T01: Add preflight gate for live API/auth readiness - -Build a repeatable preflight script and doc so environment blockers are caught before browser UAT. -- Why: avoid the ERR_CONNECTION_REFUSED/CORS/auth mismatch that currently blocks the UI. -- Steps: - 1) Create `scripts/s06-preflight.sh` (bash, executable) that assumes backend already started; probes `/api/auth/config` and `/api/admin/system` on `http://localhost:5202/api`, printing database/auth/gmailConfigured/ai status and failing fast on unreachable endpoints. - 2) Ensure script respects `API_BASE` env override and uses `curl -f` with readable errors; no secrets logged. - 3) Add a short runbook snippet to `README.md` showing backend start command from `JobTrackerApi/` and how to run the preflight (including auth token note if required). - 4) Sanity-check CORS expectations vs `job-tracker-ui/src/api.ts` and document the required origin pairing (UI :3000, API :5202). -- Failure Modes (Q5): API down → exit 1 with hint to start API; Auth required without token → script notes auth required and how to obtain; malformed JSON → show raw body and fail. -- Load Profile (Q6): trivial single-user curl calls; no scaling concern. -- Negative Tests (Q7): run script with API stopped (expect non-zero); run with wrong `API_BASE` (expect clear error message). -- Must-haves: preflight script exists/executable; README runbook mentions backend start + preflight; script outputs gmailConfigured/auth/db/ai fields. -- Verification: `bash scripts/s06-preflight.sh` - -## Inputs - -- ``JobTrackerApi/Program.cs`` -- ``JobTrackerApi/appsettings.Development.json`` -- ``job-tracker-ui/src/api.ts`` -- ``README.md`` - -## Expected Output - -- ``scripts/s06-preflight.sh`` -- ``README.md`` - -## Verification - -bash scripts/s06-preflight.sh - -## Observability Impact - -Adds preflight status surface exposing DB/auth/gmail/ai readiness via curl; provides explicit failure messages for unreachable API/CORS/auth. diff --git a/.gsd/milestones/M001/slices/S06/tasks/T01-SUMMARY.md b/.gsd/milestones/M001/slices/S06/tasks/T01-SUMMARY.md deleted file mode 100644 index e756887..0000000 --- a/.gsd/milestones/M001/slices/S06/tasks/T01-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T01 -parent: S06 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.778Z -blocker_discovered: false ---- - -# T01: Validated and recorded the live API/auth preflight gate, including README runbook guidance and negative-path shell coverage. - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S06/tasks/T01-VERIFY.json b/.gsd/milestones/M001/slices/S06/tasks/T01-VERIFY.json deleted file mode 100644 index 2fdfbfe..0000000 --- a/.gsd/milestones/M001/slices/S06/tasks/T01-VERIFY.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "schemaVersion": 1, - "taskId": "T01", - "unitId": "M001/S06/T01", - "timestamp": 1774598242061, - "passed": false, - "discoverySource": "task-plan", - "checks": [ - { - "command": "bash scripts/s06-preflight.sh", - "exitCode": 1, - "durationMs": 30, - "verdict": "fail" - } - ], - "retryAttempt": 1, - "maxRetries": 2, - "runtimeErrors": [ - { - "source": "bg-shell", - "severity": "crash", - "message": "[jobtracker-api-abs] exitCode=131 errors: A fatal error was encountered. The library 'libhostpolicy.so' required to execute the application was not found in '/home/pi/.dotnet'.; Failed to run as a self-contained app.", - "blocking": true - } - ] -} diff --git a/.gsd/milestones/M001/slices/S06/tasks/T02-PLAN.md b/.gsd/milestones/M001/slices/S06/tasks/T02-PLAN.md deleted file mode 100644 index 7819c2b..0000000 --- a/.gsd/milestones/M001/slices/S06/tasks/T02-PLAN.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -estimated_steps: 12 -estimated_files: 3 -skills_used: [] ---- - -# T02: Seed acceptance-ready job data - -Create a seed script that prepares a richer acceptance fixture (job, correspondence, saved package, follow-up readiness) using live API calls. -- Why: current DB has only 1 low-signal job; acceptance rerun needs actionable overview + workspace state. -- Steps: - 1) Write `scripts/s06-acceptance-data.sh` (bash, executable) that requires `AUTH_TOKEN` env; uses `scripts/s06-preflight.sh` first, then POSTs to `/api/jobapplications` (or PUT existing ID) to create a job with saved package fields, correspondence entry, reminder/follow-up signals, and notes. - 2) Add curl helpers for adding correspondence (`/api/correspondence/{jobId}` or equivalent), saving package material, and setting workflow/readiness if needed; use deterministic titles so rerun is idempotent (update if exists). - 3) Emit a short summary of created/updated IDs so the acceptance run can target them; avoid logging token. - 4) Document any manual token retrieval step in script comments. -- Failure Modes (Q5): missing AUTH_TOKEN → fail with guidance; 401/403 → explain token issue; 5xx → print response and fail; malformed response → show body and fail. -- Load Profile (Q6): few API calls; minimal DB impact. -- Negative Tests (Q7): run without AUTH_TOKEN (expect failure); rerun twice (should succeed idempotently); simulate 401 by bad token (expect clear message). -- Must-haves: script seeds at least one job with saved package + correspondence + follow-up readiness; outputs job id for UAT; uses preflight. -- Verification: `bash scripts/s06-acceptance-data.sh` - -## Inputs - -- ``scripts/s06-preflight.sh`` -- ``README.md`` -- ``JobTrackerApi/Controllers/JobApplicationsController.cs`` -- ``JobTrackerApi/Controllers/CorrespondenceController.cs`` - -## Expected Output - -- ``scripts/s06-acceptance-data.sh`` -- ``README.md`` - -## Verification - -bash scripts/s06-acceptance-data.sh - -## Observability Impact - -Provides seed summary output (job id, correspondence count) to inspect readiness; failures surface via script exit and printed API responses. diff --git a/.gsd/milestones/M001/slices/S06/tasks/T02-SUMMARY.md b/.gsd/milestones/M001/slices/S06/tasks/T02-SUMMARY.md deleted file mode 100644 index b056820..0000000 --- a/.gsd/milestones/M001/slices/S06/tasks/T02-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T02 -parent: S06 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.778Z -blocker_discovered: false ---- - -# T02: Seeded acceptance-ready job data through the live API with deterministic rerun-safe ids and readiness output. - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S06/tasks/T02-VERIFY.json b/.gsd/milestones/M001/slices/S06/tasks/T02-VERIFY.json deleted file mode 100644 index cf9a387..0000000 --- a/.gsd/milestones/M001/slices/S06/tasks/T02-VERIFY.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "schemaVersion": 1, - "taskId": "T02", - "unitId": "M001/S06/T02", - "timestamp": 1774598990903, - "passed": false, - "discoverySource": "task-plan", - "checks": [ - { - "command": "bash scripts/s06-acceptance-data.sh", - "exitCode": 1, - "durationMs": 9, - "verdict": "fail" - } - ], - "retryAttempt": 1, - "maxRetries": 2, - "runtimeErrors": [ - { - "source": "bg-shell", - "severity": "crash", - "message": "[jobtracker-api] exitCode=131 errors: A fatal error was encountered. The library 'libhostpolicy.so' required to execute the application was not found in '/home/pi/.dotnet'.; Failed to run as a self-contained app.", - "blocking": true - } - ] -} diff --git a/.gsd/milestones/M001/slices/S06/tasks/T03-PLAN.md b/.gsd/milestones/M001/slices/S06/tasks/T03-PLAN.md deleted file mode 100644 index 999c2b4..0000000 --- a/.gsd/milestones/M001/slices/S06/tasks/T03-PLAN.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -estimated_steps: 12 -estimated_files: 5 -skills_used: [] ---- - -# T03: Run integrated acceptance and capture evidence - -Execute the live acceptance loop and record results as an artifact for S07/UAT handoff. -- Why: prove the `/jobs → workspace → reminders/dashboard → follow-up/manual-send boundary` loop runs in the live stack after stabilization and seeding. -- Steps: - 1) Create `scripts/s06-acceptance-run.sh` to orchestrate: ensure backend running, run preflight + seed scripts, then run existing automated regressions most relevant to the loop (e.g., `end-to-end-trust-loop.test.tsx`) and capture outputs. - 2) Perform a guided browser run (can use agent-browser/Playwright) hitting /jobs, /reminders, /dashboard, opening the seeded job workspace, inspecting Tailored CV, Correspondence (linked-thread status), Follow-up draft manual-send boundary; note Gmail continuity if blocked. - 3) Write findings and screenshots/links into `docs/s06-acceptance-run.md` (what passed, what blocked, manual-send boundary observation, Gmail continuity status). Call out any gaps explicitly. - 4) Ensure commands avoid leaking tokens; artifacts redact secrets. -- Failure Modes (Q5): backend not running → script stops after preflight; tests fail → record failure in artifact; browser step blocked by auth → document and include auth instructions. -- Load Profile (Q6): single-user flows; test runner CPU-bound but acceptable. -- Negative Tests (Q7): note expected failure if Gmail remains unconfigured; ensure manual-send boundary not auto-triggered during run. -- Must-haves: acceptance-run script exists; artifact populated with live results; manual-send boundary explicitly observed; Gmail continuity status recorded (even if blocked). -- Verification: `bash scripts/s06-acceptance-run.sh` && `test -s docs/s06-acceptance-run.md` - -## Inputs - -- ``scripts/s06-preflight.sh`` -- ``scripts/s06-acceptance-data.sh`` -- ``job-tracker-ui/src/end-to-end-trust-loop.test.tsx`` -- ``job-tracker-ui/src/components/JobDetailsDialog.tsx`` -- ``job-tracker-ui/src/components/Correspondence.tsx`` - -## Expected Output - -- ``scripts/s06-acceptance-run.sh`` -- ``docs/s06-acceptance-run.md`` - -## Verification - -bash scripts/s06-acceptance-run.sh && test -s docs/s06-acceptance-run.md - -## Observability Impact - -Orchestrated run logs preflight/seed/test results; artifact captures UI observations incl. manual-send boundary and Gmail continuity. Scripts surface failures with exit codes and summarized outputs. diff --git a/.gsd/milestones/M001/slices/S06/tasks/T03-SUMMARY.md b/.gsd/milestones/M001/slices/S06/tasks/T03-SUMMARY.md deleted file mode 100644 index 0da0963..0000000 --- a/.gsd/milestones/M001/slices/S06/tasks/T03-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T03 -parent: S06 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.778Z -blocker_discovered: false ---- - -# T03: Added a repeatable live acceptance runner and recorded real S06 browser evidence for the manual-send boundary and daily loop. - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S06/tasks/T03-VERIFY.json b/.gsd/milestones/M001/slices/S06/tasks/T03-VERIFY.json deleted file mode 100644 index 7001a2f..0000000 --- a/.gsd/milestones/M001/slices/S06/tasks/T03-VERIFY.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "schemaVersion": 1, - "taskId": "T03", - "unitId": "M001/S06/T03", - "timestamp": 1774599867411, - "passed": true, - "discoverySource": "task-plan", - "checks": [ - { - "command": "bash scripts/s06-acceptance-run.sh", - "exitCode": 0, - "durationMs": 5549, - "verdict": "pass" - }, - { - "command": "test -s docs/s06-acceptance-run.md", - "exitCode": 0, - "durationMs": 5, - "verdict": "pass" - } - ] -} diff --git a/.gsd/milestones/M001/slices/S07/S07-PLAN.md b/.gsd/milestones/M001/slices/S07/S07-PLAN.md deleted file mode 100644 index 396ecde..0000000 --- a/.gsd/milestones/M001/slices/S07/S07-PLAN.md +++ /dev/null @@ -1,15 +0,0 @@ -# S07: Daily-loop UAT artifact closure - -**Goal:** Publish the executed daily-loop UAT closure artifact proving one seeded job stays coherent across /jobs, the job workspace, /reminders, and /dashboard using the existing S06 acceptance runner. -**Demo:** After this: TBD - -## Tasks -- [x] **T01: Added docs/s07-uat.md to close S07 with imported acceptance-run evidence for the seeded daily-loop job.** — - - Files: docs/s07-uat.md, docs/s06-acceptance-run.md - - Verify: test -s docs/s07-uat.md && grep -q "S06 Acceptance Backend Engineer" docs/s07-uat.md -- [x] **T02: Re-ran the acceptance flow and refreshed the S07 UAT closure with current browser evidence, manual-send-boundary proof, and the Gmail continuity limitation.** — - - Files: scripts/s06-preflight.sh, scripts/s06-acceptance-run.sh, docs/s06-acceptance-run.md, docs/s07-uat.md - - Verify: bash scripts/s06-preflight.sh && bash scripts/s06-acceptance-run.sh && test -s docs/s06-acceptance-run.md && grep -q "manual-send boundary" docs/s07-uat.md -- [x] **T03: Re-ran the focused daily-loop UI regressions, repaired the local CRA dependency state, and recorded the passing deterministic coverage in docs/s07-uat.md.** — - - Files: job-tracker-ui/src/daily-control-loop.test.tsx, job-tracker-ui/src/workflow-trust-signals.test.tsx, docs/s07-uat.md - - Verify: CI=true npm --prefix job-tracker-ui test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/workflow-trust-signals.test.tsx && grep -q "UI regression results" docs/s07-uat.md diff --git a/.gsd/milestones/M001/slices/S07/S07-RESEARCH.md b/.gsd/milestones/M001/slices/S07/S07-RESEARCH.md deleted file mode 100644 index 1cb273e..0000000 --- a/.gsd/milestones/M001/slices/S07/S07-RESEARCH.md +++ /dev/null @@ -1,122 +0,0 @@ -# S07 Research — Daily-loop UAT artifact closure - -## Summary - -This is **light/targeted** work, not a new subsystem. S06 already built the hard part: a repeatable localhost preflight + seed + acceptance runner, a deterministic acceptance fixture, and a live evidence doc at `docs/s06-acceptance-run.md`. S07’s missing artifact is the **planner-facing research artifact for this slice**, and the product-facing gap is narrower: convert the existing live rerun evidence into a durable **executed daily-loop UAT closure artifact** that explicitly proves one seeded job behaves coherently across `/jobs`, workspace entry, `/reminders`, and `/dashboard`. - -The codebase already has the acceptance data and most of the evidence: - -- `scripts/s06-acceptance-data.sh` seeds the deterministic fixture (`S06 Acceptance Labs` / `S06 Acceptance Backend Engineer`) through the live API and prints the expected workflow/readiness/reminder outputs. -- `scripts/s06-acceptance-run.sh` runs preflight + seeding + one focused UI regression and rewrites the generated section of `docs/s06-acceptance-run.md` while **preserving** the guided browser section between `` markers. -- `docs/s06-acceptance-run.md` already contains the real browser observations, debug-bundle paths, trace path, timeline path, and the explicit manual-send / Gmail-continuity limitations from the latest live run. -- `job-tracker-ui/src/daily-control-loop.test.tsx` and `job-tracker-ui/src/workflow-trust-signals.test.tsx` already encode the cross-surface contract that the same workflow signal should route `/jobs`, `/dashboard`, and `/reminders` into the same workspace semantics. - -So the natural S07 move is **not** to invent new acceptance data or new routing logic. It is to package the existing live run into the milestone’s final UAT closure shape and make the evidence chain easy to rerun and audit. - -## Requirement Focus - -Active requirements this slice supports: - -- **R008** — keep follow-up/reply assistance manual. S06 already captured the live manual-send proof; S07 must preserve that evidence in the final UAT closure artifact and avoid weakening the claim. -- **R009** — keep the loop individual-first. The seeded fixture and the `/jobs -> workspace -> /reminders -> /dashboard` proof are all single-job, single-user checks; S07 should keep the UAT narrative framed around one person managing one role end to end. - -Validated requirements this slice is effectively re-demonstrating in executed-UAT form: - -- **R005 / R006 / R007 / R010** — the same job must appear and behave coherently across the overview surfaces and the individual workspace. - -## Skills Discovered - -Installed skills already directly relevant; no additional skill install is needed. - -- `agent-browser` — relevant for the executed browser/UAT pass. Two rules matter here: - - **navigate → snapshot → interact → re-snapshot** after DOM/navigation changes, because refs become stale after page changes. - - use explicit verification/diff evidence rather than prose-only confirmation. -- `test` — relevant because S07 should continue to anchor the closure artifact to the existing focused UI regressions instead of expanding scope to a broad suite. -- `react-best-practices` / `aspnet-core` exist, but S07 does not look like a React or backend architecture change first; it is mostly artifact/evidence closure on top of established behavior. - -## Recommendation - -Treat S07 as an **artifact-closure slice**, not a feature slice. - -Build/prove in this order: - -1. **Reuse the S06 acceptance fixture and runner as the source of truth.** Do not create a second seeded job, second seed script, or second acceptance data contract. -2. **Add or refresh one final S07-owned UAT artifact** that references the latest executed run and records the exact browser evidence for: - - `/jobs` row signals - - workspace entry for that same job - - `/reminders` visibility and action routing - - `/dashboard` visibility/analytics presence - - manual-send boundary still holding - - Gmail continuity status reported honestly -3. **Keep the shell-generated/live-browser split.** `scripts/s06-acceptance-run.sh` already has a good seam: generated shell summary vs preserved guided-browser section. Reuse that pattern instead of hand-editing a monolithic markdown file. -4. **Lean on the existing focused UI tests as regression proof**, then use browser assertions/debug artifacts for the final live executed proof. - -The simplest successful version of S07 is likely one of these: - -- extend the existing S06 artifact flow so the executed browser observations become the final S07/UAT closure evidence, or -- create a small S07-specific wrapper/doc that imports the current run metadata from `docs/s06-acceptance-run.md` and adds a final closure-oriented summary without duplicating the seeding/runtime logic. - -## Implementation Landscape - -### Files that already matter - -- `docs/s06-acceptance-run.md` - - Current live evidence artifact. - - Already contains the exact observations S07 needs: `/jobs`, workspace, `/reminders`, `/dashboard`, trace/timeline/debug paths, manual-send proof, and the honest Gmail limitation. - - Important detail: it is partially generated and partially hand-preserved. - -- `scripts/s06-acceptance-run.sh` - - Orchestrates preflight, seed, focused UI regression, and doc refresh. - - Preserves the browser-observation block between `` markers. - - Natural seam if S07 wants a rerunnable final artifact instead of a one-off markdown edit. - -- `scripts/s06-acceptance-data.sh` - - Owns the deterministic fixture values and the expected follow-up/readiness state. - - If S07 references concrete labels or expected reminder badges, those should come from here rather than being duplicated by hand. - -- `.gsd/milestones/M001/slices/S06/S06-UAT.md` - - Already defines the manual test cases and pass criteria for the live acceptance rerun. - - Good source material for a final “executed results” closure artifact, but it is still a test-plan style document, not the final closure proof itself. - -- `job-tracker-ui/src/daily-control-loop.test.tsx` - - Best compact contract test for this slice’s user-facing claim. - - Proves `/jobs`, `/dashboard`, and `/reminders` route into the shared workspace flow. - -- `job-tracker-ui/src/workflow-trust-signals.test.tsx` - - Lower-level routing/readiness contract proof. - - Especially useful if S07 work changes route/query-param handling or the wording of action buttons. - -### Natural seams for task breakdown - -1. **Artifact-shape task** - - Decide whether S07 owns a new doc or reuses `docs/s06-acceptance-run.md` as the canonical executed artifact. - - Keep the generated/manual split if touching the runner. - -2. **Browser-evidence task** - - Re-run the live browser flow and capture explicit assertions/screenshots/debug bundles for the four overview/workspace surfaces. - - Record the same job identity consistently across all surfaces. - -3. **Regression/verification task** - - Re-run the focused frontend tests and the acceptance runner so the final artifact is backed by both live execution and deterministic regression output. - -## Constraints / Gotchas - -- **Do not over-claim Gmail continuity.** S06 explicitly recorded that seeded correspondence was visible but `POST /api/gmail/refresh-linked-threads` did not fire in the local run. S07 should preserve that honesty unless the environment actually exposes connected Gmail state. -- **Do not break the deterministic fixture.** `scripts/s06-acceptance-data.sh` depends on backdated `followUpAt` and correspondence timestamps to keep the fixture in `follow-up` / `Waiting 14d` state. -- **Do not replace the manual-send proof with looser prose.** The strongest current evidence is concrete network behavior: `GET .../followup-draft` seen, no `POST .../send-followup` during draft review/regeneration. -- **Avoid duplicating fixture constants.** Company/job labels, dates, and expected reminder state already live in the seed script and UAT doc. - -## Verification - -Minimum verification stack for S07 planning/execution: - -- `bash scripts/s06-preflight.sh` -- `bash scripts/s06-acceptance-run.sh` -- `test -s docs/s06-acceptance-run.md` -- from `job-tracker-ui/`: `CI=true npm test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/workflow-trust-signals.test.tsx` - -For the live browser part, prefer explicit assertions/evidence capture over prose-only checks. The final executed artifact should point to screenshot/debug-bundle/trace/timeline outputs and state exactly which surface each artifact proves. - -## Planner Takeaway - -This slice should be planned as **documentation/evidence closure on top of S06’s existing live-run machinery**. The risky work is already done. The planner should avoid new backend or UI feature work unless the rerun shows a real gap. The likely deliverable is a small, deterministic extension of the current acceptance-run artifact flow that turns S06’s live observations into the final S07 executed UAT closure for the daily loop. diff --git a/.gsd/milestones/M001/slices/S07/S07-SUMMARY.md b/.gsd/milestones/M001/slices/S07/S07-SUMMARY.md deleted file mode 100644 index cf5b276..0000000 --- a/.gsd/milestones/M001/slices/S07/S07-SUMMARY.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -id: S07 -parent: M001 -milestone: M001 -provides: - - A slice-level UAT closure artifact that downstream readers can trust without re-reading all task logs. - - A stable evidence seam between the S06 acceptance runner and human-readable daily-loop closure summary. - - A concrete record that the manual-send boundary held in the live localhost pass while Gmail continuity remained an explicit limitation. -requires: - - slice: S06 - provides: repeatable preflight, acceptance-data seeding, and canonical live acceptance artifacts for the localhost stack - - slice: S05 - provides: the shared workflow-signal contract and focused trust-loop regressions that S07 re-used as deterministic proof -affects: - - M002/S01 -key_files: - - docs/s06-acceptance-run.md - - docs/s07-uat.md - - .gsd/PROJECT.md - - .gsd/KNOWLEDGE.md - - .gsd/DECISIONS.md -key_decisions: - - D016: keep `docs/s06-acceptance-run.md` as the canonical execution log and use S07 closure artifacts to summarize/import the proof instead of duplicating raw runner output. - - Verify the R008 manual-send boundary in this build via visible draft controls plus authenticated `GET /api/jobapplications/3/followup-draft` and absence of `POST /api/jobapplications/3/send-followup` during the observed browser pass. - - Record Gmail continuity as a live-environment limitation when linked-thread refresh evidence is not actually surfaced, rather than implying a pass from seeded correspondence alone. -patterns_established: - - Use a generated-runner-artifact + human closure summary seam: the runner owns raw evidence, while the slice summary compresses what downstream readers need to know. - - Anchor cross-surface UAT to one seeded job identity so `/jobs`, workspace, `/reminders`, and `/dashboard` can be checked as one coherent object instead of four independent screenshots. - - Pair live browser acceptance evidence with focused deterministic regressions before claiming daily-loop closure. -observability_surfaces: - - `scripts/s06-preflight.sh` auth/config reachability gate - - `scripts/s06-acceptance-run.sh` runner output rendered into `docs/s06-acceptance-run.md` - - Acceptance shell logs under `docs/artifacts/s06-acceptance/logs/` - - Browser trace, timeline, and per-surface debug bundles linked from `docs/s07-uat.md` - - Focused UI regression command: `CI=true npm --prefix job-tracker-ui test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/workflow-trust-signals.test.tsx` -drill_down_paths: - - .gsd/milestones/M001/slices/S07/tasks/T01-SUMMARY.md - - .gsd/milestones/M001/slices/S07/tasks/T02-SUMMARY.md - - .gsd/milestones/M001/slices/S07/tasks/T03-SUMMARY.md -duration: "" -verification_result: passed -completed_at: 2026-03-27T08:59:47.615Z -blocker_discovered: false ---- - -# S07: Daily-loop UAT artifact closure - -**Closed M001’s daily-loop proof with an executed UAT artifact that traces the seeded acceptance job coherently across /jobs, the job workspace, /reminders, and /dashboard while preserving the manual-send boundary and honestly recording the live Gmail-continuity gap.** - -## What Happened - -S07 compressed the S06 live acceptance rerun into a downstream-friendly closure artifact instead of leaving the milestone dependent on scattered task logs. The slice established `docs/s06-acceptance-run.md` as the canonical execution record and treated the S07 closure material as an imported-evidence summary: one seeded job (`S06 Acceptance Labs` / `S06 Acceptance Backend Engineer`) is the same object across `/jobs`, the workspace, `/reminders`, and `/dashboard`, with artifact links back to the runner logs, trace, timeline, and page-specific debug bundles. The work also refreshed the closure with current browser evidence and deterministic regression coverage so the slice proves both the live stack behavior and the encoded UI contract. - -The executed flow showed the seeded row on `/jobs` with the expected trust badges, opened the real workspace for that same record, preserved the saved tailored CV and seeded correspondence message, surfaced the same job on `/reminders` with the expected follow-up date/state, and kept `/dashboard` counters and top-company activity aligned with the same seed data. The manual-send boundary remained intact: the follow-up UI exposed separate `Copy Draft` and `Send And Log Email` actions, an authenticated `GET /api/jobapplications/3/followup-draft` returned draft content, and no `POST /api/jobapplications/3/send-followup` request was triggered during the observed browser pass. S07 intentionally did not over-claim Gmail continuity: the correspondence history was visible, but a connected Gmail continuity banner/refresh was not observed in the localhost pass, so the closure records that as a limitation rather than a success. - -S07 also preserved the deterministic guardrail around the daily loop. The focused React suites (`src/daily-control-loop.test.tsx` and `src/workflow-trust-signals.test.tsx`) remain the encoded cross-surface contract for the same overview/workspace semantics, and the slice documents that future reruns must not claim closure if those suites fail. This gives downstream slices a clear dependency summary: use the S06 runner for fresh live evidence, use the S07 closure artifact for compressed interpretation, and treat Gmail-connected continuity as an environment-dependent follow-up proof rather than something this local pass retired. - -### Operational Readiness (Q8) -- **Health signal:** `bash scripts/s06-preflight.sh` reaches `/api/auth/config` and returns the expected auth-limited partial pass; `bash scripts/s06-acceptance-run.sh` finishes pass and refreshes `docs/s06-acceptance-run.md`; the focused UI regression pair passes with `2/2` suites and `6/6` tests. -- **Failure signal:** API not listening on `http://localhost:5202`, acceptance runner failing to seed/update the S06 fixture, the seeded job no longer matching across `/jobs`/workspace/`/reminders`/`/dashboard`, or any observed `POST /api/jobapplications/{id}/send-followup` during passive draft review. -- **Recovery procedure:** start the local API and UI, rerun `scripts/s06-preflight.sh`, rerun `scripts/s06-acceptance-run.sh`, repair `job-tracker-ui` dependencies with `npm --prefix job-tracker-ui install` if `react-scripts` is missing, then rerun the focused regression command before claiming closure again. -- **Monitoring gaps:** the localhost run still lacks a real Gmail-connected refresh proof, and the acceptance pass relies on artifact review rather than a dedicated machine-checked assertion for linked-thread refresh visibility. - -## Verification - -Slice-level verification passed in the target worktree. I reran `bash scripts/s06-preflight.sh && bash scripts/s06-acceptance-run.sh && test -s docs/s06-acceptance-run.md && grep -q 'manual-send boundary' docs/s07-uat.md`, which passed and refreshed the canonical acceptance artifact with a successful runner result. I also reran `CI=true npm --prefix job-tracker-ui test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/workflow-trust-signals.test.tsx && grep -q 'UI regression results' docs/s07-uat.md`, which passed with `2 passed, 2 total` suites and `6 passed, 6 total` tests. The live evidence set confirms the seeded job remains coherent across `/jobs`, workspace, `/reminders`, and `/dashboard`, that the manual-send boundary holds, and that Gmail continuity is still explicitly documented as a limitation in this environment. - -## Requirements Advanced - -- R005 — Added executed UAT evidence showing the seeded job row remains coherent from `/jobs` into the workspace rather than relying only on prior focused tests. -- R006 — Added executed UAT evidence that `/reminders` and `/dashboard` still expose the same seeded job/activity state proven in the live localhost stack. -- R008 — Re-proved the manual-send boundary in the live acceptance pass by observing only draft preparation behavior, a successful follow-up draft GET, and no send-followup POST during passive review. -- R010 — Closed the daily-loop artifact gap by tying one seeded job’s state together across `/jobs`, workspace, `/reminders`, and `/dashboard` with current runner/browser evidence. - -## Requirements Validated - -None. - -## New Requirements Surfaced - -None. - -## Requirements Invalidated or Re-scoped - -None. - -## Deviations - -The slice depended on the S06 live environment being up; the backend was not listening when verification began, so the local API/UI stack had to be brought back up before the acceptance rerun. The focused CRA regression command is also sensitive to install state in this worktree, so the slice documents dependency repair guidance instead of assuming tests always start cleanly. - -## Known Limitations - -This localhost pass still does not prove Gmail-connected linked-thread refresh in a truly configured Gmail session; it only proves the seeded correspondence remains visible and that the limitation is recorded honestly. The focused UI regression output still includes stable React Router future-flag warnings, which are non-blocking but noisy. - -## Follow-ups - -If milestone validation requires a live Gmail-continuity proof rather than an explicitly recorded limitation, rerun the S06 acceptance flow in an environment with a genuinely connected Gmail session and capture linked-thread refresh evidence in the same cross-surface artifact seam. - -## Files Created/Modified - -- `docs/s06-acceptance-run.md` — Refreshed the canonical live acceptance artifact with the latest successful rerun metadata and browser observations. -- `docs/s07-uat.md` — Updated the imported-evidence closure document with seeded-job identity, cross-surface proof, manual-send boundary wording, Gmail continuity limitation, regression results, and artifact links. -- `.gsd/PROJECT.md` — Updated current-state narrative to reflect M001 completion through S07 and the resulting daily-loop UAT closure. -- `.gsd/KNOWLEDGE.md` — Recorded the non-obvious follow-up-draft verification technique for proving the manual-send boundary when the tab does not emit a fresh captured request. -- `.gsd/DECISIONS.md` — Appended D016 documenting the S07 evidence-seam decision. diff --git a/.gsd/milestones/M001/slices/S07/S07-UAT.md b/.gsd/milestones/M001/slices/S07/S07-UAT.md deleted file mode 100644 index 8e24b39..0000000 --- a/.gsd/milestones/M001/slices/S07/S07-UAT.md +++ /dev/null @@ -1,68 +0,0 @@ -# S07: Daily-loop UAT artifact closure — UAT - -**Milestone:** M001 -**Written:** 2026-03-27T08:59:47.615Z - -# S07 UAT — Daily-loop UAT artifact closure - -## Preconditions -- Local API is running at `http://localhost:5202` and UI is running at `http://localhost:3000`. -- The S06 acceptance fixture can be seeded via `bash scripts/s06-acceptance-run.sh`. -- Seeded job identity exists after rerun: `S06 Acceptance Labs` / `S06 Acceptance Backend Engineer`. -- Browser session can reach the authenticated localhost UI used by the S06 acceptance flow. - -## Test Case 1 — `/jobs` anchors the seeded daily-loop job -1. Run `bash scripts/s06-preflight.sh`. - - Expected: script reaches `/api/auth/config`; auth-limited partial pass is acceptable, but API reachability must succeed. -2. Run `bash scripts/s06-acceptance-run.sh`. - - Expected: `docs/s06-acceptance-run.md` is refreshed and reports overall runner result `pass`. -3. Open `/jobs` in the authenticated UI session. - - Expected: the table contains `S06 Acceptance Labs • S06 Acceptance Backend Engineer`. -4. Inspect the seeded row state. - - Expected: the row shows `Follow up`, `CV ready`, and `Waiting` badges. -5. Open the row’s workspace entry. - - Expected: the real job workspace opens for the same seeded job, not a disconnected placeholder. - -## Test Case 2 — Workspace preserves the same job identity and real seeded content -1. From the seeded workspace, inspect the tailored CV area. - - Expected: saved tailored CV content is present and begins with `Saved acceptance tailored CV highlighting ASP.NET Core delivery, workflow trust signals...`. -2. Switch to the correspondence view. - - Expected: the seeded recruiter-thread message `Backend Engineer follow-up` is visible for the same job. -3. Record Gmail continuity status honestly. - - Expected: if a linked-thread continuity banner/refresh signal is visible, capture it explicitly; if not, record Gmail continuity as not proven in this run rather than marking it passed. - -## Test Case 3 — `/reminders` and `/dashboard` reflect the same seeded state -1. Navigate to `/reminders`. - - Expected: the seeded job appears under `Needs Follow-up`. -2. Inspect reminder state. - - Expected: `Follow up`, `Waiting 14d`, and `Follow-up: 10/03/2026` are all visible for the seeded job. -3. Navigate to `/dashboard`. - - Expected: dashboard counters show `Active applications = 2`, `Applied (30 days) = 2`, and `Responses logged = 1`. -4. Inspect company activity. - - Expected: `Top companies by activity` includes `S06 Acceptance Labs`. -5. Reload `/dashboard` after clearing diagnostics. - - Expected: no browser console errors and no failed network requests are observed in the clean dashboard pass. - -## Test Case 4 — Follow-up drafting stays on the manual side of the boundary -1. Open the seeded job workspace follow-up draft area. - - Expected: separate `Copy Draft` and `Send And Log Email` controls are visible. -2. Verify draft retrieval without sending. - - Expected: the live draft content is present in the workspace; if the tab does not emit a fresh captured request, confirm `GET /api/jobapplications/3/followup-draft` succeeds from the authenticated browser context. -3. Observe browser/network activity during passive draft review. - - Expected: no `POST /api/jobapplications/3/send-followup` request occurs unless the explicit send action is clicked. -4. Record the outcome. - - Expected: UAT notes state that the system prepared the draft but did not auto-send email. - -## Test Case 5 — Deterministic regression guardrail for closure -1. Run `CI=true npm --prefix job-tracker-ui test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/workflow-trust-signals.test.tsx`. - - Expected: both suites pass (`2 passed, 2 total`) and all six tests pass. -2. Confirm `docs/s07-uat.md` contains a `UI regression results` section. - - Expected: the doc records the command, pass result, and the rule that closure must not be claimed if this regression pair fails on rerun. - -## Edge Cases / Failure Handling -- If `scripts/s06-preflight.sh` cannot reach `http://localhost:5202/api`, stop and recover the local API before claiming closure. -- If `scripts/s06-acceptance-run.sh` fails to seed/update the fixture or reports a non-pass result, do not claim S07 closure; capture the runner logs and fix the blocker first. -- If the focused UI regression command fails with `react-scripts: not found`, run `npm --prefix job-tracker-ui install` and retry before treating the slice as regressed. -- If the seeded job is visible but Gmail-linked continuity is not, record that as a limitation; do not silently convert seeded correspondence visibility into a Gmail-sync pass. -- If any passive browser observation shows a `send-followup` POST without the explicit send action, treat it as a blocker against R008 and fail the UAT closure. - diff --git a/.gsd/milestones/M001/slices/S07/tasks/T01-PLAN.md b/.gsd/milestones/M001/slices/S07/tasks/T01-PLAN.md deleted file mode 100644 index f59f546..0000000 --- a/.gsd/milestones/M001/slices/S07/tasks/T01-PLAN.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -estimated_steps: 1 -estimated_files: 2 -skills_used: [] ---- - -# T01: Shape S07 UAT doc around acceptance-run evidence seam - -Why: Give S07 its own UAT closure document that reuses the S06 acceptance runner as the evidence source and frames the daily-loop proof across /jobs → workspace → reminders → dashboard while preserving the manual-send and Gmail-continuity notes. Do: review docs/s06-acceptance-run.md and scripts/s06-acceptance-run.sh for generated/manual seams; create docs/s07-uat.md with sections for surfaces, job identity, manual-send boundary evidence, Gmail continuity status, rerun commands, and artifact links; note that evidence is imported from the acceptance run rather than duplicated. Done when docs/s07-uat.md exists with the sections above and references the seeded job (S06 Acceptance Backend Engineer) and acceptance artifact. - -## Inputs - -- ``docs/s06-acceptance-run.md`` -- ``scripts/s06-acceptance-run.sh`` - -## Expected Output - -- ``docs/s07-uat.md`` - -## Verification - -test -s docs/s07-uat.md && grep -q "S06 Acceptance Backend Engineer" docs/s07-uat.md diff --git a/.gsd/milestones/M001/slices/S07/tasks/T01-SUMMARY.md b/.gsd/milestones/M001/slices/S07/tasks/T01-SUMMARY.md deleted file mode 100644 index 44f0261..0000000 --- a/.gsd/milestones/M001/slices/S07/tasks/T01-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T01 -parent: S07 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.778Z -blocker_discovered: false ---- - -# T01: Added docs/s07-uat.md to close S07 with imported acceptance-run evidence for the seeded daily-loop job. - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S07/tasks/T01-VERIFY.json b/.gsd/milestones/M001/slices/S07/tasks/T01-VERIFY.json deleted file mode 100644 index a4feeec..0000000 --- a/.gsd/milestones/M001/slices/S07/tasks/T01-VERIFY.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "schemaVersion": 1, - "taskId": "T01", - "unitId": "M001/S07/T01", - "timestamp": 1774600599706, - "passed": true, - "discoverySource": "task-plan", - "checks": [ - { - "command": "test -s docs/s07-uat.md", - "exitCode": 0, - "durationMs": 5, - "verdict": "pass" - }, - { - "command": "grep -q \"S06 Acceptance Backend Engineer\" docs/s07-uat.md", - "exitCode": 0, - "durationMs": 5, - "verdict": "pass" - } - ] -} diff --git a/.gsd/milestones/M001/slices/S07/tasks/T02-PLAN.md b/.gsd/milestones/M001/slices/S07/tasks/T02-PLAN.md deleted file mode 100644 index 6eae33a..0000000 --- a/.gsd/milestones/M001/slices/S07/tasks/T02-PLAN.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -estimated_steps: 1 -estimated_files: 4 -skills_used: [] ---- - -# T02: Re-run acceptance flow and record browser evidence + manual-send boundary - -Why: Refresh the live acceptance evidence and capture the manual-send boundary plus Gmail continuity status for S07. Do: run `bash scripts/s06-preflight.sh`; run `bash scripts/s06-acceptance-run.sh` (with AUTH_TOKEN if needed) to regenerate docs/s06-acceptance-run.md and artifacts; confirm the seeded job identity and cross-surface observations, and extract artifact links (logs, trace, timeline, screenshots/debug bundle); update docs/s07-uat.md with the latest evidence, explicitly stating the manual-send boundary (GET followup-draft seen, no POST send-followup) and the Gmail continuity limitation observed in this run. Failure modes: backend/API down → note preflight failure; auth/token missing → use runner fallback guidance; browser/assertion failures → capture log paths; malformed artifact paths → rerun and repair links. Negative checks: ensure acceptance run did not issue send-followup, and Gmail refresh absence is recorded, not implied passing. Done when both docs are updated with current run evidence and links. - -## Inputs - -- ``scripts/s06-preflight.sh`` -- ``scripts/s06-acceptance-run.sh`` -- ``docs/s07-uat.md`` - -## Expected Output - -- ``docs/s06-acceptance-run.md`` -- ``docs/s07-uat.md`` -- ``docs/artifacts/s06-acceptance/logs/`` - -## Verification - -bash scripts/s06-preflight.sh && bash scripts/s06-acceptance-run.sh && test -s docs/s06-acceptance-run.md && grep -q "manual-send boundary" docs/s07-uat.md diff --git a/.gsd/milestones/M001/slices/S07/tasks/T02-SUMMARY.md b/.gsd/milestones/M001/slices/S07/tasks/T02-SUMMARY.md deleted file mode 100644 index 61929ac..0000000 --- a/.gsd/milestones/M001/slices/S07/tasks/T02-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T02 -parent: S07 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.778Z -blocker_discovered: false ---- - -# T02: Re-ran the acceptance flow and refreshed the S07 UAT closure with current browser evidence, manual-send-boundary proof, and the Gmail continuity limitation. - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S07/tasks/T02-VERIFY.json b/.gsd/milestones/M001/slices/S07/tasks/T02-VERIFY.json deleted file mode 100644 index b7c5935..0000000 --- a/.gsd/milestones/M001/slices/S07/tasks/T02-VERIFY.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "schemaVersion": 1, - "taskId": "T02", - "unitId": "M001/S07/T02", - "timestamp": 1774601486400, - "passed": true, - "discoverySource": "task-plan", - "checks": [ - { - "command": "bash scripts/s06-preflight.sh", - "exitCode": 0, - "durationMs": 136, - "verdict": "pass" - }, - { - "command": "bash scripts/s06-acceptance-run.sh", - "exitCode": 0, - "durationMs": 5532, - "verdict": "pass" - }, - { - "command": "test -s docs/s06-acceptance-run.md", - "exitCode": 0, - "durationMs": 5, - "verdict": "pass" - }, - { - "command": "grep -q \"manual-send boundary\" docs/s07-uat.md", - "exitCode": 0, - "durationMs": 6, - "verdict": "pass" - } - ] -} diff --git a/.gsd/milestones/M001/slices/S07/tasks/T03-PLAN.md b/.gsd/milestones/M001/slices/S07/tasks/T03-PLAN.md deleted file mode 100644 index 7be2224..0000000 --- a/.gsd/milestones/M001/slices/S07/tasks/T03-PLAN.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -estimated_steps: 1 -estimated_files: 3 -skills_used: [] ---- - -# T03: Re-run focused daily-loop UI tests and fold results into UAT doc - -Why: Anchor the S07 UAT closure to the existing focused UI regressions that encode the cross-surface contract. Do: from job-tracker-ui/, run `CI=true npm test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/workflow-trust-signals.test.tsx`; capture pass/fail summaries and note any flake; update docs/s07-uat.md with the test command, date/time, and results so the UAT doc cites both live run evidence and deterministic regression coverage. Failure modes: missing node modules → npm install; test failures → log failing test output and blockers in the doc. Negative tests: ensure the doc notes what happens if these tests fail (e.g., stop claiming UAT closure). Done when tests pass and docs/s07-uat.md reflects the run and command used. - -## Inputs - -- ``job-tracker-ui/src/daily-control-loop.test.tsx`` -- ``job-tracker-ui/src/workflow-trust-signals.test.tsx`` -- ``docs/s07-uat.md`` - -## Expected Output - -- ``docs/s07-uat.md`` - -## Verification - -CI=true npm --prefix job-tracker-ui test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/workflow-trust-signals.test.tsx && grep -q "UI regression results" docs/s07-uat.md diff --git a/.gsd/milestones/M001/slices/S07/tasks/T03-SUMMARY.md b/.gsd/milestones/M001/slices/S07/tasks/T03-SUMMARY.md deleted file mode 100644 index 810679d..0000000 --- a/.gsd/milestones/M001/slices/S07/tasks/T03-SUMMARY.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: T03 -parent: S07 -milestone: M001 -provides: [] -requires: [] -affects: [] -key_files: [] -key_decisions: [] -patterns_established: [] -drill_down_paths: [] -observability_surfaces: [] -duration: "" -verification_result: "" -completed_at: 2026-03-28T22:02:57.779Z -blocker_discovered: false ---- - -# T03: Re-ran the focused daily-loop UI regressions, repaired the local CRA dependency state, and recorded the passing deterministic coverage in docs/s07-uat.md. - -## What Happened -No summary recorded. diff --git a/.gsd/milestones/M001/slices/S07/tasks/T03-VERIFY.json b/.gsd/milestones/M001/slices/S07/tasks/T03-VERIFY.json deleted file mode 100644 index 0810f42..0000000 --- a/.gsd/milestones/M001/slices/S07/tasks/T03-VERIFY.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "schemaVersion": 1, - "taskId": "T03", - "unitId": "M001/S07/T03", - "timestamp": 1774601726342, - "passed": true, - "discoverySource": "task-plan", - "checks": [ - { - "command": "CI=true npm --prefix job-tracker-ui test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/workflow-trust-signals.test.tsx", - "exitCode": 0, - "durationMs": 5631, - "verdict": "pass" - }, - { - "command": "grep -q \"UI regression results\" docs/s07-uat.md", - "exitCode": 0, - "durationMs": 6, - "verdict": "pass" - } - ] -} diff --git a/.gsd/milestones/M002/M002-CONTEXT-DRAFT.md b/.gsd/milestones/M002/M002-CONTEXT-DRAFT.md deleted file mode 100644 index ed970d0..0000000 --- a/.gsd/milestones/M002/M002-CONTEXT-DRAFT.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -depends_on: [M001] ---- - -# M002: Tracking control center — Context Draft - -**Gathered:** 2026-03-24 -**Status:** Draft — needs milestone-specific discussion before planning - -## Seed from broader discussion - -The product should feel first like a tracker and follow-up system, not just a collection of AI tools. The agreed navigation hierarchy is job table first, then follow-up/dashboard, then individual job workspace. That means M002 is the milestone where the tracking and control-center surfaces likely get stronger after M001 proves Gmail import quality and AI draft quality. - -## Intended milestone role - -M002 likely expands the app from “good Gmail + good drafts” into a clearer job-search control center for an individual user. The likely emphasis is better table/dashboard/follow-up workflow, better visibility into what needs action, and stronger continuity through the life of each application. - -## Likely capabilities - -- stronger table views and status clarity -- better follow-up/dashboard action surfacing -- tighter tracking continuity across manual updates and imported correspondence -- clearer operating rhythm for daily use -- likely analytics/pattern visibility only where it directly improves decision-making - -## Constraints already known - -- individual-first product shape remains in force -- no auto-send or auto-apply behavior -- job discovery still happens outside the app -- table → follow-up/dashboard → job workspace remains the intended daily navigation hierarchy unless usage disproves it - -## What this milestone unlocks - -A version of the product that feels more like the user’s daily control center and less like a set of helpful but disconnected screens. - -## Open questions for future discussion - -- How much of M002 should be action clarity versus analytics polish? -- Which table/dashboard views feel most lacking in current use? -- Should saved views, stronger filtering, or timeline/history clarity be core in this milestone? -- What specific “I know what to do next” experience should the dashboard deliver every morning? diff --git a/.gsd/milestones/M003/M003-CONTEXT-DRAFT.md b/.gsd/milestones/M003/M003-CONTEXT-DRAFT.md deleted file mode 100644 index d8cace7..0000000 --- a/.gsd/milestones/M003/M003-CONTEXT-DRAFT.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -depends_on: [M001, M002] ---- - -# M003: Deeper inbox-aware assistance — Context Draft - -**Gathered:** 2026-03-24 -**Status:** Draft — needs milestone-specific discussion before planning - -## Seed from broader discussion - -After the first Gmail improvements, there is likely room to deepen inbox-aware assistance and correspondence-driven help. This should still remain assistive rather than autonomous, and it should continue to support an individual user’s job-search workflow instead of turning into broad automation for its own sake. - -## Intended milestone role - -M003 likely extends the correspondence and assistance layer beyond the first trust milestone. The likely focus is richer inbox awareness, better use of message context over time, and additional AI help that stays grounded in real application history. - -## Likely capabilities - -- richer message/thread understanding after import -- better context assembly from accumulated correspondence -- broader assistance around replies, follow-up strategy, and ongoing job-specific communication -- selective expansion of AI coaching only where it strengthens the core user loop - -## Constraints already known - -- no auto-send and no auto-apply remain hard boundaries -- assistance should stay grounded in imported job/application/correspondence context -- the product is still for one person managing their own search -- the app should not drift into generic chatbot behavior - -## What this milestone unlocks - -A more aware assistant that understands the state of an application thread over time and can help the user respond more intelligently without taking control away. - -## Open questions for future discussion - -- What additional inbox-aware behavior is actually high value after M001? -- How much strategic coaching belongs here versus later? -- Which parts of correspondence history should influence drafting most? -- What privacy or trust surfaces need to become more visible as the assistant becomes more context-aware? diff --git a/.gsd/milestones/M004/M004-CONTEXT-DRAFT.md b/.gsd/milestones/M004/M004-CONTEXT-DRAFT.md deleted file mode 100644 index 29949b6..0000000 --- a/.gsd/milestones/M004/M004-CONTEXT-DRAFT.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -depends_on: [M001, M002, M003] ---- - -# M004: Trust, launchability, and hardening — Context Draft - -**Gathered:** 2026-03-24 -**Status:** Draft — needs milestone-specific discussion before planning - -## Seed from broader discussion - -The app already has meaningful breadth: auth, Gmail integration, AI service integration, drafting, reminders, imports, dashboards, and admin/system pages. Once the core workflow is strong enough, the next need is likely hardening: making the product clearer, safer, more diagnosable, and easier to live with over time. - -## Intended milestone role - -M004 likely focuses on trust, clarity, and operational hardening after the core workflow is proven. This includes the quality of validation, clarity of failure modes, performance, launch readiness, and operational confidence for a product used repeatedly during a real job search. - -## Likely capabilities - -- clearer validation and failure visibility -- UX and terminology cleanup where the product still feels messy or inconsistent -- performance and reliability improvements around key surfaces -- stronger operational/admin clarity for self-hosted or deployed use -- final trust surfaces around how AI and integrations behave - -## Constraints already known - -- the product must preserve manual control over outbound communication -- hardening should support the individual-first workflow rather than introducing enterprise complexity -- changes should build on the existing architecture rather than force a platform rewrite - -## What this milestone unlocks - -A product that not only has the right workflow, but also feels solid, comprehensible, and trustworthy enough for sustained daily use and future expansion. - -## Open questions for future discussion - -- Which hardening gaps are most painful in actual use by the time this milestone arrives? -- What launchability bar matters for this project: self-hosted personal use, broader deployment, or something in between? -- Which trust/diagnostic surfaces are most important for AI-assisted correspondence and drafting? -- What level of polish is necessary before the product feels genuinely finished rather than just feature-complete? diff --git a/.gsd/state-manifest.json b/.gsd/state-manifest.json deleted file mode 100644 index 341ea53..0000000 --- a/.gsd/state-manifest.json +++ /dev/null @@ -1,4248 +0,0 @@ -{ - "version": 1, - "exported_at": "2026-04-10T23:25:36.543Z", - "milestones": [ - { - "id": "M001", - "title": "M001: M001: Gmail and draft quality loop", - "status": "active", - "depends_on": [], - "created_at": "2026-03-28T22:02:57.775Z", - "completed_at": null, - "vision": "", - "success_criteria": [], - "key_risks": [], - "proof_strategy": [], - "verification_contract": "", - "verification_integration": "", - "verification_operational": "", - "verification_uat": "", - "definition_of_done": [], - "requirement_coverage": "", - "boundary_map_markdown": "" - }, - { - "id": "M002", - "title": "", - "status": "active", - "depends_on": [], - "created_at": "2026-03-28T22:04:42.699Z", - "completed_at": null, - "vision": "", - "success_criteria": [], - "key_risks": [], - "proof_strategy": [], - "verification_contract": "", - "verification_integration": "", - "verification_operational": "", - "verification_uat": "", - "definition_of_done": [], - "requirement_coverage": "", - "boundary_map_markdown": "" - }, - { - "id": "M003", - "title": "", - "status": "active", - "depends_on": [], - "created_at": "2026-03-28T22:04:42.699Z", - "completed_at": null, - "vision": "", - "success_criteria": [], - "key_risks": [], - "proof_strategy": [], - "verification_contract": "", - "verification_integration": "", - "verification_operational": "", - "verification_uat": "", - "definition_of_done": [], - "requirement_coverage": "", - "boundary_map_markdown": "" - }, - { - "id": "M004", - "title": "", - "status": "active", - "depends_on": [], - "created_at": "2026-03-28T22:04:42.699Z", - "completed_at": null, - "vision": "", - "success_criteria": [], - "key_risks": [], - "proof_strategy": [], - "verification_contract": "", - "verification_integration": "", - "verification_operational": "", - "verification_uat": "", - "definition_of_done": [], - "requirement_coverage": "", - "boundary_map_markdown": "" - }, - { - "id": "M005", - "title": "", - "status": "queued", - "depends_on": [], - "created_at": "2026-03-28T22:02:57.780Z", - "completed_at": null, - "vision": "Turn the current CV upload and tailoring surfaces into a robust canonical-profile pipeline: preserve raw uploads, extract a confidence-scored structured CV through a multi-pass process, let the user review/edit uncertain fields, generate job-specific tailored CV drafts separate from the master profile, and render previewable downloadable PDFs from selectable templates without crossing the manual-send or individual-first product boundaries.", - "success_criteria": [ - "Uploading a CV preserves the original artifact, raw extracted text, normalized text, extraction metadata, and a canonical structured CV profile with field-level confidence/provenance.", - "The structured CV editor highlights uncertain extracted fields and supports review, edit, accept, and reprocess flows without destroying raw source data.", - "The app can generate and persist a job-specific tailored CV draft that is separate from both the master CV and the final PDF export.", - "At least one HTML/CSS-backed template can preview and export a tailored CV as a downloadable PDF; the renderer is deterministic and uses the same template for preview and export.", - "The PDF export flow remains individual-first and manual: users explicitly choose template/options and explicitly download the generated document.", - "Template controls support at least photo visibility, one-page vs two-page mode, accent color, section ordering, and bullet density on the tailored draft/render layer." - ], - "key_risks": [ - { - "risk": "Plain-text-only parsing will keep collapsing complex/two-column CVs into weak structure and reduce trust in the review surface.", - "whyItMatters": "If extraction quality is poor, every downstream draft/PDF feature is working from a broken model and the product feels unreliable." - }, - { - "risk": "Tailored CV drafts may get tangled with the existing master CV text and per-job tailored text fields, creating unclear ownership and destructive overwrite risks.", - "whyItMatters": "Users need a stable master profile plus job-specific variants; mixing those layers will make regeneration and editing unsafe." - }, - { - "risk": "PDF generation can drift from preview or become template-specific technical debt if rendering is not deterministic.", - "whyItMatters": "Users will not trust export if the downloaded PDF differs from what they reviewed, and adding templates later will become expensive." - }, - { - "risk": "Extraction provenance/confidence can bloat the data model and UI if added without clear boundaries.", - "whyItMatters": "We need enough traceability for review and reprocessing, but not so much complexity that the editor becomes unusable." - } - ], - "proof_strategy": [ - { - "riskOrUnknown": "Can we represent canonical CV, extraction provenance, tailored drafts, and render options cleanly in the current data model?", - "retireIn": "M005/S01", - "whatWillBeProven": "Schema/API design plus persistence layer prove the project can store raw artifacts, extraction runs, canonical structured CV, and tailored draft data without collapsing existing profile flows." - }, - { - "riskOrUnknown": "Can the extractor improve real-world OCR/PDF quality beyond the current 'General blob' fallback while remaining generic?", - "retireIn": "M005/S02", - "whatWillBeProven": "Multi-pass extraction plus review UX prove common CV uploads populate structured fields with confidence/provenance and support reprocessing." - }, - { - "riskOrUnknown": "Can job-specific tailored drafts stay separate from master CV data while still reusing canonical profile content effectively?", - "retireIn": "M005/S03", - "whatWillBeProven": "Tailored draft model, generation endpoints, and edit/save flows prove job-specific variants are durable and safely editable." - }, - { - "riskOrUnknown": "Can preview and PDF export share one deterministic renderer that supports templates and layout controls?", - "retireIn": "M005/S04", - "whatWillBeProven": "One end-to-end template proves preview == export and that PDF generation/download works from the tailored draft layer." - }, - { - "riskOrUnknown": "Can multiple templates and user controls ship without making exports inconsistent or fragile?", - "retireIn": "M005/S05", - "whatWillBeProven": "Additional templates, render controls, and acceptance verification prove the system scales past a single hardcoded export." - } - ], - "verification_contract": "Verification for this milestone must cover each layer independently and together: extraction pipeline tests for deterministic/layout/LLM repair behavior, persistence tests for artifacts/provenance/draft models, frontend review/edit tests for the structured editor and confidence surfaces, tailored draft generation tests, and deterministic renderer tests where preview and exported PDF are compared against the same source draft. Non-trivial slices must also prove that raw master CV data remains preserved and that no export flow silently overwrites the canonical profile.", - "verification_integration": "Integration verification must prove upload artifact → extracted text → structured profile → tailored draft → preview → PDF export works as one coherent pipeline. The job workspace must consume the tailored draft layer without regressing existing application-package and follow-up flows.", - "verification_operational": "Operational verification must prove reprocessing is safe and repeatable, extraction runs are versioned/auditable, stored artifacts are retrievable, and PDF generation failures surface actionable errors rather than silent corruption. Any background rendering service/process must expose clear failure state.", - "verification_uat": "UAT must show a user uploading a real CV, reviewing highlighted uncertain fields, generating a tailored CV for a chosen job, switching templates/options, previewing the result, and downloading a PDF that matches the preview.", - "definition_of_done": [ - "A durable canonical CV extraction architecture exists with artifact preservation, extraction run history, and field-level provenance/confidence.", - "Users can review/edit extracted structured CV data and reprocess with a newer extractor without losing the original upload or current canonical state.", - "Tailored CV drafts are persisted per job and are clearly distinct from the master profile and the final exported document.", - "At least one template supports preview and PDF export from the tailored draft layer, with deterministic parity between preview and download.", - "Template controls are stored as render options and do not mutate the canonical profile.", - "Milestone-level verification and UAT evidence exist for the full upload → review → tailor → preview → export loop." - ], - "requirement_coverage": "This milestone extends R003 by improving the quality and controllability of tailored CV output, remains constrained by R008/R015 because generation/export stay manual, and stays aligned with R009/R016 because the flow optimizes for one person's personal job search materials rather than recruiter/team workflows. It also surfaces a likely new requirement around deterministic document export from job-tailored drafts, to be formalized when execution starts.", - "boundary_map_markdown": "### Boundary map\n- **Canonical profile layer**: raw upload artifact, extracted text, normalized text, extraction metadata, provenance/confidence, and user-reviewed structured CV.\n- **Tailored draft layer**: job-specific editable CV draft derived from canonical profile + job context; separate from master profile and separate from final export artifact.\n- **Render/export layer**: deterministic HTML/CSS template rendering, preview generation, PDF output, and render options.\n- **Current profile compatibility layer**: existing `ProfileCvText` and `ProfileCvStructureJson` remain compatible during rollout.\n- **Not in scope**: autonomous application submission, autonomous outbound communication, recruiter/team collaboration features, replacing external CV design tools wholesale.\n- **External dependencies**: existing summarizer/OCR service, browser/PDF rendering runtime, local/remote file storage for artifacts.\n" - }, - { - "id": "M006", - "title": "", - "status": "queued", - "depends_on": [], - "created_at": "2026-04-01T13:38:20.257Z", - "completed_at": null, - "vision": "Turn the existing job-local Gmail import helpers into a real integration foundation: durable OAuth connection, sync state, normalized Gmail ingestion contract, and the first platform seams required for cross-job correspondence workflows.", - "success_criteria": [ - "Gmail connection state, token lifecycle, and sync state are visible and durable for one user account.", - "A normalized Gmail ingestion/storage contract exists for messages, threads, labels, and attachment metadata, without breaking existing per-job correspondence import flows.", - "The codebase has explicit service boundaries that allow later milestones to add cross-job matching, review queues, and Phase 2 AI enrichment without rewriting the foundation." - ], - "key_risks": [ - { - "risk": "Existing Gmail logic is job-local and may be too coupled to the per-job correspondence dialog.", - "whyItMatters": "If the ingestion/model layer stays job-scoped, later confidence routing and global inbox work will become a rewrite instead of an extension." - }, - { - "risk": "OAuth/token handling may work for connect/import but not for repeatable sync lifecycle and recovery.", - "whyItMatters": "Phase 1 must be stable even when Gmail is disconnected, tokens expire, or sync partially fails." - } - ], - "proof_strategy": [ - { - "riskOrUnknown": "Can Gmail foundation be extracted from the current job-local import flow without regressions?", - "retireIn": "M006/S01-S02", - "whatWillBeProven": "Connection, token, and normalized ingestion seams work while existing GmailController behavior remains testable." - }, - { - "riskOrUnknown": "Will the data contract support later review queues and unmatched-thread workflows?", - "retireIn": "M006/S02-S03", - "whatWillBeProven": "Stored metadata and sync-state models can drive global correspondence and matching milestones without schema churn." - } - ], - "verification_contract": "Focused backend tests for Gmail service/controller seams plus focused frontend tests for connection/sync-state surfaces must pass.", - "verification_integration": "Existing per-job correspondence Gmail flows must remain functional while new shared ingestion/state seams are introduced.", - "verification_operational": "Admin/system visibility for Gmail/OAuth/sync status should remain diagnosable and non-destructive.", - "verification_uat": "A local user can connect Gmail (or see clear not-configured state), inspect sync status, and exercise the foundation UI without breaking the existing workspace.", - "definition_of_done": [ - "OAuth and sync-state foundations are in place and documented.", - "Existing Gmail per-job flows still pass focused regression tests.", - "Foundation code is ready for cross-job ingestion/linking milestones without hidden rewrites." - ], - "requirement_coverage": "Advances R001, R002, R007, and R010 as the platform base for broader correspondence integration while preserving R008/R015 manual-send boundaries.", - "boundary_map_markdown": "- **OAuth / token boundary:** Gmail account connection, token storage, refresh, and disconnect lifecycle.\n- **Ingestion boundary:** raw Gmail API responses normalized into internal message/thread/attachment metadata.\n- **Job-linking boundary:** deferred to later milestones; M006 should not hardwire ingestion to a single job UI.\n- **AI boundary:** Phase 2 interfaces prepared only; no hard dependency introduced in foundation." - }, - { - "id": "M007", - "title": "", - "status": "queued", - "depends_on": [], - "created_at": "2026-04-01T13:42:29.599Z", - "completed_at": null, - "vision": "Phase 2 of the feature sequence: convert the Gmail foundation into durable ingestion with manual sync, historical backfill, stable deduplication, and stored attachment metadata.", - "success_criteria": [ - "Manual Gmail sync works for a useful historical window and excludes spam/trash by default.", - "Imported emails are deduplicated by Gmail message id.", - "Stored records preserve message/thread metadata and attachment metadata needed by later milestones." - ], - "key_risks": [ - { - "risk": "Backfill and dedup can create noisy duplicates or partial state when the same Gmail messages appear through multiple queries.", - "whyItMatters": "Phase 1 trust depends on stable ingestion more than fancy matching." - }, - { - "risk": "Attachment handling may overcomplicate ingestion too early.", - "whyItMatters": "Phase 1 needs metadata support, not a heavyweight document-processing rewrite." - } - ], - "proof_strategy": [ - { - "riskOrUnknown": "Can manual sync/backfill ingest useful history without duplicate churn?", - "retireIn": "M007/S01-S02", - "whatWillBeProven": "Message-id dedup and repeatable sync state survive reruns." - }, - { - "riskOrUnknown": "Can attachment metadata be captured cheaply enough for Phase 1?", - "retireIn": "M007/S02", - "whatWillBeProven": "Attachment names/types/sizes are stored and surfaced without deep attachment parsing." - } - ], - "verification_contract": "Focused backend tests for sync, dedup, and attachment metadata must pass.", - "verification_integration": "M006 foundation remains intact while sync/backfill and dedup are layered on.", - "verification_operational": "Sync runs produce visible counts and failure state.", - "verification_uat": "A manual sync/backfill can be run twice without duplicate churn.", - "definition_of_done": [ - "Manual sync and historical backfill work against the shared ingestion seam.", - "Deduplication by Gmail message id is enforced and tested.", - "Attachment metadata is stored without breaking existing correspondence flows." - ], - "requirement_coverage": "Advances R001, R002, R007, and R010 by making Gmail correspondence a durable imported history rather than one-off imports.", - "boundary_map_markdown": "- **Sync boundary:** manual sync/backfill orchestrates Gmail fetches into normalized storage.\n- **Dedup boundary:** Gmail message id is the canonical duplicate key; thread grouping is secondary.\n- **Attachment boundary:** store metadata now, defer heavy attachment content extraction until explicitly needed." - }, - { - "id": "M008", - "title": "Smart Gmail Job Correspondence Integration — matching and routing", - "status": "pending", - "depends_on": [], - "created_at": "2026-04-01T13:45:43.577Z", - "completed_at": null, - "vision": "Phase 3 of the feature sequence: route Gmail correspondence across jobs with deterministic scoring, explicit confidence handling, and safe suggestion workflows.", - "success_criteria": [ - "Deterministic matching scores Gmail correspondence across jobs using normalized recruiter/company/role/thread signals.", - "High-confidence matches auto-link, medium-confidence matches queue for review, and low-confidence items remain unmatched.", - "Job-related unmatched threads can become suggested new jobs without silently mutating tracked jobs." - ], - "key_risks": [ - { - "risk": "Cross-job matching can become opaque if scoring reasons are not durable.", - "whyItMatters": "The user must be able to trust why a thread auto-linked or entered review." - }, - { - "risk": "Auto-linking can create false positives if normalization is weak.", - "whyItMatters": "Phase 1 value depends on confidence routing, not just broad import volume." - } - ], - "proof_strategy": [ - { - "riskOrUnknown": "Can deterministic scoring route matches safely without AI?", - "retireIn": "M008/S01-S02", - "whatWillBeProven": "High/medium/low confidence thresholds produce reviewable behavior with stored reasons." - }, - { - "riskOrUnknown": "Can unmatched but job-related Gmail threads become useful suggestions?", - "retireIn": "M008/S03", - "whatWillBeProven": "Suggestion flow surfaces likely job threads without silently creating bad data." - } - ], - "verification_contract": "Focused backend tests for scoring, routing, and suggestion logic must pass.", - "verification_integration": "Linked items must appear as correspondence on the correct job without status drift.", - "verification_operational": "Stored reasons and confidence must make routing diagnosable.", - "verification_uat": "A user can review medium-confidence matches and accept or reject them with clear reasons.", - "definition_of_done": [ - "Confidence-based Gmail-to-job linking exists with explicit routing.", - "High-confidence matches auto-link and medium-confidence items queue for review.", - "Unmatched job-related threads can surface as suggestions without overriding core job status." - ], - "requirement_coverage": "Advances R001, R002, R007, and R010 by linking Gmail correspondence across jobs with explicit confidence routing.", - "boundary_map_markdown": "- **Matching boundary:** deterministic rules and normalization score messages/threads against jobs.\n- **Routing boundary:** high/medium/low confidence routes must remain explicit and reviewable.\n- **Suggestion boundary:** unmatched job-related threads can become suggested new jobs without silently creating canonical jobs." - }, - { - "id": "M009", - "title": "Smart Gmail Job Correspondence Integration — correspondence UX", - "status": "pending", - "depends_on": [], - "created_at": "2026-04-01T13:45:43.578Z", - "completed_at": null, - "vision": "Phase 4 of the feature sequence: make Gmail correspondence part of the daily workflow through a global inbox, per-job timelines, and manual review controls.", - "success_criteria": [ - "A global correspondence inbox exists for linked, review, and unmatched Gmail items.", - "Per-job conversation timelines show Gmail-backed correspondence coherently.", - "Users can review, relink, unlink, add notes, and filter correspondence without hidden state changes." - ], - "key_risks": [ - { - "risk": "Inbox and review UX can become noisy if all confidence states are mixed together.", - "whyItMatters": "Phase 1 needs triage clarity, not just more screens." - }, - { - "risk": "Relink/unlink can break trust if actions are irreversible or poorly explained.", - "whyItMatters": "Users need manual control over correspondence history." - } - ], - "proof_strategy": [ - { - "riskOrUnknown": "Can a global inbox stay useful rather than overwhelming?", - "retireIn": "M009/S01", - "whatWillBeProven": "Inbox grouping/filtering keeps review and unmatched flows manageable." - }, - { - "riskOrUnknown": "Can per-job timelines and review actions remain coherent?", - "retireIn": "M009/S02-S03", - "whatWillBeProven": "Relink/unlink/notes/filter flows preserve correspondence history and user control." - } - ], - "verification_contract": "Focused frontend and integration tests for inbox/timeline/review flows must pass.", - "verification_integration": "Global inbox and per-job views must stay consistent after review actions.", - "verification_operational": "Review decisions and sync freshness must be visible.", - "verification_uat": "A user can triage correspondence globally and inspect the same linked thread inside a job timeline.", - "definition_of_done": [ - "A global correspondence inbox exists.", - "Per-job timeline and review flows are coherent and reversible.", - "Filters and notes make the correspondence surfaces practically useful." - ], - "requirement_coverage": "Advances R005, R006, R007, and R010 by turning imported Gmail correspondence into a daily-use triage and timeline surface.", - "boundary_map_markdown": "- **Inbox boundary:** global correspondence inbox summarizes imported/linked/review items across jobs.\n- **Job timeline boundary:** per-job conversation timeline shows linked Gmail correspondence cleanly beside existing notes/imported messages.\n- **Review boundary:** relink/unlink/notes/filters remain manual and auditable." - }, - { - "id": "M010", - "title": "Smart Gmail Job Correspondence Integration — Phase 2 preparation", - "status": "pending", - "depends_on": [], - "created_at": "2026-04-01T13:45:43.578Z", - "completed_at": null, - "vision": "Phase 5 of the feature sequence: prepare clean extension points and documentation for future LLM-assisted correspondence intelligence without overbuilding or making Phase 1 dependent on AI.", - "success_criteria": [ - "Future semantic matching and extraction/classification seams are prepared in code without over-implementing AI behavior.", - "Phase 1 remains fully valuable when Ollama/AI is disabled.", - "Phase 2 documentation clearly describes safe additions for semantic matching, entity extraction, stage hints, interview extraction, and follow-up/reply suggestion generation." - ], - "key_risks": [ - { - "risk": "Phase 2 prep can overbuild abstractions before real needs stabilize.", - "whyItMatters": "Preparation should reduce risk, not add speculative complexity." - }, - { - "risk": "Future AI enrichment could bypass deterministic evidence and reduce trust.", - "whyItMatters": "Phase 1 must remain valuable and understandable without AI." - } - ], - "proof_strategy": [ - { - "riskOrUnknown": "Can future AI seams be prepared without hard-coding premature behavior?", - "retireIn": "M010/S01", - "whatWillBeProven": "Interfaces/storage hooks exist without forcing live AI code into Phase 1." - }, - { - "riskOrUnknown": "Will future contributors understand the intended semantic-matching roadmap?", - "retireIn": "M010/S02", - "whatWillBeProven": "Phase 2 docs clearly explain semantic matching, extraction, stage hints, and follow-up suggestion directions." - } - ], - "verification_contract": "Focused tests ensure no Phase 1 behavior regresses when Phase 2 seams are present.", - "verification_integration": "Phase 2 seams do not change Phase 1 runtime behavior unless an implementation is explicitly wired in later.", - "verification_operational": "Docs and code make future enrichment attach evidence instead of silently mutating tracked state.", - "verification_uat": "No dedicated UAT beyond confirming Phase 1 behavior remains unchanged while docs/interfaces exist.", - "definition_of_done": [ - "Phase 2 interfaces and data seams are documented and lightly scaffolded.", - "No Phase 1 feature depends on AI being enabled.", - "Future semantic matching/classification work has a safe extension path." - ], - "requirement_coverage": "Prepares deferred correspondence/AI requirements while preserving Phase 1 independence and the manual-control constraints R008/R015.", - "boundary_map_markdown": "- **LLM seam boundary:** future semantic disambiguation lives behind explicit interfaces/services.\n- **Extraction boundary:** future AI-derived hints attach to stored correspondence/match evidence instead of replacing deterministic truth.\n- **Suggestion boundary:** future drafting/classification remains optional and non-autonomous." - }, - { - "id": "M011", - "title": "Platform hardening across frontend, API, AI service, and Ollama", - "status": "complete", - "depends_on": [], - "created_at": "2026-04-10T16:32:03.760Z", - "completed_at": "2026-04-10T23:25:36.520Z", - "vision": "Retire the highest-risk security, reliability, UX, and operability gaps across the full stack without losing the existing product surface. The outcome should be a safer frontend platform, stronger auth/session handling, clearer degraded-state behavior, a slimmer and more maintainable API startup path, and an AI/Ollama integration that is observable, bounded, and explicit about its capabilities.", - "success_criteria": [ - "The application has a materially safer security posture across frontend, API, and AI integrations.", - "Core user workflows remain functional while degraded states become explicit and actionable.", - "Startup, auth, and AI runtime behavior are easier to operate, test, and debug.", - "The platform is positioned for continued feature work without carrying today’s highest-risk technical debt." - ], - "key_risks": [ - { - "risk": "Frontend build modernization can break existing CRA-based tests and deployment assumptions.", - "whyItMatters": "Security remediation is tied to outdated build tooling, so the migration path has to preserve behavior while reducing risk." - }, - { - "risk": "Auth migration from localStorage/sessionStorage to cookies can break login, Gmail, and admin flows if done piecemeal.", - "whyItMatters": "Session changes affect every protected route and must be rolled out with clear compatibility and verification coverage." - }, - { - "risk": "`Program.cs` refactoring may destabilize startup, migrations, or local/dev bootstrap behavior.", - "whyItMatters": "The current app concentrates operational logic in startup; untangling it safely requires preserving current environment behavior." - }, - { - "risk": "AI service changes can reduce perceived product quality if fallback behavior becomes slower or less capable.", - "whyItMatters": "The app now relies on OCR, summarization, and Ollama-assisted enrichment in key package-generation flows." - } - ], - "proof_strategy": [ - { - "riskOrUnknown": "Whether the frontend can reduce current dependency/security debt without breaking builds", - "retireIn": "S01", - "whatWillBeProven": "A passing frontend build/test path on the new dependency baseline, plus a reduced audit report." - }, - { - "riskOrUnknown": "Whether secure session storage can replace browser-stored bearer tokens without breaking user flows", - "retireIn": "S02", - "whatWillBeProven": "Login/logout/profile/admin flows operating on cookie-based auth with explicit CSRF/session behavior." - }, - { - "riskOrUnknown": "Whether degraded backend/API states can be made obvious without harming normal UX", - "retireIn": "S03", - "whatWillBeProven": "Browser-level evidence showing clear empty/error/offline distinctions and centralized client data handling." - }, - { - "riskOrUnknown": "Whether backend hardening can land without regressions to startup, file handling, and admin functions", - "retireIn": "S04-S05", - "whatWillBeProven": "Passing API tests, startup validation, and explicit security/operability checks for headers, throttling, and uploads." - }, - { - "riskOrUnknown": "Whether the AI/Ollama stack can be simplified and made predictable without losing required capabilities", - "retireIn": "S06", - "whatWillBeProven": "Measured AI-service contracts, documented fallback modes, and verified admin/runtime visibility of capability status." - } - ], - "verification_contract": "Each slice must produce concrete verification at the layer it changes: frontend build/tests/browser assertions, API diagnostics/tests, startup/runtime checks, dependency audit deltas, and AI-service health/behavior evidence.", - "verification_integration": "Cross-slice verification must include browser-backed checks of login/session behavior, degraded API handling, admin/system telemetry, and at least one AI-assisted workflow path.", - "verification_operational": "Operational verification must prove startup safety, header/rate-limit posture, session/auth stability, and AI capability reporting in the admin/system surface.", - "verification_uat": "UAT focuses on the user-visible outcomes: clear auth behavior, no misleading empty states during outages, stable job/workspace flows, and understandable AI capability/degraded-state feedback.", - "definition_of_done": [ - "Frontend dependency risk materially reduced, with the critical direct vulnerability removed and build tooling direction settled.", - "Authentication/session handling no longer depends on browser-stored bearer tokens for the primary local auth path.", - "The UI distinguishes empty data from backend/API failure and presents actionable degraded-state guidance.", - "API startup/bootstrap responsibilities are separated enough to be testable and safer to deploy.", - "Core security controls are in place: rate limiting, stricter headers, production-safe CORS posture, and safer logging/file handling.", - "AI/Ollama behavior is contractually clear, observable, and degraded modes are explicit in both runtime metrics and UI." - ], - "requirement_coverage": "Advances security, reliability, operability, and UX quality requirements across the whole stack: frontend shell, API, Gmail integrations, admin tools, AI service, and Ollama-backed features.", - "boundary_map_markdown": "- **Frontend shell (`job-tracker-ui/`)** owns navigation, auth UX, degraded states, data fetching, and admin surfaces.\n- **API (`JobTrackerApi/`, `Data/`, `Models/`)** owns auth/session issuance, authorization, rate limiting, startup/bootstrap, file handling, Gmail integration, and system telemetry.\n- **AI service (`tools/summarizer/`)** owns OCR, summarization, CV normalization/classification, and Ollama reachability contracts.\n- **Infrastructure (`docker-compose.yml`, Dockerfiles, nginx.conf`)** owns proxy/security headers, service topology, and deployment/runtime defaults.\n- **Cross-cutting concerns**: security posture, observability, performance budgets, and failure-mode UX must be verified slice-by-slice end to end." - } - ], - "slices": [ - { - "milestone_id": "M001", - "id": "S01", - "title": "Smarter Gmail import and matching", - "status": "complete", - "risk": "high", - "depends": [], - "demo": "", - "created_at": "2026-03-28T22:02:57.776Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Finish S01 by turning the existing job-aware Gmail import flow into a live linked-thread continuity loop for one job workspace.", - "success_criteria": "", - "proof_level": "", - "integration_closure": "", - "observability_impact": "", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M001", - "id": "S02", - "title": "Stronger AI application package drafting", - "status": "complete", - "risk": "high", - "depends": [ - "S01" - ], - "demo": "", - "created_at": "2026-03-28T22:02:57.777Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Make the application package generator use imported job/correspondence context well enough that tailored CV, cover-letter, and recruiter-message drafts feel specific, credible, and worth starting from inside the job workspace.", - "success_criteria": "", - "proof_level": "", - "integration_closure": "", - "observability_impact": "", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M001", - "id": "S03", - "title": "Reply and follow-up drafting from real thread context", - "status": "complete", - "risk": "medium", - "depends": [ - "S01", - "S02" - ], - "demo": "", - "created_at": "2026-03-28T22:02:57.777Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Make follow-up drafting use imported correspondence and saved application material well enough that the job workspace can produce specific, trustworthy follow-up and reply drafts without crossing the manual-send boundary.", - "success_criteria": "", - "proof_level": "", - "integration_closure": "", - "observability_impact": "", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M001", - "id": "S04", - "title": "Daily control loop surfaces", - "status": "complete", - "risk": "medium", - "depends": [ - "S01", - "S03" - ], - "demo": "", - "created_at": "2026-03-28T22:02:57.777Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Make the job table, reminders view, and dashboard behave like one daily control loop so the user can scan what needs attention and jump directly into the right job workspace state.", - "success_criteria": "", - "proof_level": "", - "integration_closure": "", - "observability_impact": "", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M001", - "id": "S05", - "title": "End-to-end trust and workflow polish", - "status": "complete", - "risk": "low", - "depends": [ - "S01", - "S02", - "S03", - "S04" - ], - "demo": "", - "created_at": "2026-03-28T22:02:57.778Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Prove the full daily-use loop as one trustworthy workflow by tightening shared next-action/readiness signals, then validating overview → workspace → package → Gmail continuity → follow-up behavior without weakening the manual-send boundary.", - "success_criteria": "", - "proof_level": "", - "integration_closure": "", - "observability_impact": "", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M001", - "id": "S06", - "title": "Live environment stabilization and integrated acceptance rerun", - "status": "complete", - "risk": "high", - "depends": [ - "S05" - ], - "demo": "", - "created_at": "2026-03-28T22:02:57.778Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Live environment is repeatably startable and preflighted, seeded with acceptance-ready data, and the integrated daily loop is re-verified with a recorded artifact proving the manual-send boundary and individual-first workflow.", - "success_criteria": "", - "proof_level": "", - "integration_closure": "", - "observability_impact": "", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M001", - "id": "S07", - "title": "Daily-loop UAT artifact closure", - "status": "complete", - "risk": "medium", - "depends": [ - "S06" - ], - "demo": "", - "created_at": "2026-03-28T22:02:57.778Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Publish the executed daily-loop UAT closure artifact proving one seeded job stays coherent across /jobs, the job workspace, /reminders, and /dashboard using the existing S06 acceptance runner.", - "success_criteria": "", - "proof_level": "", - "integration_closure": "", - "observability_impact": "", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M005", - "id": "S01", - "title": "Canonical CV data model and artifact pipeline", - "status": "pending", - "risk": "high", - "depends": [], - "demo": "After this slice, a CV upload persists original artifact + extraction run metadata + canonical structured profile shell, and the system can reprocess against stored source data instead of requiring a re-upload.", - "created_at": "2026-03-28T22:04:42.691Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Introduce the persistence and API foundations for artifact-preserving multi-pass CV extraction.", - "success_criteria": "- Upload pipeline stores original file metadata, extracted text, normalized text, extractor version, and run status.\n- Canonical structured CV model can store field-level confidence/provenance without breaking current profile flows.\n- Reprocess endpoint/command can re-run extraction from stored artifacts.\n- Existing profile CV upload/parse flows remain backward-compatible.", - "proof_level": "Foundational schema/API proof with focused backend tests and no-regression verification on current profile flows.", - "integration_closure": "The new canonical model coexists with existing ProfileCvText/ProfileCvStructureJson while introducing extraction-run persistence and future migration seams.", - "observability_impact": "Adds explicit extraction-run status, version, and failure surfaces for debugging document ingestion.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M005", - "id": "S02", - "title": "Multi-pass extraction and review UX", - "status": "pending", - "risk": "high", - "depends": [ - "S01" - ], - "demo": "After this slice, uploading a noisy PDF populates the structured CV editor with confidence/provenance markers, reviewable fields, and a reprocess action instead of mostly falling into General.", - "created_at": "2026-03-28T22:04:42.691Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Implement the smarter universal extractor and the user review loop around uncertain fields.", - "success_criteria": "- Pass A/B/C/D extraction pipeline is implemented with generic heuristics, layout grouping inputs where available, LLM normalization, and validation/repair.\n- Structured editor surfaces confidence/provenance and visually flags uncertain fields.\n- User can accept/edit fields and save the canonical profile.\n- Reprocessing produces a new extraction run without destroying accepted canonical data until the user chooses to apply it.", - "proof_level": "Backend/frontend extraction proof with OCR-like regression fixtures plus structured-editor review tests.", - "integration_closure": "The profile page consumes canonical extraction runs cleanly and no longer depends on raw text-only section blobs for structured editing.", - "observability_impact": "Adds per-field extraction method/confidence and review-state visibility.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M005", - "id": "S03", - "title": "Tailored CV draft layer", - "status": "pending", - "risk": "medium", - "depends": [ - "S02" - ], - "demo": "After this slice, a job can have its own editable tailored CV draft generated from the canonical profile without mutating the master CV or raw source text.", - "created_at": "2026-03-28T22:04:42.691Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Create the per-job tailored CV draft model and generation/edit/save flow.", - "success_criteria": "- Tailored CV drafts persist separately from master profile and existing raw CV text.\n- Generation uses canonical structured profile first, with raw-text fallback where needed.\n- Users can edit and save tailored drafts per job.\n- Existing application-package flows integrate or coexist cleanly with the new draft layer.", - "proof_level": "Job-scoped generation proof with backend draft tests and workspace UI save/regeneration coverage.", - "integration_closure": "The job workspace gains a stable tailored CV draft layer that can later feed export without regressing existing drafting features.", - "observability_impact": "Adds explicit draft generation/source-version metadata and regeneration failure surfaces.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M005", - "id": "S04", - "title": "Template preview and first PDF export", - "status": "pending", - "risk": "medium", - "depends": [ - "S03" - ], - "demo": "After this slice, the user can choose an ATS Minimal template, preview the tailored CV exactly as rendered, and download a matching PDF.", - "created_at": "2026-03-28T22:04:42.691Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Ship the first deterministic HTML/CSS renderer with preview and PDF download.", - "success_criteria": "- One HTML/CSS template renders tailored draft data deterministically.\n- Preview and PDF use the same renderer/path.\n- User can explicitly download a generated PDF.\n- PDF export failures show actionable error states.\n- Export remains manual and individual-first.", - "proof_level": "End-to-end render/export proof with renderer tests and browser/UAT evidence that preview matches the downloaded PDF path.", - "integration_closure": "The job workspace can hand off tailored draft data into preview/export without mutating canonical profile or outbound communication flows.", - "observability_impact": "Adds render/export job status and error detail surfaces.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M005", - "id": "S05", - "title": "Template library and layout controls", - "status": "pending", - "risk": "medium", - "depends": [ - "S04" - ], - "demo": "After this slice, the user can switch among ATS Minimal, Modern Professional, and Compact Technical templates and control photo visibility, page length, accent color, section order, and bullet density before export.", - "created_at": "2026-03-28T22:04:42.691Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Expand export flexibility while keeping rendering deterministic and draft-driven.", - "success_criteria": "- Three initial templates are supported.\n- Layout controls are persisted on the tailored draft/render-options layer.\n- Users can preview template/control changes before export.\n- Section ordering and density controls do not corrupt the canonical profile.\n- UAT proves the full upload → review → tailor → preview → export loop.", - "proof_level": "Product-level proof with multi-template rendering verification and end-to-end UAT.", - "integration_closure": "The export system scales beyond one hardcoded template while preserving the same draft model and preview/export contract.", - "observability_impact": "Adds template/render-option visibility to preview/export diagnostics.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M006", - "id": "S01", - "title": "Gmail account and sync foundation", - "status": "pending", - "risk": "high", - "depends": [], - "demo": "After this slice, Gmail connection/sync state is explicit and durable instead of being hidden inside the current correspondence tab.", - "created_at": "2026-04-01T13:42:13.489Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Harden Gmail account connection, token lifecycle, and visible sync-state tracking for one local user.", - "success_criteria": "Connection/disconnect/status flows are explicit, durable, and covered by focused tests.", - "proof_level": "Focused backend + frontend proof.", - "integration_closure": "Existing correspondence Gmail connect/import entry points still work against the refactored foundation.", - "observability_impact": "Add durable Gmail sync/connection state surfaces and actionable error reporting.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M006", - "id": "S02", - "title": "Normalized Gmail ingestion contract", - "status": "pending", - "risk": "high", - "depends": [ - "S01" - ], - "demo": "After this slice, Gmail messages/threads/labels/attachment metadata have an explicit shared contract rather than ad-hoc per-job import details.", - "created_at": "2026-04-01T13:42:13.489Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Introduce normalized Gmail ingestion/storage models and services that later milestones can reuse for cross-job workflows.", - "success_criteria": "A shared ingestion contract exists and is covered by focused tests without breaking current message/thread import behavior.", - "proof_level": "Backend contract proof.", - "integration_closure": "Current GmailController import/refresh behavior is preserved on top of the new service/model seam.", - "observability_impact": "Expose ingestion counts, dedup signals, and failure reasons.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M006", - "id": "S03", - "title": "Phase 2 extension seam", - "status": "pending", - "risk": "medium", - "depends": [ - "S01", - "S02" - ], - "demo": "After this slice, the codebase has explicit interfaces for future AI-assisted matching/classification without any forced AI dependency in Phase 1 foundation.", - "created_at": "2026-04-01T13:42:13.489Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Define low-risk extension points for future semantic matching, extraction, and stage hinting.", - "success_criteria": "Phase 2 interfaces and docs exist, and the foundation remains fully useful with deterministic-only logic.", - "proof_level": "Code + docs proof.", - "integration_closure": "No current user-facing behavior depends on the future AI seam.", - "observability_impact": "Document and surface where future enrichment reasons/confidence can attach.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M007", - "id": "S01", - "title": "Manual sync and backfill", - "status": "pending", - "risk": "high", - "depends": [], - "demo": "After this slice, a user can manually sync Gmail history into the normalized store with a bounded backfill window.", - "created_at": "2026-04-01T13:45:43.577Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Implement manual sync orchestration and backfill window handling.", - "success_criteria": "Manual sync/backfill runs and records counts plus sync state.", - "proof_level": "Backend integration proof.", - "integration_closure": "Uses the M006 foundation without changing matching semantics yet.", - "observability_impact": "Expose sync start/end state, counts, and last-success timestamps.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M007", - "id": "S02", - "title": "Deduplication and attachment metadata", - "status": "pending", - "risk": "high", - "depends": [ - "S01" - ], - "demo": "After this slice, rerunning sync does not create duplicates and attachment metadata is visible for imported Gmail messages.", - "created_at": "2026-04-01T13:45:43.577Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Enforce message-id deduplication and store attachment metadata.", - "success_criteria": "Dedup and attachment metadata are covered by tests and visible in the data contract.", - "proof_level": "Backend + focused UI proof.", - "integration_closure": "Imported Gmail messages can be safely re-seen across reruns and query overlap.", - "observability_impact": "Add duplicate-skipped counts and attachment metadata surfaces.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M008", - "id": "S01", - "title": "Deterministic matching engine", - "status": "pending", - "risk": "high", - "depends": [], - "demo": "After this slice, imported Gmail messages/threads can be scored against all relevant jobs with durable reasons.", - "created_at": "2026-04-01T13:45:43.577Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Build deterministic cross-job scoring and normalization for Gmail-to-job matching.", - "success_criteria": "Scoring and normalization are deterministic, explainable, and tested.", - "proof_level": "Backend logic proof.", - "integration_closure": "Consumes M006/M007 seams without introducing AI dependency.", - "observability_impact": "Persist reasons, confidence, and normalization hits for every routing decision.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M008", - "id": "S02", - "title": "Confidence routing and review queue", - "status": "pending", - "risk": "high", - "depends": [ - "S01" - ], - "demo": "After this slice, high-confidence matches auto-link and medium-confidence items enter an explicit review queue.", - "created_at": "2026-04-01T13:45:43.577Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Implement confidence routing and reviewable linking outcomes.", - "success_criteria": "High/medium/low routing is visible, tested, and reversible.", - "proof_level": "Backend + focused UI proof.", - "integration_closure": "Linked correspondence lands in the right job without overriding primary job state.", - "observability_impact": "Expose auto-linked, queued, and unmatched counts plus reasons.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M008", - "id": "S03", - "title": "Suggested new jobs from unmatched threads", - "status": "pending", - "risk": "medium", - "depends": [ - "S01", - "S02" - ], - "demo": "After this slice, unmatched job-like Gmail threads appear as suggested new jobs instead of being dropped.", - "created_at": "2026-04-01T13:45:43.577Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Create a suggestion flow for unmatched job-related threads.", - "success_criteria": "Unmatched job-related Gmail threads can be reviewed as job suggestions.", - "proof_level": "Workflow proof.", - "integration_closure": "Suggestions remain separate from canonical jobs until user action.", - "observability_impact": "Track suggestion reasons and conversion decisions.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M009", - "id": "S01", - "title": "Global correspondence inbox", - "status": "pending", - "risk": "high", - "depends": [], - "demo": "After this slice, a user can triage correspondence globally instead of only inside one job dialog.", - "created_at": "2026-04-01T13:45:43.578Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Build the global correspondence inbox with confidence-aware filtering.", - "success_criteria": "Global inbox exists with useful filters for linked/review/unmatched correspondence.", - "proof_level": "Frontend workflow proof.", - "integration_closure": "Consumes linked/review/suggestion states from prior milestones.", - "observability_impact": "Expose inbox counts by state and sync freshness.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M009", - "id": "S02", - "title": "Per-job conversation timeline", - "status": "pending", - "risk": "medium", - "depends": [ - "S01" - ], - "demo": "After this slice, each job shows a cleaner Gmail-backed conversation timeline rather than isolated imported items.", - "created_at": "2026-04-01T13:45:43.578Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Enhance per-job timeline with Gmail thread continuity and linking context.", - "success_criteria": "Per-job correspondence timeline cleanly reflects linked Gmail history.", - "proof_level": "Integration proof.", - "integration_closure": "Per-job correspondence remains the detailed workspace for one job.", - "observability_impact": "Show thread/group metadata and link state in the timeline.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M009", - "id": "S03", - "title": "Review flow and manual controls", - "status": "pending", - "risk": "medium", - "depends": [ - "S01", - "S02" - ], - "demo": "After this slice, users can review, relink, unlink, and annotate correspondence with confidence.", - "created_at": "2026-04-01T13:45:43.578Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Implement review actions, notes, and practical correspondence filters.", - "success_criteria": "Review flow is reversible, filterable, and covered by focused tests.", - "proof_level": "User workflow proof.", - "integration_closure": "Manual review actions update inbox and per-job views consistently.", - "observability_impact": "Track review decisions and relink/unlink actions.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M010", - "id": "S01", - "title": "LLM-assisted extension interfaces", - "status": "pending", - "risk": "medium", - "depends": [], - "demo": "After this slice, the codebase has explicit interfaces for future semantic matching and extraction enrichment.", - "created_at": "2026-04-01T13:45:43.578Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Scaffold low-risk Phase 2 extension points in code and storage models.", - "success_criteria": "Future semantic matching/extraction interfaces exist without adding live Phase 2 dependency.", - "proof_level": "Code seam proof.", - "integration_closure": "Phase 1 runs exactly as before when no AI enrichment implementation is registered.", - "observability_impact": "Future enrichment reasons/confidence can be attached alongside deterministic evidence.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M010", - "id": "S02", - "title": "Phase 2 design documentation", - "status": "pending", - "risk": "low", - "depends": [ - "S01" - ], - "demo": "After this slice, contributors can continue with Phase 2 from docs instead of rediscovery.", - "created_at": "2026-04-01T13:45:43.578Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", - "goal": "Document Phase 2 design for semantic matching, extraction, stage/status hints, interview extraction, and drafting suggestions.", - "success_criteria": "Phase 2 roadmap/docs exist and clearly separate safe future AI work from Phase 1 behavior.", - "proof_level": "Documentation proof.", - "integration_closure": "Documentation maps future AI features onto the Phase 1 data and service seams.", - "observability_impact": "Docs define how future enrichment reasons/confidence should be stored and surfaced.", - "sequence": 0, - "replan_triggered_at": null - }, - { - "milestone_id": "M011", - "id": "S01", - "title": "S01", - "status": "complete", - "risk": "high", - "depends": [], - "demo": "The frontend builds on a safer dependency baseline, the critical audit issue is retired, and the deployment/build path is documented and reproducible.", - "created_at": "2026-04-10T16:33:49.541Z", - "completed_at": "2026-04-10T16:47:38.386Z", - "full_summary_md": "---\nid: S01\nparent: M011\nmilestone: M011\nprovides:\n - A stable frontend baseline for S02 auth/session hardening\n - A documented proof point that the remaining frontend audit debt is primarily CRA/react-scripts transitive debt\n - A container-compatible frontend lockfile and build path\nrequires:\n []\naffects:\n - S02\n - S03\nkey_files:\n - job-tracker-ui/package.json\n - job-tracker-ui/package-lock.json\nkey_decisions:\n - D019 — remediate the direct critical frontend dependency immediately, keep the CRA baseline stable for the next slice, and defer broader build-tool migration work.\npatterns_established:\n - When frontend Docker uses a different Node/npm major version than the workstation, regenerate lockfiles with the container toolchain before trusting `npm ci` reproducibility.\n - Treat root-owned generated frontend build artifacts as environment contamination; clean them before drawing conclusions from local build failures.\n - Use the smallest safe dependency remediation first when the audit shows a single direct critical issue and the rest of the debt is trapped behind legacy build tooling.\nobservability_surfaces:\n - Frontend dependency audit before/after evidence\n - Reproducible local build verification\n - Reproducible container `npm ci` verification\n - Successful `docker compose build frontend` evidence\ndrill_down_paths:\n - .gsd/milestones/M011/slices/S01/tasks/T01-SUMMARY.md\n - .gsd/milestones/M011/slices/S01/tasks/T02-SUMMARY.md\n - .gsd/milestones/M011/slices/S01/tasks/T03-SUMMARY.md\nduration: \"\"\nverification_result: passed\ncompleted_at: 2026-04-10T16:47:38.387Z\nblocker_discovered: false\n---\n\n# S01: Frontend dependency and build modernization baseline\n\n**Retired the direct critical frontend dependency issue and restored a reproducible frontend build baseline for local and Docker workflows.**\n\n## What Happened\n\nThis slice established the real frontend dependency/build baseline, retired the direct critical audit finding, and restored a reproducible build path for both the local workstation and the frontend Docker image. The direct `axios` dependency was upgraded from `^1.13.6` to `^1.15.0`, which removed the only critical finding reported by the frontend audit. During verification, the frontend build initially failed because the checked-out `job-tracker-ui/build/static` directory contained root-owned artifacts from an earlier containerized build. I repaired that workspace contamination and then found a second environment issue: the lockfile generated under the local Node 25/npm 11 toolchain was not accepted by the Node 20/npm 10 toolchain used in the frontend Dockerfile. Regenerating the lockfile with the container toolchain restored `npm ci` and `docker compose build frontend` reproducibility. The resulting baseline is stable enough to support S02 auth/session work without compounding change risk. The remaining frontend audit debt is now clearly attributable to the older CRA/react-scripts build chain rather than a direct high-risk application dependency.\n\n## Verification\n\nVerified by rerunning the frontend audit, local build, container `npm ci`, and Docker image build after upgrading axios and regenerating the lockfile with the Node 20/npm 10 container toolchain. The full frontend test suite was also run to measure regression risk; 16 suites passed and 2 pre-existing workflow/package suites remain for follow-up.\n\n## Requirements Advanced\n\n- Frontend platform hardening baseline established and direct dependency risk reduced. — \n\n## Requirements Validated\n\nNone.\n\n## New Requirements Surfaced\n\n- Need a durable policy for lockfile generation when local and container npm versions differ.\n- Need targeted follow-up for pre-existing workflow/package UI test drift.\n\n## Requirements Invalidated or Re-scoped\n\nNone.\n\n## Deviations\n\nThe slice completed with a smaller code change than originally estimated because the immediate critical risk was isolated to a direct dependency upgrade. The main unexpected work was build-environment repair: root-owned frontend build artifacts and an npm-version-specific lockfile mismatch between the workstation and the Docker image.\n\n## Known Limitations\n\nThis slice did not remove the remaining transitive audit findings tied to `react-scripts`; it established a stable baseline and retired the direct critical issue. Full frontend audit cleanup still depends on follow-on platform migration work.\n\n## Follow-ups\n\nA broader frontend build-tool migration is still needed to retire the remaining CRA/react-scripts transitive audit debt. Two existing frontend workflow/package test suites also need targeted follow-up: `src/daily-control-loop.test.tsx` and `src/end-to-end-trust-loop.test.tsx`.\n\n## Files Created/Modified\n\n- `job-tracker-ui/package.json` — Upgraded the direct axios dependency to remove the critical frontend audit finding.\n- `job-tracker-ui/package-lock.json` — Refreshed and normalized the frontend lockfile so local install, container `npm ci`, and Docker image builds agree on the dependency graph.\n", - "full_uat_md": "# S01: Frontend dependency and build modernization baseline — UAT\n\n**Milestone:** M011\n**Written:** 2026-04-10T16:47:38.388Z\n\n# UAT\n\n## Scenario: frontend dependency hardening baseline\n1. Run `cd job-tracker-ui && npm audit --audit-level=moderate --json`.\n2. Confirm the report no longer contains a critical direct finding for `axios`.\n3. Run `cd job-tracker-ui && npm run build` and confirm the production build completes.\n4. Run `docker run --rm -v /home/pi/development/JobTracker/job-tracker-ui:/app -w /app node:20-alpine sh -lc 'npm ci --foreground-scripts=false'` and confirm the container toolchain accepts the lockfile.\n5. Run `cd /home/pi/development/JobTracker && docker compose build frontend` and confirm the frontend image builds successfully.\n\n## Expected result\n- The critical direct dependency issue is gone.\n- The frontend builds successfully both locally and in Docker.\n- Remaining audit debt is limited to transitive CRA/react-scripts tooling issues, not an unresolved direct critical dependency.\n", - "goal": "Reduce immediate frontend dependency and build-chain risk, retire the critical direct vulnerability, and establish a verified path for frontend build modernization without breaking current behavior.", - "success_criteria": "- The direct critical frontend dependency finding is removed.\n- The frontend build path is verified on the remediated dependency set.\n- The project has a concrete, tested build-tool direction: either a stabilized CRA baseline with risk retired or an implemented migration foundation with parity evidence.\n- The resulting frontend baseline is good enough to support S02 auth changes without compounding build uncertainty.", - "proof_level": "Code + dependency audit + build/test evidence", - "integration_closure": "The frontend still builds and runs against the existing API contract after dependency remediation, and the chosen build direction is documented by working code and verification evidence rather than notes alone.", - "observability_impact": "Adds dependency-audit evidence and build/test verification outputs so later slices can rely on a known frontend baseline.", - "sequence": 1, - "replan_triggered_at": null - }, - { - "milestone_id": "M011", - "id": "S02", - "title": "S02", - "status": "complete", - "risk": "high", - "depends": [], - "demo": "Users authenticate without browser-stored bearer tokens, protected routes still work, and admin/Gmail-sensitive paths remain accessible under the new session model.", - "created_at": "2026-04-10T16:33:49.541Z", - "completed_at": "2026-04-10T19:58:17.355Z", - "full_summary_md": "---\nid: S02\nparent: M011\nmilestone: M011\nprovides:\n - A safer session baseline for S03 degraded-state UX work.\n - A cookie/CSRF contract that downstream admin and Gmail flows can build on without reintroducing browser token storage.\nrequires:\n - slice: S01\n provides: stabilized frontend baseline for safe auth-layer changes.\naffects:\n - S03\nkey_files:\n - JobTrackerApi/Controllers/AuthController.cs\n - JobTrackerApi/Program.cs\n - JobTrackerApi/Services/AuthSessionOptions.cs\n - JobTrackerApi/Controllers/UsersController.cs\n - JobTrackerApi/appsettings.Development.json\n - job-tracker-ui/src/auth.ts\n - job-tracker-ui/src/api.ts\n - job-tracker-ui/src/App.tsx\n - job-tracker-ui/src/pages/LoginPage.tsx\n - job-tracker-ui/src/pages/ProfilePage.tsx\n - job-tracker-ui/src/components/GoogleAuthCard.tsx\n - job-tracker-ui/src/components/AuthStatusCard.tsx\n - job-tracker-ui/src/components/UserManagementCard.tsx\n - job-tracker-ui/src/themePrefs.ts\n - job-tracker-ui/src/login-page.test.tsx\n - JobTrackerApi.Tests/AuthAndSystemControllerTests.cs\nkey_decisions:\n - Adopt HttpOnly cookie-backed local app sessions with a separate CSRF cookie/header contract.\n - Treat `/auth/me` plus `auth-changed` as the frontend session truth source instead of localStorage/sessionStorage JWT reads.\n - Apply IP-partitioned rate limiting to login and auth-triggered email/reset paths.\npatterns_established:\n - Use server-issued cookies plus `/auth/me` for session truth instead of browser-stored access tokens.\n - Keep lightweight user-scoped client preferences separate from auth transport state.\n - Treat auth-sensitive email/reset paths as abuse-controlled surfaces, not ordinary API calls.\nobservability_surfaces:\n - Explicit CSRF cookie/header contract via `/api/auth/csrf`.\n - Cleaner frontend unauthorized/session-expired handling through `/auth/me` resolution and 401 cleanup.\n - Rate-limit rejection surface for login and auth-email endpoints.\ndrill_down_paths:\n - .gsd/milestones/M011/slices/S02/tasks/T01-SUMMARY.md\n - .gsd/milestones/M011/slices/S02/tasks/T02-SUMMARY.md\n - .gsd/milestones/M011/slices/S02/tasks/T03-SUMMARY.md\nduration: \"\"\nverification_result: passed\ncompleted_at: 2026-04-10T19:58:17.358Z\nblocker_discovered: false\n---\n\n# S02: Authentication and session hardening\n\n**Moved local auth to cookie-backed sessions, added CSRF and rate limiting, and verified protected-route behavior under the new model.**\n\n## What Happened\n\nThis slice replaced the app’s primary browser-stored bearer-token model with a cookie-backed local session contract and then hardened the sensitive auth edges around that change. T01 mapped every token assumption across frontend and API and established the target session model. T02 implemented the transport: the API now writes the local app JWT into secure cookies, exposes CSRF/logout endpoints, reads the local session from cookies, and the frontend now uses credentialed requests, `/auth/me`-based route resolution, client-side auth metadata only, and updated login/profile/admin/Google auth surfaces that no longer depend on localStorage/sessionStorage bearer tokens. T03 added abuse controls with IP-partitioned rate limiting for login and auth-email paths, updated tests to the new contract, and verified the core unauthenticated/protected-route behavior against a live frontend/API pair. During runtime verification I hit a misleading SQLite startup failure first; the root cause was launching the API outside the Development environment, which pointed it at an empty database. Restarting with `ASPNETCORE_ENVIRONMENT=Development` restored the expected local behavior, and I also added `http://localhost:3001` to development CORS to support live cookie-based verification from the local CRA server.\n\n## Verification\n\nVerified with focused API auth tests (`dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter FullyQualifiedName~Auth`), focused frontend auth tests (`cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/login-page.test.tsx src/profile-page.test.tsx`), frontend production build (`cd job-tracker-ui && npm run build`), direct HTTP checks for `GET /api/auth/csrf` and `GET /api/auth/me`, and a browser pass confirming unauthenticated `/jobs` redirects to `/login` without failed requests in the observed flow.\n\n## Requirements Advanced\n\nNone.\n\n## Requirements Validated\n\nNone.\n\n## New Requirements Surfaced\n\nNone.\n\n## Requirements Invalidated or Re-scoped\n\nNone.\n\n## Deviations\n\nBrowser-backed verification covered protected-route redirect and unauthenticated session behavior, but not a successful live login/logout round-trip because the existing local development database does not accept the placeholder admin password from `JobTrackerApi/appsettings.Development.json`.\n\n## Known Limitations\n\nA successful live browser login/logout pass was not completed in this environment because the seeded local admin account does not accept the placeholder development password. Google/Gmail-linked auth flows were preserved in code but not fully exercised end-to-end in the browser during this slice.\n\n## Follow-ups\n\nS03 should harden degraded-state UX on top of this new session model, especially explicit API-down vs empty-state handling and any remaining Google/Gmail compatibility checks that need a trusted local authenticated fixture.\n\n## Files Created/Modified\n\n- `JobTrackerApi/Controllers/AuthController.cs` — Issued cookie-backed auth sessions, CSRF cookies, logout endpoint, and updated auth contracts.\n- `JobTrackerApi/Program.cs` — Read local JWTs from cookies, enforced CSRF on mutating session requests, and added auth rate limiting.\n- `JobTrackerApi/Services/AuthSessionOptions.cs` — Centralized auth cookie/header names and cookie option helpers.\n- `job-tracker-ui/src/auth.ts` — Switched frontend request/auth helpers to credentialed requests and client metadata only.\n- `job-tracker-ui/src/api.ts` — Configured axios for cookie-backed sessions, CSRF headers, and 401 cleanup.\n- `job-tracker-ui/src/App.tsx` — Moved route protection to resolved `/auth/me` session state.\n- `job-tracker-ui/src/pages/LoginPage.tsx` — Removed token-storage assumptions from login, profile, status, Google auth, admin user management, and theme scoping.\n", - "full_uat_md": "# S02: Authentication and session hardening — UAT\n\n**Milestone:** M011\n**Written:** 2026-04-10T19:58:17.359Z\n\n# UAT — S02 Authentication and session hardening\n\n## What was exercised\n\n1. Start the API with `ASPNETCORE_ENVIRONMENT=Development ASPNETCORE_URLS=http://localhost:5202 dotnet run --project JobTrackerApi/JobTrackerApi.csproj`.\n2. Start the frontend with `cd job-tracker-ui && PORT=3001 BROWSER=none npm start`.\n3. Navigate to `http://localhost:3001/jobs` without an authenticated session.\n4. Confirm the app redirects to `/login` and shows the sign-in UI instead of rendering a protected route.\n5. Confirm the observed browser pass does not show failed requests for the redirect flow.\n6. Request `GET http://localhost:5202/api/auth/csrf` and confirm the response returns `204 No Content` with an `XSRF-TOKEN` cookie.\n7. Request `GET http://localhost:5202/api/auth/me` without a session and confirm it returns `401 Unauthorized`.\n\n## Result\n\n- Protected-route gating worked: unauthenticated `/jobs` redirected to `/login`.\n- The login screen rendered cleanly under the new session model.\n- CSRF bootstrap and unauthorized session responses matched the expected contract.\n- A successful live login/logout browser pass was not completed because the existing local development database does not accept the placeholder seeded admin password from config.\n\n", - "goal": "Replace the current browser-stored bearer token model with a safer primary session design, harden auth-sensitive endpoints against abuse, and preserve the app’s protected flows under the new transport.", - "success_criteria": "- Primary local auth no longer relies on localStorage/sessionStorage bearer tokens.\n- Login/logout/profile/admin flows are verified under the new session model.\n- Auth-sensitive endpoints have meaningful throttling / abuse controls.\n- Frontend unauthorized handling is explicit and coherent under the new session transport.", - "proof_level": "Code + integration + auth-flow verification", - "integration_closure": "Frontend and API must agree on how sessions are established, persisted, refreshed/expired, and invalidated. Protected routes, admin pages, and Gmail-sensitive paths must continue to function cleanly under the new auth model.", - "observability_impact": "Adds clearer session/auth diagnostics, explicit unauthorized-state behavior, and abuse-control signals for login and reset flows.", - "sequence": 2, - "replan_triggered_at": null - }, - { - "milestone_id": "M011", - "id": "S03", - "title": "S03", - "status": "complete", - "risk": "medium", - "depends": [], - "demo": "When the API is unavailable, the UI says so clearly instead of looking empty; normal data views use a centralized query/retry model and remain responsive.", - "created_at": "2026-04-10T16:33:49.541Z", - "completed_at": "2026-04-10T22:20:05.937Z", - "full_summary_md": "---\nid: S03\nparent: M011\nmilestone: M011\nprovides:\n - A resilient client layer that downstream AI and admin slices can rely on without reintroducing outage-as-empty UX.\n - Clearer failure semantics for S06 AI/Ollama capability work when backend-dependent views degrade.\nrequires:\n - slice: S02\n provides: cookie-backed auth/session transport and explicit unauthorized behavior.\naffects:\n - S04\n - S06\nkey_files:\n - job-tracker-ui/src/hooks/useViewResource.ts\n - job-tracker-ui/src/components/ViewStateNotice.tsx\n - job-tracker-ui/src/hooks/useCompanies.ts\n - job-tracker-ui/src/components/JobTable.tsx\n - job-tracker-ui/src/components/DashboardView.tsx\n - job-tracker-ui/src/components/CompaniesTable.tsx\n - job-tracker-ui/src/components/RemindersView.tsx\n - job-tracker-ui/src/components/KanbanBoard.tsx\n - job-tracker-ui/src/pages/ProfilePage.tsx\n - job-tracker-ui/src/daily-control-loop.test.tsx\nkey_decisions:\n - Use a lightweight shared async-view-state pattern instead of introducing a new global query framework during M011.\n - Make outage-state clarity a product requirement for the top-level views first: jobs, dashboard, reminders, companies, kanban, and profile.\npatterns_established:\n - Use `useViewResource` plus `ViewStateNotice` for top-level frontend data views that need distinct loading/empty/error/retry states.\n - Do not collapse transport failures into empty arrays/nulls on user-visible index pages.\n - Record environment-limited verification separately from product-behavior proof so slices stay honest.\nobservability_surfaces:\n - Explicit unavailable/error notices for jobs, dashboard, reminders, companies, kanban, and profile.\n - Shared retry surface via `ViewStateNotice` on core data views.\n - Clearer distinction between unauthorized/auth-required and general API-unavailable states on the client.\ndrill_down_paths:\n - .gsd/milestones/M011/slices/S03/tasks/T01-SUMMARY.md\n - .gsd/milestones/M011/slices/S03/tasks/T02-SUMMARY.md\n - .gsd/milestones/M011/slices/S03/tasks/T03-SUMMARY.md\nduration: \"\"\nverification_result: passed\ncompleted_at: 2026-04-10T22:20:05.940Z\nblocker_discovered: false\n---\n\n# S03: Resilience UX and client data layer\n\n**Made core frontend views show explicit unavailable states instead of masking API failures as empty data.**\n\n## What Happened\n\nThis slice retired the user-facing outage-masking problem that showed up in the earlier browser audit. T01 mapped the failure pattern: several core views were swallowing request failures into empty arrays or nulls and then rendering normal empty states. T02 introduced a shared `useViewResource` hook plus a reusable `ViewStateNotice` component, then applied that pattern to the high-traffic surfaces that matter most for top-level product trust: the jobs list, dashboard, reminders view, companies view, kanban board, shared company hook, and the profile page’s top-level load. Those views now distinguish unavailable/error states from genuine empty data and offer retry affordances where appropriate. T03 verified the new behavior: the focused frontend regression set passed, the frontend build passed, and a browser pass with the API intentionally unavailable showed `Unable to load jobs` instead of an empty jobs table. When the API auth surface was reachable again, the frontend recovered to a normal sign-in screen. During the recovery pass I hit an unrelated local API limitation — some job-data requests still fail in this checkout because the local process logs SQLite schema errors — so I recorded that as environment evidence for S04 rather than broadening the slice.\n\n## Verification\n\nVerified with focused frontend tests (`cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/login-page.test.tsx src/profile-page.test.tsx`), frontend production build (`cd job-tracker-ui && npm run build`), browser outage verification on `http://localhost:3001/jobs`, and browser recovery verification on `http://localhost:3001/login` once the API auth surface was reachable again.\n\n## Requirements Advanced\n\nNone.\n\n## Requirements Validated\n\nNone.\n\n## New Requirements Surfaced\n\nNone.\n\n## Requirements Invalidated or Re-scoped\n\nNone.\n\n## Deviations\n\nThe with-API-available browser smoke used the login/auth-reachable path rather than a full jobs-data happy path because the local development API process still has an unrelated SQLite schema issue affecting some job-data queries in this checkout.\n\n## Known Limitations\n\nDeeper detail/workspace fetches still use local fallback-on-error patterns in some components, especially `JobDetailsDialog.tsx` and `QuickCommandDialog.tsx`. The local API process in this checkout also still logs SQLite schema errors (`RuleSettings` missing) for some job-data paths, which limited the recovery browser smoke to auth-reachable surfaces instead of a full jobs-data happy path.\n\n## Follow-ups\n\nS04 should address the underlying API startup/data-layer fragility, including the local SQLite/schema inconsistencies that limited the with-API-available browser smoke during this slice. A later frontend slice can extend the shared view-state pattern into deeper detail/workspace surfaces such as `JobDetailsDialog.tsx` and `QuickCommandDialog.tsx`.\n\n## Files Created/Modified\n\n- `job-tracker-ui/src/hooks/useViewResource.ts` — Added the shared async view-state hook for loading/error/retry handling.\n- `job-tracker-ui/src/components/ViewStateNotice.tsx` — Added the shared unavailable/error surface used by core views.\n- `job-tracker-ui/src/hooks/useCompanies.ts` — Exposed error/reload state from the shared companies hook instead of silent empty fallback.\n- `job-tracker-ui/src/components/JobTable.tsx` — Stopped the jobs list from presenting API failures as ordinary empty results.\n- `job-tracker-ui/src/components/DashboardView.tsx` — Moved dashboard summary/trend loading to shared resource state and explicit unavailable notices.\n- `job-tracker-ui/src/components/CompaniesTable.tsx` — Added explicit unavailable state to the companies view.\n- `job-tracker-ui/src/components/RemindersView.tsx` — Added explicit unavailable state to the reminders view.\n- `job-tracker-ui/src/components/KanbanBoard.tsx` — Added explicit unavailable state to the kanban board.\n- `job-tracker-ui/src/pages/ProfilePage.tsx` — Added a top-level profile load failure surface instead of silent blank state.\n- `job-tracker-ui/src/daily-control-loop.test.tsx` — Updated workflow regression assertions to match the current stable package-work surface.\n", - "full_uat_md": "# S03: Resilience UX and client data layer — UAT\n\n**Milestone:** M011\n**Written:** 2026-04-10T22:20:05.941Z\n\n# UAT — S03 Resilience UX and client data layer\n\n## What was exercised\n\n1. Start the frontend only with `cd job-tracker-ui && PORT=3001 BROWSER=none npm start`.\n2. Leave the API unavailable.\n3. Navigate to `http://localhost:3001/jobs`.\n4. Confirm the jobs page shows an explicit unavailable state (`Unable to load jobs`) instead of an empty jobs table or `No jobs found.` copy.\n5. Bring the API back up to an auth-reachable state.\n6. Navigate to `http://localhost:3001/login`.\n7. Confirm the normal sign-in UI renders again once the API is reachable.\n\n## Result\n\n- The outage path is now explicit: the jobs page reports that it cannot reach the API instead of pretending there is no data.\n- The frontend returns to a normal reachable auth surface when the API is back.\n- A full jobs-data happy-path browser smoke was still limited by an unrelated local SQLite schema issue in this checkout, which should be addressed in S04.\n\n", - "goal": "Make API outages and request failures visible to users instead of looking like ordinary empty data, using a shared resilient data-loading model across the core frontend views.", - "success_criteria": "- Core data views no longer present API/network failures as ordinary empty states.\n- The frontend uses a shared query/error model for the highest-traffic data surfaces instead of ad hoc `catch(() => [])` fallbacks.\n- Unauthorized and unavailable states are visually distinct on the client.\n- Browser verification proves the app shows an explicit unavailable/error state when the API is down.", - "proof_level": "Code + browser outage verification + focused frontend tests", - "integration_closure": "Top-level data views must stop collapsing transport failures into empty-data presentations. The frontend should distinguish loading, empty, unauthorized, and unavailable states consistently while still working against the cookie-backed auth session introduced in S02.", - "observability_impact": "Adds explicit client-visible unavailable/error states and shared retry/error surfaces so API outages and request failures are diagnosable instead of looking like normal empty datasets.", - "sequence": 3, - "replan_triggered_at": null - }, - { - "milestone_id": "M011", - "id": "S04", - "title": "S04", - "status": "complete", - "risk": "high", - "depends": [], - "demo": "The API starts cleanly with slimmer startup composition, stronger edge controls, and fewer deploy-time surprises.", - "created_at": "2026-04-10T16:33:49.541Z", - "completed_at": "2026-04-10T22:44:54.405Z", - "full_summary_md": "---\nid: S04\nparent: M011\nmilestone: M011\nprovides:\n - A cleaner API startup baseline for S05 endpoint hardening.\n - A more explicit runtime boundary for S06 AI/Ollama operational hardening.\nrequires:\n - slice: S02\n provides: Cookie-backed auth/session transport and explicit auth edge behavior.\n - slice: S03\n provides: Frontend verification evidence that exposed the startup/data-layer fragility to retire.\naffects:\n - S05\n - S06\nkey_files:\n - JobTrackerApi/Program.cs\n - JobTrackerApi/Services/StartupInitializationExtensions.cs\n - JobTrackerApi/Services/StartupReadiness.cs\n - JobTrackerApi/Services/JobEnrichmentHostedService.cs\n - JobTrackerApi/Services/DailyExportHostedService.cs\n - JobTrackerApi/Services/RulesHostedService.cs\n - JobTrackerApi/Services/FollowUpReminderHostedService.cs\nkey_decisions:\n - Extract database/bootstrap orchestration out of `Program.cs` first instead of mixing it further with middleware/auth wiring.\n - Use an explicit startup-readiness gate so background workers do not infer schema safety from fixed delays alone.\npatterns_established:\n - Keep startup/bootstrap orchestration separate from middleware/controller wiring.\n - Do not let background services infer initialization readiness from fixed delays alone.\n - Treat missing core schema assumptions as a startup-readiness condition, not just a background error to log and ignore.\nobservability_surfaces:\n - Explicit startup-readiness boundary for background services.\n - Startup warning when the core schema is incomplete and background services remain paused.\n - Cleaner separation between startup/bootstrap diagnostics and post-start background worker behavior.\ndrill_down_paths:\n - .gsd/milestones/M011/slices/S04/tasks/T01-SUMMARY.md\n - .gsd/milestones/M011/slices/S04/tasks/T02-SUMMARY.md\n - .gsd/milestones/M011/slices/S04/tasks/T03-SUMMARY.md\nduration: \"\"\nverification_result: passed\ncompleted_at: 2026-04-10T22:44:54.407Z\nblocker_discovered: false\n---\n\n# S04: API startup and platform hardening\n\n**Extracted API bootstrap out of `Program.cs` and hardened background startup behavior so the app starts cleanly without the earlier missing-table failure profile.**\n\n## What Happened\n\nThis slice hardened the API startup path by separating bootstrap concerns from general host wiring and by making background-worker readiness explicit. T01 mapped the real seam: `Program.cs` was carrying service registration, auth config, provider selection, schema/bootstrap repair, migrations, seeding, and ownership claim logic all in one imperative block, while background services assumed schema readiness after fixed delays. T02 extracted that database/bootstrap block into `StartupInitializationExtensions`, registered a shared `StartupReadiness` gate, and updated the background workers most exposed to the earlier failure noise so they wait for startup readiness before beginning their own loops. A core-schema check now prevents those workers from running if startup completes without the required tables. T03 verified the new path: the API builds cleanly, the focused auth/system tests pass, and a Development startup run reaches ready without the prior `RuleSettings`/`JobApplications` missing-table error storm. The core auth surfaces remain healthy under that path (`/api/auth/config` 200, `/api/auth/me` 401 unauthenticated).\n\n## Verification\n\nVerified with `dotnet build JobTrackerApi/JobTrackerApi.csproj`, `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter AuthAndSystemControllerTests`, a Development startup run on `http://localhost:5202`, and direct HTTP checks against `/api/auth/config` and `/api/auth/me`.\n\n## Requirements Advanced\n\nNone.\n\n## Requirements Validated\n\nNone.\n\n## New Requirements Surfaced\n\nNone.\n\n## Requirements Invalidated or Re-scoped\n\nNone.\n\n## Deviations\n\nVerification focused on startup and auth surfaces rather than a broader frontend/browser path, because S04’s target risk was startup/bootstrap behavior. The bootstrap extraction was performed mechanically first and then normalized, which changed the implementation order but not the slice outcome.\n\n## Known Limitations\n\nStartup still emits EF model validation warnings related to required relationships combined with global query filters, and the summarizer probe remains part of the startup/runtime surface. Those are platform concerns to track, but the earlier missing-table startup failure class was retired in the observed pass.\n\n## Follow-ups\n\nS05 should build on this cleaner startup baseline to harden file/admin/sensitive endpoints without relying on a monolithic startup file. S06 should treat the summarizer probe and AI-service startup behavior as part of its operational boundary review.\n\n## Files Created/Modified\n\n- `JobTrackerApi/Program.cs` — Delegates startup initialization to a focused bootstrap extension and registers startup readiness.\n- `JobTrackerApi/Services/StartupInitializationExtensions.cs` — Owns database/bootstrap initialization outside `Program.cs`.\n- `JobTrackerApi/Services/StartupReadiness.cs` — Introduces an explicit startup-readiness boundary for background services.\n- `JobTrackerApi/Services/JobEnrichmentHostedService.cs` — Waits for startup readiness before running enrichment background work.\n- `JobTrackerApi/Services/DailyExportHostedService.cs` — Waits for startup readiness before running daily exports.\n- `JobTrackerApi/Services/RulesHostedService.cs` — Waits for startup readiness before applying ghosting rules in the background.\n- `JobTrackerApi/Services/FollowUpReminderHostedService.cs` — Waits for startup readiness before follow-up reminder background passes.\n", - "full_uat_md": "# S04: API startup and platform hardening — UAT\n\n**Milestone:** M011\n**Written:** 2026-04-10T22:44:54.407Z\n\n# UAT — S04 API startup and platform hardening\n\n## What was exercised\n\n1. Build the API with `dotnet build JobTrackerApi/JobTrackerApi.csproj`.\n2. Run the focused auth/system tests with `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter AuthAndSystemControllerTests`.\n3. Start the API in Development with `ASPNETCORE_ENVIRONMENT=Development ASPNETCORE_URLS=http://localhost:5202 dotnet run --project JobTrackerApi/JobTrackerApi.csproj`.\n4. Confirm the process reaches a ready state instead of entering the earlier missing-table error state.\n5. Request `GET http://localhost:5202/api/auth/config` and confirm it returns 200.\n6. Request `GET http://localhost:5202/api/auth/me` without a session and confirm it returns 401.\n\n## Result\n\n- The API reaches ready cleanly in the observed Development startup pass.\n- The earlier `RuleSettings` / `JobApplications` missing-table startup error storm did not recur in that pass.\n- Core auth surfaces remained healthy after the bootstrap refactor and readiness-gating changes.\n\n", - "goal": "Make API startup cleaner and more predictable by extracting startup/bootstrap responsibilities out of `Program.cs`, hardening database/bootstrap behavior, and preventing background services from tripping over partially initialized state.", - "success_criteria": "- `JobTrackerApi/Program.cs` is materially slimmer and delegates startup/bootstrap work to focused helpers or services.\n- Database/bootstrap logic no longer relies on a monolithic startup block that mixes provider detection, schema repair, seeding, and runtime wiring.\n- The API can start in the local Development environment without the observed SQLite bootstrap/schema failure path recurring for core startup assumptions.\n- Verification captures both clean startup behavior and the remaining constraints explicitly if any non-core background path is still limited.", - "proof_level": "Code + startup verification + focused tests", - "integration_closure": "The API must still start with the existing auth/session work from S02, support the resilient frontend from S03, and keep hosted/background services aligned with the post-bootstrap schema state. Startup hardening cannot break current controllers, auth wiring, or the local development DB path.", - "observability_impact": "Improves startup diagnostics and reduces false-negative runtime noise by making bootstrap phases explicit and by ensuring background services do not begin work against missing schema assumptions.", - "sequence": 4, - "replan_triggered_at": null - }, - { - "milestone_id": "M011", - "id": "S05", - "title": "S05", - "status": "complete", - "risk": "medium", - "depends": [], - "demo": "Uploads, client-error reporting, avatars, and admin/system workflows behave safely and predictably under normal and adverse input.", - "created_at": "2026-04-10T16:33:49.541Z", - "completed_at": "2026-04-10T23:00:57.771Z", - "full_summary_md": "---\nid: S05\nparent: M011\nmilestone: M011\nprovides:\n - A safer API boundary and diagnostics surface for the final AI/Ollama hardening slice.\nrequires:\n - slice: S02\n provides: Cookie-backed local session model and auth boundary conventions.\n - slice: S04\n provides: Cleaner startup/runtime baseline for endpoint hardening verification.\naffects:\n - S06\nkey_files:\n - JobTrackerApi/Controllers/AttachmentsController.cs\n - JobTrackerApi/Controllers/AuthController.cs\n - JobTrackerApi/Controllers/BackupController.cs\n - JobTrackerApi/Controllers/ExportController.cs\n - JobTrackerApi/Controllers/ClientErrorsController.cs\n - JobTrackerApi.Tests/AttachmentsControllerTests.cs\n - JobTrackerApi.Tests/BackupControllerTests.cs\n - JobTrackerApi.Tests/ClientErrorsControllerTests.cs\nkey_decisions:\n - Require explicit local auth on backup, export, and attachment routes instead of relying on implicit route exposure plus ownership filters.\n - Sanitize client-error reports by logging hashes and short previews instead of raw browser stack payloads.\n - Validate avatar uploads from detected bytes, not just client-provided MIME labels.\npatterns_established:\n - Sensitive file/export routes should declare their auth boundary explicitly instead of depending on implicit ownership filters alone.\n - Diagnostics from untrusted clients should be normalized, bounded, and hashed rather than logged raw.\n - File/avatar validation should confirm server-observed content signatures for supported formats instead of trusting the browser’s content type.\nobservability_surfaces:\n - Client-error reports now emit bounded normalized fields plus stack/component hashes and short previews instead of raw payload dumps.\n - Sensitive route denial behavior is now explicit and test-covered for attachments, backup, and export endpoints.\ndrill_down_paths:\n - .gsd/milestones/M011/slices/S05/tasks/T01-SUMMARY.md\n - .gsd/milestones/M011/slices/S05/tasks/T02-SUMMARY.md\n - .gsd/milestones/M011/slices/S05/tasks/T03-SUMMARY.md\nduration: \"\"\nverification_result: passed\ncompleted_at: 2026-04-10T23:00:57.772Z\nblocker_discovered: false\n---\n\n# S05: File, admin, and sensitive endpoint hardening\n\n**Hardened sensitive file/admin-adjacent endpoints by enforcing auth boundaries, sanitizing client-error logging, and tightening avatar validation.**\n\n## What Happened\n\nThis slice tightened the API’s sensitive endpoint boundaries and payload handling. T01 mapped the real risk seam: file routes and export/backup routes were publicly routable, avatar uploads trusted browser-provided MIME labels, and `ClientErrorsController` logged raw browser payloads verbatim. T02 implemented the hardening: attachments, backup, and export routes now require explicit local auth; avatar uploads now use a tighter size limit and server-side PNG/JPEG/WebP signature detection before persistence; and client-error logging now keeps bounded normalized fields plus hashed/summarized stack signals instead of raw submitted stacks. T03 verified both code and runtime behavior. Focused controller tests passed, the API still builds, and a live Development pass confirmed the hardened routes now reject anonymous access with 401 responses. The remaining noteworthy constraint is that anonymous `client-errors` submissions also return 401 under the current auth-required environment.\n\n## Verification\n\nVerified with `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AttachmentsControllerTests|FullyQualifiedName~BackupControllerTests|FullyQualifiedName~ClientErrorsControllerTests|FullyQualifiedName~AuthAndSystemControllerTests\"`, `dotnet build JobTrackerApi/JobTrackerApi.csproj`, a Development startup run on `http://localhost:5202`, and anonymous HTTP checks against export, backup, attachments, and client-error routes.\n\n## Requirements Advanced\n\nNone.\n\n## Requirements Validated\n\nNone.\n\n## New Requirements Surfaced\n\nNone.\n\n## Requirements Invalidated or Re-scoped\n\nNone.\n\n## Deviations\n\nThe runtime pass revealed that anonymous `POST /api/client-errors` returns 401 under the current auth-required environment. I recorded that as an observed constraint instead of broadening S05 into a pre-auth diagnostics policy change.\n\n## Known Limitations\n\nAvatar images still use the existing inline `AvatarImageDataUrl` persistence model; S05 hardens that path without redesigning storage. Anonymous `client-errors` submissions currently return 401 under the auth-required environment, so pre-auth browser failures are not captured unless that policy is revisited intentionally.\n\n## Follow-ups\n\nS06 should keep the same discipline around bounded diagnostics and explicit capability/auth surfaces when hardening the AI service and Ollama integration. If product requirements later need anonymous pre-auth browser error capture, that should be an explicit follow-up rather than an accidental open endpoint.\n\n## Files Created/Modified\n\n- `JobTrackerApi/Controllers/AttachmentsController.cs` — Added explicit local auth boundary to attachment routes.\n- `JobTrackerApi/Controllers/AuthController.cs` — Tightened avatar upload size and content validation using extension checks and server-side image signature detection.\n- `JobTrackerApi/Controllers/BackupController.cs` — Added explicit local auth boundary to encrypted backup export.\n- `JobTrackerApi/Controllers/ExportController.cs` — Added explicit local auth boundary to job export endpoints.\n- `JobTrackerApi/Controllers/ClientErrorsController.cs` — Replaced raw browser-stack logging with bounded normalized fields, stack previews, and hashes.\n- `JobTrackerApi.Tests/AttachmentsControllerTests.cs` — Added auth-boundary coverage for attachment routes.\n- `JobTrackerApi.Tests/BackupControllerTests.cs` — Added auth-boundary coverage for backup and export routes.\n- `JobTrackerApi.Tests/ClientErrorsControllerTests.cs` — Added tests for client-error sanitization and avatar signature rejection.\n", - "full_uat_md": "# S05: File, admin, and sensitive endpoint hardening — UAT\n\n**Milestone:** M011\n**Written:** 2026-04-10T23:00:57.773Z\n\n# UAT — S05 File, admin, and sensitive endpoint hardening\n\n## What was exercised\n\n1. Run focused controller tests for attachments, backup/export auth boundaries, client-error sanitization, and avatar validation.\n2. Build the API with `dotnet build JobTrackerApi/JobTrackerApi.csproj`.\n3. Start the API in Development on `http://localhost:5202`.\n4. Request the hardened routes without a session:\n - `GET /api/export/jobs`\n - `POST /api/backup/encrypted`\n - `GET /api/attachments/1`\n5. Observe the current behavior of `POST /api/client-errors` without a session in the auth-required environment.\n\n## Result\n\n- Hardened sensitive file/export routes now reject anonymous access with 401 responses.\n- Client-error logging is covered by focused tests and no longer stores raw browser stack payloads in logs.\n- Avatar uploads reject unsupported byte signatures even when the browser labels them as a supported image type.\n- Anonymous `client-errors` submissions currently return 401 in this environment and are recorded as a known limitation/constraint.\n\n", - "goal": "Harden file, admin, and sensitive endpoints so uploads, backups/exports, avatar handling, and client-error reporting behave predictably under hostile or malformed input without leaking unnecessary data.", - "success_criteria": "- Sensitive export/backup/admin/file routes require the right auth boundary.\n- Avatar and attachment upload paths validate type/size/input more defensively and avoid unsafe persistence patterns.\n- Client error reporting stops logging raw browser stack payloads while still preserving useful diagnostic signals.\n- Verification covers both allowed paths and denied/malformed-input paths.", - "proof_level": "Code + focused endpoint tests + build/test verification", - "integration_closure": "S05 must preserve the cookie-backed auth/session model from S02 and the cleaner startup/runtime baseline from S04 while tightening sensitive endpoint behavior. Hardening should not break existing attachment flows, admin status pages, or export/backup workflows for authorized users.", - "observability_impact": "Sensitive endpoint failures should become explicit and bounded: rejected uploads and client-error reports should surface clear validation outcomes, and admin/file routes should no longer rely on open routing or raw payload logging for diagnosis.", - "sequence": 5, - "replan_triggered_at": null - }, - { - "milestone_id": "M011", - "id": "S06", - "title": "S06", - "status": "complete", - "risk": "high", - "depends": [], - "demo": "AI-powered features expose clear capability states, degrade gracefully when Ollama or OCR paths are limited, and run on a cleaner service contract.", - "created_at": "2026-04-10T16:33:49.541Z", - "completed_at": "2026-04-10T23:24:52.442Z", - "full_summary_md": "---\nid: S06\nparent: M011\nmilestone: M011\nprovides:\n - A complete hardened platform baseline across frontend, auth, runtime startup, sensitive endpoints, and AI/Ollama reliability.\nrequires:\n - slice: S04\n provides: Cleaner startup/runtime baseline for background services and health verification.\n - slice: S05\n provides: Sensitive endpoint/auth hardening patterns for bounded diagnostics and explicit capability surfaces.\naffects:\n []\nkey_files:\n - tools/summarizer/app.py\n - tools/summarizer/tests/test_app.py\n - JobTrackerApi/Services/SummarizerService.cs\n - tools/summarizer/README.md\nkey_decisions:\n - Make the Python summarizer model lazy-load by default and expose the resulting state explicitly in `/health`.\n - Interpret `summarize_available=false` as unhealthy in the .NET metrics layer so admin/runtime telemetry does not misreport a disabled summarizer as healthy.\n - Keep the existing `ISummarizerService` caller contract stable for now while improving degraded-path diagnostics and capability reporting underneath it.\npatterns_established:\n - Heavy local AI models should load lazily by default unless warm-up is explicitly requested.\n - Health endpoints should report capability state without performing hidden heavyweight initialization.\n - API wrappers over optional AI services should distinguish ‘HTTP reachable’ from ‘functionally available’.\nobservability_surfaces:\n - Python `/health` now exposes explicit summarizer model capability state (`model_loaded`, `model_disabled`, `summarize_available`, `model_load_error`).\n - API-side AI metrics now treat summarize-unavailable health responses as unhealthy and preserve more specific failure text from AI/probe/OCR requests.\ndrill_down_paths:\n - .gsd/milestones/M011/slices/S06/tasks/T01-SUMMARY.md\n - .gsd/milestones/M011/slices/S06/tasks/T02-SUMMARY.md\n - .gsd/milestones/M011/slices/S06/tasks/T03-SUMMARY.md\nduration: \"\"\nverification_result: passed\ncompleted_at: 2026-04-10T23:24:52.444Z\nblocker_discovered: false\n---\n\n# S06: AI service and Ollama reliability hardening\n\n**Hardened the AI/Ollama contract so model-disabled, model-load, and unreachable-Ollama states are explicit and degrade predictably.**\n\n## What Happened\n\nThis slice hardened the AI service and Ollama integration around explicit capability state and predictable degraded behavior. T01 mapped the real seam: the Python AI service loaded the summarization model at import time unless a skip flag was set, Ollama behavior was optional but coarse, and the .NET summarizer wrapper depended heavily on post-failure metrics rather than a clearer health interpretation. T02 changed that contract. The Python service now lazy-loads the summarization model by default, tracks disabled/load-failure state explicitly, and reports `model_loaded`, `model_disabled`, `summarize_available`, and `model_load_error` through `/health` without triggering a hidden warm-up. Focused Python tests now cover disabled-model health, explicit summarize 503 behavior, and configured-but-unreachable Ollama health. On the .NET side, `SummarizerService` now records clearer error detail from failed summarize/OCR/probe responses and no longer treats `summarize_available=false` as healthy. T03 verified the new contract through the project’s Python test harness, focused .NET tests, and a clean API build. The remaining uncertainty is environmental model/runtime behavior, not hidden contract ambiguity.\n\n## Verification\n\nVerified with `cd tools/summarizer && ./scripts/bootstrap-and-test.sh test`, `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AuthAndSystemControllerTests|FullyQualifiedName~ProductionConfigTests|FullyQualifiedName~OwnershipGuardTests\"`, and `dotnet build JobTrackerApi/JobTrackerApi.csproj`.\n\n## Requirements Advanced\n\nNone.\n\n## Requirements Validated\n\nNone.\n\n## New Requirements Surfaced\n\nNone.\n\n## Requirements Invalidated or Re-scoped\n\nNone.\n\n## Deviations\n\nVerification focused on explicit degraded-path behavior and contract tests rather than running a full live Ollama deployment. That kept the slice aligned with its reliability goal instead of turning it into environment orchestration work.\n\n## Known Limitations\n\n`AiServiceMetrics` still does not have dedicated .NET fields for model-loaded/model-disabled state, so that detail is currently folded into the existing `Healthy`/`LastError` view rather than surfaced as separate typed properties. Real Ollama pull/load latency remains environment-dependent and was not the target of this slice.\n\n## Follow-ups\n\nIf later milestones need deeper AI operability work, the next seam would be widening `AiServiceMetrics` with first-class model-state fields or adding a dedicated warm-up endpoint. Actual Ollama pull/load latency remains an environment/runtime concern rather than a hidden contract issue now.\n\n## Files Created/Modified\n\n- `tools/summarizer/app.py` — Changed AI runtime to lazy model load by default and added explicit health/capability fields for model and Ollama state.\n- `tools/summarizer/tests/test_app.py` — Added focused tests for disabled-model health, disabled summarize behavior, and configured-but-unreachable Ollama state.\n- `JobTrackerApi/Services/SummarizerService.cs` — Improved API-side AI error detail capture and health interpretation for summarize-unavailable states.\n- `tools/summarizer/README.md` — Documented the expanded health/capability contract.\n", - "full_uat_md": "# S06: AI service and Ollama reliability hardening — UAT\n\n**Milestone:** M011\n**Written:** 2026-04-10T23:24:52.444Z\n\n# UAT — S06 AI service and Ollama reliability hardening\n\n## What was exercised\n\n1. Run the summarizer Python test suite with `cd tools/summarizer && ./scripts/bootstrap-and-test.sh test`.\n2. Verify the Python tests cover:\n - disabled-model `/health` state\n - explicit 503 summarize behavior when model loading is disabled\n - configured-but-unreachable Ollama health reporting\n3. Run focused .NET tests with `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AuthAndSystemControllerTests|FullyQualifiedName~ProductionConfigTests|FullyQualifiedName~OwnershipGuardTests\"`.\n4. Rebuild the API with `dotnet build JobTrackerApi/JobTrackerApi.csproj`.\n\n## Result\n\n- The Python AI service now reports explicit model/capability state without hidden warm-up on `/health`.\n- Disabled summarizer state returns a clear 503 reason on `/summarize`.\n- Configured-but-unreachable Ollama state is represented explicitly in health output.\n- The .NET wrapper still builds and tests cleanly while interpreting summarize-unavailable as unhealthy instead of falsely healthy.\n\n", - "goal": "Harden the AI service and Ollama integration so startup, probe, summarize, OCR, and CV-classification paths expose explicit capability state, degrade predictably when dependencies are missing, and avoid blocking the platform on heavy or unreachable AI components.", - "success_criteria": "- The AI service no longer treats heavy model load or optional Ollama features as an implicit always-ready contract.\n- API-side AI calls expose clearer bounded failure behavior and capability metrics instead of silent `null`/generic failure collapse where avoidable.\n- Optional Ollama-backed features degrade predictably when Ollama is missing, unreachable, or missing the configured model.\n- Verification covers both healthy and degraded AI/Ollama paths.", - "proof_level": "Code + focused API/Python tests + health/behavior verification", - "integration_closure": "S06 must preserve the frontend and admin capability surfaces already in use, keep the API startup baseline from S04 clean, and avoid breaking current CV/application-package features that depend on `ISummarizerService`. Reliability hardening should make failures more explicit without changing core user-facing semantics unnecessarily.", - "observability_impact": "AI/Ollama state should become explicit through metrics, health/capability reporting, and bounded failure surfaces so future agents can tell the difference between model-not-loaded, Ollama-not-configured, Ollama-unreachable, OCR-unavailable, and request-time service failures.", - "sequence": 6, - "replan_triggered_at": null - } - ], - "tasks": [ - { - "milestone_id": "M001", - "slice_id": "S01", - "id": "T01", - "title": "Add linked Gmail thread refresh to the backend contract", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.777Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "JobTrackerApi/Controllers/GmailController.cs", - "JobTrackerApi/Services/GmailOAuthService.cs", - "JobTrackerApi.Tests/GmailControllerTests.cs", - "JobTrackerApi/Program.cs" - ], - "verify": "`dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests`", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S01", - "id": "T02", - "title": "Surface live Gmail thread continuity in the job workspace", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.777Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "job-tracker-ui/src/components/Correspondence.tsx", - "job-tracker-ui/src/types.ts", - "job-tracker-ui/src/correspondence-gmail-import.test.tsx", - "job-tracker-ui/src/components/JobDetailsDialog.tsx" - ], - "verify": "`CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx`", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S02", - "id": "T01", - "title": "Strengthen application-package context assembly and backend draft tests", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.777Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "JobTrackerApi/Controllers/JobApplicationsController.cs", - "JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs" - ], - "verify": "`dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests`", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S02", - "id": "T02", - "title": "Make the job workspace save and present the application package as real working material", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.777Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "job-tracker-ui/src/components/JobDetailsDialog.tsx", - "job-tracker-ui/src/types.ts", - "job-tracker-ui/src/job-details-generated-drafts.test.tsx" - ], - "verify": "`CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-generated-drafts.test.tsx`", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S03", - "id": "T01", - "title": "Strengthen follow-up draft context assembly and backend reply/follow-up tests", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.777Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "JobTrackerApi/Controllers/JobApplicationsController.cs", - "JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs" - ], - "verify": "`dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsFollowUpDraftTests`", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S03", - "id": "T02", - "title": "Make the follow-up workspace show thread-grounded draft state without autonomous sending", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.777Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "job-tracker-ui/src/components/JobDetailsDialog.tsx", - "job-tracker-ui/src/types.ts", - "job-tracker-ui/src/job-details-followup-drafts.test.tsx" - ], - "verify": "`CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/job-details-followup-drafts.test.tsx`", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S04", - "id": "T01", - "title": "Turn reminders and dashboard into actionable entry surfaces", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.778Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "job-tracker-ui/src/components/DashboardView.tsx", - "job-tracker-ui/src/components/RemindersView.tsx", - "job-tracker-ui/src/App.tsx" - ], - "verify": "`CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx`", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S04", - "id": "T02", - "title": "Make the job table expose the right next action and prove the daily loop", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.778Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "job-tracker-ui/src/components/JobTable.tsx", - "job-tracker-ui/src/daily-control-loop.test.tsx" - ], - "verify": "`CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/daily-control-loop.test.tsx`", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S05", - "id": "T01", - "title": "Centralize workflow trust signals across overview and readiness surfaces", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.778Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "JobTrackerApi/Controllers/JobApplicationsController.cs", - "JobTrackerApi.Tests/JobApplicationsWorkflowSignalsTests.cs", - "job-tracker-ui/src/types.ts", - "job-tracker-ui/src/jobWorkflowSignals.ts", - "job-tracker-ui/src/components/JobTable.tsx", - "job-tracker-ui/src/components/DashboardView.tsx", - "job-tracker-ui/src/components/RemindersView.tsx", - "job-tracker-ui/src/workflow-trust-signals.test.tsx" - ], - "verify": "`dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsWorkflowSignalsTests` and `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/workflow-trust-signals.test.tsx`", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S05", - "id": "T02", - "title": "Add integrated trust-loop proof and workspace polish", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.778Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "job-tracker-ui/src/components/JobDetailsDialog.tsx", - "job-tracker-ui/src/components/Correspondence.tsx", - "job-tracker-ui/src/end-to-end-trust-loop.test.tsx", - "job-tracker-ui/src/daily-control-loop.test.tsx", - ".gsd/milestones/M001/slices/S05/S05-UAT.md" - ], - "verify": "`CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/end-to-end-trust-loop.test.tsx`, `CI=true npm --prefix job-tracker-ui test -- --watch=false --runTestsByPath src/correspondence-gmail-import.test.tsx src/job-details-generated-drafts.test.tsx src/job-details-followup-drafts.test.tsx src/daily-control-loop.test.tsx`, and `CI=true npm --prefix job-tracker-ui run build`", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S06", - "id": "T01", - "title": "Validated and recorded the live API/auth preflight gate, including README runbook guidance and negative-path shell coverage.", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.778Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "scripts/s06-preflight.sh", - "README.md", - "job-tracker-ui/src/api.ts", - "JobTrackerApi/appsettings.Development.json" - ], - "verify": "bash scripts/s06-preflight.sh", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S06", - "id": "T02", - "title": "Seeded acceptance-ready job data through the live API with deterministic rerun-safe ids and readiness output.", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.778Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "scripts/s06-acceptance-data.sh", - "scripts/s06-preflight.sh", - "README.md" - ], - "verify": "bash scripts/s06-acceptance-data.sh", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S06", - "id": "T03", - "title": "Added a repeatable live acceptance runner and recorded real S06 browser evidence for the manual-send boundary and daily loop.", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.778Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "scripts/s06-acceptance-run.sh", - "docs/s06-acceptance-run.md", - "scripts/s06-preflight.sh", - "scripts/s06-acceptance-data.sh", - "job-tracker-ui/src/end-to-end-trust-loop.test.tsx" - ], - "verify": "bash scripts/s06-acceptance-run.sh && test -s docs/s06-acceptance-run.md", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S07", - "id": "T01", - "title": "Added docs/s07-uat.md to close S07 with imported acceptance-run evidence for the seeded daily-loop job.", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.778Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "docs/s07-uat.md", - "docs/s06-acceptance-run.md" - ], - "verify": "test -s docs/s07-uat.md && grep -q \"S06 Acceptance Backend Engineer\" docs/s07-uat.md", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S07", - "id": "T02", - "title": "Re-ran the acceptance flow and refreshed the S07 UAT closure with current browser evidence, manual-send-boundary proof, and the Gmail continuity limitation.", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.778Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "scripts/s06-preflight.sh", - "scripts/s06-acceptance-run.sh", - "docs/s06-acceptance-run.md", - "docs/s07-uat.md" - ], - "verify": "bash scripts/s06-preflight.sh && bash scripts/s06-acceptance-run.sh && test -s docs/s06-acceptance-run.md && grep -q \"manual-send boundary\" docs/s07-uat.md", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M001", - "slice_id": "S07", - "id": "T03", - "title": "Re-ran the focused daily-loop UI regressions, repaired the local CRA dependency state, and recorded the passing deterministic coverage in docs/s07-uat.md.", - "status": "complete", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": "2026-03-28T22:02:57.779Z", - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "", - "estimate": "", - "files": [ - "job-tracker-ui/src/daily-control-loop.test.tsx", - "job-tracker-ui/src/workflow-trust-signals.test.tsx", - "docs/s07-uat.md" - ], - "verify": "CI=true npm --prefix job-tracker-ui test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/workflow-trust-signals.test.tsx && grep -q \"UI regression results\" docs/s07-uat.md", - "inputs": [], - "expected_output": [], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S01", - "id": "T01", - "title": "Add canonical CV artifact and extraction persistence model", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Design and implement persistence models for uploaded CV artifacts, extraction runs, field provenance/confidence payloads, and the canonical CV profile wrapper. Wire EF/Core bootstrap changes and ensure the schema can coexist with current profile fields during rollout.", - "estimate": "1.5d", - "files": [ - "JobTrackerApi/Data/JobTrackerContext.cs", - "Models/ApplicationUser.cs", - "Models/StructuredCvProfile.cs", - "Models/* new cv extraction models", - "JobTrackerApi/Program.cs" - ], - "verify": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter ProfileCvControllerTests", - "inputs": [ - "Existing ProfileCvText/ProfileCvStructureJson flow", - "Current profile upload/parse endpoints" - ], - "expected_output": [ - "JobTrackerApi/Models/* canonical CV persistence types", - "Data/JobTrackerContext.cs updates", - "JobTrackerApi bootstrap/schema changes", - "migration/bootstrap coverage or auto-create logic" - ], - "observability_impact": "Adds extraction-run status/version/error fields for later debugging.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S01", - "id": "T02", - "title": "Refactor profile CV endpoints around canonical extraction runs", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Refactor the profile CV controller/service layer so upload stores artifact + extraction run metadata, parse/rebuild use a canonical extraction pipeline entry point, and reprocess-from-stored-artifact is possible without re-upload. Preserve backward compatibility in API responses while adding richer metadata.", - "estimate": "1.5d", - "files": [ - "JobTrackerApi/Controllers/ProfileCvController.cs", - "JobTrackerApi/Services/* cv extraction services", - "JobTrackerApi.Tests/ProfileCvControllerTests.cs" - ], - "verify": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter ProfileCvControllerTests", - "inputs": [ - "S01/T01 persistence model" - ], - "expected_output": [ - "Refactored upload/parse/reprocess endpoints", - "canonical extraction service boundary", - "backward-compatible response contract" - ], - "observability_impact": "Ensures upload/reprocess failures surface structured status and messages.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S01", - "id": "T03", - "title": "Capture canonical CV model context", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Document the canonical CV lifecycle and record milestone context so later slices can build on a stable vocabulary: artifact, extraction run, canonical profile, provenance/confidence, tailored draft, render options.", - "estimate": "0.5d", - "files": [ - ".gsd/milestones/M005/M005-CONTEXT.md" - ], - "verify": "Artifact saved with milestone context reviewed for terminology consistency", - "inputs": [ - "S01/T01 design", - "S01/T02 API contract" - ], - "expected_output": [ - "M005 context artifact", - "updated roadmap-consistent terminology for later slices" - ], - "observability_impact": "Improves future-agent understanding of extraction state boundaries.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S02", - "id": "T01", - "title": "Build pass A/B/C/D extraction pipeline", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Implement multi-pass extraction orchestration: deterministic field detection, layout/structure grouping hooks, LLM normalization, and validation/repair. Persist confidence, source snippets, and extraction method per field. Keep the current generic fallback behavior as the lowest-priority safety net.", - "estimate": "2d", - "files": [ - "JobTrackerApi/Services/* cv extraction pipeline", - "JobTrackerApi/Controllers/ProfileCvController.cs", - "Models/StructuredCvProfile.cs", - "Models/StructuredCvProfileJson.cs", - "JobTrackerApi.Tests/ProfileCvControllerTests.cs" - ], - "verify": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter ProfileCvControllerTests", - "inputs": [ - "S01 canonical extraction service boundary", - "existing OCR/text extraction support" - ], - "expected_output": [ - "Extraction pipeline service(s)", - "field-level provenance/confidence payloads", - "improved OCR/PDF regression coverage" - ], - "observability_impact": "Field-level extraction methods and confidence become inspectable/debuggable.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S02", - "id": "T02", - "title": "Add structured review UX with confidence and reprocess controls", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Extend the profile UI to render provenance/confidence for structured fields, highlight uncertain values, expose reprocess controls, and support user review/edit/accept flows without losing the current raw text view.", - "estimate": "2d", - "files": [ - "job-tracker-ui/src/pages/ProfilePage.tsx", - "job-tracker-ui/src/profileCv.ts", - "job-tracker-ui/src/i18n/translations.ts", - "job-tracker-ui/src/profile-page.test.tsx" - ], - "verify": "cd job-tracker-ui && CI=true ./node_modules/.bin/react-scripts test --runInBand --watch=false src/profile-page.test.tsx", - "inputs": [ - "S02/T01 field confidence payloads", - "existing structured editor UI" - ], - "expected_output": [ - "Profile structured editor with uncertainty UI", - "reprocess trigger in profile workflow", - "save/apply UX for canonical profile review" - ], - "observability_impact": "Users can see why a field was extracted and which values need review.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S02", - "id": "T03", - "title": "Expose extraction run history", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Add a lightweight extraction-run history/read model so the latest run and prior runs can be inspected or compared during debugging and support. Keep the first UI surface minimal but useful.", - "estimate": "1d", - "files": [ - "JobTrackerApi/Controllers/ProfileCvController.cs", - "JobTrackerApi.Tests/ProfileCvControllerTests.cs", - "job-tracker-ui/src/pages/ProfilePage.tsx" - ], - "verify": "Focused backend/frontend tests proving latest/prior run metadata can be read", - "inputs": [ - "S02/T01 persisted extraction runs" - ], - "expected_output": [ - "extraction-run history endpoint/read model", - "basic UI or API-level access to run history" - ], - "observability_impact": "Makes reprocessing and extraction regressions diagnosable without database spelunking.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S03", - "id": "T01", - "title": "Add tailored CV draft model and persistence", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Design and implement the tailored CV draft persistence model, including job linkage, source canonical-profile version, chosen template id, editable content blocks, and render options JSON. Keep it clearly separate from existing tailored CV text/package fields.", - "estimate": "1.5d", - "files": [ - "Models/* tailored cv draft models", - "JobTrackerApi/Data/JobTrackerContext.cs", - "JobTrackerApi/Program.cs", - "JobTrackerApi.Tests/* tailored draft tests" - ], - "verify": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests", - "inputs": [ - "M005/S01 canonical profile model", - "existing JobApplications package fields" - ], - "expected_output": [ - "TailoredCvDraft model + persistence", - "job linkage and source-version tracking", - "backward-compatible coexistence with existing package fields" - ], - "observability_impact": "Adds generation timestamps/source-version metadata for debugging stale drafts.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S03", - "id": "T02", - "title": "Add tailored CV draft generation and save endpoints", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Add backend generation/edit/save endpoints for job-scoped tailored CV drafts that prefer canonical structured profile content but can still fall back to raw CV text when needed. Ensure regeneration does not silently destroy manual edits without an explicit user action.", - "estimate": "2d", - "files": [ - "JobTrackerApi/Controllers/JobApplicationsController.cs", - "JobTrackerApi/Services/* tailored cv draft generation", - "JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs" - ], - "verify": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsApplicationPackageTests", - "inputs": [ - "S03/T01 tailored draft model", - "existing job package generation logic" - ], - "expected_output": [ - "Job-scoped tailored CV draft endpoints", - "generation/regeneration semantics", - "manual edit preservation rules" - ], - "observability_impact": "Draft generation failures include source-version/context information.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S03", - "id": "T03", - "title": "Add tailored CV draft workspace UX", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Extend the job workspace UI so users can generate, edit, save, and regenerate a tailored CV draft in a dedicated layer without confusing it with the master CV or other package artifacts.", - "estimate": "2d", - "files": [ - "job-tracker-ui/src/components/JobDetailsDialog.tsx", - "job-tracker-ui/src/job-details-generated-drafts.test.tsx", - "job-tracker-ui/src/i18n/translations.ts" - ], - "verify": "cd job-tracker-ui && CI=true ./node_modules/.bin/react-scripts test --runInBand --watch=false src/job-details-generated-drafts.test.tsx", - "inputs": [ - "S03/T02 draft endpoints", - "existing package workspace UI" - ], - "expected_output": [ - "Workspace tailored CV draft editor", - "explicit regenerate/save flows", - "clear distinction from master profile" - ], - "observability_impact": "Shows source-version/regeneration state to the user when draft data is stale or regenerated.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S04", - "id": "T01", - "title": "Build ATS Minimal renderer", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Implement an ATS Minimal HTML/CSS CV renderer driven entirely by the tailored draft model and render options. The same template must support browser preview and PDF output.", - "estimate": "2d", - "files": [ - "job-tracker-ui/src/cv-templates/* or shared renderer files", - "JobTrackerApi or UI-side render contract code", - "tests for renderer mapping" - ], - "verify": "Focused renderer/unit tests plus deterministic HTML output snapshot checks", - "inputs": [ - "M005/S03 tailored draft model", - "template/render option contract" - ], - "expected_output": [ - "ATS Minimal HTML/CSS renderer", - "template data-mapping layer", - "render snapshot/contract coverage" - ], - "observability_impact": "Renderer failures can identify missing/invalid draft sections or option payloads.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S04", - "id": "T02", - "title": "Add preview and PDF export pipeline", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Add preview and PDF export endpoints/workflow. Use one deterministic rendering path for preview and Playwright/Chromium-backed PDF generation, with stored/exportable artifacts and explicit manual download action.", - "estimate": "2d", - "files": [ - "JobTrackerApi/Controllers/* cv export endpoints", - "JobTrackerApi/Services/* pdf export service", - "job-tracker-ui/src/components/JobDetailsDialog.tsx or dedicated preview UI" - ], - "verify": "End-to-end verification that preview request succeeds and PDF export returns a downloadable file", - "inputs": [ - "S04/T01 renderer", - "browser/PDF runtime availability" - ], - "expected_output": [ - "preview endpoint/view", - "PDF export endpoint/service", - "downloadable PDF artifact" - ], - "observability_impact": "Export status, duration, and failure messages are visible for support/debugging.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S04", - "id": "T03", - "title": "Verify preview/export parity", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Add targeted browser/UAT verification that preview and downloaded PDF correspond to the same draft/template and that export stays manual.", - "estimate": "1d", - "files": [ - "job-tracker-ui browser verification artifacts", - ".gsd milestone UAT artifacts" - ], - "verify": "Browser/UAT flow: generate draft → preview ATS Minimal → download PDF → confirm parity evidence", - "inputs": [ - "S04/T02 working export flow" - ], - "expected_output": [ - "browser/UAT artifact for preview vs export parity", - "debug bundle or acceptance notes" - ], - "observability_impact": "Gives durable proof that preview/export parity holds in a real browser pass.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S05", - "id": "T01", - "title": "Add template library beyond ATS Minimal", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Add Modern Professional and Compact Technical templates on top of the shared draft/render contract, keeping preview/export parity and minimizing template-specific branching in core code.", - "estimate": "2d", - "files": [ - "job-tracker-ui/src/cv-templates/*", - "renderer tests" - ], - "verify": "Renderer tests cover ATS Minimal, Modern Professional, and Compact Technical", - "inputs": [ - "M005/S04 renderer contract" - ], - "expected_output": [ - "Two additional HTML/CSS templates", - "shared render contract exercised by all templates", - "template coverage tests" - ], - "observability_impact": "Template id and render diagnostics make template-specific failures traceable.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S05", - "id": "T02", - "title": "Add template-level layout controls", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Implement persisted layout controls: show/hide photo, one-page vs two-page mode, accent color, section ordering, and bullet density. Ensure controls live on the tailored draft/render-options layer and never mutate the canonical profile.", - "estimate": "2d", - "files": [ - "job-tracker-ui/src/components/JobDetailsDialog.tsx or dedicated export controls", - "JobTrackerApi tailored draft endpoints/models", - "job-tracker-ui tests" - ], - "verify": "Focused frontend/backend tests for persisted render options and preview updates", - "inputs": [ - "M005/S03 tailored draft persistence", - "M005/S04 renderer" - ], - "expected_output": [ - "render options UI + persistence", - "section ordering controls", - "density/page/photo toggles" - ], - "observability_impact": "Render options become inspectable and debuggable when previews/exported PDFs differ from expectation.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M005", - "slice_id": "S05", - "id": "T03", - "title": "Run full CV-to-PDF acceptance loop", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Run milestone-level acceptance on the whole loop using a real CV/job pair: upload, review uncertain fields, generate tailored draft, switch templates/options, preview, export, and confirm download path.", - "estimate": "1d", - "files": [ - ".gsd/milestones/M005/slices/S05/S05-UAT.md", - ".gsd milestone summaries" - ], - "verify": "Browser/UAT evidence for the full end-to-end CV intelligence/export workflow", - "inputs": [ - "All prior M005 slices" - ], - "expected_output": [ - "M005 slice summary/UAT evidence", - "acceptance checklist with screenshots/artifacts" - ], - "observability_impact": "Produces durable acceptance proof and highlights remaining trust gaps for later milestones.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M006", - "slice_id": "S01", - "id": "T01", - "title": "Refactor Gmail connection foundation", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Extract the current Gmail OAuth/token/status behavior into a clearer foundation service seam without changing user-visible behavior yet. Review `GmailController`, `GmailOAuthService`, `GmailConnection`, `JobTrackerContext`, and current migrations/bootstrap logic. Introduce any missing sync-state fields needed for later manual sync/history work (for example last sync attempt, last sync success, last sync error, last sync mode/source) while preserving existing token storage and refresh behavior.", - "estimate": "1 context window", - "files": [ - "JobTrackerApi/Controllers/GmailController.cs", - "JobTrackerApi/Services/GmailOAuthService.cs", - "Models/GmailConnection.cs", - "Data/JobTrackerContext.cs", - "JobTrackerApi/Program.cs", - "JobTrackerApi.Tests/GmailControllerTests.cs" - ], - "verify": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter GmailControllerTests", - "inputs": [ - "Existing Gmail OAuth service", - "Existing Gmail controller endpoints", - "Current Gmail connection model" - ], - "expected_output": [ - "Updated Gmail connection model/schema/bootstrap logic", - "Service/controller seam ready for later sync work", - "Focused backend regression coverage for status/connect/disconnect behavior" - ], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M006", - "slice_id": "S01", - "id": "T02", - "title": "Expose Gmail sync state in UI", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Surface the refined Gmail connection/sync state in the frontend where users already manage correspondence. Keep the current per-job UX working, but make connection state, last sync state, and actionable error messages explicit and durable so later global inbox/sync flows can reuse the same language and components.", - "estimate": "1 context window", - "files": [ - "job-tracker-ui/src/components/Correspondence.tsx", - "job-tracker-ui/src/types.ts", - "job-tracker-ui/src/correspondence-gmail-import.test.tsx", - "job-tracker-ui/src/i18n/translations.ts" - ], - "verify": "cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/correspondence-gmail-import.test.tsx", - "inputs": [ - "Current Correspondence component", - "Current Gmail status API response" - ], - "expected_output": [ - "Updated correspondence/settings UI state surfaces", - "Focused frontend regression coverage for Gmail connection state", - "Reusable copy/components for later global Gmail flows" - ], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M006", - "slice_id": "S01", - "id": "T03", - "title": "Prepare extension seam and docs", - "status": "pending", - "one_liner": "", - "narrative": "", - "verification_result": "", - "duration": "", - "completed_at": null, - "blocker_discovered": false, - "deviations": "", - "known_issues": "", - "key_files": [], - "key_decisions": [], - "full_summary_md": "", - "description": "Prepare low-risk Phase 2 extension seams inside the Gmail foundation without turning on AI-dependent behavior. Define interfaces/data slots for future semantic disambiguation and enrichment reasons/confidence so later milestones can attach them to sync/matching outcomes. Document the foundation decisions and the planned M007-M010 sequence so later slices do not rediscover the same boundaries.", - "estimate": "0.5 context window", - "files": [ - "JobTrackerApi/Services", - "docs", - "tools/summarizer/README.md", - ".gsd/milestones/M006/M006-CONTEXT.md" - ], - "verify": "rg -n \"Phase 2|semantic|enrichment|deterministic\" docs JobTrackerApi | head -50", - "inputs": [ - "M006 context artifact", - "Current AI/Ollama patterns" - ], - "expected_output": [ - "Interface/types for future enrichment seam", - "Foundation documentation updated", - "Tests/docs clarify deterministic-first behavior" - ], - "observability_impact": "", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S01", - "id": "T01", - "title": "Audited the frontend baseline, upgraded axios to remove the critical direct vulnerability, and restored reproducible local and Docker frontend builds.", - "status": "complete", - "one_liner": "Audited the frontend baseline, upgraded axios to remove the critical direct vulnerability, and restored reproducible local and Docker frontend builds.", - "narrative": "I audited the current frontend dependency and build baseline in `job-tracker-ui`, confirmed that the only direct critical finding was `axios`, and verified that the rest of the remaining audit findings were transitive through `react-scripts` and its older build chain. I then upgraded `axios` from `^1.13.6` to `^1.15.0`, refreshed the lockfile, and fixed a contaminated build workspace where root-owned files under `job-tracker-ui/build/static` were blocking local builds. After that, I rebuilt the frontend locally and inside Docker. The Docker build still failed once because the lockfile generated by the local Node 25/npm 11 toolchain was not accepted by the Node 20/npm 10 image used in the frontend Dockerfile. I regenerated the lockfile using the same Node 20/npm 10 container toolchain, re-ran `npm ci` under that environment, and verified that the frontend image now builds successfully. The result is a stable CRA baseline with the critical direct vulnerability removed and a clear follow-on decision: do not couple the upcoming auth/session hardening slice to a framework migration, but keep the broader migration on the roadmap because CRA remains the source of most remaining frontend audit debt.", - "verification_result": "Baseline and after-state checks were run on the frontend dependency graph, local build path, npm-version compatibility, and Docker image build. The critical direct audit finding was removed, the frontend now builds locally again, and the Docker frontend image build passes after regenerating the lockfile with the container toolchain. The only failing verification left in the frontend suite was a pair of pre-existing UI tests unrelated to the axios/package-lock remediation.", - "duration": "", - "completed_at": "2026-04-10T16:45:12.983Z", - "blocker_discovered": false, - "deviations": "`npm run build` initially failed because `job-tracker-ui/build/static` contained root-owned artifacts from an earlier containerized build. I repaired the workspace by cleaning/regenerating the directory through Docker before continuing. Docker `npm ci` then exposed an npm-version-specific lockfile mismatch (`npm 11` locally vs `npm 10` in the Node 20 image), so I regenerated the lockfile with the container toolchain to restore reproducible builds.", - "known_issues": "Frontend audit debt remains concentrated behind `react-scripts` and its transitive toolchain: 27 vulnerabilities remain after the direct axios remediation (9 low, 3 moderate, 15 high). Full retirement of that debt still requires a broader build-tool migration or equivalent ecosystem move.", - "key_files": [ - "job-tracker-ui/package.json", - "job-tracker-ui/package-lock.json" - ], - "key_decisions": [ - "D019 — remediate the direct critical dependency now, keep the CRA baseline stable for the next slice, and defer broader build-tool migration to dedicated follow-on work." - ], - "full_summary_md": "---\nid: T01\nparent: S01\nmilestone: M011\nkey_files:\n - job-tracker-ui/package.json\n - job-tracker-ui/package-lock.json\nkey_decisions:\n - D019 — remediate the direct critical dependency now, keep the CRA baseline stable for the next slice, and defer broader build-tool migration to dedicated follow-on work.\nduration: \nverification_result: mixed\ncompleted_at: 2026-04-10T16:45:12.981Z\nblocker_discovered: false\n---\n\n# T01: Audited the frontend baseline, upgraded axios to remove the critical direct vulnerability, and restored reproducible local and Docker frontend builds.\n\n**Audited the frontend baseline, upgraded axios to remove the critical direct vulnerability, and restored reproducible local and Docker frontend builds.**\n\n## What Happened\n\nI audited the current frontend dependency and build baseline in `job-tracker-ui`, confirmed that the only direct critical finding was `axios`, and verified that the rest of the remaining audit findings were transitive through `react-scripts` and its older build chain. I then upgraded `axios` from `^1.13.6` to `^1.15.0`, refreshed the lockfile, and fixed a contaminated build workspace where root-owned files under `job-tracker-ui/build/static` were blocking local builds. After that, I rebuilt the frontend locally and inside Docker. The Docker build still failed once because the lockfile generated by the local Node 25/npm 11 toolchain was not accepted by the Node 20/npm 10 image used in the frontend Dockerfile. I regenerated the lockfile using the same Node 20/npm 10 container toolchain, re-ran `npm ci` under that environment, and verified that the frontend image now builds successfully. The result is a stable CRA baseline with the critical direct vulnerability removed and a clear follow-on decision: do not couple the upcoming auth/session hardening slice to a framework migration, but keep the broader migration on the roadmap because CRA remains the source of most remaining frontend audit debt.\n\n## Verification\n\nBaseline and after-state checks were run on the frontend dependency graph, local build path, npm-version compatibility, and Docker image build. The critical direct audit finding was removed, the frontend now builds locally again, and the Docker frontend image build passes after regenerating the lockfile with the container toolchain. The only failing verification left in the frontend suite was a pair of pre-existing UI tests unrelated to the axios/package-lock remediation.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `Baseline: `cd job-tracker-ui && npm audit --audit-level=moderate --json` showed 28 vulnerabilities including 1 critical direct finding on axios <1.15.0.` | -1 | unknown (coerced from string) | 0ms |\n| 2 | `Baseline: `cd job-tracker-ui && npm run build` initially failed with EACCES because `job-tracker-ui/build/static` contained root-owned files.` | -1 | unknown (coerced from string) | 0ms |\n| 3 | `After remediation: `cd job-tracker-ui && npm install` updated axios to 1.15.0 and removed the direct critical vulnerability.` | -1 | unknown (coerced from string) | 0ms |\n| 4 | `After remediation: `cd job-tracker-ui && npm audit --audit-level=moderate --json` showed 27 remaining vulnerabilities and 0 critical findings.` | -1 | unknown (coerced from string) | 0ms |\n| 5 | `After remediation: `cd job-tracker-ui && npm run build` completed successfully.` | -1 | unknown (coerced from string) | 0ms |\n| 6 | `Compatibility check: `docker run --rm -v ... node:20-alpine sh -lc 'npm ci'` initially failed on a lockfile mismatch (`Missing: yaml@2.8.3 from lock file`).` | -1 | unknown (coerced from string) | 0ms |\n| 7 | `Compatibility fix: `docker run --rm --user 1000:1000 -v ... node:20-alpine sh -lc 'npm install'` regenerated the lockfile using the same npm major version as the Docker image.` | -1 | unknown (coerced from string) | 0ms |\n| 8 | `After compatibility fix: `docker run --rm -v ... node:20-alpine sh -lc 'npm ci --foreground-scripts=false'` completed successfully.` | -1 | unknown (coerced from string) | 0ms |\n| 9 | `Container verification: `cd /home/pi/development/JobTracker && docker compose build frontend` completed successfully.` | -1 | unknown (coerced from string) | 0ms |\n| 10 | `Regression signal: `cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false` finished with 16 passing suites and 2 failing suites (`daily-control-loop.test.tsx`, `end-to-end-trust-loop.test.tsx`) that appear unrelated to the dependency remediation itself.` | -1 | unknown (coerced from string) | 0ms |\n\n## Deviations\n\n`npm run build` initially failed because `job-tracker-ui/build/static` contained root-owned artifacts from an earlier containerized build. I repaired the workspace by cleaning/regenerating the directory through Docker before continuing. Docker `npm ci` then exposed an npm-version-specific lockfile mismatch (`npm 11` locally vs `npm 10` in the Node 20 image), so I regenerated the lockfile with the container toolchain to restore reproducible builds.\n\n## Known Issues\n\nFrontend audit debt remains concentrated behind `react-scripts` and its transitive toolchain: 27 vulnerabilities remain after the direct axios remediation (9 low, 3 moderate, 15 high). Full retirement of that debt still requires a broader build-tool migration or equivalent ecosystem move.\n\n## Files Created/Modified\n\n- `job-tracker-ui/package.json`\n- `job-tracker-ui/package-lock.json`\n", - "description": "1. Inspect the current frontend toolchain and dependency graph in `job-tracker-ui/`.\n2. Re-run and capture the current audit/build baseline (`npm audit`, install/build/test commands) so the slice has a before/after proof point.\n3. Identify which findings are direct, which are transitive through `react-scripts`, and what minimum safe upgrades are possible without migration.\n4. Produce a short implementation decision for this slice: immediate package remediation only, or package remediation plus build-tool migration foundation if that is the smallest credible way to retire the risk.", - "estimate": "0.5-1 day", - "files": [ - "job-tracker-ui/package.json", - "job-tracker-ui/package-lock.json", - "job-tracker-ui/", - "docker-compose.yml" - ], - "verify": "cd job-tracker-ui && npm audit --audit-level=moderate || true\ncd job-tracker-ui && npm run build", - "inputs": [ - "job-tracker-ui/package.json", - "existing `npm audit` results", - "current frontend build/test scripts" - ], - "expected_output": [ - "Verified dependency/audit baseline for the current frontend", - "Concrete remediation strategy grounded in the actual package graph" - ], - "observability_impact": "Creates the slice’s baseline proof for dependency risk and build health.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S01", - "id": "T02", - "title": "Applied the frontend dependency remediation and restored a reproducible build path for both local and Docker builds.", - "status": "complete", - "one_liner": "Applied the frontend dependency remediation and restored a reproducible build path for both local and Docker builds.", - "narrative": "I implemented the smallest safe dependency remediation by upgrading the direct `axios` dependency to `^1.15.0` and refreshing the lockfile accordingly. I also normalized the lockfile against the Node 20/npm 10 toolchain used by the frontend Docker image so that `npm ci` and `docker compose build frontend` remain reproducible. I did not start a framework migration in this task because the audit evidence showed that the critical risk could be retired with a much smaller change surface, and the upcoming auth/session slice should not inherit migration churn unnecessarily.", - "verification_result": "The remediated dependency set was verified through local install/build, container `npm ci`, and a full frontend Docker image build. The direct critical axios finding is no longer present.", - "duration": "", - "completed_at": "2026-04-10T16:46:52.965Z", - "blocker_discovered": false, - "deviations": "The implementation change itself was minimal: only the direct vulnerable dependency and lockfile needed updates. The larger build-path work was environmental rather than architectural, centered on root-owned build artifacts and npm-version lockfile compatibility.", - "known_issues": "The broader `react-scripts` transitive vulnerability set is still present and remains scheduled follow-up work under the milestone’s frontend-platform hardening track.", - "key_files": [ - "job-tracker-ui/package.json", - "job-tracker-ui/package-lock.json" - ], - "key_decisions": [ - "D019 — keep the CRA baseline stable for the next slice instead of coupling auth hardening to a framework migration." - ], - "full_summary_md": "---\nid: T02\nparent: S01\nmilestone: M011\nkey_files:\n - job-tracker-ui/package.json\n - job-tracker-ui/package-lock.json\nkey_decisions:\n - D019 — keep the CRA baseline stable for the next slice instead of coupling auth hardening to a framework migration.\nduration: \nverification_result: mixed\ncompleted_at: 2026-04-10T16:46:52.964Z\nblocker_discovered: false\n---\n\n# T02: Applied the frontend dependency remediation and restored a reproducible build path for both local and Docker builds.\n\n**Applied the frontend dependency remediation and restored a reproducible build path for both local and Docker builds.**\n\n## What Happened\n\nI implemented the smallest safe dependency remediation by upgrading the direct `axios` dependency to `^1.15.0` and refreshing the lockfile accordingly. I also normalized the lockfile against the Node 20/npm 10 toolchain used by the frontend Docker image so that `npm ci` and `docker compose build frontend` remain reproducible. I did not start a framework migration in this task because the audit evidence showed that the critical risk could be retired with a much smaller change surface, and the upcoming auth/session slice should not inherit migration churn unnecessarily.\n\n## Verification\n\nThe remediated dependency set was verified through local install/build, container `npm ci`, and a full frontend Docker image build. The direct critical axios finding is no longer present.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | ``cd job-tracker-ui && npm install` updated axios to 1.15.0 and synchronized the package tree.` | -1 | unknown (coerced from string) | 0ms |\n| 2 | ``cd job-tracker-ui && npm run build` succeeded after the workspace cleanup.` | -1 | unknown (coerced from string) | 0ms |\n| 3 | ``docker run --rm --user 1000:1000 -v /home/pi/development/JobTracker/job-tracker-ui:/app -w /app node:20-alpine sh -lc 'npm install'` regenerated the lockfile for the container toolchain.` | -1 | unknown (coerced from string) | 0ms |\n| 4 | ``docker run --rm -v /home/pi/development/JobTracker/job-tracker-ui:/app -w /app node:20-alpine sh -lc 'npm ci --foreground-scripts=false'` succeeded.` | -1 | unknown (coerced from string) | 0ms |\n| 5 | ``cd /home/pi/development/JobTracker && docker compose build frontend` succeeded.` | -1 | unknown (coerced from string) | 0ms |\n\n## Deviations\n\nThe implementation change itself was minimal: only the direct vulnerable dependency and lockfile needed updates. The larger build-path work was environmental rather than architectural, centered on root-owned build artifacts and npm-version lockfile compatibility.\n\n## Known Issues\n\nThe broader `react-scripts` transitive vulnerability set is still present and remains scheduled follow-up work under the milestone’s frontend-platform hardening track.\n\n## Files Created/Modified\n\n- `job-tracker-ui/package.json`\n- `job-tracker-ui/package-lock.json`\n", - "description": "1. Update direct vulnerable dependencies first, starting with the critical direct package.\n2. Apply the smallest safe lockfile/package changes that materially reduce the current risk.\n3. If `react-scripts` remains the blocker, introduce the minimum viable migration foundation needed to move away from it safely instead of forcing partial unsafe upgrades.\n4. Keep the existing app behavior and API integration intact while changing the build baseline.", - "estimate": "1-2 days", - "files": [ - "job-tracker-ui/package.json", - "job-tracker-ui/package-lock.json", - "job-tracker-ui/Dockerfile", - "job-tracker-ui/nginx.conf", - "job-tracker-ui/public/index.html", - "job-tracker-ui/src/index.tsx" - ], - "verify": "cd job-tracker-ui && npm install\ncd job-tracker-ui && npm run build", - "inputs": [ - "T01 findings", - "current CRA/Vite-compatible frontend entrypoints and build config" - ], - "expected_output": [ - "Remediated frontend dependency set", - "Updated lockfile and build configuration matching the chosen direction" - ], - "observability_impact": "Reduces dependency risk and proves the new build path is executable in this worktree.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S01", - "id": "T03", - "title": "Verified the remediated frontend baseline, quantified the remaining CRA-driven audit debt, and recorded the stable platform direction for the next slice.", - "status": "complete", - "one_liner": "Verified the remediated frontend baseline, quantified the remaining CRA-driven audit debt, and recorded the stable platform direction for the next slice.", - "narrative": "I reran the frontend audit, local production build, container install path, and Docker image build to establish the after-state baseline for S01. The critical direct dependency issue is gone, the local and Docker build paths are both working, and the remaining audit debt is now clearly attributable to the older CRA/react-scripts toolchain rather than the direct application dependency set. I also ran the full frontend test suite to measure regression risk. Most suites passed, but two existing workflow/package tests still fail for reasons outside the dependency remediation. With that evidence in hand, the platform direction for this slice is now explicit: the frontend can safely proceed into auth hardening on a stabilized CRA baseline, but a broader build-tool migration remains necessary later to remove the remaining transitive audit debt.", - "verification_result": "After-state verification included frontend audit, local build, container `npm ci`, Docker image build, and full frontend test execution. The baseline is reproducible, the critical direct vulnerability is retired, and remaining failures are isolated as follow-up work rather than hidden.", - "duration": "", - "completed_at": "2026-04-10T16:47:07.042Z", - "blocker_discovered": false, - "deviations": "The final verification surfaced two failing frontend suites that were not introduced by the dependency change and appear to be pre-existing behavioral drift in workflow/package UI tests. I recorded them as remaining issues instead of expanding this task into unrelated UI behavior fixes.", - "known_issues": "`CI=true npm test -- --runInBand --watch=false` still reports failing suites in `src/daily-control-loop.test.tsx` and `src/end-to-end-trust-loop.test.tsx`, with assertions around expected workflow/package text and saved package values. Those failures need separate follow-up and are not explained by the axios/package-lock update alone.", - "key_files": [ - "job-tracker-ui/package.json", - "job-tracker-ui/package-lock.json" - ], - "key_decisions": [ - "D019 — stabilize the CRA baseline now and defer the larger migration decision to a later dedicated step." - ], - "full_summary_md": "---\nid: T03\nparent: S01\nmilestone: M011\nkey_files:\n - job-tracker-ui/package.json\n - job-tracker-ui/package-lock.json\nkey_decisions:\n - D019 — stabilize the CRA baseline now and defer the larger migration decision to a later dedicated step.\nduration: \nverification_result: mixed\ncompleted_at: 2026-04-10T16:47:07.041Z\nblocker_discovered: false\n---\n\n# T03: Verified the remediated frontend baseline, quantified the remaining CRA-driven audit debt, and recorded the stable platform direction for the next slice.\n\n**Verified the remediated frontend baseline, quantified the remaining CRA-driven audit debt, and recorded the stable platform direction for the next slice.**\n\n## What Happened\n\nI reran the frontend audit, local production build, container install path, and Docker image build to establish the after-state baseline for S01. The critical direct dependency issue is gone, the local and Docker build paths are both working, and the remaining audit debt is now clearly attributable to the older CRA/react-scripts toolchain rather than the direct application dependency set. I also ran the full frontend test suite to measure regression risk. Most suites passed, but two existing workflow/package tests still fail for reasons outside the dependency remediation. With that evidence in hand, the platform direction for this slice is now explicit: the frontend can safely proceed into auth hardening on a stabilized CRA baseline, but a broader build-tool migration remains necessary later to remove the remaining transitive audit debt.\n\n## Verification\n\nAfter-state verification included frontend audit, local build, container `npm ci`, Docker image build, and full frontend test execution. The baseline is reproducible, the critical direct vulnerability is retired, and remaining failures are isolated as follow-up work rather than hidden.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | ``cd job-tracker-ui && npm audit --audit-level=moderate --json` now reports 0 critical findings and 27 remaining transitive findings.` | -1 | unknown (coerced from string) | 0ms |\n| 2 | ``cd job-tracker-ui && npm run build` succeeded.` | -1 | unknown (coerced from string) | 0ms |\n| 3 | ``docker run --rm -v /home/pi/development/JobTracker/job-tracker-ui:/app -w /app node:20-alpine sh -lc 'npm ci --foreground-scripts=false'` succeeded.` | -1 | unknown (coerced from string) | 0ms |\n| 4 | ``cd /home/pi/development/JobTracker && docker compose build frontend` succeeded.` | -1 | unknown (coerced from string) | 0ms |\n| 5 | ``cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false` produced 16 passing suites and 2 failing suites, leaving a clear follow-up list instead of an unverified baseline.` | -1 | unknown (coerced from string) | 0ms |\n\n## Deviations\n\nThe final verification surfaced two failing frontend suites that were not introduced by the dependency change and appear to be pre-existing behavioral drift in workflow/package UI tests. I recorded them as remaining issues instead of expanding this task into unrelated UI behavior fixes.\n\n## Known Issues\n\n`CI=true npm test -- --runInBand --watch=false` still reports failing suites in `src/daily-control-loop.test.tsx` and `src/end-to-end-trust-loop.test.tsx`, with assertions around expected workflow/package text and saved package values. Those failures need separate follow-up and are not explained by the axios/package-lock update alone.\n\n## Files Created/Modified\n\n- `job-tracker-ui/package.json`\n- `job-tracker-ui/package-lock.json`\n", - "description": "1. Run focused frontend tests and any app-entry smoke verification needed after the dependency/build changes.\n2. Re-run `npm audit` and compare against the baseline to confirm the critical direct issue is retired and quantify remaining transitive debt.\n3. Validate the frontend container/build path still works with the chosen approach.\n4. Document the resulting baseline and any intentionally deferred migration work so later slices are not guessing.", - "estimate": "0.5-1 day", - "files": [ - "job-tracker-ui/package.json", - "job-tracker-ui/package-lock.json", - "job-tracker-ui/Dockerfile", - "job-tracker-ui/src/", - "docker-compose.yml" - ], - "verify": "cd job-tracker-ui && npm audit --audit-level=moderate || true\ncd job-tracker-ui && CI=true npm test -- --runInBand --watch=false\ncd job-tracker-ui && npm run build\ncd /home/pi/development/JobTracker && docker compose build frontend", - "inputs": [ - "Updated frontend from T02", - "existing frontend tests" - ], - "expected_output": [ - "After-state audit/build/test evidence", - "Documented frontend platform direction and deferred debt list" - ], - "observability_impact": "Produces the slice-level evidence that S01 actually retired risk instead of only moving packages around.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S02", - "id": "T01", - "title": "Mapped the existing bearer-token auth surface and defined the cookie-backed target session model for S02.", - "status": "complete", - "one_liner": "Mapped the existing bearer-token auth surface and defined the cookie-backed target session model for S02.", - "narrative": "I mapped the full current auth/session surface across the frontend and API. The frontend presently treats a JWT stored in `localStorage` or `sessionStorage` as the primary session source of truth: `auth.ts` persists it, `api.ts` attaches it as a Bearer header, `App.tsx` gates protected routes based on whether the token exists, and components like `GoogleAuthCard`, `AuthStatusCard`, `UserManagementCard`, and `themePrefs.ts` read directly from that token or its decoded payload. On the API side, local app auth is JWT Bearer-based, with the local token issued by `TokenService` and returned from `/auth/login`, `/auth/register`, and `/auth/google/exchange`; authorization then depends on the `Authorization: Bearer` header path configured in `Program.cs`. That map made the migration seam clear: if we only remove browser storage without changing the API transport and frontend route guards together, we will break protected flows. I therefore defined the target model for S02 as an HttpOnly cookie-backed app session for the primary local auth path, with the API reading the local app JWT from a cookie, the frontend moving away from browser-stored app tokens as the primary session source, and CSRF protection added for state-changing requests. Google credential exchange should remain server-side and issue the same app session transport so the app has one coherent authenticated state model.", - "verification_result": "Verified the current auth/session surface with targeted code search and file inspection across the frontend auth helpers, protected-route logic, Google sign-in flow, and API auth/token setup. The complete list of token assumptions and the replacement session seam are now explicit.", - "duration": "", - "completed_at": "2026-04-10T16:49:40.585Z", - "blocker_discovered": false, - "deviations": "None.", - "known_issues": "Google sign-in and a few frontend preference helpers currently treat a browser-stored JWT as the source of truth, so the implementation task needs to update those touchpoints together rather than piecemeal.", - "key_files": [ - "job-tracker-ui/src/auth.ts", - "job-tracker-ui/src/api.ts", - "job-tracker-ui/src/pages/LoginPage.tsx", - "job-tracker-ui/src/components/GoogleAuthCard.tsx", - "job-tracker-ui/src/components/AuthStatusCard.tsx", - "job-tracker-ui/src/themePrefs.ts", - "job-tracker-ui/src/components/UserManagementCard.tsx", - "job-tracker-ui/src/App.tsx", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Services/TokenService.cs", - "JobTrackerApi/Program.cs" - ], - "key_decisions": [ - "D020 — replace browser-stored bearer tokens with an HttpOnly cookie-backed app session and explicit CSRF handling for state-changing requests." - ], - "full_summary_md": "---\nid: T01\nparent: S02\nmilestone: M011\nkey_files:\n - job-tracker-ui/src/auth.ts\n - job-tracker-ui/src/api.ts\n - job-tracker-ui/src/pages/LoginPage.tsx\n - job-tracker-ui/src/components/GoogleAuthCard.tsx\n - job-tracker-ui/src/components/AuthStatusCard.tsx\n - job-tracker-ui/src/themePrefs.ts\n - job-tracker-ui/src/components/UserManagementCard.tsx\n - job-tracker-ui/src/App.tsx\n - JobTrackerApi/Controllers/AuthController.cs\n - JobTrackerApi/Services/TokenService.cs\n - JobTrackerApi/Program.cs\nkey_decisions:\n - D020 — replace browser-stored bearer tokens with an HttpOnly cookie-backed app session and explicit CSRF handling for state-changing requests.\nduration: \nverification_result: mixed\ncompleted_at: 2026-04-10T16:49:40.584Z\nblocker_discovered: false\n---\n\n# T01: Mapped the existing bearer-token auth surface and defined the cookie-backed target session model for S02.\n\n**Mapped the existing bearer-token auth surface and defined the cookie-backed target session model for S02.**\n\n## What Happened\n\nI mapped the full current auth/session surface across the frontend and API. The frontend presently treats a JWT stored in `localStorage` or `sessionStorage` as the primary session source of truth: `auth.ts` persists it, `api.ts` attaches it as a Bearer header, `App.tsx` gates protected routes based on whether the token exists, and components like `GoogleAuthCard`, `AuthStatusCard`, `UserManagementCard`, and `themePrefs.ts` read directly from that token or its decoded payload. On the API side, local app auth is JWT Bearer-based, with the local token issued by `TokenService` and returned from `/auth/login`, `/auth/register`, and `/auth/google/exchange`; authorization then depends on the `Authorization: Bearer` header path configured in `Program.cs`. That map made the migration seam clear: if we only remove browser storage without changing the API transport and frontend route guards together, we will break protected flows. I therefore defined the target model for S02 as an HttpOnly cookie-backed app session for the primary local auth path, with the API reading the local app JWT from a cookie, the frontend moving away from browser-stored app tokens as the primary session source, and CSRF protection added for state-changing requests. Google credential exchange should remain server-side and issue the same app session transport so the app has one coherent authenticated state model.\n\n## Verification\n\nVerified the current auth/session surface with targeted code search and file inspection across the frontend auth helpers, protected-route logic, Google sign-in flow, and API auth/token setup. The complete list of token assumptions and the replacement session seam are now explicit.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | ``rg -n \"authToken|Authorization|Bearer|clearAuthToken|setAuthToken|getAuthToken|JwtBearer|TokenService|request-password-reset|login|logout\" job-tracker-ui/src JobTrackerApi -S` enumerated the frontend and API auth touchpoints.` | -1 | unknown (coerced from string) | 0ms |\n| 2 | `Reviewed `job-tracker-ui/src/auth.ts` and confirmed that localStorage/sessionStorage currently hold the app token.` | -1 | unknown (coerced from string) | 0ms |\n| 3 | `Reviewed `job-tracker-ui/src/api.ts` and confirmed the Authorization header is attached from browser storage on each request.` | -1 | unknown (coerced from string) | 0ms |\n| 4 | `Reviewed `job-tracker-ui/src/App.tsx`, `LoginPage.tsx`, `GoogleAuthCard.tsx`, `AuthStatusCard.tsx`, `UserManagementCard.tsx`, and `themePrefs.ts` to identify direct frontend dependencies on browser-stored JWT state.` | -1 | unknown (coerced from string) | 0ms |\n| 5 | `Reviewed `JobTrackerApi/Controllers/AuthController.cs`, `JobTrackerApi/Services/TokenService.cs`, and `JobTrackerApi/Program.cs` to confirm local app auth is currently JWT Bearer-based and returned directly to the frontend.` | -1 | unknown (coerced from string) | 0ms |\n\n## Deviations\n\nNone.\n\n## Known Issues\n\nGoogle sign-in and a few frontend preference helpers currently treat a browser-stored JWT as the source of truth, so the implementation task needs to update those touchpoints together rather than piecemeal.\n\n## Files Created/Modified\n\n- `job-tracker-ui/src/auth.ts`\n- `job-tracker-ui/src/api.ts`\n- `job-tracker-ui/src/pages/LoginPage.tsx`\n- `job-tracker-ui/src/components/GoogleAuthCard.tsx`\n- `job-tracker-ui/src/components/AuthStatusCard.tsx`\n- `job-tracker-ui/src/themePrefs.ts`\n- `job-tracker-ui/src/components/UserManagementCard.tsx`\n- `job-tracker-ui/src/App.tsx`\n- `JobTrackerApi/Controllers/AuthController.cs`\n- `JobTrackerApi/Services/TokenService.cs`\n- `JobTrackerApi/Program.cs`\n", - "description": "1. Inspect the current auth/session path across frontend and API: login, token issuance, request auth, logout, protected-route checks, and unauthorized handling.\n2. Identify every place that assumes a browser-stored bearer token (`localStorage`, `sessionStorage`, Authorization headers).\n3. Design the target session model for this app: secure cookie issuance, server expectations, CSRF strategy for state-changing requests, and compatibility with admin and Gmail-related flows.\n4. Record the migration seam so implementation can proceed without partial mixed-state confusion.", - "estimate": "0.5-1 day", - "files": [ - "job-tracker-ui/src/auth.ts", - "job-tracker-ui/src/api.ts", - "job-tracker-ui/src/App.tsx", - "job-tracker-ui/src/pages/LoginPage.tsx", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Services/TokenService.cs", - "JobTrackerApi/Program.cs" - ], - "verify": "rg -n \"authToken|Authorization|Bearer|clearAuthToken|setAuthToken|JwtBearer|TokenService|request-password-reset|login\" job-tracker-ui/src JobTrackerApi -S", - "inputs": [ - "Current frontend auth helpers", - "Current API auth/token setup", - "S01 stabilized frontend baseline" - ], - "expected_output": [ - "Concrete auth/session migration design grounded in current code", - "Exact list of frontend and API touchpoints to change" - ], - "observability_impact": "Creates the baseline map for auth/session diagnostics and compatibility checks.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S02", - "id": "T02", - "title": "Replaced browser-stored bearer auth with a cookie-backed session transport across the frontend and API.", - "status": "complete", - "one_liner": "Replaced browser-stored bearer auth with a cookie-backed session transport across the frontend and API.", - "narrative": "I rewired the primary local auth path so the API now issues the app JWT into secure cookies instead of returning it for browser storage, and the frontend now operates on session state rather than persisted bearer tokens. On the frontend, `job-tracker-ui/src/auth.ts` now manages only lightweight client auth metadata and CSRF helpers; `job-tracker-ui/src/api.ts` uses `withCredentials`, axios XSRF settings, and explicit 401 cleanup; `job-tracker-ui/src/App.tsx` resolves auth through `/auth/me` plus `auth-changed` events for route gating; and `job-tracker-ui/src/pages/LoginPage.tsx`, `src/pages/ProfilePage.tsx`, `src/components/GoogleAuthCard.tsx`, `src/components/AuthStatusCard.tsx`, `src/components/UserManagementCard.tsx`, and `src/themePrefs.ts` were updated to stop depending on browser-stored JWTs. On the API side, `JobTrackerApi/Controllers/AuthController.cs` now signs users in by setting session and CSRF cookies, adds logout and CSRF endpoints, and accepts `RememberMe` on local and Google auth requests. `JobTrackerApi/Program.cs` now reads the local JWT from the auth cookie and enforces CSRF on mutating requests for authenticated sessions. `JobTrackerApi/Services/AuthSessionOptions.cs` centralizes cookie/header naming and cookie settings so the server transport stays coherent.", - "verification_result": "Verified the new transport with focused API auth tests and updated frontend auth tests. The API project builds cleanly with cookie-based JWT extraction and CSRF enforcement in place, and the frontend builds cleanly after removing token-storage assumptions from login/profile/admin/Google auth surfaces.", - "duration": "", - "completed_at": "2026-04-10T19:57:16.245Z", - "blocker_discovered": false, - "deviations": "None.", - "known_issues": "A full live browser login/logout round-trip could not be completed against the existing local database because `admin@example.com` in this checkout does not accept the placeholder development password from `appsettings.Development.json`. The transport itself is covered by focused tests; browser verification was limited to protected-route and unauthenticated session behavior.", - "key_files": [ - "job-tracker-ui/src/auth.ts", - "job-tracker-ui/src/api.ts", - "job-tracker-ui/src/App.tsx", - "job-tracker-ui/src/pages/LoginPage.tsx", - "job-tracker-ui/src/pages/ProfilePage.tsx", - "job-tracker-ui/src/components/GoogleAuthCard.tsx", - "job-tracker-ui/src/components/AuthStatusCard.tsx", - "job-tracker-ui/src/components/UserManagementCard.tsx", - "job-tracker-ui/src/themePrefs.ts", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Program.cs", - "JobTrackerApi/Services/AuthSessionOptions.cs" - ], - "key_decisions": [ - "Keep bearer token generation on the server but move browser transport to secure cookies.", - "Use `/auth/me` plus an `auth-changed` event as the frontend session truth source instead of localStorage/sessionStorage JWT reads.", - "Pair the session cookie with a separate CSRF cookie/header contract for mutating requests." - ], - "full_summary_md": "---\nid: T02\nparent: S02\nmilestone: M011\nkey_files:\n - job-tracker-ui/src/auth.ts\n - job-tracker-ui/src/api.ts\n - job-tracker-ui/src/App.tsx\n - job-tracker-ui/src/pages/LoginPage.tsx\n - job-tracker-ui/src/pages/ProfilePage.tsx\n - job-tracker-ui/src/components/GoogleAuthCard.tsx\n - job-tracker-ui/src/components/AuthStatusCard.tsx\n - job-tracker-ui/src/components/UserManagementCard.tsx\n - job-tracker-ui/src/themePrefs.ts\n - JobTrackerApi/Controllers/AuthController.cs\n - JobTrackerApi/Program.cs\n - JobTrackerApi/Services/AuthSessionOptions.cs\nkey_decisions:\n - Keep bearer token generation on the server but move browser transport to secure cookies.\n - Use `/auth/me` plus an `auth-changed` event as the frontend session truth source instead of localStorage/sessionStorage JWT reads.\n - Pair the session cookie with a separate CSRF cookie/header contract for mutating requests.\nduration: \nverification_result: passed\ncompleted_at: 2026-04-10T19:57:16.244Z\nblocker_discovered: false\n---\n\n# T02: Replaced browser-stored bearer auth with a cookie-backed session transport across the frontend and API.\n\n**Replaced browser-stored bearer auth with a cookie-backed session transport across the frontend and API.**\n\n## What Happened\n\nI rewired the primary local auth path so the API now issues the app JWT into secure cookies instead of returning it for browser storage, and the frontend now operates on session state rather than persisted bearer tokens. On the frontend, `job-tracker-ui/src/auth.ts` now manages only lightweight client auth metadata and CSRF helpers; `job-tracker-ui/src/api.ts` uses `withCredentials`, axios XSRF settings, and explicit 401 cleanup; `job-tracker-ui/src/App.tsx` resolves auth through `/auth/me` plus `auth-changed` events for route gating; and `job-tracker-ui/src/pages/LoginPage.tsx`, `src/pages/ProfilePage.tsx`, `src/components/GoogleAuthCard.tsx`, `src/components/AuthStatusCard.tsx`, `src/components/UserManagementCard.tsx`, and `src/themePrefs.ts` were updated to stop depending on browser-stored JWTs. On the API side, `JobTrackerApi/Controllers/AuthController.cs` now signs users in by setting session and CSRF cookies, adds logout and CSRF endpoints, and accepts `RememberMe` on local and Google auth requests. `JobTrackerApi/Program.cs` now reads the local JWT from the auth cookie and enforces CSRF on mutating requests for authenticated sessions. `JobTrackerApi/Services/AuthSessionOptions.cs` centralizes cookie/header naming and cookie settings so the server transport stays coherent.\n\n## Verification\n\nVerified the new transport with focused API auth tests and updated frontend auth tests. The API project builds cleanly with cookie-based JWT extraction and CSRF enforcement in place, and the frontend builds cleanly after removing token-storage assumptions from login/profile/admin/Google auth surfaces.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter FullyQualifiedName~Auth` | 0 | ✅ pass | 163ms |\n| 2 | `cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/login-page.test.tsx src/profile-page.test.tsx` | 0 | ✅ pass | 15267ms |\n| 3 | `dotnet build JobTrackerApi/JobTrackerApi.csproj` | 0 | ✅ pass | 5430ms |\n| 4 | `cd job-tracker-ui && npm run build` | 0 | ✅ pass | 0ms |\n\n## Deviations\n\nNone.\n\n## Known Issues\n\nA full live browser login/logout round-trip could not be completed against the existing local database because `admin@example.com` in this checkout does not accept the placeholder development password from `appsettings.Development.json`. The transport itself is covered by focused tests; browser verification was limited to protected-route and unauthenticated session behavior.\n\n## Files Created/Modified\n\n- `job-tracker-ui/src/auth.ts`\n- `job-tracker-ui/src/api.ts`\n- `job-tracker-ui/src/App.tsx`\n- `job-tracker-ui/src/pages/LoginPage.tsx`\n- `job-tracker-ui/src/pages/ProfilePage.tsx`\n- `job-tracker-ui/src/components/GoogleAuthCard.tsx`\n- `job-tracker-ui/src/components/AuthStatusCard.tsx`\n- `job-tracker-ui/src/components/UserManagementCard.tsx`\n- `job-tracker-ui/src/themePrefs.ts`\n- `JobTrackerApi/Controllers/AuthController.cs`\n- `JobTrackerApi/Program.cs`\n- `JobTrackerApi/Services/AuthSessionOptions.cs`\n", - "description": "1. Implement the new primary auth/session transport on the API and frontend.\n2. Move local login away from browser-stored bearer tokens toward secure cookie-based sessions (with associated CSRF/session handling as required by the chosen design).\n3. Update frontend request handling, logout, protected-route checks, and unauthorized behavior to match the new model.\n4. Preserve existing app flows, including admin and Gmail-linked surfaces, under the new auth/session contract.", - "estimate": "1.5-2.5 days", - "files": [ - "job-tracker-ui/src/auth.ts", - "job-tracker-ui/src/api.ts", - "job-tracker-ui/src/App.tsx", - "job-tracker-ui/src/pages/LoginPage.tsx", - "job-tracker-ui/src/pages/ProfilePage.tsx", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Program.cs", - "JobTrackerApi/Services/TokenService.cs" - ], - "verify": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter Auth\ncd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/login-page.test.tsx src/profile-page.test.tsx", - "inputs": [ - "T01 migration design", - "existing auth endpoints and protected-route behavior" - ], - "expected_output": [ - "Working cookie/session-based primary auth path", - "Updated frontend auth/request logic aligned with the new server contract" - ], - "observability_impact": "Improves session failure visibility and removes client-side token persistence as the primary trust mechanism.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S02", - "id": "T03", - "title": "Added rate limiting and verified protected-route / unauthenticated session behavior for the new auth model.", - "status": "complete", - "one_liner": "Added rate limiting and verified protected-route / unauthenticated session behavior for the new auth model.", - "narrative": "I hardened the auth-sensitive edges around the new session model and verified the protected-route behavior that was practical in this environment. `JobTrackerApi/Program.cs` now registers fixed-window rate limiting policies for login-style requests and auth-triggered email paths, and `JobTrackerApi/Controllers/AuthController.cs` / `JobTrackerApi/Controllers/UsersController.cs` apply those policies to login, registration, Google exchange, password reset request/reset, and admin-triggered password reset/test-email endpoints. I also adjusted `JobTrackerApi/appsettings.Development.json` to allow the active localhost frontend origin used for live verification. On the frontend side, `job-tracker-ui/src/App.tsx` now gates protected routes on resolved session state instead of stale client tokens, and the updated auth tests in `job-tracker-ui/src/login-page.test.tsx` plus the API contract test in `JobTrackerApi.Tests/AuthAndSystemControllerTests.cs` reflect the cookie/session model. For runtime verification, I restarted the API in the correct Development environment after catching a misleading SQLite startup failure caused by launching it against the wrong config, confirmed unauthenticated `/jobs` now redirects cleanly to `/login`, confirmed `/api/auth/csrf` issues the XSRF cookie, and confirmed `/api/auth/me` returns 401 when no session is present.", - "verification_result": "Verified auth abuse controls and protected-route behavior with focused API tests, frontend auth tests/build, direct HTTP checks for CSRF and unauthorized session responses, and a browser pass against the locally running frontend/API pair.", - "duration": "", - "completed_at": "2026-04-10T19:57:41.019Z", - "blocker_discovered": false, - "deviations": "The browser-backed verification covered protected-route redirect and unauthenticated session behavior, but not a successful live login/logout round-trip because the existing local development database does not accept the placeholder admin password in `JobTrackerApi/appsettings.Development.json`.", - "known_issues": "Google/Gmail-linked flows were preserved in code but not fully browser-verified in this pass because the local environment lacks a trustworthy authenticated user session and valid Google credentials for a clean end-to-end exchange test.", - "key_files": [ - "JobTrackerApi/Program.cs", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Controllers/UsersController.cs", - "JobTrackerApi/appsettings.Development.json", - "job-tracker-ui/src/App.tsx", - "job-tracker-ui/src/api.ts", - "job-tracker-ui/src/pages/LoginPage.tsx", - "job-tracker-ui/src/login-page.test.tsx", - "JobTrackerApi.Tests/AuthAndSystemControllerTests.cs" - ], - "key_decisions": [ - "Use ASP.NET Core rate limiting policies partitioned by client IP for login and auth-email paths.", - "Treat the development environment itself as part of auth verification: the API must run with `ASPNETCORE_ENVIRONMENT=Development` and include the active frontend origin in CORS for cookie-based dev testing." - ], - "full_summary_md": "---\nid: T03\nparent: S02\nmilestone: M011\nkey_files:\n - JobTrackerApi/Program.cs\n - JobTrackerApi/Controllers/AuthController.cs\n - JobTrackerApi/Controllers/UsersController.cs\n - JobTrackerApi/appsettings.Development.json\n - job-tracker-ui/src/App.tsx\n - job-tracker-ui/src/api.ts\n - job-tracker-ui/src/pages/LoginPage.tsx\n - job-tracker-ui/src/login-page.test.tsx\n - JobTrackerApi.Tests/AuthAndSystemControllerTests.cs\nkey_decisions:\n - Use ASP.NET Core rate limiting policies partitioned by client IP for login and auth-email paths.\n - Treat the development environment itself as part of auth verification: the API must run with `ASPNETCORE_ENVIRONMENT=Development` and include the active frontend origin in CORS for cookie-based dev testing.\nduration: \nverification_result: mixed\ncompleted_at: 2026-04-10T19:57:41.019Z\nblocker_discovered: false\n---\n\n# T03: Added rate limiting and verified protected-route / unauthenticated session behavior for the new auth model.\n\n**Added rate limiting and verified protected-route / unauthenticated session behavior for the new auth model.**\n\n## What Happened\n\nI hardened the auth-sensitive edges around the new session model and verified the protected-route behavior that was practical in this environment. `JobTrackerApi/Program.cs` now registers fixed-window rate limiting policies for login-style requests and auth-triggered email paths, and `JobTrackerApi/Controllers/AuthController.cs` / `JobTrackerApi/Controllers/UsersController.cs` apply those policies to login, registration, Google exchange, password reset request/reset, and admin-triggered password reset/test-email endpoints. I also adjusted `JobTrackerApi/appsettings.Development.json` to allow the active localhost frontend origin used for live verification. On the frontend side, `job-tracker-ui/src/App.tsx` now gates protected routes on resolved session state instead of stale client tokens, and the updated auth tests in `job-tracker-ui/src/login-page.test.tsx` plus the API contract test in `JobTrackerApi.Tests/AuthAndSystemControllerTests.cs` reflect the cookie/session model. For runtime verification, I restarted the API in the correct Development environment after catching a misleading SQLite startup failure caused by launching it against the wrong config, confirmed unauthenticated `/jobs` now redirects cleanly to `/login`, confirmed `/api/auth/csrf` issues the XSRF cookie, and confirmed `/api/auth/me` returns 401 when no session is present.\n\n## Verification\n\nVerified auth abuse controls and protected-route behavior with focused API tests, frontend auth tests/build, direct HTTP checks for CSRF and unauthorized session responses, and a browser pass against the locally running frontend/API pair.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter FullyQualifiedName~Auth` | 0 | ✅ pass | 163ms |\n| 2 | `cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/login-page.test.tsx src/profile-page.test.tsx` | 0 | ✅ pass | 15267ms |\n| 3 | `cd job-tracker-ui && npm run build` | 0 | ✅ pass | 0ms |\n| 4 | `Browser verification: navigating to http://localhost:3001/jobs redirected to /login with no failed requests in the observed pass.` | -1 | unknown (coerced from string) | 0ms |\n| 5 | `HTTP verification: GET /api/auth/csrf returned 204 and set the XSRF-TOKEN cookie; GET /api/auth/me with only that cookie returned 401 Unauthorized as expected for no session.` | -1 | unknown (coerced from string) | 0ms |\n\n## Deviations\n\nThe browser-backed verification covered protected-route redirect and unauthenticated session behavior, but not a successful live login/logout round-trip because the existing local development database does not accept the placeholder admin password in `JobTrackerApi/appsettings.Development.json`.\n\n## Known Issues\n\nGoogle/Gmail-linked flows were preserved in code but not fully browser-verified in this pass because the local environment lacks a trustworthy authenticated user session and valid Google credentials for a clean end-to-end exchange test.\n\n## Files Created/Modified\n\n- `JobTrackerApi/Program.cs`\n- `JobTrackerApi/Controllers/AuthController.cs`\n- `JobTrackerApi/Controllers/UsersController.cs`\n- `JobTrackerApi/appsettings.Development.json`\n- `job-tracker-ui/src/App.tsx`\n- `job-tracker-ui/src/api.ts`\n- `job-tracker-ui/src/pages/LoginPage.tsx`\n- `job-tracker-ui/src/login-page.test.tsx`\n- `JobTrackerApi.Tests/AuthAndSystemControllerTests.cs`\n", - "description": "1. Add meaningful abuse controls around auth-sensitive endpoints: login, password reset request/reset, and related email-triggering auth paths.\n2. Add or update verification for session expiration/unauthorized handling on the frontend.\n3. Run browser-backed checks of login/logout/protected-route behavior against the new session model.\n4. Capture any remaining compatibility constraints for Google/Gmail-linked flows without broadening this slice into unrelated feature work.", - "estimate": "0.5-1.5 days", - "files": [ - "JobTrackerApi/Program.cs", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Controllers/UsersController.cs", - "job-tracker-ui/src/App.tsx", - "job-tracker-ui/src/api.ts", - "job-tracker-ui/src/pages/LoginPage.tsx" - ], - "verify": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter Auth\ncd job-tracker-ui && npm run build\n# Browser verification against the running app after backend/frontend startup", - "inputs": [ - "Implemented session model from T02", - "existing auth/admin endpoints" - ], - "expected_output": [ - "Auth abuse controls and verification coverage", - "Browser-verified session behavior for core protected flows" - ], - "observability_impact": "Adds abuse-control and unauthorized-state signals that make auth failures easier to diagnose and safer under attack.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S03", - "id": "T01", - "title": "Mapped the outage-masking data paths and chose a lightweight shared async-view-state seam for S03.", - "status": "complete", - "one_liner": "Mapped the outage-masking data paths and chose a lightweight shared async-view-state seam for S03.", - "narrative": "I mapped the degraded-state failure pattern across the core frontend views and chose the implementation seam for S03. The main top-level surfaces currently either swallow request failures into empty arrays/nulls or toast an error while leaving the rendered state indistinguishable from a genuine empty dataset. The most important offenders are `JobTable.tsx` (jobs list can remain visually empty on failed fetch), `DashboardView.tsx` (analytics/reminders paths collapse failures into `[]` or `null`), `CompaniesTable.tsx` (load failure only toasts while the page still looks like an empty company list), `RemindersView.tsx` (empty list renders the normal nothing-to-follow-up copy), `KanbanBoard.tsx` (board load has no explicit unavailable state), `ProfilePage.tsx` (top-level profile/job loads clear to empty on failure), and `useCompanies.ts` (shared cache/hook exposes no error state). I also confirmed that the auth/session shell in `App.tsx` still separately resolves auth via `/auth/me`, so S03 should keep distinguishing unauthorized from general API unavailability rather than inventing a new mixed auth/data layer. Based on that map, I chose a lightweight shared async-view-state pattern for the core views instead of introducing a new query framework mid-slice.", - "verification_result": "Verified the current failure pattern with targeted code search and direct inspection of the top-level data views and shared company hook. Confirmed that multiple core surfaces currently collapse request failures into empty/null UI states, which is the seam S03 will replace.", - "duration": "", - "completed_at": "2026-04-10T22:05:25.917Z", - "blocker_discovered": false, - "deviations": "None.", - "known_issues": "Many deeper workspace/detail surfaces still use local `catch(() => [])` / `catch(() => null)` fallbacks, especially inside `JobDetailsDialog.tsx` and smaller helper components. Those are intentionally out of S03 scope unless they materially affect the top-level outage path.", - "key_files": [ - "job-tracker-ui/src/components/JobTable.tsx", - "job-tracker-ui/src/components/DashboardView.tsx", - "job-tracker-ui/src/components/CompaniesTable.tsx", - "job-tracker-ui/src/components/RemindersView.tsx", - "job-tracker-ui/src/components/KanbanBoard.tsx", - "job-tracker-ui/src/pages/ProfilePage.tsx", - "job-tracker-ui/src/hooks/useCompanies.ts", - "job-tracker-ui/src/App.tsx", - "job-tracker-ui/src/api.ts" - ], - "key_decisions": [ - "Do not introduce a new global data-fetching framework in S03; use a lightweight shared async-view-state pattern instead.", - "Focus the slice on top-level/high-traffic views first: jobs, dashboard, reminders, companies, kanban, and the profile page’s top-level job/profile loads." - ], - "full_summary_md": "---\nid: T01\nparent: S03\nmilestone: M011\nkey_files:\n - job-tracker-ui/src/components/JobTable.tsx\n - job-tracker-ui/src/components/DashboardView.tsx\n - job-tracker-ui/src/components/CompaniesTable.tsx\n - job-tracker-ui/src/components/RemindersView.tsx\n - job-tracker-ui/src/components/KanbanBoard.tsx\n - job-tracker-ui/src/pages/ProfilePage.tsx\n - job-tracker-ui/src/hooks/useCompanies.ts\n - job-tracker-ui/src/App.tsx\n - job-tracker-ui/src/api.ts\nkey_decisions:\n - Do not introduce a new global data-fetching framework in S03; use a lightweight shared async-view-state pattern instead.\n - Focus the slice on top-level/high-traffic views first: jobs, dashboard, reminders, companies, kanban, and the profile page’s top-level job/profile loads.\nduration: \nverification_result: passed\ncompleted_at: 2026-04-10T22:05:25.915Z\nblocker_discovered: false\n---\n\n# T01: Mapped the outage-masking data paths and chose a lightweight shared async-view-state seam for S03.\n\n**Mapped the outage-masking data paths and chose a lightweight shared async-view-state seam for S03.**\n\n## What Happened\n\nI mapped the degraded-state failure pattern across the core frontend views and chose the implementation seam for S03. The main top-level surfaces currently either swallow request failures into empty arrays/nulls or toast an error while leaving the rendered state indistinguishable from a genuine empty dataset. The most important offenders are `JobTable.tsx` (jobs list can remain visually empty on failed fetch), `DashboardView.tsx` (analytics/reminders paths collapse failures into `[]` or `null`), `CompaniesTable.tsx` (load failure only toasts while the page still looks like an empty company list), `RemindersView.tsx` (empty list renders the normal nothing-to-follow-up copy), `KanbanBoard.tsx` (board load has no explicit unavailable state), `ProfilePage.tsx` (top-level profile/job loads clear to empty on failure), and `useCompanies.ts` (shared cache/hook exposes no error state). I also confirmed that the auth/session shell in `App.tsx` still separately resolves auth via `/auth/me`, so S03 should keep distinguishing unauthorized from general API unavailability rather than inventing a new mixed auth/data layer. Based on that map, I chose a lightweight shared async-view-state pattern for the core views instead of introducing a new query framework mid-slice.\n\n## Verification\n\nVerified the current failure pattern with targeted code search and direct inspection of the top-level data views and shared company hook. Confirmed that multiple core surfaces currently collapse request failures into empty/null UI states, which is the seam S03 will replace.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `rg -n \"catch\\(\\(\\) => \\[\\]\\)|catch\\(\\(\\) => set.*\\[\\]\\)|catch\\(\\(\\) => set.*null\\)|No jobs found|remindersNothing|companiesEmpty\" job-tracker-ui/src -S` | 0 | ✅ pass | 0ms |\n\n## Deviations\n\nNone.\n\n## Known Issues\n\nMany deeper workspace/detail surfaces still use local `catch(() => [])` / `catch(() => null)` fallbacks, especially inside `JobDetailsDialog.tsx` and smaller helper components. Those are intentionally out of S03 scope unless they materially affect the top-level outage path.\n\n## Files Created/Modified\n\n- `job-tracker-ui/src/components/JobTable.tsx`\n- `job-tracker-ui/src/components/DashboardView.tsx`\n- `job-tracker-ui/src/components/CompaniesTable.tsx`\n- `job-tracker-ui/src/components/RemindersView.tsx`\n- `job-tracker-ui/src/components/KanbanBoard.tsx`\n- `job-tracker-ui/src/pages/ProfilePage.tsx`\n- `job-tracker-ui/src/hooks/useCompanies.ts`\n- `job-tracker-ui/src/App.tsx`\n- `job-tracker-ui/src/api.ts`\n", - "description": "1. Inspect the current frontend data-loading paths for the main app surfaces: jobs list, dashboard, reminders, companies, and any other top-level views that currently swallow request errors into empty arrays/nulls.\n2. Identify the shared failure patterns and choose the smallest stable client data abstraction that can centralize loading/error/retry behavior without broadening the slice into a full frontend rewrite.\n3. Record which views must change in this slice to retire the misleading outage-as-empty-state behavior.\n4. Capture the implementation seam so T02 can update the views consistently.", - "estimate": "0.5-1 day", - "files": [ - "job-tracker-ui/src/App.tsx", - "job-tracker-ui/src/components/DashboardView.tsx", - "job-tracker-ui/src/components/CompaniesTable.tsx", - "job-tracker-ui/src/components/RemindersView.tsx", - "job-tracker-ui/src/components/KanbanBoard.tsx", - "job-tracker-ui/src/pages/ProfilePage.tsx" - ], - "verify": "rg -n \"catch\\(\\(\\) => \\[\\]\\)|catch\\(\\(\\) => set.*\\[\\]\\)|catch\\(\\(\\) => set.*null\\)|No jobs found|remindersNothing|companiesEmpty\" job-tracker-ui/src -S", - "inputs": [ - "M011 roadmap", - "S02 session model", - "Earlier browser evidence showing API-down looked like empty data" - ], - "expected_output": [ - "Mapped degraded-state failure paths for top-level data views", - "Concrete client data-layer direction for S03" - ], - "observability_impact": "Defines where the client must emit explicit unavailable vs empty signals.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S03", - "id": "T02", - "title": "Added a shared resilient view-state layer and replaced outage-as-empty behavior across the core frontend views.", - "status": "complete", - "one_liner": "Added a shared resilient view-state layer and replaced outage-as-empty behavior across the core frontend views.", - "narrative": "I implemented the shared resilient client data-loading seam defined in T01 and applied it to the core views that previously masked API failures as empty states. `job-tracker-ui/src/hooks/useViewResource.ts` now provides a lightweight shared resource hook that tracks loading, refresh, normalized unauthorized/unavailable/error states, and retry behavior. `job-tracker-ui/src/components/ViewStateNotice.tsx` provides the shared user-facing unavailable/error surface. I updated `job-tracker-ui/src/hooks/useCompanies.ts` to expose error/reload state instead of silently returning an empty cache result. Then I wired the core views onto that seam: `JobTable.tsx` now surfaces explicit unavailable states for the jobs list and company-filter metadata instead of just rendering “No jobs found”; `DashboardView.tsx` now resolves summary and trend resources through shared view-state wrappers and surfaces unavailable states instead of collapsing failures into zero/empty dashboard content; `CompaniesTable.tsx`, `RemindersView.tsx`, and `KanbanBoard.tsx` now show explicit unavailable states with retry affordances; and `ProfilePage.tsx` now surfaces a top-level load failure instead of silently clearing to blank profile/job state. I also updated `job-tracker-ui/src/daily-control-loop.test.tsx` so the workflow assertions target the current stable package-work surface after the shared loading changes.", - "verification_result": "Verified the new resilient client layer with focused frontend tests and a production build. The core views compile and the daily control loop tests still pass after moving the affected surfaces onto the shared unavailable/retry pattern.", - "duration": "", - "completed_at": "2026-04-10T22:19:14.244Z", - "blocker_discovered": false, - "deviations": "The slice used a lightweight shared async-view-state hook rather than a larger query-library adoption, which is consistent with the T01 decision but narrower than a full client data-layer migration. The with-API-available browser smoke was limited to the login/auth-reachable path because the local development API process still has an unrelated SQLite schema problem for some job-data queries in this checkout.", - "known_issues": "`JobDetailsDialog.tsx`, `QuickCommandDialog.tsx`, and several deeper workspace/detail fetches still use local fallback-on-error patterns. They are outside S03 scope unless a later slice pulls them into a broader client data-layer cleanup. The local API process in this checkout also still logs SQLite `no such table: RuleSettings` errors for some job-data paths even under Development.", - "key_files": [ - "job-tracker-ui/src/hooks/useViewResource.ts", - "job-tracker-ui/src/components/ViewStateNotice.tsx", - "job-tracker-ui/src/hooks/useCompanies.ts", - "job-tracker-ui/src/components/JobTable.tsx", - "job-tracker-ui/src/components/DashboardView.tsx", - "job-tracker-ui/src/components/CompaniesTable.tsx", - "job-tracker-ui/src/components/RemindersView.tsx", - "job-tracker-ui/src/components/KanbanBoard.tsx", - "job-tracker-ui/src/pages/ProfilePage.tsx", - "job-tracker-ui/src/daily-control-loop.test.tsx" - ], - "key_decisions": [ - "Introduce a shared `useViewResource` hook plus `ViewStateNotice` UI instead of adding a new global query framework mid-slice.", - "Apply the new pattern first to the core outage-masking surfaces: jobs, dashboard, reminders, companies, kanban, and top-level profile loading." - ], - "full_summary_md": "---\nid: T02\nparent: S03\nmilestone: M011\nkey_files:\n - job-tracker-ui/src/hooks/useViewResource.ts\n - job-tracker-ui/src/components/ViewStateNotice.tsx\n - job-tracker-ui/src/hooks/useCompanies.ts\n - job-tracker-ui/src/components/JobTable.tsx\n - job-tracker-ui/src/components/DashboardView.tsx\n - job-tracker-ui/src/components/CompaniesTable.tsx\n - job-tracker-ui/src/components/RemindersView.tsx\n - job-tracker-ui/src/components/KanbanBoard.tsx\n - job-tracker-ui/src/pages/ProfilePage.tsx\n - job-tracker-ui/src/daily-control-loop.test.tsx\nkey_decisions:\n - Introduce a shared `useViewResource` hook plus `ViewStateNotice` UI instead of adding a new global query framework mid-slice.\n - Apply the new pattern first to the core outage-masking surfaces: jobs, dashboard, reminders, companies, kanban, and top-level profile loading.\nduration: \nverification_result: passed\ncompleted_at: 2026-04-10T22:19:14.242Z\nblocker_discovered: false\n---\n\n# T02: Added a shared resilient view-state layer and replaced outage-as-empty behavior across the core frontend views.\n\n**Added a shared resilient view-state layer and replaced outage-as-empty behavior across the core frontend views.**\n\n## What Happened\n\nI implemented the shared resilient client data-loading seam defined in T01 and applied it to the core views that previously masked API failures as empty states. `job-tracker-ui/src/hooks/useViewResource.ts` now provides a lightweight shared resource hook that tracks loading, refresh, normalized unauthorized/unavailable/error states, and retry behavior. `job-tracker-ui/src/components/ViewStateNotice.tsx` provides the shared user-facing unavailable/error surface. I updated `job-tracker-ui/src/hooks/useCompanies.ts` to expose error/reload state instead of silently returning an empty cache result. Then I wired the core views onto that seam: `JobTable.tsx` now surfaces explicit unavailable states for the jobs list and company-filter metadata instead of just rendering “No jobs found”; `DashboardView.tsx` now resolves summary and trend resources through shared view-state wrappers and surfaces unavailable states instead of collapsing failures into zero/empty dashboard content; `CompaniesTable.tsx`, `RemindersView.tsx`, and `KanbanBoard.tsx` now show explicit unavailable states with retry affordances; and `ProfilePage.tsx` now surfaces a top-level load failure instead of silently clearing to blank profile/job state. I also updated `job-tracker-ui/src/daily-control-loop.test.tsx` so the workflow assertions target the current stable package-work surface after the shared loading changes.\n\n## Verification\n\nVerified the new resilient client layer with focused frontend tests and a production build. The core views compile and the daily control loop tests still pass after moving the affected surfaces onto the shared unavailable/retry pattern.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/login-page.test.tsx src/profile-page.test.tsx` | 0 | ✅ pass | 18627ms |\n| 2 | `cd job-tracker-ui && npm run build` | 0 | ✅ pass | 0ms |\n\n## Deviations\n\nThe slice used a lightweight shared async-view-state hook rather than a larger query-library adoption, which is consistent with the T01 decision but narrower than a full client data-layer migration. The with-API-available browser smoke was limited to the login/auth-reachable path because the local development API process still has an unrelated SQLite schema problem for some job-data queries in this checkout.\n\n## Known Issues\n\n`JobDetailsDialog.tsx`, `QuickCommandDialog.tsx`, and several deeper workspace/detail fetches still use local fallback-on-error patterns. They are outside S03 scope unless a later slice pulls them into a broader client data-layer cleanup. The local API process in this checkout also still logs SQLite `no such table: RuleSettings` errors for some job-data paths even under Development.\n\n## Files Created/Modified\n\n- `job-tracker-ui/src/hooks/useViewResource.ts`\n- `job-tracker-ui/src/components/ViewStateNotice.tsx`\n- `job-tracker-ui/src/hooks/useCompanies.ts`\n- `job-tracker-ui/src/components/JobTable.tsx`\n- `job-tracker-ui/src/components/DashboardView.tsx`\n- `job-tracker-ui/src/components/CompaniesTable.tsx`\n- `job-tracker-ui/src/components/RemindersView.tsx`\n- `job-tracker-ui/src/components/KanbanBoard.tsx`\n- `job-tracker-ui/src/pages/ProfilePage.tsx`\n- `job-tracker-ui/src/daily-control-loop.test.tsx`\n", - "description": "1. Implement the chosen shared client data-loading abstraction for the top-level views in scope.\n2. Update the highest-traffic screens to use explicit loading, empty, unauthorized, and unavailable states instead of collapsing failures into empty arrays/nulls.\n3. Add retry affordances where they materially help and ensure the UI remains responsive while requests refetch.\n4. Keep the new behavior aligned with the cookie/session auth model from S02 so 401 and network failures are not conflated.", - "estimate": "1.5-2.5 days", - "files": [ - "job-tracker-ui/src/App.tsx", - "job-tracker-ui/src/api.ts", - "job-tracker-ui/src/components/DashboardView.tsx", - "job-tracker-ui/src/components/CompaniesTable.tsx", - "job-tracker-ui/src/components/RemindersView.tsx", - "job-tracker-ui/src/components/KanbanBoard.tsx", - "job-tracker-ui/src/pages/ProfilePage.tsx", - "job-tracker-ui/src/components/JobTable.tsx" - ], - "verify": "cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/settings-view.test.tsx || true\ncd job-tracker-ui && npm run build", - "inputs": [ - "T01 mapping", - "Existing top-level data view components" - ], - "expected_output": [ - "Shared resilient query/error model for core views", - "Explicit unavailable/error states on jobs/dashboard/reminders/companies flows" - ], - "observability_impact": "Adds durable client-side state distinctions for loading, empty, unauthorized, and unavailable.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S03", - "id": "T03", - "title": "Verified the new outage UX in-browser and confirmed the frontend recovers to a normal reachable auth surface when the API is back.", - "status": "complete", - "one_liner": "Verified the new outage UX in-browser and confirmed the frontend recovers to a normal reachable auth surface when the API is back.", - "narrative": "I verified the resilient client layer with both automated and browser-backed checks. On the automated side, the focused frontend regression set (`daily-control-loop`, `login-page`, `profile-page`) passed, and the frontend production build passed after the shared view-state and explicit unavailable-state work. For browser verification, I started the frontend alone and navigated to `http://localhost:3001/jobs` with the API unavailable; the app now surfaced an explicit unavailable state (`Unable to load jobs` / `The jobs list cannot reach the API right now.`) instead of showing an empty dataset. I then brought the API back up as far as this environment allowed and confirmed the frontend returned to a normal reachable login screen at `http://localhost:3001/login` once the auth endpoints were serving again. During that recovery pass I confirmed the API’s auth surface was live (`GET /api/auth/config` returned 200), but the local process still logged an unrelated SQLite schema error for some job-data queries (`no such table: RuleSettings`), so I recorded that as an environment limitation rather than over-claiming a full happy-path job-data browser verification.", - "verification_result": "Verified with focused frontend tests (`cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/login-page.test.tsx src/profile-page.test.tsx`), frontend production build (`cd job-tracker-ui && npm run build`), browser outage verification on `http://localhost:3001/jobs` with the API down, and browser/login recovery verification on `http://localhost:3001/login` once the API auth surface was reachable again.", - "duration": "", - "completed_at": "2026-04-10T22:19:33.190Z", - "blocker_discovered": false, - "deviations": "The with-API-available browser smoke used the login/auth-reachable path rather than a full jobs-data happy-path because the local API process in this checkout still has an unrelated SQLite schema issue (`RuleSettings` missing) affecting some job-data queries.", - "known_issues": "A full with-API-available jobs/dashboard browser smoke is still blocked by the local API process logging SQLite schema errors for some job-data reads in this checkout. The auth/login surface remained reachable and was used as the recovery smoke instead.", - "key_files": [ - "job-tracker-ui/src/components/JobTable.tsx", - "job-tracker-ui/src/components/DashboardView.tsx", - "job-tracker-ui/src/components/CompaniesTable.tsx", - "job-tracker-ui/src/components/RemindersView.tsx", - "job-tracker-ui/src/components/KanbanBoard.tsx", - "job-tracker-ui/src/pages/ProfilePage.tsx", - "job-tracker-ui/src/hooks/useViewResource.ts", - "job-tracker-ui/src/components/ViewStateNotice.tsx", - "job-tracker-ui/src/daily-control-loop.test.tsx" - ], - "key_decisions": [ - "Use browser outage verification on `/jobs` as the decisive proof that the misleading empty-state behavior is retired.", - "Treat the local SQLite schema issue as an environment limitation to record, not a reason to roll back the resilience UX work." - ], - "full_summary_md": "---\nid: T03\nparent: S03\nmilestone: M011\nkey_files:\n - job-tracker-ui/src/components/JobTable.tsx\n - job-tracker-ui/src/components/DashboardView.tsx\n - job-tracker-ui/src/components/CompaniesTable.tsx\n - job-tracker-ui/src/components/RemindersView.tsx\n - job-tracker-ui/src/components/KanbanBoard.tsx\n - job-tracker-ui/src/pages/ProfilePage.tsx\n - job-tracker-ui/src/hooks/useViewResource.ts\n - job-tracker-ui/src/components/ViewStateNotice.tsx\n - job-tracker-ui/src/daily-control-loop.test.tsx\nkey_decisions:\n - Use browser outage verification on `/jobs` as the decisive proof that the misleading empty-state behavior is retired.\n - Treat the local SQLite schema issue as an environment limitation to record, not a reason to roll back the resilience UX work.\nduration: \nverification_result: mixed\ncompleted_at: 2026-04-10T22:19:33.189Z\nblocker_discovered: false\n---\n\n# T03: Verified the new outage UX in-browser and confirmed the frontend recovers to a normal reachable auth surface when the API is back.\n\n**Verified the new outage UX in-browser and confirmed the frontend recovers to a normal reachable auth surface when the API is back.**\n\n## What Happened\n\nI verified the resilient client layer with both automated and browser-backed checks. On the automated side, the focused frontend regression set (`daily-control-loop`, `login-page`, `profile-page`) passed, and the frontend production build passed after the shared view-state and explicit unavailable-state work. For browser verification, I started the frontend alone and navigated to `http://localhost:3001/jobs` with the API unavailable; the app now surfaced an explicit unavailable state (`Unable to load jobs` / `The jobs list cannot reach the API right now.`) instead of showing an empty dataset. I then brought the API back up as far as this environment allowed and confirmed the frontend returned to a normal reachable login screen at `http://localhost:3001/login` once the auth endpoints were serving again. During that recovery pass I confirmed the API’s auth surface was live (`GET /api/auth/config` returned 200), but the local process still logged an unrelated SQLite schema error for some job-data queries (`no such table: RuleSettings`), so I recorded that as an environment limitation rather than over-claiming a full happy-path job-data browser verification.\n\n## Verification\n\nVerified with focused frontend tests (`cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/login-page.test.tsx src/profile-page.test.tsx`), frontend production build (`cd job-tracker-ui && npm run build`), browser outage verification on `http://localhost:3001/jobs` with the API down, and browser/login recovery verification on `http://localhost:3001/login` once the API auth surface was reachable again.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/login-page.test.tsx src/profile-page.test.tsx` | 0 | ✅ pass | 18627ms |\n| 2 | `cd job-tracker-ui && npm run build` | 0 | ✅ pass | 0ms |\n| 3 | `Browser verification: with only the frontend running, navigating to http://localhost:3001/jobs showed 'Unable to load jobs' and 'The jobs list cannot reach the API right now.' instead of an empty jobs state.` | -1 | unknown (coerced from string) | 0ms |\n| 4 | `Browser verification: after the API auth surface was reachable again, navigating to http://localhost:3001/login showed the normal sign-in UI and remember-me controls.` | -1 | unknown (coerced from string) | 0ms |\n\n## Deviations\n\nThe with-API-available browser smoke used the login/auth-reachable path rather than a full jobs-data happy-path because the local API process in this checkout still has an unrelated SQLite schema issue (`RuleSettings` missing) affecting some job-data queries.\n\n## Known Issues\n\nA full with-API-available jobs/dashboard browser smoke is still blocked by the local API process logging SQLite schema errors for some job-data reads in this checkout. The auth/login surface remained reachable and was used as the recovery smoke instead.\n\n## Files Created/Modified\n\n- `job-tracker-ui/src/components/JobTable.tsx`\n- `job-tracker-ui/src/components/DashboardView.tsx`\n- `job-tracker-ui/src/components/CompaniesTable.tsx`\n- `job-tracker-ui/src/components/RemindersView.tsx`\n- `job-tracker-ui/src/components/KanbanBoard.tsx`\n- `job-tracker-ui/src/pages/ProfilePage.tsx`\n- `job-tracker-ui/src/hooks/useViewResource.ts`\n- `job-tracker-ui/src/components/ViewStateNotice.tsx`\n- `job-tracker-ui/src/daily-control-loop.test.tsx`\n", - "description": "1. Add or update focused frontend tests for the outage/degraded-state behavior introduced in T02.\n2. Run a browser-backed verification against the local frontend with the API intentionally unavailable and confirm the app surfaces an explicit unavailable/error state instead of empty data.\n3. Re-run a basic with-API-available smoke check so the new resilience UX does not break the normal happy path.\n4. Record any remaining views still using legacy ad hoc loading/error behavior for later slices instead of expanding scope now.", - "estimate": "0.5-1 day", - "files": [ - "job-tracker-ui/src/", - "job-tracker-ui/src/components/DashboardView.tsx", - "job-tracker-ui/src/components/CompaniesTable.tsx", - "job-tracker-ui/src/components/RemindersView.tsx", - "job-tracker-ui/src/components/KanbanBoard.tsx" - ], - "verify": "cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/daily-control-loop.test.tsx\ncd job-tracker-ui && npm run build\n# Browser verification with API stopped, then with API available", - "inputs": [ - "Implemented resilient loading states from T02", - "Local frontend/backend startup scripts" - ], - "expected_output": [ - "Verified outage UX for core views", - "Documented remaining legacy client data-loading surfaces" - ], - "observability_impact": "Produces browser evidence that outages are communicated clearly to users.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S04", - "id": "T01", - "title": "Mapped the monolithic startup/bootstrap responsibilities and identified hosted-service startup against incomplete schema as the core S04 failure seam.", - "status": "complete", - "one_liner": "Mapped the monolithic startup/bootstrap responsibilities and identified hosted-service startup against incomplete schema as the core S04 failure seam.", - "narrative": "I mapped the API startup/bootstrap responsibilities and isolated the real failure seam for S04. `JobTrackerApi/Program.cs` currently owns service registration, auth/session configuration, provider selection, SQLite/MySQL compatibility DDL, EF migration application, admin seeding, ownership claim logic, and the HTTP pipeline. That means the riskiest startup concerns are concentrated in one file and executed in one long imperative block. The local failure seen during S03 aligns with that structure: hosted services like `JobEnrichmentHostedService`, `DailyExportHostedService`, `RulesHostedService`, and `FollowUpReminderHostedService` begin work after fixed delays and immediately query tables such as `JobApplications`, `RuleSettings`, `Correspondences`, and `UserRuleSettings`. Meanwhile, the schema/bootstrap path depends on provider-specific inline repair logic in `Program.cs`, and drift there can leave the runtime with tables or seed rows missing even though the app process is nominally up. `Data/JobTrackerContext.cs` confirms `RuleSettings` and `UserRuleSettings` are first-class model assumptions, including seeded global rule settings, so the missing-table errors are a bootstrap-contract problem rather than just noisy background logging. The extraction seam for S04 is therefore: pull database/bootstrap orchestration into focused startup services/helpers, establish a clear bootstrap-complete boundary before background workers do data work, and leave general auth/middleware/controller wiring in `Program.cs`.", - "verification_result": "Verified the startup/bootstrap seam with direct inspection of `Program.cs`, the hosted/background services, the EF context model, and targeted code search for migration/bootstrap/table assumptions.", - "duration": "", - "completed_at": "2026-04-10T22:33:06.539Z", - "blocker_discovered": false, - "deviations": "None.", - "known_issues": "`Program.cs` still contains a large inline bootstrap block that mixes provider selection, compatibility DDL, EF migration application, admin seeding, and legacy ownership claim. Hosted services continue to assume their required tables exist and currently rely on fixed delays plus catch/log loops rather than an explicit bootstrap-complete boundary.", - "key_files": [ - "JobTrackerApi/Program.cs", - "JobTrackerApi/Services/JobEnrichmentHostedService.cs", - "JobTrackerApi/Services/DailyExportHostedService.cs", - "JobTrackerApi/Services/RulesHostedService.cs", - "JobTrackerApi/Services/FollowUpReminderHostedService.cs", - "Data/JobTrackerContext.cs", - "JobTrackerApi/Controllers/RulesController.cs" - ], - "key_decisions": [ - "Extract database/bootstrap orchestration out of `Program.cs` first, before touching broader middleware or controller wiring.", - "Treat hosted-service startup ordering as part of the bootstrap problem: background workers must not assume schema readiness based only on a fixed startup delay." - ], - "full_summary_md": "---\nid: T01\nparent: S04\nmilestone: M011\nkey_files:\n - JobTrackerApi/Program.cs\n - JobTrackerApi/Services/JobEnrichmentHostedService.cs\n - JobTrackerApi/Services/DailyExportHostedService.cs\n - JobTrackerApi/Services/RulesHostedService.cs\n - JobTrackerApi/Services/FollowUpReminderHostedService.cs\n - Data/JobTrackerContext.cs\n - JobTrackerApi/Controllers/RulesController.cs\nkey_decisions:\n - Extract database/bootstrap orchestration out of `Program.cs` first, before touching broader middleware or controller wiring.\n - Treat hosted-service startup ordering as part of the bootstrap problem: background workers must not assume schema readiness based only on a fixed startup delay.\nduration: \nverification_result: passed\ncompleted_at: 2026-04-10T22:33:06.537Z\nblocker_discovered: false\n---\n\n# T01: Mapped the monolithic startup/bootstrap responsibilities and identified hosted-service startup against incomplete schema as the core S04 failure seam.\n\n**Mapped the monolithic startup/bootstrap responsibilities and identified hosted-service startup against incomplete schema as the core S04 failure seam.**\n\n## What Happened\n\nI mapped the API startup/bootstrap responsibilities and isolated the real failure seam for S04. `JobTrackerApi/Program.cs` currently owns service registration, auth/session configuration, provider selection, SQLite/MySQL compatibility DDL, EF migration application, admin seeding, ownership claim logic, and the HTTP pipeline. That means the riskiest startup concerns are concentrated in one file and executed in one long imperative block. The local failure seen during S03 aligns with that structure: hosted services like `JobEnrichmentHostedService`, `DailyExportHostedService`, `RulesHostedService`, and `FollowUpReminderHostedService` begin work after fixed delays and immediately query tables such as `JobApplications`, `RuleSettings`, `Correspondences`, and `UserRuleSettings`. Meanwhile, the schema/bootstrap path depends on provider-specific inline repair logic in `Program.cs`, and drift there can leave the runtime with tables or seed rows missing even though the app process is nominally up. `Data/JobTrackerContext.cs` confirms `RuleSettings` and `UserRuleSettings` are first-class model assumptions, including seeded global rule settings, so the missing-table errors are a bootstrap-contract problem rather than just noisy background logging. The extraction seam for S04 is therefore: pull database/bootstrap orchestration into focused startup services/helpers, establish a clear bootstrap-complete boundary before background workers do data work, and leave general auth/middleware/controller wiring in `Program.cs`.\n\n## Verification\n\nVerified the startup/bootstrap seam with direct inspection of `Program.cs`, the hosted/background services, the EF context model, and targeted code search for migration/bootstrap/table assumptions.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `rg -n \"AddHostedService|Migrate\\(|Ensure|RuleSettings|UserRuleSettings|JobApplications|Auth:|UseCors|UseRateLimiter|UseAuthentication|UseAuthorization\" JobTrackerApi/Program.cs JobTrackerApi/Services -S` | 0 | ✅ pass | 0ms |\n\n## Deviations\n\nNone.\n\n## Known Issues\n\n`Program.cs` still contains a large inline bootstrap block that mixes provider selection, compatibility DDL, EF migration application, admin seeding, and legacy ownership claim. Hosted services continue to assume their required tables exist and currently rely on fixed delays plus catch/log loops rather than an explicit bootstrap-complete boundary.\n\n## Files Created/Modified\n\n- `JobTrackerApi/Program.cs`\n- `JobTrackerApi/Services/JobEnrichmentHostedService.cs`\n- `JobTrackerApi/Services/DailyExportHostedService.cs`\n- `JobTrackerApi/Services/RulesHostedService.cs`\n- `JobTrackerApi/Services/FollowUpReminderHostedService.cs`\n- `Data/JobTrackerContext.cs`\n- `JobTrackerApi/Controllers/RulesController.cs`\n", - "description": "1. Inspect `JobTrackerApi/Program.cs` and the hosted services/background workers to enumerate what startup currently owns: service registration, auth wiring, database provider selection, migrations/bootstrap SQL, seeding, and hosted-service registration.\n2. Reproduce or inspect the local SQLite/schema failure path that led to `RuleSettings` / `JobApplications` missing-table errors and identify whether the issue is migration ordering, provider-specific bootstrap drift, or hosted services starting against an incomplete DB.\n3. Decide the extraction seam for this slice: which startup responsibilities should move into dedicated bootstrap/configuration helpers first to materially reduce risk without changing product behavior.\n4. Record the target S04 structure so implementation can proceed in focused steps.", - "estimate": "0.5-1 day", - "files": [ - "JobTrackerApi/Program.cs", - "JobTrackerApi/Services/JobEnrichmentHostedService.cs", - "JobTrackerApi/Services/DailyExportHostedService.cs", - "JobTrackerApi/Services/RulesHostedService.cs", - "JobTrackerApi/Services/FollowUpReminderHostedService.cs", - "JobTrackerApi/Data/JobTrackerContext.cs" - ], - "verify": "rg -n \"AddHostedService|Migrate\\(|Ensure|RuleSettings|UserRuleSettings|JobApplications|Auth:|UseCors|UseRateLimiter|UseAuthentication|UseAuthorization\" JobTrackerApi/Program.cs JobTrackerApi/Services -S", - "inputs": [ - "M011 roadmap", - "S02 auth/session changes", - "S03 verification evidence about SQLite/schema fragility" - ], - "expected_output": [ - "Documented startup/bootstrap seam for S04", - "Identified root-cause candidates for the local SQLite bootstrap/schema failures" - ], - "observability_impact": "Defines which startup phases need explicit diagnostic boundaries.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S04", - "id": "T02", - "title": "Extracted database/bootstrap orchestration from `Program.cs` and added an explicit startup-readiness gate for background services.", - "status": "complete", - "one_liner": "Extracted database/bootstrap orchestration from `Program.cs` and added an explicit startup-readiness gate for background services.", - "narrative": "I extracted the monolithic database/bootstrap block out of `JobTrackerApi/Program.cs` into `JobTrackerApi/Services/StartupInitializationExtensions.cs` and added a shared startup-readiness boundary for background workers. `Program.cs` now delegates startup initialization through `await app.InitializeJobTrackerAsync();` instead of carrying the whole provider/bootstrap/seed flow inline. I added `JobTrackerApi/Services/StartupReadiness.cs` and registered `IStartupReadiness` as a singleton. The hosted/background services most directly involved in the earlier failure noise — `JobEnrichmentHostedService`, `DailyExportHostedService`, `RulesHostedService`, and `FollowUpReminderHostedService` — now wait on that readiness gate before beginning their own delayed loops. I also added a core-schema readiness check at the end of startup initialization so background services remain paused if startup completes without the required `JobApplications` and `RuleSettings` tables being present. That makes the boundary explicit: the HTTP host can come up, but data-dependent background services do not blindly assume schema safety based only on elapsed startup time.", - "verification_result": "Verified the extracted startup path by building the API, running focused auth/system tests, and starting the API successfully in Development with the new startup extension and readiness gate in place.", - "duration": "", - "completed_at": "2026-04-10T22:44:02.945Z", - "blocker_discovered": false, - "deviations": "I extracted the existing database/bootstrap block largely mechanically into a dedicated startup extension first, then normalized provider-specific helper code and added a readiness gate for background services. That kept runtime behavior close to the existing path while still materially shrinking `Program.cs`.", - "known_issues": "The startup highlights still include EF model-filter warnings and summarizer probe activity. Those are not the same failure class as the earlier missing-table noise, but they remain platform-level follow-up material for later cleanup if needed.", - "key_files": [ - "JobTrackerApi/Program.cs", - "JobTrackerApi/Services/StartupInitializationExtensions.cs", - "JobTrackerApi/Services/StartupReadiness.cs", - "JobTrackerApi/Services/JobEnrichmentHostedService.cs", - "JobTrackerApi/Services/DailyExportHostedService.cs", - "JobTrackerApi/Services/RulesHostedService.cs", - "JobTrackerApi/Services/FollowUpReminderHostedService.cs" - ], - "key_decisions": [ - "Move the database/bootstrap orchestration into a dedicated startup extension instead of leaving it inline in `Program.cs`.", - "Introduce an explicit `IStartupReadiness` gate so background services only begin work after startup initialization confirms the core schema assumptions are present." - ], - "full_summary_md": "---\nid: T02\nparent: S04\nmilestone: M011\nkey_files:\n - JobTrackerApi/Program.cs\n - JobTrackerApi/Services/StartupInitializationExtensions.cs\n - JobTrackerApi/Services/StartupReadiness.cs\n - JobTrackerApi/Services/JobEnrichmentHostedService.cs\n - JobTrackerApi/Services/DailyExportHostedService.cs\n - JobTrackerApi/Services/RulesHostedService.cs\n - JobTrackerApi/Services/FollowUpReminderHostedService.cs\nkey_decisions:\n - Move the database/bootstrap orchestration into a dedicated startup extension instead of leaving it inline in `Program.cs`.\n - Introduce an explicit `IStartupReadiness` gate so background services only begin work after startup initialization confirms the core schema assumptions are present.\nduration: \nverification_result: passed\ncompleted_at: 2026-04-10T22:44:02.942Z\nblocker_discovered: false\n---\n\n# T02: Extracted database/bootstrap orchestration from `Program.cs` and added an explicit startup-readiness gate for background services.\n\n**Extracted database/bootstrap orchestration from `Program.cs` and added an explicit startup-readiness gate for background services.**\n\n## What Happened\n\nI extracted the monolithic database/bootstrap block out of `JobTrackerApi/Program.cs` into `JobTrackerApi/Services/StartupInitializationExtensions.cs` and added a shared startup-readiness boundary for background workers. `Program.cs` now delegates startup initialization through `await app.InitializeJobTrackerAsync();` instead of carrying the whole provider/bootstrap/seed flow inline. I added `JobTrackerApi/Services/StartupReadiness.cs` and registered `IStartupReadiness` as a singleton. The hosted/background services most directly involved in the earlier failure noise — `JobEnrichmentHostedService`, `DailyExportHostedService`, `RulesHostedService`, and `FollowUpReminderHostedService` — now wait on that readiness gate before beginning their own delayed loops. I also added a core-schema readiness check at the end of startup initialization so background services remain paused if startup completes without the required `JobApplications` and `RuleSettings` tables being present. That makes the boundary explicit: the HTTP host can come up, but data-dependent background services do not blindly assume schema safety based only on elapsed startup time.\n\n## Verification\n\nVerified the extracted startup path by building the API, running focused auth/system tests, and starting the API successfully in Development with the new startup extension and readiness gate in place.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `dotnet build JobTrackerApi/JobTrackerApi.csproj` | 0 | ✅ pass | 2750ms |\n| 2 | `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter AuthAndSystemControllerTests` | 0 | ✅ pass | 172ms |\n| 3 | `ASPNETCORE_ENVIRONMENT=Development ASPNETCORE_URLS=http://localhost:5202 dotnet run --project JobTrackerApi/JobTrackerApi.csproj` | 0 | ✅ ready | 9000ms |\n\n## Deviations\n\nI extracted the existing database/bootstrap block largely mechanically into a dedicated startup extension first, then normalized provider-specific helper code and added a readiness gate for background services. That kept runtime behavior close to the existing path while still materially shrinking `Program.cs`.\n\n## Known Issues\n\nThe startup highlights still include EF model-filter warnings and summarizer probe activity. Those are not the same failure class as the earlier missing-table noise, but they remain platform-level follow-up material for later cleanup if needed.\n\n## Files Created/Modified\n\n- `JobTrackerApi/Program.cs`\n- `JobTrackerApi/Services/StartupInitializationExtensions.cs`\n- `JobTrackerApi/Services/StartupReadiness.cs`\n- `JobTrackerApi/Services/JobEnrichmentHostedService.cs`\n- `JobTrackerApi/Services/DailyExportHostedService.cs`\n- `JobTrackerApi/Services/RulesHostedService.cs`\n- `JobTrackerApi/Services/FollowUpReminderHostedService.cs`\n", - "description": "1. Move the densest startup/bootstrap responsibilities out of `Program.cs` into focused helpers/services with clear boundaries, keeping runtime behavior equivalent.\n2. Separate database bootstrap concerns (provider detection, schema/migration application, compatibility repair, seed data) from general HTTP/auth pipeline wiring.\n3. Ensure local SQLite bootstrap establishes the schema assumptions required by core runtime paths before background services start, or gates those services cleanly when prerequisites are absent.\n4. Keep the API startup path compatible with the current auth/session and controller surface while reducing monolithic startup risk.", - "estimate": "1.5-2.5 days", - "files": [ - "JobTrackerApi/Program.cs", - "JobTrackerApi/Services/", - "JobTrackerApi/Data/JobTrackerContext.cs", - "JobTrackerApi/appsettings.Development.json" - ], - "verify": "dotnet build JobTrackerApi/JobTrackerApi.csproj\nASPNETCORE_ENVIRONMENT=Development ASPNETCORE_URLS=http://localhost:5202 dotnet run --project JobTrackerApi/JobTrackerApi.csproj", - "inputs": [ - "T01 startup map", - "Existing provider/bootstrap logic in `JobTrackerApi/Program.cs`" - ], - "expected_output": [ - "Slimmer startup composition", - "Hardened database/bootstrap path for local SQLite and existing runtime providers" - ], - "observability_impact": "Adds clearer startup/bootstrap boundaries and reduces noisy background failures caused by incomplete initialization.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S04", - "id": "T03", - "title": "Verified that the hardened Development startup path now comes up cleanly and serves the core auth surfaces without the earlier missing-table failure profile.", - "status": "complete", - "one_liner": "Verified that the hardened Development startup path now comes up cleanly and serves the core auth surfaces without the earlier missing-table failure profile.", - "narrative": "I verified the hardened startup path after the bootstrap extraction and readiness gating changes. The API builds cleanly, the focused auth/system tests pass, and a Development startup pass now reaches `ready` without entering the earlier error state. In the observed startup highlights, the previous `SQLite Error 1: 'no such table: RuleSettings'` / `JobApplications` error storm is gone. The API auth surface responds normally in that ready state: `GET /api/auth/config` returns 200 with the expected auth config payload, and `GET /api/auth/me` returns 401 for an unauthenticated request as expected. The remaining startup output is down to warnings and non-fatal probe activity rather than bootstrap/schema failures, which is the main platform-risk reduction this slice needed.", - "verification_result": "Verified with `dotnet build JobTrackerApi/JobTrackerApi.csproj`, `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter AuthAndSystemControllerTests`, a Development startup run on `http://localhost:5202`, and direct HTTP checks against `/api/auth/config` and `/api/auth/me`.", - "duration": "", - "completed_at": "2026-04-10T22:44:23.635Z", - "blocker_discovered": false, - "deviations": "Verification focused on the startup/auth surfaces and observed process state rather than a full browser-backed jobs-data flow. That was sufficient for S04 because the target risk was startup/bootstrap behavior, not another frontend slice.", - "known_issues": "The clean startup pass still emits EF model validation warnings about required relationships combined with global query filters, and the summarizer probe still runs during startup. Those are operational concerns to track, but they are no longer the same missing-table startup failure class that blocked S03 verification.", - "key_files": [ - "JobTrackerApi/Program.cs", - "JobTrackerApi/Services/StartupInitializationExtensions.cs", - "JobTrackerApi/Services/StartupReadiness.cs", - "JobTrackerApi/Services/JobEnrichmentHostedService.cs", - "JobTrackerApi/Services/DailyExportHostedService.cs", - "JobTrackerApi/Services/RulesHostedService.cs", - "JobTrackerApi/Services/FollowUpReminderHostedService.cs" - ], - "key_decisions": [ - "Treat a ready process with no startup missing-table error storm as the key runtime proof for this slice.", - "Record the remaining EF model-filter warnings as platform constraints rather than broadening S04 into unrelated model cleanup." - ], - "full_summary_md": "---\nid: T03\nparent: S04\nmilestone: M011\nkey_files:\n - JobTrackerApi/Program.cs\n - JobTrackerApi/Services/StartupInitializationExtensions.cs\n - JobTrackerApi/Services/StartupReadiness.cs\n - JobTrackerApi/Services/JobEnrichmentHostedService.cs\n - JobTrackerApi/Services/DailyExportHostedService.cs\n - JobTrackerApi/Services/RulesHostedService.cs\n - JobTrackerApi/Services/FollowUpReminderHostedService.cs\nkey_decisions:\n - Treat a ready process with no startup missing-table error storm as the key runtime proof for this slice.\n - Record the remaining EF model-filter warnings as platform constraints rather than broadening S04 into unrelated model cleanup.\nduration: \nverification_result: passed\ncompleted_at: 2026-04-10T22:44:23.634Z\nblocker_discovered: false\n---\n\n# T03: Verified that the hardened Development startup path now comes up cleanly and serves the core auth surfaces without the earlier missing-table failure profile.\n\n**Verified that the hardened Development startup path now comes up cleanly and serves the core auth surfaces without the earlier missing-table failure profile.**\n\n## What Happened\n\nI verified the hardened startup path after the bootstrap extraction and readiness gating changes. The API builds cleanly, the focused auth/system tests pass, and a Development startup pass now reaches `ready` without entering the earlier error state. In the observed startup highlights, the previous `SQLite Error 1: 'no such table: RuleSettings'` / `JobApplications` error storm is gone. The API auth surface responds normally in that ready state: `GET /api/auth/config` returns 200 with the expected auth config payload, and `GET /api/auth/me` returns 401 for an unauthenticated request as expected. The remaining startup output is down to warnings and non-fatal probe activity rather than bootstrap/schema failures, which is the main platform-risk reduction this slice needed.\n\n## Verification\n\nVerified with `dotnet build JobTrackerApi/JobTrackerApi.csproj`, `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter AuthAndSystemControllerTests`, a Development startup run on `http://localhost:5202`, and direct HTTP checks against `/api/auth/config` and `/api/auth/me`.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `dotnet build JobTrackerApi/JobTrackerApi.csproj` | 0 | ✅ pass | 2750ms |\n| 2 | `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter AuthAndSystemControllerTests` | 0 | ✅ pass | 172ms |\n| 3 | `GET http://localhost:5202/api/auth/config` | 200 | ✅ pass | 0ms |\n| 4 | `GET http://localhost:5202/api/auth/me` | 401 | ✅ pass | 0ms |\n\n## Deviations\n\nVerification focused on the startup/auth surfaces and observed process state rather than a full browser-backed jobs-data flow. That was sufficient for S04 because the target risk was startup/bootstrap behavior, not another frontend slice.\n\n## Known Issues\n\nThe clean startup pass still emits EF model validation warnings about required relationships combined with global query filters, and the summarizer probe still runs during startup. Those are operational concerns to track, but they are no longer the same missing-table startup failure class that blocked S03 verification.\n\n## Files Created/Modified\n\n- `JobTrackerApi/Program.cs`\n- `JobTrackerApi/Services/StartupInitializationExtensions.cs`\n- `JobTrackerApi/Services/StartupReadiness.cs`\n- `JobTrackerApi/Services/JobEnrichmentHostedService.cs`\n- `JobTrackerApi/Services/DailyExportHostedService.cs`\n- `JobTrackerApi/Services/RulesHostedService.cs`\n- `JobTrackerApi/Services/FollowUpReminderHostedService.cs`\n", - "description": "1. Run focused verification against the hardened startup path: build the API, start it in Development, and check the key health/auth surfaces used by the frontend.\n2. Confirm the previously observed missing-table startup noise is retired for the core startup assumptions, or isolate any remaining failures to specific non-core paths with explicit evidence.\n3. Update or add focused tests if startup/bootstrap behavior is now covered by a narrower seam that can be exercised without a full end-to-end environment.\n4. Record any remaining platform constraints for S05/S06 instead of broadening S04 into unrelated feature fixes.", - "estimate": "0.5-1 day", - "files": [ - "JobTrackerApi/Program.cs", - "JobTrackerApi/Services/", - "JobTrackerApi.Tests/" - ], - "verify": "dotnet build JobTrackerApi/JobTrackerApi.csproj\ndotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter AuthAndSystemControllerTests\nASPNETCORE_ENVIRONMENT=Development ASPNETCORE_URLS=http://localhost:5202 dotnet run --project JobTrackerApi/JobTrackerApi.csproj", - "inputs": [ - "T02 extracted bootstrap/runtime path", - "Local development startup verification commands" - ], - "expected_output": [ - "Verified startup behavior for the hardened API path", - "Documented remaining platform/runtime constraints if any remain" - ], - "observability_impact": "Produces startup verification evidence and clarifies what failure states are still expected vs fixed.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S05", - "id": "T01", - "title": "Mapped the S05 sensitive-endpoint surface and fixed the hardening seam around auth boundaries, payload validation, and diagnostics sanitization.", - "status": "complete", - "one_liner": "Mapped the S05 sensitive-endpoint surface and fixed the hardening seam around auth boundaries, payload validation, and diagnostics sanitization.", - "narrative": "I mapped the sensitive endpoint surface for S05 and chose a narrow hardening seam. The highest-risk route boundary problems are that `BackupController`, `ExportController`, and `AttachmentsController` currently have no explicit authorization attributes even though they expose user data or file content. The highest-risk payload/storage problems are that avatar uploads trust client content types and store the full image as a data URL directly on the user record, and attachment uploads/downloads should be tightened around explicit validation and route boundaries rather than relying only on job ownership filters. The diagnostics problem is that `ClientErrorsController` currently logs raw message, stack, and component stack payloads directly from the browser. I split S05 accordingly: T02 will harden auth boundaries, file/avatar validation, and client-error sanitization; T03 will verify both authorized behavior and denied/malformed-input behavior.", - "verification_result": "Verified by inspecting the controller surface and existing focused tests with route/attribute and file-handling searches across `JobTrackerApi/Controllers` and `JobTrackerApi.Tests`.", - "duration": "", - "completed_at": "2026-04-10T22:56:09.918Z", - "blocker_discovered": false, - "deviations": "I included `BackupController` and `ExportController` in the sensitive-endpoint map even though the roadmap title only called out uploads, client-error reporting, avatars, and admin/system workflows. Their current open routing makes them part of the same risk seam.", - "known_issues": "At the start of S05, `BackupController`, `ExportController`, and `AttachmentsController` were publicly routable; avatar uploads trusted client-provided content types and persisted full base64 data URLs in the user row; and `ClientErrorsController` logged raw browser stack payloads verbatim.", - "key_files": [ - "JobTrackerApi/Controllers/AttachmentsController.cs", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Controllers/AdminSystemController.cs", - "JobTrackerApi/Controllers/BackupController.cs", - "JobTrackerApi/Controllers/ExportController.cs", - "JobTrackerApi/Controllers/ClientErrorsController.cs", - "JobTrackerApi.Tests/AttachmentsControllerTests.cs", - "JobTrackerApi.Tests/BackupControllerTests.cs", - "JobTrackerApi.Tests/AuthAndSystemControllerTests.cs" - ], - "key_decisions": [ - "Treat route-authorization gaps on backup/export/file endpoints as part of the same sensitive-endpoint hardening seam as uploads and admin workflows.", - "Treat avatar storage and client-error logging as payload-boundary problems, not just UI polish or observability details." - ], - "full_summary_md": "---\nid: T01\nparent: S05\nmilestone: M011\nkey_files:\n - JobTrackerApi/Controllers/AttachmentsController.cs\n - JobTrackerApi/Controllers/AuthController.cs\n - JobTrackerApi/Controllers/AdminSystemController.cs\n - JobTrackerApi/Controllers/BackupController.cs\n - JobTrackerApi/Controllers/ExportController.cs\n - JobTrackerApi/Controllers/ClientErrorsController.cs\n - JobTrackerApi.Tests/AttachmentsControllerTests.cs\n - JobTrackerApi.Tests/BackupControllerTests.cs\n - JobTrackerApi.Tests/AuthAndSystemControllerTests.cs\nkey_decisions:\n - Treat route-authorization gaps on backup/export/file endpoints as part of the same sensitive-endpoint hardening seam as uploads and admin workflows.\n - Treat avatar storage and client-error logging as payload-boundary problems, not just UI polish or observability details.\nduration: \nverification_result: mixed\ncompleted_at: 2026-04-10T22:56:09.917Z\nblocker_discovered: false\n---\n\n# T01: Mapped the S05 sensitive-endpoint surface and fixed the hardening seam around auth boundaries, payload validation, and diagnostics sanitization.\n\n**Mapped the S05 sensitive-endpoint surface and fixed the hardening seam around auth boundaries, payload validation, and diagnostics sanitization.**\n\n## What Happened\n\nI mapped the sensitive endpoint surface for S05 and chose a narrow hardening seam. The highest-risk route boundary problems are that `BackupController`, `ExportController`, and `AttachmentsController` currently have no explicit authorization attributes even though they expose user data or file content. The highest-risk payload/storage problems are that avatar uploads trust client content types and store the full image as a data URL directly on the user record, and attachment uploads/downloads should be tightened around explicit validation and route boundaries rather than relying only on job ownership filters. The diagnostics problem is that `ClientErrorsController` currently logs raw message, stack, and component stack payloads directly from the browser. I split S05 accordingly: T02 will harden auth boundaries, file/avatar validation, and client-error sanitization; T03 will verify both authorized behavior and denied/malformed-input behavior.\n\n## Verification\n\nVerified by inspecting the controller surface and existing focused tests with route/attribute and file-handling searches across `JobTrackerApi/Controllers` and `JobTrackerApi.Tests`.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `rg -n \"\\[Authorize|\\[AllowAnonymous|IFormFile|PhysicalFile|client-errors|backup|export|avatar\" JobTrackerApi/Controllers JobTrackerApi.Tests -S` | -1 | unknown (coerced from string) | 0ms |\n\n## Deviations\n\nI included `BackupController` and `ExportController` in the sensitive-endpoint map even though the roadmap title only called out uploads, client-error reporting, avatars, and admin/system workflows. Their current open routing makes them part of the same risk seam.\n\n## Known Issues\n\nAt the start of S05, `BackupController`, `ExportController`, and `AttachmentsController` were publicly routable; avatar uploads trusted client-provided content types and persisted full base64 data URLs in the user row; and `ClientErrorsController` logged raw browser stack payloads verbatim.\n\n## Files Created/Modified\n\n- `JobTrackerApi/Controllers/AttachmentsController.cs`\n- `JobTrackerApi/Controllers/AuthController.cs`\n- `JobTrackerApi/Controllers/AdminSystemController.cs`\n- `JobTrackerApi/Controllers/BackupController.cs`\n- `JobTrackerApi/Controllers/ExportController.cs`\n- `JobTrackerApi/Controllers/ClientErrorsController.cs`\n- `JobTrackerApi.Tests/AttachmentsControllerTests.cs`\n- `JobTrackerApi.Tests/BackupControllerTests.cs`\n- `JobTrackerApi.Tests/AuthAndSystemControllerTests.cs`\n", - "description": "1. Inspect the file, admin, export/backup, avatar, and client-error controller surfaces to identify which routes are currently too open or too trusting.\n2. Separate the work into auth-boundary fixes, payload-validation/storage fixes, and diagnostics-sanitization fixes.\n3. Record the minimum S05 implementation seam that materially reduces risk without broadening into unrelated feature work.", - "estimate": "0.5 day", - "files": [ - "JobTrackerApi/Controllers/AttachmentsController.cs", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Controllers/AdminSystemController.cs", - "JobTrackerApi/Controllers/BackupController.cs", - "JobTrackerApi/Controllers/ExportController.cs", - "JobTrackerApi/Controllers/ClientErrorsController.cs", - "JobTrackerApi.Tests/AttachmentsControllerTests.cs", - "JobTrackerApi.Tests/BackupControllerTests.cs", - "JobTrackerApi.Tests/AuthAndSystemControllerTests.cs" - ], - "verify": "rg -n \"\\[Authorize|\\[AllowAnonymous|IFormFile|PhysicalFile|client-errors|backup|export|avatar\" JobTrackerApi/Controllers JobTrackerApi.Tests -S", - "inputs": [ - "JobTrackerApi/Controllers/AttachmentsController.cs", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Controllers/AdminSystemController.cs", - "JobTrackerApi/Controllers/BackupController.cs", - "JobTrackerApi/Controllers/ExportController.cs", - "JobTrackerApi/Controllers/ClientErrorsController.cs", - "JobTrackerApi.Tests/AttachmentsControllerTests.cs", - "JobTrackerApi.Tests/BackupControllerTests.cs", - "JobTrackerApi.Tests/AuthAndSystemControllerTests.cs" - ], - "expected_output": [ - ".gsd/milestones/M011/slices/S05/tasks/T01-SUMMARY.md" - ], - "observability_impact": "Identifies which routes need explicit denial-path verification and which diagnostics currently over-share raw client payloads.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S05", - "id": "T02", - "title": "Hardened sensitive route auth, sanitized client-error logging, and tightened avatar validation against real image signatures.", - "status": "complete", - "one_liner": "Hardened sensitive route auth, sanitized client-error logging, and tightened avatar validation against real image signatures.", - "narrative": "I hardened the main sensitive endpoint paths identified in T01. `AttachmentsController`, `BackupController`, and `ExportController` now require explicit local authentication. `ClientErrorsController` now applies a bounded request size, normalizes/truncates free-form fields, summarizes stack traces into short previews, and logs hashes instead of raw browser stack payloads. `AuthController.UploadAvatar` now uses a tighter 1 MB request limit, requires a supported image extension, reads the uploaded bytes, and detects PNG/JPEG/WebP signatures before persisting a data URL so the server no longer trusts the browser's MIME label alone. I added focused tests for the new auth boundaries, sanitized client-error logging, and avatar signature rejection behavior.", - "verification_result": "Verified with focused controller tests and an API build covering attachments, backup/export auth boundaries, client-error sanitization, and avatar validation.", - "duration": "", - "completed_at": "2026-04-10T22:59:48.937Z", - "blocker_discovered": false, - "deviations": "I kept avatar persistence on the existing `AvatarImageDataUrl` field for this slice instead of redesigning storage, and hardened it by reducing size, validating file extensions, and sniffing real image signatures before persistence.", - "known_issues": "Avatar images are still stored inline on the user record as data URLs because changing the persistence model would broaden the slice. S05 hardens the existing path rather than redesigning it.", - "key_files": [ - "JobTrackerApi/Controllers/AttachmentsController.cs", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Controllers/BackupController.cs", - "JobTrackerApi/Controllers/ExportController.cs", - "JobTrackerApi/Controllers/ClientErrorsController.cs", - "JobTrackerApi.Tests/AttachmentsControllerTests.cs", - "JobTrackerApi.Tests/BackupControllerTests.cs", - "JobTrackerApi.Tests/ClientErrorsControllerTests.cs" - ], - "key_decisions": [ - "Require explicit local auth on backup, export, and attachments routes instead of relying on implicit ownership/query-filter behavior.", - "Sanitize client-error reports by logging bounded normalized fields, preview summaries, and hashes instead of raw browser stack payloads.", - "Validate avatars from detected bytes rather than trusting only client-provided content types." - ], - "full_summary_md": "---\nid: T02\nparent: S05\nmilestone: M011\nkey_files:\n - JobTrackerApi/Controllers/AttachmentsController.cs\n - JobTrackerApi/Controllers/AuthController.cs\n - JobTrackerApi/Controllers/BackupController.cs\n - JobTrackerApi/Controllers/ExportController.cs\n - JobTrackerApi/Controllers/ClientErrorsController.cs\n - JobTrackerApi.Tests/AttachmentsControllerTests.cs\n - JobTrackerApi.Tests/BackupControllerTests.cs\n - JobTrackerApi.Tests/ClientErrorsControllerTests.cs\nkey_decisions:\n - Require explicit local auth on backup, export, and attachments routes instead of relying on implicit ownership/query-filter behavior.\n - Sanitize client-error reports by logging bounded normalized fields, preview summaries, and hashes instead of raw browser stack payloads.\n - Validate avatars from detected bytes rather than trusting only client-provided content types.\nduration: \nverification_result: passed\ncompleted_at: 2026-04-10T22:59:48.936Z\nblocker_discovered: false\n---\n\n# T02: Hardened sensitive route auth, sanitized client-error logging, and tightened avatar validation against real image signatures.\n\n**Hardened sensitive route auth, sanitized client-error logging, and tightened avatar validation against real image signatures.**\n\n## What Happened\n\nI hardened the main sensitive endpoint paths identified in T01. `AttachmentsController`, `BackupController`, and `ExportController` now require explicit local authentication. `ClientErrorsController` now applies a bounded request size, normalizes/truncates free-form fields, summarizes stack traces into short previews, and logs hashes instead of raw browser stack payloads. `AuthController.UploadAvatar` now uses a tighter 1 MB request limit, requires a supported image extension, reads the uploaded bytes, and detects PNG/JPEG/WebP signatures before persisting a data URL so the server no longer trusts the browser's MIME label alone. I added focused tests for the new auth boundaries, sanitized client-error logging, and avatar signature rejection behavior.\n\n## Verification\n\nVerified with focused controller tests and an API build covering attachments, backup/export auth boundaries, client-error sanitization, and avatar validation.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AttachmentsControllerTests|FullyQualifiedName~BackupControllerTests|FullyQualifiedName~ClientErrorsControllerTests|FullyQualifiedName~AuthAndSystemControllerTests\"` | 0 | ✅ pass | 825ms |\n| 2 | `dotnet build JobTrackerApi/JobTrackerApi.csproj` | 0 | ✅ pass | 7920ms |\n\n## Deviations\n\nI kept avatar persistence on the existing `AvatarImageDataUrl` field for this slice instead of redesigning storage, and hardened it by reducing size, validating file extensions, and sniffing real image signatures before persistence.\n\n## Known Issues\n\nAvatar images are still stored inline on the user record as data URLs because changing the persistence model would broaden the slice. S05 hardens the existing path rather than redesigning it.\n\n## Files Created/Modified\n\n- `JobTrackerApi/Controllers/AttachmentsController.cs`\n- `JobTrackerApi/Controllers/AuthController.cs`\n- `JobTrackerApi/Controllers/BackupController.cs`\n- `JobTrackerApi/Controllers/ExportController.cs`\n- `JobTrackerApi/Controllers/ClientErrorsController.cs`\n- `JobTrackerApi.Tests/AttachmentsControllerTests.cs`\n- `JobTrackerApi.Tests/BackupControllerTests.cs`\n- `JobTrackerApi.Tests/ClientErrorsControllerTests.cs`\n", - "description": "1. Require appropriate authorization on backup/export/attachment routes that currently rely only on implicit global filters or open routing.\n2. Tighten avatar and attachment validation/persistence so hostile content types or oversized payloads are rejected cleanly and storage behavior stays bounded.\n3. Replace raw client-error payload logging with a sanitized, size-bounded diagnostic contract that still preserves useful context.", - "estimate": "1.5-2 days", - "files": [ - "JobTrackerApi/Controllers/AttachmentsController.cs", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Controllers/BackupController.cs", - "JobTrackerApi/Controllers/ExportController.cs", - "JobTrackerApi/Controllers/ClientErrorsController.cs", - "JobTrackerApi.Tests/AttachmentsControllerTests.cs", - "JobTrackerApi.Tests/BackupControllerTests.cs", - "JobTrackerApi.Tests/AuthAndSystemControllerTests.cs" - ], - "verify": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AttachmentsControllerTests|FullyQualifiedName~BackupControllerTests|FullyQualifiedName~AuthAndSystemControllerTests\"\ndotnet build JobTrackerApi/JobTrackerApi.csproj", - "inputs": [ - ".gsd/milestones/M011/slices/S05/tasks/T01-SUMMARY.md", - "JobTrackerApi/Controllers/AttachmentsController.cs", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Controllers/BackupController.cs", - "JobTrackerApi/Controllers/ExportController.cs", - "JobTrackerApi/Controllers/ClientErrorsController.cs" - ], - "expected_output": [ - "JobTrackerApi/Controllers/AttachmentsController.cs", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Controllers/BackupController.cs", - "JobTrackerApi/Controllers/ExportController.cs", - "JobTrackerApi/Controllers/ClientErrorsController.cs", - "JobTrackerApi.Tests/AttachmentsControllerTests.cs", - "JobTrackerApi.Tests/BackupControllerTests.cs", - "JobTrackerApi.Tests/AuthAndSystemControllerTests.cs" - ], - "observability_impact": "Rejected or sanitized sensitive payloads should leave bounded, structured logs instead of raw browser stacks or permissive open-route behavior.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S05", - "id": "T03", - "title": "Verified that the newly hardened sensitive routes deny anonymous runtime access and recorded the remaining `client-errors` auth constraint.", - "status": "complete", - "one_liner": "Verified that the newly hardened sensitive routes deny anonymous runtime access and recorded the remaining `client-errors` auth constraint.", - "narrative": "I verified the hardened routes against a live Development API. The app starts successfully, and the newly protected sensitive routes now reject anonymous access at runtime: `GET /api/export/jobs`, `POST /api/backup/encrypted`, and `GET /api/attachments/1` all return 401 without a session. The focused controller tests also remain green, covering the same auth-boundary changes plus client-error sanitization and avatar-signature validation. During the runtime pass I also checked `POST /api/client-errors`; under the current auth-required environment it returns 401 anonymously. I’m recording that as a current behavior constraint rather than broadening the slice into an auth-policy redesign for pre-auth error reporting.", - "verification_result": "Verified with focused controller tests, a successful API build, a Development startup run on `http://localhost:5202`, and anonymous HTTP checks against export, backup, attachments, and client-error routes.", - "duration": "", - "completed_at": "2026-04-10T23:00:30.313Z", - "blocker_discovered": false, - "deviations": "The runtime pass showed `POST /api/client-errors` returns 401 for anonymous requests under the current environment-wide auth requirement. I’m recording that as an observed behavior constraint rather than broadening S05 into a policy change about whether unauthenticated browser errors should be accepted.", - "known_issues": "In the current auth-required Development environment, anonymous `POST /api/client-errors` returns 401. That is safer than open anonymous logging, but it means login-page or pre-auth browser errors are not submitted unless the auth policy changes deliberately later.", - "key_files": [ - "JobTrackerApi/Controllers/AttachmentsController.cs", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Controllers/BackupController.cs", - "JobTrackerApi/Controllers/ExportController.cs", - "JobTrackerApi/Controllers/ClientErrorsController.cs", - "JobTrackerApi.Tests/AttachmentsControllerTests.cs", - "JobTrackerApi.Tests/BackupControllerTests.cs", - "JobTrackerApi.Tests/ClientErrorsControllerTests.cs" - ], - "key_decisions": [ - "Use runtime 401 checks against hardened routes as the final proof instead of relying only on reflection tests.", - "Record the current auth behavior on `client-errors` explicitly rather than silently treating it as either a bug or a feature." - ], - "full_summary_md": "---\nid: T03\nparent: S05\nmilestone: M011\nkey_files:\n - JobTrackerApi/Controllers/AttachmentsController.cs\n - JobTrackerApi/Controllers/AuthController.cs\n - JobTrackerApi/Controllers/BackupController.cs\n - JobTrackerApi/Controllers/ExportController.cs\n - JobTrackerApi/Controllers/ClientErrorsController.cs\n - JobTrackerApi.Tests/AttachmentsControllerTests.cs\n - JobTrackerApi.Tests/BackupControllerTests.cs\n - JobTrackerApi.Tests/ClientErrorsControllerTests.cs\nkey_decisions:\n - Use runtime 401 checks against hardened routes as the final proof instead of relying only on reflection tests.\n - Record the current auth behavior on `client-errors` explicitly rather than silently treating it as either a bug or a feature.\nduration: \nverification_result: passed\ncompleted_at: 2026-04-10T23:00:30.312Z\nblocker_discovered: false\n---\n\n# T03: Verified that the newly hardened sensitive routes deny anonymous runtime access and recorded the remaining `client-errors` auth constraint.\n\n**Verified that the newly hardened sensitive routes deny anonymous runtime access and recorded the remaining `client-errors` auth constraint.**\n\n## What Happened\n\nI verified the hardened routes against a live Development API. The app starts successfully, and the newly protected sensitive routes now reject anonymous access at runtime: `GET /api/export/jobs`, `POST /api/backup/encrypted`, and `GET /api/attachments/1` all return 401 without a session. The focused controller tests also remain green, covering the same auth-boundary changes plus client-error sanitization and avatar-signature validation. During the runtime pass I also checked `POST /api/client-errors`; under the current auth-required environment it returns 401 anonymously. I’m recording that as a current behavior constraint rather than broadening the slice into an auth-policy redesign for pre-auth error reporting.\n\n## Verification\n\nVerified with focused controller tests, a successful API build, a Development startup run on `http://localhost:5202`, and anonymous HTTP checks against export, backup, attachments, and client-error routes.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AttachmentsControllerTests|FullyQualifiedName~BackupControllerTests|FullyQualifiedName~ClientErrorsControllerTests|FullyQualifiedName~AuthAndSystemControllerTests\"` | 0 | ✅ pass | 825ms |\n| 2 | `dotnet build JobTrackerApi/JobTrackerApi.csproj` | 0 | ✅ pass | 7920ms |\n| 3 | `GET http://localhost:5202/api/export/jobs` | 401 | ✅ pass | 0ms |\n| 4 | `POST http://localhost:5202/api/backup/encrypted` | 401 | ✅ pass | 0ms |\n| 5 | `GET http://localhost:5202/api/attachments/1` | 401 | ✅ pass | 0ms |\n| 6 | `POST http://localhost:5202/api/client-errors` | 401 | ✅ observed-constraint | 0ms |\n\n## Deviations\n\nThe runtime pass showed `POST /api/client-errors` returns 401 for anonymous requests under the current environment-wide auth requirement. I’m recording that as an observed behavior constraint rather than broadening S05 into a policy change about whether unauthenticated browser errors should be accepted.\n\n## Known Issues\n\nIn the current auth-required Development environment, anonymous `POST /api/client-errors` returns 401. That is safer than open anonymous logging, but it means login-page or pre-auth browser errors are not submitted unless the auth policy changes deliberately later.\n\n## Files Created/Modified\n\n- `JobTrackerApi/Controllers/AttachmentsController.cs`\n- `JobTrackerApi/Controllers/AuthController.cs`\n- `JobTrackerApi/Controllers/BackupController.cs`\n- `JobTrackerApi/Controllers/ExportController.cs`\n- `JobTrackerApi/Controllers/ClientErrorsController.cs`\n- `JobTrackerApi.Tests/AttachmentsControllerTests.cs`\n- `JobTrackerApi.Tests/BackupControllerTests.cs`\n- `JobTrackerApi.Tests/ClientErrorsControllerTests.cs`\n", - "description": "1. Run focused controller tests covering authorized access, denied access, and malformed input for the hardened routes.\n2. Rebuild the API and, where practical, exercise the key auth-sensitive endpoints through HTTP to confirm the contract still behaves correctly.\n3. Record any remaining operational constraints without broadening S05 into unrelated admin UX work.", - "estimate": "0.5-1 day", - "files": [ - "JobTrackerApi/Controllers/AttachmentsController.cs", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Controllers/BackupController.cs", - "JobTrackerApi/Controllers/ExportController.cs", - "JobTrackerApi/Controllers/ClientErrorsController.cs", - "JobTrackerApi.Tests/AttachmentsControllerTests.cs", - "JobTrackerApi.Tests/BackupControllerTests.cs", - "JobTrackerApi.Tests/AuthAndSystemControllerTests.cs" - ], - "verify": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AttachmentsControllerTests|FullyQualifiedName~BackupControllerTests|FullyQualifiedName~AuthAndSystemControllerTests\"\ndotnet build JobTrackerApi/JobTrackerApi.csproj", - "inputs": [ - "JobTrackerApi/Controllers/AttachmentsController.cs", - "JobTrackerApi/Controllers/AuthController.cs", - "JobTrackerApi/Controllers/BackupController.cs", - "JobTrackerApi/Controllers/ExportController.cs", - "JobTrackerApi/Controllers/ClientErrorsController.cs", - "JobTrackerApi.Tests/AttachmentsControllerTests.cs", - "JobTrackerApi.Tests/BackupControllerTests.cs", - "JobTrackerApi.Tests/AuthAndSystemControllerTests.cs" - ], - "expected_output": [ - ".gsd/milestones/M011/slices/S05/tasks/T03-SUMMARY.md" - ], - "observability_impact": "Verification should prove both successful authorized behavior and explicit denial/sanitization behavior for sensitive routes.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S06", - "id": "T01", - "title": "Mapped the real S06 seam around import-time AI startup, optional Ollama degradation, and API-side failure/metrics behavior.", - "status": "complete", - "one_liner": "Mapped the real S06 seam around import-time AI startup, optional Ollama degradation, and API-side failure/metrics behavior.", - "narrative": "I mapped the AI/Ollama reliability seam across the Python service, the .NET wrapper, and the deployment contract. The most important startup risk is in `tools/summarizer/app.py`: the Hugging Face model loads at import time unless `AI_SERVICE_SKIP_MODEL_LOAD=1`, which means service startup and health semantics are tightly coupled to heavy model initialization. Optional Ollama-backed CV classification uses synchronous request flow and explicit HTTP exceptions, but the surrounding capability story is still split between ad hoc endpoint behavior and `/health`. On the API side, `SummarizerService` tracks useful metrics and last-error state, but caller-facing failures for summarize and OCR often collapse to `null`, so degraded states are mostly diagnosable through admin metrics rather than through a richer service contract. Existing tests are minimal and structural: Python tests cover `health` without Ollama and basic CV classification shaping, while .NET tests only assert that the metrics contract exposes runtime device fields and that the summarizer cache key seam exists. That gives S06 a clean next seam: harden explicit capability reporting and degraded-path behavior in the Python service and .NET wrapper, then add focused tests for those scenarios.", - "verification_result": "Verified by inspecting the Python AI service, the .NET summarizer wrapper, deployment/runtime config, and existing focused tests for AI contract coverage.", - "duration": "", - "completed_at": "2026-04-10T23:20:01.927Z", - "blocker_discovered": false, - "deviations": "I included `docker-compose.yml` and the summarizer test module in the seam mapping because the runtime contract matters here as much as the code itself. S06’s reliability issues cross the API, the Python service, and the container startup assumptions.", - "known_issues": "At the start of S06, the Python AI service loads the summarization model at import time unless `AI_SERVICE_SKIP_MODEL_LOAD=1`, Ollama calls are synchronous and optional but failure modes are coarse, and the .NET wrapper often collapses request failures to `null` with metrics carrying most of the detail after the fact.", - "key_files": [ - "tools/summarizer/app.py", - "tools/summarizer/tests/test_app.py", - "JobTrackerApi/Services/SummarizerService.cs", - "JobTrackerApi.Tests/AuthAndSystemControllerTests.cs", - "JobTrackerApi.Tests/ProductionConfigTests.cs", - "JobTrackerApi.Tests/OwnershipGuardTests.cs", - "docker-compose.yml" - ], - "key_decisions": [ - "Treat import-time model load in `tools/summarizer/app.py` as the primary startup reliability seam rather than broadening immediately into model-quality work.", - "Treat AI reliability as two linked contracts: Python service capability reporting and API-side failure/metrics behavior.", - "Keep S06 focused on explicit degraded-path behavior and observability rather than redesigning all AI call sites." - ], - "full_summary_md": "---\nid: T01\nparent: S06\nmilestone: M011\nkey_files:\n - tools/summarizer/app.py\n - tools/summarizer/tests/test_app.py\n - JobTrackerApi/Services/SummarizerService.cs\n - JobTrackerApi.Tests/AuthAndSystemControllerTests.cs\n - JobTrackerApi.Tests/ProductionConfigTests.cs\n - JobTrackerApi.Tests/OwnershipGuardTests.cs\n - docker-compose.yml\nkey_decisions:\n - Treat import-time model load in `tools/summarizer/app.py` as the primary startup reliability seam rather than broadening immediately into model-quality work.\n - Treat AI reliability as two linked contracts: Python service capability reporting and API-side failure/metrics behavior.\n - Keep S06 focused on explicit degraded-path behavior and observability rather than redesigning all AI call sites.\nduration: \nverification_result: mixed\ncompleted_at: 2026-04-10T23:20:01.926Z\nblocker_discovered: false\n---\n\n# T01: Mapped the real S06 seam around import-time AI startup, optional Ollama degradation, and API-side failure/metrics behavior.\n\n**Mapped the real S06 seam around import-time AI startup, optional Ollama degradation, and API-side failure/metrics behavior.**\n\n## What Happened\n\nI mapped the AI/Ollama reliability seam across the Python service, the .NET wrapper, and the deployment contract. The most important startup risk is in `tools/summarizer/app.py`: the Hugging Face model loads at import time unless `AI_SERVICE_SKIP_MODEL_LOAD=1`, which means service startup and health semantics are tightly coupled to heavy model initialization. Optional Ollama-backed CV classification uses synchronous request flow and explicit HTTP exceptions, but the surrounding capability story is still split between ad hoc endpoint behavior and `/health`. On the API side, `SummarizerService` tracks useful metrics and last-error state, but caller-facing failures for summarize and OCR often collapse to `null`, so degraded states are mostly diagnosable through admin metrics rather than through a richer service contract. Existing tests are minimal and structural: Python tests cover `health` without Ollama and basic CV classification shaping, while .NET tests only assert that the metrics contract exposes runtime device fields and that the summarizer cache key seam exists. That gives S06 a clean next seam: harden explicit capability reporting and degraded-path behavior in the Python service and .NET wrapper, then add focused tests for those scenarios.\n\n## Verification\n\nVerified by inspecting the Python AI service, the .NET summarizer wrapper, deployment/runtime config, and existing focused tests for AI contract coverage.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `rg -n \"OLLAMA|SKIP_MODEL_LOAD|/health|/summarize|/extract-text|RunProbeAsync|GetMetricsAsync|LastError|Probe\" tools/summarizer JobTrackerApi/Services JobTrackerApi.Tests -S` | -1 | unknown (coerced from string) | 0ms |\n\n## Deviations\n\nI included `docker-compose.yml` and the summarizer test module in the seam mapping because the runtime contract matters here as much as the code itself. S06’s reliability issues cross the API, the Python service, and the container startup assumptions.\n\n## Known Issues\n\nAt the start of S06, the Python AI service loads the summarization model at import time unless `AI_SERVICE_SKIP_MODEL_LOAD=1`, Ollama calls are synchronous and optional but failure modes are coarse, and the .NET wrapper often collapses request failures to `null` with metrics carrying most of the detail after the fact.\n\n## Files Created/Modified\n\n- `tools/summarizer/app.py`\n- `tools/summarizer/tests/test_app.py`\n- `JobTrackerApi/Services/SummarizerService.cs`\n- `JobTrackerApi.Tests/AuthAndSystemControllerTests.cs`\n- `JobTrackerApi.Tests/ProductionConfigTests.cs`\n- `JobTrackerApi.Tests/OwnershipGuardTests.cs`\n- `docker-compose.yml`\n", - "description": "1. Inspect `tools/summarizer/app.py`, `JobTrackerApi/Services/SummarizerService.cs`, and current tests to map the real reliability seams: import-time model load, synchronous Ollama calls, OCR extraction limits, probe assumptions, and silent API-side failure handling.\n2. Separate the work into service-contract hardening, API metrics/failure-surface hardening, and verification.\n3. Record the narrowest S06 implementation seam that materially improves operability without redesigning the whole AI stack.", - "estimate": "0.5 day", - "files": [ - "tools/summarizer/app.py", - "tools/summarizer/tests/test_app.py", - "JobTrackerApi/Services/SummarizerService.cs", - "JobTrackerApi/Controllers/AdminSystemController.cs", - "tools/summarizer/README.md", - "docker-compose.yml" - ], - "verify": "rg -n \"OLLAMA|SKIP_MODEL_LOAD|/health|/summarize|/extract-text|RunProbeAsync|GetMetricsAsync|LastError|Probe\" tools/summarizer JobTrackerApi/Services JobTrackerApi.Tests -S", - "inputs": [ - "tools/summarizer/app.py", - "tools/summarizer/tests/test_app.py", - "JobTrackerApi/Services/SummarizerService.cs", - "JobTrackerApi/Controllers/AdminSystemController.cs", - "tools/summarizer/README.md", - "docker-compose.yml" - ], - "expected_output": [ - ".gsd/milestones/M011/slices/S06/tasks/T01-SUMMARY.md" - ], - "observability_impact": "Maps which AI failures are currently collapsed together and which capability states are already surfaced versus still implicit.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S06", - "id": "T02", - "title": "Hardened AI/Ollama capability reporting with lazy model loading, explicit degraded health state, and clearer API-side error interpretation.", - "status": "complete", - "one_liner": "Hardened AI/Ollama capability reporting with lazy model loading, explicit degraded health state, and clearer API-side error interpretation.", - "narrative": "I hardened the AI reliability contract across both sides of the boundary. In `tools/summarizer/app.py`, the summarization model now loads lazily instead of at module import by default. The service tracks explicit runtime state through `MODEL_LOADED`, `MODEL_DISABLED`, and `MODEL_LOAD_ERROR`, and `/health` now reports `model_loaded`, `model_disabled`, `summarize_available`, and `model_load_error` without forcing a hidden warm-up. The `/summarize` path still loads the model on demand, but when model loading is disabled or fails it now returns a clear 503 reason instead of a generic missing-model message. I expanded `tools/summarizer/tests/test_app.py` to cover the disabled-model health path, explicit 503 summarize behavior, and configured-but-unreachable Ollama behavior. On the .NET side, `SummarizerService` now preserves more useful detail from failed summarize/OCR/probe responses, and `GetMetricsAsync` now treats `summarize_available=false` from `/health` as unhealthy so the admin/system metrics surface does not misreport the AI service as healthy just because the HTTP endpoint responded 200.", - "verification_result": "Verified with the summarizer Python test suite via the project bootstrap script, focused .NET tests, and repeated API builds after the AI-service contract changes.", - "duration": "", - "completed_at": "2026-04-10T23:24:03.785Z", - "blocker_discovered": false, - "deviations": "I kept the existing `AiServiceMetrics` shape instead of widening it with new model-state fields. Instead, I propagated the new Python health semantics through the existing `Healthy` and `LastError` interpretation so the admin/API surface becomes clearer without broad contract churn.", - "known_issues": "`AiServiceMetrics` still does not expose dedicated fields for model-loaded versus model-disabled state; that detail is currently conveyed through the Python `/health` payload and the .NET `Healthy`/`LastError` interpretation rather than a widened .NET DTO.", - "key_files": [ - "tools/summarizer/app.py", - "tools/summarizer/tests/test_app.py", - "JobTrackerApi/Services/SummarizerService.cs", - "tools/summarizer/README.md" - ], - "key_decisions": [ - "Make the Python summarizer model load lazy by default instead of import-time eager, and expose that state explicitly through `/health`.", - "Treat `summarize_available=false` from the Python service as unhealthy in the .NET metrics layer so the admin surface does not report a false healthy state when summarization is disabled or failed to load.", - "Preserve the existing caller-facing `null` return contract in `SummarizerService` for now, but improve the recorded error detail from failed AI/probe/OCR responses." - ], - "full_summary_md": "---\nid: T02\nparent: S06\nmilestone: M011\nkey_files:\n - tools/summarizer/app.py\n - tools/summarizer/tests/test_app.py\n - JobTrackerApi/Services/SummarizerService.cs\n - tools/summarizer/README.md\nkey_decisions:\n - Make the Python summarizer model load lazy by default instead of import-time eager, and expose that state explicitly through `/health`.\n - Treat `summarize_available=false` from the Python service as unhealthy in the .NET metrics layer so the admin surface does not report a false healthy state when summarization is disabled or failed to load.\n - Preserve the existing caller-facing `null` return contract in `SummarizerService` for now, but improve the recorded error detail from failed AI/probe/OCR responses.\nduration: \nverification_result: passed\ncompleted_at: 2026-04-10T23:24:03.783Z\nblocker_discovered: false\n---\n\n# T02: Hardened AI/Ollama capability reporting with lazy model loading, explicit degraded health state, and clearer API-side error interpretation.\n\n**Hardened AI/Ollama capability reporting with lazy model loading, explicit degraded health state, and clearer API-side error interpretation.**\n\n## What Happened\n\nI hardened the AI reliability contract across both sides of the boundary. In `tools/summarizer/app.py`, the summarization model now loads lazily instead of at module import by default. The service tracks explicit runtime state through `MODEL_LOADED`, `MODEL_DISABLED`, and `MODEL_LOAD_ERROR`, and `/health` now reports `model_loaded`, `model_disabled`, `summarize_available`, and `model_load_error` without forcing a hidden warm-up. The `/summarize` path still loads the model on demand, but when model loading is disabled or fails it now returns a clear 503 reason instead of a generic missing-model message. I expanded `tools/summarizer/tests/test_app.py` to cover the disabled-model health path, explicit 503 summarize behavior, and configured-but-unreachable Ollama behavior. On the .NET side, `SummarizerService` now preserves more useful detail from failed summarize/OCR/probe responses, and `GetMetricsAsync` now treats `summarize_available=false` from `/health` as unhealthy so the admin/system metrics surface does not misreport the AI service as healthy just because the HTTP endpoint responded 200.\n\n## Verification\n\nVerified with the summarizer Python test suite via the project bootstrap script, focused .NET tests, and repeated API builds after the AI-service contract changes.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `cd tools/summarizer && ./scripts/bootstrap-and-test.sh test` | 0 | ✅ pass | 3090ms |\n| 2 | `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AuthAndSystemControllerTests|FullyQualifiedName~ProductionConfigTests|FullyQualifiedName~OwnershipGuardTests\"` | 0 | ✅ pass | 171ms |\n| 3 | `dotnet build JobTrackerApi/JobTrackerApi.csproj` | 0 | ✅ pass | 3680ms |\n\n## Deviations\n\nI kept the existing `AiServiceMetrics` shape instead of widening it with new model-state fields. Instead, I propagated the new Python health semantics through the existing `Healthy` and `LastError` interpretation so the admin/API surface becomes clearer without broad contract churn.\n\n## Known Issues\n\n`AiServiceMetrics` still does not expose dedicated fields for model-loaded versus model-disabled state; that detail is currently conveyed through the Python `/health` payload and the .NET `Healthy`/`LastError` interpretation rather than a widened .NET DTO.\n\n## Files Created/Modified\n\n- `tools/summarizer/app.py`\n- `tools/summarizer/tests/test_app.py`\n- `JobTrackerApi/Services/SummarizerService.cs`\n- `tools/summarizer/README.md`\n", - "description": "1. Harden the Python AI service so model-load, health, summarize, OCR, and optional Ollama paths expose explicit capability/failure state without blocking unrelated features unnecessarily.\n2. Tighten the API summarizer service so failed AI calls preserve more useful diagnostics/metrics and degrade predictably for callers.\n3. Add or update focused tests for degraded Ollama/model scenarios and the API-side metrics/failure contract.", - "estimate": "1.5-2.5 days", - "files": [ - "tools/summarizer/app.py", - "tools/summarizer/tests/test_app.py", - "JobTrackerApi/Services/SummarizerService.cs", - "JobTrackerApi.Tests", - "tools/summarizer/README.md" - ], - "verify": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AuthAndSystemControllerTests|FullyQualifiedName~ProductionConfigTests|FullyQualifiedName~OwnershipGuardTests\"\ncd tools/summarizer && ./scripts/bootstrap-and-test.sh test", - "inputs": [ - ".gsd/milestones/M011/slices/S06/tasks/T01-SUMMARY.md", - "tools/summarizer/app.py", - "JobTrackerApi/Services/SummarizerService.cs" - ], - "expected_output": [ - "tools/summarizer/app.py", - "tools/summarizer/tests/test_app.py", - "JobTrackerApi/Services/SummarizerService.cs", - "JobTrackerApi.Tests/", - "tools/summarizer/README.md" - ], - "observability_impact": "Healthy versus degraded AI capability states should become visible through health/metrics surfaces and tests should cover bounded failure behavior.", - "full_plan_md": "", - "sequence": 0 - }, - { - "milestone_id": "M011", - "slice_id": "S06", - "id": "T03", - "title": "Verified the AI/Ollama reliability contract through focused Python and .NET tests and recorded the remaining environment-only limits.", - "status": "complete", - "one_liner": "Verified the AI/Ollama reliability contract through focused Python and .NET tests and recorded the remaining environment-only limits.", - "narrative": "I verified the hardened AI/Ollama contract at the right boundary for this slice. The Python summarizer tests now prove that `/health` reports explicit disabled-model state without forcing warm-up, that `/summarize` returns a clear 503 when model loading is disabled, and that configured-but-unreachable Ollama is surfaced cleanly through health metadata. The focused .NET tests still pass after the `SummarizerService` changes, and the API builds cleanly. Together that gives the slice the evidence it needed: the AI service can describe its own degraded capability state, the API no longer treats `summarize_available=false` as healthy, and the remaining limits are environment/model-deployment concerns rather than hidden contract ambiguity.", - "verification_result": "Verified with `cd tools/summarizer && ./scripts/bootstrap-and-test.sh test`, `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AuthAndSystemControllerTests|FullyQualifiedName~ProductionConfigTests|FullyQualifiedName~OwnershipGuardTests\"`, and `dotnet build JobTrackerApi/JobTrackerApi.csproj`.", - "duration": "", - "completed_at": "2026-04-10T23:24:23.066Z", - "blocker_discovered": false, - "deviations": "Verification stayed at the focused test and health-contract level instead of running a full live Ollama stack. That was the right boundary for S06 because the slice target was explicit degraded behavior and capability reporting, not proving a specific local model deployment.", - "known_issues": "Live verification against a real local Ollama instance was not required for this slice. The contract is now explicit for configured-but-unreachable and model-disabled states, but actual model pull/load latency remains environment-dependent.", - "key_files": [ - "tools/summarizer/app.py", - "tools/summarizer/tests/test_app.py", - "JobTrackerApi/Services/SummarizerService.cs", - "tools/summarizer/README.md" - ], - "key_decisions": [ - "Use the project bootstrap script for Python verification instead of assuming host-level `pytest` availability.", - "Treat environment-missing Ollama as a degraded-path contract to verify, not a blocker for completing the slice." - ], - "full_summary_md": "---\nid: T03\nparent: S06\nmilestone: M011\nkey_files:\n - tools/summarizer/app.py\n - tools/summarizer/tests/test_app.py\n - JobTrackerApi/Services/SummarizerService.cs\n - tools/summarizer/README.md\nkey_decisions:\n - Use the project bootstrap script for Python verification instead of assuming host-level `pytest` availability.\n - Treat environment-missing Ollama as a degraded-path contract to verify, not a blocker for completing the slice.\nduration: \nverification_result: passed\ncompleted_at: 2026-04-10T23:24:23.065Z\nblocker_discovered: false\n---\n\n# T03: Verified the AI/Ollama reliability contract through focused Python and .NET tests and recorded the remaining environment-only limits.\n\n**Verified the AI/Ollama reliability contract through focused Python and .NET tests and recorded the remaining environment-only limits.**\n\n## What Happened\n\nI verified the hardened AI/Ollama contract at the right boundary for this slice. The Python summarizer tests now prove that `/health` reports explicit disabled-model state without forcing warm-up, that `/summarize` returns a clear 503 when model loading is disabled, and that configured-but-unreachable Ollama is surfaced cleanly through health metadata. The focused .NET tests still pass after the `SummarizerService` changes, and the API builds cleanly. Together that gives the slice the evidence it needed: the AI service can describe its own degraded capability state, the API no longer treats `summarize_available=false` as healthy, and the remaining limits are environment/model-deployment concerns rather than hidden contract ambiguity.\n\n## Verification\n\nVerified with `cd tools/summarizer && ./scripts/bootstrap-and-test.sh test`, `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AuthAndSystemControllerTests|FullyQualifiedName~ProductionConfigTests|FullyQualifiedName~OwnershipGuardTests\"`, and `dotnet build JobTrackerApi/JobTrackerApi.csproj`.\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `cd tools/summarizer && ./scripts/bootstrap-and-test.sh test` | 0 | ✅ pass | 3090ms |\n| 2 | `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AuthAndSystemControllerTests|FullyQualifiedName~ProductionConfigTests|FullyQualifiedName~OwnershipGuardTests\"` | 0 | ✅ pass | 171ms |\n| 3 | `dotnet build JobTrackerApi/JobTrackerApi.csproj` | 0 | ✅ pass | 3680ms |\n\n## Deviations\n\nVerification stayed at the focused test and health-contract level instead of running a full live Ollama stack. That was the right boundary for S06 because the slice target was explicit degraded behavior and capability reporting, not proving a specific local model deployment.\n\n## Known Issues\n\nLive verification against a real local Ollama instance was not required for this slice. The contract is now explicit for configured-but-unreachable and model-disabled states, but actual model pull/load latency remains environment-dependent.\n\n## Files Created/Modified\n\n- `tools/summarizer/app.py`\n- `tools/summarizer/tests/test_app.py`\n- `JobTrackerApi/Services/SummarizerService.cs`\n- `tools/summarizer/README.md`\n", - "description": "1. Verify the hardened AI/Ollama contract with focused .NET and Python tests plus health/behavior checks where practical.\n2. Confirm the API/admin metrics surface still reports useful capability state without requiring a fully healthy Ollama instance.\n3. Record any remaining environment-only limitations separately from product behavior.", - "estimate": "0.5-1 day", - "files": [ - "tools/summarizer/app.py", - "tools/summarizer/tests/test_app.py", - "JobTrackerApi/Services/SummarizerService.cs", - "JobTrackerApi/Controllers/AdminSystemController.cs", - "JobTrackerApi.Tests" - ], - "verify": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AuthAndSystemControllerTests|FullyQualifiedName~ProductionConfigTests|FullyQualifiedName~OwnershipGuardTests\"\ncd tools/summarizer && ./scripts/bootstrap-and-test.sh test", - "inputs": [ - "tools/summarizer/app.py", - "tools/summarizer/tests/test_app.py", - "JobTrackerApi/Services/SummarizerService.cs", - "JobTrackerApi/Controllers/AdminSystemController.cs", - "JobTrackerApi.Tests" - ], - "expected_output": [ - ".gsd/milestones/M011/slices/S06/tasks/T03-SUMMARY.md" - ], - "observability_impact": "Verification should prove that AI capability state stays explicit under both available and degraded dependency conditions.", - "full_plan_md": "", - "sequence": 0 - } - ], - "decisions": [ - { - "seq": 1, - "id": "D001", - "when_context": "M001", - "scope": "scope", - "decision": "Product entry point", - "choice": "External job discovery, then import into the app", - "rationale": "The user finds jobs on job sites first; the app begins when a role is imported.", - "revisable": "No", - "made_by": "collaborative", - "superseded_by": null - }, - { - "seq": 2, - "id": "D002", - "when_context": "M001", - "scope": "pattern", - "decision": "AI action model", - "choice": "Assistive drafting and analysis only; no autonomous sending", - "rationale": "The user wants AI help but explicitly does not want auto-send or auto-apply behavior.", - "revisable": "No", - "made_by": "collaborative", - "superseded_by": null - }, - { - "seq": 3, - "id": "D003", - "when_context": "M001", - "scope": "scope", - "decision": "Primary user", - "choice": "Individual job seeker", - "rationale": "The product is designed for individuals managing their own search, not recruiter or team workflows.", - "revisable": "Yes — if product direction changes later", - "made_by": "collaborative", - "superseded_by": null - }, - { - "seq": 4, - "id": "D004", - "when_context": "M001", - "scope": "pattern", - "decision": "Daily navigation hierarchy", - "choice": "Job table first, then follow-up/dashboard, then individual job workspace", - "rationale": "The user explicitly described this as the intended control flow for daily use.", - "revisable": "Yes — if real usage disproves the hierarchy", - "made_by": "collaborative", - "superseded_by": null - }, - { - "seq": 5, - "id": "D005", - "when_context": "M001", - "scope": "roadmap", - "decision": "First milestone focus", - "choice": "Prioritize Gmail import quality and AI draft quality before broader expansion", - "rationale": "The user identified Gmail import and AI drafts as the weakest current areas and the first bar for daily use.", - "revisable": "Yes — if execution proves another blocker is more fundamental", - "made_by": "collaborative", - "superseded_by": null - }, - { - "seq": 6, - "id": "D006", - "when_context": "M001/S02", - "scope": "workspace-persistence", - "decision": "How the saved application answer draft should persist inside the job workspace before a dedicated field exists", - "choice": "Store the application answer draft in a replaceable notes block and make SaveApplicationDrafts overwrite notes when notes are explicitly provided", - "rationale": "The existing append-only notes behavior made the Tailored CV workspace untrustworthy because repeated saves duplicated the application answer indefinitely. A replaceable notes block preserves current schema compatibility while giving the workspace a stable saved/read-back loop for later slices.", - "revisable": "Yes", - "made_by": "agent", - "superseded_by": null - }, - { - "seq": 7, - "id": "D007", - "when_context": "M001/S01", - "scope": "gmail-continuity", - "decision": "What “good Gmail import” now means for M001", - "choice": "Treat Gmail import as full-thread continuity: the user must be able to import the whole relevant thread, and already-linked Gmail threads must refresh automatically so later inbound messages and user-sent replies appear on the job without manual re-import. This supersedes the narrower one-time-import interpretation inside D005’s Gmail-import focus.", - "rationale": "The user explicitly asked whether the app can bring the whole email thread and automatically show their reply later without re-pulling/importing again. That changes the trust bar from “find and import the right message” to “keep the linked thread current over time,” while still preserving the no-auto-send boundary from D002.", - "revisable": "Yes — if Gmail API or product constraints later require a different sync model", - "made_by": "human", - "superseded_by": null - }, - { - "seq": 8, - "id": "D008", - "when_context": "M001/S01 planning", - "scope": "gmail-sync", - "decision": "How linked Gmail threads stay current in S01", - "choice": "Use a job-scoped refresh flow over already-imported Gmail thread IDs, triggered from the job workspace/API, instead of building inbox-wide Gmail watch/history cursor infrastructure in M001.", - "rationale": "The codebase already persists ExternalThreadId per correspondence and lacks Gmail history/watch infrastructure. Fetching known linked threads for one job keeps scope bounded, fits the single-user workspace model, supports duplicate-safe import of new inbound and sent replies, and creates a trustworthy continuity loop without adding brittle webhook/cursor state before the milestone proves value.", - "revisable": "Yes — if real usage shows job-scoped pull refresh is too slow or misses important continuity cases.", - "made_by": "agent", - "superseded_by": null - }, - { - "seq": 9, - "id": "D009", - "when_context": "M001/S01 closure", - "scope": "gmail-matching", - "decision": "Where Gmail candidate aggregation and ranking logic should live for job-aware import", - "choice": "Keep Gmail query-hit aggregation, dedupe, matched-query traces, and ranking reasons in the backend contract instead of recreating that logic in the React workspace.", - "rationale": "The correspondence workspace needs explanatory candidate ranking plus duplicate visibility, and putting the logic in the API keeps one source of truth for scoring/import state while preventing browser-side heuristic drift.", - "revisable": "Yes", - "made_by": "agent", - "superseded_by": null - }, - { - "seq": 10, - "id": "D010", - "when_context": "M001/S03 closeout", - "scope": "followup-drafting", - "decision": "How follow-up grounding should be exposed to the workspace", - "choice": "Return explicit follow-up grounding fields (`contextSummary`, `contextSignals`, `threadSubject`, and last-correspondence metadata) from the backend DTO instead of making the React workspace infer them client-side.", - "rationale": "The slice needed draft trust, not just draft text. Putting grounding signals in the API contract gives the UI a durable explanation surface, keeps thread/package inference in one place with generation logic, and makes backend/frontend tests assert the same source of truth.", - "revisable": "Yes", - "made_by": "agent", - "superseded_by": null - }, - { - "seq": 11, - "id": "D011", - "when_context": "M001/S05 planning", - "scope": "workflow-trust", - "decision": "How S05 should represent daily-loop readiness and next-action state across overview surfaces", - "choice": "Introduce explicit workflow trust/action signals from the backend/UI contract and reuse them across the table, dashboard, reminders, and shared workspace instead of continuing to infer behavior from free-form `followUpReason` strings or raw `notes` presence in each component.", - "rationale": "S05 is an end-to-end polish slice where the remaining risk is fragmented trust, not missing subsystems. Centralizing workflow signals avoids heuristic drift between overview surfaces, respects the saved application-answer notes-block constraint, and gives the final integrated regression one source of truth for R010 while preserving the explicit manual-send boundary required by R008.", - "revisable": "Yes", - "made_by": "agent", - "superseded_by": null - }, - { - "seq": 12, - "id": "D012", - "when_context": "M001/S05", - "scope": "workspace-observability", - "decision": "Where linked Gmail thread continuity status should be exposed in the final trust loop", - "choice": "Show linked-thread refresh state directly in the correspondence workspace, including linked thread count and last refresh outcome, instead of hiding continuity feedback inside the Gmail import modal only.", - "rationale": "The end-to-end trust loop depends on users being able to verify that already-linked Gmail threads stay current without re-importing. Surfacing continuity state in the main workspace keeps the job timeline trustworthy, supports final UAT, and avoids making thread refresh feel like a hidden one-off import behavior.", - "revisable": "Yes", - "made_by": "agent", - "superseded_by": null - }, - { - "seq": 13, - "id": "D013", - "when_context": "M001/S06/T02", - "scope": "seeding", - "decision": "How acceptance-ready job data is created for S06 live reruns", - "choice": "Seed the acceptance fixture through the live companies/jobapplications/correspondence API plus the dedicated tailored-cv, application-drafts, and followup endpoints, using deterministic company/title/thread/message identifiers for idempotent reruns.", - "rationale": "The slice goal is a repeatable live environment check, so seeding through the same HTTP contract the UI uses proves the real backend surface, keeps package/readiness behavior aligned with production code paths, and avoids brittle direct DB mutations or duplicate correspondence on reruns.", - "revisable": "Yes", - "made_by": "agent", - "superseded_by": null - }, - { - "seq": 14, - "id": "D014", - "when_context": "M001/S06/T03", - "scope": "acceptance-run", - "decision": "How the S06 live acceptance runner should authenticate seeding and protected UI verification without requiring manual token export every rerun.", - "choice": "Allow the S06 acceptance runner to mint a localhost-only admin JWT from the checked-in dev JWT settings plus the local SQLite admin record when AUTH_TOKEN is missing.", - "rationale": "The current DB snapshot contains an admin user but the placeholder development password is not reliable, and the task’s verification command must stay repeatable. A localhost-only signed JWT fallback keeps the run fully local, avoids secret prompts, does not print token material, and still exercises the real protected API/UI paths.", - "revisable": "Yes", - "made_by": "agent", - "superseded_by": null - }, - { - "seq": 15, - "id": "D015", - "when_context": "M001/S06", - "scope": "environment", - "decision": "S06 preflight auth handling", - "choice": "Treat /api/auth/config reachability plus an auth-limited /api/admin/system probe as a guided partial-pass, and never echo bearer tokens in preflight output.", - "rationale": "S06 needs a repeatable go/no-go gate before browser UAT. The live stack can be healthy even when admin-only diagnostics require an extra token, so the preflight should fail hard only for unreachable/malformed API responses while still surfacing clear AUTH_TOKEN guidance and protecting secrets on shared terminals.", - "revisable": "Yes", - "made_by": "agent", - "superseded_by": null - }, - { - "seq": 16, - "id": "D016", - "when_context": "M001/S07", - "scope": "uat-artifact", - "decision": "How S07 daily-loop closure should capture acceptance evidence", - "choice": "Keep docs/s06-acceptance-run.md as the canonical execution log and use S07 closure artifacts to summarize/import the cross-surface proof rather than duplicating raw runner output.", - "rationale": "S07's job is to prove one seeded job stays coherent across /jobs, workspace, /reminders, and /dashboard while preserving the manual-send boundary. Reusing the S06 runner output as the canonical source keeps reruns idempotent, prevents drift between generated logs and human summary text, and gives downstream slices one stable place for detailed evidence plus one concise dependency summary.", - "revisable": "Yes", - "made_by": "agent", - "superseded_by": null - }, - { - "seq": 17, - "id": "D017", - "when_context": "M005 planning", - "scope": "delivery", - "decision": "How M005 execution should be staged and published", - "choice": "Execute M005 one slice at a time, verify each slice independently, push each slice on its own git branch, then continue to the next slice only after the prior slice is stable.", - "rationale": "The CV intelligence/export milestone is high-risk and multi-layered. Slice-by-slice branching and push discipline will keep extraction, tailored draft, and PDF rendering changes reviewable and reduce regression blast radius.", - "revisable": "Yes", - "made_by": "human", - "superseded_by": null - }, - { - "seq": 18, - "id": "D018", - "when_context": "M005 planning", - "scope": "verification", - "decision": "What document corpus should drive universal CV extraction verification", - "choice": "Use the real CV files placed in /home/pi/cvs as a regression corpus for universal extractor work, alongside synthetic/unit fixtures.", - "rationale": "A universal CV extractor cannot be validated only against synthetic fixtures. Real CVs with different layouts, OCR quality, and structure are required to test extraction, review UX, and rendering assumptions.", - "revisable": "Yes", - "made_by": "human", - "superseded_by": null - }, - { - "seq": 19, - "id": "D019", - "when_context": "M011/S01", - "scope": "frontend-platform", - "decision": "How to handle frontend build-tool risk during the initial platform hardening slice", - "choice": "Remediate the direct critical frontend dependency immediately, keep the CRA baseline for the next hardening slice, and defer the broader frontend build-tool migration to a later dedicated implementation step.", - "rationale": "The audit showed one critical direct dependency issue (`axios`) and a large remaining body of transitive risk concentrated behind `react-scripts`. Upgrading the direct dependency removed the critical finding with low change surface, restored a reproducible local and Docker build baseline, and avoids coupling S02 auth/session work to a framework migration. The remaining CRA transitive debt is still real, but it is now a contained follow-on migration concern rather than an immediate blocker.", - "revisable": "Yes", - "made_by": "agent", - "superseded_by": null - }, - { - "seq": 20, - "id": "D020", - "when_context": "M011/S02", - "scope": "authentication", - "decision": "What session transport should replace browser-stored bearer tokens in the frontend and API", - "choice": "Use an HttpOnly cookie-backed app session for the primary local auth path, have the API read the local app JWT from a secure cookie instead of browser storage, keep Google credential exchange server-side, and add CSRF protection for state-changing requests.", - "rationale": "The current design stores the app bearer token in localStorage/sessionStorage and attaches it via an Authorization header on every request, which leaves the primary local auth path exposed to XSS-driven token theft. A cookie-backed session keeps the app token out of browser storage, lets the API enforce the local auth path centrally, preserves existing JWT-based authorization semantics on the server, and gives the frontend a cleaner source of truth through `/auth/me` and explicit unauthorized responses. Adding CSRF protection alongside the cookie keeps state-changing requests safe under the new transport.", - "revisable": "Yes", - "made_by": "agent", - "superseded_by": null - }, - { - "seq": 21, - "id": "D021", - "when_context": "M011/S03/T01", - "scope": "frontend-architecture", - "decision": "How to centralize degraded-state handling for the core frontend views in S03.", - "choice": "Use a lightweight shared frontend async-view-state pattern for S03 instead of introducing a new global data-fetching framework in this slice.", - "rationale": "The current risk is not lack of a full query library; it is that core views swallow request failures into empty arrays or nulls and then render normal empty states. A small shared abstraction for loading/empty/error/retry state can retire that product risk quickly across the highest-traffic views without broadening S03 into a framework migration or destabilizing the existing app.", - "revisable": "Yes", - "made_by": "agent", - "superseded_by": null - } - ], - "verification_evidence": [ - { - "id": 1, - "task_id": "T01", - "slice_id": "S01", - "milestone_id": "M011", - "command": "Baseline: `cd job-tracker-ui && npm audit --audit-level=moderate --json` showed 28 vulnerabilities including 1 critical direct finding on axios <1.15.0.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:45:12.983Z" - }, - { - "id": 2, - "task_id": "T01", - "slice_id": "S01", - "milestone_id": "M011", - "command": "Baseline: `cd job-tracker-ui && npm run build` initially failed with EACCES because `job-tracker-ui/build/static` contained root-owned files.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:45:12.983Z" - }, - { - "id": 3, - "task_id": "T01", - "slice_id": "S01", - "milestone_id": "M011", - "command": "After remediation: `cd job-tracker-ui && npm install` updated axios to 1.15.0 and removed the direct critical vulnerability.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:45:12.984Z" - }, - { - "id": 4, - "task_id": "T01", - "slice_id": "S01", - "milestone_id": "M011", - "command": "After remediation: `cd job-tracker-ui && npm audit --audit-level=moderate --json` showed 27 remaining vulnerabilities and 0 critical findings.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:45:12.984Z" - }, - { - "id": 5, - "task_id": "T01", - "slice_id": "S01", - "milestone_id": "M011", - "command": "After remediation: `cd job-tracker-ui && npm run build` completed successfully.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:45:12.984Z" - }, - { - "id": 6, - "task_id": "T01", - "slice_id": "S01", - "milestone_id": "M011", - "command": "Compatibility check: `docker run --rm -v ... node:20-alpine sh -lc 'npm ci'` initially failed on a lockfile mismatch (`Missing: yaml@2.8.3 from lock file`).", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:45:12.984Z" - }, - { - "id": 7, - "task_id": "T01", - "slice_id": "S01", - "milestone_id": "M011", - "command": "Compatibility fix: `docker run --rm --user 1000:1000 -v ... node:20-alpine sh -lc 'npm install'` regenerated the lockfile using the same npm major version as the Docker image.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:45:12.984Z" - }, - { - "id": 8, - "task_id": "T01", - "slice_id": "S01", - "milestone_id": "M011", - "command": "After compatibility fix: `docker run --rm -v ... node:20-alpine sh -lc 'npm ci --foreground-scripts=false'` completed successfully.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:45:12.984Z" - }, - { - "id": 9, - "task_id": "T01", - "slice_id": "S01", - "milestone_id": "M011", - "command": "Container verification: `cd /home/pi/development/JobTracker && docker compose build frontend` completed successfully.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:45:12.984Z" - }, - { - "id": 10, - "task_id": "T01", - "slice_id": "S01", - "milestone_id": "M011", - "command": "Regression signal: `cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false` finished with 16 passing suites and 2 failing suites (`daily-control-loop.test.tsx`, `end-to-end-trust-loop.test.tsx`) that appear unrelated to the dependency remediation itself.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:45:12.984Z" - }, - { - "id": 11, - "task_id": "T02", - "slice_id": "S01", - "milestone_id": "M011", - "command": "`cd job-tracker-ui && npm install` updated axios to 1.15.0 and synchronized the package tree.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:46:52.965Z" - }, - { - "id": 12, - "task_id": "T02", - "slice_id": "S01", - "milestone_id": "M011", - "command": "`cd job-tracker-ui && npm run build` succeeded after the workspace cleanup.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:46:52.965Z" - }, - { - "id": 13, - "task_id": "T02", - "slice_id": "S01", - "milestone_id": "M011", - "command": "`docker run --rm --user 1000:1000 -v /home/pi/development/JobTracker/job-tracker-ui:/app -w /app node:20-alpine sh -lc 'npm install'` regenerated the lockfile for the container toolchain.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:46:52.965Z" - }, - { - "id": 14, - "task_id": "T02", - "slice_id": "S01", - "milestone_id": "M011", - "command": "`docker run --rm -v /home/pi/development/JobTracker/job-tracker-ui:/app -w /app node:20-alpine sh -lc 'npm ci --foreground-scripts=false'` succeeded.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:46:52.965Z" - }, - { - "id": 15, - "task_id": "T02", - "slice_id": "S01", - "milestone_id": "M011", - "command": "`cd /home/pi/development/JobTracker && docker compose build frontend` succeeded.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:46:52.965Z" - }, - { - "id": 16, - "task_id": "T03", - "slice_id": "S01", - "milestone_id": "M011", - "command": "`cd job-tracker-ui && npm audit --audit-level=moderate --json` now reports 0 critical findings and 27 remaining transitive findings.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:47:07.042Z" - }, - { - "id": 17, - "task_id": "T03", - "slice_id": "S01", - "milestone_id": "M011", - "command": "`cd job-tracker-ui && npm run build` succeeded.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:47:07.042Z" - }, - { - "id": 18, - "task_id": "T03", - "slice_id": "S01", - "milestone_id": "M011", - "command": "`docker run --rm -v /home/pi/development/JobTracker/job-tracker-ui:/app -w /app node:20-alpine sh -lc 'npm ci --foreground-scripts=false'` succeeded.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:47:07.042Z" - }, - { - "id": 19, - "task_id": "T03", - "slice_id": "S01", - "milestone_id": "M011", - "command": "`cd /home/pi/development/JobTracker && docker compose build frontend` succeeded.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:47:07.042Z" - }, - { - "id": 20, - "task_id": "T03", - "slice_id": "S01", - "milestone_id": "M011", - "command": "`cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false` produced 16 passing suites and 2 failing suites, leaving a clear follow-up list instead of an unverified baseline.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:47:07.042Z" - }, - { - "id": 21, - "task_id": "T01", - "slice_id": "S02", - "milestone_id": "M011", - "command": "`rg -n \"authToken|Authorization|Bearer|clearAuthToken|setAuthToken|getAuthToken|JwtBearer|TokenService|request-password-reset|login|logout\" job-tracker-ui/src JobTrackerApi -S` enumerated the frontend and API auth touchpoints.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:49:40.585Z" - }, - { - "id": 22, - "task_id": "T01", - "slice_id": "S02", - "milestone_id": "M011", - "command": "Reviewed `job-tracker-ui/src/auth.ts` and confirmed that localStorage/sessionStorage currently hold the app token.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:49:40.585Z" - }, - { - "id": 23, - "task_id": "T01", - "slice_id": "S02", - "milestone_id": "M011", - "command": "Reviewed `job-tracker-ui/src/api.ts` and confirmed the Authorization header is attached from browser storage on each request.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:49:40.585Z" - }, - { - "id": 24, - "task_id": "T01", - "slice_id": "S02", - "milestone_id": "M011", - "command": "Reviewed `job-tracker-ui/src/App.tsx`, `LoginPage.tsx`, `GoogleAuthCard.tsx`, `AuthStatusCard.tsx`, `UserManagementCard.tsx`, and `themePrefs.ts` to identify direct frontend dependencies on browser-stored JWT state.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:49:40.585Z" - }, - { - "id": 25, - "task_id": "T01", - "slice_id": "S02", - "milestone_id": "M011", - "command": "Reviewed `JobTrackerApi/Controllers/AuthController.cs`, `JobTrackerApi/Services/TokenService.cs`, and `JobTrackerApi/Program.cs` to confirm local app auth is currently JWT Bearer-based and returned directly to the frontend.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T16:49:40.585Z" - }, - { - "id": 26, - "task_id": "T02", - "slice_id": "S02", - "milestone_id": "M011", - "command": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter FullyQualifiedName~Auth", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 163, - "created_at": "2026-04-10T19:57:16.245Z" - }, - { - "id": 27, - "task_id": "T02", - "slice_id": "S02", - "milestone_id": "M011", - "command": "cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/login-page.test.tsx src/profile-page.test.tsx", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 15267, - "created_at": "2026-04-10T19:57:16.245Z" - }, - { - "id": 28, - "task_id": "T02", - "slice_id": "S02", - "milestone_id": "M011", - "command": "dotnet build JobTrackerApi/JobTrackerApi.csproj", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 5430, - "created_at": "2026-04-10T19:57:16.245Z" - }, - { - "id": 29, - "task_id": "T02", - "slice_id": "S02", - "milestone_id": "M011", - "command": "cd job-tracker-ui && npm run build", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 0, - "created_at": "2026-04-10T19:57:16.245Z" - }, - { - "id": 30, - "task_id": "T03", - "slice_id": "S02", - "milestone_id": "M011", - "command": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter FullyQualifiedName~Auth", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 163, - "created_at": "2026-04-10T19:57:41.019Z" - }, - { - "id": 31, - "task_id": "T03", - "slice_id": "S02", - "milestone_id": "M011", - "command": "cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/login-page.test.tsx src/profile-page.test.tsx", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 15267, - "created_at": "2026-04-10T19:57:41.019Z" - }, - { - "id": 32, - "task_id": "T03", - "slice_id": "S02", - "milestone_id": "M011", - "command": "cd job-tracker-ui && npm run build", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 0, - "created_at": "2026-04-10T19:57:41.019Z" - }, - { - "id": 33, - "task_id": "T03", - "slice_id": "S02", - "milestone_id": "M011", - "command": "Browser verification: navigating to http://localhost:3001/jobs redirected to /login with no failed requests in the observed pass.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T19:57:41.019Z" - }, - { - "id": 34, - "task_id": "T03", - "slice_id": "S02", - "milestone_id": "M011", - "command": "HTTP verification: GET /api/auth/csrf returned 204 and set the XSRF-TOKEN cookie; GET /api/auth/me with only that cookie returned 401 Unauthorized as expected for no session.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T19:57:41.019Z" - }, - { - "id": 35, - "task_id": "T01", - "slice_id": "S03", - "milestone_id": "M011", - "command": "rg -n \"catch\\(\\(\\) => \\[\\]\\)|catch\\(\\(\\) => set.*\\[\\]\\)|catch\\(\\(\\) => set.*null\\)|No jobs found|remindersNothing|companiesEmpty\" job-tracker-ui/src -S", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 0, - "created_at": "2026-04-10T22:05:25.917Z" - }, - { - "id": 36, - "task_id": "T02", - "slice_id": "S03", - "milestone_id": "M011", - "command": "cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/login-page.test.tsx src/profile-page.test.tsx", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 18627, - "created_at": "2026-04-10T22:19:14.244Z" - }, - { - "id": 37, - "task_id": "T02", - "slice_id": "S03", - "milestone_id": "M011", - "command": "cd job-tracker-ui && npm run build", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 0, - "created_at": "2026-04-10T22:19:14.244Z" - }, - { - "id": 38, - "task_id": "T03", - "slice_id": "S03", - "milestone_id": "M011", - "command": "cd job-tracker-ui && CI=true npm test -- --runInBand --watch=false src/daily-control-loop.test.tsx src/login-page.test.tsx src/profile-page.test.tsx", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 18627, - "created_at": "2026-04-10T22:19:33.191Z" - }, - { - "id": 39, - "task_id": "T03", - "slice_id": "S03", - "milestone_id": "M011", - "command": "cd job-tracker-ui && npm run build", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 0, - "created_at": "2026-04-10T22:19:33.191Z" - }, - { - "id": 40, - "task_id": "T03", - "slice_id": "S03", - "milestone_id": "M011", - "command": "Browser verification: with only the frontend running, navigating to http://localhost:3001/jobs showed 'Unable to load jobs' and 'The jobs list cannot reach the API right now.' instead of an empty jobs state.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T22:19:33.191Z" - }, - { - "id": 41, - "task_id": "T03", - "slice_id": "S03", - "milestone_id": "M011", - "command": "Browser verification: after the API auth surface was reachable again, navigating to http://localhost:3001/login showed the normal sign-in UI and remember-me controls.", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T22:19:33.191Z" - }, - { - "id": 42, - "task_id": "T01", - "slice_id": "S04", - "milestone_id": "M011", - "command": "rg -n \"AddHostedService|Migrate\\(|Ensure|RuleSettings|UserRuleSettings|JobApplications|Auth:|UseCors|UseRateLimiter|UseAuthentication|UseAuthorization\" JobTrackerApi/Program.cs JobTrackerApi/Services -S", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 0, - "created_at": "2026-04-10T22:33:06.539Z" - }, - { - "id": 43, - "task_id": "T02", - "slice_id": "S04", - "milestone_id": "M011", - "command": "dotnet build JobTrackerApi/JobTrackerApi.csproj", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 2750, - "created_at": "2026-04-10T22:44:02.945Z" - }, - { - "id": 44, - "task_id": "T02", - "slice_id": "S04", - "milestone_id": "M011", - "command": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter AuthAndSystemControllerTests", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 172, - "created_at": "2026-04-10T22:44:02.945Z" - }, - { - "id": 45, - "task_id": "T02", - "slice_id": "S04", - "milestone_id": "M011", - "command": "ASPNETCORE_ENVIRONMENT=Development ASPNETCORE_URLS=http://localhost:5202 dotnet run --project JobTrackerApi/JobTrackerApi.csproj", - "exit_code": 0, - "verdict": "✅ ready", - "duration_ms": 9000, - "created_at": "2026-04-10T22:44:02.945Z" - }, - { - "id": 46, - "task_id": "T03", - "slice_id": "S04", - "milestone_id": "M011", - "command": "dotnet build JobTrackerApi/JobTrackerApi.csproj", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 2750, - "created_at": "2026-04-10T22:44:23.635Z" - }, - { - "id": 47, - "task_id": "T03", - "slice_id": "S04", - "milestone_id": "M011", - "command": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter AuthAndSystemControllerTests", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 172, - "created_at": "2026-04-10T22:44:23.635Z" - }, - { - "id": 48, - "task_id": "T03", - "slice_id": "S04", - "milestone_id": "M011", - "command": "GET http://localhost:5202/api/auth/config", - "exit_code": 200, - "verdict": "✅ pass", - "duration_ms": 0, - "created_at": "2026-04-10T22:44:23.635Z" - }, - { - "id": 49, - "task_id": "T03", - "slice_id": "S04", - "milestone_id": "M011", - "command": "GET http://localhost:5202/api/auth/me", - "exit_code": 401, - "verdict": "✅ pass", - "duration_ms": 0, - "created_at": "2026-04-10T22:44:23.635Z" - }, - { - "id": 50, - "task_id": "T01", - "slice_id": "S05", - "milestone_id": "M011", - "command": "rg -n \"\\[Authorize|\\[AllowAnonymous|IFormFile|PhysicalFile|client-errors|backup|export|avatar\" JobTrackerApi/Controllers JobTrackerApi.Tests -S", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T22:56:09.918Z" - }, - { - "id": 51, - "task_id": "T02", - "slice_id": "S05", - "milestone_id": "M011", - "command": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AttachmentsControllerTests|FullyQualifiedName~BackupControllerTests|FullyQualifiedName~ClientErrorsControllerTests|FullyQualifiedName~AuthAndSystemControllerTests\"", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 825, - "created_at": "2026-04-10T22:59:48.937Z" - }, - { - "id": 52, - "task_id": "T02", - "slice_id": "S05", - "milestone_id": "M011", - "command": "dotnet build JobTrackerApi/JobTrackerApi.csproj", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 7920, - "created_at": "2026-04-10T22:59:48.937Z" - }, - { - "id": 53, - "task_id": "T03", - "slice_id": "S05", - "milestone_id": "M011", - "command": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AttachmentsControllerTests|FullyQualifiedName~BackupControllerTests|FullyQualifiedName~ClientErrorsControllerTests|FullyQualifiedName~AuthAndSystemControllerTests\"", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 825, - "created_at": "2026-04-10T23:00:30.313Z" - }, - { - "id": 54, - "task_id": "T03", - "slice_id": "S05", - "milestone_id": "M011", - "command": "dotnet build JobTrackerApi/JobTrackerApi.csproj", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 7920, - "created_at": "2026-04-10T23:00:30.313Z" - }, - { - "id": 55, - "task_id": "T03", - "slice_id": "S05", - "milestone_id": "M011", - "command": "GET http://localhost:5202/api/export/jobs", - "exit_code": 401, - "verdict": "✅ pass", - "duration_ms": 0, - "created_at": "2026-04-10T23:00:30.313Z" - }, - { - "id": 56, - "task_id": "T03", - "slice_id": "S05", - "milestone_id": "M011", - "command": "POST http://localhost:5202/api/backup/encrypted", - "exit_code": 401, - "verdict": "✅ pass", - "duration_ms": 0, - "created_at": "2026-04-10T23:00:30.313Z" - }, - { - "id": 57, - "task_id": "T03", - "slice_id": "S05", - "milestone_id": "M011", - "command": "GET http://localhost:5202/api/attachments/1", - "exit_code": 401, - "verdict": "✅ pass", - "duration_ms": 0, - "created_at": "2026-04-10T23:00:30.313Z" - }, - { - "id": 58, - "task_id": "T03", - "slice_id": "S05", - "milestone_id": "M011", - "command": "POST http://localhost:5202/api/client-errors", - "exit_code": 401, - "verdict": "✅ observed-constraint", - "duration_ms": 0, - "created_at": "2026-04-10T23:00:30.313Z" - }, - { - "id": 59, - "task_id": "T01", - "slice_id": "S06", - "milestone_id": "M011", - "command": "rg -n \"OLLAMA|SKIP_MODEL_LOAD|/health|/summarize|/extract-text|RunProbeAsync|GetMetricsAsync|LastError|Probe\" tools/summarizer JobTrackerApi/Services JobTrackerApi.Tests -S", - "exit_code": -1, - "verdict": "unknown (coerced from string)", - "duration_ms": 0, - "created_at": "2026-04-10T23:20:01.927Z" - }, - { - "id": 60, - "task_id": "T02", - "slice_id": "S06", - "milestone_id": "M011", - "command": "cd tools/summarizer && ./scripts/bootstrap-and-test.sh test", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 3090, - "created_at": "2026-04-10T23:24:03.785Z" - }, - { - "id": 61, - "task_id": "T02", - "slice_id": "S06", - "milestone_id": "M011", - "command": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AuthAndSystemControllerTests|FullyQualifiedName~ProductionConfigTests|FullyQualifiedName~OwnershipGuardTests\"", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 171, - "created_at": "2026-04-10T23:24:03.785Z" - }, - { - "id": 62, - "task_id": "T02", - "slice_id": "S06", - "milestone_id": "M011", - "command": "dotnet build JobTrackerApi/JobTrackerApi.csproj", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 3680, - "created_at": "2026-04-10T23:24:03.785Z" - }, - { - "id": 63, - "task_id": "T03", - "slice_id": "S06", - "milestone_id": "M011", - "command": "cd tools/summarizer && ./scripts/bootstrap-and-test.sh test", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 3090, - "created_at": "2026-04-10T23:24:23.066Z" - }, - { - "id": 64, - "task_id": "T03", - "slice_id": "S06", - "milestone_id": "M011", - "command": "dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter \"FullyQualifiedName~AuthAndSystemControllerTests|FullyQualifiedName~ProductionConfigTests|FullyQualifiedName~OwnershipGuardTests\"", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 171, - "created_at": "2026-04-10T23:24:23.066Z" - }, - { - "id": 65, - "task_id": "T03", - "slice_id": "S06", - "milestone_id": "M011", - "command": "dotnet build JobTrackerApi/JobTrackerApi.csproj", - "exit_code": 0, - "verdict": "✅ pass", - "duration_ms": 3680, - "created_at": "2026-04-10T23:24:23.066Z" - } - ] -} \ No newline at end of file