WING-136 / WW-143 vs WING-132 — Reconciliation Decision Memo¶
Status: STAGED, NOT PUSHED. Held for Andrew's morning review per council verdict (council
reconciliation, /llm-council sonnet tier, 4/4 seats — D/Grok, B/Codex, A/Claude, C/Gemini —
unanimous on substance). Commits a17ff0c (reconciliation) + 76683e5 (post-rebase fixes) sit
on claude/vibrant-feistel-712f4b, rebased cleanly onto origin/master (which includes
WING-132's 63ab970). Full backend suite: 4058/4059 (the 1 failure is pre-existing/unrelated —
see §5).
1. What happened¶
Two fleet sessions independently wired SimplifyJobsSource (landed via WW-104/WING-109,
0400613) into the job queue on the same night, because WW-104's own commit deferred that work
to a follow-up ticket it called "WW-143" without checking the number was already used by an
unrelated, already-Done onboarding epic.
- WING-132 (WT-5988, degree/internship epic) filed its own ticket, built
ResyncFromJobSourcesAsync, and landed it onorigin/master(63ab970) before this session could push. - This session (WT-87df) had, earlier the same night, run a full feasibility spike +
/llm-council(opus tier, 4/4 unanimous PIVOT) + direct conversation with Andrew establishing a doctrine: recommended/PUSH jobs must be propose-only, never auto-saved. Andrew's own words: "we keep both because the linkedin saved jobs are jobs they already knew they wanted" — LinkedIn/PULL = high-intent user choice; Recommended/PUSH = machine-surfaced, unproven, requires an explicit user action to promote. This session builtGetRecommendedAsync/PromoteRecommendationAsync/DismissRecommendationAsyncon that doctrine, gated by a 3-seat council review, and committed locally (d27b983) — but WING-132 had already landed.
2. The conflict¶
- Behavioral: WING-132's
ResyncFromJobSourcesAsyncdidexisting.AddRange(added); store.SaveAsync(...)— SimplifyJobs candidates landed directly in the Saved queue behind a feature flag, with no confirm step. This is a genuine violation of the propose-never-auto-save doctrine (unanimous council finding — see §3). WING-132's own commit doc records a degraded review that night (Claude chair unavailable... Gemini review attempts unavailable... local Ollama produced no usable finding output), which weakens confidence this was a deliberate product tradeoff rather than implementation-pressure shortcut. - Structural: both commits added a
JobPosting.Sourceproperty with incompatible types — WING-132: a closedJobSourceenum (Unknown/LinkedIn/SimplifyJobs); this session: a plainstring. Same field name, different shape — a straight rebase would not have compiled.
3. Council verdict (verbatim ranking D > B > A > C, unanimous on substance)¶
Full report:
llm-council/reports/2026-07-24_010332_architecture-reconciliation-two-independent-work.md
- Yes, genuine doctrine violation. The split is intent-provenance, not source-name or epic scope: LinkedIn/saved = user already chose it (PULL); Simplify/Recommended = machine-surfaced (PUSH). A feature flag changes blast radius, not semantics — no council member accepted "internships are different" as a legitimate carve-out for auto-save.
- Supersede (option a), don't coexist. Two writers into the same store with opposite
semantics (auto-Saved vs. Proposed-gated) guarantees races and re-breaks the doctrine under
the next epic. Keep WING-132's useful pieces (flag pattern, dedup test fixtures, fetch
structure); cut the direct-to-Saved write; fold it into the same
dedup-then-Proposed-then-gate-recheck mechanism as
GetRecommendedAsync. - Merge the
Sourcetype — neither the plain string nor the rigid closed enum alone. Adopted: typed known-source identity (JobSourceenum, WING-132's shape) plus an open extension hatch (JobSource.Other+JobPosting.SourceNamefree-text) so a futureIJobSourceadapter never requires an enum edit just to be recorded — mirrors the existingAtsInfo.Kind(enum)+TenantHost(string) pattern already used elsewhere in this exact model.OriginState(Saved/Proposed/Dismissed) stays exactly as built — orthogonal, doctrine-critical, non-negotiable. Source answers where from; OriginState answers what commitment. Conflating those was WING-132's root bug. - Hold tonight; prep only. Governor RED + this is Andrew's personal doctrine call made hours earlier in direct conversation — not something to unilaterally reverse on a teammate's shipped commit while he sleeps. Do the full reconciliation, run the suite, write this memo, leave staged. Don't push.
4. What changed, concretely¶
JobPosting.cs: kept ONESourceproperty, typedJobSource(was two conflicting declarations post-merge — a duplicate-member compile error git's line-based merge didn't even flag as a conflict). AddedSourceName(string, the escape hatch).OriginState(Saved/Proposed/Dismissed) unchanged from this session's original design.JobSource.cs: addedOthercase for the extension hatch.JobQueueService.cs:- Constructor: kept both
jobSourcesandfeatureFlagsoptional params (union of both commits' additions); standardized the backing field name onsources. - Added
ResolveJobSource(string, out string)— maps anIJobSource.Nameto the typed enum,JobSource.Other+ the original name for anything unrecognized. ResyncFromJobSourcesAsyncrewritten: same flag gate, same per-source fetch/failure isolation, same cancellation propagation — but now dedups via the SAMEDedupKeysFor/BuildDedupKeyshelpersGetRecommendedAsyncuses (canonical ATS apply URL, LinkedInUrl fallback, fuzzy company+title+location — a superset of WING-132's original ApplyUrl-only check), marks survivorsOriginState.Proposed(notSaved), and re-checks against a fresh reload insidestoreGatebefore persisting (the same TOCTOU guardGetRecommendedAsynchas). Returns the count of newly-Proposed jobs. The flag now gates the propose path, not an auto-save path.GetRecommendedAsync's candidate mapping updated to useResolveJobSourceinstead of a raw string assignment (compile fix for the type merge).- Test fixes: WING-132's own
JobSourceQueueTests.csneeded NO test-body changes — all 4 of its tests only assert the raw store contents + return count, neither of which changed meaning. This session'sJobQueueServiceTests.cshad 2 stale string-literalSourceassertions updated to the enum. One production bug found by the full suite:ResyncFromJobSourcesAsyncinitially calledfit.AnalyzeDeterministic, NRE'ing againstJobSourceQueueTests' unconfiguredIJobFitAnalyzermock (their original code never called it). Removed —GetQueueAsync's existing backfill already computes Fit for any row, Proposed included, lazily on next read; no behavior lost, no new dependency at this call site.
5. Known pre-existing failure (NOT from this reconciliation)¶
DataPortabilityServiceTests.SensitiveDomains_AreIntakeAndGaps fails on plain origin/master
(verified via git show origin/master:... on both files before touching anything) — WW-98
(1d875ef, a different epic, different session) added two new IsSensitive = true backup
domains (education/academic records, skills) without updating this test's expected list. Flagged
as a separate task, not fixed here — out of scope for this reconciliation.
6. Recommendation for Andrew¶
Merge claude/vibrant-feistel-712f4b (commits a17ff0c + 76683e5, on top of current
origin/master) once reviewed. It supersedes WING-132's auto-add behavior with the
propose-then-promote flow while preserving everything else WING-132 built (feature flag, fetch
structure, its own tests — unmodified and still passing). WT-5988 (WING-132's author) has already
agreed this is the right call and is holding further pushes to JobPosting/JobQueueService
until this lands.