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 |
| 🟢 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, commitc0a3eec). 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¶
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¶
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¶
"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¶
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¶
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)¶
~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)¶
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¶
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¶
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¶
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.