Skip to content

WorkWingman — Delivery Board

Meet the team

Our build crew is a family of AI teammates — we call them by name. The name is the teammate; the "Runs on" column is the AI tool they work through. So when a task or review says Cedric did it, that's the teammate — the underlying tool is in the table.

Teammate Runs on What they usually do
Clahadore Jones-Gaffney Claude (Anthropic) Planning, coordinating the work, reviewing, and the hard calls
Cedric "Cody" Jones-Gaffney Codex / ChatGPT (OpenAI) Writing code and giving a second-opinion review
Jenny Jones-Gaffney Gemini (Google) Writing code, research, and review
Gronktayvius "Gronk" Jones-Gaffney Grok (xAI) Tough adversarial review — trying to break things before users can

All four share the surname Jones-Gaffney because they're family. Led by Andrew (Andrew David Jones-Gaffney).

Live interactive board: https://claude.ai/code/artifact/27a44c1c-d629-4067-995e-a02fc41444a9 — the kanban view (In Progress · In Review · Done). This file and that artifact are kept in lockstep: both move on every status transition.

Live project board: the active sprint plus every delivered epic, reconstructed from the full commit history. Cards move on every STATUS TRANSITION (started / in review / done) — not just at commit time — so In Progress and In Review reflect work as it happens; Done rows carry the commit that landed them. (274 commits, Jul 3 → Jul 8, 2026 so far.)

Ground rules the whole board inherits: every commit passes a multi-model review gate (Codex + Gemini, later + Semgrep/gitleaks/ZAP; Grok seated Jul 9 night — from then on review chips name Grok explicitly whenever it sat, including degraded runs; chips tagged pre-Grok seat are gates that ran before Grok joined); the automation never clicks Submit, never types stored passwords, and never creates accounts; nothing is pushed or sent anywhere without an explicit decision.

Posture right now: 3,095 backend tests passing (full suite, on master after the BYOK council-review pass) · full frontend spec suite green · Semgrep 0 findings · gitleaks clean over full history · ZAP baseline 0 FAIL / 66 PASS · Greenhouse + Workday + iCIMS background apply-drive live · provider-agnostic BYOK AI harnesses (LLM · research/RAG · audio) live · tester-ready NSIS installer shipped · CI on the self-hosted warm pool (GitHub cloud minutes exhausted for the window).


