docs: consolidate repository guides and preserve audit history

This commit is contained in:
cesnimda
2026-09-11 14:22:41 +02:00
parent 115441a409
commit 682a3028bb
404 changed files with 3402 additions and 2621 deletions
-801
View File
@@ -1,801 +0,0 @@
# JobTracker programme decisions and assumptions
## DEC-001 — Programme source paths
- **Date:** 2026-08-02
- **Decision:** The source files are exactly `docs/todo/work.md` and `docs/todo/ollama.md`; no fallback path search was needed.
- **Reason/evidence:** Both exact files exist and were read completely (922 and 825 lines respectively).
- **Alternatives considered:** `docs/work-programmes/` content-based discovery, required only if names differed.
- **Consequences:** Original references in the master plan use these paths and line ranges.
- **User approval required:** No; factual discovery.
- **Reversible:** Yes, update paths if the sources move.
## DEC-002 — One authoritative plan, compatibility pointer only
- **Date:** 2026-08-02
- **Decision:** `docs/work-programmes/master-work-plan.md` is authoritative. The older requested `docs/plans/post-audit-ux-reliability-program.md` will point to it instead of duplicating the checklist.
- **Reason/evidence:** The current request explicitly requires one authoritative record; `work.md:46-59` requires the older plan path, and `docs/plans/` did not exist.
- **Alternatives considered:** duplicate both full plans; rejected because they would drift.
- **Consequences:** Legacy references remain valid without a second status source.
- **User approval required:** No; directly reconciles both instructions.
- **Reversible:** Yes.
## DEC-003 — Canonical origin precedes identity recovery
- **Date:** 2026-08-02
- **Decision:** SEC-001/SEC-002 precede Microsoft legacy relinking and email change/recovery.
- **Reason/evidence:** JT-002 security URLs currently fall back to request Host; recovery built first could send attacker-controlled links.
- **Alternatives considered:** follow the suggested identity-first order; rejected as unsafe.
- **Consequences:** SEC-001 is the first implementation package.
- **User approval required:** No; safer dependency ordering was requested.
- **Reversible:** No practical reason to reverse.
## DEC-004 — Scoped production authority
- **Date:** 2026-08-02
- **Decision:** Production mutation authority applies only to the production-local-AI inventory/benchmark/install/configuration/rollout described in `ollama.md`, and only after its backup/rollback gates. Other production deployment still requires explicit instruction.
- **Reason/evidence:** `work.md:15` prohibits production deploy; `ollama.md:3` and the current request authorize scoped AI work.
- **Alternatives considered:** treat either instruction as globally overriding the other; rejected as over-broad.
- **Consequences:** repository work continues; PROD items remain blocked without documented access.
- **User approval required:** No; this is the narrow intersection of explicit instructions.
- **Reversible:** Yes with new authority.
## DEC-005 — ADR-004 is superseded for the scoped local-first programme
- **Date:** 2026-08-02
- **Decision:** Implement one central local-first routing policy with controlled external fallback, while retaining existing models/configuration for rollback.
- **Reason/evidence:** ADR-004 and current architecture choose one deployment provider; `ollama.md:398-451,574-608` explicitly requires ordered routing/fallback and the user asked to execute it.
- **Alternatives considered:** keep one provider and ignore the new programme; scatter fallback in callers; both rejected.
- **Consequences:** ADR-004 must later be superseded/updated in its own cohesive package. No provider change occurs before privacy/entitlement/queue controls.
- **User approval required:** Already supplied by the programme request.
- **Reversible:** Yes; old provider path/config remains for rollback.
## DEC-006 — Free means no AI in the target policy
- **Date:** 2026-08-02
- **Decision:** The target external plan model is Free (no AI, core non-AI tracking) and Pro (defined AI capabilities). Preserve existing user data and internal `Premium` compatibility while migrating behavior.
- **Reason/evidence:** `work.md:636-719` is explicit; current roadmap/code instead permits limited Free AI and uses `Premium`.
- **Alternatives considered:** retain existing limited Free AI; rejected because it contradicts the new programme.
- **Consequences:** POL-001 requires a deliberate server-side policy/compatibility package; copy changes cannot precede enforcement.
- **User approval required:** Already supplied.
- **Reversible:** Product policy is reversible later; data migration should remain additive.
## DEC-007 — One durable operation foundation
- **Date:** 2026-08-02
- **Decision:** Strategy Snapshot, CV processing and other long AI work share OPS-001/AI-001; feature packages supply handlers/results/UI only.
- **Reason/evidence:** Both programmes specify the same state, idempotency, retry, notification and restart requirements and explicitly warn against duplicate implementations.
- **Alternatives considered:** separate CV and Strategy queues; rejected as duplicated infrastructure and inconsistent behavior.
- **Consequences:** AI-003/AI-004 depend on the common foundation.
- **User approval required:** No; explicitly requested.
- **Reversible:** The model can be extended; duplicate queues should not be introduced.
## DEC-008 — New-table ownership is unresolved until provider-safe design
- **Date:** 2026-08-02
- **Decision:** Do not create operation/deletion tables until their package selects one schema owner and proves fresh/upgrade SQLite and MariaDB behavior.
- **Reason/evidence:** `docs/infrastructure/database-ownership.md` mandates reconciler DDL for current cross-provider tables, while audit JT-019 identifies dual schema ownership as a risk and the Phase 0 identity design prefers a real EF migration.
- **Alternatives considered:** silently follow either document; rejected because the conflict is material.
- **Consequences:** no migration is created in SEC-001; OPS-001 records the eventual choice and rollback evidence.
- **User approval required:** No now; ask only if the evidence leaves materially different safe choices.
- **Reversible:** Yes before schema deployment.
## DEC-009 — Production access is currently blocked, not guessed
- **Date:** 2026-08-02
- **Decision:** Mark production inventory/benchmark/rollout blocked while continuing safe repository work.
- **Reason/evidence:** existing backup/verification docs say this environment has no route or production credentials; only CI secret names and `/opt/job-tracker/app` are documented.
- **Alternatives considered:** guess SSH host/user or scan; explicitly prohibited.
- **Consequences:** PROD-001/003/004 and REL-001 cannot be `DONE`; sanitized templates/harnesses can still be built.
- **User approval required:** No.
- **Reversible:** Immediately when documented access is provided.
## DEC-010 — One canonical origin replaces provider callback overrides
- **Date:** 2026-08-02
- **Decision:** `App:PublicBaseUrl` now builds Gmail and Microsoft Graph callback URLs; the old provider-specific redirect variables are removed from Compose and the example environment.
- **Reason/evidence:** Independent callback origins contradicted SEC-001's single-origin trust boundary and allowed configuration drift. Provider registrations must use the documented paths under the canonical origin.
- **Alternatives considered:** Accept overrides only after equality validation; rejected because it preserves duplicate configuration with no supported separate-origin use case.
- **Consequences:** Operators with legacy redirect variables must register/use `<APP_PUBLIC_BASE_URL>/api/gmail/oauth/callback` and `/api/microsoft-graph/oauth/callback`. Existing variables no longer affect the application.
- **User approval required:** No; this implements the approved canonical-origin prerequisite.
- **Reversible:** Yes by restoring validated overrides, but only if a real separate-origin requirement is established.
## DEC-011 — Dedicated one-hop nginx trust network
- **Date:** 2026-08-02
- **Decision:** Nginx and backend share a dedicated internal `WEB_PROXY_SUBNET`; forwarded headers are accepted for one hop only from that configured CIDR. Production Compose has no host-bound app or Ollama ports; development port bindings require `docker-compose.dev.yml` explicitly.
- **Reason/evidence:** Docker's dynamic/default/shared networks cannot safely identify nginx as the trusted hop, and the old auto-loaded override disabled proxy trust while exposing ports.
- **Alternatives considered:** clear all known proxy collections; trust the external shared network; assign a single static container IP. The first two trust too much, while a dedicated CIDR tolerates container replacement without pinning one container address.
- **Consequences:** production must inventory Docker networks and set a non-overlapping `WEB_PROXY_SUBNET` before deploy. External Traefik still requires separate operator verification.
- **User approval required:** No; this is the scoped security prerequisite.
- **Reversible:** Yes via version rollback and previous environment, but rollback must not reopen host ports unintentionally.
## DEC-012 — Nginx Host is derived, not separately configured
- **Date:** 2026-08-02
- **Decision:** The frontend container derives nginx's exact server name at startup from `APP_PUBLIC_BASE_URL`; no second host setting exists. Unknown Hosts receive nginx 444 except `/health`.
- **Reason/evidence:** Hard-coding the current domain or introducing an independent nginx-host variable would violate the single canonical-origin contract and create drift.
- **Alternatives considered:** application-only Host rejection; separate `APP_EXTERNAL_HOST`; operator-only Traefik filtering. Each leaves one repository boundary weak or duplicates authority.
- **Consequences:** the frontend container fails fast on malformed/unsupported origins; IPv6 host literals are currently outside the deploy preflight contract.
- **User approval required:** No.
- **Reversible:** Yes; broaden only with matching parser, nginx and deployment tests.
## DEC-013 — Microsoft sign-in has one tenant-qualified trust path
- **Date:** 2026-08-02
- **Decision:** Microsoft ID tokens are accepted only by the exchange/link validator, which requires the configured account mode plus exact GUID `tid`/`oid` and issuer agreement. The raw Microsoft bearer scheme is removed. Email-like claims are metadata, not verified ownership.
- **Reason/evidence:** The UI already exchanges Microsoft tokens for local sessions; maintaining a second issuer-disabled API bearer path duplicated and weakened the trust decision. `oid` alone is tenant-scoped.
- **Alternatives considered:** harden both bearer and exchange paths; retain `common` implicitly in Production; use `sub` or email as identity. Each adds duplicated policy or preserves the audited ambiguity.
- **Consequences:** undocumented raw-token API clients stop working; Production must choose a tenant mode. Legacy identity ownership remains unresolved until SEC-004 and Microsoft must not be enabled there first.
- **User approval required:** No; this is the validated P0-1A contract.
- **Reversible:** The account mode is configurable; raw bearer support should return only with a documented requirement and the same policy/tests.
## DEC-014 — Split email ownership from session revocation
- **Date:** 2026-08-02
- **Decision:** Split the original SEC-005 into SEC-005A (session/recovery revocation) and SEC-005B (registration/pending email plus migration/UI).
- **Reason/evidence:** The revocation work uses the existing schema and is independently testable/rollbackable; pending email requires a coordinated database and frontend contract. Keeping both under one active item violated the requested small-package cycle.
- **Alternatives considered:** keep one broad item; rejected because its status could not accurately distinguish verified security behavior from an unstarted migration/UI flow.
- **Consequences:** SEC-004 depends on both children. Original source/audit references remain on each, so no requirement was lost.
- **User approval required:** No; this is tracking granularity within approved scope.
- **Reversible:** Yes by presenting them as one release, but their verification remains separate.
## DEC-015 — Pending email uses Identity tokens and one provider-aware EF migration
- **Date:** 2026-08-02
- **Decision:** Store only the proposed address and request time, rotate the Identity security stamp for each replacement request, and use the built-in change-email token. Add both fields through one EF migration whose SQLite and MariaDB column types are explicit; do not duplicate these Identity columns in the startup reconciler.
- **Reason/evidence:** The active email must remain authoritative until proof. Identity already binds tokens to user, new email, purpose and security stamp; stamp rotation makes the latest request win without another token table. Dry-run SQL showed SQLite-scaffolded types were unsafe for MariaDB until the migration branched by provider. Audit/schema decision P0-4B assigns these Identity fields to EF.
- **Alternatives considered:** immediately replace `Email`; store plaintext confirmation tokens; add a request table/nonce abstraction; add the same columns to startup reconciliation. The first two are unsafe, and the latter two add duplicate state/ownership without a demonstrated need.
- **Consequences:** deployment must run `20260802205800_AddPendingEmailChange` before the new API version. SQLite uses `TEXT`; MariaDB uses `varchar(320)` and `datetime(6)`. Rolling-version and MariaDB execution still require verification.
- **User approval required:** No; this is the smallest implementation of the approved ownership contract.
- **Reversible:** Yes via the migration `Down` before data relies on pending requests; active email data is unchanged.
## DEC-016 — SEC-005B remains short of local browser verification
- **Date:** 2026-08-02
- **Decision:** Mark SEC-005B `IMPLEMENTED — NOT VERIFIED`, not `VERIFIED LOCALLY` or `DONE`.
- **Reason/evidence:** all automated suites and an isolated API runtime pass, but the in-app browser denied localhost because its administrator policy check could not be verified. SMTP and MariaDB execution are also unavailable.
- **Alternatives considered:** infer browser behavior from component tests or claim the existing Docker UI; rejected because the running containers are old images and the programme forbids inferred test claims.
- **Consequences:** SEC-004 repository work can proceed, but SEC-005B retains explicit browser/provider/production acceptance checks.
- **User approval required:** No; truthful status accounting is required.
- **Reversible:** Yes immediately after the blocked checks pass.
## DEC-017 — Canonical Microsoft ownership never backfills legacy evidence
- **Date:** 2026-08-02
- **Decision:** Add nullable bounded `MicrosoftTenantId`/`MicrosoftObjectId` with one unique composite index, use only that pair for ownership, and leave every legacy subject/email row null-canonical until explicit dual-proof relinking.
- **Reason/evidence:** `oid` is tenant-scoped, legacy subjects may be `oid` or `sub`, and provider email is mutable metadata. A disposable migration rehearsal preserved duplicate legacy values and enforced unique proven pairs. The recovery ceremony requires both a purpose-bound token delivered to the confirmed app email and a fresh Microsoft token for the same pair.
- **Alternatives considered:** backfill from `common`, legacy subject, email or next-seen token; retain email fallback; add a general identity-provider framework. Each would merge unproven identities or add unrelated abstraction.
- **Consequences:** ambiguous/unconfirmed legacy users require operator assistance; production needs a counts-only inventory and relink window. Link/unlink revoke sessions, and passwordless unlink is refused until a safe provider reauthentication path exists.
- **User approval required:** No; this implements the approved JT-001 safety contract.
- **Reversible:** Additive schema is reversible before canonical data is relied upon. Application rollback must never restore email auto-linking.
## DEC-018 — Keep MariaDB date work in SQL and bound SQLite fallback by owner/job
- **Date:** 2026-08-02
- **Decision:** Branch only at the affected `DateTimeOffset` query roots: SQLite materializes owner/job-scoped rows before ordering or range comparison; MariaDB keeps server-side ordering, filtering, aggregation and pagination.
- **Reason/evidence:** the real SQLite provider throws before execution, while Pomelo generates the required SQL. Changing all timestamp storage would require a risky migration and editing response endpoints individually would leave CV cleanup/reprocess siblings broken. Real-provider tests and fresh HTTP rehearsal pass.
- **Alternatives considered:** global timestamp conversion/schema rewrite; always materialize on every provider; modify historical migrations; catch-and-retry translation exceptions. These add migration risk, production performance cost or hide separate JT-019 ownership drift.
- **Consequences:** SQLite work is bounded by tenant/job and current entitlement limits but still happens in memory; production MariaDB behavior is unchanged. If per-user AI history grows materially, a later UTC scalar column/index migration may be measured and designed.
- **User approval required:** No; this is the smallest root-cause correction within CORE-001.
- **Reversible:** Yes; revert the provider branches and relational test, with no data rollback.
## DEC-019 — Keep the editable interview board canonical and move the generated brief
- **Date:** 2026-08-02
- **Decision:** Keep `GET /interview-prep` for the durable editable `InterviewPrepItem` board, move the distinct cached/generated `InterviewPrepNote` response to `GET /interview-prep/brief`, and delete the unused flat timeline action so the grouped/filterable timeline remains canonical.
- **Reason/evidence:** both interview representations have active repository UI callers and incompatible DTOs, while only the newer timeline has a caller. Overloading by query parameter or deleting one live feature would preserve ambiguity or break behavior. Reflection and isolated HTTP tests now show one action per method/path with owner 200, other user 404 and anonymous 401.
- **Alternatives considered:** delete either interview feature; retain the old path with a discriminator; rename the editable board; merge DTOs. Each creates unnecessary compatibility state or breaks the current workspace contract.
- **Consequences:** undocumented direct consumers of the generated brief must adopt `/brief`; repository clients/docs are updated. No data or schema changes.
- **User approval required:** No; this is the smallest cohesive repair of confirmed JT-004.
- **Reversible:** Yes by restoring the route/client and flat action, but rollback also restores the exploitable availability defect and is not recommended.
## DEC-020 — Attachment final paths are the durable operation identity
- **Date:** 2026-08-02
- **Decision:** Represent recoverable attachment mutations as `<final>.uploading` and `<final>.deleting`; do not add a separate JSON journal, schema or periodic worker. Reconcile once after database initialization and preserve unknown plain files.
- **Reason/evidence:** every final path is already generated, unique and root-bounded. The suffix plus row existence encodes every required recovery decision, and real-SQLite failure injection proves upload promotion, delete restore/purge and idempotent restart behavior. A second journal would introduce dual durable state and another crash-ordering problem.
- **Alternatives considered:** JSON operation journal; database operation table; object-store abstraction; periodic/multi-replica reconciler. None is required for the current single-filesystem deployment and each adds coordination or migration cost.
- **Consequences:** recovery retries on safe restart rather than a timer; persistent failures remain observable until restart/operator action. Multi-replica or high-volume deployments must first add a lease and measured periodic reconciliation. SEC-009 must reuse these root/quarantine rules.
- **User approval required:** No; this is the smallest implementation of the approved JT-010 recovery contract.
- **Reversible:** Yes after draining/reviewing all suffix markers. No schema rollback exists; unknown plain orphans must never be guessed away.
## DEC-021 — Worker enumeration bypasses filters once, then re-enters owner scope
- **Date:** 2026-08-02
- **Decision:** `BackgroundTenantRunner` may ignore tenant filters only to enumerate non-empty job owners. Each owner is processed sequentially in a new scope whose live `CurrentUserService` restores all normal query filters. Rules, reminders, daily export and enrichment receive separate default-false switches.
- **Reason/evidence:** hosted scopes have no HTTP identity and therefore returned zero rows. Leaving services on while fixing that root cause would unexpectedly start status mutations, file exports, email and AI calls. Real-SQLite tests prove owner filtering, per-owner settings, failure isolation and fake-only side effects.
- **Alternatives considered:** unfiltered queries throughout each worker; a privileged DbContext; automatic activation under legacy settings; a distributed scheduler. These enlarge the trust boundary, create rollout risk or solve unmeasured scale.
- **Consequences:** workers remain inert until explicitly enabled after OPS/POL prerequisites. Execution is sequential/single-instance; leasing and durable operations are the next package. Existing backup, probe and CV-run handling remain unchanged.
- **User approval required:** No; this is the requested safe foundation and does not activate production work.
- **Reversible:** Yes by keeping switches false and reverting the runner/service changes. Already-generated exports or user-visible mutations require separate reviewed rollback.
## DEC-022 — Durable operations are EF-owned and store references, not payloads
- **Date:** 2026-08-02
- **Decision:** Split OPS-001 into independently reviewable OPS-001A state/schema, OPS-001B notifications and OPS-001C APIs/UI. `UserOperations` is owned only by a provider-conditional EF migration; the startup reconciler does not create or alter it. The row stores bounded policy/subject references and no generic raw payload.
- **Reason/evidence:** a manually branched migration produces correct SQLite and bounded MariaDB DDL, avoiding the repository's old SQLite-type failure while reducing JT-019 dual ownership. CV/Strategy inputs already have durable domain IDs; copying private text into a queue row is unnecessary. Concurrent SQLite tests prove one idempotent row and one lease winner.
- **Alternatives considered:** extend `CvExtractionRun`; reconciler plus no-op migration; generic JSON payload; external queue/Redis; one large schema/UI package. These duplicate feature state, preserve dual ownership, increase private-data copies/infrastructure, or prevent small rollbackable review.
- **Consequences:** feature producers reference domain rows and must re-check entitlement/privacy before work. Notifications/API/UI follow without changing the operation identity. MariaDB execution remains a deployment gate.
- **User approval required:** No; this resolves the recorded DEC-008 conflict using repository evidence and the requested smallest reliable design.
- **Reversible:** Yes before consumers rely on rows; stop producers/drain rows before `Down`. Additive table may remain during application rollback.
## DEC-023 — One current generic notification is atomic with terminal operation state
- **Date:** 2026-08-02
- **Decision:** Store one owner-scoped `UserNotification` per current terminal operation outcome, committed in the same relational transaction. Use generic bounded text, no email delivery and no private operation/failure content. A manual retry removes the prior notification so the next terminal outcome can replace it.
- **Reason/evidence:** a unique nullable operation foreign key supplies database idempotency; a forced notification-write failure proves the terminal update rolls back. This reuses OPS-001A rather than introducing an outbox framework or a second queue.
- **Alternatives considered:** transient frontend notifications; email outbox; multiple immutable notifications per retry attempt; generic event bus. Transient state fails restart recovery, email is not authorized, and the latter two add delivery/history machinery not required by either programme.
- **Consequences:** OPS-001C can expose stable read/dismiss state without creating another notification model. Historical retry-attempt notifications are not retained; operation attempt/failure fields remain the technical state. MariaDB execution remains a deployment gate.
- **User approval required:** No; this implements the approved persistent-notification prerequisite without external side effects.
- **Reversible:** Yes before API/UI consumers rely on it. Older application versions tolerate the additive table; schema `Down` deletes notification state.
## DEC-024 — Operation APIs expose state, not worker internals
- **Date:** 2026-08-02
- **Decision:** Expose authenticated owner list/detail/cancel/retry and notification list/count/read/dismiss APIs, but no generic operation-create endpoint. Return only bounded user-facing state; omit idempotency keys, lease tokens, provider/model fields, raw failure text and result references. Use bounded polling rather than realtime infrastructure.
- **Reason/evidence:** feature producers must enforce entitlement/privacy and durable subject references before admission, so a generic create endpoint would bypass later policy. Existing Axios, MUI and browser events cover the UI without a dependency. Two-user HTTP checks prove copied identifiers return 404.
- **Alternatives considered:** WebSockets/SignalR; a generic JSON task API; exposing the full entity; merging reminders and operation notifications. These add infrastructure, unsafe authority or misleading counts without a current need.
- **Consequences:** AI-003/004 own feature admission and result navigation. The operation page polls every 15 seconds while mounted; the shell polls unread count every 60 seconds. Realtime delivery can be reconsidered only if measured UX/load requires it.
- **User approval required:** No; this is the smallest implementation of both programmes' stable status and notification contract.
- **Reversible:** Yes. UI/API removal leaves durable operation/notification data intact; application rollback can retain additive tables.
## DEC-025 — Public Pro policy uses live roles while retaining legacy billing identifiers
- **Date:** 2026-08-02
- **Decision:** Expose only `free` and `pro`; make Free AI entitlement and limits zero; authorize explicit AI actions with one live database-role policy; recheck queued/background work at execution; preserve the internal `Premium` Identity role and `Stripe:PricePremium` configuration key for billing/data compatibility.
- **Reason/evidence:** the new programme supersedes the old limited-Free-AI model. Claim-only authorization would let an already-issued session retain AI after downgrade, while renaming the persisted role/config now adds migration and rollback risk without changing user-visible behavior. Core job create/detail and deterministic enrichment were traced separately because they must still work when their optional model call is skipped.
- **Alternatives considered:** static `RequireRole`; guards copied into every controller; renaming the Identity role/config; wrapping every summarizer call in a new provider abstraction; blocking whole controllers. These leave stale-claim/worker bypasses, add scattered checks, create needless migration risk, pre-empt POL-002/AI-002, or hide existing non-AI data.
- **Consequences:** explicit locked APIs return stable `pro_required`; Admin maps to Pro; UI receives `ai`/`proThemes`; existing AI history and non-AI editing remain accessible. Usage accounting outside AI Workspace remains incomplete and blocks provider rollout/full POL-001 verification. PRODUCT-001 still owns removal of invented landing-page price/tier claims.
- **User approval required:** No; the programme explicitly requires Free=no-AI and centralized enforcement.
- **Reversible:** Yes as one repository-only policy/UI change with no schema update. Workers must remain off while rolling back to avoid restoring an entitlement bypass.
## DEC-026 — Classify synthetic AI workloads before finalizing privacy routing
- **Date:** 2026-08-02
- **Decision:** Advance PROD-002 immediately after POL-001 and before POL-002, even though the recommended list placed the general privacy policy first.
- **Reason/evidence:** POL-002 explicitly depends on task type, privacy class, payload shape, latency and fallback suitability. POL-001 produced the reachable-call inventory, and PROD-002 can safely classify it and create synthetic fixtures without production access or provider calls. Writing policy first would either duplicate this inventory or invent categories without fixtures.
- **Alternatives considered:** keep POL-002 next and revise it later; perform PROD-001 hardware inventory first; start queue implementation. The first creates churn, while the latter two are blocked by production access or need the privacy contract.
- **Consequences:** PROD-002 is the sole in-progress item; POL-002 follows with evidence-backed classes. No production/provider action is introduced.
- **User approval required:** No; the user directed dependency-aware reordering and conflict recording.
- **Reversible:** Yes; documentation/fixtures can be revised before routing code depends on them.
## DEC-027 — External AI needs two administrator/user gates and defaults to local
- **Date:** 2026-08-03
- **Decision:** Persist `AiEnabled` and `ExternalAiProcessingAllowed` per user; preserve AI-enabled behaviour for existing accounts, default external consent to false, and permit an external `/cv/*` request only when backend and sidecar administrator gates, a supported configured provider, live Pro entitlement, AI-enabled preference and explicit user consent all agree. Background calls without an authenticated request fail safe to local.
- **Reason/evidence:** the existing global sidecar `AI_PROVIDER` could route full CV data externally without a user decision. One backend policy plus a sidecar permission header closes that execution path without deleting rollback provider configuration or inventing the final AI-002 router.
- **Alternatives considered:** remove Gemini/Groq; rely on UI/local storage consent; trust one environment flag; refactor every AI interface now; silently use external when configured. These either break rollback compatibility, are bypassable, or prematurely duplicate AI-001/002.
- **Consequences:** disabling AI is enforced from live database state; external processing is off by default and needs deliberate two-sided configuration. True local-first fallback triggers, operation policy snapshots, provider/reason persistence, payload minimization and cost controls remain explicit AI-001/002 gates, so POL-002 is not overstated as fully verified.
- **User approval required:** No; this is the smallest safe implementation of the programme's explicit privacy controls and preserves provider configurations.
- **Reversible:** Yes. Application rollback should leave the additive preference columns in place; turning both administrator gates off immediately restores local-only processing without data loss.
## DEC-028 — Durable AI work extends UserOperations with a default-off typed worker
- **Date:** 2026-08-03
- **Decision:** Reuse OPS-001A/B/C for every long AI task. Add one task-handler worker and one admission service; expose no generic create endpoint. Admission stores only a domain subject reference and policy snapshot, enforces Pro/privacy/capacity/idempotency/deadline, and returns the existing stable status URL. Start at one worker and keep it disabled until real handlers and rollout checks pass.
- **Reason/evidence:** the existing operation store already supplies persistent states, atomic claims, leases, retries, cancellation, restart recovery, notifications and owner APIs/UI. A second CV/Strategy queue or Redis would duplicate proven state. A generic create API would let callers bypass task-specific ownership and payload validation.
- **Alternatives considered:** separate in-memory channel; Redis/Hangfire; one worker per feature; synchronous provider calls; generic public task creation; unbounded hosted-service parallelism. These lose restart state, duplicate infrastructure, expand authority or fail the congestion requirement.
- **Consequences:** AI-003/004 only add typed handlers/producers. Current capacity serialization is process-local for the documented single-backend deployment; database reservation is required before multiple backend replicas. Provider/model semaphores and circuit/provenance remain AI-002 responsibilities.
- **User approval required:** No; this follows both programmes' explicit instruction to reuse the smallest reliable existing infrastructure.
- **Reversible:** Yes. Keep the worker switch false, remove admission/worker registrations, and retain operation rows/API history. No new schema was added in this slice.
## DEC-029 — One sequential sidecar router owns local-first fallback
- **Date:** 2026-08-09
- **Decision:** Keep provider execution behind the existing sidecar boundary, make Ollama the default primary, permit at most one sequential external fallback, and carry the backend's rechecked privacy/task decision through the AI-001 execution scope. Reuse existing operation provider/model/progress fields for provenance; add no queue/provider schema or dependency.
- **Reason/evidence:** every generative `/cv/*` path already converges on one `_provider_generate` family, while deterministic tasks and `/summarize` must remain local. Sidecar fake-transport tests prove local success, consent/config/task/cost denials, schema fallback, circuit behavior, external failure and no parallel duplicate call. Backend tests prove policy propagation and success/failure provenance.
- **Alternatives considered:** provider selection in each controller; browser-selected providers; a second provider abstraction in .NET; simultaneous local/cloud racing; a new circuit/attempt table; increasing synchronous timeouts. These scatter policy, expose authority, duplicate the established boundary, risk double charge/output, add unneeded schema, or mask the queued-operation root cause.
- **Consequences:** `AI_ROUTING_MODE` supports `local_only`, `local_first` and explicitly gated `external_only`; invalid values fail closed. New durable task IDs stay local until allowlisted. The current circuit is process-local and one AI worker is the effective single-model concurrency limit. Per-request prompt size limits external spend/exposure, but complete monthly cross-feature accounting and model selection remain rollout gates.
- **User approval required:** No; this directly implements the approved local-first programme without invoking a provider or production service.
- **Reversible:** Yes. Set `EXTERNAL_AI_ENABLED=false` or `AI_ROUTING_MODE=local_only`; the older `AI_PROVIDER`/model configuration is retained. Existing nullable operation fields and AI history remain readable.
## DEC-030 — Strategy generation is one typed operation and one structured inference
- **Date:** 2026-08-09
- **Decision:** Make Focus Plan GET cache-only and move generation to typed `strategy.snapshot` work on the shared queue. Encode only job and at most four attachment IDs, reuse active operations, and replace four sequential model calls with one bounded JSON response whose complete shape is validated before the unique cache row is updated. Keep Strategy absent from the external fallback allowlist.
- **Reason/evidence:** the traced button/GET path owned four serial model calls and had no restart/cancel/retry identity. AI-001/002 already provide every needed state/policy/routing primitive. One structured inference reduces timeout exposure and makes publication atomic without a second queue or schema.
- **Alternatives considered:** increase HTTP/proxy timeouts; keep GET as a command; add a Strategy queue/table; store prompts/private text in operation payload; four model calls inside the worker; race external/local providers. These retain the root failure, duplicate infrastructure/private data/output, or violate established routing safety.
- **Consequences:** existing cached `FocusPlanDto` remains readable, while generation now returns 202 and the UI resumes by operation ID/context. Retry can overwrite only the same unique result row. Worker activation and real-model tuning remain rollout gates; operation records provide deduplication/provenance, not full monthly billing accounting.
- **User approval required:** No; both programmes explicitly require durable Strategy work and consolidation with AI-001/002.
- **Reversible:** Yes. Keep the worker off and revert `a621226`; no schema/dependency changed. Cancel or drain queued `strategy.snapshot` rows before removing the handler.
## DEC-031 — CV extraction runs own review data; UserOperations own execution
- **Date:** 2026-08-09
- **Decision:** Keep `CvExtractionRun` as the artifact/result/review record and make one typed `cv.process` operation reference its numeric ID. Replace the unbounded channel and separate hosted service with AI-001 admission/leases/retries/cancellation/notifications. Upload returns 202 after persistence; no raw CV payload is duplicated into operation state.
- **Reason/evidence:** the complete trace showed useful persistent review state but two competing execution mechanisms: synchronous upload and a process-local channel. Reusing both existing models gives restart-safe orchestration without a new queue/schema and preserves the mandatory accept/discard gate.
- **Alternatives considered:** raise proxy timeouts; keep synchronous upload; add a second CV queue/table; store raw CVs/prompts in operation payloads; replace extraction runs with generic operations. These retain the 504/lost-wakeup path, duplicate infrastructure/private data, or discard domain review/version history.
- **Consequences:** all four long CV actions share one default-off worker and operation UI. Parser-version/process isolation remains SEC-006/007; browser/model/MariaDB/production gates remain before rollout. Existing clients must accept the upload endpoint's 202 operation response.
- **User approval required:** No; both programmes explicitly require one durable operation foundation and preservation of human review.
- **Reversible:** Yes. Keep the worker off, revert `c3c5af8`, and retain operation/extraction rows. Cancel or drain `cv.process` rows before removing the handler.
## DEC-032 — Provider account management and signed-out authentication share logic, not presentation
- **Date:** 2026-08-09
- **Decision:** Add an explicit sign-in presentation to the existing Google and Microsoft components. It reuses provider token exchange and two-factor handling but skips signed-in account discovery, link/unlink controls and linking copy. Keep the full account presentation unchanged for authenticated profile/settings surfaces.
- **Reason/evidence:** the programme requires a conventional single sign-in card without implying account linking. Duplicating provider callback code would risk divergence from the hardened tenant/linking path, while rendering account-state panels on login creates the prohibited clutter and misleading relationship.
- **Alternatives considered:** retain tabs; create duplicate login-only provider clients; hide copy with CSS; combine provider exchange and account linking. These preserve the UX defect, duplicate sensitive logic, hide rather than remove inaccessible state, or weaken the identity boundary.
- **Consequences:** login presentation becomes simpler without changing backend identity ownership. Provider account management remains available only in its existing authenticated surfaces. Real-provider and production verification are still required.
- **User approval required:** No; this is the smallest implementation of the explicit UX-001 requirement and preserves the prior security contracts.
- **Reversible:** Yes. Reverting UX-001 restores the tabbed presentation; no provider link, session, schema or configuration data changes.
## DEC-033 — Theme preference is application-owned; MUI only renders resolved mode
- **Date:** 2026-08-09
- **Decision:** Keep the existing `themeMode:<user>` storage keys as the only preference store, resolve user → anonymous → System explicitly, and feed the resulting Light/Dark mode into MUI without MUI storage or provider/router remounts. Use a dedicated auth-user event and read-only cross-tab storage subscription; apply the same resolution in a pre-paint Next script.
- **Reason/evidence:** the trace proved the general auth event fired before the new user key was stored and no later theme event occurred. Refresh therefore changed namespace and appeared random. Provider keys/router dependencies also discarded state, while MUI's default storage could become a second source of truth.
- **Alternatives considered:** keep provider keys; emit another general auth event; store one global preference; let MUI own `mui-mode`; add a server profile migration. These retain remounts/request loops, lose user isolation, create competing precedence or add unnecessary backend scope.
- **Consequences:** users without a saved scoped choice inherit the explicit anonymous choice, otherwise the documented default is System. Explicit Light/Dark ignore OS changes. The pre-paint script must remain behaviorally aligned with `themePrefs`; tests cover both.
- **User approval required:** No; this directly implements the approved UX-002 contract without schema, dependency or production changes.
- **Reversible:** Yes. Revert `11734ee`; existing preference values remain unchanged and readable.
## DEC-034 — Important-term quality stays deterministic and on demand
- **Date:** 2026-08-09
- **Decision:** Harden the existing shared `JobCvMatchService` rather than add a model or stored analysis version. Clean HTML/chrome at the matcher boundary, use bilingual/general filler categories, rank bounded phrase runs before singleton terms, and extend the canonical skill vocabulary for punctuation-sensitive technologies. Rename UI output to “important terms.”
- **Reason/evidence:** the complete path proved the reported Norwegian words came from English-only deterministic token ranking. Imported descriptions may be clean, but manual descriptions/notes reach the same matcher. Results are recomputed and the generated learning sync already preserves user decisions, so a cache/schema migration would solve a nonexistent storage problem.
- **Alternatives considered:** hardcode the five examples; call Ollama for keywords; add a result table/version column; filter only in the UI; silently delete learning items. These are incomplete, less reliable, duplicate state, leave API consumers dirty or discard user history.
- **Consequences:** term changes appear on the next request; obsolete pending generated learning items auto-complete under existing behavior while done/dismissed decisions remain. The curated vocabulary remains intentionally bounded and test-driven.
- **User approval required:** No; this is the deterministic-first implementation explicitly required by both programmes.
- **Reversible:** Yes. Revert `da1aa8b`; no schema, cache or provider state changes.
## DEC-035 — Career Workspace derives actions from existing domain state
- **Date:** 2026-08-09
- **Decision:** Render one action-oriented overview from the completeness and durable import state already owned by `CareerProfilePage`; load only the separate recent-CV list. Link actions to the existing profile/import anchors, builder and saved-job workflow, and retain extraction Apply/Discard as the sole merge gate.
- **Reason/evidence:** duplicating `/career/profile` and `/profile-cv/runs` requests in the route wrapper would create competing loading/polling state. The existing page already has correct durable run, review and version data, while CV variants are a separate bounded list. The old wrapper duplicated headings and explanation without exposing next actions.
- **Alternatives considered:** a second workspace data loader; moving all profile state into a new global store; direct one-click job-CV creation without a job choice; rewriting the profile editor. These add inconsistent state, premature architecture or bypass the established job/application boundary.
- **Consequences:** the page presents concise state-aware navigation without changing API, profile persistence or imported-data approval semantics. Browser and production verification remain before `DONE`; deeper builder/job interaction remains in CAREER-002/JOBS-001.
- **User approval required:** No; this is the smallest cohesive implementation of the approved programme requirement.
- **Reversible:** Yes. Revert the CAREER-001 implementation commit; no data/configuration migration exists.
## DEC-036 — CV autosave uses revision ordering and explicit discard warnings
- **Date:** 2026-08-09
- **Decision:** Keep the existing debounced variant autosave API, but track the latest name/settings and a monotonic client revision. Only the newest request may update the visible save state. Offer an explicit retry and block route/unload navigation until the user chooses to discard pending or failed edits.
- **Reason/evidence:** the editor already has versioned server saves, but a route change during the debounce and overlapping responses could lose work or falsely display `Saved`. Blank names were ignored by the backend while remaining blank in the UI. The existing API is sufficient; the defect is client coordination and feedback.
- **Alternatives considered:** replace autosave with a manual-only form; add a queue/schema; extend timeouts; silently flush during unload; refactor variant versioning. These regress the working interaction, add unrelated infrastructure or cannot reliably complete during page teardown.
- **Consequences:** users receive a native discard decision for in-app navigation and browser-standard warning on unload. A blank name stays unsaved until corrected. The deeper builder redesign and browser checks remain CAREER-002 work.
- **User approval required:** No; this is a reversible data-loss safeguard within the approved programme.
- **Reversible:** Yes. Revert `b58cc19`; server variants and version history require no migration.
## DEC-037 — Variant entries override profile facts; they do not delete them
- **Date:** 2026-08-09
- **Decision:** Keep profile-backed entries read-only at the ownership boundary: a CV variant may reorder, hide or override them, but cannot delete the Career Profile item. Provide full add/edit/reorder/confirmed-delete behavior only for variant-owned custom entries using the existing `string[]` model.
- **Reason/evidence:** the architecture and resolver define variants as lenses over stable Career Profile item keys. Treating Delete in the Builder as profile deletion would violate separation and risk changing every CV. The custom-section model already owns document-specific content and needs no schema change.
- **Alternatives considered:** delete profile entries from the Builder; clone profile entries into variants; add a second custom-entry table; keep the opaque one-item-per-line textarea. These violate ownership, duplicate data/infrastructure or fail the approved interaction requirement.
- **Consequences:** original entries use Hide rather than Delete; custom entries receive explicit destructive confirmation. Existing render/public/PDF/version data remains compatible.
- **User approval required:** No; this preserves the programme's explicit Career Profile separation and existing data.
- **Reversible:** Yes. Revert `a5b74e0` and `2043349`; settings JSON remains backward-compatible.
## DEC-038 — Job email has one canonical route with filtered compatibility redirects
- **Date:** 2026-08-09
- **Decision:** Make `/correspondence` the canonical Job email hub and represent recruitment review as `?view=review`. Redirect the legacy `/correspondence/review` route into that filtered view while reusing the existing review component and APIs.
- **Reason/evidence:** the two pages were separate navigation surfaces over related linked/review workflows, while the job workspace already reuses the same correspondence domain. Route composition removes duplicate information architecture without prematurely rewriting mature Gmail decision logic.
- **Alternatives considered:** delete the old route immediately; copy review cards into the inbox; merge backend endpoints before a provider capability trace; keep both pages indefinitely. These break bookmarks, duplicate behavior, expand risk or preserve the approved UX defect.
- **Consequences:** existing links remain compatible and there is one user-facing hub. Provider-neutral thread state and explicit draft/send remain separate MAIL-001 increments and are not implied by this routing change.
- **User approval required:** No; the product decision to consolidate and preserve route compatibility is explicit.
- **Reversible:** Yes. Revert `6008b4a`; no persisted correspondence/provider data changes.
## DEC-039 — Connected email capabilities are explicit and provider-neutral
- **Date:** 2026-08-09
- **Decision:** Consume the existing `IEmailProviderRegistry` through one owner-scoped read controller and expose provider status/search/thread/plain-text detail without changing scopes or adding a second provider abstraction. Report send as unavailable until a provider has an implemented, tested send contract.
- **Reason/evidence:** Gmail, Microsoft Graph and IMAP already implement the same read seam, but only Gmail consumed it and the hub could not state actual capabilities. Their current OAuth/service contracts are read-only. The separate follow-up action uses application SMTP and cannot honestly represent connected-provider send.
- **Alternatives considered:** copy Gmail controller behavior for each provider; claim SMTP as provider send; add speculative provider-action interfaces; broaden OAuth scopes before designing re-consent/audit/uncertain delivery; return untrusted HTML through the shared detail API. These duplicate logic, mislead users, widen privileges prematurely or create an unsafe rendering path.
- **Consequences:** the hub can truthfully identify connected Gmail/Outlook/IMAP accounts and a shared API exists for later UI composition. Provider-native state changes and send remain unavailable and must be introduced with capability flags, re-consent and delivery-state tests. Message detail returns plain text only.
- **User approval required:** No; this is a local, fake-tested repository increment within MAIL-001 and invokes no provider or email service.
- **Reversible:** Yes. Revert `536d403`; no schema, dependency, OAuth scope, provider token or persisted message changes.
## DEC-040 — Message detail is plain text with an explicit saved-copy fallback
- **Date:** 2026-08-09
- **Decision:** Fetch live detail through the neutral provider endpoint when a row has a provider/message ID, but render only plain text. If provider access fails, warn and fetch the tenant-scoped persisted correspondence copy. Manual entries use the persisted path directly.
- **Reason/evidence:** imported correspondence remains useful after token expiry or provider outage, while returning/rendering provider HTML would widen the untrusted-content surface. The existing correspondence row is already the local job record and needs no duplicate store.
- **Alternatives considered:** render provider HTML; fail the whole view when reauthorization is required; silently fall back; include full message bodies in every inbox list response; add a message-cache table. These increase XSS/payload/state risk, hide stale provenance or duplicate existing data.
- **Consequences:** users can distinguish live and saved content availability, malformed legacy metadata cannot break detail, and late responses cannot populate another selected row. The saved copy may be stale and is labeled as such when live access fails.
- **User approval required:** No; this is local read-only behavior with synthetic/mocked tests and no provider invocation.
- **Reversible:** Yes. Revert `a20775c`; no schema, dependency, provider scope or persisted data changes.
## DEC-041 — Delivery idempotency is a content-free tenant ledger
- **Date:** 2026-08-09
- **Decision:** Persist one `EmailSendAttempt` per owner/client UUID with a SHA-256 payload hash and strict pending → sending → sent/failed/uncertain transitions. Terminal and uncertain attempts cannot restart; a new explicit review must use a new request UUID. Store no recipient, subject or body.
- **Reason/evidence:** SMTP/provider calls cannot be made atomic with a database commit. A crash or transport interruption after acceptance is inherently uncertain, so retrying the same attempt can duplicate email. The ledger must exist before any provider scope/button is enabled and must preserve only the metadata needed for deduplication/audit.
- **Alternatives considered:** rely on disabled buttons; store request IDs on correspondence; reuse AI operations; retry on every timeout; save full draft content in the audit row; add a message outbox that assumes provider idempotency. These do not prevent concurrent/direct-API duplicates, conflate domains, risk duplicate sends/private-data retention or promise atomicity the providers do not offer.
- **Consequences:** API integration can reserve a unique attempt before external I/O and fail closed on pending/uncertain records. A provider success followed by database failure remains reconcilable rather than blindly retried. User export must include the non-sensitive metadata; job/account deletion cascades it. The migration is additive and EF-owned with explicit SQLite/MariaDB types.
- **User approval required:** No; the approved MAIL-001 programme requires idempotent/uncertain send safety. This increment is inert and used no provider.
- **Reversible:** Yes. Before reverting `653f011`, stop send admission, reconcile/drain attempts and downgrade `20260809195014_AddEmailSendAttempts`. No production migration has been applied.
## DEC-042 — Send permission is explicit; transport uncertainty fails closed
- **Date:** 2026-08-09
- **Decision:** Request Gmail send and Graph Mail.Send scopes on new/reconnected accounts, derive `CanSend` from the stored granted scope, keep IMAP read-only, and expose one neutral delivery contract. Treat an HTTP rejection as known failed-before-delivery, but any network interruption/cancellation as uncertain.
- **Reason/evidence:** read access cannot authorize send, and existing tokens must not be assumed upgraded. Provider HTTP acceptance is outside the database transaction; after a broken transport the application cannot safely prove that no message was accepted. Mocked HTTP tests prove scope, payload and classification without contacting providers.
- **Alternatives considered:** reuse application SMTP; silently expand existing token authority; mark all errors failed/retryable; expose provider error bodies; add SMTP credentials to the IMAP connection; enable a route before adapter tests. These misrepresent identity, risk duplicate delivery/data exposure, widen secret storage or invert the required dependency order.
- **Consequences:** existing connections show read-only until explicit reconnect consent. Gmail supports its provider thread ID; Graph currently sends a new message and does not claim reply-thread semantics. The later API must reserve the ledger before calling either adapter and surface uncertain state for manual reconciliation.
- **User approval required:** No; this repository-side programme requirement used fake transports only. Real account consent/send still requires an explicitly authorized synthetic provider account.
- **Reversible:** Yes. Revert `e9937ac` to stop requesting/using send permission. Already granted provider permission is managed by the provider/user connection and is not automatically revoked by a code rollback.
## DEC-043 — Send admission reserves before delivery and never retries ambiguity
- **Date:** 2026-08-10
- **Decision:** Admit provider delivery only through an authenticated, user-rate-limited route that requires an owned job, explicit confirmation and a canonical client UUID. Reserve and begin the content-free ledger before provider I/O; persist sent correspondence, a content-free job event and the sent state in one local transaction. Return existing sent results but reject every other duplicate, especially uncertain attempts.
- **Reason/evidence:** provider acceptance cannot share the database transaction. Owner-scoped SQLite tests prove malformed, unconfirmed and cross-tenant requests do not reserve or send; duplicate, rejected and interrupted attempts call the fake provider at most once. Canonical UUID formatting closes a simple deduplication bypass.
- **Alternatives considered:** call the provider before reserving; retry timeouts; rely on a disabled button; store message content in the ledger/event; use legacy application SMTP; mark local persistence failure as sent. These can duplicate delivery, lose audit state, expose content or bypass connected-provider consent.
- **Consequences:** a successful provider call with failed local persistence is intentionally uncertain and requires manual reconciliation. A process stop after admission can leave a `sending` row; a later repository increment must age it into an explicit uncertain/manual-review state without redelivery. Existing read-only connections cannot send until re-consented.
- **User approval required:** No; MAIL-001 explicitly authorizes local implementation and fake verification. Real provider consent/send remains gated.
- **Reversible:** Yes. Disable admission/UI, then revert `123fc55`. No schema rollback is needed for this route-only increment.
## DEC-044 — Replies remain bound to their provider thread
- **Date:** 2026-08-10
- **Decision:** Offer an editable reply only for a message whose exact provider connection currently has send consent. Keep provider/from identity and thread read-only while allowing recipient, subject and body edits; require the shared app-owned confirmation before POST. Treat client/network ambiguity as uncertain and expose no retry action.
- **Reason/evidence:** a Gmail thread ID has no valid Graph meaning, and silently switching providers would misrepresent reply semantics. Mocked UI tests prove cancel invokes no API, the visible reviewed fields match the request, the UUID is stable for the attempt, and uncertainty disables resend.
- **Alternatives considered:** allow cross-provider thread switching; use browser `confirm`; hide recipient/thread; auto-retry network failures; enable manual correspondence through application SMTP; add a new draft framework before proving the flow. These weaken provenance, accessibility, consent or duplicate safety.
- **Consequences:** read-only connections require explicit reconnect consent. This increment supports replies, not durable refresh recovery or a new-message composer; those remain tracked rather than being implied. Basic email remains ungated by Pro and no AI path can send.
- **User approval required:** No; this is synthetic local MAIL-001 implementation. Real provider/email verification remains gated.
- **Reversible:** Yes. Revert `449faeb`; API, ledger, scopes and saved correspondence remain unchanged.
## DEC-045 — Interrupted sends age to terminal safety states without retry
- **Date:** 2026-08-10
- **Decision:** Run a post-readiness safety scan every five minutes. After 15 minutes, pending attempts become failed-before-provider and sending attempts become uncertain. Use conditional cross-owner updates in one local transaction and create one content-free owner notification; never invoke or enqueue provider delivery.
- **Reason/evidence:** a process can stop between ledger reservation, provider acceptance and local completion. The application can prove an old pending attempt never reached the provider, but cannot prove the same for sending. Real-SQLite two-owner tests prove classification, fresh-row preservation, notification isolation and repeat-run idempotency.
- **Alternatives considered:** retry on restart; leave rows indefinitely; mark every row failed; mark every row sent; scan provider mailboxes; add provider-specific reconciliation. These risk duplicate email, misleading state, excessive provider authority or unresolved user state.
- **Consequences:** interrupted sending requires manual Sent-folder review. Multiple replicas may select the same candidate, but the status predicate allows only one update/notification. The five-minute query intentionally avoids a new migration in this increment; large-ledger performance remains a measured rollout check.
- **User approval required:** No; this is local safety recovery with no provider/external call.
- **Reversible:** Yes. Revert `ee5ef7e`; existing terminal states and notifications remain truthful and should not be rewritten.
## DEC-046 — Resolve the audit gate with supported patches, then the smallest router major
- **Date:** 2026-08-10
- **Decision:** Update transitive js-yaml/nanoid within their existing major lines and move React Router to 7.18.2 because the later two React Router advisories have no patched 6.x release. Adapt only the obsolete RouterProvider flag and Jest encoding globals; keep the existing route model.
- **Reason/evidence:** 6.30.4 fixed the originally reported protocol-relative redirect but remained affected by two newer advisories, so `npm audit` still failed. React 19 and Node 22 satisfy v7 requirements. Audit, focused data-router tests, all frontend tests and the production build pass on 7.18.2.
- **Alternatives considered:** `npm audit fix --force` without review; suppress moderate findings; stay on 6.30.4; redesign routing. These either hide the resolution, leave the deployment gate red or expand scope unnecessarily.
- **Consequences:** the frontend now requires Node 20 or later through React Router v7. CI/live deployment and navigation smoke remain required before `DONE`.
- **User approval required:** Yes; the user explicitly requested repair of the reported live deployment audit failure and continuation.
- **Reversible:** Technically yes by reverting `b55a592`, but that restores known advisories and the failed gate. Prefer a forward compatibility fix if a deployment-only issue appears.
## DEC-047 — Follow-up drafting stays; direct SMTP delivery is retired
- **Date:** 2026-08-10
- **Decision:** Preserve grounded follow-up draft generation/edit/copy, replace its direct send control with a link to canonical Job email, and make the legacy send endpoint return 410 without an application SMTP dependency. Keep scheduled reminder notification email unchanged.
- **Reason/evidence:** the legacy action bypassed provider identity/re-consent, explicit final confirmation, the idempotency ledger and uncertain-delivery handling already implemented in MAIL-001. Drafting itself is safe and useful. Focused worker tests prove the separate scheduled reminder path remains registered and functional with fakes.
- **Alternatives considered:** route the legacy body directly into `/api/email/send`; retain both send paths; remove drafting/reminders; silently redirect the POST. These either bypass the reviewed confirmation context, preserve conflicting authority, remove unrelated functionality or misrepresent a state-changing API response.
- **Consequences:** users copy the draft or open Job email and review provider/from/recipient/content before sending. Old API callers receive an explicit terminal response and must migrate; no correspondence/follow-up date is falsely recorded as sent.
- **User approval required:** No; this is the smallest safe completion of the already active MAIL-001 programme and sends no email.
- **Reversible:** Yes by reverting `8fe3903`, but that reintroduces the unsafe SMTP bypass. Prefer forward migration of any remaining caller to `/api/email/send` with explicit confirmation.
## DEC-048 — Export delivery state, not content fingerprints
- **Date:** 2026-08-10
- **Decision:** Add one explicit send-attempt export record to both existing owner export surfaces. Include provider/request/status/provider-message/failure/timestamps, but omit the internal payload hash. Verify the existing job foreign-key cascade with real SQLite; leave complete identity/account deletion to SEC-009.
- **Reason/evidence:** users need readable delivery history, while the ledger intentionally stores no recipient, subject or body. The payload hash exists only for deduplication/conflict checks and is not meaningful portable data. Two-owner daily files and decrypted backup tests prove coverage/isolation; the cascade test proves one hard job deletion does not affect the other tenant.
- **Alternatives considered:** export the entity directly; include the payload hash; omit attempts; claim UserManager identity deletion is complete; add a second cleanup routine. These expose internal correlation data, lose audit history, overstate the current account lifecycle or duplicate the database cascade.
- **Consequences:** export schemas gain an additive `EmailSendAttempts` collection. Complete account/database/file/token/backup deletion remains an explicit SEC-009 release item rather than hidden in MAIL-001.
- **User approval required:** No; this is the approved MAIL-001 export/cascade requirement using synthetic local data only.
- **Reversible:** Yes. Revert `aff34cc`; no schema or stored data changes. Existing export files remain valid historical artifacts under their configured retention.
## DEC-049 — Keep Career Workspace smoke behavior-based
- **Date:** 2026-08-10
- **Decision:** Replace the removed marketing-copy assertion in the Career Workspace Playwright smoke with an assertion that the visible `Open CV Builder` action targets `/career/builder`.
- **Reason/evidence:** Gitea run 608 proves the dependency audit now passes and the page heading renders, but CI fails on copy that the current unit test explicitly expects to be absent. The route action is always rendered and tests the supported user journey. Full local Playwright passes 4/4.
- **Alternatives considered:** restore obsolete copy solely for the test; weaken the smoke to heading-only; increase timeout. These would contradict current product behavior, reduce journey coverage or hide no timing defect.
- **Consequences:** wording can evolve without breaking CI while the page-to-builder navigation contract remains protected. Replacement CI and live deployment verification are still required.
- **User approval required:** No; this is a focused correction to a stale test exposed by the user-authorized deployment repair.
- **Reversible:** Yes. Revert the focused smoke change, though run 608 would fail again until the expectation or page behavior is reconciled.
## DEC-050 — Share correspondence context without broadening mailbox authority
- **Date:** 2026-08-10
- **Decision:** Give the shared correspondence component a minimal company/recruiter/role context contract and supply it from both the job dialog and Application Workspace. Do not add provider mutation buttons or scopes in this increment.
- **Reason/evidence:** the workspace reused the correct correspondence domain but passed `null as any`, so its suggestion surface lost job context. Gmail currently grants readonly plus send, Graph grants read plus send, and IMAP is read-only; none has a safe read/write mutation seam. Focused tests and the full frontend/build pass.
- **Alternatives considered:** duplicate the correspondence implementation; expand the workspace aggregate with a full job object; silently request Gmail modify/Graph read-write scopes; display unsupported actions. These add coupling, authority or misleading behavior.
- **Consequences:** both application surfaces now generate the same optional contextual searches without automatic linking. Provider category mutations remain explicit remaining work that requires a separately reviewed scope/re-consent design.
- **User approval required:** No; this is a local shared-view correction inside active MAIL-001 and invokes no provider.
- **Reversible:** Yes. Revert the context-prop changes and test; stored data, scopes and provider grants are unchanged.
## DEC-051 — Unlink the app relationship, never the provider copy
- **Date:** 2026-08-10
- **Decision:** Expose confirmed Gmail unlink in the canonical hub through the existing owner-scoped unlink endpoint. Label the data effect explicitly and show no equivalent action for providers without a supported link domain.
- **Reason/evidence:** unlink already existed in the per-job component but not the hub, creating inconsistent copies of the same workflow. The endpoint removes imported JobTracker correspondence for the owned job and returns the thread to review; it does not call Gmail deletion. UI confirmation and real-SQLite two-user tests pass.
- **Alternatives considered:** delete provider mail; implement a duplicate endpoint; show disabled Outlook/IMAP actions; remove unlink from the per-job view. These broaden authority, duplicate behavior, mislead users or regress a supported workflow.
- **Consequences:** a thread can be reconsidered in recruitment review after unlink. Imported JobTracker copies are removed only after confirmation, while the mailbox remains untouched. Relink/move stays on the existing per-job management surface for now.
- **User approval required:** No; this is local implementation of the approved MAIL-001 unlink workflow and uses only mocked/synthetic data.
- **Reversible:** Yes. Revert `1dabbeb`; the endpoint and per-job unlink remain available, with no schema/provider grant change.
## DEC-052 — Report only capabilities the connection can use
- **Date:** 2026-08-10
- **Decision:** Distinguish disconnected, connected-without-read, read-only/re-consent, read-plus-send and provider-status failure in the canonical hub. Keep saved correspondence visible during provider-status failure.
- **Reason/evidence:** the previous chip appended `Read only` even to disconnected providers and silently removed all provider context when the status request failed. Focused failure/capability tests, full frontend and build pass.
- **Alternatives considered:** keep the ambiguous chip; hide the entire inbox on provider failure; infer archive/read-write support from provider name. These misstate capability, reduce failure isolation or invent authority not present in installed scopes.
- **Consequences:** users can distinguish connection state from send consent and know saved data remains available. Mailbox organization actions remain absent until a reviewed provider contract and re-consent path exist.
- **User approval required:** No; this corrects local state communication and invokes no provider.
- **Reversible:** Yes. Revert `f9e641c`; no API, schema, scope or stored state changes.
## DEC-053 — Basic email stays Free; private drafts need a tenant-owned store
- **Date:** 2026-08-10
- **Decision:** Pin basic provider email to local authenticated access without a Pro policy. Do not persist private email content in browser storage or reuse recruiter-message/job draft fields for provider email recovery.
- **Reason/evidence:** the current send route and hub have no entitlement gate, while all send safety checks remain. Repository inventory found no email-draft entity that owns provider/thread/recipient/subject/body/idempotency together; existing recruiter drafts are a different product domain.
- **Alternatives considered:** add Pro to basic email; use localStorage/sessionStorage; overload `RecruiterMessageDraft`; claim in-memory replies are durable. These violate the product decision, expose content on shared browsers, conflate domains or overstate recovery.
- **Consequences:** Free users retain non-AI email. Durable/new-message drafts require an owner-filtered database model, bounded API, export/deletion coverage and rollback migration as a separate cohesive increment. Future AI assistance remains Pro/privacy gated.
- **User approval required:** No; this is repository-local verification and a safety boundary inside approved MAIL-001.
- **Reversible:** The regression test can be reverted with `7f41cb2`; no runtime/schema state changed. Adding a Pro gate later would be an explicit product change.
## DEC-054 — Make draft persistence inert before exposing private content
- **Date:** 2026-08-10
- **Decision:** Add an owner-filtered, revisioned `EmailDraft` tied by cascade to an owned job, with explicit SQLite/MariaDB migration paths, but expose no draft API or UI until readable export and complete deletion implications are covered.
- **Reason/evidence:** refresh recovery needs server-side ownership, while browser storage and recruiter-message fields violate privacy/domain boundaries. A real-SQLite two-owner test proves isolation and job cascade; backend 625/625, model-current and dual-provider up/down SQL checks pass.
- **Alternatives considered:** ship schema/API/UI together; use local storage; reuse correspondence or recruiter drafts; persist provider tokens or idempotency attempts in the draft. These make a larger private-data boundary harder to review, expose shared-browser content or conflate delivery state with editable content.
- **Consequences:** migration `20260810075206_AddEmailDrafts` is additive and reversible, but production must not expose drafts until export/deletion coverage and a bounded owner/job-validating API are verified. A full blank SQLite migration rehearsal remains blocked by the pre-existing JT-019 historical-chain defect, not this migration.
- **User approval required:** No; MAIL-001 authorizes repository-local durable drafts and no production migration/provider action occurred.
- **Reversible:** Downgrade the migration before reverting `14b396a`. No reachable application behavior exists in this increment.
## DEC-055 — Export drafts through existing owner-scoped backup boundaries
- **Date:** 2026-08-10
- **Decision:** Include complete readable email drafts in the authenticated encrypted backup and per-owner daily export, using an explicit DTO and the owning job ID set. Do not add a separate export route.
- **Reason/evidence:** private draft data must be portable before it is user-reachable. Existing exports already own tenant isolation and readable user data; focused two-owner tests prove the on-demand payload and hashed daily files contain only their owner's draft.
- **Alternatives considered:** omit bodies; export only metadata; add a draft-only download; delay export until account deletion. These produce an incomplete user export, duplicate authorization or expose a reachable data category without portability.
- **Consequences:** daily JSON exports now contain draft bodies under the same filesystem-at-rest protections and retention policy as correspondence. SEC-009 must explicitly delete live drafts and define backup/export retention; no export schema version was broken because the collection is additive.
- **User approval required:** No; this is a repository-local privacy prerequisite using synthetic data and existing export authority.
- **Reversible:** Revert `2fa4e38`; stored drafts remain unchanged, but draft UI/API must not ship without another readable export path.
## DEC-056 — Keep draft saves incomplete, bounded and provider-inert
- **Date:** 2026-08-10
- **Decision:** Expose local-authenticated draft CRUD under `/api/email/drafts`. Require an owned job and registered provider at creation, allow incomplete recipient/subject/body for autosave, keep provider/thread/job immutable, and require a matching revision for updates/deletes.
- **Reason/evidence:** refresh recovery must preserve work before send fields are complete, while provider/thread provenance must not silently change. Atomic revision predicates prevent last-write-wins loss; explicit owner clauses plus global filters deny foreign job and direct draft IDs in real SQLite tests.
- **Alternatives considered:** require send-valid content on every save; contact the provider during save; allow provider/thread changes; use unconditional updates; hide foreign rows only in UI. These break autosave/offline recovery, add side effects, weaken provenance, lose concurrent edits or fail authorization.
- **Consequences:** saving never sends or checks connection state. Send still uses the separate explicit-confirmed/idempotent boundary and revalidates all fields/provider consent. UI must surface revision conflicts and treat bodies as untrusted plain text.
- **User approval required:** No; this is approved local MAIL-001 work with synthetic data and no provider/production action.
- **Reversible:** Revert `a9bb22e`; the inert/exported schema remains for a later UI. Existing stored drafts are unaffected.
## DEC-057 — Persist one delivery identity for the life of a draft
- **Date:** 2026-08-10
- **Decision:** Assign every server draft one canonical client-request UUID, preserve it across edits/refresh, include it in readable exports, and allow listing all current-owner drafts for recovery.
- **Reason/evidence:** the send ledger keys idempotency by client request ID. Generating a new UUID after refresh would let identical restored content reserve a second delivery attempt. Focused tests prove creation, edit preservation, export and tenant-filtered all-draft listing.
- **Alternatives considered:** generate UUID only at send; regenerate after every edit/refresh; derive it from content; store it only in browser memory. These permit duplicate delivery after refresh, conflate content changes with attempt identity or lose the safety state on navigation.
- **Consequences:** one draft maps to one send attempt identity until the user explicitly starts a new attempt after a confirmed failure. A sent/restored draft can only replay the existing ledger result. The additive migration defaults only during the same pre-exposure rollout; no draft UI existed before it.
- **User approval required:** No; this closes a local safety dependency before UI exposure and sends no email.
- **Reversible:** Downgrade `20260810080858_AddEmailDraftClientRequestId` before reverting `80b5532`. Do not deploy draft UI without an equivalent persisted idempotency identity.
## DEC-058 — Make draft persistence explicit and conflict-visible in the hub
- **Date:** 2026-08-10
- **Decision:** Keep reply editing local until the user chooses Save draft, then adopt the server draft/revision/client-request identity. Offer saved drafts for refresh recovery, require revisioned delete, and surface 409 conflicts without replacing local text.
- **Reason/evidence:** silent autosave introduces navigation/race semantics that are not yet proven. Explicit save is predictable, supports incomplete drafts and preserves the existing final send confirmation. Focused conflict/recovery tests, full frontend and build pass.
- **Alternatives considered:** browser storage; silent debounced autosave; last-write-wins; discard on refresh; auto-send after save. These expose private data, risk lost edits or weaken explicit send consent.
- **Consequences:** unsaved edits are intentionally not refresh-durable; saved replies are. A successful send attempts revisioned draft cleanup, while the persisted client ID keeps any surviving copy duplicate-safe. Compose-new-message and durable failed-attempt identity rotation remain separate increments.
- **User approval required:** No; this is approved local MAIL-001 UI work with mocked APIs and no email/provider action.
- **Reversible:** Revert `d3d2b67`; stored drafts remain available through the API/export but no UI consumes them.
## DEC-059 — Rotate draft delivery identity only from a terminal failure
- **Date:** 2026-08-10
- **Decision:** Add an explicit revisioned `new-attempt` action that issues a new draft client-request UUID only when the authenticated owner's matching ledger row is `failed`. Wire the existing Prepare new attempt UI to this action for saved drafts.
- **Reason/evidence:** ordinary edits/refresh must preserve idempotency, while a provider-confirmed failure needs a deliberate recovery path. Pending/sending/uncertain/sent states cannot disprove delivery and must not rotate. Real-SQLite owner/stale/status tests and mocked UI pass.
- **Alternatives considered:** let the browser invent UUIDs; rotate on any error; rotate during save; automatically retry failed sends. These lose durable state, can duplicate uncertain/sent mail or weaken explicit approval.
- **Consequences:** definitively failed drafts can be reviewed and retried under a new ledger identity; all ambiguous or successful attempts remain non-retryable. Unsaved local drafts retain the existing explicit local new-attempt behavior because no durable ledger relationship exists yet.
- **User approval required:** No; this is approved safety work with fake/local evidence and no email/provider call.
- **Reversible:** Revert `29de263`; saved failed drafts then have no durable retry rotation and the UI should not offer that action.
## DEC-060 — Reuse the reviewed draft boundary for new messages
- **Date:** 2026-08-10
- **Decision:** Compose new email starts only after selecting an owned recent job and a connected send-capable provider. It creates a local blank, threadless draft and reuses explicit Save draft plus Review and send; read-only/disconnected providers are not choices.
- **Reason/evidence:** new messages need the same ownership, persistence, confirmation, idempotency and failure semantics as replies. The existing owner-filtered job list and provider capability endpoint provide those selection inputs without a second send implementation.
- **Alternatives considered:** free-text job IDs; allow disconnected/read-only providers; bypass persistence; a second composer/send route; infer a provider. These expose authorization errors, promise unavailable delivery or duplicate safety logic.
- **Consequences:** empty-job and missing-send-consent states explain why compose is unavailable. The hub currently lists the 100 most recent owned jobs; older jobs remain accessible from their job workspace and broader searchable selection is follow-up UX, not a hidden authorization bypass.
- **User approval required:** No; local UI work uses mocked APIs and sends no message.
- **Reversible:** Revert `b735963`; reply drafting and all server draft/send boundaries remain.
## DEC-061 — Stop MAIL scope at installed provider authority
- **Date:** 2026-08-10
- **Decision:** Mark MAIL-001 `IMPLEMENTED — NOT VERIFIED` after completing the repository draft/send/link flows. Do not add read/unread, pin/read-later, archive, spam or trash UI because the installed Gmail/Graph/IMAP contracts and granted scopes do not authorize those mutations.
- **Reason/evidence:** Phase 9 says these states apply where supported. Current Gmail adds read/send, Graph adds read/send, and IMAP is read-only; the neutral interface exposes read/detail/send only. Displaying or simulating category actions would be false capability and production-risking scope creep.
- **Alternatives considered:** request modify/read-write scopes silently; mutate only JobTracker copies while labelling them provider actions; add disabled controls; keep MAIL indefinitely in progress. These broaden consent, misrepresent mailbox state, clutter UX or block independent work.
- **Consequences:** real-provider re-consent, provider category design, browser gates, SEC-009 deletion and production verification remain explicit blockers before `DONE`. JOBS-001 becomes the sole in-progress item.
- **User approval required:** No for status correction; any future provider-scope/re-consent rollout needs a separately reviewed package and safe account verification.
- **Reversible:** Yes. MAIL-001 can return to `IN PROGRESS` when provider mutation authority is approved and testable.
## DEC-062 — Expose only verified discovery provenance
- **Date:** 2026-08-10
- **Decision:** Treat NAV as verified because the controller itself fetches the official NAV feed; expose a stable source key/display name, label acquisition as `searched`, stamp the request retrieval time and map a deadline only from NAV's explicit `applicationDue` field. Do not infer work mode or add a one-option source filter.
- **Reason/evidence:** the previous card hardcoded NAV and an update date while the response's source field was unused. Phase 4 requires honest source/type/retrieval/deadline data and explicitly prohibits presenting inference as verification. Focused provenance/import tests and full regressions pass.
- **Alternatives considered:** derive source from listing hostname; infer remote/hybrid from title/location; add a disabled or single-option source filter; label `date_modified` as retrieval time. These would confuse derived and verified data or add controls without a real choice.
- **Consequences:** NAV cards now explain where and how the listing was obtained and preserve attribution into tracking. Missing work mode remains visibly absent rather than guessed; source filtering becomes useful only when a second real provider is implemented.
- **User approval required:** No; this is approved repository work using synthetic fixtures with no external request.
- **Reversible:** Revert `511a9f6`; no schema or stored data changes are involved.
## DEC-063 — Close synthetic JOBS scope before resuming theme order
- **Date:** 2026-08-10
- **Decision:** Move JOBS-001 to `IMPLEMENTED — NOT VERIFIED` after its complete synthetic browser and regression matrix, then make UX-003 the sole in-progress item before JOBS-002.
- **Reason/evidence:** JOBS now has honest provenance/import/result/duplicate behavior plus mocked 375/768/1440, theme, keyboard and long-content evidence. Remaining NAV/production checks are external. Theme corrections precede the job-workspace redesign in the validated programme order and the browser environment is currently available.
- **Alternatives considered:** keep JOBS in progress while repeatedly retrying unavailable live NAV/production; begin the larger JOBS-002 package; skip the outstanding Kanban theme requirement. These would misstate the active work or ignore the established dependency order.
- **Consequences:** UX-003 becomes the only in-progress package. JOBS remains explicitly incomplete rather than `DONE`; its live gates stay visible.
- **User approval required:** No; this is tracking and safe local sequencing within the approved programme.
- **Reversible:** Yes; JOBS can resume when live NAV or production verification becomes available.
## DEC-064 — Use one accessible Kanban move state model
- **Date:** 2026-08-10
- **Decision:** Drive pointer and keyboard moves from the same picked-up job and active-column state. Use live theme variables for every surface/tone, Space to pick up/cancel, Enter or Space on a focused valid column to drop, and update local state only after the API succeeds.
- **Reason/evidence:** the prior static light palette caused confirmed white columns in dark mode, while native drag alone exposed no target or keyboard semantics and rejected moves became unhandled promises. Shared state makes valid/active/invalid/selected/failure states consistent without replacing the precise status menu.
- **Alternatives considered:** isolated dark hex colours; CSS-only drop feedback; optimistic moves with rollback; a second keyboard-only status implementation; replacing drag with a new library. These duplicate policy, risk stale UI or add unnecessary dependency/complexity.
- **Consequences:** pointer and keyboard users receive the same server-confirmed behavior and announcements; custom `Other` remains visibly invalid. The existing exact-stage menu stays available and now has an accessible name.
- **User approval required:** No; this is approved local UX/accessibility work with mocked browser APIs.
- **Reversible:** Revert `fb6f17e` and `4e5ce0c`; no schema/data change is involved.
## DEC-065 — Reuse the workspace as a route-backed list overlay
- **Date:** 2026-08-10
- **Decision:** Keep `/applications/:id` as the full-page fallback, but make a table row's primary Open action render that same workspace in a MUI dialog at `/jobs?workspace={id}&section={section}`. Use a pushed history entry for opening, replace only section changes, Back for UI-originated close and query removal for direct-link close.
- **Reason/evidence:** V-158 confirmed that the previous quick-dialog/full-page chain discarded list context and could not deep-link the embedded presentation. The workspace already composes the authoritative domain components; reusing it avoids a second workspace. MUI supplies the requested focus trap/restoration and full-screen mobile presentation.
- **Alternatives considered:** build a second drawer workspace; keep the quick dialog primary; clone workspace sections into `JobTable`; make the full-page route the only URL. These duplicate ownership or leave the Phase 11 navigation defect.
- **Consequences:** list state remains mounted while the overlay is open, direct workspace/section URLs and Back/Forward work, and a full-page link remains. Complete URL-backed filters and unsaved-edit guards are separate required increments before JOBS-002 can leave progress.
- **User approval required:** No; this is approved repository implementation with mocked local data and no production/provider action.
- **Reversible:** Revert the JOBS-002 overlay commit; no schema, dependency or stored-data change is involved.
## DEC-066 — Make theme preference independent of authentication
- **Date:** 2026-08-15
- **Decision:** Supersede DEC-033's account-scoped storage portion with one canonical browser key, `jobtracker.themeMode`. Migrate the current legacy value once, apply the same resolution in the pre-paint script, and ignore auth-user changes for theme state.
- **Reason/evidence:** the user-visible preference is application chrome, while the auth-derived key is resolved asynchronously and can differ across startup paths. Tying these together retained competing sources of truth and allowed refresh to change scheme. Focused persistence/migration/provider tests and production TypeScript build pass.
- **Alternatives considered:** add route-specific theme effects; keep user/anonymous fallback ordering; let MUI own a second storage key. These preserve the race or reintroduce multiple owners.
- **Consequences:** theme remains stable through login/logout/navigation/refresh on a browser. Former account keys remain readable for one-time migration but are no longer written.
- **User approval required:** No; this corrects the requested persistence defect without schema, dependency or production change.
- **Reversible:** Remove the canonical read/write and restore auth-key subscriptions; legacy values were not deleted.
## DEC-067 — Protect the final administrator at the API boundary
- **Date:** 2026-08-15
- **Decision:** Refuse demotion or deletion of the final Admin in `UsersController`; expose current-user and removal-safety state to the admin UI; require an app-owned destructive confirmation for any demotion and stronger copy for self-demotion/self-deletion. Preserve unrelated roles during an Admin toggle.
- **Reason/evidence:** confirmation alone cannot protect direct API calls or concurrent UI versions. Existing MUI confirm/prompt primitives already match the application and avoid a second dialog dependency. Four controller and three UI tests cover final-admin protection, other-admin demotion, self cancel and self confirm.
- **Alternatives considered:** SweetAlert2; silently forbid every self-demotion; UI-only warning. These duplicate the design system, prevent legitimate handover, or fail to enforce the invariant.
- **Consequences:** the final administrator cannot be removed by supported API paths. A self-demotion remains possible only when another administrator exists and the user explicitly confirms.
- **User approval required:** No; this is requested safety hardening with no production mutation.
- **Reversible:** Revert the controller/UI change; no stored data or schema changed.
## DEC-068 — Make the job workspace a dedicated canonical page
- **Date:** 2026-08-15
- **Decision:** Supersede DEC-065. Route every job/application open action to `/jobs/:id`, keep `/applications/:id` only as a query-preserving compatibility redirect, and remove the legacy dialog/expandable-detail path from the applications table. Preserve the URL-owned list location in route state for the workspace return control.
- **Reason/evidence:** the user explicitly rejected the popup interaction and asked for a scalable application workspace. The existing workspace already composes the authoritative checklist, CV, cover-letter, documents, intelligence, timeline and correspondence domains, so the safe change is canonical routing and richer aggregate data rather than another implementation.
- **Alternatives considered:** retain the route-backed overlay; make a drawer; copy legacy dialog tools into a new page. These conflict with the requested dedicated-page model or duplicate domain ownership.
- **Consequences:** rows/cards and contextual shortcuts open one responsive workspace; internal row controls remain independent; list state survives return; old application links still resolve. The former quick dialog remains in source for rollback until broader regression proves it can be safely deleted.
- **User approval required:** No; explicitly requested.
- **Reversible:** Restore DEC-065 routing/list presentation; no schema or stored data changed.
## DEC-069 — Keep CV ingestion hybrid and isolate status polling
- **Date:** 2026-08-15
- **Decision:** Keep deterministic Python/library text extraction and OCR, pass extracted text through the existing Ollama-first normalization/classification routes, then validate/diff/review in C#. Poll extraction-run status independently from profile content and never replace unsaved editor state during background refresh.
- **Reason/evidence:** the repository already implements the user-proposed Python → Ollama → structured-data flow. Python libraries are the correct boundary for PDF/DOCX/image decoding; Ollama adds value in semantic section recognition, but cannot safely or deterministically replace binary parsing. The observed reset was caused by full-profile polling, not controlled-input behavior.
- **Alternatives considered:** send binary files directly to Ollama; remove deterministic repair/fallback logic; continue full-profile polling; auto-save on every poll. These reduce format coverage, factuality, review safety or user control.
- **Consequences:** all career fields retain unsaved edits while processing status changes. AI reconstruction remains explicit, local-first and review-gated; accuracy work can be benchmarked per model without changing the ingestion boundary.
- **User approval required:** No; this implements the requested behavior within the existing approved local-AI architecture.
- **Reversible:** Rejoin run/profile loads, though that would restore the confirmed data-loss UX defect; no schema/config/data migration changed.
## DEC-070 — Let long CV content paginate instead of shrinking or clipping
- **Date:** 2026-08-15
- **Decision:** Keep normal entries together, classify over-height entries/list items as flowable, wrap every user-controlled text boundary, and use physical A4/Letter metrics in the editor. Put custom and profile-backed sections in one persisted order. Serialize autosaves and save before export/public rendering.
- **Reason/evidence:** the renderer used `overflow:hidden`, fixed `1fr` columns and `break-inside:avoid` on every entry; the editor hardcoded A4 and rounded page counts. Together these could hide partial pages, clip unbroken values or make a block taller than the printable page impossible to paginate. Pathological Chromium/PDF proof produced nine readable pages with zero horizontal offenders without reducing font sizes.
- **Alternatives considered:** globally shrink text; truncate content; make every entry freely splittable; create a free-form canvas editor; maintain a separate custom-section order. These reduce readability, damage content, or duplicate state.
- **Consequences:** preview, public HTML and PDF retain one render path; normal entries avoid awkward splits while large content can cross pages safely. Existing variants remain compatible and acquire shared custom ordering on edit.
- **User approval required:** No; this implements the requested CV rework without schema, dependency or production changes.
- **Reversible:** Revert the renderer/editor/resolver checkpoint; stored settings remain compatible because the existing `Sections` and `custom:<key>` contract is used.
## DEC-071 — Encapsulate application-answer compatibility storage
- **Date:** 2026-08-15
- **Decision:** Keep the existing marked answer block in `JobApplication.Notes` for storage compatibility, but make the backend the owner of extracting, removing and updating it. Expose human notes and application answers as separate workspace fields, and preserve the answer through the general application editor.
- **Reason/evidence:** the retired modal was the only editor that understood the marker. The dedicated page displayed markers as ordinary notes, and saving the general editor could erase the answer. A new schema/table would add migration risk for one text value while the existing representation remains adequate behind a clean boundary.
- **Alternatives considered:** expose the marker format in every editor; create a second application-package table immediately; copy the retired modal wholesale. These leak implementation details, add avoidable migration/state duplication, or restore the popup architecture the user rejected.
- **Consequences:** the dedicated page edits and clears answer/recruiter drafts directly, ordinary notes remain readable, existing rows require no migration, and legacy calls continue to work. A future normalized column/table can migrate behind the same API without another UI change.
- **User approval required:** No; this is compatibility-safe implementation of the requested dedicated workspace.
- **Reversible:** Revert the workspace/API boundary; no schema or stored-data rewrite occurred.
## DEC-072 — Scale the complete public CV, not its document viewport
- **Date:** 2026-08-15
- **Decision:** Keep the anonymous public CV iframe at its physical A4 rendering width, measure the rendered multi-page height, and scale the entire iframe into the available screen width with a sized clipping container.
- **Reason/evidence:** reducing only the iframe width leaves the renderer's fixed 210mm page wider than the embedded viewport, which can create internal clipping even when the outer page no longer scrolls. Chromium now proves both the outer page and embedded document have no horizontal overflow at 375px.
- **Alternatives considered:** hide outer overflow; make server CV pages fluid; shrink typography; duplicate a mobile-only renderer. These conceal clipping, change PDF geometry/readability or fork the authoritative render path.
- **Consequences:** public browser viewing preserves the same A4 layout used by PDF/export while fitting narrow screens; multi-page content reserves its scaled height. PDF output is unchanged.
- **User approval required:** No; this is a requested responsive/accessibility correction without schema, dependency or production change.
- **Reversible:** Revert the `PublicCvPage` frame wrapper; no stored data changed.
## DEC-073 — Keep public plan copy capability-based and checkout-owned
- **Date:** 2026-08-15
- **Decision:** Publish exactly Free and Pro from one frontend capability catalogue, keep `AccountPlans` authoritative for enforcement, and let configured Stripe Checkout own price, interval and any trial terms. Reuse one dismissible contextual Pro notice rather than scattering promotional Alert implementations.
- **Reason/evidence:** the homepage advertised three tiers, fixed prices, Free AI and unlimited AI while the server allowed only Free-without-AI and finite Pro entitlements. Central copy and conditional billing actions remove that contradiction without changing secure server policy.
- **Alternatives considered:** hardcode the current Stripe price; query Stripe anonymously; retain a speculative BYOK tier; show an upgrade button when billing is unavailable; interrupt Free users with repeated dialogs. These leak/change commercial ownership, create false actions or use dark patterns.
- **Consequences:** public claims stay stable across deployment-specific commercial configuration; Free users see honest locked states and retain manual/existing content; checkout terms remain inspectable at the payment boundary.
- **User approval required:** No; this implements the approved master-plan requirement without external billing action.
- **Reversible:** Revert the PRODUCT-001 presentation commit. Server entitlement and stored billing state are unchanged.
## DEC-074 — Attribute generated files through opaque owner roots
- **Date:** 2026-08-15
- **Decision:** Store new CV PDF and daily export files beneath a deterministic SHA-256 owner directory. Use a UUID as the stored PDF filename while preserving the friendly renderer name only for download. Retain support for pruning legacy date-root CV output without moving or assigning old files.
- **Reason/evidence:** SEC-009 cannot safely export or delete shared date/candidate-derived paths because no durable record attributes them to a user. A one-way owner directory is stable, avoids raw identity disclosure in paths and gives inventory/deletion an exact root.
- **Alternatives considered:** guess ownership from candidate/date filenames; add a database row for every ephemeral PDF; embed raw user IDs in paths; move all legacy outputs. These risk cross-user attribution, unnecessary schema, identity leakage or destructive migration.
- **Consequences:** all new generated outputs have an exact owner boundary and collision-resistant storage path. Existing legacy files age out under retention and remain excluded from user deletion unless independently attributed.
- **User approval required:** No; additive storage hardening within the requested account lifecycle, with no existing data mutation.
- **Reversible:** Restore shared date paths for future files. Existing owner-scoped files remain valid retention artifacts and must not be bulk-moved or deleted during rollback.
## DEC-075 — Separate readable portability export from operational backup
- **Date:** 2026-08-15
- **Decision:** Add a recent-authenticated, per-user-rate-limited readable ZIP export alongside—not in place of—the existing application-key-encrypted backup. Build one explicit redacted owner inventory with checksum manifest and reuse it as the future deletion inventory boundary.
- **Reason/evidence:** the encrypted backup is useful for application recovery but unreadable without the deployment key and omits many owned categories. A portability export must be readable, complete, tenant-isolated and secret-free; it must not be mislabeled as backup erasure.
- **Alternatives considered:** expose the encrypted backup as user export; serialize the whole EF graph; reuse the jobs-only export; include provider/token/security rows verbatim. These are unreadable, partial, cycle-prone or credential disclosures.
- **Consequences:** users can download JSON and owned files with independently verifiable SHA-256 checksums. Missing/legacy/external/backup categories are disclosed truthfully. The service becomes the authoritative inventory seam for deletion without coupling export to deletion activation.
- **User approval required:** No; this is the requested repository-side data lifecycle, using synthetic tests and no production data.
- **Reversible:** Remove the endpoint/UI and service. Existing downloaded ZIPs remain user-owned files; no stored schema or data changed.
## DEC-076 — Dark-launch account deletion as a durable staged lifecycle
- **Date:** 2026-08-15
- **Decision:** Replace admin Identity-row deletion with one disabled-by-default coordinator for self-service and admin deletion. Lock the identity immediately, quarantine verified owned files before a transactional explicit row purge, retain retry ledgers, and write a minimal pseudonymous tombstone outside the restored database before completion.
- **Reason/evidence:** Identity-only deletion leaves owned rows, files, provider credentials, sessions and queued work behind. Cascades cannot coordinate filesystem failures or prevent an older backup from resurrecting the user. Real-SQLite tests prove two-owner isolation, safe quarantine failure, idempotence and restored-account replay.
- **Alternatives considered:** broad cascade foreign keys; best-effort controller deletes; delete database rows before files; edit old backups in place; enable immediately after local tests. These lose retry/audit boundaries, risk stranded private files or partial erasure, and overclaim backup/provider guarantees.
- **Consequences:** `AccountLifecycle:DeletionEnabled` remains false until retention, protected tombstone custody, cache/provider handling and a disposable restore rehearsal are approved. Existing sessions are rejected as soon as status becomes pending. Deletion request/file records intentionally survive the user row and the background worker resumes durable requests even while the feature flag blocks new ones.
- **User approval required:** Production activation and retention decisions only. Repository implementation uses synthetic data and remains inert by default.
- **Reversible:** Disable requests (the default), allow in-flight reconciliation to finish, then downgrade the additive migration only after no request remains. Never remove the separate tombstone ledger while backups capable of restoring deleted identities still exist.
## DEC-077 — Make production model benchmarking plan-only by default
- **Date:** 2026-08-15
- **Decision:** Use one standard-library harness over the checked-in synthetic evaluation set. It plans without network access by default, requires `--execute` plus an explicit output for inference, never pulls/deletes models, permits only loopback or an explicitly opted-in literal private IP, and persists hashes/metrics/scores rather than raw inputs/prompts/outputs.
- **Reason/evidence:** The measured production host can run bounded tests, but its network and backup stop conditions are open and model execution is not authorized. A ready harness removes future ad-hoc prompt/data/report handling without silently widening current authority.
- **Alternatives considered:** benchmark immediately over SSH; use production CV/email content; install a benchmark dependency/framework; persist raw responses for later scoring; auto-pull every candidate. These violate current authority/privacy boundaries or add avoidable supply-chain/resource risk.
- **Consequences:** candidate selection remains blocked and no model decision is claimed. Approved runs can compare 4K/8K timing, token rate, JSON/constraint quality and Ollama VRAM metadata reproducibly while host GPU/RAM metrics are captured separately.
- **User approval required:** Yes before any model pull, production inference, or production report execution. No approval is required for plan-only fixture validation.
- **Reversible:** Remove the script/tests/template; no dependency, model, application, production, schema or configuration state changed.
## DEC-078 — Make cache erasure a durable deletion stage
- **Date:** 2026-08-15
- **Decision:** Clear the content-keyed AI-sidecar cache through an authenticated maintenance endpoint inside the existing `purging_files` stage. Treat sidecar failure as retryable and withhold the deletion tombstone/completion acknowledgement until purge succeeds. Mount tombstones on a separate named Compose volume while keeping new deletion requests disabled by default.
- **Reason/evidence:** Expiry after one hour did not satisfy immediate live-cache erasure, and the default tombstone path shared `/data` with the application restore boundary. Backend 6/6 and sidecar 23/23 prove failure/retry and token-protected purge; Compose validation proves the separate repository configuration.
- **Alternatives considered:** wait for TTL; restart the whole sidecar; remove caching; allow deletion to complete with a warning. These retain deleted content temporarily, disrupt unrelated work, regress performance, or falsely acknowledge completion.
- **Consequences:** a rare account deletion globally clears the shared summary cache because entries are not owner-keyed. An unavailable sidecar delays completion but does not restore deleted live rows/files. Production still needs protected volume custody, provider semantics, retention decisions and a disposable restore rehearsal.
- **User approval required:** Production activation and operational mutation only. Repository implementation remains inert behind `ACCOUNT_DELETION_ENABLED=false`.
- **Reversible:** Keep deletion disabled, drain any in-flight request, then revert the endpoint/client and Compose volume mapping. Never remove a deployed tombstone ledger while older restorable backups exist.
## DEC-079 — Synchronize durable CV terminal state centrally
- **Date:** 2026-08-15
- **Decision:** When the shared operation store cancels, terminally fails, recovers an expired deadline, or retries a `cv.process` operation, update its explicitly referenced owner-scoped `CvExtractionRun` in the same transaction boundary.
- **Reason/evidence:** cancellation before worker claim was authoritative in `UserOperation` but left the domain history row queued indefinitely. Focused 17/17 tests cover cancel-before-claim, retry, deadline recovery and existing lease behavior; backend 660/660 passes.
- **Alternatives considered:** reconcile only in the controller; wait for a later worker; add a generic observer framework. These miss recovery paths, preserve stale history, or overbuild a two-task operation system.
- **Consequences:** generic operations remain independent; the one existing persisted subject projection is synchronized through a narrow task/subject check with an explicit owner predicate.
- **User approval required:** No; local consistency fix with no schema, dependency or production change.
- **Reversible:** Revert the store helper/tests. No stored format changed.
## DEC-080 — Separate durable AI usage from user-visible history
- **Date:** 2026-08-15
- **Decision:** Store content-free AI usage in an append-only owner ledger keyed by source. Reserve allowance before AI Workspace work and atomically with Strategy/CV operation creation; replace conservative reservations with actual character-based estimates where complete output metadata exists. Backfill legacy `AiInteraction` counters without copying prompts or generated text.
- **Reason/evidence:** `AiInteraction` is private, user-deletable history and covered only one feature, so deleting history reset plan usage while durable Strategy/CV calls were invisible. Real SQLite tests prove idempotence, tenant isolation, limits, history-independent totals, Strategy finalization, CV reservation, export/deletion handling and legacy backfill; fresh application startup reaches the new migration.
- **Alternatives considered:** retain `AiInteraction` as the meter; add counters to every feature table; estimate only after success; persist prompts/results in a billing record. These couple enforcement to deletable content, scatter one policy across unrelated schemas, permit unbounded concurrent admission, or duplicate private material.
- **Consequences:** monthly usage is stable across history deletion and duplicate durable admission. CV remains conservatively reserved until complete multi-stage telemetry exists. Older synchronous AI actions still require the same admission seam before limits are universal. The process-local reservation gate is sufficient only for the current single-backend topology.
- **User approval required:** Production migration/rollout only. The additive repository migration and synthetic tests do not change production.
- **Reversible:** Disable AI work, downgrade the additive migration only after preserving any required usage evidence, and restore the prior interaction-based display. Existing user content is unchanged.
-217
View File
@@ -1,217 +0,0 @@
# JobTracker master programme progress
Updated: 2026-09-01
## 2026-08-29 audit implementation programme
### Completed
- Restored visibly distinct CV templates by clearing stale structural overrides when a template changes while preserving CV content and deliberate colour, typography, spacing and document settings. The live A4/Letter preview now fits its pane automatically and responds to pane and viewport resizing; manual zoom remains available.
- Consolidated both template choosers into one ATS-first gallery backed by real rendered sample documents. ATS-safe designs are prioritised and explained, decorative sidebar layouts are not falsely labelled ATS-friendly, existing non-ATS selections remain visible, and all designs remain one click away.
- Removed Docker Compose warnings for deliberately optional Microsoft-tenant and production-relative frontend API variables; missing required provider configuration remains enforced by deployment preflight.
- Extracted the CV template/design/layout controls from the monolithic editor into a dedicated typed component while preserving settings, accessibility, localization and persistence behavior. The editor dropped below 1,200 lines and both creation/editor template flows continue sharing the same gallery.
- Diagnosed Gitea Actions run 696 from its authenticated job log: application tests passed, but repeated truncated npm/Playwright downloads were followed by a native exit 139 before browser assertions ran. CI now invokes the lockfile-installed Playwright CLI directly, extends the bounded download timeout, retries only SIGSEGV, and does not redownload the same dependency tree after a transient runner crash.
- Removed the unreachable legacy `JobDetailsDialog`, its duplicate insight tabs/panels, and tests coupled only to that retired surface. Live correspondence and routed-workspace contracts retain focused coverage; the cleanup removed 2,239 lines without changing the active Job Workspace.
- Began the controller-responsibility split by moving job statistics, monthly analytics, tag counts, overview analytics, and tag trends into one dedicated authenticated analytics controller. Existing `api/jobapplications/*` routes and response contracts are unchanged; the shared tag parser replaces a byte-for-byte duplicate local parser.
- Introduced shared application spacing tokens for 16px mobile gutters, 24px tablet/desktop gutters, 32px wide-screen gutters, 24px section rhythm, and 16–20px card padding. `AppShell` and the first redesigned surfaces consume these tokens.
- Reworked the Dashboard around today's prioritized actions. Empty accounts no longer render zero-value metrics, ten empty funnel stages, empty time-in-stage, company, skill, or activity panels. The activity SVG now scales to its container instead of requiring a clipped fixed-width mobile canvas.
- Made onboarding dismissible per account/browser while preserving the checklist until it is completed or dismissed.
- Made Settings use a compact section selector at mobile widths, kept tabs for larger screens, narrowed the content column, and removed its duplicate in-page title. Newly touched settings copy is localized in English and Norwegian Bokmål.
- Replaced the ordinary-user Google configuration variable instruction with a human-readable unavailable state.
- Made Playwright independent of nested `npm` calls and accidental system `.NET` ordering. The E2E launcher resolves `DOTNET_HOST_PATH`, `DOTNET_ROOT`, or the user-local SDK before falling back to PATH and invokes Next/Playwright through the current Node runtime.
- Made application CV matching language-aware. It detects/normalizes the advert and linked-CV languages, uses an already-saved English advert translation for an English CV when available, never starts a translation or mutates either document during a match read, and tells the user when a cross-language comparison is translated or limited.
- Expanded the shared deterministic keyword filter to remove Norwegian recruitment filler including `både`, `del`, `dnbs`, `faglig`, `får`, `god`, `hos`, and `utvikling` instead of presenting those words as CV gaps.
- Split the Career Workspace into Overview, Career Profile, and Import Review destinations. Navigation is URL-backed, uses tabs above mobile widths and a labelled select at 375px, and preserves unsaved editor state while changing sections. Import diagnostics no longer force users to scroll through the normal profile editor.
- Removed the duplicate Career Workspace title inside the Overview content; the shared application shell remains the single page-level heading.
- Forced local Next development and Playwright's non-export server onto webpack after reproducing a Next 16 Turbopack panic (`Next.js package not found`) with the bundled portable Node runtime.
- Localized the Job Workspace recruiter-status suggestion and strategy snapshot end to end, including status labels, action buttons, empty/loading outcomes, queued-operation feedback, and both English and Norwegian Bokmål copy.
- Improved the Job Workspace overview card grid so compact screens use one readable column, tablets use two, and wide screens use five without leaving a cramped orphan card.
- Moved the remaining shared shell navigation labels into the global locale catalogue so sidebar and mobile-navigation controls announce themselves in the selected language.
- Localized the application checklist from stable system keys rather than persisted English text. Categories, generated tasks, descriptions, progress, errors, tooltips, and accessible reorder/delete names now switch between English and Bokmål while user-authored task text remains untouched.
- Localized Cover Letter, CV attachment, application-draft, and Interview Prep failure states and document-language selectors. Interview categories now derive from stable category keys while user-authored questions and answers remain unchanged.
- Reused the checklist's stable system-key translation mapping for the Overview's recommended next action, preventing the English backend seed text from leaking into a Bokmål workspace.
- Corrected the CV Match source caption to interpolate the selected CV name once, localized known match bands, and made intelligence/timeline load failures and dates respect the selected UI locale.
- Finished the Career Profile import/review localization pass: queue, cancel, retry, diff, confidence, destructive, save-state, completeness, link-editor, language-selector, and version-history controls now use the global English/Bokmål catalogue while imported CV values remain unchanged.
- Localized backend-owned career completeness labels through one shared UI mapping so Overview and Profile display Bokmål without changing the API contract or stored profile data.
- Removed the remaining mojibake from the translation catalogue, including account email, Microsoft relink, account-deletion, and learning-path messages.
- Localized the Operations and Notifications activity page controls, dates, progress accessibility labels, empty/error/loading states, cancellation feedback, and retry/dismiss actions in English and Norwegian Bokmål.
- Localized Job Discovery across search, source guidance, retry/empty/loading states, result sorting, vacancy metadata, locale-aware dates, and save/view actions.
- Localized Connected Accounts and the shared email-provider connection controls, validation, OAuth/IMAP feedback, connection states, and accessible form labels without translating provider names or user email addresses.
- Localized the consolidated Job Email hub across provider status, search/filter controls, saved drafts, compose/reply flows, destructive confirmations, send safety states, message metadata, pagination, and failure handling. Persisted subjects, bodies, company names, roles, addresses, and provider names remain unchanged.
- Corrected the Job Email Direction and Link State controls so their visible labels are programmatically associated with the selects, and removed the remaining mojibake separators/loading text from the workflow.
- Localized the complete public landing experience—hero, previews, feature cards, workflow, plan capabilities, CTAs, accessibility text, and footer—while retaining the server-aligned Free/Pro plan structure and commercial-terms safeguards.
- Reused the shared EN/NO language control on the unauthenticated landing page, so language can be changed before sign-in and persists through the existing locale mechanism.
- Finished the remaining active CV Builder format labels: long-document page counts, US Letter naming, and localized long/short date examples now follow the selected UI language.
- Localized the Admin System runtime and CV benchmark diagnostics, including probe/email failures, model/Ollama state, parser findings, benchmark summaries, fallback guidance, and locale-aware timestamps while leaving runtime/provider/file values untouched.
- Consolidated active list/dashboard/Kanban/reminder loading and failure presentation through the shared resource notice. Retry/progress accessibility follows EN/NB, duplicate fallback text is suppressed, and Kanban drag/keyboard announcements are localized.
- Migrated Admin Audit to the shared retryable resource state so an unavailable API is no longer misreported as an empty log. Audit actions/dates, notification-setting feedback, profile-load fallbacks, and CV extraction run notifications now follow EN/NB while stored event/profile content remains unchanged.
- Localized the pre-authentication security-check label and the embedded Turnstile challenge language, so switching to Bokmål no longer leaves the verification widget in English.
- Localized the remaining active helper defaults in the admin SMTP test form, pasted-email importer, and image-crop alternative text.
- Restored the local Python 3.12 toolchain and both virtual environments; the AI sidecar now passes 26/26 tests. Added a Next.js-compatible ESLint 9 flat configuration, strict zero-warning scripts, and a patched CommonJS-compatible `brace-expansion` override. The lint gate passes with zero findings and npm audit reports zero vulnerabilities.
- Pinned the repository to .NET SDK 9.0.317, generated content-hashed transitive NuGet locks for the API and test project, and made CI restores fail on lock drift. Locked restore and backend 719/719 pass; the current NuGet graph has no known vulnerable packages.
- Added standard-library JT-017 supply-chain gates: seven-class no-value tracked-secret scanning, deterministic CycloneDX generation across npm/NuGet/Python locks, synthetic canary tests, CI integration and an explicit advisory/credential-response policy. The current tree is clean and the SBOM contains 964 unique locked components.
- Rebuilt the active developer/operator documentation around the actual Next.js 16/.NET 9 application, replaced CRA and `npm start` guidance, separated normal and Playwright ports, corrected React Router 7 and the SQLite/MariaDB provider matrix, removed the obsolete npm peer override, and verified the documented clean install, lint, test, build and locked-restore commands.
- Began the JT-019 schema-ownership retirement with an executable 49-table ownership partition and transferred the leaf `SystemEmailSettings` table from MariaDB-only startup DDL to an additive provider-aware migration. Fresh SQLite now receives the table; legacy rows are preserved and startup no longer creates it.
- Continued the job-application controller split by moving canonical pipeline metadata, soft delete/restore, follow-up scheduling, and event history into `JobApplicationLifecycleController` without changing routes, authorization, tenant filtering, response shapes, or event behavior. Status mutation remains with core updates until its shared applied-date invariant has a single service owner.
- Added a minimal public `/ready` dependency probe alongside the existing `/health` liveness probe. Nginx exposes both, deployment validation now checks the frontend, API liveness, database readiness, and public auth configuration separately, while detailed dependency metadata remains restricted to Admin/System.
- Hardened deployment replacement semantics: backend/frontend images receive exact commit tags, the prior running core images are retained as the rollback release, and any post-replacement failure automatically restores both previous services while keeping the deployment result failed. Removed the non-actionable blanket `compose pull || true` suppression.
- Completed the next lifecycle-controller boundary by moving status changes and deterministic email status suggestions out of the core controller. The applied-date invariant and cleared-date audit event now live in `JobLifecycleEvents`, so full edits and status-only updates cannot drift.
- Added axe-powered WCAG A/AA browser gates for public entry points and the Dashboard, Jobs, Kanban, Career, Settings, and Admin/System workspaces. Fixed the shared violations they exposed: primary-action contrast, dark-surface language-toggle contrast, account-avatar contrast, sidebar list semantics, job-filter accessible names, and the Google sign-in wrapper role.
- Removed the duplicate frontend production build from CI: Next now builds once before browser smoke, and Playwright serves that exact static export instead of rebuilding it. Ordinary build failures remain terminal and only the runner's explicit exit-139 signature retries.
- Transferred the independent `UserRuleSettings` table from both provider startup paths to its own provider-aware migration; owner-keyed settings survive adoption, downgrade and retry.
- Moved `GmailReviewDecisions` into a provider-aware migration, preserving existing SQLite decisions and closing the previously missing MariaDB table path.
- Moved recovery codes, trusted devices, and revocable user sessions into one provider-aware authentication-support migration; populated legacy rows and indexes survive adoption, downgrade, and retry.
- Moved Gmail, Microsoft Graph, and IMAP connection records into one provider-aware migration; encrypted credentials, sync state, uniqueness constraints, and historical repair paths are preserved.
- Moved CV upload artifacts and extraction runs into a provider-aware migration; existing hashes, structured results, indexes, and nullable artifact retention semantics are preserved.
- Moved job-specific tailored CV drafts into a provider-aware migration; generated and edited content, indexes, and application cascade semantics are preserved.
- Moved persisted interview-preparation and AI workspace notes into one provider-aware migration; reviewed outputs, uniqueness, and application cascades are preserved.
- Moved saved CV variants and append-only revision history into one provider-aware migration; public slugs, settings, indexes, job-link nulling, and history cascades are preserved.
- Moved append-only AI interaction history into a provider-aware migration; generated results, modes, providers, usage counters, indexes, and application cascades are preserved.
- Moved application checklist workflow state into a provider-aware migration; system-key idempotency, manual tasks, ordering, completion state, and application cascades are preserved.
- Moved append-only cover-letter revision history into a provider-aware migration; manual and AI text, source/action metadata, ordering, and application cascades are preserved.
- Moved durable interview-preparation items into a provider-aware migration; user and AI content, answers, practice state, sources, ordering, and application cascades are preserved.
- Moved the complete Career Profile aggregate into a provider-aware migration; canonical and long-tail JSON, version history, all six relational child types, stable ordering, indexes, and cascades are preserved.
- Completed JT-019 by moving all seven ASP.NET Identity tables into provider-aware migration ownership; accounts, credentials, 2FA state, preferences, roles, claims, external logins, tokens, indexes, and cascades are preserved. Startup schema code is now repair-only.
- Added immutable submitted-application packages to the Job Workspace. Each append-only snapshot freezes the career-profile/CV source, rendered CV, cover letter, application material and SHA-256-identified copies of all attachments; history can be reviewed and preserved files downloaded without following mutable live references. Tenant filtering, account export/deletion, storage reconciliation, SQLite/MariaDB migrations and EN/NB UI are included.
- Added a factual Career Evidence Bank with categories, keywords and provenance. Only user-approved records are stored; AI has no write route. Verified evidence is recommended by the existing deterministic application-tailoring flow when its tags or content match the advert, and the feature includes tenant isolation, account lifecycle handling, provider-safe migration ownership and EN/NB UI.
### In progress
- Continue the next safe maintainability and UX backlog package. The newer saved-search/calendar/browser-extension/CV-template feature branch remains separately gated from production.
### Remaining
- Revalidate the remaining production-only blockers; the authorised main-branch deployment completed successfully as version 286 with public liveness and database-readiness checks passing.
- Obtain explicit production approval before merging and deploying the newer scheduled-search, calendar, browser-extension and CV-template feature branch.
- Continue visual hierarchy/responsive polish only when a concrete browser regression or reviewed design target identifies a remaining defect; the current automated desktop/mobile journeys pass.
- Continue maintainability packages, accessibility automation, mobile regression coverage, and the prioritized product-value roadmap after the foundations are stable.
### Blocked
- Production rollout remains dependent on an online Gitea runner matching `ubuntu-latest` and the deployment host becoming healthy.
- Existing external/provider/retention decisions remain listed in `BLOCKERS.md` and are not silently reclassified here.
### Discovered during implementation
- The prior E2E command relied on both `dotnet` and nested `npm` being discoverable through ambient PATH. This reproduced the audit's tooling failure on the current workstation.
- The 375px Settings regression test still expected desktop tabs. It now verifies the responsive section selector instead.
- The previous Dashboard rendered its time-in-stage empty state using the unrelated “No tags yet” copy. Empty analytical panels are now suppressed instead of presenting mismatched messages.
- The Application Analysis match previously ignored both `JobApplication.DescriptionLanguage`/`TranslatedDescription` and the linked CV's language setting. This could under-score an English CV against a Norwegian advert even when import had already stored a usable English translation.
- The bundled portable Node runtime starts Next 16 successfully, but Turbopack repeatedly panicked while emitting `/page`; the webpack development path serves the same route correctly and is now the documented/scripted default.
- Playwright exposed duplicate React keys in the long Code-template CV despite passing its assertions. Read-only inspection of the disposable SQLite database proved every relational Career Profile child had been inserted twice: a newly visible profile could be read and legacy-backfilled before its child replacement completed. Profile snapshot/children/history saves are now atomic, malformed duplicate item/order keys are normalized, and race-affected stored rows resolve once on reads without destructive cleanup.
### Verification
- Gitea run 696 evidence: all backend/frontend unit stages passed; failure isolated to Playwright runtime bootstrap/execution after truncated archives and exit 139. The revised shell branch preserves ordinary non-139 failures and uses the installed CLI without `npx` fallback installation.
- Post-cleanup frontend: ESLint passed with zero warnings, all 60 suites and 260/260 tests passed, optimized Next production build and integrated TypeScript passed, and Playwright passed 10/10 including public and long searchable multi-page CV PDFs.
- Analytics-controller extraction: Release build passed with 0 warnings/errors and the complete backend suite passed 736/736.
- Lifecycle-controller extraction: locked restore passed, Release build passed with 0 warnings/errors, and the complete backend suite passed 736/736.
- Health/readiness split: Release build passed with 0 warnings/errors, the complete backend suite passed 736/736, and the focused Playwright probe passed 1/1 against a real disposable SQLite-backed API process.
- Deployment rollback configuration: Docker Compose configuration validation passed with non-secret fixture values. Runtime rollback rehearsal remains pending because the local Linux Docker daemon is offline.
- Status lifecycle extraction: Release build passed with 0 warnings/errors and the complete backend suite passed 736/736, including applied-date preservation, status suggestions, and cross-tenant not-found behavior.
- Accessibility package: public axe flow passed 1/1 and authenticated axe flow passed 1/1 across six workspaces; ESLint passed with zero warnings; all 60 frontend suites and 260/260 tests passed; optimized Next build and integrated TypeScript passed; npm audit reported zero vulnerabilities after adding `@axe-core/playwright`.
- Prebuilt-export browser path: focused Playwright passed 1/1 while serving the existing optimized export without invoking another Next build.
- Submitted-package focused verification: storage/snapshot plus provider migration tests passed 6/6; the complete migration chain passed 18/18; application-assets UI passed 16/16; ESLint and the optimized Next build/TypeScript passed.
- Submitted-package complete regression: backend 740/740 and frontend 60 suites with 261/261 tests passed. The full run also exposed and fixed a date-sensitive reminder fixture that mixed a fixed worker clock with `DateTime.Now` seed data.
- Career-evidence focused verification: backend/controller/schema gates passed 21/21, the evidence UI regression passed 1/1, ESLint passed with zero warnings, and the optimized Next build/TypeScript passed. Complete regression then passed backend 741/741 and frontend 61 suites with 262/262 tests.
- Saved job-search implementation: tenant-owned NAV search definitions now retain per-vacancy first-seen, last-seen and dismissal state; repeat runs identify only genuinely new results, while one-off discovery remains unchanged. Account export/deletion and provider-safe schema ownership include the new records. Focused backend tests passed 3/3, focused frontend tests passed 6/6, schema/migration gates passed 20/20, ESLint passed with zero warnings, and the optimized Next build/TypeScript passed.
- Interview debrief loop: interview-stage workspaces now create an explicit, durable debrief in the existing user-owned preparation model, prompting for questions, strengths, improvements, feedback and next steps without generating answers. It remains editable/deletable and is localized in English and Bokmål. Focused backend tests passed 11/11, focused frontend tests passed 13/13, ESLint and the optimized Next build/TypeScript passed.
- Recruiter relationship view: each company now exposes a tenant-scoped view over its existing recruiter details, active applications, status, message totals and recent correspondence, with direct application navigation and no duplicate contact store. Focused backend authorization/data tests passed 2/2, the frontend interaction test passed 1/1, ESLint and the optimized Next build/TypeScript passed.
- Offer comparison workspace: active applications in the existing Offer status now appear in a responsive side-by-side comparison of recorded compensation, location, decision deadline, next contact, next action and user notes. Missing facts remain explicitly unknown; no parallel offer record was introduced. Focused frontend tests passed 2/2, ESLint and the optimized Next build/TypeScript passed.
- Post-product-batch complete regression: backend 744/744 and frontend 63 suites with 267/267 tests passed after saved searches, interview debriefs, recruiter relationship history and offer comparison; ESLint remained at zero warnings and the optimized Next build/TypeScript passed.
- Focused frontend: 2 suites, 6 tests passed.
- Full frontend: 64 suites, 272 tests passed.
- Next production build and TypeScript: passed.
- Full backend: 736/736 tests passed after completing the Identity/JT-019 ownership transfer.
- Portable Playwright launcher: resolved the user-local .NET 9 SDK; backend Release build passed with 0 warnings/errors.
- Playwright: final complete browser rerun passed 10/10, including authenticated job creation/workspace flows and both public and long searchable multi-page CV PDF exports.
- Focused backend match/intelligence verification: 34/34 passed.
- Focused frontend application-intelligence verification: 11/11 passed.
- Career/Profile focused verification: 2 suites, 19/19 passed, including the final navigation/state-preservation regression; final full-suite/E2E gates remain pending.
- Job Workspace focused verification: 2 suites, 9/9 passed; TypeScript passed.
- Checklist localization verification: 7/7 passed, including a Bokmål regression that preserves user-authored content.
- Cover Letter/CV assets and Interview Prep focused verification: 2 suites, 27/27 passed, including Bokmål category rendering that preserves user content.
- Workspace/checklist integration verification: 2 suites, 14/14 passed, including localized system-derived next actions.
- Application intelligence focused verification: 11/11 passed; TypeScript passed after the creation-tab/localization batch.
- Career Profile and Operations focused verification: 2 suites, 16/16 passed, including Bokmål completeness/confidence and operations empty-state regressions.
- Full frontend after the Career/Operations localization batch: 62 suites, 260/260 tests passed.
- Next optimized production build and its integrated TypeScript check passed after the Career/Operations localization batch.
- Job Discovery and email-provider connections focused verification: 2 suites, 8/8 passed, including Bokmål workflow regressions; TypeScript passed.
- Job Email focused verification: 16/16 passed, including a Bokmål regression that preserves correspondence content; TypeScript passed.
- Full frontend after the Job Email localization batch: 62 suites, 263/263 tests passed.
- Next optimized production build and integrated TypeScript check passed after the Job Email localization batch.
- Public landing focused verification: 2 suites, 3/3 passed, including live switching to Bokmål and preservation of protected-route sign-in intent; TypeScript passed.
- CV Builder focused verification: 12/12 passed; TypeScript passed after the final format-label pass.
- Admin System focused verification: 3/3 passed, including Bokmål benchmark diagnostics that preserve runtime values; TypeScript passed.
- Job creation and Gmail workflow localization verification: 4 focused suites, 11/11 passed; TypeScript passed. The add-job stepper, Gmail continuity management, review queue, usage progress labels, errors and status copy now switch between English and Bokmål without translating user/job/email content.
- Shared active-surface localization/accessibility verification: Profile 13/13 and settings/auth/landing 18/18 passed; TypeScript passed. Tag entry, Pro notices, disabled registration, profile load recovery, dashboard totals, language selector semantics, job-selection checkboxes and locale-aware correspondence dates were aligned with the global EN/NB system.
- Final localization regression gate: all 62 frontend suites and 267/267 tests passed after correcting the isolated Pro-notice provider fallback; the optimized Next production build and integrated TypeScript check passed; the full .NET 9 backend suite passed 713/713.
- Career Profile atomicity/CV locale verification: focused backend 55/55 and frontend 10/10 passed; the targeted long Code-template Playwright/PDF flow passed 1/1 with no duplicate-key or out-of-range locale warning. Its fresh disposable database contained exactly 9 experiences, 1 education, 8 skills, 1 project, 1 certification and 2 languages, with zero duplicate experience ItemKeys.
- Post-fix complete regression: backend 716/716 and frontend 62 suites with 268/268 tests passed; the optimized Next production build and integrated TypeScript check passed.
- Shared view-state focused verification: 3 suites and 15/15 tests passed, including Bokmål loading/retry and Kanban interaction coverage; TypeScript passed.
- Admin Audit/settings/profile focused verification: 3 suites and 17/17 tests passed, including unavailable-versus-empty recovery and Bokmål audit actions that preserve stored event content; TypeScript passed.
- Turnstile/auth recovery focused verification: 4 suites and 18/18 tests passed; the provider widget receives the Bokmål language code and TypeScript passed.
- Final shared-state/localization regression: all 64 frontend suites and 272/272 tests passed; optimized Next build and integrated TypeScript passed; complete Playwright passed 10/10, including public PDF and the searchable multi-page Code template. Google Identity now initializes its global SDK once while rerenders replace only the active credential handler; the focused login regression, ESLint, TypeScript and a fresh 10/10 browser run pass without the repeated-initialize warning.
- Admin/correspondence/profile helper-copy verification: 3 suites and 22/22 tests passed, including a Bokmål SMTP-message regression; TypeScript passed.
- Local toolchain/lint verification: Python 3.12.10; sidecar 26/26 tests passed with five existing SWIG deprecation warnings; ESLint passes with zero warnings; frontend 64 suites and 272/272 tests passed; optimized Next build and integrated TypeScript passed; npm audit reports zero vulnerabilities.
- Backend matcher/intelligence focused verification: 35/35 passed, including detection of a manually created Norwegian advert with no saved translation.
- Full backend: 712/712 tests passed on .NET 9.
- Next optimized production build and TypeScript: passed after the Job Workspace/checklist batch.
- Playwright: 8/10 passed on the first complete run; both failures were ambiguous selectors in the newly responsive Career selector, not product failures. Both corrected focused regressions now pass (2/2); final full rerun remains in the end-of-batch gate.
- Manual desktop browser review: webpack development server rendered the new Career navigation and Overview correctly in dark mode; API-dependent profile status remained unavailable because the backend was not running for that isolated UI review.
- **Overall programme status:** Active but externally blocked. Eight packages are locally verified and twenty-seven are implemented with verification incomplete. The prioritized admin-only version indicator, every immediate repository/browser item, SEC-006/SEC-007 repository boundaries, SEC-009, the PROD-001 read-only inventory, and the PROD-003 safe benchmark harness are complete on the feature branch.
- **Current work package:** JT-017 remaining immutable CI action/image/installer provenance, retained SBOM, container/licence scan and model-stack advisory resolution. JT-018 and JT-019 are complete in repository scope; the remaining JT-017 gates require verified upstream metadata or the unavailable Docker/scanner runtime.
- **Completed work packages:** None are `DONE`; all repository security/AI packages still have applicable browser, provider and/or production gates.
- **Locally verified work:** SEC-001, SEC-002, SEC-003, SEC-005A, CORE-001, PROD-002, DEP-001 and VER-001 (`VERIFIED LOCALLY`).
- **Implemented, verification incomplete:** SEC-004, SEC-005B, SEC-006, SEC-007, SEC-008, SEC-009, CORE-002, BG-001, OPS-001A/B/C, POL-001/002, AI-001/002/003/004, UX-001/002/003, QA-001, CAREER-001/002, MAIL-001, JOBS-001/002 and PRODUCT-001 (`IMPLEMENTED — NOT VERIFIED`). Their safe repository/browser scope is implemented; production/native-device/provider/retention gates remain where recorded.
- **Production-verified work:** None.
- **Blocked work:** PROD-001/003/004 and REL-001 require network/backup/model/deployment authority and unfinished dependencies. Real provider and live deletion/restore checks remain gated; DEP-001 awaits approved merge/live verification.
- **Deferred work:** None. Conditional multi-replica coordination, model deletion, realtime operation delivery and unrelated production changes remain outside current packages.
- **Immediate order:** all sixteen immediate repository items are complete locally, including the original UI/release queue plus SEC-009 cache/tombstone safety, worker restart clocks, universal AI accounting, email-token/Stripe lifecycle tests, exhaustive Job email selectors, the repaired migration chain, CV/public-edge hardening and measured admin/mail scaling. PROD-001 read-only evidence and the PROD-003 plan-only harness are also complete. The final audit is checking tooling/documentation before declaring only external blockers remain.
- **Status counts:** 8 `VERIFIED LOCALLY`; 27 `IMPLEMENTED — NOT VERIFIED`; 0 `IN PROGRESS`; 0 `NOT STARTED`; 4 `BLOCKED`; 0 `DONE`; 0 `DEFERRED`.
- **Test status:** backend 736/736; frontend 64/64 suites and 272/272 tests; ESLint and TypeScript zero findings; AI sidecar 37/37 without warnings; Ollama benchmark harness 5/5 plus safe dry-run; optimized production build; EF model parity; SQLite/MariaDB migration scripts; blank/idempotent/populated SQLite migration-chain tests; fresh complete-chain SQLite application startup; disposable earlier-chain MariaDB 11.8 application startup; Docker Compose config; safe-failure deployment preflight; and Playwright 10/10. Supply-chain controls pass 3/3, the tracked tree is clean, and the generated CycloneDX SBOM contains 964 components. npm audit remains at zero. The final ownership migrations have generated MariaDB SQL but still need a current MariaDB runtime rehearsal; the parser image and container scan are pending because Docker Desktop's daemon is offline. Jest's slow/open-handle behavior remains recorded.
- **Deployment status:** Gitea run 705 passed the complete test and deploy jobs. Production version 286 / commit `ea7d9f3` passed public frontend, API liveness, database readiness and auth-configuration smoke checks; the prior release stayed available until the health-checked replacement window.
- **Production status:** Public liveness and database readiness are verified for version 286. Authenticated journeys, provider delivery, complete backup/restore, failure-injection rollback, port closure and retention/tombstone behavior remain unverified and explicitly blocked. No private rows/content, secrets, model inference or provider call were inspected during this verification.
- **Known regressions:** None found by automated/local browser checks. Jest still needs `--forceExit` and reports its existing open-handle notice. Email-provider/send tests are fake/local only; real delivery is not claimed. Current MAIL browser evidence is 1280×720 only because the browser surface could not resize or perform native Tab traversal. Interrupted attempts are aged after 15 minutes and notified without retry; the five-minute scan is unmeasured on a large ledger. The direct clean EF-only SQLite defect and synchronous AI accounting gap are closed; migration/reconciler dual ownership remains architectural debt.
- **Outstanding security findings:** JT-001 repository ownership remains High deployment risk until migration/inventory/provider checks; production portion of JT-002; JT-006 and SEC-009 production retention/restore plus JT-011/JT-012/JT-022 prerequisites. JT-005 foundations are implemented; AI worker activation awaits controlled rollout. Production still exposes ports contrary to the release-branch contract, and JT-007/JT-008/JT-010 lack provider/production verification.
## Current evidence
- `docs/audits/audit-remediation-backlog.md`
- `docs/audits/verification-log.md`
- `docs/verification/sec-001-canonical-origin.md`
- `docs/verification/sec-002-ingress-compose.md`
- `docs/verification/sec-003-microsoft-tenant.md`
- `docs/verification/sec-004-microsoft-identity.md`
- `docs/verification/sec-005a-session-revocation.md`
- `docs/verification/sec-005b-email-ownership.md`
- `docs/verification/core-001-sqlite-provider-parity.md`
- `docs/verification/core-002-route-uniqueness.md`
- `docs/verification/sec-008-attachment-consistency.md`
- `docs/verification/sec-009-account-lifecycle.md`
- `docs/verification/bg-001-tenant-workers.md`
- `docs/verification/ops-001a-durable-operations.md`
- `docs/verification/ops-001b-notifications.md`
- `docs/verification/ops-001c-operation-ui.md`
- `docs/verification/pol-001-free-pro-entitlements.md`
- `docs/verification/pol-002-ai-privacy.md`
- `docs/verification/ai-001-durable-ai-queue.md`
- `docs/verification/ai-002-provider-routing.md`
- `docs/verification/ai-003-strategy-snapshot-queue.md`
- `docs/verification/ai-004-cv-processing-queue.md`
- `docs/verification/ux-001-unified-authentication.md`
- `docs/verification/ux-002-deterministic-theme-state.md`
- `docs/verification/qa-001-job-term-quality.md`
- `docs/verification/career-001-career-workspace.md`
- `docs/verification/career-002-cv-builder.md`
- `docs/verification/mail-001-job-email-hub.md`
- `docs/verification/jobs-001-job-discovery.md`
- `docs/verification/ux-003-kanban-theme.md`
- `docs/verification/product-001-honest-plans.md`
- `docs/verification/ver-001-complete-regression.md`
- `docs/verification/prod-002-ai-evaluation.md`
- `docs/production/production-ai-hardware-assessment.md`
- `docs/production/production-ai-rollout-and-rollback.md`
- `docs/production/ollama-model-benchmark.md`
- `docs/work-programmes/master-work-plan.md`
-816
View File
@@ -1,816 +0,0 @@
# JobTracker master work plan
Prepared: 2026-08-02
Authoritative sources:
- `docs/todo/work.md` (UX/reliability programme, lines 1-922)
- `docs/todo/ollama.md` (production local-AI programme, lines 1-825)
- `docs/audits/audit-remediation-backlog.md` (validated security and reliability prerequisites)
This file is the authoritative merged implementation plan. It avoids duplicate implementations: Strategy Snapshot, CV processing, durable operations, notifications, entitlement, AI privacy, provider routing, tenant-safe workers and restart recovery are each represented once and retain references to both source programmes.
## Status and completion rules
Allowed statuses are `NOT STARTED`, `IN PROGRESS`, `IMPLEMENTED — NOT VERIFIED`, `VERIFIED LOCALLY`, `DEPLOYED — NOT VERIFIED`, `DONE`, `BLOCKED`, and `DEFERRED`.
`DONE` requires every applicable acceptance criterion, focused and regression tests, browser/accessibility/theme/mobile checks, tenant and entitlement checks, documentation, migration/rollback evidence, and production verification. Repository-only work that still requires production is at most `VERIFIED LOCALLY`.
At most one implementation item may be `IN PROGRESS`. No package is currently in progress: every remaining package is blocked by an external decision, production mutation, provider interaction, or explicitly unapproved package-index access.
## Consolidated dependency order
```text
SEC-001 -> SEC-002 -> SEC-003 -> SEC-004
| | \-> SEC-005A -> SEC-005B
| \--------------> BG-001
|
+-> SEC-006 -> SEC-007 -> AI-004
+-> SEC-008 ----------------> SEC-009
CORE-001 -> CORE-002 -> BG-001 -> OPS-001A -> OPS-001B -> OPS-001C
OPS-001A -> POL-001 -> POL-002 -> AI-001 -> AI-002
PROD-002 -> POL-002 -> AI-001
PROD-002 -> PROD-003
PROD-001 -> PROD-003 -> PROD-004
AI-001 + AI-002 -> AI-003 and AI-004
UX-001/UX-002/QA-001 may proceed after their security prerequisites.
CAREER-001 -> CAREER-002; MAIL-001, JOBS-001, JOBS-002, UX-003 and PRODUCT-001 follow foundations.
All implemented surfaces -> VER-001 -> REL-001.
```
Ordering differences from the suggested list:
- SEC-001 canonical origin precedes Microsoft legacy relinking and email recovery because those links cannot prove ownership while request Host can influence their origin.
- SEC-006/SEC-007 parser hardening precedes the CV queue migration; moving an unsafe parser into a queue does not make it safe.
- SEC-008 attachment consistency precedes complete account deletion.
- The synthetic workload inventory/evaluation set (PROD-002) can proceed without production access and should inform routing and benchmarks early.
- Production inventory, benchmark and rollout remain independent blockers; repository-side queue, policy and UX work continues without them.
## Immediate completion queue (2026-08-15)
This queue records the highest-value work that can proceed without production credentials, provider consent or a new product decision. It reuses the work packages below rather than creating duplicate implementations.
| Order | Immediate work | Owning package(s) | Current state and finish line |
|---:|---|---|---|
| 1 | Admin-only deployed-version indicator in the application header | DEP-001, VER-001 | Implemented with authenticated API and shell tests. The badge shows the CI deployment version and exposes the commit SHA in its accessible label/tooltip only for administrators; full regression, remote CI and deployment smoke remain. |
| 2 | Lossless Career field persistence | CAREER-001 | Implemented and locally verified. Manual website/location/contact/date/language values now use a reviewed-data persistence boundary; extraction heuristics remain isolated to extraction. Full remote/production smoke remains. |
| 3 | CV contact/header/sidebar contrast correction | CAREER-002 | Implemented and locally verified. Header/custom-accent and sidebar palettes own readable foregrounds; real Chromium computed-style/overflow checks and a 17-page A4 PDF proof pass. |
| 4 | Dedicated Job Details parity and JOBS-002 closure | JOBS-002 | Locally complete. Application answers/recruiter drafts now live on the dedicated page, note markers are encapsulated, edits are lossless, dirty navigation is guarded, focus returns to the row, tenant regressions pass, and Chromium covers 375/768/1440 light/dark/history/error/long data. Production smoke remains. |
| 5 | Cross-application contrast/accessibility pass | UX-002, UX-003, VER-001 | Locally complete. All icon controls own programmatic names; CV cards are keyboard links; the A4 public CV scales without inner/outer mobile overflow; dark Alert contrast is measured in Chromium at WCAG AA; full frontend/build/Playwright pass. Native assistive-technology and a general CI crawler remain external/future gates. |
| 6 | Honest Free/Pro homepage and upgrade surfaces | PRODUCT-001 | Locally complete. One catalogue drives exactly Free/Pro; retired tier/price/Free-AI/unlimited claims are gone; configured billing state controls the real upgrade action; contextual notices are reusable/dismissible. Frontend 232/232, policy/billing 30/30, build and Chromium 8/8 pass. |
| 7 | Complete application action matrix and full regression | VER-001 | Locally complete. Backend 647/647, frontend 232/232, sidecar 22/22, build, Compose configuration, safe-failure preflight and Chromium 9/9 pass; external provider/native-AT/production cells remain explicitly unverified. |
| 8 | Tracking and blocker reconciliation | All | Complete for this checkpoint. The plan, progress, handoff, verification log, action matrix and `BLOCKERS.md` distinguish repository work from external gates; continue updating them with each later package. |
| 9 | Complete live-account deletion cache/tombstone safety | SEC-009 | Locally complete. Sidecar-cache failure is retryable and fail-closed; tombstones use separate persistent storage; activation remains disabled pending retention and restore decisions. |
| 10 | Prove worker clocks and restart idempotency | BG-001 | Locally complete with injected clocks, exact-threshold tests, fresh worker instances and reminder/export deduplication. Production canary remains disabled. |
| 11 | Universal synchronous AI usage admission | POL-001 | Locally complete. Shared generation paths reject Free/exhausted users before provider I/O and avoid double-counting durable/workspace operations. |
| 12 | Prove email-token and Stripe downgrade lifecycles | SEC-005B, POL-001 | Locally complete with real Identity-token replay/expiry/custom-username tests and fake-gateway active/past-due/canceled Stripe transitions. External SMTP/Stripe journeys remain blocked. |
| 13 | Remove the Job email 100-application selector ceiling | MAIL-001 | Locally complete through bounded owner-filtered server search and tenant/UI regressions. |
| 14 | Repair the full historical migration chain | CORE-001, JT-019 | Locally complete for blank/idempotent/populated SQLite, EF-only-to-application startup, provider scripts and disposable MariaDB 11.8 fresh/restart. Production restore/rollout remains blocked. |
| 15 | Close low-risk CV/public-edge hardening | JT-020, JT-023, JT-025 | Locally complete. Authenticated previews are sandboxed without scripts, public PDF budgets are client-and-slug scoped, and the expired tracked JWT fixture is removed and ignored. Git history remediation remains a separate coordinated decision. |
| 16 | Remove measured admin/mail scaling defects | JT-021, MAIL-001 | Locally complete. Admin roles use two reads independent of user count; Job email has bounded accessible pagination with totals and deterministic ordering while the old endpoint remains compatible. Provider/production capacity measurement remains external. |
## Requirement coverage index
| Source section | Covered by |
|---|---|
| Work Phase 1 baseline/plan (36-61) | This master plan, the progress/handoff/decisions files, compatibility pointer under `docs/plans/`, VER-001 |
| Work Phase 2 authentication (63-100) | UX-001, SEC-003, SEC-004, SEC-005 |
| Work Phase 3 theme (102-133) | UX-002 |
| Work Phase 4 job search (135-172) | JOBS-001 |
| Work Phase 5 keyword quality (174-263) | QA-001, PROD-002 |
| Work Phase 6 Career Workspace (265-307) | CAREER-001 |
| Work Phase 7 CV 504 (309-386) | SEC-006, SEC-007, OPS-001A/B/C, AI-001, AI-004 |
| Work Phase 8 CV Builder/FlowCV (388-452) | CAREER-002 |
| Work Phase 9 consolidated email (454-527) | MAIL-001, POL-001, POL-002 |
| Work Phase 10 Kanban dark mode (529-560) | UX-003 |
| Work Phase 11 application table/workspace (562-634) | CORE-002, JOBS-002, AI-003, MAIL-001 |
| Work Phases 12-13 Free/Pro (636-721) | POL-001, PRODUCT-001 |
| Work Phase 14 Strategy Snapshot (723-777) | CORE-001, CORE-002, OPS-001A/B/C, POL-001, POL-002, AI-001, AI-003 |
| Work Phase 15 action verification (779-855) | VER-001 |
| Work quality/browser/completion (857-922) | Every UI package, VER-001, REL-001 |
| Ollama Phases 1-2 inventory/safety (58-176) | PROD-001 |
| Ollama Phase 3 workloads/evaluation (177-258) | PROD-002 |
| Ollama Phases 4-6 candidate/tuning/decision (259-397) | PROD-003 |
| Ollama Phase 7 routing/privacy (398-451) | POL-001, POL-002, AI-002 |
| Ollama Phases 8-9 queue/backpressure (452-573) | BG-001, OPS-001A/B/C, AI-001 |
| Ollama Phase 10 fallback (574-608) | POL-002, AI-002, PROD-004 |
| Ollama Phase 11 frontend states (609-639) | OPS-001C, AI-003, AI-004 |
| Ollama Phases 12-14 observability/rollout/validation (640-775) | PROD-004, REL-001 |
| Ollama tests/report (776-825) | Every AI package, VER-001, REL-001 |
## Work items
### SEC-001 — Canonical external origin and application Host guard
- **Source programme:** shared security prerequisite; Work 17-34; Ollama 26-56; audit P0-2A/JT-002.
- **Original requirement references:** `work.md:17-34`; `ollama.md:26-56`; `audit-remediation-backlog.md` P0-2A.
- **Related findings:** JT-002.
- **Priority:** P0 security prerequisite.
- **Dependencies:** confirmed canonical production URL; none in code.
- **Affected components:** ASP.NET startup/configuration, security-link/OAuth/billing/reminder URL builders, cookie policy, config tests, environment/deploy preflight docs.
- **Acceptance criteria:** Production requires one canonical HTTPS origin; request/forwarded Host never changes an external URL; unknown production Host is rejected; local Development/Test remains explicit and working.
- **Required tests:** origin parser/startup; every URL caller; hostile Host/forwarded headers; Unicode/ports/paths; secure-cookie behavior; relevant backend regression suite.
- **Required browser verification:** local login/reset/verification navigation when a local email sink is available; no visual redesign.
- **Required production verification:** canonical and hostile Host smoke after SEC-002; not required to claim local verification.
- **Status:** `VERIFIED LOCALLY`.
- **Blocker:** production/ingress verification depends on SEC-002; a live local Production-mode process launch was rejected by the execution policy before starting, so it is not claimed.
- **Evidence:** `docs/verification/sec-001-canonical-origin.md`; `ExternalOriginTests`; focused security/controller slice 79/79; full backend 474/474; Release build; Compose config; normalized deploy-shell syntax.
- **Commit:** none.
- **Remaining work:** verify canonical and hostile Host behavior through the complete proxy path in SEC-002; exercise reset/verification navigation with a safe local email sink; deploy and verify before `DONE`.
### SEC-002 — Production ingress, forwarded headers and Compose separation
- **Source programme:** shared production/security prerequisite; Work 17-34; Ollama 123-176; audit P0-2B/JT-002.
- **Original requirement references:** `work.md:17-34`; `ollama.md:123-176`; audit P0-2B.
- **Related findings:** JT-002, JT-013, JT-020.
- **Priority:** P0.
- **Dependencies:** SEC-001; actual proxy network/IP supplied by operator for production verification.
- **Affected components:** production/development Compose selection, nginx forwarded headers, explicit known proxy/network config, deploy script/runbook, CI deployment invocation.
- **Acceptance criteria:** dev override is never auto-loaded in production; no direct production application ports; exact-host ingress contract; sanitized two-hop forwarding; rollback command documented.
- **Required tests:** merged Compose assertions, production config tests, nginx/proxy integration, deploy shell checks.
- **Required browser verification:** canonical public/API navigation through local proxy.
- **Required production verification:** exact Traefik route, closed ports, correct HTTPS/client IP/cookies.
- **Status:** `VERIFIED LOCALLY`.
- **Blocker:** external Traefik topology, production CIDR/network inventory, firewall state and provider routes are unavailable for production verification; the pinned nginx base image was not installed locally and was not pulled without internet permission.
- **Evidence:** `docs/verification/sec-002-ingress-compose.md`; production Compose has no published frontend/backend/Ollama ports; dev Compose publishes only 3000/5202/11434; proxy parser tests; nginx syntax/substitution checks; frontend build; backend 476/476.
- **Commit:** none.
- **Remaining work:** inventory/set the non-overlapping production CIDR; verify operator Traefik exact-host/header replacement and firewall; build the pinned image in approved CI; run local/prod proxy and hostile-Host smoke before `DONE`.
### SEC-003 — Microsoft tenant and issuer trust policy
- **Source programme:** Work authentication/audit prerequisite.
- **Original requirement references:** `work.md:91-100`; audit P0-1A/JT-001.
- **Related findings:** JT-001.
- **Priority:** P0.
- **Dependencies:** supported single/multitenant mode configured.
- **Affected components:** Microsoft token validator, smart auth scheme, sign-in configuration, mocked validator tests.
- **Acceptance criteria:** exact issuer/`tid`; (`tid`,`oid`) returned; invalid/missing/mismatched tenant rejected; unused raw bearer trust path removed or proven necessary and hardened.
- **Required tests:** all tenant modes, issuer/audience/signature/lifetime, personal/organizational, same `oid` across tenants.
- **Required browser verification:** mocked Microsoft success/failure/cancel only; real provider later if safely configured.
- **Required production verification:** configured tenant mode and synthetic/provider smoke without exposing tokens.
- **Status:** `VERIFIED LOCALLY`.
- **Blocker:** none for validator implementation; real Microsoft smoke needs provider configuration.
- **Evidence:** `docs/verification/sec-003-microsoft-tenant.md`; tenant/issuer validator matrix; raw bearer branch removed; focused 42/42 and full backend 491/491; Compose and deploy-shell config checks.
- **Commit:** none.
- **Remaining work:** production inventory/configuration and mocked/real safe provider smoke; SEC-004 canonical pair persistence/relinking must complete before JT-001 closes or Microsoft is enabled in production.
### SEC-004 — Canonical Microsoft links and safe legacy relinking
- **Source programme:** Work authentication/audit prerequisite.
- **Original requirement references:** `work.md:91-100`; audit P0-1B/JT-001.
- **Related findings:** JT-001.
- **Priority:** P0.
- **Dependencies:** SEC-001, SEC-003, SEC-005A and SEC-005B recent reauthentication/email proof.
- **Affected components:** `ApplicationUser`, EF model/migration, auth exchange/link/unlink/recovery UI and APIs.
- **Acceptance criteria:** composite tenant/object key is unique owner; no email auto-link; no silent legacy backfill/merge; legitimate legacy users have explicit non-locking recovery.
- **Required tests:** fresh/legacy SQLite+MariaDB migration, collisions, two tenants/same email, last-credential guard, 2FA, mocked relink.
- **Required browser verification:** local mocked new sign-in and legacy relink.
- **Required production verification:** anonymized legacy inventory before migration; monitored relink window.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** real Microsoft/browser/SMTP and disposable MariaDB checks are unavailable; production legacy inventory is unknown.
- **Evidence:** `docs/verification/sec-004-microsoft-identity.md`; focused auth 34/34; backend 507/507; frontend 152/152 and build; dual-provider SQL; disposable SQLite legacy/unique-index rehearsal.
- **Commit:** none.
- **Remaining work:** real mocked-browser Microsoft popup/relink flow with an SMTP sink; disposable MariaDB migration; production counts-only legacy inventory, rolling-version smoke and monitored relink window before `DONE`.
### SEC-005A — Session and recovery revocation
- **Source programme:** Work auth constraints; shared recovery prerequisite.
- **Original requirement references:** `work.md:17-34,63-100`; audit P0-4A/P0-4B, JT-007/JT-008.
- **Related findings:** JT-007, JT-008.
- **Priority:** P0.
- **Dependencies:** SEC-001; coordinates with SEC-004.
- **Affected components:** logout, password reset/change, local session validation, trusted devices, pending 2FA challenges and auth tests.
- **Acceptance criteria:** logout revokes the exact copied `sid`; reset revokes all sessions/devices without disabling 2FA; password change rotates the current session, revokes others and preserves only the current trusted device; session validation binds `sid` to user; stale pending 2FA fails after a security-stamp change.
- **Required tests:** valid/expired logout cookie; copied session; reset across users/devices; password rotation; trusted-device retention/removal; `sid`/user mismatch; pending 2FA stamp.
- **Required browser verification:** logout in two tabs; password change/reset with a local email sink; 2FA recovery.
- **Required production verification:** copied-cookie/reset/change smoke without logging token values.
- **Status:** `VERIFIED LOCALLY`.
- **Blocker:** browser and production checks require safe running environments.
- **Evidence:** `docs/verification/sec-005a-session-revocation.md`; focused 48/48; full backend 497/497.
- **Commit:** none.
- **Remaining work:** browser/runtime and production verification before `DONE`; SEC-005B owns email state.
### SEC-005B — Verified registration and pending-email transitions
- **Source programme:** Work auth constraints; shared identity/recovery prerequisite.
- **Original requirement references:** `work.md:17-34,63-100`; audit P0-4B, JT-007/JT-008.
- **Related findings:** JT-007, JT-008.
- **Priority:** P0.
- **Dependencies:** SEC-001, SEC-005A; coordinates with SEC-004.
- **Affected components:** registration, verification/resend, profile DTOs, pending-email request/confirm/cancel APIs, `ApplicationUser`, one additive EF migration/snapshot, auth/profile UI and tests.
- **Acceptance criteria:** verification-required registration creates no session and returns typed 202; active email never changes before proof; latest pending request wins; confirmation uses Identity change-email semantics, conditionally updates username, clears pending state and revokes all sessions/devices.
- **Required tests:** registration/no-cookie transition; verify then login; enumeration-resistant resend; duplicate/latest/mismatched/expired/replayed pending email; username preservation; passwordless/provider users; SQLite and MariaDB migration paths.
- **Required browser verification:** mocked/local-sink register/resend/verify and request/cancel/confirm email at desktop/mobile with keyboard access.
- **Required production verification:** safe SMTP link/origin and version-skew smoke; no real personal address.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** in-app browser localhost access was denied by its admin policy check; no safe SMTP sink, disposable MariaDB, or production environment is available.
- **Evidence:** `docs/verification/sec-005b-email-ownership.md`; focused backend 35/35; full backend 501/501; frontend 151/151 and build; SQLite upgrade and dual-provider migration scripts; isolated API 202/no-cookie and 403/no-cookie checks.
- **Commit:** none.
- **Remaining work:** real-browser desktop/mobile/keyboard flows with a local email sink; disposable MariaDB migration execution; production SMTP/origin/version-skew verification before `DONE`. Real Identity valid/expired/replayed verification and custom-username email-change behavior pass locally (V-182).
### SEC-006 — Compatible document-parser dependency update
- **Source programme:** Work CV 504; Ollama parser prerequisite; audit P0-3A.
- **Original requirement references:** `work.md:309-386`; `ollama.md:44-56,177-258`; audit JT-006.
- **Related findings:** JT-006, JT-017.
- **Priority:** P0.
- **Dependencies:** approved package-index access during implementation; synthetic benign corpus.
- **Affected components:** Python requirements/lock/hash, parser tests and image build.
- **Acceptance criteria:** compatible fixed versions; no unaccepted reachable High/Critical parser advisory; clean reproducible install; benign extraction parity.
- **Required tests:** dependency resolution/audit, Python suite, generated benign corpus.
- **Required browser verification:** none for dependency-only package.
- **Required production verification:** image digest and smoke before activation.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** Docker Desktop is installed but its Linux daemon is offline, so the hash-locked production image has not been built locally.
- **Evidence:** `docs/verification/sec-006-parser-dependencies.md`; upload-facing dependency audit is clear; clean hash install succeeds; parser suite 32/32; backend 719/719.
- **Commit:** none.
- **Remaining work:** build the production image from `requirements-linux.lock` and run its health/benign extraction smoke when a Docker daemon is available. Torch/Transformers advisories remain separately tracked under JT-017 and are not accepted as upload-parser findings.
### SEC-007 — Bounded isolated document processing
- **Source programme:** Work CV 504; Ollama privacy/queue; audit P0-3B/P0-3C.
- **Original requirement references:** `work.md:309-386`; `ollama.md:177-258,452-573`; audit JT-006/JT-011.
- **Related findings:** JT-006, JT-011.
- **Priority:** P0.
- **Dependencies:** SEC-006.
- **Affected components:** backend upload/fallback, FastAPI parser child, queue backpressure, container non-root/resource/tmp cleanup.
- **Acceptance criteria:** bounded file/page/pixel/decompression/memory/time work; child/process group killed; no binary backend fallback; safe cleanup/errors; private tokenized service remains.
- **Required tests:** generated boundary/corrupt fixtures, harmless sleeping child, cancellation/restart cleanup, outage/no-fallback, container assertions.
- **Required browser verification:** synthetic CV upload status/failure; authorized private CV local-only only after safeguards.
- **Required production verification:** measured memory/CPU limits and canary synthetic extraction.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** production sizing and canary require access; an actual Linux image build/runtime smoke requires a running Docker daemon.
- **Evidence:** `docs/verification/sec-007-parser-isolation.md`; child process/minimal environment/deadline/process-tree termination/stale cleanup tests pass; Compose resolves non-root-image/read-only/capability/PID/CPU/memory/tmpfs controls. Parser 37/37, focused backend 51/51 and full backend 719/719 pass.
- **Commit:** none.
- **Remaining work:** build and run the Linux image, prove rlimits and container controls from inside it, measure benign PDF/DOCX/image memory/CPU, perform the synthetic browser upload failure/success journey, then canary only with explicit production authorization.
### SEC-008 — Recoverable attachment mutations
- **Source programme:** audit prerequisite for account lifecycle and workspace files.
- **Original requirement references:** Work 17-34 and file/application requirements; audit P0-5/JT-010.
- **Related findings:** JT-010.
- **Priority:** P0 data integrity.
- **Dependencies:** coordinate file-root/journal format with SEC-009.
- **Affected components:** attachment controller/file helper/reconciler/tests.
- **Acceptance criteria:** every DB/filesystem failure converges to committed or durable retryable state; no silent orphan/missing file; rename is metadata-only; owner/path isolation.
- **Required tests:** invalid later file, cancellation, DB/move/delete failure, restart stages, traversal/symlink, two users.
- **Required browser verification:** upload/rename/delete/refresh with synthetic files.
- **Required production verification:** report-only orphan inventory and monitored journal counters.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** in-app browser localhost is policy-blocked; no production report-only inventory/monitoring or MariaDB environment is available. This host denied disposable symlink creation, so that branch is code-inspected only.
- **Evidence:** audit JT-010 execution path; `docs/verification/sec-008-attachment-consistency.md`; 20/20 focused and 525/525 full backend tests; isolated User A/User B upload/download/rename/delete HTTP rehearsal.
- **Commit:** none.
- **Remaining work:** browser upload/rename/delete/refresh; executable symlink test on a capable host; production report-only orphan inventory and counters; MariaDB runtime; reconcile suffix markers before any rollback.
### SEC-009 — Complete readable export and account deletion lifecycle
- **Source programme:** Work audit constraint; Ollama private-data lifecycle; audit P0-6A/P0-6B.
- **Original requirement references:** `work.md:17-34`; `ollama.md:17-22,427-450`; audit JT-009.
- **Related findings:** JT-009, JT-013, JT-022.
- **Priority:** P0 data lifecycle.
- **Dependencies:** SEC-005 revoke-all, SEC-008, owner inventory, backup-retention/tombstone decision.
- **Affected components:** domain inventory/export ZIP, owner-scoped files/caches/queues, deletion saga, provider tokens, migrations, backup restore/runbooks/UI.
- **Acceptance criteria:** readable complete redacted export; idempotent live deletion across rows/files/tokens/queue/cache; no other tenant impact; backup retention truthful; restore tombstones prevent resurrection.
- **Required tests:** two users/every entity, manifest/checksums/redaction, fault/restart/idempotence, provider failures, disposable restore replay.
- **Required browser verification:** disposable self/admin export/delete and confirmations.
- **Required production verification:** backup retention/tombstone rehearsal before self-service enablement.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** legal/operator retention and production restore decisions block activation, not the repository-side disabled/dark launch.
- **Evidence:** audit JT-009 inventory/design; `docs/verification/sec-009-account-lifecycle.md`; V-173/V-174/V-175/V-179. Owner-scoped generated storage, complete redacted readable ZIP, immediate lockout, transactional owner-isolated row/file/cache purge, retry, fail-closed tombstones and restored-backup replay pass real-SQLite, sidecar, focused API/UI, full backend/frontend, Compose, build and Chromium checks.
- **Commit:** `842e793`.
- **Remaining work:** repository scope is complete, including retryable authenticated sidecar-cache purge and a separately mounted Compose tombstone volume. Production activation remains blocked by retention/legal decisions, deployed/protected tombstone custody, complete restored-backup rehearsal, remote-provider semantics and staged disposable-account rollout.
### CORE-001 — Restore default SQLite/MariaDB behavior parity
- **Source programme:** Work Strategy/Career failures; audit P1-1.
- **Original requirement references:** `work.md:723-777`; audit JT-003.
- **Related findings:** JT-003.
- **Priority:** P0 broken default workflow.
- **Dependencies:** none.
- **Affected components:** Career/Application workspace date-order/filter queries and provider matrix tests.
- **Acceptance criteria:** variants/runs/history/workspace return correct owner/empty/non-owner results on both supported providers.
- **Required tests:** fresh/seeded HTTP provider matrix, date/month/timezone boundaries.
- **Required browser verification:** SQLite Career/Application workspace after API tests.
- **Required production verification:** MariaDB smoke after deployment.
- **Status:** `VERIFIED LOCALLY`.
- **Blocker:** production MariaDB execution and production browser checks remain unavailable. The direct blank-file EF-only defect is closed; historical dual schema ownership remains JT-019 architectural debt.
- **Evidence:** audit runtime reproduction JT-003; `docs/verification/core-001-sqlite-provider-parity.md`; V-186; 3/3 real-provider query tests; 3/3 migration-chain tests; 680/680 backend regression; isolated fresh-SQLite owner/empty/non-owner HTTP matrix; disposable MariaDB 11.8 fresh/restart smoke.
- **Commit:** none.
- **Remaining work:** production Career/Application browser verification and production MariaDB restore/rollout smoke. Keep the compatibility migration/reconciler contract covered until a later expand/verify/contract release can consolidate ownership safely.
### CORE-002 — Remove ambiguous application-workspace routes
- **Source programme:** Work Strategy and embedded workspace; audit P1-2.
- **Original requirement references:** `work.md:562-634,723-777`; audit JT-004.
- **Related findings:** JT-004.
- **Priority:** P0 broken core route.
- **Dependencies:** CORE-001; inventory frontend/API consumers.
- **Affected components:** workspace/timeline/interview controllers, API clients/tests/docs.
- **Acceptance criteria:** one action per verb/path; owner 200, non-owner 404, anonymous 401; UI panels load.
- **Required tests:** route-table uniqueness, HTTP ownership, frontend panel tests.
- **Required browser verification:** direct/deep-link workspace panels and Back/Forward.
- **Required production verification:** authenticated workspace smoke.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** in-app browser localhost is policy-blocked; no production/MariaDB HTTP environment is available.
- **Evidence:** audit runtime ambiguous route reproduction; `docs/verification/core-002-route-uniqueness.md`; reflection route regression; 31/31 focused and 511/511 full backend; 153/153 frontend and build; owner 200/non-owner 404/anonymous 401 HTTP matrix.
- **Commit:** none.
- **Remaining work:** browser direct/deep-link, Back/Forward and rendered-panel verification; production authenticated smoke before `DONE`.
### BG-001 — Tenant-safe hosted-worker foundation
- **Source programme:** both; shared prerequisite.
- **Original requirement references:** `work.md:17-34,723-777`; `ollama.md:44-56,452-529`; audit JT-005.
- **Related findings:** JT-005, JT-012, JT-022.
- **Priority:** P0/P1.
- **Dependencies:** CORE-001/CORE-002; do not activate workers before OPS-001B, POL-001 and POL-002.
- **Affected components:** rules/reminders/export/enrichment/CV/AI hosted services, owner context, worker kill switches and tests.
- **Acceptance criteria:** explicit owner selection, deny-on-null avoided safely, idempotent results, structured failures, no cross-owner work, disabled workers remain off.
- **Required tests:** two-owner/no-HttpContext, enable/disable, restart/retry/clock, fake email/AI.
- **Required browser verification:** notification/result surfaces only after OPS-001C.
- **Required production verification:** one-worker canary and owner-safe metrics.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** activation blocked until policy/notification prerequisites; foundation code is not blocked.
- **Evidence:** audit JT-005 service inspection; `docs/verification/bg-001-tenant-workers.md`; real-SQLite two-owner worker suite with fake email/AI, exact fixed-clock threshold, fresh-instance restart idempotency and reminder/export deduplication; Compose validation; isolated default-off startup/health/no-export check.
- **Commit:** none.
- **Remaining work:** activation policy prerequisites, browser result surfaces and a monitored single-worker production canary. Local restart/clock coverage is complete. Keep all four switches false.
### OPS-001A — Durable operation record and lease state machine
- **Source programme:** both; shared CV/Strategy/AI operation foundation.
- **Original requirement references:** `work.md:360-386,761-777`; `ollama.md:452-529`.
- **Related findings:** JT-005, JT-013, JT-014, JT-022.
- **Priority:** P1 foundation.
- **Dependencies:** BG-001; explicit schema ownership.
- **Affected components:** `UserOperation` entity, owner/idempotency/claim indexes, state/lease store, provider-aware EF migration and tests.
- **Acceptance criteria:** stable owner-scoped ID; atomic idempotent create/claim; bounded states/retries/leases/deadlines/cancellation; restart recovery; no raw private payload field.
- **Required tests:** concurrent create/claim, two tenants, transition guards, lease expiry/final attempt, cancellation, retry delay, deadlines, migration up/down/provider SQL.
- **Required browser verification:** not applicable until OPS-001C exposes owner APIs.
- **Required production verification:** executable MariaDB upgrade/down rehearsal and monitored schema rollout.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** disposable fresh/restart MariaDB now passes; production schema rollout and monitored consumer canary remain unavailable.
- **Evidence:** `docs/verification/ops-001a-durable-operations.md`; 7/7 focused and 539/539 full backend tests; SQLite upgrade/down/up and fresh startup; dual-provider scripts.
- **Commit:** none.
- **Remaining work:** production rollout; task-specific producers must validate references/policies and use OPS-001B/C rather than storing private payloads.
### OPS-001B — Persistent operation notifications and terminal outbox
- **Source programme:** both; shared completion/failure visibility and reminder safety.
- **Original requirement references:** `work.md:360-386,761-777`; `ollama.md:512-529,609-639`.
- **Related findings:** JT-005, JT-012, JT-014, JT-022.
- **Priority:** P1 foundation.
- **Dependencies:** OPS-001A; keep real SMTP/AI workers disabled.
- **Affected components:** owner notification entity/store, operation terminal transactions, unread/dismiss state, provider-safe schema and tests.
- **Acceptance criteria:** success/failure/cancellation notification is committed atomically with terminal state; owner isolation; idempotent single notification; no private content; retained across restart.
- **Required tests:** every terminal state, duplicate completion, DB failure rollback, two owners, unread/read/dismiss, migration provider scripts.
- **Required browser verification:** deferred to OPS-001C.
- **Required production verification:** schema rollout and synthetic notification canary only.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** disposable fresh/restart MariaDB now passes; production migration/canary remains unavailable.
- **Evidence:** `docs/verification/ops-001b-notifications.md`; 9/9 focused and 541/541 full backend tests; forced transaction rollback; SQLite upgrade/down/up; current model snapshot; generated SQLite/MariaDB up/down SQL.
- **Commit:** none.
- **Remaining work:** complete production schema/notification canaries. Owner APIs/UI are implemented in OPS-001C; no email delivery is part of this package.
### OPS-001C — Owner operation/notification APIs and frontend queue client
- **Source programme:** both; shared queued-operation UX.
- **Original requirement references:** `work.md:360-386,761-777`; `ollama.md:499-510,609-639`.
- **Related findings:** JT-014, JT-015, JT-022.
- **Priority:** P1 foundation.
- **Dependencies:** OPS-001A/B; POL-001 locked-state admission precedes AI producers.
- **Affected components:** status/list/cancel/retry APIs, notification read/dismiss APIs, frontend polling/status/notification client and shell badge.
- **Acceptance criteria:** stable status URL, owner-only list/detail/cancel/retry; refresh/navigation recovery; honest states/errors; completion badge/read/dismiss; no duplicate submission or private diagnostics.
- **Required tests:** two-user API, refresh/poll/retry/cancel, invalid/expired IDs, component/keyboard/accessibility states.
- **Required browser verification:** queued/refresh/navigate/retry/cancel/completion notification with synthetic handler at 375/768/1440 and light/dark.
- **Required production verification:** authenticated synthetic polling/notification smoke.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** browser localhost remains policy-blocked, but repository/API/component work can continue.
- **Evidence:** `docs/verification/ops-001c-operation-ui.md`; 12/12 focused backend, 544/544 backend, 3/3 focused UI, 156/156 frontend and production build; isolated two-user HTTP owner/cross-owner matrix.
- **Commit:** none.
- **Remaining work:** real browser responsive/theme/keyboard/refresh checks, MariaDB/production smoke and feature-specific producers in AI-003/004. No generic create API is exposed.
### POL-001 — Canonical Free/Pro entitlement policy
- **Source programme:** Work Free/Pro and Ollama entitlement enforcement.
- **Original requirement references:** `work.md:636-721`; `ollama.md:17-22,412-449,501-529,638`.
- **Related findings:** JT-012, JT-022.
- **Priority:** P1 security/business policy.
- **Dependencies:** OPS-001A operation admission contract.
- **Affected components:** role/subscription capability service, API authorization, worker admission/recheck, usage accounting, auth DTO, frontend locked states.
- **Acceptance criteria:** exactly Free and Pro externally; Free has no AI; server rejects direct/batch/background bypass; Pro/expired/downgraded/admin behavior consistent; non-AI data remains accessible.
- **Required tests:** endpoint inventory for Free/Pro/expired/downgraded/admin, worker recheck, usage, direct requests and two tenants.
- **Required browser verification:** locked state/upgrade action/dismissal and Pro execution; mobile/theme/accessibility.
- **Required production verification:** configured Stripe/role mapping only when operator activation is approved.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** Stripe/MariaDB/production verification is unavailable. Repository entitlement and universal user-generation accounting are complete.
- **Evidence:** `docs/verification/pol-001-free-pro-entitlements.md`; V-181/V-184/V-185; focused entitlement/billing 33/33; full backend 677/677; existing frontend/browser entitlement evidence.
- **Commit:** none.
- **Remaining work:** configured Stripe Checkout/portal/webhook production smoke. Mocked checkout, active → expired → canceled/replayed role transitions and `price_` validation are complete (V-185). PRODUCT-001 has removed the former landing-page price/third-tier/unlimited claims.
### POL-002 — AI privacy, consent and external-fallback policy
- **Source programme:** both.
- **Original requirement references:** `work.md:17-34,495-505,723-777`; `ollama.md:398-451,574-608`.
- **Related findings:** JT-012, JT-022, JT-025.
- **Priority:** P1 privacy/security.
- **Dependencies:** POL-001, PROD-002 task/privacy classification.
- **Affected components:** persistent settings, operation policy snapshot, payload minimization, admin diagnostics, fallback audit, UI privacy explanation.
- **Acceptance criteria:** local-only/default/fallback policy enforced server-side; private categories never leave without permission; minimum payload; provider/reason recorded; opt-out never overridden; no browser secrets.
- **Required tests:** task/privacy matrix, consent changes, payload capture/redaction, fallback allowed/prohibited, entitlement, two tenants.
- **Required browser verification:** user/admin controls and disclosure/locked/failure states.
- **Required production verification:** external egress capture with synthetic data only; no real private CV/email.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** browser localhost is denied; MariaDB/production/external-provider verification is unavailable. Task-specific payload minimization/accounting depend on AI-003/004.
- **Evidence:** `docs/verification/pol-002-ai-privacy.md`; focused backend 72/72 and final policy 28/28; sidecar 18/18; focused frontend 8/8; full backend 576/576; full frontend 47 suites/158 tests; production build; config and migration script checks.
- **Commit:** none.
- **Remaining work:** browser user/admin disclosure checks; MariaDB and production synthetic egress proof; AI-003/004 task-specific payload minimization and real producer verification. AI-001/002 now carry rechecked policy/task context and record bounded local-first provenance; V-184 closes shared synchronous accounting.
### AI-001 — Durable AI queue, backpressure and operation APIs
- **Source programme:** both.
- **Original requirement references:** `work.md:360-386,761-777`; `ollama.md:452-573,609-639`.
- **Related findings:** JT-005, JT-011, JT-013, JT-014.
- **Priority:** P1.
- **Dependencies:** BG-001, OPS-001A/B/C, POL-001, POL-002.
- **Affected components:** AI operation handlers/worker, priority/concurrency/capacity/deadline/circuit, API polling/retry/cancel, frontend shared queue UI.
- **Acceptance criteria:** HTTP returns 202/stable URL; bounded priority queue protects Ollama; exact state transitions; no duplicate billing/output; refresh/restart recovery; scheduled jobs cannot starve interactive work.
- **Required tests:** queue capacity/priority, atomic claim, circuit, timeout/retry/jitter, duplicate click/idempotency, cancellation, shutdown/restart, tenant and policy recheck.
- **Required browser verification:** synthetic operation status across refresh/nav/double-click/offline/retry/cancel.
- **Required production verification:** queue depth/age, one-worker canary, Ollama offline/restart and app/worker restart.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** MariaDB/production restart verification is unavailable and the worker remains off by default.
- **Evidence:** `docs/verification/ai-001-durable-ai-queue.md`; focused queue/state/API tests 17/17; full backend 581/581; Compose config and diff checks.
- **Commit:** none.
- **Remaining work:** MariaDB and monitored single-worker production canary. Strategy/CV browser refresh, double-click, cancel and retry are covered locally; AI-002 supplies local-first circuit/provenance. Do not create a second CV- or Strategy-specific queue.
### AI-002 — Ollama adapter and local-first provider routing
- **Source programme:** Ollama local-first; Work privacy/Pro constraints.
- **Original requirement references:** `ollama.md:398-451,574-608`; `work.md:17-34,723-777`.
- **Related findings:** JT-012, JT-017, JT-022.
- **Priority:** P1.
- **Dependencies:** PROD-002, POL-001, POL-002, AI-001.
- **Affected components:** central task routing policy, Ollama/external adapters, sidecar/backend provider boundary, circuit/health, provider diagnostics/config.
- **Acceptance criteria:** deterministic then primary local then optional local then permitted external then clear failure; one policy considers task/privacy/entitlement/health/deadline/cost; no simultaneous duplicate completion.
- **Required tests:** routing matrix, Ollama adapter, schema failure, local circuit, fallback allowed/prohibited/unavailable, cost limits and deduplication.
- **Required browser verification:** provider-agnostic queued states and appropriate fallback disclosure.
- **Required production verification:** actual selected local model and controlled synthetic fallback.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** browser and production checks, actual local-model selection and controlled provider fallback depend on administrator browser policy plus PROD-001/003 access/benchmarks. Repository behavior is not blocked.
- **Evidence:** `docs/verification/ai-002-provider-routing.md`; V-098–V-100; focused backend 26/26, full backend 588/588, sidecar fake-transport 22/22, Compose/diff checks pass.
- **Commit:** none.
- **Remaining work:** keep Strategy/CV local-only until explicit external task approval; MariaDB/selected-model/controlled-provider/production verification. Shared synchronous AI actions now use central usage admission (V-184). Old provider/model configuration remains available for rollback.
### PROD-001 — Read-only production AI inventory and rollout safety
- **Source programme:** Ollama Phases 1-2.
- **Original requirement references:** `ollama.md:58-176`.
- **Related findings:** JT-013, JT-017, JT-020, JT-021.
- **Priority:** P1 production gate.
- **Dependencies:** documented/configured production access; none for repository report templates.
- **Affected components:** production hardware assessment, current Ollama/app/network/config inventory, backup and rollout/rollback report.
- **Acceptance criteria:** sanitized measured OS/CPU/RAM/GPU/storage/Ollama/deployment inventory; no public Ollama; config/backup/restart/interruption/rollback recorded before mutation.
- **Required tests:** read-only commands only; backup mechanism evidence; no secrets/content in reports.
- **Required browser verification:** none.
- **Required production verification:** this item is itself production read-only verification.
- **Status:** `BLOCKED`.
- **Blocker:** read-only inventory is complete, but Ollama/frontend host ports are published on all interfaces, complete current backups/restores are unproved, the production checkout has an unreviewed mode-only deploy-script change, and closing those gaps requires approved production mutations.
- **Evidence:** `docs/production/production-ai-hardware-assessment.md`; `docs/production/production-ai-rollout-and-rollback.md`; V-176. Sanitized read-only SSH measured hardware, GPU, storage, Docker, networks, selected provider/model, health, limits and backup presence without reading secrets/logs/content or changing state.
- **Commit:** none.
- **Remaining work:** approve and close the network/port, complete-backup/scratch-restore, dirty-checkout and retention gates; then repeat the sanitized inventory to verify the safe target state.
### PROD-002 — AI workload inventory and synthetic evaluation set
- **Source programme:** Ollama Phase 3; Work keyword/AI/CV/email features.
- **Original requirement references:** `ollama.md:177-258`; `work.md:174-263,309-386,454-527,723-777`.
- **Related findings:** JT-012, JT-022.
- **Priority:** P1.
- **Dependencies:** code inventory; no production access.
- **Affected components:** workload catalog, synthetic/redacted fixtures, deterministic-versus-AI and privacy/latency/output classifications.
- **Acceptance criteria:** every AI task has input/size/output/language/latency/quality/privacy/fallback/interactive/entitlement/current-provider classification; evaluation set covers every listed English/Norwegian/noisy/adversarial/long/invalid case without real data.
- **Required tests:** fixture validity, deterministic expected signals, strict JSON schemas, prompt-injection containment inputs.
- **Required browser verification:** none; fixtures later drive UI packages.
- **Required production verification:** none until benchmark.
- **Status:** `VERIFIED LOCALLY`.
- **Blocker:** none; authorized private CV is optional local-only and never committed/external.
- **Evidence:** `docs/verification/prod-002-ai-evaluation.md`; `docs/ai/workload-inventory.md`; fixture validation 1/1; full backend 569/569; frontend 47 suites/157 tests and build.
- **Commit:** none.
- **Remaining work:** PROD-003 later executes model benchmarks and sets measured thresholds. Revise classifications if POL-002 route tracing finds an omitted payload; no production/provider work is required for this package.
### PROD-003 — Production model benchmarks and model decision
- **Source programme:** Ollama Phases 4-6.
- **Original requirement references:** `ollama.md:259-397`.
- **Related findings:** JT-021.
- **Priority:** P1 production gate.
- **Dependencies:** PROD-001 measured hardware, PROD-002 evaluation set.
- **Affected components:** benchmark harness/evidence and `ollama-model-benchmark.md`.
- **Acceptance criteria:** exact candidate tags/license/version/quantization/resources/latency/throughput/quality/JSON/Norwegian/injection/failure/repeat results; measured context/tuning; primary/optional/deterministic/external decision.
- **Required tests:** repeated synthetic benchmark at 4K/8K and 16K only if safe; one inference initially; no very large/cloud model.
- **Required browser verification:** none.
- **Required production verification:** measured on actual machine; local workstation results are labeled separately.
- **Status:** `BLOCKED`.
- **Blocker:** production model pull/inference is not authorized, and PROD-001's all-interface Ollama plus incomplete backup/restore stop conditions remain open.
- **Evidence:** `scripts/run-ollama-evaluation.py`; `scripts/test-ollama-evaluation.py`; `docs/production/ollama-model-benchmark.md`; V-177. Plan-only harness tests 4/4 and validates an eight-request 4K/8K Strategy plan without a model call.
- **Commit:** none.
- **Remaining work:** after approvals and PROD-001 safety closure, verify candidate metadata/licenses, pull one candidate at a time, execute repeated synthetic 4K/8K benchmarks, measure active GPU/RAM/offload and complete the evidence-based model decision.
### PROD-004 — Local-model rollout, fallback, observability and operations
- **Source programme:** Ollama Phases 9-14.
- **Original requirement references:** `ollama.md:531-775`.
- **Related findings:** JT-005, JT-013, JT-017, JT-021, JT-022.
- **Priority:** P1 production deployment.
- **Dependencies:** AI-001, AI-002, PROD-001/003, verified backup/rollback.
- **Affected components:** Ollama limits/model install, app config/migrations/worker, telemetry/health/runbook, validation matrix.
- **Acceptance criteria:** selected digest installed without deleting old model; bounded Ollama/app queues; local-first default; safe fallback; privacy-safe metrics/health; drain/restart/rollback; full production validation matrix.
- **Required tests:** offline/timeout/restart/congestion/concurrent/fallback/free/pro/two-tenant/notification matrix.
- **Required browser verification:** queued AI journeys in production using synthetic data.
- **Required production verification:** mandatory; no `DONE` without actual deploy, resource observation and restart recovery.
- **Status:** `BLOCKED`.
- **Blocker:** production access plus all dependencies.
- **Evidence:** none yet.
- **Commit:** none.
- **Remaining work:** repository runbooks/config only after measured values; no guessed limits/model.
### AI-003 — Strategy Snapshot durable-operation migration
- **Source programme:** both; one consolidated implementation.
- **Original requirement references:** `work.md:723-777`; `ollama.md:609-639,730-765`.
- **Related findings:** JT-003, JT-004, JT-005, JT-012, JT-014, JT-022.
- **Priority:** P1 broken user workflow.
- **Dependencies:** CORE-001/002, AI-001/002, POL-001/002.
- **Affected components:** Strategy button/API/operation handler/result persistence/UI status/notification.
- **Acceptance criteria:** root timeout reproduced; enqueue returns 202; stable result; success/failure/timeout/cancel/retry/idempotent double-click/refresh/restart; no duplicate usage/output.
- **Required tests:** endpoint/handler/provider fakes, all required states, entitlement/privacy/tenant checks, E2E.
- **Required browser verification:** complete queue/status/error/retry/cancel/refresh/back-forward/mobile/theme flow.
- **Required production verification:** local model success, timeout and restart recovery.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** browser localhost policy, selected local model, MariaDB, restart canary and production access remain unavailable; worker stays default-off.
- **Evidence:** `docs/verification/ai-003-strategy-snapshot-queue.md`; verification-log V-101–V-103; `docs/audits/evidence/ai-003/README.md`.
- **Commit:** `a621226` (`feat(ai): queue strategy snapshots`).
- **Remaining work:** selected-model timeout/quality test; MariaDB and production single-worker restart/canary/rollback. Local browser/mobile/theme/refresh/back-forward coverage exists in the wider application suite; no Strategy-specific queue was created.
### AI-004 — CV-processing 504 and durable-operation migration
- **Source programme:** both; one consolidated implementation.
- **Original requirement references:** `work.md:309-386`; `ollama.md:609-639,730-765`.
- **Related findings:** JT-006, JT-011, JT-014.
- **Priority:** P1 broken user workflow/security.
- **Dependencies:** SEC-006/007, OPS-001A/B/C, AI-001; authorized private file optional only after safe fixture reproduction.
- **Affected components:** browser upload/API/proxy/artifact/parser/normalization/result polling/recovery/UI queue states.
- **Acceptance criteria:** 504 origin established; enqueue/persist/progress/result; bounded processing/retry/cancel/cleanup; restart recovery; no timeout inflation; review gate preserved.
- **Required tests:** safe synthetic PDFs/DOCX/images, proxy/backend/parser/provider failure, duplicate/refresh/restart, E2E.
- **Required browser verification:** synthetic CV first; authorized private file via temporary local copy only, never logged/committed/external.
- **Required production verification:** synthetic/local-only canary, no external payload, restart recovery.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** browser localhost policy, selected-model execution, MariaDB and production reproduction remain unavailable. SEC-006/007 repository hardening is implemented; its Linux image/runtime proof is separately gated by the offline Docker daemon.
- **Evidence:** `docs/verification/ai-004-cv-processing-queue.md`; `docs/verification/sec-006-parser-dependencies.md`; `docs/verification/sec-007-parser-isolation.md`; V-104–V-107/V-180/V-181/V-190/V-191; real SQLite synthetic integration proves 202/active deduplication/owner-scoped handler/retry provenance/notification/review gate, usage reservation and pre-claim cancellation/deadline synchronization; backend 719/719; parser 37/37.
- **Commit:** `c3c5af8` (`feat(cv)!: queue durable processing`).
- **Remaining work:** browser synthetic upload/refresh/retry/cancel/review at required widths/themes/keyboard; Linux parser image/runtime proof; selected-model and worker-restart canary; MariaDB/production rollout. Do not use the private CV for external/provider processing.
### UX-001 — Unified authentication page
- **Source programme:** Work Phase 2.
- **Original requirement references:** `work.md:63-100`.
- **Related findings:** JT-001, JT-007, JT-008, JT-015.
- **Priority:** P2 after auth safety.
- **Dependencies:** SEC-003/004/005 behavior contracts.
- **Affected components:** login/register UI, Microsoft/Google buttons, error/cancel/return handling, translations/tests.
- **Acceptance criteria:** one username/password card, `or`, normal Google/Microsoft alternatives, recovery/register links; prohibited provider-status clutter removed; no linking implication.
- **Required tests:** invalid credentials/provider failure/cancel/return, focus/order/labels.
- **Required browser verification:** 375/768/1440, light/dark, keyboard/focus, logged-out/provider mocks.
- **Required production verification:** real provider smoke only with authorized accounts.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** light/System-theme browser, configured/real-provider and production checks require the UX-002 preference work plus authorized provider/deployment environments.
- **Evidence:** `docs/verification/ux-001-unified-authentication.md`; V-108–V-110; focused 13/13, full frontend 47/47 suites and 166/166 tests, production build, responsive dark-theme browser captures at 375/768/1440.
- **Commit:** `93b8692` (`feat(auth): unify sign-in options`).
- **Remaining work:** light/System theme browser; configured-provider browser; real authorized Google/Microsoft cancel/return; production smoke. Keep identity migration separate.
### UX-002 — Deterministic theme state
- **Source programme:** Work Phase 3.
- **Original requirement references:** `work.md:102-133`.
- **Related findings:** JT-015.
- **Priority:** P2.
- **Dependencies:** inspect all theme sources.
- **Affected components:** theme provider/bootstrap/local/profile/cross-tab state and tests.
- **Acceptance criteria:** saved user > anonymous local > system only in System > default; no unexpected route/nav changes or startup flash; loop-free tab sync.
- **Required tests:** Light/Dark/System, login/logout/refresh/navigation/storage/preference listeners/tabs.
- **Required browser verification:** 375/768/1440, light/dark/system, refresh/navigation/two tabs/reduced motion.
- **Required production verification:** normal browser smoke after deploy.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** production and live authenticated multi-user browser environments are unavailable; repository/browser work is complete.
- **Evidence:** `docs/verification/ux-002-deterministic-theme-state.md`; V-111–V-113 and V-170; deterministic-theme coverage plus full frontend 54/54 suites and 228/228 tests, build, Light/Dark/System/navigation/refresh/two-tab browser checks, computed dark-Alert AA contrast and responsive public-CV proof.
- **Commit:** `11734ee` (`fix(theme): make preference state deterministic`).
- **Remaining work:** live User A/User B preference switching and production browser smoke; retain existing preference keys during rollout.
### QA-001 — Job-analysis and keyword quality
- **Source programme:** Work Phase 5; Ollama deterministic workload rule.
- **Original requirement references:** `work.md:174-263`; `ollama.md:177-223`.
- **Related findings:** JT-021 (measurement), AI quality.
- **Priority:** P2.
- **Dependencies:** PROD-002 fixtures; current pipeline/caching version inventory.
- **Affected components:** import cleanup/language/token/stop words/phrases/skills/scoring/prompt/postprocess/storage/UI label.
- **Acceptance criteria:** function/filler/chrome suppressed generically; technologies/punctuation/multiword phrases preserved; contextual generic terms; honest label; versioned regeneration behavior.
- **Required tests:** seven specified Norwegian/English/mixed/short/noisy/tech/filler fixtures.
- **Required browser verification:** result presentation/empty/error/long Norwegian text at three widths/themes.
- **Required production verification:** synthetic analysis comparison; no silent historical rewrite.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** browser presentation and production comparison remain; deterministic repository work is complete.
- **Evidence:** `docs/verification/qa-001-job-term-quality.md`; V-114–V-116; seven required fixtures, focused matcher 15/15, affected backend 54/54, full backend 601/601, focused UI 13/13, full frontend 172/172 and build.
- **Commit:** `da1aa8b` (`fix(match): prioritize meaningful job terms`).
- **Remaining work:** browser empty/error/long Norwegian/three-width/theme presentation; synthetic production comparison. No stored analysis migration exists.
### CAREER-001 — Career Workspace action-oriented redesign
- **Source programme:** Work Phase 6.
- **Original requirement references:** `work.md:265-307`.
- **Related findings:** JT-003, JT-015.
- **Priority:** P2.
- **Dependencies:** CORE-001 and AI-004 status contract.
- **Affected components:** Career Workspace hierarchy/empty/onboarding/import review/completeness/recent docs/status UI.
- **Acceptance criteria:** concise actions for profile/import/review/resume/builder/general/job CV/recent/completeness/errors; long paragraph removed; approval gate preserved.
- **Required tests:** first/returning/incomplete/processing/failure state and navigation.
- **Required browser verification:** three widths, light/dark, keyboard/focus/loading/empty/error/Norwegian.
- **Required production verification:** synthetic account smoke.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** browser session was already finalized; three-width/theme/keyboard/Norwegian checks and production synthetic-account smoke remain.
- **Evidence:** `docs/verification/career-001-career-workspace.md`; V-117–V-119, V-164 and V-167; focused Career/Profile UI 17/17, affected backend 112/112, full backend 642/642, extraction backend 8/8, sidecar 22/22 and production build. State-aware actions/recent CVs are implemented, extraction polling no longer overwrites unsaved form state, reviewed values round-trip without extraction reinterpretation, and the Apply/Discard gate is unchanged.
- **Commit:** `268b3a0` (`feat(career): clarify workspace actions`) plus the V-164 polling and V-167 persistence checkpoints.
- **Remaining work:** browser and production gates. Live model-quality benchmarking and deeper builder interaction belong to CAREER-002.
### CAREER-002 — CV Builder interaction redesign and external research
- **Source programme:** Work Phase 8.
- **Original requirement references:** `work.md:388-452`.
- **Related findings:** JT-003, JT-015, JT-025.
- **Priority:** P2.
- **Dependencies:** CAREER-001, AI-004; inspect existing advanced builder before changing it.
- **Affected components:** builder list/editor/sections/entries/reorder/visibility/autosave/validation/preview/responsive/accessibility.
- **Acceptance criteria:** all specified section/edit/add/delete/reorder/hide/validation/save/nav/preview behaviors while preserving data/templates/render/export/version/import/profile separation.
- **Required tests:** editing/collapse/add/delete/reorder/save/failure/persistence/preview.
- **Required browser verification:** authorized FlowCV research if accessible, never bypass auth; original JobTracker design at three widths/themes/keyboard/focus.
- **Required production verification:** existing variants/edit/export/public render smoke.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** authenticated JobTracker three-width/theme/keyboard checks and production synthetic-variant smoke require their runtime environments.
- **Evidence:** `docs/verification/career-002-cv-builder.md`; V-120–V-125, V-165 and V-168. Builder/public UI 22/22 and renderer/templates 25/25 pass. Real Chromium confirms contrast ownership and zero overflow for default/light custom/sidebar palettes; a harder pathological fixture produces a readable 17-page A4 PDF with final-page text and no text shrinking.
- **Commit:** `b58cc19`, `a5b74e0`, `2043349` plus the V-165 checkpoint.
- **Remaining work:** complete authenticated application-browser/production gates and the honest deployed DOCX capability check. Public competitor-pattern research is complete; no authenticated competitor session is claimed.
### MAIL-001 — Consolidated job-email hub and explicit sending
- **Source programme:** Work Phase 9.
- **Original requirement references:** `work.md:454-527`.
- **Related findings:** JT-005, JT-012, JT-015, JT-022, JT-025.
- **Priority:** P2.
- **Dependencies:** BG-001, OPS-001B/C notifications, POL-001/002, provider tenant safety.
- **Affected components:** Gmail Review/Correspondence routes, shared domain/components, Gmail/Graph/IMAP, detection/linking, drafts/send audit/application embedding.
- **Acceptance criteria:** one hub plus shared application view; linked/suggested messages; provider identity/search/filter/states; editable draft and explicit confirmed idempotent send; no autonomous AI/send; weak signals never auto-link.
- **Required tests:** link/unlink/dismiss/draft/send duplicate/uncertain/provider failure/reauth/two tenants/free/pro/application embed.
- **Required browser verification:** all states at three widths/themes/keyboard; mocked providers only unless safe configured account.
- **Required production verification:** provider read/draft/send requires explicit authorized synthetic account; never real unsolicited email.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** real provider/re-consent, full SEC-009 deletion, MariaDB, production and required 375/768/1440/theme/keyboard browser gates are unavailable or require new authority.
- **Evidence:** `docs/verification/mail-001-job-email-hub.md`; V-126–V-153/V-183/V-188. Draft/new-message UI 13/13, API/idempotency/rotation 10/10, persistence 1/1 with dual-provider reversible SQL and readable export 4/4; Free send policy 7/7; provider states 9/9; hub unlink 8/8 UI and 2/2 API; shared application context focused 10/10; paged inbox/admin focused 9/9 and UI 15/15; backend 683/683; frontend 58 suites/239 tests plus build; local empty/disconnected and compatibility-route browser smoke at 1280×720.
- **Commit:** `6008b4a` (hub), `536d403` (neutral reads), `a20775c` (safe detail), `653f011` (ledger), `e9937ac` (Gmail/Graph delivery adapters and consent), `123fc55` (explicit-confirmed send API), `449faeb` (confirmed reply composer), `ee5ef7e` (interrupted-send recovery), `8fe3903` (legacy SMTP retirement), `aff34cc` (content-free export and cascade evidence), `ff547df` (shared application context), `1dabbeb` (confirmed hub unlink), `f9e641c` (honest provider states), `7f41cb2` (Free email policy regression), `14b396a` (inert tenant draft persistence), `2fa4e38` (owner-isolated readable draft export), `a9bb22e` (tenant-safe revisioned draft API), `80b5532` (persisted draft send identity), `d3d2b67` (saved reply recovery/conflicts), `29de263` (definitive-failure identity rotation), `b735963` (new-message job/provider drafting).
- **Remaining work:** SEC-009 repository deletion coverage is complete but production activation remains gated; provider mailbox category capabilities require separately authorized scopes/re-consent and remain absent; browser/production verification remains. Server-side owner-filtered job choice search now reaches applications beyond the old first-page ceiling (V-183). Existing connections need explicit re-consent; IMAP remains read-only. No real email; uncertain sends need manual reconciliation. The clean full-chain SQLite rehearsal now passes (V-186).
### JOBS-001 — Job-search source and assessment redesign
- **Source programme:** Work Phase 4.
- **Original requirement references:** `work.md:135-172`.
- **Related findings:** JT-015, JT-021, JT-024.
- **Priority:** P2.
- **Dependencies:** source provenance inventory; CORE fixes.
- **Affected components:** discovery DTO/storage/source labels/filter/sort/cards/import flow.
- **Acceptance criteria:** every listing shows honest source/type/original link/retrieval/deadline; derived sources labeled; source preserved on import; scan/search/filter/location/work-mode/errors/duplicates/mobile improved.
- **Required tests:** provenance mapping/filter/import preservation/empty/loading/error/duplicates.
- **Required browser verification:** three widths/themes/keyboard/long text/Norwegian/import.
- **Required production verification:** official NAV/safe provider smoke; unavailable providers labeled.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** live official NAV compatibility and production smoke remain external; browser data/import was mocked.
- **Evidence:** `docs/verification/jobs-001-job-discovery.md`; V-154–V-156. Verified source/acquisition/retrieval/deadline/import, honest result states/retry/sorts/missing-data disclosure, feed duplicate withdrawal and mocked browser journey at 375/768/1440 with light/dark, keyboard, long Norwegian content and reviewed import; backend 631/631, frontend 201/201, Playwright 5/5 and production build pass.
- **Commit:** `511a9f6` (honest provenance/import), `3f74b23` (result states/sort), `82f4526` (duplicate/browser/contrast regression and evidence).
- **Remaining work:** live NAV compatibility, native mobile assistive-technology and production smoke. Add source filtering only when more than one real source is available.
### JOBS-002 — Applications table and dedicated workspace
- **Source programme:** Work Phase 11.
- **Original requirement references:** `work.md:562-634`.
- **Related findings:** JT-003, JT-004, JT-015, JT-021.
- **Priority:** P2.
- **Dependencies:** CORE-001/002, MAIL-001 embedding contract, AI-003 status.
- **Affected components:** applications table, filters/search/sort, canonical dedicated route, workspace sections/focus/unsaved state.
- **Acceptance criteria:** scan-friendly priority columns; list context preserved; deep-link/back-forward/direct URL; accessible focus/return; responsive dedicated page; no job-details popup.
- **Required tests:** route/history/filter persistence/focus/unsaved/direct link/mobile, tenant authorization.
- **Required browser verification:** three widths/themes/keyboard/back-forward/refresh/error/long data.
- **Required production verification:** existing application/workspace smoke.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** authenticated production smoke remains external.
- **Evidence:** V-158–V-162; `docs/verification/jobs-002-application-workspace.md`.
- **Commit:** `bd5362c` (URL-owned list state), `109745e` (canonical dedicated page/table/sidebar integration).
- **Remaining work:** repository and real-Chromium scope is locally complete, including application-package parity, dirty navigation, focus return, tenant denial, three widths/themes/history/error/long data. Authenticated production smoke and a native assistive-technology spot check remain. Do not place every field in the table or duplicate workspace data.
### UX-003 — Kanban theme-state correction
- **Source programme:** Work Phase 10.
- **Original requirement references:** `work.md:529-560`.
- **Related findings:** JT-015.
- **Priority:** P2.
- **Dependencies:** UX-002 shared theme tokens preferably first.
- **Affected components:** Kanban column/card/drag/focus/loading/error styles and tests.
- **Acceptance criteria:** no white dark-mode targets; all listed drag/empty/card/hover/keyboard/error states have shared-token contrast and light/mobile quality.
- **Required tests:** component/visual state coverage.
- **Required browser verification:** three widths, light/dark, pointer and keyboard drag, focus/contrast.
- **Required production verification:** board smoke.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** production board smoke and native assistive-device testing remain external.
- **Evidence:** `docs/verification/ux-003-kanban-theme.md`; V-157. Confirmed white dark-mode columns, then verified shared theme surfaces, pointer/keyboard/invalid/failure/loading states, 1440/768/375 behavior and dark/light contrast; component 7/7, frontend 204/204, Playwright 6/6 and build pass.
- **Commit:** `fb6f17e` (theme/state/keyboard/mobile root fix and evidence), `4e5ce0c` (required-width and real browser hover/drag verification).
- **Remaining work:** production board smoke and native assistive-device validation only.
### PRODUCT-001 — Homepage plans and respectful Pro promotion
- **Source programme:** Work Phases 12-13.
- **Original requirement references:** `work.md:636-721`.
- **Related findings:** JT-012, JT-015, JT-022.
- **Priority:** P2.
- **Dependencies:** POL-001 canonical policy.
- **Affected components:** homepage/pricing/registration/settings/nav/metadata/upgrade prompts/locked states/translations/tests.
- **Acceptance criteria:** exactly Free (no AI/core tracking) and Pro (defined AI capabilities); no invented price/trial/limit; central capability data; concise dismissible non-dark-pattern promotion.
- **Required tests:** copy/capability consistency, Free/Pro/expired/downgraded locked states, dismissal/no false generation.
- **Required browser verification:** homepage and contextual prompts at three widths/themes/keyboard/accessibility.
- **Required production verification:** configured price text only if actual billing product exists; otherwise no invented values.
- **Status:** `IMPLEMENTED — NOT VERIFIED`.
- **Blocker:** configured Stripe lifecycle, native assistive technology and production checks remain external; repository behavior no longer depends on a product decision.
- **Evidence:** `docs/verification/product-001-honest-plans.md`; V-171. Focused frontend 30/30, current policy/billing 30/30, full frontend 57 suites/232 tests, build and Playwright 8/8.
- **Commit:** none.
- **Remaining work:** configured synthetic Checkout/webhook/portal/expiry/downgrade browser journey, native assistive-technology spot check and production smoke. Commercial terms remain owned by Stripe Checkout.
### VER-001 — Complete application action matrix and regression pass
- **Source programme:** Work Phase 15; Ollama validation/tests.
- **Original requirement references:** `work.md:779-903`; `ollama.md:730-798`.
- **Related findings:** all relevant audit findings, especially JT-014/JT-015/JT-016.
- **Priority:** P1 verification gate.
- **Dependencies:** all implemented work packages; matrix may be populated incrementally earlier.
- **Affected components:** `docs/verification/application-action-matrix.md`, browser evidence, regression tests.
- **Acceptance criteria:** every meaningful safe control/action has route/role/plan/result/path/loading/success/failure/auth/tenant/tests/manual/automated/finding classification; failures fixed or accurately blocked.
- **Required tests:** full backend/frontend/Python/E2E plus regressions for confirmed defects.
- **Required browser verification:** running app, synthetic users/data, 375/768/1440, themes/keyboard/focus/refresh/back/tabs/slow/error; no real email/paid provider/destructive production action.
- **Required production verification:** applicable smoke actions only after deployment; local and production classifications remain distinct.
- **Status:** `VERIFIED LOCALLY`.
- **Blocker:** remote CI, external providers, native assistive technology and production access block individual external cells, not the completed local matrix.
- **Evidence:** `docs/verification/ver-001-complete-regression.md`; V-172. Backend 647/647, frontend 57 suites/232 tests, sidecar 22/22, production build, Compose config, preflight negative tests and Chromium 9/9 pass.
- **Commit:** none.
- **Remaining work:** keep the rolling matrix current; execute only its provider/native-AT/production cells when those environments and approvals exist.
### DEP-001 — Frontend advisory deployment gate
- **Source programme:** live deployment blocker reported 2026-08-10; audit supply-chain finding.
- **Original requirement references:** user deployment failure report; JT-017.
- **Related findings:** JT-017.
- **Priority:** P0 release blocker.
- **Dependencies:** none for repository remediation; CI/live access for final verification.
- **Affected components:** `job-tracker-ui/package.json`, lockfile, RouterProvider compatibility and Jest jsdom setup.
- **Acceptance criteria:** npm audit is clean without suppressing advisories; production build and route regressions pass; CI consumes the fixed lockfile; live deployment proceeds.
- **Required tests:** resolved dependency tree, `npm audit`, focused router tests, full frontend tests and production build.
- **Required browser verification:** route/navigation smoke after deployment; existing automated route coverage is required before push.
- **Required production verification:** CI audit and live deployment from the fixed commit.
- **Status:** `VERIFIED LOCALLY`.
- **Blocker:** final deployment verification depends on the remote CI/live environment.
- **Evidence:** `docs/verification/dep-001-frontend-advisories.md`; V-137/V-141; audit 0 vulnerabilities, focused 24/24, full 190/190 and production build pass; Gitea run 609 passed complete pull-request CI in 4m20s after the stale browser assertion correction.
- **Commit:** `b55a592` (pushed).
- **Remaining work:** the admin-only header version indicator is implemented on the release branch; run full/remote gates, merge/deploy from `main`, confirm the visible badge matches the CI run version and production commit, run route smoke, and update to `DONE` only after production verification.
### REL-001 — Production validation and remaining audit closure
- **Source programme:** both final reports and production validation.
- **Original requirement references:** `work.md:905-922`; `ollama.md:640-825`; audit backlog remaining phases.
- **Related findings:** all open findings.
- **Priority:** P1 release gate.
- **Dependencies:** VER-001, PROD-004, completed repository packages, backup/rollback/access.
- **Affected components:** `docs/production/production-ai-validation.md`, operations runbook, deployment evidence, audit verification logs, final reports.
- **Acceptance criteria:** truthful production/local/mocked/blocked matrix; queue/model/routing/restart/tenant/Free-Pro/privacy verified; remaining findings explicitly open/deferred; rollback proven.
- **Required tests:** full release command matrix and production synthetic smoke.
- **Required browser verification:** production journeys requiring real deployment, no private data in evidence.
- **Required production verification:** mandatory for `DONE` where source programme requires rollout.
- **Status:** `BLOCKED`.
- **Blocker:** production access plus unfinished dependencies.
- **Evidence:** current production documents explicitly say production data/access not verified.
- **Commit:** none.
- **Remaining work:** continue safe local packages; do not claim production completion.
## Conflict register summary
Full decisions are in `docs/work-programmes/decisions.md`.
1. **AI provider architecture:** ADR-004/current roadmap say one deployment provider; the newer Ollama programme explicitly requires local-first plus controlled fallback. The new programme is the target, implemented centrally and compatibly; old config/models remain for rollback.
2. **Free AI:** resolved by POL-001/PRODUCT-001. Free has no AI admission at the server boundary, manual/existing data remains accessible, and public copy now matches that behavior without renaming persisted roles.
3. **Production authority:** Work says no production deploy unless instructed; Ollama programme and the current request authorize only scoped local-AI production work after inventory/backup/rollback. No other production mutation is authorized.
4. **Schema ownership:** OPS-001A chose one EF-owned, provider-conditional migration for `UserOperations` and deliberately omitted reconciler DDL. MariaDB script generation passes; executable server verification remains.
5. **Compose documentation:** docs claim a separate dev override, but the filename is auto-loaded by production deploy. SEC-002 corrects actual behavior.
6. **CV Builder premise:** programme asks for capabilities already present. CAREER-002 begins with a browser/code gap analysis and changes only evidenced gaps.
-18
View File
@@ -1,18 +0,0 @@
# JobTracker session handoff
Updated: 2026-08-15
- **Exact current task:** no independent implementation remains. SEC-009, PROD-001 read-only inventory/reporting and the PROD-003 plan-only benchmark harness are complete; continue only after a recorded blocker is authorized/resolved.
- **Last completed step:** repaired and regression-tested the full historical migration chain for standalone SQLite tooling and provider-aware application startup, including a disposable MariaDB 11.8 fresh/restart rehearsal.
- **Files currently modified:** master progress/work-plan/handoff/decisions/blockers/evidence; SEC-009 verification; production hardware/rollout/benchmark reports; production backup checkpoint; Ollama benchmark script/tests.
- **Commands already run:** SEC-009 focused backend 21/21, backend 657/657, frontend focused 8/8 and full 237/237, optimized build, EF parity, MariaDB script generation and Chromium 9/9; benchmark harness 4/4 plus plan-only dry run; sanitized read-only SSH inventory and gzip integrity across 21 existing dumps.
- **Test results:** all repository gates pass. PROD-001 is PASS/PARTIAL because measured all-interface ports and incomplete/stale backup/restore evidence fail its safety acceptance. No model inference was run. Jest retains the documented open-handle notice.
- **Services currently running:** none on task-owned ports 3000/5202. Playwright stopped its disposable API/Next servers. Pre-existing Docker services were not changed.
- **Temporary files or processes:** no task-owned process is running. Playwright stopped its disposable API/Next servers. No raw benchmark output/evidence file was created.
- **Production changes currently active:** none. Read-only SSH observed metadata/health/selected non-secret settings and backup integrity only. No log/private-row/content/secret read, deployment, migration, provider call, inference, model pull, restart, backup, restore or production file/config change occurred.
- **Rollback status:** SEC-009 is additive migration `20260815164027_AddAccountDeletionLifecycle`. Keep deletion disabled, reconcile any durable request, and retain tombstones before downgrade. `842e793` is pushed. Production still runs `de937d25dc5e` / version `157`; its checkout has a pre-existing mode-only `deploy/deploy.sh` change that must be preserved/reviewed.
- **Uncommitted changes:** documentation and the plan-only Ollama benchmark harness/tests following pushed SEC-009 commit `842e793`; no dependency, model, application runtime, schema or production state change in this checkpoint.
- **Known failures:** PR 28's newest remote CI is not yet confirmed. Production publishes frontend 3000 and JobTracker Ollama 11434 on all interfaces; latest observed database-only backup is 2026-08-02; no complete files/keys/tombstone restore proof; root is 83% used; deployed AI sidecar is old direct-Gemini behavior; production script mode is dirty. SEC-006 internet access, SEC-007 dependency, provider/re-consent, Stripe price, signup, retention/legal, backup/restore, model execution/deployment and legacy cutover decisions remain recorded blockers. JT-019's blank-chain defect is fixed, while dual ownership remains debt; Jest open handles remain.
- **Exact next action:** after this documentation/harness checkpoint is committed and pushed, stop. Resume from the highest-priority blocker the user authorizes: recommended first is production network plus complete backup/scratch-restore safety, then bounded synthetic model benchmarking.
- **Work that can continue independently:** none identified after the PROD-003 harness. Do not bypass blockers by pulling models, changing ports/firewalls, restoring data, contacting providers, or using package indexes without explicit authority.
- **Decisions still required from the user:** production network/port mutation; complete backup and scratch restore; retention/tombstone/legal policy; model pull/synthetic production inference; deployment/worker activation; parser package-index access; provider/Stripe/signup actions; legacy cutover timing.