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).
Active sprint¶
| Status | Key | Ticket | Epic | Notes |
|---|---|---|---|---|
| ⚪ 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. |
| ⚪ Backlog | WW-90 · WING-12 | OfferDetail.delete() fails silently — no catchError, no toast, unlike its sibling methods |
Insights | offer-detail.ts:172-176 — this.api.deleteOffer(o.id).subscribe(() => this.router.navigate(['/offers'])). Two gaps on one line: the subscribe(next) form handles only next, so a failed delete (network drop, backend 500) becomes an unhandled RxJS error and the user sees nothing — offer still on screen, no explanation, no retry; and the router.navigate promise is discarded, so a future guard or route rename surfaces as an unhandled rejection. This is the outlier in its own file: recordDecision() at line 160 already does it right (catchError → toasts.show('Couldn't save your decision.', 'Try again in a moment.') → of(null)). ToastService is already injected, so the fix carries no new dependency. Latent gate hazard: there is no delete-failure test today; adding one with throwError against the current code would produce an unhandled rejection and return the suite to exit 1 — the exact WW-88 class of failure. AC: failed delete shows a toast and stays put; success still navigates; spec gains a throwError test asserting the toast fires and navigate is NOT called; npm test still exits 0. Found by the Gemini and Grok seats during the WW-88 council (both rated it out of scope for a test-only commit; Codex and the local seat did not raise it); verified by hand against the source. |
| ⚪ 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. |
| ⚪ Backlog | WW-78 · WING-75 | [SPIKE] USAJOBS API surface + auth/rate-limit recon | Sources | Research-only (output = decision doc, no prod code). GTM goal: prove "USAJOBS as a pipeline" is real + cheap before the pitch promises it. Enumerate all 3 REST APIs — Search (auth: API-key request form + User-Agent + Authorization-Key headers; paging 250 default / 500 via ResultsPerPage), Historic JOA (NO auth, bulk), Announcement Text — + unauth codelists. Deliverable: capability matrix + go/no-go on JSON-only ingestion (no Playwright). Unblocks WW-79..83. Spec §Phase-2 research spikes in docs/technical/veteran-vertical-design.md. |
| ⚪ Backlog | WW-79 · WING-76 | [SPIKE] Hiring-path & veterans-preference codelist mapping | Sources | Research-only. GTM goal: the veteran-preference surfacing that differentiates our channel. Pull /codelist/hiringpaths + /codelist/militarystatuscodes; map vet-preference paths (vet, 30%-disabled, disabled, mil-spouse, VEOA/VRA) → neutral posting-chip taxonomy. Kerr gate: chips = factual posting metadata, NEVER sort keys/scores/eligibility oracles. Deliverable: mapping table + chip spec. Parallel w/ WW-80. |
| ⚪ Backlog | WW-80 · WING-77 | [SPIKE] Occupational-series ↔ MOS crosswalk join | Sources | Research-only. GTM goal: closes the core loop — a veteran's MOS → real federal jobs. Join USAJOBS /codelist/occupationalseries to the DoD/O*NET MOC crosswalk (WW-76 P1) → MOS → federal series → Search-keyword query builder. Deliverable: join feasibility + coverage/gaps report (which MOSs map cleanly, which don't). Parallel w/ WW-79. |
| ⚪ Backlog | WW-81 · WING-78 | [SPIKE] Search (BYO-key) vs Historic JOA (bulk) ingestion strategy | Sources | Research-only. GTM goal: distribution friction — how fast a veteran gets value on install. Compare per-user BYO Search key (rate limit, minutes-to-issue onboarding friction) vs Historic JOA bulk pull (no auth, our-side ingest, freshness lag). Deliverable: architecture decision (which powers discovery vs refresh) + onboarding-friction estimate. Depends on WW-78 rate-limit findings. |
| ⚪ Backlog | WW-82 · WING-79 | [SPIKE] Federal apply-path reality (login.gov, DD-214/SF-15, federal résumé) | Sources | Research-only. GTM goal: honest end-to-end path, not just discovery — the pitch's credibility. What a USAJOBS application actually requires: login.gov auth (WW never touches — deep-link out only), required docs (DD-214, SF-15), federal-résumé conventions. Deliverable: apply-prep checklist spec + federal-résumé template scope. Independent. |
| ⚪ Backlog | WW-83 · WING-80 | [SPIKE] SAM.gov opportunities API recon (P3 precursor) | Sources | Research-only. GTM goal: the contractor pipeline for veterans going independent. Endpoints, key tiers (~10 req/day basic), set-aside/NAICS filters, UEI/SAM entity-registration reality for an individual contractor. Deliverable: P3 feasibility + daily-pull + cache design; flags whether individual-contractor bidding is realistic for our users. P3 on-ramp. |
| ⚪ 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). |
| 🔵 In Review | WW-74 · WING-72 | LinkedIn repost tracking — neutral "listing history" context (council-gated minimal build) | Insights | Built on branch wt/WT-55ee/WW-74 (e622315), NOT merged/pushed. 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. Remaining before Done: master merge (multi-session FF-push) + live in-app browser verify of the card. 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¶
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.