| ⚪ Backlog (later, not current sprint) | WING-230 epic (children WING-231…WING-236) | Mobile Control Plane — cloud-agent mobile SKU (metered subscription) + free desktop-companion mode | Mobile | Council-vetted 2026-07-29 (Jenny/Gronktayvius/Clahadore seats, Cedric chair; Andrew's cloud-only + companion rulings AMENDED and accepted). Phone = controller, never automation runtime. Order: WING-231 COGS pilot FIRST — blocks all pricing/billing/store work. Cost driver = residential proxies + IAP cut, not compute; floor ~$20–40/mo metered. LinkedIn hard-banned. Biggest unpriced risk: correlated account-ban blast radius from shared cloud/proxy fingerprints. Andrew 2026-07-29: "not something for right now but to look at getting done later." |

Active sprint

Status Key Ticket Epic Notes
🔴 Blocker — · WING-520 Master CI is red across 9 jobs, so no PR can merge — PR mode requires green CI, so this blocks every open PR regardless of its contents (WING-517 among them). DevOps The decisive receipt: the docs-only board commit 1166487d8 failed 9 jobs. It changes one markdown file, and a markdown edit cannot break Semgrep, Angular or Electron. PR #61's merge to master failed the same way on 08-13, so master has been red at least a day. Failing: Backend (.NET), Frontend (Angular), Semgrep, gitleaks, Cloud Live Run broker, Electron E2E, Docs two-track, Determinism, Live Jira reconciliation. Backend = 10 deterministic failures (10 failed / 6704 passed), identical by name in CI and locally, reproduced on master in a clean disposable worktree — 7 of the 10 cluster on profile-import materialization / clone-persona catalog, plus the broker's own Profile_import_contract test, so that is likely ONE root cause and not eight. Distinct from WING-370: those were load-driven and non-deterministic, these reproduce in a clean checkout. Semgrep fails on a scan TIMEOUT, not a finding — 2 timeouts on the minified workwingman-pageagent.iife.js — so the SAST gate is red for infrastructure reasons. Why it compounds: an always-red gate cannot fail, so a genuinely broken PR is indistinguishable from the noise and reviewers learn to merge past it — the exact condition that made PR mode standing on 08-07. Clearing WING-517 required hand-baselining its failures in a clean master worktree; nobody should repeat that per PR.
⚪ To do — · WING-523 Paste-a-URL → tracked job, via a deterministic fetcher that is never allowed to refuse. A user-supplied URL creates a tracked record before enrichment runs and persists independent of session memory; a parse failure still leaves the raw URL tracked. Job sources / import From the Jack & Jill teardown (Nick Kaufmann's usage log, Slack #research 2026-08-13/14, + my walkthrough of the registered account 08-14). The competitor failure this closes: Nick pasted a listing URL, it was never tracked, fell out of session memory, and Jack's own web search refuses "live job listings" — so it burned ~8 visible tool calls and 3 apologies and never recovered it. Their tracker only holds jobs they sourced, because their supply is recruiter-relationship curated. Ours is ~44 ATS sources, so an arbitrary URL has to be a first-class input. Contract test must use a verbatim captured listing payload, not a hand-built fixture.
⚪ To do — · WING-524 Application answer snippet bank — user-owned reusable answers keyed by question intent, drawn on by the apply flow, visible and overridable before submit. WING-269 / apply From the Jack & Jill teardown. Nick: "it also has no features to help with actually filling out the job applications. it could have a snippet add-on function". J&J does zero form-filling — the whole category of repeated free-text (why this company, comp expectations, work authorization, accommodations, self-ID) is unserved. Agent may propose a snippet from a submitted answer, never silently overwrite one; pre-fill is shown before submit, per the on-the-rails principle.
⚪ To do — · WING-525 Document version gallery + real docx/pdf export + diff against master. Tailored documents become versioned artifacts tied to the job, not chat output. Documents From the Jack & Jill teardown. Nick, verbatim: "it does not deliver a machine readible tailored version of your resume for download in pdf or docx. it should have a gallery to track and edit different resume versions, instead it just gives you the changes and instructs you to copy and paste." He also reports it "often hallucinates or gets the facts wrong" — which makes the missing edit surface worse, not better. Export must be ATS-parseable (no image-only PDFs). Depends on WING-528 so agent commentary cannot reach the exported file.
⚪ To do — · WING-526 Cross-source job listing dedupe — same role syndicated across aggregator + ATS collapses to one record with multiple source links. Job sources From the Jack & Jill teardown: "it also commonly scans duplicate job listings from different hiring platforms". Worse for us than for them at ~44 sources, so it needs solving before source count is a selling point. Conservative by design: when uncertain, keep both and mark as possible duplicates — a dropped real listing beats a visible duplicate. Do NOT key on host+path; that key once merged every Stripe job into one (WING-372).
🔵 In review WT-328f · WING-527 Digest assembler over a last-reported snapshot — RESCOPED 08-14 from the original "per-user daily digest email"; delivery split to WING-532, scheduling to WING-533. Pure, deterministic, channel-agnostic; nothing calls it yet. On PR #76 (53001c34b + e500e872a), two-track docs docs/technical/daily-digest.md + docs/plain/daily-digest.md. Retention Investigation killed three of the original ACs before a line was written: no mail transport anywhere in the repo, no scheduler in src (zero hits for AddHostedService/BackgroundService/PeriodicTimer/new Timer(), and no captured delivery address — TenantController.cs:27 redeems every invitation with email: null. The load-bearing decision: the domain has no source-agnostic "when did this user first see this" — JobPosting.PostedAt is the EMPLOYER's date, ListingHistory.FirstSeenAt is LinkedIn-scrape only, JobQueryEvidence.FirstSeenAt is nullable, ApplicationRecord has no status-changed time at all — so the digest owns its own DigestSnapshot and diffs against it. No schema change; double-reporting is structurally impossible rather than merely avoided. Empty is a first-class SUCCESS state, and a standing interview rides along as context but can never trigger a send. Council 3/4 seats — Cedric (chair), Jenny, Gronktayvius, all seated and uncontaminated (council-seat absent on greatroom → manual disposable-checkout fallback, assert ran on every seat, both trees byte-identical); Clahadore skipped, advisory-only since Claude authored + governor BLACKOUT. Seats reviewed the CODE diff only (24.5KB) — the full 34KB diff sits in the range where Codex returns exit 0 with empty output, which scores as a clean pass. 0 Critical / 4 High / 1 Medium / 4 Low, all applied. Two Highs were real outages every one of my own tests passed straight through: DigestSnapshot used IReadOnlySet<string>, which System.Text.Json cannot deserialize — LocalJsonStore would have thrown on reload, so duplicate suppression was silently dead across restarts, correct in every test and broken on the first process restart. And a rescheduled interview was silently swallowed — the key ignored StartsAt for id-bearing rows, so a moved date kept its key, stayed not-newly-announced, and on an otherwise quiet day produced no mail; a moved interview is the one thing a digest most exists to tell you. Also fixed: legacy demo applications leaked (pre-IsDemo seeds deserialize false; empty JobId is the documented marker), the second document artifact was lost forever (per-job keys, not per-artifact), a backfilled interview id re-announced, blank application ids collided, reported interviews accumulated unbounded. Chairman dropped 12 findings as factually wrong incl. SQL timestamp truncation (no database in this product) and ephemeral interview ids (persisted immediately). Kerr finding OPEN: NewJobs.Count > 0 alone triggers a send and job count is driven by our own ingestion volume — capped at 10 with TotalNewJobs carrying the truth, but whether ingestion volume should trigger mail at all is a delivery-policy call on WING-532. Receipts: Core build 0 errors; DigestAssemblerTests 21/21; negative control (letting a standing interview trigger a send) turns it red 1/13, reverted and re-verified. Verdict unverified, not passed — full suite Failed: 10, Passed: 6722 where all 10 match WING-520's documented master breakage by name and count (none in DigestAssemblerTests, none touching Digest) but no clean-master baseline was re-run to prove name-for-name identity, and nothing calls the assembler yet so there is no runtime surface to smoke. Cannot merge until WING-520 clears.
⚪ To do — · WING-532 Digest delivery via AgentMail — client, consent, egress registration. Split from WING-527. Retention There is NO outbound mail capability in this repo — no IEmailSender, SmtpClient, MailKit or SendGrid, and "AgentMail" appears in exactly one comment (src/WorkWingman.Site/Services/DocsReaderSignInService.cs:31). This is a transport build, not wiring. Address decision (Andrew 08-14): reuse the profile Contact.Email rather than capture-and-verify. Consequence recorded because the call was made with it stated — that address was given to us to put on job applications, not for us to mail — so consent lives in the opt-in: off by default, exact address shown before the first send, changeable, one-click unsubscribe. Hard constraint: 3 AgentMail inboxes fleet-wide; one digest inbox + plus-alias per user, an inbox-per-user design fails at user #4. Must register the new egress in Core/Privacy/EgressSurfaceRegistry.cs — that registry's own comment records a prior miss where a LIVE path was filed as planned, which is exactly the failure the entry prevents. Carries WING-527's open Kerr decision: may ingestion volume trigger a send at all?
⚪ To do — · WING-533 Digest scheduling — Cloud Scheduler trigger, not an in-process timer. Split from WING-527. Retention The API has no scheduling mechanism of any kind today. Not a BackgroundService: Cloud Run throttles CPU outside a request and the pilot services scale to zero, so an in-process timer would appear to work locally and silently not fire in production — the same class as WING-420, correct-looking code addressing the wrong runtime. Shape: Cloud Scheduler → an authenticated endpoint that walks due users; desktop, single-user and local, may use a timer. Idempotency comes from WING-527's snapshot, not from hoping the trigger fires once — Cloud Scheduler retries. Empty assembly is a counted no-op, one user's failure must not abort the run, and the schedule is per environment so no digest fires from a QA rung at a pilot user.
⚪ To do — · WING-528 Front-stage/back-stage output scrubber + contract test — strip agent-directed commentary from every artifact before it reaches a user or an employer, and assert the negative. Documents / quality From the Jack & Jill teardown. Nick's framing (Goffman): an LLM has no structural wall between back-stage instruction and front-stage deliverable, so operator-directed text leaks into the artifact — he reports it on every model he has used. Our blast radius is a cover letter reaching a hiring manager with coaching text in it. Scrubs the artifact, never the chat message (coaching belongs in chat). Strips are logged so leak rate is measurable per model.
⚪ To do — · WING-529 Explicit read-only scope disclosure on LinkedIn connect (and every other connection) — specific about what we read, explicit that we never write, post, message or edit. Onboarding / trust From the Jack & Jill teardown. Nick rated their onboarding well but flagged one gap, verbatim: "i needed some reassurance it wasnt going to do anything on linkedin on my behalf or edit my linkedin profile for me". Free to give, expensive to omit — a user who suspects we might post as them will not connect, and profile import is the fastest path to a populated profile. Claim must be enforced, not just stated: test asserts no write scope is requested.
⚪ To do — · WING-530 Agent must fail honestly — never substitute its default capability for the one that was asked for. Agent behavior / trust From the Jack & Jill teardown; filed by Clahadore alongside the seven Andrew asked for, because it is the behavioral root of Nick's most-repeated complaint and belongs in no single one of them. Nick: "it often reverts to searching for more jobs when it cant process a request / which is really grating" — at its worst Jack launched an unrelated search mid-task and then apologized for being "tone-deaf". The rule: say what you cannot do, name why, offer the surface that can. This is where chat-mutable nav pays off. Covered by behavior tests over tool-failure paths, not prompt wording.
⚪ To do — · WING-519 Startup validation: fail configuration when the configured LLM provider cannot operate in this deployment. Raised as the council's Kerr finding on WING-517: both guards that ticket added reward a structural proxy ("no un-allowlisted constructor", "the id isn't in the CLI preset list") while the outcome we want is operational availability. A provider id that is neither a CLI preset nor actually usable passes both — the same looks-configured-but-inoperable defect WING-517 fixed, relocated into the tests meant to prevent it. Cloud edition Constraints written into the ticket so this doesn't get built naively: probing is async and a probe can cost a 5s Ollama timeout (the router caches availability for 10s precisely for that), so a startup sweep of every harness would add seconds to a user-visible Cloud Run cold start — validate the configured default, not the fleet. Must distinguish not registered (configuration error, fail startup) from registered but currently unreachable (transient — must NOT fail startup, or an upstream blip becomes our downtime). Desktop/BYOK with no provider configured has to keep starting. The council explicitly preferred degraded-capability reporting (the existing ProjectChatCapability "locked" shape) over fail-fast for a BYOK-optional product.
🔵 In review WT-73d6 · WING-517 In cloud the default AI provider resolves to a harness that cannot run there. Cloud sets no WorkWingman__Llm__Provider, so the default stays the literal "claude" — the CLI harness id, not the key-backed anthropic one — and the Cloud Run image ships no CLI. LlmHarnessSelector.Default returns that registered-but-unavailable harness (the preset is registered without probing availability, so the FirstOrDefault fallback never fires). LlmRouter picks by availability and is fine; three call sites bypass it and use Default directly: InterviewQuestionCoachService.cs:64 and :118, AudioOverviewService.cs:104. In cloud those degrade silently — null is the frozen "advisor unavailable" contract, so nothing surfaces. Cloud edition Found while verifying WING-510's key wiring; the key itself is fine (both rungs mount latest off enabled secrets, live probe PROD/QA both HTTP 200 on claude-sonnet-5). Council opus tier, degraded 2/4 seats — Cedric + Jenny answered, Gronktayvius timed out at 300s, the Clahadore seat AND the chairman seat both failed exit 1, so the synthesis was chaired by hand. Both surviving seats and both peer reviews ranked identically: config pin + route the bypass sites + an architecture test. Availability-aware Default was rejected — it is a synchronous property and CheckAsync can cost a 5s Ollama timeout (why the router caches availability for 10s), so probing in the getter means sync-over-async or a stale cache; the long form is to delete Default for an async resolve. Fail-fast startup was proposed by Jenny and overruled by Cedric + both reviews as too severe for a BYOK-optional product — use the existing ProjectChatCapability "locked" degraded pattern. Chair correction, verified at the producer: both seats called direct-Default a governor bypass; MeteringLlmHarness does enforce a hard Allowed=false on both paths, so what is actually skipped is the soft steer (PreferCheapOrLocal, Red/Blackout redirection) and the RoutingDecision audit row. Note for the implementer: LlmProviderPreference loads a persisted llm-provider that overwrites the config seed, so the pin is a no-op wherever a preference was saved — and in server edition that preference is process-wide shared (WING-208). Landed on PR #70 (e826d57a4 + council fixes 2701d6d4f), awaiting reviewer approval + CI. Council 3/4 seats — Cedric (chair), Gronktayvius, Jenny; Clahadore FAILED (usage-governor denied the claude -p call, advisory-only since Claude authored). Chairman: 1 High, 1 Medium, 3 Low, all applied. The High was mine and only Cedric caught it: routing the coach through ILlmRouter made exhaustion look like an empty answer, and since InterviewCoachingController frees its chat_turns reservation only in a catch, every-provider-failed became a 200 with a spent quota — the controller's own comment says "failed generation frees the reservation". Now throws LlmRoutingExhaustedException, with Attempts separating "tried and failed" from "none eligible". A guard I wrote to stop regressions had the very property it was written to stop: SafeGetTypes failed OPEN on a partial type load while its comment claimed the opposite, so a forbidden consumer that failed to load would have gone green. Receipts: build 0 errors; affected filter 39/39; full suite 6704/6714 with the same 10 failures reproduced on master in a clean worktree (1166487d8), so none are from this diff; 3 negative controls, each firing its own guard. Verdict unverified, not passed — the diff touches tests and the cloud path can't be exercised locally (prod /api/runtime-capabilities is 401). Kerr finding split to WING-519. Three commands exited 0 while doing nothing during this work (a filter against a missing project path, a --no-build run against a stale assembly, and a baseline whose build was blocked by a file lock from the running WorkWingman.Api) — each would have read as a pass on exit code alone.
⚪ To do — · WING-516 Cloud pilot serves one request at a time — raise ww-yc container concurrency from 1 to 8. Cloud edition Filed 2026-08-14.
⚪ To do — · WING-515 linux-arm64 target — arch dimension in the payload paths, cross-build on greatroom's x64 WSL, native smoke on the Surface. Delivery / packaging The Linux lane has no architecture handling at all today (verified 0 grep hits for any arch token across electron/package.json, make-brand.ps1, main.js), so the dimension costs one matrix row now and a refactor later. Build-host/test-host split carried unchanged from WING-501. AC3 is the load-bearing one: assert the ELF machine type of every shipped binary — WING-501's arm64 installer wrote 503 of 756 files, dropped every ARM64 PE, exited 5 and reported success; the file-by-file manifest caught it, not suspicion. No arm64-Linux host exists in the fleet yet — installing WSL on the Surface is a prerequisite, not an assumption. Sequencing: electron/package.json is held by WT-2399's live WING-278 lane.
⚪ To do — · WING-514 Broker: connect-purpose drive target + cookie readback, so cloud connect can use a fenced session. Cloud edition Filed 2026-08-14.
⚪ To do — · WING-513 Docs CI still red on master — "References completeness" fails: System.Text.Json undocumented in references.md. Documentation Filed 2026-08-14.
⚪ To do — · WING-512 Harden docs CI around docs/discovery/ — declare the pairing exemption explicitly and add an inventory gate. Documentation Filed 2026-08-14.
⚪ To do — · WING-511 Cloud data export — one-time secure download link for the passphrase-encrypted export. Cloud edition Filed 2026-08-14.
⚪ To do WT-73d6 · WING-510 Claude in cloud is our AI usage, not a user connection — remove the connect card, add metering. Desktop stays BYOK via the nested CLI terminal; cloud runs on our key and billing via the Claude API, so there is nothing for a cloud user to connect, authenticate or set up. Explicitly rejected: a streamed CLI terminal in the tenant pod, relaying to the user's desktop CLI, and prompting a cloud user for an API key. Cloud edition Split from WING-505. Metering is an AC, not an optimisation — every cloud tenant's spend lands on our usage. Verification pass (WT-73d6, 2026-08-14) established the ceiling: we cannot mint a key per tenant (Admin API: "new API keys can only be created through the Claude Console"), and usage/cost reporting has no end-user dimension (groups by workspace / api_key / model / service_tier only) — so per-tenant attribution can only live in our app, and no vendor or GCP feature retrofits it. The door already exists: MeteringLlmHarness wraps every harness, writes one ledger row per call via IAiUsageRecorder, and hard-enforces IUsageGovernor. Missing: a tenant dimension on the ledger row, and a per-tenant budget instead of the current global deny. Both blocked on WING-208 — the server store is a process-wide singleton at /data, so there is no per-tenant state to hang a quota on; anything built before it is global-with-extra-steps. If enforcement ever needs to leave the process, Apigee's LLMTokenQuota policy is the right shape (per-app token quotas, 429 at limit) but still needs the app-side tenant identity first. Each cloud user getting their own Muse Glimmer instance (not yet built) is the natural carrier for that id.
🟣 Automated review WT-463f · WING-463 [SPIKE] Linked household accounts and PCS-aware family job comparison — independent adult accounts publish revocable, scenario-scoped projections into a household coordination realm; first-class trailing-spouse workflow; no shared-account merge, partner control, hidden composite score, or inferred military eligibility. Household / relocation Architecture and two-track docs drafted on wt/WT-463f/WING-463. Hard prerequisites: stable provider subject identity and tenant-scoped private stores; current process-wide legacy stores remain fail-closed. Venture gate: recent-PCS behavioral discovery, then a synthetic-data consent prototype; demand is not yet claimed. Required security review is running against the post-fix tree.
🟢 Done WT-a225 · WING-404 The browser rung was built, tested, registered in DI — and nothing ever called it. Rungs 0-1 (read the url, follow 3xx) ran in production; rung 2 did not, so resolve silently meant "resolve the easy ones" and an unresolved row is un-appliable because EngineRegistry.Resolve(Unknown) returns no engine. A pasted Adzuna tracker names no vendor and its redirect chain ends at a page that only reveals the employer once rendered, so those rows resolved to nothing at all. Now escalates behind deep=true, which only the import page sets — the search grid upgrades every visible card automatically and must never launch a browser for rows the user merely scrolled past. Job sources / import Two tranches: the ladder + Jooble + bulk import (ed3a42bb9, live on all four surfaces, verified by WT-0f7d at the SERVED asset), then this wiring (69dff9f82). Council CHANGES REQUESTED, 3 High, all fixed — seats Cedric (chair) / Jenny / Gronktayvius, quorum 3; Clahadore seat FAILED (governor blocked the Opus spawn, then claude -p hit the argv cap at 21KB) and was advisory-only regardless since Claude authored. The worst finding was mine: the wiring made the browser path a BYPASS around the redirect resolver's SSRF policy — default-ports-only, public-DNS and per-hop revalidation lived privately inside RedirectChainAtsResolver, so the browser path carried a shape check alone. A hardened path and a soft path over the same user-supplied url means the soft one IS the policy. Extracted to OutboundNavigationPolicy, shared by both, plus Playwright interception that re-checks every top-level navigation BEFORE it leaves (reading page.Url after is too late — the request already happened). Also: deep resolve drove the profile holding real LinkedIn/Workday logins at pasted urls (own profile now), and "one page at a time" was a comment not a control (lease now). Two lessons banked. (1) job-search.scss had an unclosed block so the Angular bundle would not compile AT ALL while 2010 tests passed green — only running the app finds that class. (2) A mid-run git checkout against a live worktree makes vitest finish SHORT and still report all-green: a "133 files / 1852 tests" run looked exactly like this branch reverting WING-403. Static vitest list showed 2026 declared vs master's 2023 — a strict superset — and a clean re-run gave 134/2026 matching exactly. Compare against a baseline run of master alone before believing a count drop. Receipts: build 0 errors, backend 6134/6136 (2 known load flakes, both pass isolated), frontend 134 files / 2026 passed, bundle generation clean 31.6s, negative control 3/6 fail with the wiring removed. NOT click-through verified — blocked on WING-423, filed from this ticket and now the unblocker for the fleet-wide browser-test directive.
🟢 Done WT-e833 · WING-420 Telemetry, surveys and tutorial progress were silently dead for every CLOUD user of the shipped build. Three clients hardcoded workWingmanApiUrl ?? 'http://127.0.0.1:5211'; only Electron injects that global, so on cloud all three addressed the end user's own machine. Status 0, nothing logged, features looked present. The only visible symptom was desktop copy ("Can't reach the local engine") on the cloud sign-in page — found by browser-testing the SHIPPED WING-372 build, root-caused with WT-0f7d. Cloud edition Routing was only half. The telemetry transport is raw fetch/sendBeacon, so the CSRF interceptor never runs and /api/metrics/events is not in TenantSessionMiddleware.IsPublic — the obvious fix would have moved the outage from "status 0, dropped" to "403, dropped" and closed the ticket looking correct. Caught by the council, not by my two self-review passes. Now sends X-WW-CSRF + credentials:same-origin and skips sendBeacon when a grant exists (beacon cannot set headers). Flagged to Andrew, not buried: this ACTIVATES a data flow dormant since launch — no cloud user's telemetry has ever reached the server. Round 2 found that once it does, a logout inside the 15s buffer flushes tenant A's events under tenant B's cookie; buffer now dropped and session re-minted on tenant change. The guard I wrote to prevent regression was itself broken: its comment-stripping regex treated // in http:// AND in regex literals like /^[a-z]+:\/\// as comments, deleting 12.7% of scanned text and failing open on four shipped files — it PASSED while the bug was reintroduced. Only a negative control found that; it parses the TypeScript AST now. Landed 1a9e98847. Receipts: vitest 134 files / 2024 passed, prod build clean, 5 negative controls each proven to fail for its own reason, browser verification against the real production bundle (zero requests to :5211, cloud copy replaces the desktop toast). Follow-up WING-424.
🟢 Done WT-e833 · WING-403 "Returned an empty list" was indistinguishable from "never had an API key" — which is how Adzuna sat dormant in production for days looking healthy. IJobSource/ISearchableJobSource now return JobSourceResult: Ok / NotConfigured / Failed. Job sources Gated twice, then sat unlanded while master moved 68 commits — WT-3145 caught that it was not on master while I believed it was, and that a local commit was an orphan on no remote branch. Both correct. The rebase was not mechanical: WING-404 had inlined fetch, mapping and a new fail-soft ResolveAtsAsync step exactly where this branch delegated to TryAsync (kept WING-404's body, changed only the returns), and JoobleSource — added by WING-404 after this branch was written — had to join the contract. Its own comments already argued this ticket's thesis. The defect the gate caught was mine: my adapted tests read .Jobs then Assert.All, which passes vacuously on an empty collection — a Failed outcome would have turned every one green while the adapter returned nothing, the exact failure this ticket exists to end. Gronktayvius named the blocker (Jooble had zero Failed/leak tests while production grew that surface); three added. Both Gemini seats raised a CRITICAL claiming exception handling was dropped — refuted by a passing test, a same-family correlated miss worth recording. Landed 8bea2dfe5 + 03aa20f42. Receipts: build 0 errors, WorkWingman.Tests 6090/6091, 2 negative controls. 3 remaining failures are pre-existing on master → WING-425. Follow-up WING-426. Downstream WT-3145 notified.
⚪ To do — · WING-427 Jooble's key is a 500-request LIFETIME cap and nothing counts against it — enforcement today is Jooble refusing us, which is the worst place to discover it. Needs a visible remaining-count and a hard stop. Also owns the partnership ask: draft at docs/business/jooble-partnership-draft.md is written and NOT SENT (Andrew has final say; worth raising with Pam or David P). Job sources Split from WING-404. The source is registered ISearchableJobSource ONLY — an IJobSource registration puts it on the background sync TIMER and would spend a user's whole allowance without them ever searching; do not "tidy" it into the other sources' pattern. Verified quirks: id is a NUMBER (a string binding threw and a catch-all swallowed it into an empty result), and the source field names the ORIGINATING BOARD — free provenance Adzuna does not give us, useful to WING-372.
⚪ To do — · WING-428 Do not build Talent.com until a measurement says it is worth building. Jooble's source field names talent.com as an UPSTREAM FEED, so a third integration may buy duplicate rows plus a third key, failure mode and rate limit for little new coverage. Job sources Split from WING-404. Ticket specifies the overlap measurement and a decision gate in BOTH directions — high overlap closes it as "won't do, with evidence" so it is not re-litigated later. Key on AtsPostingId, never host+path (that key once merged ALL Stripe jobs into one). Budget the sample against WING-427's 500 lifetime cap and state what it spent; ~30 queries answers it, do not sweep.
⚪ To do WT-cce5 · WING-369 Design landed, implementation not started — 91628fc68, two-track (docs/technical/honesty-guard-target-company.md + docs/plain/…). The honesty guard rejects any org the profile doesn't attest, and the company being APPLIED TO is by definition absent from the profile — so an ordinary sentence ("the Platform Engineer role at Kohler Group") rejects the ENTIRE draft and the user silently gets an untailored template even with a healthy LLM. Cover letters name the company constantly, so this fires on most drafts. Documents Three seats worked it independently and reached the SAME shape: stop treating the cover letter as one free-form blob — the model returns labelled fields, the app owns assembly, the renderer owns every target-company mention. Principle both broken designs produced: using the target value to REJECT is safe; using it to REDACT or EXEMPT is not (exempt-by-name also exempts "I worked at "; mask-by-value turns posting-scraped text into a redaction pattern — that is how appended text rode through unscanned in WING-333). Interim, small and shippable: model writes a target-free body, composer supplies salutation/opening/closing from reviewed templates, any occurrence of the target name in model prose = rejection. Target state: constrained document plan over server-issued opaque fact IDs — also reaches claims the entity regex cannot see at all ("I led the migration", "ten years of experience"). Rejected: grammar classifiers (do not survive relative-clause laundering / anaphora / inverted syntax) and rewrite-until-it-passes loops (optimize the model into the guard's blind spots). Six residual attacks tabled. Seats: Cedric, Jenny, Gronktayvius (adversarial, broke his own proposals). No Claude seat — governor RED.
🟢 Done WT-e833 · WING-372 Ticking "Remote only" returned filtered and unfiltered jobs as one list. RemoteOnly was honored in exactly ONE place — the decorator wrapping Greenhouse/Ashby/Lever; Adzuna and USAJOBS never referenced it, and everything merged into one flat list. SalaryFloor was applied by nobody. Landed f4fd03e72 and DEPLOYED to prod/qa/both pilots (digest sha256:9124c390…b38c). Job search Sources now publish what they OBSERVED as SearchEvidence with provenance, and one central JobSearchCriteriaEvaluator judges every row — no "already filtered server-side" exemption, because a self-declared exemption is how this shipped. Four outcomes: "the source says no" (drop) and "the source never says" (disclose) are different facts. Unconfirmable rows go to a separate, counted, opt-in disclosure, never merged into results. Three bigger defects surfaced: (1) USAJOBS had never returned a single job since it shipped 07-24 — UserArea.Details.MajorDuties is an ARRAY, the DTO said string?, so every response aborted and the degrade-to-empty catch swallowed it; four more field drifts sat behind it, all found by capturing a verbatim payload from the live producer. (2) Adzuna salary is a MODEL PREDICTION (salary_is_predicted="1", min==max on every captured row) — the pushdown a design council recommended would have filtered a user's pay floor on invented numbers; rejected with the capture as receipt. (3) WorkModel defaulted to Hybrid that no ingest path ever set — now Unknown, with read-time repair for rows already on disk (WT-a37c independently saw this as a facet reading "Hybrid (68)" for all 68 saved jobs). Found by RUNNING THE APP, invisible to every test: a Stripe role located in Japan sat in the confirmed-remote list — CanonicalApplyUrl keyed on host+path while Stripe puts identity in the query (?gh_jid=NNN), so ALL Stripe jobs collapsed to one key and the survivor inherited the evidence of everything it swallowed. After the fix: 20 distinct Stripe roles instead of 1, 0 confirmed rows without real evidence. Fixing it failed two of my own tests that had passed because of the bug. Receipts: live UI vs real Greenhouse boards — Showing 25 of 174 confirmed · 571 couldn't be checked, Greenhouse found 433 / confirmed 158 / couldn't confirm 275 / excluded 0 (158+275+0=433). dotnet test 5851/5853 (2 pre-existing, +77 tests), vitest 1981/1981, ng build clean. Council 3 rounds, 9 seats, 25 findings fixed — most mine, several introduced while fixing earlier ones; one chairman ruling overridden with evidence. Follow-ups: WING-373 (cross-source paging), WING-403 (a down source reads as an empty one), WING-331 repro.
🟠 Needs human review WT-cce5 · WING-333 Documents page shipped a placeholder instead of a resume — every work-history profile got a two-line "(Resume body generated from intake profile — connect Claude for full tailoring.)" while the person's work history, education, certifications and story answers sat unused in the profile. Connections reported the Claude CLI logged in from a credentials file holding EMPTY tokens, and every generate failure collapsed into "no AI writing engine was available". Landed master f7ad60364. Documents New ProfessionalDocumentComposer builds a real resume + cover letter from literal profile fields — selection only: no computed tenure, no rated proficiency, recency/tense claimed only when the record shows a role current, posting keywords used only where the profile literally records them (whole-phrase, Prove-validated skills only), expired/unconfirmed certs labelled. ClaudeCredentialState decides auth on a live token, not a file existing; fails open on unrecognized shapes, and Unknown surfaces as unverified rather than authenticated. ILlmDiagnosticHarness keeps the real failure reason — and MeteringLlmHarness now forwards it: it wraps every harness, so the downcast always failed and the whole feature was unreachable in production while its unit tests passed (WING-305 shape). User-facing text is stderr only, sanitized, capped — no ex.Message, no stdout. Generated filenames follow the user's name (both were hardcoded to Andrew's, and a test pinned that as correct). Council, 2 rounds: round 1 = Block, 8 findings; round 2 found 3 Criticals in my own round-1 fix — masking by attacker-controlled value (company/title are scraped) let text appended to a Target role: line ride through unscanned, laundered an unattested promotion at a real employer, and could blank a large span of the document. Replaced with exact whole-line comparison against lines the caller builds. Found by RUNNING THE APP, invisible to 5,829 passing tests: every employment date was being deleted from the résumé (guard attested only ISO YYYY-MM while the profile stores "Sep 2025"), and a posting titled "… Machine Learning Validation (PhD)" was read as Andrew claiming a doctorate. Receipts: Failed: 1, Passed: 5829 (the 1 pre-existing); regenerate 200; warnings 11 → 1. Needs Andrew: (a) I wrote documents into REAL data — dotnet run silently applies the — Real launchSettings profile and overrode WorkWingman__Environment=sandbox; one ADDED entry in ~/Wingman/data/documents.json (General Motors), nothing of Andrew's overwritten, removal awaiting his call; (b) restart WorkWingman to reclaim the api-token I clobbered (WING-331); (c) browser-initiated WRITE still unproven (API + page render are). Follow-ups: WING-369, WING-370, WING-371.
🟠 Needs human review WT-cce5 · WING-371 Document drafting timed out every time, so documents were never AI-tailored — all task classes shared one unmeasured 45s per-call budget. Landed 92678bbf6. Documents MEASURED, not guessed: a prompt of the real shape/size (~9.9k chars) took 104s, exit 0, valid JSON with both documents — on a prompt SMALLER than production's (no company research, no writing samples). 45s never had a chance. TailoredDocuments now gets 240s; every other class keeps the provider default, since a multi-minute ceiling on small prompts turns a wedged CLI into a long hang where a fast deterministic answer exists. 240s is explicitly PROVISIONAL and the source says so — one observation, one machine, one model; p95-over-logged-durations is the follow-up. Gate caught two real defects in my first cut: Timeout.InfiniteTimeSpan is -1ms, so providerDefault < budget read "wait forever" as "shorter than 4 minutes" and replaced it (Jenny); and there was no upper bound, so a future long preset would become a silent multi-minute wait — added AbsoluteMaximum (Cedric). Tests now enumerate the enum instead of hand-listing, and cover the argument-list RunAsync overload grok/codex/agy take — wiring only the string overload would have left them on 45s while claude-path tests passed (same shape as the WING-333 metering-decorator bug). Deferred, not dropped: one end-to-end deadline with per-attempt remaining time; coalescing repeated Generate clicks; process-tree kill on cancel; progress UI for a 4-minute wait. Receipts: Failed: 2, Passed: 5837 — both the known load-flake family (WING-370). Seats: Cedric, Jenny, Gronktayvius.
🔵 In progress WT-cce5 · WING-370 Suite flake — original diagnosis DISPROVEN, ticket stays open. Landed 0231bd6b7: harness tests no longer mutate process-global %USERPROFILE% (they use the CliLlmHarness home/env seams). Worth keeping, but it does NOT fix the flake. Testing Three consecutive full-suite runs WITH the mutation already removed failed 2/3/4 tests — a different set each time (MsLearnCatalogSource, TranscriptPreParser, JobSourcesResync, LocalVoiceLive, Grok_ConcurrentProbe, DataBackupExport), mostly timeout signatures, while run time went ~1m20s → 6–9 min on a box carrying 34 claude.exe processes. Cedric also showed the mechanism could not have worked as described: xunit's CollectionDefinition(DisableParallelization = true) DOES serialize against other collections. And AppEnvironment resolves the profile via the known-folder API, which ignores %USERPROFILE% entirely — so the export test's directory never moved. Points at suite-wide timing fragility under resource starvation (FLT-192 territory), not one bad test. Next: re-run 3× on an idle box before touching any test; TranscriptPreParser failing in 234ms is the one that cannot be load-explained. A source-scanning guard test was written and dropped — all three seats judged it unsound (defeatable, non-hermetic); belongs in an analyzer.
🟠 Needs human review WT-75b4 · WING-334 Multi-target delivery pipeline + per-surface QA matrix — docs landed master 0440e46ef, fanned out to all PCs. One repo, trunk-based, one release pipeline; targets become packaging jobs over shared artifacts (stage 1 builds shared code once per SHA and uploads → stage 2 packages each surface without rebuilding product source → stage 3 releases by promotion). Adding mac/linux/mobile = one packaging job + one manifest row. DevOps SCOPE CORRECTED MID-TICKET by Andrew: the app architecture is settled (Cloud Run → GKE Playwright runner, sticky egress IPs, mobile screen streaming, desktop editions) and the real question was DevOps — how one change reaches every version without rework. The architecture prescriptions were removed; delivery only. Root finding (verified): every target rebuilds shared code today — Dockerfile:9 and electron/build-installer.ps1:45 each run npm run build independently, then both dotnet publish the same API project. Five SPA-bearing surfaces are planned and nothing forces any two to ship the same bits. There is no release workflow at all. QA: uniform artifacts make a QA result transferable AT THE SHARED LAYER only — that is what bounds the QA lab's per-device matrix, and growth in that matrix is an alarm that something shared leaked into one target. All four docs state the pipeline is PLANNED, NOT IN FORCE. Gate 4/4 seats over two passes, and the seats reversed real content: Gronktayvius — “no packaging job contains a compiler” is FALSE for mobile/Electron native shells (invariant is no rebuild of product source), runner+site do not carry the SPA, promote-by-digest does not extend to installers, QA transferability oversold; Jenny — the proposed CI Dockerfile variant contradicted this doc's own duplicate-Dockerfile finding, and nightly would have held PRODUCTION SIGNING KEYS while packaging unreviewed trunk; Cedric — the GKE runner is NOT a separate artifact today (Dockerfile:52-55 installs Chromium into the Api image), mobile ships frontend only, and the QA matrix omitted per-platform FUNCTIONAL smoke despite real OS-dependent branches (LlmHarnessRegistration.cs, Windows SAPI path in LocalVoiceSynthesizer.cs). Needs Andrew: WING-341/343/344 are runtime-architecture tickets from the over-scoped brief — close or keep? WING-345/347/349/350 are delivery and stay. Also WING-356: docs CI gate red on master (19 unpaired two-track docs, pre-existing).
🟢 Done WT-3aca (+ WT-5f2c) · WING-328 Installer silently no-ops on /S — exit 0 while nothing installs. AC1/AC2/AC3 all closed, landed master 4715aba95 (WT-3aca) on top of 551691548 (WT-5f2c's InstallLocation mirror + verify-install.ps1 receipt). Real mechanism was a POISONED, SELF-PERPETUATING REGISTRY VALUE, pinned with an InstallDiag breadcrumb rather than inferred: stock NSIS accepts a non-absolute /D (incl. drive-relative C:BadPath, which looks absolute but resolves against the CWD); that run uninstalls the working app, extracts nothing, then persists the bad path to HKCU\Software\{APP_GUID}\InstallLocation and exits 0 — so every later bare /S reads the poisoned value back (multiUser.nsh:26), installs nothing, exits 0 forever on a machine that now has no app. /S /D=<good path> appears to fix it, which is why it read as a path quirk. Fix: preInit sets a non-zero error level that only customInstall clears after confirming the exe is on disk (fails closed against all three stock silent-Quit paths without patching node_modules); customInit validates $INSTDIR before uninstallOldVersion can run and REPAIRS a poisoned value so a stuck machine heals from a plain /S; customCheckAppRunning re-validates after the directory page can rewrite $INSTDIR. electron/test-installer-exit-codes.ps1 asserts invariants, not just non-zero: exact exit code + diag stage, zero files written, existing install and registry byte-identical, plus -IncludeRecovery for the poisoned-registry heal. Both prior root-cause theories (mine: empty Uninstall InstallLocation; WT-3aca's first: elevation Quit) were wrong — the empty mirror was real but incidental. Council: Cedric chaired, Jenny + Gronktayvius seated, all three caught a drive-relative-path hole in the first cut. Desktop Found by WT-9e37 in the Train 2 desktop click-through, 2026-08-05. False success signal — an agent or human sees exit 0 and reports "desktop updated" while the demo runs a days-old build; cost one round trip today. WING-327 is a duplicate (filed by WT-5f2c before finding this one; linked, WING-328 canonical). ROOT CAUSE IN THE ORIGINAL REPORT IS WRONG — derived independently by WT-5f2c and WT-3aca from the electron-builder 25 templates: /S resolves InstallLocation from INSTALL_REGISTRY_KEY = Software\{APP_GUID} (multiUser.nsh:26), NEVER from the Uninstall entry, and no stock template writes it there — the empty value is stock behavior, real but NOT the no-op cause. Actual mechanism still unpinned; both leading candidates ruled out (the elevation Quit at installer.nsi:96-110 needs hasPerMachineInstallation, and HKLM is absent; the app-running Quit auto-answers /SD IDOK and proceeds). Remaining suspects: Start-Process without -Wait sampling mtimes early, or the app being open. Landed master 551691548: electron/build/installer.nsh mirrors InstallLocation into the Uninstall entry (helps external upgrade/inventory tooling) + electron/verify-install.ps1 receipt ([version]-normalized — ProductVersion is 4-part 0.2.1.0 vs 3-part package.json; -SelfTest pins it). Council gate: Jenny + Gronktayvius seated, Cedric member seat FAILED (exit 124), Cedric chaired; one High fixed. AC3 receipt: exe mtime advanced 8/5 06:32:14 → 09:34:58 on a bare /S with no /D, exit 0; made unambiguous by deleting resources/api/WorkWingman.Api.dll and re-running bare /S — file restored, exit 0, verify-install.ps1 → VERIFY OK. Andrew's app installed and launching. INCIDENT during WT-5f2c's AC2 attempt: a /S /D=<ACL-denied dir> test whose .onInit guard failed to fire let the installer reach uninstallOldVersion and uninstall the real greatroom install (API/frontend/Playwright/uninstaller + Software\{GUID} key); restored and verified, user data never at risk (deleteAppDataOnUninstall:false). That failure is now WING-328's acceptance case, and its pass condition includes "leaves the existing install and running app untouched" — a guard exiting non-zero after uninstallOldVersion would satisfy a literal AC2 while still destroying the install. STANDING HAZARD: a pre-fix installer run with a bad /D is destructive AND poisons the registry — rebuild before running any stale electron/dist artifact. Three further WT-5f2c conclusions were wrong and are corrected on the ticket: grepping a built .exe for macro strings is invalid (NSIS LZMA-compresses them); registry reads flip-flop while installer inner-instances finish after -Wait returns on the outer, so only quiesced reads count; and the WebRootPath ...\wwwroot log line is cosmetic (desktop serves the frontend from app.asar; no build in the tree ships a wwwroot) with the 3126→1341 file delta being runtime accumulation, not lost hand-built state.
✅ Done WT-53c6 · WING-329 Unprompted "Something went wrong / The local engine returned an error" toast fires on the demo path — seen navigating Saved jobs → Switch demo persona Desktop Fixed b8081a86c. Root cause was STRUCTURAL, not a late-settling background call: on desktop there are no tenant sessions, so GET /api/tenant/session answers 401 every launch by design (capability-resolver.ts:37 treats 401/404 as confirmed absence and proceeds). The global interceptor toasted every HttpErrorResponse, and demoPersonaGuard resolves capabilities through that helper — hence the toast on the click into the picker while personas still loaded fine. Connections/Settings/DiscoveryFilters/StudyCast/GuidedDemoService shared it. Fix: new ERROR_SURFACE HttpContext token; session read silent for 401/404 only; connectivity failures (status 0, timeout) NEVER suppressed; orphan rule GET/HEAD-only and stands down mid-navigation; 401 copy chosen by destination (cloud says sign in, not restart). Council reshaped it — the first draft hid a dead engine on the demo screen and silenced failed writes. 1928 tests green. No screenshot (pane would not composite; computer-use denied) — evidence is DOM/console.
✅ Done WT-2a00 · WING-330 Identity chip stays "Andrew Jones · Premium verified" while inside a demo persona workspace. Landed b7c866821. Option A: new GET /api/demo/active-world resolves IAppEnvironment.Name PER REQUEST; persona display name from the seed provider, never a controller literal. DTO carries switchesWorlds because the CLOUD edition deliberately never moves worlds — without it the client cannot tell "you are in your own world" from "this server has nothing to say", and either guess reintroduces the defect on one edition (council rejected "explicit real always wins"). Desktop AC1 veteran/switcher/firsttimer + persona→persona verified in the running app (ME/Marcus, NC/Nia, EB/Elena, "Demo persona"); AC2 reset → AJ/Andrew Jones; AC3 cold start with {"ActiveWorld":"veteran"} on disk → real, chip shows the real account, pinned by a test whose pointer is written by the real switch path. Council 2 rounds / 4 seats (Cedric chair, Jenny, Cyber Jenny, Gronktayvius; Clahadore seat FAILED both rounds on expired OAuth — advisory only, quorum unaffected). Round 2 caught the round-1 fixes reopening the defect in the cold-start window, and a verified signal the chip computed and ignored. Kerr: my own tests rewarded "the call was made" while hoping for the identity — rewritten to assert rendered identity. Failure now reads UNKNOWN ("? / Workspace / Identity unavailable"), never the account holder; a later failed read keeps the name but says "Identity unverified" — both fault-injected and observed. dotnet test 5774/5776 (2 pre-existing, reproduced on clean baseline), vitest 1974/1974, eslint clean. Follow-ups: WING-331 (shared api-token clobber), WING-332 (cloud persona lost across reload).
🔵 In review WT-5eca · WING-331 Shared api-token file: ANY dev API launch rewrote %LOCALAPPDATA%\WorkWingman\api-token and broke the installed app's auth — on any port, so port hygiene did not mitigate it. Fixed 2356a0cc2 on wt/WT-5eca/WING-331 (pushed, fleet fan-out). The API now picks its token file from HOW it was launched and HOW it was built, never from a path string: dotnet-hosted or a Debug-configuration apphost → …\WorkWingman\dev\api-token; anything else (the Release apphost the installer ships) → the installed file; WORKWINGMAN_TOKEN_PATH still wins. Unknown inputs resolve to INSTALLED — the failure this must never cause is a working installed app losing its token bridge, so an already-installed build is unaffected without reinstalling. electron/main.js states its choice on both spawn branches: packaged owns the installed token, a source checkout (npm run dev) uses the dev token for the sidecar AND its own ww:get-api-token bridge. Startup logs the token PATH, never the value. Council reshaped the design twice: the first cut classified on bin\Debug|Release PATH SEGMENTS — both Jenny seats rejected that (CWE-654/807: a real install under a directory named that way would be misread as dev and lose auth) and Cedric caught that the Electron dev branch still clobbered. Chairman final: 0 Critical/0 High, 1 Medium + 3 Low, all applied. Kerr accepted: the rule rewards a proxy for "no dev launch alters the installed credential", so every launcher we own pins the variable explicitly and classification is the safety net, not the contract. Seats 4/5 — Cedric (chair), Regular Jenny, Cyber Jenny, Gronktayvius; Clahadore FAILED (claude CLI OAuth expired, advisory-only, Claude authored). Residual documented + accepted: a Release build published to a custom dir and run by hand still writes the installed token (pre-existing, not a regression). Fleet/dev The defect was observed live while fixing it: a peer worktree's pre-fix dev API (started 23:39:55) rewrote the installed token at 23:40:11 — the reported mechanism, reproduced unprompted. Receipts: dotnet test WorkWingman.slnx → 5850/5851 in WorkWingman.Tests, the 1 failure passes 3/3 isolated (load flake); ApiTokenTests 29/29; every other red attributed by stashed-baseline A/B (2 SimpleModeJourney E2E + LocalRagLiveSmoke reproduce identically without the diff; the MetricsController rate-limit test keys on a wall-clock minute and passes 4/4 both sides). Live smoke: built exe on a free port wrote the dev token, gated route 401 without it and 200 with it, installed token byte-identical (sha 8F7AAD8B…, mtime unchanged).
⚪ To do unassigned · WING-332 Cloud: identity chip loses the persona across a page reload — server correctly reports real/switchesWorlds:false and the only client signal is in-memory session state. Cloud Found by the WING-330 council (round 2, Cedric, P1). NOT a regression — the chip was a hardcoded literal before, so cloud was wrong in every state. Needs server-side per-tenant active-persona state (or deriving it from the DemoSeedStamp.PersonaKey already on every seeded document).
🟠 Needs human review WT-a37c · WING-315 Amazon-style faceted search — shared discovery rail + chips + live server facet counts on Queue, Study and the job-search window; one filter model, no backend change. Branch wt/WT-7fb6/WING-315 @ 6abbdabbb, 6 commits, tree clean, tip == upstream. Frontend GATED — all 6 commits. Rounds 1-3 on the feature (Gronktayvius: "nothing blocking"), round 4-5 on the fix commits, round 6 on the previously-ungated f0eebfbff..6abbdabbb: Cedric "no actionable correctness regressions", Jenny "NO BLOCKING ISSUES" (all 5 hunt targets clean incl. exported allowlist stays frozen + private Set), Gronktayvius "no blocking findings" (independently confirmed fail-closed layoff counting is "correct, not optional"). Seat failures recorded: Jenny timed out ×3 overnight, Cyber Jenny FAILED ×2, Clahadore seat OAuth-expired. TESTS (streaming peer, parallel — greatroom times out at 605s from RAM starvation, not defects): tsc clean, vitest 130/130 files, 1933 passed / 2 skipped, 18.2s @ 6abbdabbb. CLICK-THROUGH ×2. (1) @ faa2fcc92 vs live API + 68 real saved jobs: counts matched producer exactly, ticking Good refined other groups 68→38 and filtered list to 38 while fit's own counts held (skip-self). (2) @ 6abbdabbb vs an isolated sandbox API on 5599 (scratch token path; verified Andrew's shared token untouched mid-run) exercising the NEW count semantics: client-only predicate ⇒ server counts suppressed + "Counts computed from this result list"; server-only predicate ⇒ every option count: null + "Counts are unavailable until matching server and layoff evidence are verified"; unselected zero-count option hidden; selected zero-count option renders Remote (0) checked and clearable — the half of the rule nothing had pinned through 9 passes and 4 rounds. Disclosure: f0eebfbff was pushed with 6 failing tests (terra couldn't run the suite locally, I committed on a typecheck alone). Caught on the peer; from 1a11fe633 on, every commit went to a scratch ref and was peer-verified before the branch tip moved. Blocked on Andrew: land decision only. WING-372 rebases onto this and carries the WorkModel.Unknown allowlist entry.
🔵 In progress WT-f879 · WING-325 Flow/Studio nav parity — every page reachable in BOTH modes incl. Integrations (reverses WING-155 power-only stance) + permanent Introduction (/first-run) links; a2ui-lab stays nav-less. Routes simple-flagged, More sheet + Studio sidebar expanded, spec allowlist re-ratified Simple Mode Andrew directive 2026-08-04. Branch wt/WT-f879/WING-325. Live-verified both modes on dev server (More sheet 39 rows, Studio sidebar 39 rows incl. flags; /integrations + /first-run + /metrics render in Flow). Full suite + council gate pending.
🟠 Needs human review WT-3145 · WING-316 Aggregator→real ATS resolution — Adzuna/SimplifyJobs postings now resolve to the ATS that actually hosts them (AtsDetector.DetectFromUrl host-strict + asset/Google-path/Avature guards; IAtsResolver/RedirectChainAtsResolver redirect-chain-only, no HTML fetch; per-hop SSRF guards incl. pre-flight DNS + mapped-IPv4/CGNAT/0.0.0.0-8 rejection, 80/443 only, no cookies/creds; ats-resolutions cache w/ TTL classes + canonical keys + cached-result revalidation; ≤50 resolutions/pass + Unknown→Known merge-upgrade so resolution survives re-sync; "via Adzuna/SimplifyJobs" provenance rendered SEPARATE from the ATS column; unresolvable = Unknown, never guessed) Jobs Landed on master: 885879c0 (feature, codex terra) + ea431b8a6 (gate follow-ups). Design-time seats (Jenny + Cyber Jenny + Gronktayvius) BEFORE implementation — their verdicts became binding v1.1/v1.2 constraints. Gate: 4 seats + Clahadore chaired (Codex authored); Opus seat FAILED (API error mid-run) — recorded, not counted. Gate caught: data-loss (empty resync wrote stale snapshot outside storeGate), SSRF bypass (IPv4-mapped IPv6 skipped private-range checks), Adzuna key→logs 2 ways (CWE-532), duplicate Adzuna DI registration (double fetch vs 250-call/day tier). Receipts: build 0 err; tests Failed 1 / Passed 5751 — that 1 pre-existing, proved by full-suite run on untouched integration head. OPEN: no usage pass (nobody ran the app + clicked the ATS column); Adzuna mapping unverified vs real producer (needs credentials-stripped capture, w/ WING-324/325). Train 3/4 (WT-68a3, 08-05): pilots ww-yc + ww-spc deployed on image 9d7098636 (closed 7-slot secret set incl. Adzuna-demo + smoke; rung=pilot; DBs ww-yc/ww-spc; WING-321 resolved); REAL smoke redemptions HTTP 200 on both, records cleaned, Verify-PilotAccess PASS; masters 94c939ef9/9d7098636/5232f1feb/c14db8d0f all council-gated. Incident fixed in-run: spc has NO LB (Cloudflare→run.app), boundary flags briefly locked reviewers out, restored open-ingress. yc-audience drop EXECUTED (WT-0e2a ruling, 08-06): ladder edit 2591c9f03 (gated), EnvLadder promote qa→prod → ww-team-00033-pmt @100% digest fe887425, config==ladder VERIFIED; ww-team has ZERO yc refs (onboard alive, bad-code 401), ww-yc still mounts unrotated yc hash ceiling 1000, guard PASS. 9 test records inert; hygiene = WING-408. STILL BLOCKED on Andrew: seed plaintext code secrets (yc/team/spc — procedure delivered), SSL-cert quota bump (10/10) for spc on shared LB, Adzuna naming HELD (two distinct app_ids proven).
🟢 Done WT-be81 · WING-324 Producer contract suites — verbatim sandbox-captured payloads + key-set equality (helper producer-contract.ts, fixtures w/ mandatory provenance, suites for /api/job-query, /capabilities, /api/demo/personas, docs/technical/producer-contracts.md); caught + fixed two LIVE master drifts: ITEM_KEYS missing experience (WING-315 fix never landed) and DemoPersona key→personaKey (WING-319, picker dead) Testing Landed master 80a569f1b (merge of b9f02da08 via WT-0f7d); riding the demo image. Review gate: Jenny + Cyber Jenny + Gronktayvius seated, Clahadore advisory (author) + chaired (Cedric FAILED ×2, codex internal error). Incident resolved in-branch: first fixture captured REAL saved-jobs data (USERPROFILE env ≠ .NET isolation) — history rewritten, sandbox re-capture isDemo-verified, GCS staging tarballs purged; GitHub GC residual flagged. Receipts: contract suites 13+8 tests green, ng build 0 err; full-suite reds attributed pre-existing/flake vs clean-master baseline.
🟢 Done WT-6e4d · WING-270 Extension ingest API — per-tenant saved-jobs endpoint (server edition): POST /api/ingest/saved-jobs w/ pairing-token auth (wwsjt_, SHA-256 at rest, FixedTimeEquals, 180d expiry), mint/revoke/status under /api/tenant/ingest-token, hostile-input caps, desktop-identity dedupe, per-token-hash rate partition, Bench-consumable fixtures WING-269 Extension Review gate (security-sensitive full panel): Regular Jenny (gemini-3.1-pro-high) + Cyber Jenny (gemini-3.6-flash-high, ASVS framing) + Gronk General + Gronk Red Team seated; Cedric chairman (gpt-5.5 high): 0 Critical / 4 High / 6 Medium / 3 Low — all 13 fixed (strict token-shape rate partition, structural payload cap, atomic token install + lookup cleanup, stored-doc size budget, clone-before-mutate, expiry, pre-binding header check, forbidden-host URLs, URL-required jobs, + 3 Lows). Local/house seats skipped per 07-29 usage policy. Validation by Clahadore post-rebase: build 0 err, 5352/5352. Landed master fc10ae8bd+7ed15bcd8, fleet sync-push fanned out. Unblocks WING-277/271/272.
⚪ To do WING-277 Mobile share-sheet + link-paste ingestion — promoted: primary zero-install mobile path for cloud-only users; builds on WING-270; GrapheneOS/Vanadium test target WING-269 Extension Next in the WING-269 sequence, before/parallel with the MV3 extension (WING-271/272).
🔵 In progress WT-570b/WT-e33f · WING-207 Marketing MPA (htmx+Razor) + WingCMS: team page w/ Ringer-style tiered bios (humans + AI teammates), researched credentials, full degrees, photos re-pulled hi-res, AI platform logos, light/dark toggle, WW favicon, Mission/Big-3 pages, GCS-backed content + /Admin editor on cms.workwingman.ai, WingCms CLI+MCP for token-efficient edits Site Branch wt/WT-e33f/WING-207-mpa. Live at workwingman.ai. Gates: Jenny+Gronk per commit (High catches: AI-bio wipe on roster save, photo-override, bio misattribution; gcloud ArgumentList hardening).
⚪ To do WING-218 Re-point wingcms MCP at main-repo build after WING-207 merges Site Registered MCP points at worktree build path; dies when worktree cleaned.
⏳ Awaiting input WING-237 Three-instrument NDA strategy — tester (one-way, click-wrap) / team (adds IP assignment) / investor (the current mutual draft) Legal Pam drafted the Mutual NDA; it fits investor and partnership talks and is the wrong shape for a 50-tester pilot (entity-only party fields, mutual duties that scale against us, a patent section that reads as alarming to a volunteer, wet-signature blocks vs a web form). Consultation sent 2026-07-30 to Pam, David Gaffney, David Porter, Lisa. Open with them: tester confidentiality tail length · feedback assignment vs licence · whether team IP is already covered in offer letters or contractor agreements · all three drafted together vs tester-first. Correction issued in-thread to all four: the original emails credited Andrew as drafter, which was wrong. Not legal advice — all three need counsel before signing. App-side work sits in WING-222 (NDA gate), WING-226 (in-app reminder, must name the specific agreement), WING-206 (ww-testers tenant). Access form can collect everything except the agreement step meanwhile.
⏳ Awaiting input WING-229 Team availability Doodle forms (Thu Jul 30 – Mon Aug 3) — three overlapping groups, hourly 8am–8pm CT Ops Built + sent 2026-07-29 from workwingman-intake@agentmail.to (Andrew CC'd) on his explicit in-chat authorization. Three Google Forms — full team (7) / leadership five (4) / Andrew·Pam·Lisa (2) — each with per-day hourly checkboxes, shared results summary ON (verified after reload), plus a view-only response Sheet shared with that group for per-person rows. Needs Andrew: (1) confirm "entire team" was right to include Nick/Shereeba/Allyson, (2) whether to enforce sign-in against duplicate submissions, (3) pick the hour + send invites — no further sends on his behalf per the 2026-07-29 burnout notice (forms draft-only). Gotcha for reuse: Forms API has no field for responder-visible results; UI-only, and it lives under Presentation, not Responses.
🟢 Done WING-208 Cloud user-testing v3 telemetry + Looker Studio dashboard Telemetry Report: https://lookerstudio.google.com/reporting/50106c66-363e-464e-8eea-c5a091413743 — 4 pages: Overview / Modes & Pages / Apply Automation / Reviews & Surveys over ww_telemetry views. Jira WING-208 Done.
🟢 Done WW-148 (no Jira ticket filed — small fast-follow, casual ask) Levels.fyi company salary deep-link (Andrew: "can we do login with glassdoor or levels.fyi to have deep links for relevant jobs") Sources Research first, honest verdict: neither Glassdoor nor Levels.fyi offers any real login/OAuth for third parties — Glassdoor closed public API 2021, enterprise-partner-only since 2024; Levels.fyi ToS bans scraping and only sells a paid data API (unrelated to user login). Andrew confirmed mid-research the real ask was deep-links, not auth. Levels.fyi company salary pages are confirmed live-public (no login wall) unlike Glassdoor's company salary pages (need an internal numeric employer ID, no reliable way to derive one from a name — skipped, existing Glassdoor() interview-search link stays as-is). Added StudyDeepLinks.LevelsFyi(company) alongside the existing Glassdoor/Blind company-research links. Council-code-review gate: Cedric (Codex, member+chair) clean; Jenny (Gemini) + Gronk (Grok) seated and converged; local qwen3:8b FAILED preflight (no token in 60s); house councils skipped (47-line diff too small to warrant). Chairman: 1 Medium (reusing the LeetCode-topic ToTopicSlug for company paths is the wrong abstraction — legal suffixes/punctuation/aliases will silently 404) + 2 Low (empty/null input built a malformed //salaries URL that a test pinned; a C# row was a nonsense company case) — all fixed: dedicated private ToCompanySlug helper, LevelsFyi returns null on an unresolvable slug (matches LeetCodeCompany's null-on-unknown contract), test suite updated. Kerr: none (pure URL builder, no reward surface). Rebased twice through fast-moving master (WW-83/WING-80 spike + handoff docs landing concurrently — no path conflicts). Build 0 err, full suite 4463/4463. Landed master b656d81.
🟢 Done WW-147 · WING-166 Queue bucket presentation refinement — dynamic per-bucket header + quieter tab bar (Andrew: "internships and recommended jobs cloud the top of the saved job page") Sources Follow-up to WW-146 (separation logic UNCHANGED — each bucket still its own clean list). Presentation/hierarchy only: (1) header title/subtitle reflect the ACTIVE bucket via a single BUCKET_COPY map + bucketTitle()/bucketSubtitle()/sortChipLabel() computeds ("Your saved queue" / "Recommended for you" / "Military & federal jobs" / "Education & internships") instead of the hard-coded, now-wrong "Saved jobs pulled from LinkedIn"; sort chip shows "skill match" on Recommended (fit-ranked) vs "post date ↑" elsewhere; (2) tab bar de-emphasized — inactive buckets faint + borderless, active bucket the one solid accent pill, counts a whisper (opacity 0.5) so large Education counts no longer shout at the top. Council-code-review gate: Cedric (Codex, member+chair) + Jenny (Gemini) + Gronk (Grok) + local qwen3:8b all seated; Cedric+Codex-member clean, Jenny/Gronk converged on Lows (all applied: single BUCKET_COPY map, sortChipLabel computed, whole-pixel fonts, ASCII apostrophe, and the real one — expanded tests to cover subtitle + sort-chip + rendered .page-title/.page-sub/.sort-chip bindings). Chairman final: 0 findings, Kerr-clean. Live-verified: Saved→"Your saved queue", Recommended→"Recommended for you"+"Sort: skill match". queue spec 60/60, ng build clean. Landed master 109a227.
🟢 Done WW-146 · WING-165 Queue bucket filter tabs (Andrew: "education/military take over the job queue… needs a filter") + Recommended save-into-queue Sources Built on branch claude/goofy-haibt-874cdc (WT-0de9). The saved queue intermixed LinkedIn saves with promoted USAJOBS (military) + SimplifyJobs (education) jobs in one flat list — live data showed 1449 saved / 61 LinkedIn / ~1388 education, so education drowned the queue. Andrew tightened the spec twice: real SEPARATE bucket views, not one filtered list, no "All". Built: tab bar showing ONE bucket at a time (Saved / Military / Education / Recommended), each rendering ONLY its own items — pure organization by Source + pipeline stage, not a ranking/score (Kerr-clean, chairman confirmed). Tab-visibility = track ENABLED (federal.master/education.master) OR bucket non-empty, so a flooded track is isolable even with its flag off (the flood insight). Recommended = distinct Proposed stage, lazy-loaded (endpoint polls external sources) with Save (promote→its source bucket, optimistic) + Dismiss. New thin [RequireLocalToken] JobsController endpoints POST recommended (POST not GET — side-effecting) / POST {id}/promote / POST {id}/dismiss delegating to pre-existing IJobQueueService methods; CancellationToken.None on mutations so client-nav can't cancel a save mid-write. Council-code-review gate (TWO rounds — spec tightened mid-gate): Cedric (Codex, member+chair) + Jenny (Gemini) + Gronk (Grok) all seated both rounds; local qwen3:8b seated (degraded — diff truncated to 24KB). Round 2 raised real fixes, all applied + chairman-verified fixed: status-aware 404 (no zombie recommendations), mutation CancellationToken.None, self-healing effect (hidden tab can't silently steal the view), order-preserving rollback, allocation-free ISO string sort, takeUntilDestroyed. Final chairman verdict: 0 Critical/High/Medium, Kerr-clean; 3 residual Low accepted (service-level recommend rate-limit = recommend-jobs-epic scope; cache TTL = intentional; double-submit = covered by optimistic remove). Validation: API build 0err, ng build clean, queue spec 58/58, backend Job tests 315/315; live in-browser verify (sandbox) — Saved 61 / Military 0 / Education 1388 / Recommended, each its own clean list. Landed master aa1feda.
🟢 Done WW-96c · WING-98 Simple home screen /home — next-action hero, matched jobs, progress strip Simple Mode Fleet-built (gaming PC), council-reviewed/landed by WT-c9de. Single-winner state machine (add-resume > active-run > matched-jobs) over existing ApiService, every read fails soft. Fit chips render backend FitLevel faithfully, never invented (Kerr). Apply defers to /job/:id. Landed 524b074.
🟢 Done WW-96d · WING-99 data-mode CSS restyles of simple-reachable shared pages (job-detail, documents, applied, offers, live-run) Simple Mode Fleet-built (streaming PC), council-reviewed/landed by WT-c9de. Subtractive :host-context(body[data-mode='simple']) only — power mode pixel-unchanged; every hidden selector verified present in its template before landing. Landed ce2801f.
🟢 Done WW-96f · WING-101 E2E gate — toggle persistence, guards, home CTAs, mode-aware defaults Simple Mode Built by WT-c9de directly (shared-fixture surgery, not fleet-delegated). Root cause: E2EFixture creates a genuinely fresh browser context per test (correct — matches default-simple fresh install), which exposed two real gaps: 5 pre-existing power-mode journeys needed wingman-ui-mode=power seeding, and WW-143's First Flight redirect hijacked every fresh context regardless of seeded mode (fixed via its documented wingman-onboarding-bypass testing escape hatch). Along the way: AddInitScriptAsync evaluates its string arg as a script body, not a callback — an arrow-function-expression string silently no-ops. Added SimpleModeJourneyTests (4 new: fresh-install defaults simple, active-run hero, guard redirect, toggle+reload persistence). Full gate: vitest 1033/1033, ng build green, dotnet test WorkWingman.E2E 11/11. Landed abecd02+87bfc3e.
🟢 Done WW-96e · WING-100 More sheet + settings mode control + first-run default + empty-state copy Simple Mode Fleet-built (streaming PC), council-reviewed/landed by WT-c9de. MoreSheet in SimpleShellChrome's reserved slot: /connections//doctor links, theme toggle (simple mode's only theme control), "Switch to Power Mode". Settings gains an Appearance card (theme + Simple/Power segmented mode row, local UiModeService only). Focus-trapped dialog, Esc/backdrop close, aria-modal, focus-visible throughout, focus returned to trigger on close. Simple nav stays ≤7 destinations. Landed 1a51b63.
— WING-95 epic status (WT-c9de, 2026-07-24) All six WW-96 sub-tickets (96a–96f / WING-96–101) landed on master, build+test+E2E green throughout. Epic's own predeclared next step (see WING-95 venture-evidence comment): observe against the Sean/Shereeba tester cohort before calling the home-screen composition validated — epic left In Progress in Jira on purpose, not auto-closed on ticket completion.
🟢 Done WW-143 · WING-123 Guided first-run ONBOARDING WALKTHROUGH meta epic — First Flight (3-screen orientation) + non-blocking Setup Hub (4-card checklist, auto-satisfy detectors) at /first-run Onboarding Council-architected (fable, 3/5 seats + verdict, llm-council/reports/2026-07-23_183357_*). Orchestrates entry into sibling epics (WW-96 mode pick, WW-97/109 track choices persisted for replay, profile import-diff, /connections vault+LinkedIn, /doctor) without owning their internals — service-API-only writes, capability-probe degradation (canShowStep, mode-aware steps() routes scrape card to /home in simple / /queue in power). Local-only event log (zero telemetry, wingman-onboarding-events), Kerr-guarded milestone naming (first_job_scraped, never "activation"). Convenience skip (per-step/hub) + testing bypass (?skipOnboarding=1 / wingman-onboarding-bypass). Council-code-review gate: Cedric chaired, Jenny+Gronk seated, 8/8 findings fixed (High: doctor-unknown≠satisfied; Kerr rename). Rebased clean onto WW-96b. 954/954 tests. Discovery experiment predeclared, not yet run: docs/discovery/ww-117-onboarding-concierge-v0.md (filename predates this ticket-number correction). Note: built under the internal placeholder "WW-117" before this ticket number existed; WW-117 in Jira/BOARD.md is a DIFFERENT ticket (Benefits T0) — corrected to WW-143, in-tree comments fixed 52667b0. Landed 773ad7b/7fc00c7.
🟢 Done WING-146 Federal track T1: nav filter + route guards + settings toggle consuming shared FeatureFlagsService Sources Landed master 99aa41a. Filed fresh (working id "WW-109" collided with the education epic's already-landed WW-109/WING-132 T7b, below — same collision class as WW-144/WING-130 and WING-144). Rescoped as thin consumer of WING-102's shared IFeatureFlagsService (no second store): federal.master key registered backend+frontend+drift-fixture, NavEntry/FEDERAL_NAV_ENTRIES seam (deliberately empty — no dead links pre-routes) + visibleNavEntries() filter, federalFeatureGuard (reuses featureFlagGuard), Settings toggle row. Two duplicate-declaration bugs fixed on integration (stale-base rebase re-added FederalMaster/getFeatureFlags mock that WW-110 had already landed). Closes the veteran-vertical MVP wave — 13/13 T1-T8 + spikes now on master.
🟢 Done WW-110 · WING-115 Federal track T2: flag-gated source scheduling seam Sources Landed master 7b140bd. Rescoped to consume WING-102's shared IFeatureFlagsService (no second store). SourcePollingGate<TSource> snapshots flags once per scheduler call, fails closed; wired into layoff/comp/benefits/stockquote/company-research/GDELT polling; federal.master default-off. 3749 backend tests, 23 flag-contract tests. Unblocks WW-111.
🟢 Done WW-111 · WING-116 UsaJobsSource T3: BYO-key Search adapter + codelist bundle + get-key flow Sources Landed master 434a404. USAJOBS Search adapter (BYO-key via existing vault, never-throw contract), embedded codelist bundle + hiring-path chip mapper (WW-79 taxonomy), "Connect USAJOBS" settings card with WW-81's honest copy verbatim, flag-gated polling (WW-110 seam). Historic JOA optional path deferred. 4231 backend + 1088 frontend tests. Codex review: source gating, key masking, codelist hardening applied.
🟢 Done WW-112 · WING-117 MOS translation T4: MOC corpus + crosswalk lookup + confirm→apply proposals + onboarding step Sources Landed master 1671ead. 12,062-entry DMDC/O*NET crosswalk (CC-BY 4.0, embedded resource), pure lookup service, MocProfileProposalBuilder — every proposal below the 70 auto-accept bar, confirm-only. /api/vocab/moc/* token-gated. Onboarding "Military background (optional)" step. 30 backend + 909 frontend tests. Codex review: 2 P2s fixed (exact-code search precedence, branch-filter re-search).
🟢 Done WW-113 · WING-118 Clearance T5: enum on EligibilityLogistics + encryption + LLM-payload strip + factual chips Sources Landed master 4c8e97a. 5-seat council gate (Codex/Gemini/Grok/gaming qwen3-next+qwen3-coder; local seat failed preflight, streaming skipped); chairman: 9 findings, all applied — incl. P1 data-loss fix (undecrypted DPAPI ciphertext preserved not blanked), 5 ATS engines fixed (raw non-empty check answered Yes for "None"), Kerr ruling killed meets/"above your clearance" verdict copy → chip renders 2 literal facts only. 52 backend + 20 frontend clearance tests. Unblocks WW-111/WW-114/WW-115/WW-116 (posting field + comparison-safe chip pattern now exist).
🟢 Done WW-114 · WING-119 Federal resume T6: TemplateDrafter template + ClaudeDrafter federal mode + job-scoped gap flow Sources Landed master 83268b4. FederalResumeTemplate (2-page hard cap + truncation notice, month-year dates, hours/week, series/grade), FederalResumeGapService (≤6-question resumable gap flow, lazy at first federal draft), per-application facts on DocumentGenerationRequest, per-role fields on WorkHistoryEntry encrypted via the WW-113 SecretProtector pattern, ClaudeDrafter federal mode with DraftHonestyGuard untouched. Recovered from a wedged codex-kind attempt's uncommitted WIP (verified against spec before landing) and merged against WW-113/WW-99's independent additions to the same files. 75 backend + 202 frontend affected tests, full builds green.
🟢 Done WW-115 · WING-120 Vet-preference T7: static OPM rules engine + GS band card + hiring-path chips Sources Landed master b7f86e0. VetPreferenceRules (Core, static OPM 5-pt/10-pt/none/unsure), GsBandGuidanceCatalog (informational-only band card, explicit "no official mapping exists" disclaimer), standalone vet-preference-chip + gs-band-card components (not yet wired into federal pages — deferred to WW-111/116 consumers). FederalMetadataGuardTests locks eligibility never entering sort/filter/score. 962 frontend + 3756 backend tests.
🟢 Done WW-116 · WING-121 Federal apply-prep T8: checklist card + USAJOBS deep-link out Sources Landed master 7c6a17c. FederalApplyPrepCard (login.gov/DD-214/SF-15/fed-résumé readiness checklist, per-item checkable) gated on a usajobs.gov marker, renders on job-detail alongside the WW-113 clearance chip. Deep-link out only, never touches login.gov. 43 affected-spec + full-suite tests.
🟢 Done WING-144 [SPIKE] Veteran education-benefits (GI Bill) data feasibility — federal↔education track bridge Sources Doc landed: ww-gi-bill-education-benefits-feasibility.md. Filed fresh (working id "WW-117" collided with the real WW-117/WING-122 Benefits T0 row below — same collision class as WW-144/WING-130 same day). GO: public CC0 GIBCT API (api.va.gov/v0/gi) for school-level chips (approved/Yellow-Ribbon/housing-basis), no PII. NO-GO: personalized calculator, Lighthouse identity APIs, SkillBridge/VR&E chips (no public join), any benefits-maximizer ranking. Bridge = shared school metadata, not shared veteran identity.
🟢 Done WW-144 · WING-130 Company deep-research / interview-intelligence epic — landed master b941c96: honesty spine (CompanyIntel/CompanyFactGuard) + Leadership/OrgChart/CorporateActivity/Community-DEI adapters (Wikidata/SEC/GDELT) + consent-gated matching + card FE + power-mode job/:id/company-intel route Company-Intel Ticket-number correction: this epic was built under the internal placeholder "WW-97", which collides with the unrelated, already-landed Education FeatureFlagsService T0 ticket (WW-97 · WING-102, row below) — re-filed as WW-144/WING-130 2026-07-24; no code changed, only the ticket reference. Council-architected (fable tier). Shares Core.Enrichment.SourcedField<T>/SourceCitation with Benefits (WW-117/WING-122, converged 2026-07-23). Full epic landed: spine, shared-primitive adoption, Leadership+OrgChart, CorporateActivity, Community/DEI, consent/matching, card FE, and the route+flag. TWO independent council-code-review gates run (backend adapters + FE card/route; Codex+Gemini+Grok every time, local FAILED RAM canary both times, house councils skipped for fleet contention) — backend gate: 5 High/10 Medium/5 Low fixed (c40269d); FE gate: 0 Critical/High, 4 Medium/10 Low fixed (2928572), incl. one Codex/Gemini/Grok-converged finding (unvalidated LinkedIn/news URLs reaching [href], fixed with an https+host-checked helper). Merging into master surfaced a genuine TS mirror duplication with the Benefits epic (SourcedField/SourceCitation independently declared by both epics) — deduped, kept Benefits' canonical superset. Final validation: backend 4210/4210, frontend 1084/1084, both builds clean. Fast-follow filed for deferred structural items: WW-145 · WING-140.
🟢 Done WW-117 · WING-122 Benefits T0: shared Core.Enrichment primitive — SourcedField<T>, SourceCitation, FieldStatus/Confidence/CaptureMethod enums, employer-key normalizer, per-company local store Benefits Landed 48ba12e. Shared contract CONVERGED with the company-intel epic (adopts SourcedField<T>, rebasing its SourcedFact<T> onto this; added SourceKind.Wikidata/OfficialDeiPage for its domain). Per-field provenance; NotOnRecord is the only citation-free status. Council-architected 2026-07-23 (opus; report llm-council/reports/2026-07-23_181250_*), spec docs/technical/company-benefits-comparison.md. Council-code-review gate: Codex+Gemini+Grok (house councils raw-HTTP 401-gated, skipped); Grok P1 wrong-T-quarantine fixed via per-key deserialize. Build 0 err, 40/40 tests.
🟢 Done WW-118 · WING-123 Benefits T1: CompanyBenefitsProfile record COMPOSING BenefitsInsight + typed sub-records (HealthCoverage/LeavePolicy/ParentalLeave/FamilyForming/Retirement/MentalHealth/Perk) + TS mirror Benefits Landed f254122. Composes BenefitsInsight (INS-9 = automated subrecord); PTO incl. cash-out; every rich field defaults SourcedField<T>.NotOnRecord(). Fleet-built (Gemini) under RED, Grok-reviewed (clean). Build 0 err, 42/42 tests incl. store round-trip. TS mirror added (unverified locally — no ng CLI in worktree; pure type decls). Unblocks WW-119/121/123 (T2/T4/T6). Deps: WW-117.
🟢 Done WW-119 · WING-124 Benefits T2: user-entry write path (first-class API into the profile, NOT an IBenefitsSourceAdapter) + LocalJsonStore persistence Benefits Adapters stay public-signal-only (never-throw, most-recent-AsOf); user truth is a separate write path. Requires a provenance KIND on save (offer letter/recruiter/official page/personal note); URL optional; personal note → UnverifiedPaste. Deps: WW-118. Sonnet-tier. Landed 7663078. Fleet-built (Gemini)/Grok-reviewed; fixes: AsOf-no-regress + transient-null-preserves-signals. 7/7 tests.
🟢 Done WW-120 · WING-125 Benefits T3: offer-letter / benefits-page structured-paste importer (strict-JSON LLM extraction → LlmExtractedFromUserDoc fields, confirm-before-persist) Benefits LLM may ONLY extract text the user pasted — never originate/infer/browse a benefit value. Output tagged machine-extracted, carries source snippet/span, never Automated. Empty-input golden test returns zero fields. Deps: WW-119. Sonnet-tier + ceiling on prompt. Landed d54548f. Fleet-built (Gemini)/Grok-reviewed. 9/9 tests.
🟢 Done WW-121 · WING-126 Benefits T4: render-layer honesty guard (extends DraftHonestyGuard) + 5 guard tests Benefits Honesty-critical — ceiling-build directly. Value/NotOffered/Conflicting w/o citation = build failure; only NotOnRecord citation-free. Banned-certainty scan on benefits copy; CEI never a plan guarantee + PlanVsCarrier always present; family-forming/gender-affirming fit stays on-device. Deps: WW-118. Ceiling-tier. Landed f0f2590. Ceiling-built directly (Claude), Grok-gated. 20/20 tests.
🟢 Done WW-122 · WING-127 Benefits T5: /compare route + cross-company comparison view (multi-select, coverage meter, "not on record" chips, differences/sourced-only/needs-confirmation filters) Benefits Power-only (data.simple omitted; guard redirects deep links to /home). ~4–6 column cap. Kept SEPARATE from OfferEvaluationService — no blended benefits score (Kerr trap). Deps: WW-118, WW-121. Sonnet-tier. Landed 49b8df9. Fleet-built (Gemini)/Grok-gated. FE unverified locally (no ng CLI in worktree, same as T1 — flagged honestly).
🟢 Done WW-123 · WING-128⚠️ Benefits T6: Simple-Mode calm one-line benefit chip on job/offer cards Benefits CSS-only, simple-visible (body[data-mode='simple']), no new route — e.g. "Aetna · trans-inclusive ✓ · PTO cash-out ✓ · 4 on record". WW-96 binding contract (subtractive gating, persistedSignal for local toggles). Deps: WW-118. Haiku-tier. Landed eac367d. Wired into job-detail (offers deferred — Offer model lacks a company field). Fleet-built/Grok-gated. ⚠️ WT-c9de, 2026-07-24: real Jira WING-128 is now a different, unrelated ticket (a test-isolation bug fix) — this row's link is stale/wrong. No matching Jira issue found for this summary; Benefits epic should confirm and correct the real number.
🟢 Done WW-124 · WING-129 Benefits T7: feature-flag wiring (ff.companyEnrichment master → benefits.autoSignals/benefits.profileAndCompare/companyBriefing) + docs Benefits WW-97 FeatureFlagsService pattern; visibility = flagOn && modePolicy; unknown flag = off. Master default-OFF, children default-ON. Deps: WW-117. Haiku-tier. Landed c4450f5. EPIC COMPLETE (WW-117..124 all Done). Grok caught FE MASTER_PREFIXES gap (master kill-switch wasn't actually darking FE) — fixed.
🟢 Done WW-126 · WING-124 Take-home tax + COL T0: shared contract — provenance slots + local completeness — landed 03995f2 Take-home Epic anchor, block WW-126..133 — deepens the EXISTING tax engine (IncomeTaxEstimator already had federal/FICA/all-50-state/local brackets; this epic surfaces + honesty-guards it, not a rebuild). Adds SourceKind +IrsPublication/StateRevenueDept/LocalTaxAuthority (appended after Other, no ordinal shift); TaxEstimate +Federal/Fica/State/LocalSources citation slots (empty, T2 populates) + LocalTaxStatus{NotModeled=0,Modeled,VerifiedNone} + LocalContributionIncomplete — both fail-closed by construction (NotModeled/zero-value + incomplete=true default) so an unmodeled jurisdiction can never silently render $0 local tax; estimator marks a matched local Modeled+complete. Council-architected (opus, D>A≈B>C, report llm-council/reports/2026-07-23_195923_*): net take-home and COL stay separate axes, never blended (JobImpact.ColAdjustedDelta slated for deletion in T6a); all-4-filing-status + per-rate citations + tri-state local coverage ruled for T1–T3. Consumed by Total Rewards (WW-125/134+) as the shared seam. Council-code-review gate: Cedric+Jenny+Gronk seated (local Ollama FAILED preflight; house councils skipped, trivial diff); 3 findings applied (honesty-safe enum zero-value, fail-closed completeness default + estimator wiring, enum ordinal stability), 1 Low skipped. Build 0 err, 3811/3811 tests green (full suite, post-rebase). T1 (WW-127 filing-status), T2 (WW-128 citations), T3 (WW-129 local coverage), T4 (WW-130 property+car COL) fleet-dispatched off this commit.
🟢 Done WW-127 Take-home tax + COL T1: four-filing-status tax engine + state std deduction — landed b15d79e Take-home Internal number collision, not a code issue: the Total Rewards epic also used the internal placeholder "WW-127" for its TotalRewardsService ticket, which took the real Jira key WING-127 — see the WW-136 row below; this Tax-T1 ticket has no Jira issue of its own. Extends IncomeTaxEstimator from Single-only to all four IRS filing statuses (Single/MarriedJointly/HeadOfHousehold/MarriedSeparately): per-status federal brackets + standard deduction (real 2026 IRS Rev. Proc. 2025-32 figures, verified against Tax Foundation), per-status FICA Additional Medicare thresholds (26 U.S.C. §3101(b)(2): $200k Single/HoH, $250k MFJ, $125k MFS), and a state standard deduction for the 8 rolling-conformity jurisdictions (CO/ID/IA/MO flat-rate, MT/NM/ND/DC progressive) — remaining ~42 states stay NotModeled, taxed on gross, per council MVP scope. Council-code-review gate: Cedric+Gronk+local qwen3:8b seated (Jenny FAILED — agy OAuth unavailable; house councils skipped, bounded diff); chairman (Cedric) synthesized 1 Critical + 3 High + 2 Medium: added TaxDataset.Validate() to fail closed on a malformed dataset instead of silently computing wrong tax from zero-defaulted fields, and gated the state-deduction "complete" flag so a progressive state's still-Single-only bracket schedule (MT/NM/ND/DC) is never presented as verified for a non-Single filer even though the deduction is applied. Round-4 validation also caught + fixed 2 stale ApiSmokeTests using FilingStatus=1 as an out-of-range sentinel from before the enum expanded. Rebased 3× through a highly active fleet window (WW-104/110/112/121/130 landing concurrently) before a clean FF-push. Build 0 err, 4000/4000 tests green (full suite, post-rebase onto master). Unblocks Total Rewards (WW-134+/WT-4ad5) net-take-home row for non-Single filers.
🟢 Done WW-136 · WING-127 Total Rewards T3: TotalRewardsService read-time projection (tax + COL + equity + benefits + market context) + GET api/offers/{id}/total-rewards — landed 344841d Total Rewards Fleet handoff from WT-4ad5 (clean slate, no partial branch) to WT-6eb2 after WW-125/134/140 landed. Consumes IIncomeTaxEstimator directly (not the Single-only ITakeHomeCalculator seam — all 4 filing statuses ratified by the epic owner) and IEquityValuationService directly (not EquityValuationAdapter/IEquityValuation, which collapses PSU to null — also ratified); the adapter is still wired into DI behind a Func<EquityValuation, IEquityValuation> factory for future callers (e.g. JobImpactService), per the ticket's ask. Uses the profile's single GetEquityAsync() grant for every offer (OfferComp.EquityGrantIds has no backing resolver — EquityGrant carries no Id); WING-145 (blocks WING-129/WW-141) tracks the real per-offer equity-grant-store fix. Council-code-review gate: Codex (native review) + Grok (agentic research) + gaming-PC council (qwen3-coder/devstral); local FAILED RAM canary. Chairman (Codex) synthesis: 3 High + 4 Medium + 2 Low — fixed the Kerr-guarded subtotal bug (equity's full multi-year grant value was being summed into the ANNUAL pretax subtotal, moved to its own EquityGrantValue field, excluded from the subtotal by construction), sign-on mistaxed as recurring annual income (excluded from tax-gross, still its own pretax row), unhandled tax-estimator exceptions on unknown state/negative gross (now degrade to NotOnRecord, never a 500), Offer.LocationTerms fallback when the job is unlinked, a complete honesty copy-scan (was only scanning one field) + a disclaimer reworded to avoid self-triggering the guard's non-negation-safe "best/worst offer" ban, dropped an unused stock-history fetch, parallelized independent reads, and a MoneyRange invariant guard. One chairman suggestion (force equity NotOnRecord for every offer) was NOT carried — overridden by the epic owner's explicit informed ratification of the single-grant approach. Build 0 err, full suite 4230/4230 (was 4210).
🟢 Done WW-137 · WING-129 Total Rewards T4: RewardsHonestyGuard Citation + Frame guards + service-layer PSU band invariant, wired into TotalRewardsService.ComposeAsync — landed ccb11af Total Rewards Implements the two remaining guards from total-rewards-psu-design.md §8 (Citation, Frame) plus CheckBandInvariant(EquityGrant, EquityValuation) — the design doc's golden case: "PSU with no attainment returns a threshold..max band + reason, not a number." Council-code-review gate: Codex + Grok + gaming-PC council (qwen3-coder/devstral); local FAILED RAM canary (every gate this session). Three reviewers independently converged on one real defect: the collapsed-band check (ThresholdValue == MaxValue, non-zero) false-positived on a legitimate fixed-delivery PSU whose grant terms genuinely set ThresholdMultiplier == MaxMultiplier — fixed by comparing the grant's own PSU multipliers instead of guessing legitimacy from dollar values alone. Grok additionally flagged (not reachable via the one production EquityValuationService, whose if/else if branch structurally never sets both PsuBand and CurrentGrantValue — but fixed as defense-in-depth, same "second line of defense" rationale as CheckCitations): the band invariant didn't inspect CurrentGrantValue for a PSU valuation, and BuildEquityRowAsync's PSU branch could still fall through to the point path. Also hardened CheckCitations to delegate to SourcedField.SatisfiesHonestyInvariant (null-safe against hostile "sources": null) and flag a NotOnRecord field carrying a leaked non-default Value, mirroring BenefitsHonestyGuard. Self-caught bug while implementing that: unconstrained generic T? compiles to plain T for value types, so Value is not null is a no-op on a decimal field — switched to EqualityComparer<T>.Default.Equals(value, default). Build 0 err, full suite 4280/4280 (was 4230 pre-WW-137, +30 from an unrelated WW-111/114 rebase pickup). Next: WW-138 (PSU calculator UI, extends existing /equity page) + WW-139 (offer-detail breakdown/card face); WING-129 stays In Progress until those land.
🟢 Done WW-138 · WING-129 Total Rewards T5: PSU calculator UI on the existing /equity page (anchor form, assumed-anchor stamp, band display) + painted-door "Compare Offers" instrument — landed a8af053 Total Rewards No new route — extends /equity per the design doc's T5 line. PSU pill + terms sub-form (metric kind/label, performance period, target units, floor/threshold/target/max multipliers, user attainment), gated totalRewards.psu (auto-ANDed with its master). RSU/option-only fields (share count, vesting years, cliff months) hidden for PSU. Band display: threshold/target/max (falls back to share counts when unpriced), target anchor labeled "plan target — not a prediction", isAssumedAnchors → "typical assumption" stamp, user-asserted point shown INSIDE the band, never replacing it; editing any anchor flips assumedAnchors off for good. Fixed a latent honesty gap while wiring this in: the valuation card's old unpriceableReason-exclusive branch would have hidden a real PSU band whenever BuildPsuBand also sets a reason (e.g. "no attainment asserted" — an expected, common case) — band and reason now render together. Painted-door "Compare Offers" button fires a new rewards.compareOffers.clicked metrics event (added to MetricsEventTypes.ClientWritable) + a coming-soon toast — records real demand for WW-141, which stays gated dark until the design doc's predeclared go-signal (≥5 users/≥2 companies in 30 days, or ≥3 explicit requests) clears. Council-code-review gate: Codex + gaming-PC council (6 members: qwen3-next:80b/qwen3-coder:30b/devstral:24b/gpt-oss:20b/gemma4:26b clean) + local Ollama FAILED preflight (every gate this session); Grok failed internally (max_tokens_truncation, no verdict — recorded as a failed seat). Codex found 2 real defects, both fixed: the band display was gated only on psuBand presence, not re-checked against the flag (a previously-saved PSU grant could bypass the gate since the API doesn't know about the client-side flag — a hostile server can't be trusted to enforce a client-only gate, so the template must check both); and the asserted-attainment row hid entirely when assertedValue was null (no price yet) despite assertedShares being available, inconsistent with the band's own fallback. Gaming-council's claims (client-side type whitelisting, missing input validation, memory leak on appendMetricsEvent, etc.) were all checked against the actual code and dismissed — false premises or pre-existing intentional architecture (server-side-only whitelist enforcement matches every other client-writable event type; compareOffers() sends zero props so nothing to sanitize; PSU anchor bounds already fail closed via AnchorsSane server-side). Manually verified live end-to-end in-browser against a sandboxed backend (WorkWingman:Environment=sandbox, isolated data dir — never touched the real running instance on 5211): PSU form, band rendering, the band+unpriceableReason coexistence fix, and the painted-door instrument (event POST confirmed 204, toast confirmed rendered) all work as designed. Build 0 err, backend 4282/4282 (was 4280), frontend 1105/1105 (was 1103, +22). Next: WW-139 (offer-detail rewards breakdown + calm total-comp card face); WING-129 stays In Progress until that lands.
🟢 Done WW-139 · WING-129 Total Rewards T6: offer-detail rewards breakdown (GET api/offers/{id}/total-rewards) + calm one-line offer-card qualifier chips — landed 4306613. EPIC COMPLETE for tonight — WING-129 → Done (WW-141/WING-145 stay deliberately gated dark, not blockers) Total Rewards Offer-detail: new "Total rewards" card — dollar components + subtotal (always a range), equity kept separate, net take-home its own row, market context side-by-side, honesty findings rendered as caveats (never dropped), disclaimer+as-of always shown, subtractive simple-mode gating scoped to the card's own class. Offers-list: qualifier chips only — "Equity (range)" / "Take-home inputs on file" — deliberately no numbers, one shared getEquity() call for the whole list, no per-row fetch; an explicit architect ruling (real numbers on list cards would turn the list into an implicit ranking surface — the Kerr trap §4 forbids, and exactly why WW-141 stays gated dark) rather than a perf shortcut. Council-code-review gate: Codex + Grok + gaming-PC council (6 members) + local Ollama FAILED preflight (every gate this session). Codex: both new surfaces bypassed totalRewards.master entirely — fixed, gated to skip the fetch rather than fetch-then-hide. Grok (3 real findings, all fixed): a stale-data race where navigating between offers before a slower total-rewards call resolved could silently overwrite the current offer with a previous one's figures (generation-guard + regression test simulating the exact interleaving); a chip wording overclaim ("Take-home on record" implied a verified computation the client-side check doesn't confirm — the backend's CityStateParser can still fail on free text like "Remote" — renamed to "Take-home inputs on file"); backend-provided SourcedField.notes (PSU assumed-anchor caveats, NotOnRecord reasons) fetched but never rendered, silently dropping real provenance text (now shown as footnotes). Also fixed on inspection: whitespace-only ticker counted as equity-on-record, honestyFindings decoupled from .axis-citations into its own class, total-rewards fetch also skipped in Simple Mode (not just CSS-hidden). Gaming-council's findings mostly false positives against actual code (XSS claims ignore Angular's escaping, "no auth" ignores the existing [RequireLocalToken] gate, MoneyRange fields are non-nullable contradicting a "might be missing" claim); one item (filing status always defaults Single, no UI selector) confirmed by two independent seats and documented as intentional v1 scope rather than built out (a real scope expansion, not this ticket's ask). Backend unchanged this round. Frontend 1126/1126 (was 1105, +21).
🟢 Done WW-128 · WING-148 Take-home tax + COL T2: federal/FICA + state citation provenance + honesty guard — landed 193f740 Take-home Populates the T0 citation slots (FederalSources/FicaSources/StateSources) and enforces "uncited modeled rate = build/test failure" for the always-modeled federal/FICA/state axes — mirrors WW-129 T3's local-axis TaxSourceRef/CitationsRetrievedAt pattern, generalized via BuildSources' defaultKind param rather than a parallel citation type. RequireSource fails closed (InvalidOperationException) on a null source, blank title, or unparseable asOf. Real per-jurisdiction agency names/URLs in tax-data-2026.json (Texas Comptroller, California FTB, NY Dept of Taxation and Finance, etc. — not templated "Department of Revenue" guesses) for federal (all 4 filing statuses), FICA, and all 51 states. Council-code-review gate, two rounds: Cedric+Gronk seated both times (Jenny FAILED — agy OAuth timeout both attempts; local Ollama FAILED preflight — RAM-starved). Round 1 (pre-T3-rebase base) found RetrievedAt=AsOf conflation, count-only citation guard, wrong state-agency titles — fixed. Round 2 (post-T3-rebase, current diff) found the shared federal/FICA citation had url:null after the round-1 fix (federal=IRS, FICA=SSA are different authorities, one shared citation can only carry one URL) — split into two real citations combined into one shared array (Assert.Same). Rebased 3× onto a fast-moving fleet window (WW-129 T3's own council-fix pass, WW-127's four-filing-status expansion, then WW-124 benefits-epic completion) — reconciled TaxSourceRef/FilingStatusFederalData shape changes each time, re-verified build+full-suite after every rebase. Build 0 err, 4033/4033 tests green (full suite, prior to the final no-op rebase); two failures on a later run confirmed pre-existing on origin/master and unrelated (stale DataPortabilityServiceTests assertion vs WW-98's new domains; LocalRagLiveSmokeTests needs a live local Ollama).
🟢 Done WW-129 · WING-139 Take-home tax + COL T3: curated local-tax VerifiedNone + citations — landed fd44962 (supersedes the earlier 5354fc9 gate below) Take-home Curates 11 modeled cities across 4 states (Ohio's Cincinnati/Toledo/Akron; St. Louis/Kansas City MO primary-source verified; Louisville/Lexington KY) with per-entry source citations, plus a verifiedNoLocalTax list (~29 states with no local-tax authority anywhere, Tax Foundation + IL DOR cited) wiring LocalTaxStatus.VerifiedNone distinct from NotModeled — an absent jurisdiction stays NotModeled/incomplete, never coerced to $0. Maryland/Indiana county rates investigated but not added: conflicting secondary sources, unreadable authoritative PDFs — left NotModeled rather than risk a fabricated rate. First council-code-review gate (Cedric+Gronk, 5354fc9): 1 Medium + 5 Low fixed. A SECOND, independent council-code-review pass (WT-57e5, before landing) — Cedric chair (with real external verification against the cited Tax Foundation survey) + Jenny + Gronk — caught issues the first pass missed: a fabricated citation vintage (~20 rows cited a 2023 Tax Foundation survey but stamped asOf: 2026-01-01, corrected to the real year or null), a citation-free result still reporting "complete" (honesty-invariant gap — SatisfiesHonestyInvariant's own rule violated elsewhere; fixed + load-time validation added so the shipped dataset can never contain an uncited local-tax row), CaptureMethod.Automated mislabeling hand-curated data (added CaptureMethod.MaintainerCurated), a malformed SourceKind silently promoted to LocalTaxAuthority (now fails closed), and a false "~top-MSA" coverage claim (corrected to the real 11-city count, doc + Louisville work-locality caveat added). One Grok finding ("VerifiedNone unwired") was verified FALSE — a 16KB diff-cap truncation artifact in the review tooling itself, not a code defect. Rebased through T1's concurrent filing-status landing (one real merge conflict in IncomeTaxEstimator.cs/tax-data-2026.json/col-tax.md, resolved by hand — both tiers' honesty fixes coexist). Build 0 err, 4007/4007 tests green (full suite, post-rebase onto master).
🟢 Done WW-130 Take-home tax + COL T4: property tax + car cost-of-living adapters + Categories breakdown — landed c72354a Take-home Adds PropertyTaxSource (Census ACS B25103_001E median real-estate tax, reuses CensusAcsSource.ResolveCbsaCode's ~10-metro CBSA lookup) and CarCostSource (bundled state insurance+registration reference table — never a live API; ValuePenguin/NAIC for insurance, World Population Review for registration, DC has no registration figure so its Car category stays empty rather than guessed) as new ICostOfLivingSource adapters, plus an additive CostOfLivingSnapshot.Categories per-kind breakdown (CostOfLivingCategory: Kind/Value/Unit/Source/Citation/AsOf) that coexists with the existing scalar aggregate fields untouched — RealColAdjustmentProvider/CityComparisonService keep working unchanged. Test-authoring + docs offloaded to Grok per usage governor (Gemini unauthenticated this session). Council-code-review gate: Cedric (chair) + Gronk + local qwen3:8b seated (Jenny FAILED — agy OAuth unauthenticated); 2 Low fixed (PropertyTaxSource InvariantCulture parsing, CarCostSource CancellationToken observance). Build 0 err, 3941/3941 tests green (full suite, post-rebase onto master). Unblocks T5 (/compare-net view) + T7 (calm-line) once T6a (honesty render-guard + ColAdjustedDelta deprecation) lands.
🟢 Done WW-131 · WING-142 Take-home tax + COL T6a: honesty render-guard + ColAdjustedDelta/ColAdjustedTakeHomeDelta deletion — landed a3e0e92 Take-home Ceiling-tier scoring-logic surgery. Deletes both forbidden pre-blended "net take-home × COL index" scalars (JobImpact.ColAdjustedDelta, CityComparisonResult.ColAdjustedTakeHomeDelta) outright — replaced with two never-blended figures shown side by side: the raw take-home/comp delta (TakeHomeDelta/RawTakeHomeDelta) and a plain ColIndexRatio context figure, independent of salary data (computed off cities alone). OfferEvaluationService's RealComp axis now bands Strong/Mixed/Weak on the raw delta alone; COL context is a separate disclosed narrative line, never folded into the band (Kerr-clean). New TakeHomeHonestyGuard (mirrors DraftHonestyGuard's advisory shape) binds "estimate, not tax advice" plus fail-closed local-tax-incompleteness caveats to take-home/COL/offer-comp-axis surfaces — required widening ITakeHomeCalculator.EstimateTakeHome to return the full TaxEstimate (not a bare decimal?) so the caveats actually reach JobImpact/Offer RealComp, not only CityComparisonService. Council-code-review gate: Cedric+Jenny+Gronk seated (local Ollama FAILED preflight); chairman (Cedric) synthesized 1 High + 4 Medium + 2 Low: honesty guard bypassed on the two primary surfaces (High, fixed via the seam-widening above), Unknown-band offers silently dropped available COL context (fixed), unlabeled city-less caveats collapsed two different warnings via de-dup (fixed — caveats now labeled by city), a non-positive COL index rendered a nonsense "0x" ratio instead of degrading to null (fixed on both RealColAdjustmentProvider and CityComparisonService), near-1.0 ratios disagreed with their own displayed rounding (fixed), honesty disclosures were mixed into Citations instead of SelfReportCaveats (fixed). Rebased 2× through a fast-moving fleet window (WW-128 T2 landing, then a board-only peer commit) before a clean FF-push. Build 0 err; 93/93 WW-131-targeted tests green; full suite 4056/4057 and separately 4057/4057-minus-1, with exactly two non-regressing failures both confirmed pre-existing/environmental and unrelated: LocalRagLiveSmokeTests (live-Ollama dependency, passed on isolated retry) and DataPortabilityServiceTests.SensitiveDomains_AreIntakeAndGaps (confirmed failing on a clean origin/master checkout too — WW-98 education/skills domains never updated in that test; flagged as a separate follow-up, not fixed here to stay in scope). Unblocks T5 (WW-132, /compare-net view, session local_27ab5170) to begin its paint step.
🟢 Done WW-132 · WING-138 Take-home tax + COL T5: /compare-net cross-job view — landed d90eb05 (groundwork e71f5d1) Take-home Power-only /compare-net page: lists the saved job queue sorted by estimated take-home-Δ vs the user's Financial-History baseline (descending, null-delta jobs last — the only sort, no user-selectable/composite sort). Reuses two already-landed, already honesty-guarded endpoints (getQueue, getJobImpact) — zero new backend surface. ColIndexRatio renders as separate plain-language COL context (colContext logic copied from the already-reviewed JobImpactCard, round-before-branch, reject non-positive ratios) — never blended into the dollar deltas; no "best value" badge/winner anywhere; every row's honestyCaveats renders in full, never deduped. Two empty states (empty queue → /queue; jobs but no comparable impact → /financial-history). Groundwork commit separately added the previously-absent TaxEstimate/CityComparisonResult/CitySalary/FilingStatus TS mirrors + ApiService finances client methods, and fixed 3 unrelated pre-existing frontend breaks surfaced by finally running a real ng build/npm test in a fresh worktree (a stale FEATURE_FLAG_DEFAULTS index cast, a duplicate vestCadence key from two sessions' concurrent edits, mockApi() missing root-service stubs — the last one landed upstream (e8278cc) before this session got to it). Fleet-built (Grok) against a detailed spec; ticket-number mixup with a sibling session resolved by reading the council report's chairman table directly rather than trusting a relayed paraphrase (real mapping: WW-131=T6a, WW-132=T5). Council-code-review gate ran below its 2-seat floor: Codex seated clean ("no actionable correctness issues"); local Ollama failed twice (HTTP 000/500, GPU contention) and Grok's own review attempt truncated to an intro sentence twice under the same load; Gemini unauthenticated. Implementer self-check against every binding rule (single-axis sort, no blend, no badge, full caveat rendering) substituted given the degraded gate, on a low-risk read-only diff with no new backend surface. ng build 0 errors; frontend 1007/1007 tests green (full suite, incl. 6 new compare-net specs).
🟢 Done WW-133 · WING-164 Take-home tax + COL T7 (FINAL): Simple Mode calm-line + takeHome feature flag — landed 1dca970 Take-home Epic complete. job-impact-card.ts's existing "What changes for you" card (already gathering TaxEstimate-derived take-home data) grows a Simple Mode calm one-liner — "~$X,XXX/mo take-home — after taxes & commute" — tap-to-expand for source vintage + honesty disclaimer, never expanded by default. New JobImpact.TakeHomeMonthly/TaxDataVintage fields (absolute monthly take-home for the job, independent of TakeHomeDelta which needs both current+job salary) — reuses the existing model, zero forked calc. New takeHome.master/takeHome.calmLine flag pair, same master/AND-ed-child shape as every sibling epic, mirrored backend+frontend+contracts/feature-flags.defaults.json. Display-only: never a sort key, never blended into dollar deltas — /compare-net (WW-132/T5) keeps its own single-axis sort untouched. Council-code-review gate: Cedric+Jenny+Gronk seated (local Ollama FAILED preflight — RAM-starved, no token in 60s; house councils skipped, small 21KB diff). Chairman (Cedric) synthesized 1 High + 3 Medium + 2 Low, all fixed: Simple Mode was hiding the dense power content unconditionally even when the calm line had nothing to show (flag off by default, or no monthly figure) — title-only empty cards (fixed via hasPowerData()/calm-active CSS scoping); a <button> contained invalid block-level disclosure markup, breaking screen-reader announcement (fixed — sibling region + aria-controls); the collapsed copy still said "take-home" after commute was subtracted from it, blending payroll take-home with an un-modeled expense (fixed — relabeled "left" once commute folds in); the tap-to-expand state leaked across job navigation (fixed — resets on jobId change); blank TaxEstimate.DataVintage rendered an empty "(v)" source line (fixed — normalized to null); backend tests never covered the new fields (fixed — 3 new cases). One reviewer's "always show the disclaimer" finding was overruled — conflicts with the explicit tap-to-reveal product spec, which mirrors the already-shipped offer-detail reveal pattern. Rebased cleanly onto 9 peer commits (WW-100/101 transcript work, WW-143 discovery queue) before a clean FF-push. Build 0 err; backend 4330/4330, frontend 1141/1141 (full suites, post-rebase).
🟢 Done WW-97 · WING-102 Education epic T0 walking skeleton: FeatureFlagsService + api/settings/features + featureFlagGuard + gated nav + HonestyGuard [Obsolete] shell + IsEnabledAsync + cross-lang drift contract test — landed 7ae0d76 Education Epic spine, lands first — unblocks every edu lane AND the Simple-Mode sibling (WW-96). Backend-synced flags (AutomationSettings pattern, settings collection) + Angular signal service (ThemeService pattern) + localStorage cache; education.master default-OFF, children default-ON under it; unknown flag = off; backend rejects disabled-feature calls. SHARED contract with WW-96 (ui.simpleMode) — coordinate with WT-c9de before landing. Council-architected (fable tier, 5 seats + 5 house votes, report llm-council/reports/2026-07-23_175015_*). Review gate: council-code-review — Cedric (chair) + Jenny + Gronk + Gaming-PC qwen3-coder:30b + qwen3-next:80b seated; local seat FAILED (RAM canary); streaming skipped. 3 Medium + 3 Low + 1 Kerr applied. Gates self-run: backend build 0/0 + 84 targeted, tsc clean, vitest 193.
🟢 Done WW-98 · WING-103 T1: SkillRecord (claim/prove/aspire structural axes) + AcademicRecord contracts + EducationEntry.Id migration + TopSkills projection — landed 1d875ef Education New LocalJsonStore collections education/skills/skilltracks registered in BackupDomainRegistry (IsSensitive=true — academic PII), NOT in intake doc. Three axes = three structural fields, NO aggregate score anywhere by construction. EducationEntry.Id migration is load-and-persist stable (ProfileService.GetIntakeAsync) — a property-initializer-only default would mint a fresh id every load and silently orphan AcademicRecord.EducationEntryId links. Review gate: Gronktayvius (Grok) — found the RESULT doc still claimed TopSkills got [JsonIgnore]d even after the code fix (docs-landmine, same footgun already caught once) + the unstable-Id bug; gaming qwen3-coder findings were noise (misapplied the no-aggregate-score rule to plain transcript facts like GPA/credits). Both real findings fixed directly. Gates: backend 0/0 + 37 tests, tsc clean.
🟢 Done WW-99 · WING-104 T2: HonestyGuard real impl + resume-pipeline hook + banned-phrase gate tests — landed b728c69 Education FilterMaterialSkills() Prove-only, HARD, unconditional — wired into ClaudeDrafter's tailored-resume choke point. Council-mandated bridge (llm-council + gaming/streaming house, ~10/11 seats): SkillRecords has zero producers yet, so a separate never-touches-the-filter UnverifiedLegacy channel renders legacy TopSkills with explicit unverified framing when Prove material is empty; new drafting.skillsProveOnlyStrict flag (default OFF) can force strict-empty + mandatory user warning instead. InProgress degrees phrased via DegreeAdvisorCopy.DegreeInProgress. Banned-phrase test (unlocks/guarantees/qualifies) = review-gate blocker. Review gate: 2 rounds — Codex+Grok (gaming noise, discarded) caught UI never rendering the warning (HIGH), flag-read fail-open (HIGH), hollow ProveItems + blank-Name skills counting as material (MEDIUM×2), all fixed. Pre-existing ThankYou/Outreach TopSkills framing gap filed as WW-99b/WING-143 (not a WW-99 regression). Gates: backend 0/0+133 tests, tsc clean, vitest 67 files/1049 tests.
🟢 Done WW-100 · WING-105 T3: Transcript deterministic parser → proposals — landed a840a08 Education Reuses FieldProposal/ResumeProposal idiom exactly; new propose-from-transcript endpoint, flag-gated education.transcript (master-ANDed, default off). Course→skill = proposals only (confidence 65, lowest tier); no auto-mining — AcademicCourse.Skills explicitly documented as display-only, never substitutes for a real ProveItem(Kind:Course). Review gate: Codex+Grok (Grok ran actual ReDoS timing probes: no High/Critical for this threat model, hardening-not-exploit) — regex timeouts added, GPA/credit-total selection fixed (was first-wins, picked up junk over the real cumulative value), SourceKind no longer reports 100% confidence on zero-anchor garbage input, stale term-context leak fixed. Gates: backend 0/0+15 tests.
🟢 Done WW-101 · WING-106 T4: DARS hybrid parser (deterministic anchors + Claude enrichment) → proposals — landed fe168cd Education Mirrors WW-39b's IClaudeResumeExtractor/two-endpoint pattern exactly; wired into the real ILlmHarness routing (local-preferred, privacy-sensitive academic data, same class as Benefits extraction). mergeAcademicRecord field-level merge helper built + unit-tested — ready for WW-102/T5 to wire into the accept UI (not yet wired; RESULT.md corrected to not overclaim). Review gate: Codex+Grok independently found REAL, demonstrable fabrication bypasses in the anti-hallucination evidence-validation (zero-term auto-pass, ANY-vs-ALL term matching, "Math"⊆"mathematics" substring false-positives flipping completed↔remaining, unvalidated status/credits, courses matched against the whole report instead of the specific source span) — all closed via proper tokenized whole-word matching + semantic completion-conflict checks. Merge-helper scalar-blind-overwrite + course-retake-collision (code-only key, no term) also fixed. Gates: backend 0/0+14 tests, tsc clean, 4 frontend merge tests.
⚪ Backlog WW-102 · WING-107 T5: Import-diff wiring for education artifacts + course→skill accept UX Education Transcript/DARS proposals through the code-review-style per-field diff (import-diff epic pattern, proposal-apply.ts). Lives in profile/onboarding import flow — no new top-level route. Deps: WW-100/101 + WW-98.
🟢 Done WW-103 · WING-108 T6: JobPosting.EmploymentType + LinkedInJobLdParser internship tag + scraper propagation + schema.org-URL handling — landed c4be7a8 Education Additive field (today only WorkModel exists); extraction in LD parser only — council unanimous. Best-effort metadata, never a silent filter (employers mislabel internships as "Entry Level"). Enables queue faceting. Deps: WW-97. Review gate: council-code-review (Cedric chair + Jenny + Gronk + Gaming-PC qwen3-coder; local FAILED, streaming skipped) caught a HIGH dead-wiring bug — parser extracted EmploymentType but the scraper initializer never copied it into the persisted JobPosting, so every scraped job stayed Unknown; parser unit tests passed anyway. Fixed + propagation test + schema.org-URL last-segment mapping + length guard + metadata-only doc guard (Kerr). Gates self-run: backend 30/30, tsc clean, vitest 902/902 (1 flake ruled out, WW-89 class).
🟢 Done WW-104 · WING-109 T7: SimplifyJobs GitHub internship-list source (pluggable IJobSource) — interface+adapter landed 0400613 Education Research-decided second source (ww-internship-source-research): open, no auth, daily-updated, links straight to employer ATS pages WW already drives. Pluggable interface keeps Handshake slot-in-able later (deferred: university-gated). LinkedIn saved internships = primary, zero new scrape code (parity confirmed). Queue-wiring + provenance split into WW-109/WING-132 (that commit's own deferral target "WW-143" was a naming collision with the already-Done onboarding epic WING-123 — no ticket covered wiring until now).
🟢 Done WW-109 · WING-132 T7b: Wire SimplifyJobsSource into JobQueueService + JobPosting.Source provenance enum — landed 63ab970 Education Follow-up to WW-104. JobSource enum (Unknown/LinkedIn/SimplifyJobs), default Unknown — never silently claim LinkedIn origin (honesty posture). New sibling ResyncFromJobSourcesAsync (existing ResyncFromLinkedInAsync untouched), gated education.internships, dedup by Ats.ApplyUrl. Uses master's canonical IJobSource shape. Gates self-run: backend 0/0 + 68 tests.
⚪ Backlog WW-105 · WING-110 T8: Skills & Growth "Ready now vs Grow into" view (/skills-growth) Education Ready = Prove evidence; Grow into = empty Prove. Three axes rendered as distinct channels, never one score. Flag education.skillsGrowth; nav after /personality; power-only at launch (simple-visible candidate later). Deps: WW-98, WW-99.
⚪ Backlog WW-106 · WING-111 T9: Skill Builder close-the-gap plans (/skill-builder) Education StudyPlan {DegreeCourse
⚪ Backlog WW-107 · WING-112 T10: Degree Fast-Track Advisor (/degree-advisor) Education Mockup WorkWingman-Degree-FastTrack-Advisor-demo.html. Per saved job: May-already-qualify (equivalency) / Screen-not-yet-met (shortest accredited route, cheapest-credit-first CLEP/ACE/Sophia/JST) / Not-a-hard-screen (preferred ≠ required). Approved-strings catalog only — "satisfies an education screen", never "unlocks"; accredited non-profit/public only; no paid placement. Deps: WW-98 + partial AcademicRecord.
⚪ Backlog WW-108 · WING-113 T11: Internship-track surfaces — jobs-queue facet + skill demand from saved internships Education Facet on existing queue (no new route); skill-demand aggregation feeds SkillTrack targets; coursework/projects count as accepted Prove evidence, never padding; in-progress degree framed as strength via HonestyGuard phrasing. Deps: WW-103, WW-98.
⚪ Backlog WW-94 · WING-86 Triage the wider class of unguarded Playwright waiting calls in Automation/ — the WW-91/WING-14 tail Quality A triage task, NOT a defect list — do not mass-convert. WW-91 fixed 9 unbounded waiting calls guarded by catch (PlaywrightException) only. It was scoped by searching inside those catch blocks, and a council seat (Grok) found the blind spot: calls with no guard at all are invisible to that search — Paycom/Paycor's legend/heading reads were fixed reactively inside WW-91. This is the rest of that class. A heuristic scan flags ~78 candidate calls across ~14 engines, but that is an over-count by design: many are wrapped by a try further out, and — the point — many SHOULD fail the run and must be left alone (if GotoAsync can't reach the apply page, or the one resume SetInputFilesAsync fails, throwing is correct; swallowing hides a broken apply behind a cheerful "done"). The work is classification, per-site, not a regex: best-effort probe → route through LocatorProbes Try*/IsMiss keeping the default budget (never a tighter explicit timeout — manufactures load-flake, the c77df35 trap); load-bearing → leave, document why. The count itself is untrustworthy — WW-91's wobbled 24→2→7→6→9 across four scripts before a per-site read settled it, so enumerate with an analyzer or careful read, not the ~12-line window. WW-86 family: c77df35 → 9a35144 → WW-91 → this. Confirmed present; not a regression; not blocking — shipped fixes already cover the observed flakes.
🔵 In Review WW-92 Amazon.Tests's one browser test takes the shared browser — the last manual LaunchAsync in a test project Quality WW-93 promoted SharedBrowser to tools/WorkWingman.TestSupport and adopted it in Meta.Tests (11 browsers → 1). AmazonLabTests.cs was the only remaining test file calling Chromium.LaunchAsync itself — but note exactly one test in it launches a browser (Engine_answers_each_radio_question…), not the per-test storm the row title first assumed. Honest value, measured before building: SharedBrowser bounds browsers to one per assembly, and Amazon.Tests is its own process with a single browser test, so it was already at one browser — adopting SharedBrowser changes the count 1 → 1. This is NOT a perf fix; that story ended with Meta. The value is the invariant: no test project launches its own browser anymore, so a second browser test added to Amazon.Tests later shares instead of spawning a second Chromium, and "browsers ≤ assemblies" holds by construction rather than by luck. Preserved the test's graceful skip when Chromium is absent (SharedBrowser's install-fallback throws; the test catches and returns, unchanged intent). Corrections carried from this row's first draft (they'd have justified work that isn't needed): "all 13 labs launch a browser per test" was false (only Meta and Amazon ever did; Google/Icims/Microsoft launch none — 13 tests in 894ms proves it), and the PaycorApplyEngineTests failure blamed on lab load does not reproduce post-9a35144 (measured pre-rebase; the victim side was fixed independently). Separately still open and now WT-ae5b's (WW-86 follow-up): once no test launches its own browser, tests/WorkWingman.Tests/xunit.runner.json's maxParallelThreads: 0.25x may be pure cost — re-measure without it and delete if same-or-faster, since WW-86 found the cap made the suite faster, which is the tell it was throttling browser launches, not CPU.
⚪ Backlog WW-89 · WING-13 Frontend gate hole #2 — a vitest worker that fails to spawn exits 1 with every test green Quality The same masked-regression symptom WW-88 just fixed, arriving by a different route. Under memory pressure the threads pool fails to start a worker ([vitest-pool]: Failed to start threads worker for test files .../app.spec.ts → Timeout waiting for worker to respond); that file's tests are silently dropped from collection rather than reported failed, and vitest exits 1 with Errors 1 error while every test that ran passed. The healthy run is 43 files / 851 tests; the degraded run is 42 / 849, and nothing in the summary says two tests vanished — the denominator just shrinks. Worse than WW-88 in one respect: all-green output plus exit 1 invites "the exit code is just noise", which is exactly how a real regression walks through. Reproduced 2026-07-16 on andygreatroompc (29.37 GB): at 1.17 GB free / 89.5% commit it failed twice consecutively; same tree at ~4 GB free ran 43/851/exit 0 three times. Trigger is starvation at worker-spawn, not the code under test. Do NOT "fix" by raising the worker timeout — that trades a fast visible failure for a slow one and still drops the tests. Real options: (a) make dropped collection fatal and legible (name the file that never ran); (b) assert the expected file/test count in CI so silent shrinkage can't pass, independent of exit code; (c) evaluate pool: 'forks' vs threads (maxWorkers=1 is already set and still starves); (d) a RAM-floor preflight mirroring the council skill's local-seat canary — refuse to report a verdict the environment can't support. Same family as WW-86's load-rotating backend flakiness. Diagnosed from the log, not the symptom (WW-50/WW-86 lesson): first suspected as a rebase regression, ruled out — the nine commits it rebased past touch only .cs/.csproj.
🟢 Done WW-90 · WING-12 OfferDetail.delete() no longer swallows errors silently — landed b89cf11 Insights Added catchError → toast (Couldn't delete. / Try again in a moment.) → EMPTY on deleteOffer()'s pipe; success still navigates to /offers, failure stays put with feedback instead of an unhandled RxJS error. Council-code-review gate: Codex + Grok seated (Gemini/local/house councils skipped, trivial diff); Codex caught a real P1 in the first pass — the initial fix used a null sentinel to distinguish the error path, but HttpClient.delete<void> emits null on a real 204 success too, which would have silently blocked navigation on every successful delete. Fixed with EMPTY (completes without emitting, so the success subscriber never runs on error) instead of any sentinel value; corrected the pre-existing test mock (of(void 0) → of(null)) to match real HttpClient behavior, closing the exact gap that let the original bug ship unnoticed. Grok found no High/Critical; flagged sibling offers-list.ts has the identical bug — spawned as a follow-up task chip, out of scope here. Bonus catch during rebase: discovered + fixed a live master-breaking regression unrelated to this ticket — the shared mockApi() test helper (spec-support.ts) never stubbed getFeatureFlags/putFeatureFlags, which FeatureFlagsService (added by tonight's flag-wiring commits) calls at construction time, breaking all 38 job-detail.spec.ts tests with "is not a function". Landed as its own commit e8278cc. Build clean, 61/61 files, 1001/1001 tests green (full frontend suite, post-rebase onto master).
⚪ Backlog WW-73 · WING-10 Full-scope mutation measurement done right (WW-34 follow-up, council-ordered sequencing) Security (a) Fix VsTest socket instability: per-module/per-project shards, bounded concurrency, raised timeouts, and fix timeout-vs-killed scoring (exclude or bucket timeouts) before any full-scope number is trusted; (b) only after one clean full run: add the non-blocking scheduled broad run; (c) then ratchet per-module break thresholds against that real baseline; (d) after a week of stable scoped-gate runs, tighten scoped break 90 → ~95–97 (97.64% ceiling leaves ~7.6pt slack today). Historical failed full-scope runs are NOT score baselines. Council verdict 7/11 (llm-council/reports/2026-07-11_004747_*).
🟡 Needs Andrew WW-63e · WING-62 Fresh-desktop NVDA reconfirm + qualitative human NVDA/Narrator listen — the one irreducibly-human accessibility check Quality Everything automatable shipped (structure gate + apply-flow surfaces, 4da1144). NVDA's automation build degraded after many launches tonight; a fresh session re-confirms the spoken output, then a person judges whether the experience is actually good.
⚪ Backlog WW-65 · WING-64 Electron→Tauri shell migration — post-pilot, trigger-gated Architecture Council verdict 7/9 (3:1, logged in llm-council/reports/COUNCIL-LOG.md): stay on Electron; Pake rejected outright. Reopen ONLY on a trigger (tester size/RAM complaints or macOS demand), post-pilot: spike → a11y WebView2 parity hard gate → updater bridge release → opt-in ring cutover. Track A owns; GREEN-tier only; three council gates before any port work.
⚪ Backlog WW-75 · WING-73 Offer Review & Decision — accept/negotiate/wait support (council-specced) Insights Full-council design 7/11 (Claude/Codex/Gemini/Grok fable tier + 3 house seats; unanimous ranking Grok>Claude>Codex>Gemini; llm-council/reports/2026-07-11_130158_*; spec = docs/technical/offer-review-design.md). Three levers accept/negotiate/wait, never a ranking oracle. 6 axes: real comp (reuse JobImpact/COL/tax/equity chain) > timing/opp-cost > hard constraints (GATES not weighted axes) > growth delta > stability > fit. Kerr bans (binding): composite 0–100 score, point pipeline %, equity point-dollars, negotiation-success %, culture-fit %, company health score, Accept-as-CTA. Output = ordinal bands Strong/Mixed/Weak/Unknown first-class + per-axis narrative + scenario cards + decision journal. Intake ≤8 questions lifetime via new OfferGapAnalyzer. Pipeline EV = qualitative bands from published stage ranges (per-user calibration dishonest <~15 outcomes/stage); static verified extension templates, never auto-send. Stability panel shallow (layoff/WARN, revenue-trend enum, RSU vol haircut band) — starts Alpha Vantage, FMP unwired, no phase blocks on it. No personality test: O*NET work-values 6-item rank + retro culture signal → one Fit-axis line. New Offer entity (comp encrypted) + OfferService/OfferEvaluationService/PipelineOutlookService/StabilityContextService; equity → per-offer grants; tax estimator Single-only gap scheduled. MVP = Offer CRUD + deadline tracker + comp card + banded axes + scenario stubs + templates + journal.
🟢 Done WW-78 · WING-75 [SPIKE] USAJOBS API surface + auth/rate-limit recon Sources Doc landed: ww-78-usajobs-api-surface-recon.md. GO on pure-JSON ingestion, no Playwright. BYO-key Search (~5-10min form friction, approval-time undocumented) + no-key Historic JOA/Announcement Text/codelists for bulk. ToS is the harder constraint than tech — company-wide key needs legal/OPM clearance, BYO-key aligns cleanly. Unblocks WW-79..83.
🟢 Done WW-79 · WING-76 [SPIKE] Hiring-path & veterans-preference codelist mapping Sources Doc landed: usajobs-hiring-path-chip-mapping.md. Go on hp-* chips from HiringPath[] (vet/mspouse/nguard first-class); conditional-go on VEOA/VRA/30%-disabled via whomayapply/specialhirings (needs WW-78 field-occupancy sample); no-go on MilitaryStatusCode as posting metadata — preference points are profile self-attest + apply-prep only, never a posting label. Feeds WW-115.
🟢 Done WW-80 · WING-77 [SPIKE] Occupational-series ↔ MOS crosswalk join Sources Doc landed: ww-80-mos-occupational-series-crosswalk.md. No published SOC↔OPM crosswalk exists (G.ONET ~0.8% filled) — MOS→series is NOT a clean 3-table chain. Recommends hybrid ranked query-builder (DODOCC structural + lexical title match + family prior) emitting search seeds, never equivalence/fit claims. Worked examples for 25B/88M/92Y/68W/3D0X2.
🟢 Done WW-81 · WING-78 [SPIKE] Search (BYO-key) vs Historic JOA (bulk) ingestion strategy Sources Doc landed: ww-81-search-vs-historic-ingestion.md. Architecture split: Search+BYO-key powers live discovery (never block first-run on it), Historic JOA powers bulk/demo/corpus only (never applyable — no ApplyURI). Guided 3-step key-connect wizard + honest copy drafted. Feeds WW-111.
🟢 Done WW-82 · WING-79 [SPIKE] Federal apply-path reality (login.gov, DD-214/SF-15, federal résumé) Sources Doc landed: ww82-federal-apply-path-reality.md. Key surprise: USAJOBS résumés hard-capped at 2 pages (old 5-10pg advice obsolete). login.gov = MFA, not identity-proofing, for USAJOBS. Apply is a 2-system handoff (USAJOBS 5 steps → agency's own Submit). Full document matrix + résumé rejection drivers + status-state table. Feeds WW-114/WW-116.
🟢 Done WW-83 · WING-80 [SPIKE] SAM.gov opportunities API recon (P3 precursor) Sources Doc landed: ww-83-samgov-opportunities-recon.md (aa96b91). CONDITIONAL-GO for a P3 daily-pull+cache contractor-opportunity pipeline: pure-JSON Opportunities v2 API exists, first-class SDVOSB/VOSB/small-business set-aside + NAICS filters, but needs an entity-tier (~1k/day) key or Data Services bulk extracts — a bare individual key (~10 req/day) is NO-GO for production. UEI-only registration cannot bid; full SAM registration (free, up to ~10 business days per SAM's own guidance) plus a separate SBA VetCert are both required for SDVOSB/VOSB set-asides — registration is realistic but not instant, and copy must say so honestly. Kerr ban carried forward: no win-likelihood %/composite bid score, ever. Fleet-built (Grok). Doc-only, no code.
⚪ Backlog WW-76 · WING-74 Veteran vertical: MOS→civilian translation + USAJOBS pipeline + SAM.gov contractor bids (3 phases) Sources Spec = docs/technical/veteran-vertical-design.md (Kerr-checked at requirements altitude 2026-07-11; converts the YC veteran-channel claim from decorative to load-bearing per pitch-council verdict llm-council/reports/2026-07-11_135155_*). P1: bundled DMDC/ONET MOC crosswalk (static corpus, offline, WW-45 pattern) → skills/occupations enter ONLY as provenance-tagged proposals through the WW-39 confirm→apply rows — never auto-applied; confirmed-only data feeds fit + drafters; FitLevel enum UNCHANGED. P2: UsaJobsSource — free BYO key (vault), JSON API pipeline peer to LinkedIn; hiring-path/vet-preference chips = factual metadata NEVER sort keys (WW-74 rule); federal-résumé template mode (explicit user choice, honesty guard untouched); WW never clicks Submit, never touches login.gov. P3: SamOpportunitiesSource — contract opportunities for individual contractors; eligibility (set-aside/NAICS) = GATES not scores; Kerr bans:* win-likelihood %, composite bid score, federal-fit %, bids-submitted as hero metric (WW-68 headline stands); 10 req/day basic tier → daily scheduled pull + cache. Framing rule: service to veterans, not extraction (binding on copy/metrics/UI).
🟢 Done WW-74 · WING-72 LinkedIn repost tracking — neutral "listing history" context (council-gated minimal build) Insights Landed master e622315 (this row previously said "NOT merged/pushed" — stale; confirmed 2026-07-24 by WT-c2bc that e622315 is on origin/master with the full stack: backend model/service/interface/tests, listing-history-card FE, and the GET api/jobs/{id}/listing-history controller endpoint — a live browser re-verify of the card is still a reasonable follow-up but no code/merge work remains). Recon found the design pre-existed further than the spec assumed: LinkedInPostingSignal + extractor already wired at scrape time (LinkedInJobsScraper.cs:405) — the real gap was cross-listing linkage (a repost mints a NEW LinkedIn job id/URL → lands as an orphan queue row). Added: ListingHistoryEntry (append-only Sightings[]), ListingIdentity (SHA256 canonicalKey = company+title+locationBucket, exact-only auto-link, ≥0.85 same-company similarity = possible-match flag never merged), ListingHistoryService (best-effort resync hook, derived days at read), token-gated GET api/jobs/{id}/listing-history mirroring WW-59, neutral listing-history-card. Code-review gate: Codex (member+chair) + Gemini 3.1 Pro cross-reviewed; both independently caught a High bug — history recorded against the transient scrape GUID → 404 for already-saved jobs — fixed by remapping to the persisted queue-row id; +query-param strip (CanonicalUrl), clock-skew clamp, URL-encode. Gemini's cache finding skipped (matches LocalJsonStore full-load pattern). Grok not run (54k prompt > grok -p arg cap); house seats void (MCP can't read file-ref). Backend 64 targeted (incl. 4 new regression) + frontend 33 specs green; both builds clean. Council spec: build minimal, unanimous 4/4 (llm-council/reports/2026-07-11_001107_*). Detect reposts from OUR scrape history, never LinkedIn's fragile "Reposted" DOM label (closed shadow DOM on /jobs/view/; capture raw list-card label text opportunistically only). Identity: canonicalKey = hash(normalize(company) + normalize(title) + locationBucket); auto-link ONLY company-exact + title near-exact, anything fuzzier = "possible match" flag, never silent merge (multi-req employers false-merge otherwise); append-only listingIds[] under one canonical job. LocalJsonStore fields: canonicalKey, listingIds[], firstSeenAt, lastSeenAt, firstPostedAt, lastRepostedAt, repostCount, rawPostedText, companyNorm, titleNorm; total-days-open + days-since-repost DERIVED at read time, not stored. UI = one neutral "Listing history (from your scrapes)" context line; Kerr guardrails: never a score, sort key, red icon, or ghost-job label (proxy A = avoid reposted listings vs goal B = apply to right jobs — punishes legit evergreen/gov/big-tech reqs); copy: "re-listed; confirm still open — not a reason to skip"; optional pairing: long market-time → tactic suggestion (direct InMail over ATS). Explicitly OUT: urgency/desperation scores, ghost-job classifier, auto-deprioritize rules, opt-out-default filters.
🟢 Done (local) WW-68 · WING-67 Metrics tab + Keys/Plans Angular UI TROI Landed local master 7f70c8c (not pushed). /metrics "Your AI efficiency" section after Value Metrics content, real-wired to token-gated plan/capacity/usage endpoints; empty MEASURED/MODELED/routing/outcome panes stay honest until WW-69/WW-71. Backend 3,290/3,290 green; frontend 777/777 green; live browser verify passed — /metrics hit api/llm/plans + usage-summary + capacity (all 200), honest empty state rendered, 0 console errors. Gemini review findings applied. Awaiting Andrew push.
⚪ Backlog WW-69 · WING-68 Model router + RoutingRulesGrid TROI Task classes → models by fit + remaining budget, governor-aware: parse/extract/classify → local Ollama else cheapest; resume prose/interview answers → strongest key. User-visible + overridable. Savings claims per Token-ROI §5: MEASURED only with priced same-taskClass baseline.
⚪ Backlog WW-70 · WING-69 WW caveman: taskClass compression boundary TROI Compression allowed for internal pipeline classes {parse, extract, classify, dedupe, score, summarize-internal}; artifact-producing classes (resume/coverLetter/thankYou/interviewAnswer/learningPlan) structurally reject compression — user-facing artifacts always full quality. Savings MODELED unless A/B-measured.
⚪ Backlog WW-71 · WING-70 DurableOutcomeLinker TROI Links ledger spend → outcomes: submitted+kept-7d headline, artifact-adopted (explicit user keep/export/send + min meaningful diff), regenerations-per-artifact anti-frugality guardrail; interviews = context line only. Feeds Metrics tab + Value Metrics event log.
⚪ Backlog WW-72 · WING-71 WW council: multi-BYOK deliberation for high-stakes artifacts TROI Resume finals, key interview answers, high-stakes cover letters (learning plans/projects on request). Never auto-convened; disabled at YELLOW+. Consent modal w/ est. $, window-impact %, per-seat drop, max-spend cap, [Single model instead]. Tracking: $/adopted-council-artifact (adoption MEASURED, uplift MODELED).

Board status: the repo is pushed and CI green — the first push proved 11/11 jobs on a real GitHub runner (f02e64a, closes WW-33); as of b606a93 the workflows now target the self-hosted warm pool (home-ci runner group) because the GitHub-hosted cloud minutes are exhausted for the billing window. Every buildable Forms/ATS/Ops ticket is done; WW-32 is closed (real CVS Workday tenant surfaced + fixed a genuine phone/postal selector bug); the first push's three reds are fixed (WW-50 + WW-51). The tester-ready installer shipped (WW-55..58: bundled Chromium fixes the LinkedIn "local engine" failure, opt-in auto-updater, brand identity, tagline hero — installed and wizard-verified on Andrew's machine). Since then a provider-agnostic BYOK AI layer landed — the AIH epic below: one LLM harness (Claude/Codex/Gemini/Grok/Meta/Ollama), harness-agnostic grounded research + local RAG, BYOK audio overviews, and the Claude-CLI tailored-document drafters — the drafting path behind a verified zero-tools CLI lockdown and generated documents behind an anti-fabrication honesty guard — plus the company-health apply-time signal (WW-59). In progress now: WW-34's mutation ratchet is the last active build item — the apply vertical measures 100% of killable mutants; a full-scope run is measuring the rest before setting the break threshold. The accessibility ladder is fully automated — enforcing static axe gate (244c3d1, WW-63), live-backend axe + keyboard pass over real data (b10e146, WW-63b), and a real-NVDA screen-reader rig that asserts actual spoken output (afa0459, WW-63c) — leaving only WW-63d, the qualitative human NVDA/Narrator walkthrough. WW-62 is merged (4b71ee7 — token isolation + single-instance lock, council-gated to convergence). The BYOK layer then passed its full multi-model council review (AIH-7, ce6e40a): 11 verified findings fixed — research/ask/ingest + harness-list endpoints token-gated, correct Windows argv escaping, RAG ingest race + chunker skip fixed, cancellation honored, audio range-streaming — with the suite grown to 3,095 green.

Done this sprint

| Key | Ticket | Commit | | WW-95 | Deleted the 0.25x xunit parallelism cap — the tail of the WW-86 family. The cap was misdiagnosed at birth. WW-86 capped WorkWingman.Tests at maxParallelThreads: 0.25x and the suite went greener and faster; "faster from a cap" is the tell I missed — a thread cap relieving CPU contention does not speed a suite up, so it was never a CPU cap. It was rate-limiting the 137 per-test Chromium launches, treating the flake symptom while the real cause (a browser/memory storm) stayed. Once SharedBrowser (WW-93/WW-92) bound each assembly to one browser — zero Chromium.LaunchAsync left in any test project — the storm was gone and the cap became measurable in isolation for the first time. Measured, interleaved, same built tree (toggled only xunit.runner.json in the output dir, --no-build, identical binaries): capped ~119s vs uncapped ~74s steady-state, 3654/3654 green both, uncapped green even in the round that ran during a fleet spike at 1.6 GB free — the exact memory pressure the cap was blamed for. So it protected nothing; it throttled 3654 tests to 5 threads on 22 cores against browsers that no longer launch. Suite now runs at xunit's default width; CI-path re-run with coverage 3654/3654, 1m5s. Codex review caught a real migration gap (single-seat gate, RED governor): deleting the source file leaves the stale bin/xunit.runner.json on an incremental build, and xUnit reads the cap from there — a pulled-in dev would stay silently capped while the docs say uncapped (the same silent-wrong-config class as the whole WW-86/87 line). Added a BeforeTargets=Build delete of $(OutDir)xunit.runner.json, verified by planting a stale capped file and watching the build remove it; safe to drop once every checkout has rebuilt. This deletion is the better epitaph for WW-86 than the cap was — the compensation comes out because the cause got fixed. | 5d86a91 | | WW-91 · WING-14 | The tail of the WW-86 Playwright-flake family: 9 unbounded waiting calls across 7 apply engines were guarded by catch (PlaywrightException) only. Playwright for .NET raises PlaywrightException for a detached/unactionable element but throws System.TimeoutException (which does NOT derive from it) on a blown timeout, so a 30s-default expiry escaped and killed the whole apply run where the code plainly meant to shrug and try the next selector — the exact bug c77df35 fixed for the explicit-timeout sites, at the sites carrying no explicit budget. Two shapes: single-call sites take LocatorProbes' no-arg Try* overloads (keeping Playwright's default budget — imposing a tighter one would manufacture new load-flakiness, the trap c77df35 documented); block-level guards (Icims/Workday option readers, Greenhouse combo fallback) wrap several calls and mean "…or report none", so LocatorProbes.IsMiss was made internal and they write catch (Exception ex) when (LocatorProbes.IsMiss(ex)) — the exception pair defined exactly once. Scope measured per-site, not swept: IsVisibleAsync/CountAsync return immediately (can't time out) and Dayforce already caught both types, so both were left alone. Council earned this one: a seat (Grok) found the gap the audit could not see — it searched for unbounded calls inside catch blocks, so Paycom/Paycor's ReadPrescreenLabelAsync, which read legend/heading in NO try/catch at all, was structurally invisible; fixed here, all three reads now fall through on a miss. Three other seat findings dropped as false against git show HEAD: two claimed Workday's add.ClickAsync() had no prior guard (it had catch (PlaywrightException) { unmatched.Add(sk); }), one claimed Adp's break moved (always unconditional). Chairman: 0 surviving findings, and correctly rejected the original AC as an overclaim — this proves only the sites it touched. The rest of the class (unbounded waiting calls with NO guard, ~78 candidates, many of which SHOULD fail a run) is triaged as WING-86, not swept. Verified: build Release 0 errors; WorkWingman.Tests 3649/3649 green ×2. | 56474b4 | | WW-93 | The four orphan ScraperLab test projects join WorkWingman.slnx, and a test makes it stick. The bug: tests/WorkWingman.ScraperLab.{Google,Icims,Meta,Microsoft}.Tests (143 tests) were on disk but not in the solution, so dotnet build/test WorkWingman.slnx never touched them — which is how Google.Tests sat with 20 compile errors while the build said "0 Error(s)" (WW-87 found it by reading the diff). This was a1868e7 incompletely applied, not policy. That commit already fixed this exact bug for 23 projects, calling the exclusion a bug — "nothing in CI compiled or ran them" — but matched with a tools/ glob, so the four under tests/ were never seen. The "lab-exclusion rule" of b277aa4/ef48346 exists in no document, only those two commit messages, and cannot mean "lab tests stay out": a1868e7 re-admitted nine lab .Tests projects, and docs/ATS-LABS-TODO.md has said "Add both the lab and a .Tests project to WorkWingman.slnx" the whole time. b277aa4 DID remove Microsoft.Tests deliberately (peer WT-ae5b's catch, against my initial "accidental" framing) — but it removed the engine with it, and a1868e7 re-admitted the engine alone, so master implemented neither decision. tests/…Microsoft.Tests/stryker-config.json still declares "solution": "WorkWingman.slnx" — its own config assumed the membership b277aa4 took away. Council 7 seats / 5 lineages, unanimous: "exclusion is sediment, not policy"; the load-bearing argument is that every case for excluding labs is about running them, not compiling them — so compilation is exhaustive, execution selective. Landed: (1) the 4 projects in the .slnx (34 on disk = 34 in solution, exactly); (2) SharedBrowser promoted tests/WorkWingman.Tests/ → tools/WorkWingman.TestSupport/ and adopted in Meta.Tests, which launched a driver+Chromium per test (xUnit builds one instance per test method) — assemblies are what parallelise, so a helper only one assembly can reach caps only that one. Isolation preserved exactly: IBrowser.NewPageAsync() made a new context per call, so the helper takes a fresh context per page and tracks it — page1/page2 are still independent. Playwright added to TestSupport is free: all 17 consumers already pin 1.61.0, and its "dependency-free" rule was about PROJECT refs (keeping Core/Infrastructure out of E2E), not NuGet. Meta 6m15s → 16s. (3) [assembly: AssemblyTrait("Category", "Lab")] on all 13 lab test projects → dotnet test WorkWingman.slnx --filter Category!=Lab (.slnf rejected by council: a second membership list that drifts); (4) SolutionMembershipTests — globs csproj, parses the slnx, asserts set equality both ways. It lives in WorkWingman.Tests because that is the ONLY project CI executes: a script can be deleted or never reached, and prose already failed twice. (5) ci.yml names the solution instead of inferring it. Council round 2 (Codex/Gemini/Grok/local qwen3:8b; house seats skipped) caught 3 real defects, all fixed: the guard could pass on two empty sets (a guard against silent no-ops that could itself silently no-op — Grok); the csproj walk enumerated node_modules/.git before discarding them (Gemini, 506ms→282ms); one throwing context dispose stranded the rest. Two seats invented the same SharedBrowser leak — a false positive: git rendered the move as a rename, so the method body (which already has the try/catch) was not in their diff. Agreement between seats reading the same incomplete artifact is not corroboration. Verified by running, not asserting: guard green 5/5 and red 2/5 twice when a project is deleted from the slnx; --filter Category!=Lab → "No test matches"; full solution 3887 non-E2E passed / 0 failed (WorkWingman.Tests 3654/3654, 14m25s→1m32s), all 13 labs green. E2E's 7 reds are the documented missing npm run build, unchanged and untouched by this diff. Corrections I shipped against myself: "the omission was accidental" (partly wrong), "this change makes the suite flakier" (measured pre-rebase; 9a35144 fixed the victim side — it does not reproduce), "all 13 labs launch a browser per test" and "27 lab test projects" (false: only Meta and Amazon launch; there are 13). Each would have justified work that isn't needed. Follow-up WW-92 | 6f75eb9 | | --- | --- | --- | | WW-88 · WING-85 | npm test in frontend/ exited 1 with all 851 tests passing — a green run and a red run were indistinguishable by exit code, so the suite could not gate anything. Cause: offer-detail.spec.ts mocked ActivatedRoute but let the real Router reach the component; OfferDetail.delete() calls router.navigate(['/offers']), which rejects NG04002: Cannot match any routes against an empty TestBed route table, and vitest counts the unhandled rejection as an error. The spec's own comment called that rejection "harmless (no test impact)" — it was wrong, and the wrongness is the whole bug: the rejection had no test impact and total gate impact. Fixed by stubbing Router with { navigate } rather than provideRouter([]), which shadows the mocked :id param (component then loads id '' instead of 'offer-1' — the trap the original comment correctly documented). Confirmed pre-existing and unrelated to WW-73 by stashing all local changes and reproducing NG04002 on the clean base. Council 4/4 CLI seats (Codex/Gemini/Grok/local qwen3:8b; house seats skipped by request): unanimous that stubbing beats a routed harness here, 3/4 independently confirmed the new navigate assertion is sound under synchronous of(void 0); the local seat dissented and was overruled as wrong on RxJS semantics (claimed the sync observable causes a race). Applied the one shared finding — setup() now returns { fixture, navigate } instead of mutating a describe-scope let. Validation: unverified, not passed — npm test was never run green on the exact landed tree. frontend/ is byte-identical to a tree that ran 43 files / 851 tests / exit 0 three times, and all nine commits it rebased past touch only .cs/.csproj that vitest never reads — strong inference, but inference. The two rerun attempts died to the box at 1.1 GB free (→ WW-89, filed). | 9381771 | | WW-87 · WING-84 | LoopbackListener follow-ups from the WW-86 council. Dropped the requestedPort hint — only ever a first guess the helper re-picks past, so 29 call sites were handing it a probed port it would have found itself; deleting it took the last 5 duplicated FreePort() probes and 8 int port ctor params with it. out param → tuple (Start() -> (HttpListener, string BaseUrl)) — Gemini and Grok both preferred it, and the argument isn't style: after a collision the bound port is NOT the one asked for, so an ignored out-param binds one port and advertises another. With no port at construction time there is no honest provisional url, so Shape-B fixtures expose BaseUrl via a throwing getter and the two BaseUrl_is_loopback_only tests now Start() first — asserting the ACTUALLY-bound url instead of a ctor-composed string, which is strictly stronger. The retry is finally tested: it was unreachable on purpose (the public picker only returns FREE ports), so an internal Start(Func<int> nextPort, int maxAttempts) seam + InternalsVisibleTo lets a test squat a port and feed it back as every pick — exhaustion, attempt-limit, re-pick and picker-throws now covered, on the exact path that shipped broken in c373db9 and survived 11 green runs. Plus a _disposed guard on FakePaycorSite, and Oracle's --port flag removed (nothing consumed basePort any more, so it silently did nothing) with its stale learnings-doc bullet rewritten. Council: Codex caught that tests/WorkWingman.ScraperLab.Google.Tests is NOT in WorkWingman.slnx — the ctor change broke it with 20 compile errors while dotnet build WorkWingman.slnx said "0 Error(s)"; fixed here, orphan-projects blind spot filed separately. Grok caught, for the second round running, that this change's own new test contained the WW-79 race (probe a port, release it, assert that exact port) — rewritten. Also from Grok: listener leaked if the picker threw (fixed + tested), maxAttempts <= 0 silently bound once (now rejected). Verified: 4/4 slnx runs green at 3645/3645, zero non-E2E failures, + all four out-of-solution projects run by hand (39/13/79/12) | 9e1335b | | WW-86 · WING-83 | dotnet test WorkWingman.slnx nondeterministically red — 6/4/2 failures over three runs on an unmodified tree, 11 distinct tests, all green in isolation. Diagnosed from the logs, not the names (WW-50's lesson): the four tests named in the report were never the cause — they already isolate via TempStore's per-instance GUID temp dir. The failing set rotated with machine load: as background load drained across four runs, failures fell 6→4→2→0 with no code change. Three real causes. (1) Oversubscription — apply-engine tests each launch their own headless Chromium; at xunit's default width (× ~10 projects under the .slnx) they saturate the box and wall-clock waits expire. xunit.runner.json caps at 0.25x cores, which made the suite faster (2m26s→~1m50s) — the oversubscription was costing throughput. (2) Real production data loss in LocalJsonStore — WithRetryAsync caught only IOException, but Windows reports a File.Move colliding with a reader's open handle as UnauthorizedAccessException, which derives from SystemException (compiler proves it: CS0184 "never of the provided type"). The retry could never catch the platform's most common contention error; best-effort callers (RoutingDecisionLog, AiUsageLedger) then swallowed it and dropped rows. Reproduced idle in 525ms as Expected 25/Actual 24. Also guarded the Directory.GetFiles sweep so litter collection can't fail a save. (3) Port TOCTOU in the fake sites: GetFreePort() releases its probe before HttpListener.Start() binds. Fixed in two passes — Grok caught that the first sweep missed the copies drifted to a different helper name (FreePort(), port picked at the call site, so the window spans construction). The second commit then found the first one's retry could not retry at all: HttpListener.Start() closes itself when the bind fails, so looping on the same instance throws ObjectDisposedException on attempt 2. Eleven green suite runs and a 4-seat council all missed it — the race is rare enough the retry path never once executed, and green runs only prove the code that ran. Only a test that forces the collision finds it. The correct retry needs a fresh listener per attempt, which a caller can't produce inside its own catch — so the helper had to own creation, making the dedupe the fix rather than the cleanup: new dependency-free tools/WorkWingman.TestSupport/LoopbackListener (jittered backoff, returns the url actually bound), all 21 fixtures migrated, ~23 duplicated helpers deleted, net −154 lines. The duplication wasn't hypothetical debt — it's exactly why the first sweep missed files. Also killed FakePaycorSite's null! (→ throwing getter) and MsLearnCatalogSourceTests' blind port guess. Note: these two commits carry a fix(WW-79): prefix in error — WW-79 is the USAJOBS veterans-preference spike; the ticket number was invented mid-session and the mislabel was found only after both had been pushed. History left intact (no force-push per the isolation policy); this row is the reconciliation. | c373db9 + 0cd6895 | | WW-85 · WING-82 | ApiToken's token-file write retried only IOException while guarding a cross-process race Windows reports as UnauthorizedAccessException — same root-cause class as WW-86's LocalJsonStore retry, spun out when it was spotted in passing. Worse failure mode than a dropped telemetry row: ApiToken is constructed at app launch, so an escaping exception fails startup | e4521d5 | | WW-84 · WING-81 | Equity "Grant Details" + all of Financial History rendered form fields/pills/hint-text unstyled — the shared .field/.pills/.pill/.q/.q-label/.footnote/.grid2 primitives live per-feature in ~20 component scss files but were missing from these two, so they diverged from the rest of the app. Promoted the canonical primitives to global styles.scss as a fallback (Angular emulated encapsulation keeps the 20 local copies higher-specificity, so no other page changes; only the gap pages inherit). Council review (Codex + Gemini + Grok seats; local/house seats skipped — trivial additive CSS): scoped the pill fallback to .pills .pill so it doesn't leak button chrome onto bare status badges (offer-detail's <span class="pill gate-pill">); rejected namespacing/:hover/mobile-collapse — goal is parity with the existing unprefixed primitives, not new divergence. Also fixes offer-detail's own previously-unstyled pill group | 717c0d8 | | WW-67 · WING-66 | Per-key plan tiers + WW usage governor: plan settings in settings JSON (never the vault — key material untouched), per key alias/provider/declaredTier/userMonthlyWWCap/reservePercent/model allowlist; PlanObservation capture of anthropic-ratelimit-*/x-ratelimit-*/429 retry-after (exact header allowlist + value cap, RFC HTTP-date Retry-After parsed) via a second AsyncLocal ambient — GenerateAsync contract unchanged; tier inference vs declared with mismatch SURFACED never overwritten, confidence enum declared/header-confirmed/inferred/unknown; editable MODELED capacity table (embedded defaults + source URLs); UsageGovernor per key per window — WW cap = cap×(1−reserve), GREEN<60 / YELLOW 60–80 (enrichment+suggestions pause) / RED 80–95 (core apply only) / BLACKOUT ≥95 or active hard-429 (all paused; opt-in failover; UserOverride with cost-warning flag), wired into the WW-66 metering door so blocks actually block (GovernorBlocked ledger rows); token-gated /api/llm/plans endpoints. Deferred: explicit user-initiated signal → WW-68 UI plumb; fire-and-forget ledger staleness accepted (WW-66 precedent, bounded by 15s TTL + hard-429 cache invalidation). Built via Codex offload (c98ebf9), review gate: Claude ceiling + Codex + Gemini — 8 findings fixed, 2 justified-deferred (f05be5d); Grok + house seats not run. Suite 3,286/3,286 green | c8bbe96 (c98ebf9 + f05be5d) | | WW-34 · WING-8 | Mutation coverage ratchet for the apply vertical — CI gate wired and enforcing: mutation.yml backend job now runs stryker-ww34-scoped.json (4 apply-vertical core files, validated 97.64% = 100% of killable mutants — 289 killed / 0 timeout / 2 survived-equivalent w/ in-source proofs / 5 NoCoverage) with break: 90 (ratchet ON), job renamed mutation-gate (apply-vertical scoped, break 90) so green never reads as full-codebase coverage; triggers = weekly + dispatch + master pushes/PRs touching the mutated core or its tests (test-only bypass closed). Broad ~22-module run REMOVED until VsTest socket fix — two full-scope collapses scored timeouts as killed (inflated, discarded); follow-up = WW-73. Survivor-killer tests landed earlier at 2165909. Council verdict 7/11 unanimous option-1 (Claude+Grok+Gemini seats; Codex/local seats not run; house seats concur). Review gate: Codex (test-path bypass) + Gemini (concurrency cross-cancel, persistent-runner tool install) — all applied; Grok seat not run | e24a1c3 (+ 2165909) | | WW-66 · WING-65 | BYOK AI-usage metering — sole-door content-free ledger: MeteringLlmHarness decorator wraps EVERY registered harness (runtime all-metered proof — no harness escapes the door; hostname string-grep kept as defense-in-depth); AiUsageEvent structurally cannot hold content (no prompt/response fields — PII excluded by type); AiCallContext AsyncLocal ambient reports real provider tokens without changing the frozen GenerateAsync signature (null = not measured, never a fake zero); append-only ai-usage-{yyyy-MM} shards, fire-and-forget ledger writes off the caller's critical path, thrown harness calls still metered as Failed then rethrown. Meter-before-optimize satisfied — unblocks WW-67..72. Council-gated (Gemini seat; Codex sandbox-blind, Claude chaired; Grok seat not run); Kerr check: runtime all-metered proof closes the bypass vector | b28bf7a (feat 6fd624f) | | WW-64 · WING-63 | DraftHonestyGuard fabrication-detection expansion: the fd2d8d1-deferred bypasses closed — narrative-verb orgs ("joined Google", "attended MIT"), ex-Org shorthand, affiliation-prefix orgs ("graduate of Yale"), all case-insensitive with alumni/alumnae plurals; "with"-bound titles gated behind an explicit "as <Title> with" lead-in (bare "with" swallows collaboration prose); commas kept in title tokens ("VP, Engineering" binds whole); dotted degrees (M.B.A.) recognized by both the level check and the school-anchored tuple check (deg terminator \b → (?![A-Za-z])\.? — \b fails after a terminal dot); ex-Role/latin-finance exclusions (ex-CEO, ex-Officio, ex-Dividend). Postfix "<Org> graduate" deliberately excluded (collides with "Computer Science graduate"). Council gate to convergence, 4 rounds (Codex chair + Codex/Gemini/local seats; Grok seat wired into the council mid-gate, degraded this run; house-PC seats down). Tests 37→62 adversarial · full suite 3,120/3,120 green | 8d51f0d | | WW-63d · WING-61 | Full-route + apply-flow accessibility: a deterministic a11y-tree structure gate over all 22 routes (one h1, <main>, heading order, every focusable control has an accessible name, no positive tabindex) now enforced in CI, extended to the two dynamic apply-flow states (judgement / account-wall) via read-only fabricated-run mocks. Fixed a real bug: the mid-run account-wall pause ("create an account to continue") had no screen-reader announcement — now a labelled navigable region + a persistent aria-live region announced on the state transition (host only, never the vault credential), all controls named. NVDA fidelity suite extended (representative-route h1, account-wall region, judgement question). Codex+Gemini council (pre-Grok seat): all findings applied (firewall read-only hole, reliable-announce live region, detector accuracy, PII redaction). Structure 0/22 + dynamic states · axe 0/22 · frontend 757/757 | 4da1144 | | WW-63c · WING-60 | Real-NVDA screen-reader rig: drives actual NVDA via Guidepup, asserts SPOKEN OUTPUT as text over 5 flows (skip-link-first-at-top, nav names + role, form-label announcements, tagline no-spam, sensitive-dialog announce/Escape). Strictly READ-ONLY over real data (per-request token injection, non-GET + WS + SW blocked, propose-from-linkedin stubbed + allowlisted, PII-safe report). Local-only, never CI. Caught + fixed a real skip-link exposure bug (clip-path→clip:rect). Two-track docs + 2 Mermaid diagrams. Codex+Gemini council (pre-Grok seat): all findings applied; verified 4 pass/1 skip + strict-mode negative check | afa0459 | | WW-63b · WING-59 | Live-backend a11y pass: axe over REAL data (25 routes incl. param variants from real ids, 0 violations; job-detail target-size was the one data-full find, fixed) + keyboard-behavior tests (skip-link ✓, strict-mode option for env-gated ones) behind a hard READ-ONLY firewall (context-scoped, WS/SW blocked, mutation attempt = red test, artifacts off, PII-safe report). Codex+Gemini council (pre-Grok seat): all 8 findings applied | b10e146 | | WW-62 · WING-57 | ApiToken test isolation + Electron single-instance lock (root cause of the demo-morning 401 hang): WORKWINGMAN_TOKEN_PATH override so test suites/Stryker never clobber the live app's token; test-assembly [ModuleInitializer] temp path with ProcessExit cleanup; requestSingleInstanceLock(); Electron strips the override from the spawned API env (preload-path contract). Full council gate (Codex chairman + Codex/Gemini/local qwen3:8b/gaming-PC qwen3-coder:30b seats; pre-Grok seat): 2 findings applied, 1 justified-skipped, 8 dropped; 3,095/3,095 green | 4b71ee7 (53dddaf + 649e2db) | | WW-63 · WING-58 | WCAG 2.2 AA package: ARIA/table/combobox/tab semantics across ~40 templates, native <dialog> modals (guarded showModal), skip-link + .sr-only + global reduced-motion kill-switch, contrast token floors both themes (semantic ladder kept), eslint a11y rules, and an enforcing axe-core CI gate (22 routes, 0 violations; critical/serious or unauditable = fail). Codex+Gemini council (pre-Grok seat): 5 findings applied, 2 deferred to WW-63b, 2 refuted | 244c3d1 | | WW-61 · WING-9 | CI rerouted to the self-hosted warm pool: GitHub-hosted cloud minutes exhausted for the billing window, so ci.yml + mutation.yml now target the home-ci runner group (org warm pool) instead of ubuntu-latest/windows-latest | b606a93 | | WW-60 · WING-56 | Claude-CLI zero-tools hardening: verified against the real CLI (claude.exe 2.1.201, cmd.exe /c, stdin) that --allowedTools "" is only an allow-list and left Read/Bash live (read a planted secret back); switched all three CLI drafters to the documented --tools "" (blocks tools, still returns text). Added PromptSafety — fences untrusted JD/resume/interview text, neutralizes forged fence markers, forbids URL/image exfil markup — and pinned SystemProcessRunner WorkingDirectory against a claude.cmd CWD hijack. Codex+Gemini council to convergence (pre-Grok seat) | 580478f + fd2d8d1 | | WW-59 · WING-55 | Company-health apply-time signal: backward-looking read (layoff history + news + price momentum) surfaced as a job-detail card at apply time — informational, never advice | eb9dad5 + c38b1e9 | | WW-58 · WING-54 | Brand hero (Angular): reusable <app-brand-hero> on Onboarding — wide lockup + the four canonical taglines (elevator-pitch doc) rotating in a fade/slide carousel over a drifting amber/teal aura; theme-adaptive, responsive, honors prefers-reduced-motion; verified live in both themes | 3b91612 | | WW-57 · WING-53 | Brand identity pass: "Ascending Wing" placeholder mark (3-model convergence — Claude + Codex + Gemini briefs), generated icon.ico/favicon.ico/adaptive favicon.svg (make-brand.ps1), amber promoted to first-class --brand token, logo tile flips ink↔cream by theme so it never sinks into the background; sidebar lockup + tagline | 3b91612 + 5255d53 | | WW-56 · WING-52 | Opt-in auto-updater (electron-updater on NSIS, council-endorsed): Settings → Updates card with three modes — Manual (never phones home) / Notify (default) / Automatic — progress bar, restart-and-install; window.workWingmanUpdates IPC bridge; dev builds degrade to unsupported, browser degrades to "managed by the desktop app"; generic feed inert until latest.yml + exe are hosted | 3b91612 | | WW-55 · WING-51 | Installer completes the "one program" promise: Playwright Chromium bundled into the NSIS installer (PLAYWRIGHT_BROWSERS_PATH + extraResources + reproducible build-installer.ps1) — fixes the LinkedIn vault login "Could not reach the local engine" (browser was missing on clean machines, mid-request download timed out); branded assisted wizard (sidebar/header art, beta EULA, no-UAC per-user); /healthz readiness probe with X-WorkWingman-Api marker; 7za shim for winCodeSign symlinks without Dev Mode | 3b91612 + 379911e + 622421b | | WW-33 · WING-23 | Security CI proven on a real GitHub runner: after the first push, all four Security jobs (Semgrep SAST, gitleaks secrets, ZAP DAST baseline, dependency audit) pass green on ubuntu-latest — not just locally via Docker. Full run: 11/11 jobs green | f02e64a (run) | | WW-51 · WING-47 | First-push CI corrections (green locally ≠ green on a Win+Linux matrix): (1) WW-50 wrongly added a Linux sudo rm apt-cleanup to the windows-latest backend install step — reverted, apt-hardening kept only on the ubuntu churn+E2E jobs; (2) ConnectionsService vault badge used Path.GetFileName on a Windows path → returned whole on the Linux runner, normalized \→/; (3) references.md missing KPCLib + System.Security.Cryptography.ProtectedData, exposed once the Docs job got past two-track to the references gate. Codex caught the windows-vs-linux job | f02e64a | | WW-54 · WING-50 | Docs link-integrity gate: a relative-link checker across all of docs/ (Gate 2b — catches the kind of ../plain → ../business-plain break the WW-50 move risked) + orphan gate widened to docs/reference + docs/business-plain; also repointed stale doc→source links left by the Phase-2 stub removal (cb297f4) | f52fb9f + 2f12987 | | WW-53 · WING-49 | iCIMS real-tenant gaps (Cotiviti): fill the structured address (AddressCity/AddressZip/AddressState/AddressCountry) and label-driven custom questions (<label for>-resolved, for opaque rcf<n> ids); country filled from Contact.Country, not a literal | e7118b3 + a51efef | | WW-52 · WING-48 | Greenhouse real-tenant gap (PerfectServe): fill the required Location (City) + Country comboboxes (type-then-pick-from-list), which the engine previously left for the user | a760dd0 | | WW-50 · WING-46 | First-build CI green: diagnosed each first-push red from its log, not its name. Churn wasn't a flaky test — playwright --with-deps hit the runner's intermittently-unsigned packages.microsoft.com apt repo; drop that source before all three installs (Chromium libs come from Ubuntu's own repos). Electron wasn't a boot hang — .brand-name now wraps the REAL/SANDBOX pill, so exact toHaveText('Work Wingman') retried to timeout; assert on a new env-independent data-testid="brand-product". Docs was real — moved misfiled records → docs/reference/, plain business companions → new docs/business-plain/ (council: move, don't game). Full council (Codex + qwen3:8b + gpt-oss:120b + qwen3-coder:30b) concurred all three. | 93d8155 | | WW-32 · WING-22 | Real-tenant Workday fix (CVS Health): the signed-in "My Information" form revealed the data-automation-id sits on the field CONTAINER, inner <input> is bare — so our input-first contact selectors made phone + postal-code fail outright and the rest match only by luck. Contact chains now lead with [data-automation-id='formField-…'] input:not([type='hidden']) + phone/postal name fallbacks. Validation doc | 761aae9 | | WW-49 · WING-45 | Bug-hunt fixes: address suggestion adopted WHOLESALE (+ Nominatim now requires a street Line1) so a partial pick can't blank a typed ZIP/state; skill picker over-fetches 30 then caps to 12 AFTER removing already-selected so the dropdown can't empty. Also: real-tenant Workday validation doc (WW-32) | (this) | | WW-48 · WING-44 | Bug fix (found mid-run): résumé PASTE stamped a bogus SourceResumeFile="pasted résumé" — hid the WW-41 Generate fallback AND gave engines a path File.Exists rejects. Now applyResume never fakes a file; provenance moved to a cosmetic label; Generate stays offered so paste-only users get a real attachable file | 4a40aa1 | | WW-42 · WING-35 | First-push prep: docs/reference/first-push-checklist.md — verified pre-push hygiene (no artifacts/secrets/large blobs; gitignore/gitattributes/gitleaks/Stryker/SECURITY.md all present; clean tree) + what the first push triggers (full ci.yml security suite + mutation.yml) + post-push verification = WW-33 | (this commit) | | WW-44b · WING-39 | Skill-graph picker (Angular): onboarding skills step gets a real editor — chips + debounced corpus search + a "related to what you added" one-tap strip (excludes already-selected); live-verified (react→related minus already-picked Angular) | 7980fe8 | | WW-44a · WING-38 | Related-skills graph: SkillGraph (~20 overlapping clusters over the WW-45 corpus → shared-family adjacency, no-self/dedup/case-insensitive; corpus-integrity test) + RelatedSkills (normalize→graph) + /api/vocab/skills/related | f06b63b | | WW-45 · WING-40 | Canonical ATS reference vocab: AtsReferenceVocab (curated skills/certs/schools seeds + complete education-levels) + AtsReferenceVocabService (prefix/substring Suggest for pickers, symbol-aware Normalize via OptionMatching, null below threshold) + /api/vocab/*; seed sizes queryable (no silent cap) | 9ecffd2 | | WW-46 · WING-41 | Address autofill: CensusAddressSearchService (US Census geocoder — resolves real residential addresses Nominatim misses) + CompositeAddressSearchService (authority-first, Census→Nominatim) + fast-fail Autocomplete pipeline (4s/no-retry) on both; frontend/endpoint unchanged; live-verified vs the real Census API | a814fb8 | | WW-43b · WING-37 | LinkedIn import UI (Angular): "Import from your connected LinkedIn" button on onboarding feeds the SAME confirm→apply rows as résumé paste; proposal-source flag keeps LinkedIn imports from stamping SourceResumeFile; best-effort toasts; button verified live | 63527ab | | WW-43a · WING-36 | LinkedIn profile import: LinkedInProfileLdParser (pure, schema.org Person ld+json) + LinkedInProfileProposalBuilder (→ proposals on the WW-39 field-path convention) + LinkedInProfileImporter (own profile, read-only, never-throws, login-wall→gap) + /propose-from-linkedin | bb3b1cc | | WW-47c · WING-43 | Field-validation UI (Angular): review-step banner lists present-but-malformed fields (email/phone/URL/date) via /validate, advisory (never blocks save); @let-clean, clears-before-fetch, takeUntilDestroyed; verified live | 829d583 | | WW-47a/b · WING-42 | Canonical validation: FieldValidators (+date/safe-text/by-format), ProfileValidator, /validate endpoint; never-type-invalid guard in all 3 engine fill paths (Greenhouse/iCIMS/Workday, secrets exempt) → skips malformed + logs "left for you" | 043c6e1 | | WW-41b · WING-34 | No-résumé start (Angular): review-step "Generate from my answers" card (offer / ready+preview / already-on-file); persists intake → generate-resume → points SourceResumeFile at the file; race-guarded vs the review auto-save, takeUntilDestroyed; verified live (file on disk, card flips) | 0b7d81c | | WW-41a · WING-33 | No-résumé start backend: ResumeComposer (pure IntakeProfile→plain-text résumé, invents nothing) + ResumeGenerationService (writes generated-resumes/base-resume.txt in a contained dir → sets SourceResumeFile → persists) + /generate-resume endpoint; engine attach works unchanged (File.Exists-only). Inverse twin of WW-39 | 863e622 | | WW-40b · WING-32 | Profile Gaps panel (Angular): /gaps route + nav item; Recommended/Optional sections, apply-time pill, error-reverting dismiss, empty state; verified live (10 gaps, dismiss 10→9) | 7c19732 | | WW-40a · WING-31 | Profile Gaps backend: ProfileGapAnalyzer (completeness, blank-row + partial-address aware) + ProfileGapsService (apply-time "left for you" harvest, deduped, serialized, dismissable) + /gaps endpoints; sensitive backup domain | 1b1be61 | | WW-39c · WING-30 | Resume-parse confirm UI (Angular): paste-résumé card on onboarding step 1 → confidence-checked proposal rows (edit inline) → Apply maps into the typed profile (whitelisted, index-capped); background /enrich merges education/work history; stale-callback token; verified live | f4f54e1 | | WW-39b · WING-29 | Resume-parse Claude-CLI enrichment: mockable IProcessRunner (hard timeout, tree-kill, no truncation/orphan) + ClaudeResumeExtractor (best-effort, gated, never-throws) filling education/work-history; split /enrich endpoint off the critical path | 2676dae | | WW-39a · WING-28 | Resume-parse backbone: deterministic ResumePreParser (email/phone/name/links/skills → proposals, never saved) + canonical FieldValidators + POST /api/profile/propose-from-resume; 10 review rounds | a24c754 | | WW-38 · WING-27 | "How did you hear about us?" + referral across all engines: honesty order (referral → stated channel → LinkedIn default), referrer name/email follow-ups; shared ApplicationSource | ae0953d | | WW-37 · WING-26 | Skills + language picker fill across all engines: symbol-aware matching (C#≠C++≠C), descriptor tolerance, Greenhouse skills-vs-spoken routing, iCIMS hidden-checkbox fallback, Workday prompt-style skills | 59176c6 | | WW-36 · WING-25 | Credential translation across all engines: levels, majors (aliases), expected graduation, cert sections | bc71432 + de9e006 | | WW-35 · WING-24 | Profile + intake for real forms: credential kinds, certs, language proficiency, referrals | 3a10ec5 | | WW-31 · WING-21 | iCIMS promoted to production — third ATS engine (iframe-aware, coordinated wall, stops at EEO) | b7a549a | | WW-29 · WING-20 | Workday: fill every work-history block (repeater targeting + dates + current-role skew) | d8f690e | | WW-25 · WING-16 | Workday multi-phase resumable drive (account wall + degree judgement) | 2d65168 | | WW-25b · WING-17 | Coordinate the account wall · Workday background drive ON | da58b0e | | WW-28a · WING-18 | Semgrep SAST hard gate in CI + both first-scan findings fixed | f6891f0 | | WW-28b · WING-19 | Security workflow: Semgrep MCP, gitleaks, ZAP DAST, SECURITY.md | 2bf147d |


AI spend — retroactive backfill (Token-ROI, 2026-07-10)

Read this first: every figure below is estimated — reconstructed after the fact by joining session logs to ticketed commits by time window (token-roi backfill, commit c0a3eec). Dollars are equivalent API prices, not cash (work ran on plan subscriptions). Phases show [unsplit] because historical board transitions weren't logged — per the Token-ROI v1.1 rule we refuse to fabricate phase splits. Spend that couldn't be attributed without guessing is listed as unattributed, never pro-rated. Live tickets (WW-66+) will carry exact, phase-split figures once metering lands.

Ticket AI spend (est.) Turns Phases
CON-7 ~$292 1 [unsplit]
CON-1 ~$224 16 [unsplit]
WW-55 ~$183 7 [unsplit]
APL-9 ~$165 1 [unsplit]
DOC-1 ~$119 1 [unsplit]
AIH-3 ~$112 1 [unsplit]
WW-52 ~$109 5 [unsplit]
APL-3 ~$97 1 [unsplit]
WW-66 (design sitting) ~$93 1 [unsplit]
TST-5 ~$84 1 [unsplit]
WW-34 ~$83 2 [unsplit]
FND-6 ~$76 1 [unsplit]
APL-10 ~$75 1 [unsplit]
CON-11 ~$67 4 [unsplit]
TST-1 ~$57 1 [unsplit]

Unattributed (visible by design): repos-root sessions ~$16,709 (1,143 turns — most work ran at the shared repos root on master, too ambiguous to assign without guessing); WorkWingman-cwd residue ~$499; all other repos itemized in backfill2.json. Grand total across all history: ~$20,607 equivalent-API (1,314 session files). Per the Kerr table: a rising unattributed share counts against the measuring system, not the tickets.

Delivered epics

WING-248 · Cloud docs backfill — Jul 30

Story: technical making-of chapter · plain

45 docs + 5 D2 diagrams backfilled across technical/plain/business/business-plain tracks: release pipeline + incident taxonomy (WING-257), Cloudflare edge (254), WingCMS (252), GCP infra (255), API keys (256), OAuth + reader tiers (259), MPA site (250), editions matrix (262), metrics/telemetry (260), tutorial recorder (261), Porkbun (253), cloud cost template (258), Cloud Run plain/business completion (249), manifest rebuild + diagrams + making-of (264). Negative finding recorded: no separate YC SPA exists (251). Drafted by Jenny/Gronktayvius from pinned fact briefs, dual-seat reviewed. Sibling fleet docs: FLT-183 (Vikunja mirror), FLT-184 (MkDocs docs hub).

AIH · Provider-agnostic AI (BYOK) — Jul 7–8

Story: technical · plain

Every AI capability moved behind one bring-your-own-key seam so the user picks the provider and holds the key. The Claude-CLI drafting path adds a verified zero-tools lockdown (--tools "") plus PromptSafety fencing, generated documents pass an anti-fabrication honesty guard, and the other BYOK paths (LLM / research-RAG / audio) fence untrusted input through the same PromptSafety seam — so untrusted job/resume text can never turn a model into an exfiltration or fabrication vector.

Key Ticket Commit
AIH-1 Tailored documents: Claude-CLI resume rewrite + cover letter + thank-you notes + .docx writer, with DraftHonestyGuard rejecting any draft that invents an employer/school/degree/year 0925e62
AIH-2 Claude-CLI security hardening: --tools "" zero-tools lockdown (verified --allowedTools "" was ineffective), PromptSafety untrusted-data fencing + marker neutralization + no-exfil-markup, CWD-hijack fix; honesty-guard bypasses closed 580478f + fd2d8d1
AIH-3 Provider-agnostic BYOK LLM harness (Claude/Codex/Gemini/Grok/Meta/Ollama) + LlmController + connections wiring; the drafters/extractors rewired onto it 86c5509 + 8644c81
AIH-4 Harness-agnostic grounded research + local RAG: LocalRAG (Ollama embeddings, cosine retrieval, citations), honest-failure on missing embedder, StudyController research endpoints 3793429 + 1c329bd
AIH-5 BYOK audio overviews: provider-agnostic TTS + AI music + grounded scripts (AudioOverviewController), untrusted script text fenced a7e6f68 + bd04086
AIH-6 Integration merge to master + Codex integration-gate fixes on the BYOK merge 9cd8f9c + eb07b3c
AIH-7 Full council review of the BYOK merge (Codex + Gemini seats + Codex chairman; house-PC seats unreachable; pre-Grok seat): 11 verified findings fixed — [RequireLocalToken] on research/ingest + research/ask (+ length cap, unknown-harness 400) and /api/llm/harnesses (account-email leak), correct CommandLineToArgvW argv escaping (+ round-trip tests), RAG ingest SemaphoreSlim, chunker skip fix, cancellation rethrow, audio range-streaming, Stdin/Timeout override parsing, probe via ArgumentList, HMAC disposal. 3,095/3,095 green ce6e40a

BIZ · Business & strategy — Jul 6

Story: technical · plain

A multi-model council (Codex/gpt-5.5 + Gemini + gaming/streaming-PC open councils, Claude chairing) decided the monetization sequence — free local core → paid convenience → institutional seats → consent-gated staffing → verification → partnerships last — then dedicated research agents deepened it into a full business doc track.

Key Ticket Status
BIZ-1 Council monetization verdict + decision record ✅ Done (council-verdict)
BIZ-2 Monetization strategy, roadmap, GTM & positioning (detailed + plain) ✅ Done
BIZ-3 Financial model — pricing/costs/ROI/break-even (narrative + live .xlsx) ✅ Done
BIZ-4 Pitch deck + elevator pitch + executive one-pager (.md + .pptx) ✅ Done
BIZ-5 Legal/compliance & risk analysis (ToS/CFAA/FCRA/privacy/security, cited) ✅ Done
BIZ-6 llm-council.exe — persist partial answers before chairman step ✅ Done
BIZ-7 Adaptive Solutions partner pitch deck — council-reviewed, revised, rendered to .pptx; de-slop pass retitled slide 7 (.pptx regen pending PowerPoint close) ✅ Done (50c634e + ef542df)
BIZ-8 Competitive analysis: 10-vendor sweep (OSS + commercial auto-apply) → competitive-analysis.md + plain twin; conclusions folded into GTM positioning; Black-owned identity positioning across business docs; managed-AI tier clarified opt-in/BYOK-default; July-8 making-of chapter (both tracks). Council-gated (Codex + Gemini, pre-Grok seat; 19 findings applied) ✅ Done (a3eb109 + 32e9d6f)

APL · The apply-automation vertical — Jul 5–6

Story: technical · plain

"Start Run" went from checklist theater to a real, resumable, coordinated browser drive.

Key Ticket Commit
APL-1 IApplyAutomationEngine contract + AtsKind→engine router 29a468a
APL-2 Production Greenhouse engine drives the fake site end-to-end 50f3a1e
APL-3 Background apply-run driver + advisor seam — Start Run drives for real a28be22
APL-4 Capture Ats.ApplyUrl from the page (externalApply unwrap + SSRF host guard) e914cc1
APL-5 Atomic pause/resume + defensive URL decode (full-council review) e0ba560
APL-6 Resumable-drive infrastructure — park at a pause holding the live page 804c9c5
APL-7 Greenhouse resumable custom-question answering (never guess) cfd0855
APL-8 Auth test gaps filled (GoogleOAuth / GitHub device flow / ClaudeCli) 9c3cc5c
APL-9 Workday multi-phase resumable drive 2d65168
APL-10 Account-wall coordination + background drive ON da58b0e
APL-11 Fill every work-history block (repeater targeting + dates) d8f690e
APL-12 iCIMS production engine — iframe topologies, coordinated account wall, EEO-default stop b7a549a

SEC · Security & scanning — Jul 4–6

Story: technical · plain

CVE gates first, then the full SAST / secrets / DAST stack.

Key Ticket Commit
SEC-1 Vulnerable-dependency CI gate + Dependabot 55b277c
SEC-2 Sensitive-endpoint gating consolidated on RequireLocalToken 7feb614
SEC-3 Semgrep SAST hard gate (675 files; both findings fixed) f6891f0
SEC-4 Semgrep MCP + gitleaks history gate + ZAP DAST + no-store API + SECURITY.md 2bf147d

CON · Connections, vault & real data — Jul 5

Story: technical · plain

Demo boilerplate out — real KeePass vault, working sign-ins, real scraped jobs.

Key Ticket Commit
CON-1 Structured intake + Google OAuth (PKCE loopback) + honest save UX 98a8cb2
CON-2 Profile links + portfolio repos on Connections c520927
CON-3 Real .kdbx vault + working GitHub/Claude/LinkedIn sign-ins 3d26ae0
CON-4 Apple Calendar via .ics email + dual real/sandbox environments c5daed1
CON-5 Medium study suggestions via per-tag RSS 2337c1e
CON-6 Tier-0 Google (no sign-in) + LinkedIn connect fixes + env pill 4d2f248
CON-7 Queue stops serving demo boilerplate bd4c0e2
CON-8 LinkedIn saved-jobs scraper rebuilt (JSON-LD first) + platform research dbfbedf
CON-9 Real study-platform deep links + MS Learn Catalog API 506760d
CON-10 ATS detection extended to 12 families d18e5d7
CON-11 Analytics engine seam (funnel/trend/salary) 17e1b5f
CON-12 Full merge audit — found the apply vertical unwired; became the APL epic 005b86d

INS · Job insights & interview prep — Jul 4 (overnight run 2)

Story: technical · plain

~28 feature branches fanned out concurrently, merged through the review gate, then 7 integration seams wired so nothing stayed orphaned.

Key Ticket Commit
INS-1 Financial profile + job-impact comparison 52a068b
INS-2 Lifestyle costs (HUD housing equivalence + commute projection) 054ef5d
INS-3 Family costs (USDA groceries + NCES/SEDA schools) c24c587
INS-4 Cost-of-living + local income-tax estimator fdc2ddf
INS-5 Comp signals (H-1B LCA + BLS OEWS + posted ranges) 25d1f94
INS-6 Equity-grant valuation (informational only, never advice) 8bdf07d
INS-7 News → realized-price event study (backward-looking) 034b2a7
INS-8 Company research briefing (GitHub + GDELT + Wikidata) 0530a9d
INS-9 Benefits insight (Form 5500 + HRC CEI) 3897ec8
INS-10 Assessment-platform detection + practice deep links 33ed317
INS-11 Learning paths (role-matched practice catalog) 05ef630
INS-12 Demographic resources (opt-in, local-only) 5337668
INS-13 Prep studio (company LeetCode libraries + phone flashcards) 897774c
INS-14 Interview prep bank 6dff92d
INS-15 NotebookLM opt-in prep export ef8f598
INS-16 BYO-key onboarding (vault-stored, masked) 27d6e77
INS-17 Interviewer research (read-only, no auto-connect) 0e502db
INS-18 Self-assessment suite (local IPIP Big Five) c4e3c66
INS-19 Interview retro loop dc17ab6
INS-20 Interview coaching edf3511
INS-21 ATS account-wall handling (vault mint + KeePassXC handoff) 6fa3436
INS-22 Data backup (Excel round-trip + opt-in Drive sync) 993e77e
INS-23 Study media + visuals (real results only; BYO-key illustrations) c82df83
INS-24 Integration: 7 seams wired + job-detail page + anti-hallucination verifier 7373f15…

LAB · ScraperLab — Jul 4 (overnight run 1)

Story: technical · plain

Fifteen offline ATS learning labs: every ATS practiced against a local fake site before touching a real form. Selector fallback chains and judgement pauses were learned here.

Key Ticket Commit
LAB-1 Workday lab — selector fallback chains hardened (visible-scope trick) 6dfebcb
LAB-2 Greenhouse · Lever · iCIMS labs (iCIMS iframe-aware) bf15b7b…
LAB-3 Oracle/Taleo · UKG · SuccessFactors labs 1243c59…
LAB-4 Paycom · Paycor · Dayforce · ADP labs 1997e76…
LAB-5 Microsoft · Amazon · Google · Meta labs (advisor-ready pauses) b2559be…
LAB-6 Answer taxonomy + AnswerResolver + DRAFT-ONLY outreach 61cd682
LAB-7 Job signals (repost detection + legal layoff history) d38310f
LAB-8 PageAgent research → hybrid pivot (Playwright drives, AI advises) 4115c88…
LAB-9 Frontend resilience interceptor (GET-only retry, error→toast) 875eee9
LAB-10 20-branch merge + integration fixes 9fd3155…

TST · Testing foundation — Jul 3–4

Story: technical · plain

24 tests → 2,648: mocked coverage on both sides, E2E journeys, mutation testing.

Key Ticket Commit
TST-1 Backend mocked coverage (NSubstitute + Bogus, Flurl HttpTest) 6d7b356
TST-2 Frontend specs for every screen + service (faker builders) 0d7a3e5
TST-3 Playwright.NET UI journeys (Page Objects, own CI job) 1f6839d
TST-4 Electron desktop-shell E2E c49a459
TST-5 Stryker.NET backend mutation + weekly workflow cdc916e
TST-6 Frontend StrykerJS via standalone Analog vitest 96bf02e

DOC · Two-track documentation & diagrams — Jul 3–6

Story: technical · plain

Technical + plain versions of everything, CI-gated so they can't drift.

Key Ticket Commit
DOC-1 Two-track learning docs + Mermaid diagrams 9037713
DOC-2 C4 diagrams (D2) + rendered SVGs + drift-check + self-heal CI 01959ae…
DOC-3 Docs gates: two-track pairing, no orphans, src-changes-carry-docs 713c31d
DOC-4 Making-of journal (living build story, both tracks) 981a246…
DOC-5 Merge-audit + security-audit write-ups bcaa48c…

FND · Foundation — Jul 3

Story: technical · plain

Design import → working Electron + Angular + .NET skeleton with CI and a dependency doctor.

Key Ticket Commit
FND-1 AutoApply prototype design import 7d35e4b
FND-2 .NET backend skeleton (Core, Flurl+Polly, Playwright, local-first services) 2135bce
FND-3 Angular frontend (9 screens) + Electron shell 8a77132
FND-4 CI: Azure Pipelines → GitHub Actions 647d865
FND-5 Full dependency + framework refresh (Angular 22, Electron 43) 99a3626
FND-6 Dependency doctor (detect on launch, per-item approval, token-gated) 75ccfe9

Defect & review-bounce history

Every ticket goes through the review gate before its commit lands, and the gate regularly sends work back. This ledger keeps that history: what bounced, who caught it, and what was found only after code landed. Review models are advisors, not oracles — so the ledger also records claims the gate REJECTED with evidence, because a rejected false positive is a decision worth remembering too.

Review bounces — caught before the commit landed

A "bounce" = a reviewer finding that sent the ticket back for another pass before its gate converged. Chronic themes the gate kept enforcing: honest progress reporting (never count a field that didn't land), Stop means stop (no mutation after a Stop, across every await), and intent ≠ reality (verify what the page actually did).

Bug Ticket Found by Finding Outcome
BUG-1 FND (CI) Codex CORS config blocked the packaged Electron origin; --no-build broke fresh-checkout dev start Fixed 0ec7519
BUG-2 FND-6 Codex Dependency doctor's install endpoint was unauthenticated; Playwright installer assumed a PATH CLI; KeePassXC undetectable off-PATH Fixed 329477f
BUG-3 FND-6 Codex Electron preload read the API token from the wrong path (LOCALAPPDATA mismatch) Fixed 1c3f2eb
BUG-4 TST-5 Codex Two mutants "killed" by over-suppression instead of real assertions (reseed-id stability, failed-serialization atomicity) Un-disabled + covered 922d640
BUG-5 LAB-10 build break Triple-duplicate SelectorAttempt record collision surfaced at the 20-branch merge Fixed 9fd3155
BUG-6 LAB-10 / INS-24 Codex Integration-diff P2s on both overnight merges Fixed a164e03, 798de59
BUG-7 APL-3 Codex Run published to ActiveRuns before its first log entry — a concurrent poller could serialize mid-add Fixed afbbd22
BUG-8 APL-5 full council Pause/resume not atomic vs a concurrent Stop; apply-URL decode could throw on malformed input Fixed e0ba560
BUG-9 APL-9 Codex ×8 rounds Workday drive bounced repeatedly: background launch unsafe pre-coordination (P1); only first work-history entry filled; saved degree answers not consulted before parking; Stop not honored mid-fill or across resolve awaits; EEO advance unverified; run left AwaitingJudgement after resume; resumed degree choice unvalidated; FieldsFilled counted profile values not DOM writes All fixed in 2d65168
BUG-10 APL-9 Gemini Stop check raced the selector-resolve await; fill-count inflated when halted mid-contact Fixed 2d65168
BUG-11 APL-10 Codex ×5 rounds Coordination bounced: null-from-coordinator treated as "signed in" (would fill the app on a login page); stopped runs resurrectable mid-check (P1 — coordinator ran on the wrong cancellation token); drives left zombie-Running; blocked runs mislabeled "final review"; coordinated prompt lost the real sign-in URL All fixed in da58b0e
BUG-12 SEC-3 Gemini Electron waitForApi could hang forever — Node fetch has no timeout, bypassing the 30s deadline Fixed f6891f0
BUG-13 SEC-4 Codex Gitleaks CI job would abort on runners (root container vs runner-owned checkout → git "dubious ownership"); allowlist was whole-file (could hide future real leaks); electron dep tree Dependabot'd but not audit-gated All fixed in 2bf147d
BUG-14 APL-11 Codex ×7 rounds Repeater work bounced repeatedly: naive loop would overwrite block 0 (shared automation-ids); added blocks left required dates blank; current role never ticked "currently work here"; blank end date wrongly treated as "current"; fake-site clone kept the checkbox checked (caught by a failing test); hidden end-date sections skewed later blocks' visible indices; skew tracked intent not reality; fallback-tenant counts read 0/0; Stop raced the checkbox tick All fixed in d8f690e
BUG-15 APL-11 Gemini Real Workday hides the end-date section asynchronously (React render cycle) — the immediate visible-count read would always race it; pre-pause saved-degree reuse ignored click failure; hardcoded log blamed EEO for education failures Fixed in d8f690e

| BUG-16 | APL-12 | Codex ×9 rounds | iCIMS promotion bounced repeatedly: email-probe raised false account walls (password field is the real gate signal); async-injected iframes mis-read as inline; contact email skipped on no-gate tenants; stopped runs resurrectable at the wall and the judgement raise (P1); unrecognized resume pages stranded the drive; advancing past EEO would silently keep iCIMS's PRESELECTED protected-status defaults (P1) — the engine now stops there; unresolved judgements no longer advanced past (same latent gap found + fixed in Workday); paste-resume textarea could shadow a hidden file input | All fixed in b7a549a | | BUG-17 | APL-12 | Gemini | Instant CountAsync frame probes raced JS hydration (bounded waits now); stop-guard gaps at resume attach + degree selects | Fixed in b7a549a |

| BUG-18 | WW-36 | Codex ×9 rounds | Translation layer bounced: judgement parks didn't stop LATER education fills (both engines); the translator's "Bachelor's default" overstated partial profiles (P1-class honesty); "graduation year" vs "expected graduation date" label precision ×3 (year-only fields nearly got yyyy-MM); cert-name selector fallbacks could hit checkboxes/date fields; unconditional cert-section Next clicked on from review on no-cert tenants; missing stop re-checks + quiet probes | All fixed in de9e006 | | BUG-19 | WW-36 | Gemini | New public matcher entry points NRE'd on null degree text (profiles legitimately omit it); Combine("aa","a","a") set-logic would fabricate associate bridges from stray "a" tokens; noisy noExpire probe | Fixed in bc71432/de9e006 |

Found after the fact — landed code, caught later

Bug Surface Found by Finding Outcome
LIVE-1 Apply vertical merge audit The headline: StartRunAsync seeded a checklist and logged "Run started" without ever opening a browser — the whole vertical was scaffolded but never connected across the overnight merges Audit 005b86d → became the APL epic
LIVE-2 Job queue live use Queue served demo boilerplate as if it were real scraped jobs; resync didn't fail fast without LinkedIn Fixed bd4c0e2
LIVE-3 Connections live use LinkedIn connect flow + scraper URLs broken in real use Fixed 4d2f248
LIVE-4 Intake live use Saving intake returned 400 (validator demanded implicit-flow fields that PKCE doesn't use) Fixed in the Google OAuth work 98a8cb2
LIVE-5 Connections live use Token-file race between API startup and Electron preload Fixed c520927
LIVE-6 Interview retro live use Recording company feedback wiped unsaved retro fields Fixed 875ade6
LIVE-7 StudyVisualStore Semgrep (first full scan) SaveAsync path-combined an unsanitized file name — a path-traversal hazard one caller away from real (all existing callers mint Guid names) Hardened f6891f0
LIVE-8 API caching ZAP baseline API responses (personal data) were cacheable — flagged on the live loopback API Cache-Control: no-store on everything 2bf147d
LIVE-9 Git history gitleaks (first full-history scan) 2 hits in 131 commits — both reviewed false positives (enum identifiers; a literal YOUR_LAUNCH_TOKEN docs placeholder) Narrow line-targeted allowlist in .gitleaks.toml 2bf147d
LIVE-10 Semgrep MCP live smoke test The planned uvx semgrep-mcp / ghcr.io/semgrep/mcp forms were deprecated upstream — the old image serves only a deprecation notice Registered the current semgrep mcp form 2bf147d
LIVE-11 Address autofill live use The address filler couldn't autofill a real user address — not every address resolves through the current fill path Tracked → WW-46 (postal validation/autocomplete source)

Rejected findings — claims the gate refuted with evidence

Recorded because "reviewer said X, evidence said otherwise" is a decision with precedent value. House rule: Codex is the tiebreaker; every rejection needs a concrete refutation, never a shrug.

Claim By Refutation
Git CRLF warnings in diff text are "compilation errors" (twice) Gemini Build succeeded + full suite green both times
Headers.CacheControl "doesn't compile" Gemini Strongly-typed IHeaderDictionary property since .NET 6; built, tested, and observed live via curl
gitleaks git is "an invalid command" Gemini The exact invocation had just scanned 131 commits successfully, twice
GitHub auto-links bare commit SHAs in .md files Gemini Auto-linking works in issues/PRs, not rendered markdown — board uses full commit URLs (Codex tiebreak)
Degree-dropdown click result ignored Gemini Code already captured and branched on the result (misread)
LocateSubmit would block 30s on the EEO wall Gemini ResolveAsync uses a 1.2s per-selector timeout, not Playwright's default
Coordinated prompt's SignupUrl fix "is lost before save" Gemini No database — the UI polls the same live object; the mutation is visible on the next poll
CountContactFields allocation "hot path" Gemini Called once per run — not a hot path (superseded anyway by the DOM-write counter)
visible=true chained locator "searches descendants, resolver always null" (P1, asserted twice) Codex Refuted by passing E2E — a leaf <input type='submit'> resolves and fills read back; pattern normalized to the explicit >> form for readability
Coordinated account wall "deadlocks — engine never sets AwaitingAccount" Gemini The SERVICE's CheckAccountWallAsync sets PendingAccount + status itself (seam ownership); proven by the Workday coordination tests
Angular templates "can't compile object spread — job-detail fails to build" (P1) Codex ng build succeeded, 27/27 job-detail specs pass, live preview rendered the exact card — Angular 22 supports it
"iCIMS level/expected fills index Education[0] unguarded" Gemini The fills already sit inside the Education.Count > 0 block — diff-context misread
Referrer WorkEmail "can be null → crash / CS8619" on iCIMS+Workday+Greenhouse (WW-38) Gemini WorkEmail is a non-nullable string (=""); build emitted zero CS8619; FillPresentAsync skips empty. No null reaches a fill
Referrer fills "lack an IsHalted guard between name and email" (WW-38) Gemini Both engines' FillPresentAsync/FillAsync self-guard IsHalted on entry AND across the resolve await — a Stop between fills is already honored

Scorecard so far: Codex — high precision, two refuted claims (the visible=true descendant assertion and the template-spread compile claim — both stale-knowledge, both disproven by passing builds/tests), caught five P1s (unauthenticated install endpoint, stopped-run resurrection, iCIMS EEO preselected defaults, judgement stop race, month-input DateOnly 400s). Gemini — real catches the others missed (async-hide race, fetch-with-no-timeout, cross-OS path normalization, hydration-racing frame probes) but ~9 refuted claims, mostly from reasoning about a stack it assumed rather than the one in front of it. The scanners each earned a LIVE row on their first run. That spread is the argument for the council: no single reviewer has the whole picture.


Gate evolution across the project: Codex solo → Codex + Gemini → full multi-model council → + Semgrep / gitleaks / ZAP → + Grok seat (Jul 9; chips name Grok whenever it sat, first runs degraded). See SECURITY.md for the current stack and technical/making-of.md for the narrative version of this board.