Remove merge=union from the issues ledger, per its own #133 - #1444
Conversation
Ledger #133 already recorded union as the wrong driver for this file — "two sides each bumping the marker produce two `next-id` lines, corrupting the file silently where a conflict would fail loudly" — but `.gitattributes` still set it and `check-outstanding-issues.mjs` *required* it, so the repo's own tested conclusion was contradicted by its own config. PR #1430 confirmed the cost at scale: four merges in one session, each reporting success while duplicating the entire open-items table (`#59 appears 2 times (lines 101, 166)` and so on for every row), each needing a manual rebuild from origin/main. Union also makes `git merge-tree` report a clean tree, so the pre-merge conflict check cannot warn. Unlike docs/branch-review-ledger.md — which keeps its custom `merge=ledger` driver, union plus exact-row dedupe — this file allocates IDs by read-modify-write. Concurrent appends therefore need manual renumbering whatever the driver does (hit twice on 2026-07-30: #125 and #135 collisions), so union bought nothing and only hid the overlap. Default 3-way merge conflicts honestly instead. The gate's attribute check is inverted rather than deleted, so a driver reappearing here is a red gate. AGENTS.md, docs/process-hardening.md, .claude/skills/issues/SKILL.md and docs/scripts-index.md are updated to match, and #133's driver half is marked resolved with its still-open half (fixed-width padding making every row edit one hunk) left intact. Verified: reintroducing `docs/outstanding-issues.md merge=union` fails the gate with "must have NO merge driver (found merge=union)", and removing it passes with "no merge driver" — the gate bites, not just passes. verify:cheap exit 0: Test Files 434 passed (434), Tests 4563 passed | 4 skipped (4567). Whole-tree prettier clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XrPbbfU9yWuEjEVypCr4ZQ
This pull request has been ignored for the connected project Preview Branches by Supabase. |
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in:17 minutes Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:9065f468ad
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Codex review is right: the new check accepted `unset` alongside `unspecified`, and those are not the same state. Per gitattributes, an Unspecified `merge` attribute is the documented default 3-way text merge — the contract this PR establishes — while Unset (`-merge`) takes the current branch's version and declares the merge conflicted, so every two-sided edit becomes a manual resolution. A global or future attributes file could therefore have violated the contract with the gate still printing "no merge driver". Reproduced before fixing: appending `docs/outstanding-issues.md -merge` made `git check-attr` report `merge: unset` and the guard passed. It now fails with a message naming the Unset/Unspecified distinction and telling the reader to drop the negated attribute rather than add one. The acceptance decision moves into an exported `mergeAttributeProblem` so the distinction is unit-tested rather than only reasoned about, with four cases in tests/repo-hygiene.test.ts: `unspecified` accepted; `unset`, `union`/`ledger`, and an empty reading all rejected. The empty case matters because an unparsed check-attr output would otherwise make the whole check vacuous. Verified: with `-merge` present the gate fails on the new message; with it removed it passes "no merge driver". repo-hygiene 47 passed (47). verify:cheap exit 0 — Test Files 435 passed (435), Tests 4508 passed | 4 skipped (4512). Whole-tree prettier clean. (Test total differs from this branch's earlier run because it now carries main's #1423/#1427/#1438; this commit adds four.) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XrPbbfU9yWuEjEVypCr4ZQ
Uh oh!
There was an error while loading. Please reload this page.
* issues: close#140 as a duplicate of #133, resolved by #1444#140 was opened mid-session for the union-driver damage before I noticed #133 had already recorded the same finding, earlier and with the same conclusion. Two open rows described one condition, and PR #1444 has since removed that condition: `merge=union` is gone from `.gitattributes`, `check:outstanding-issues` now requires an unspecified `merge` attribute, and regression tests cover `union`, `-merge` and an unparsed reading. Moved to the archive table rather than deleted, pointing readers at #133 — whose still-open half is the real conflict-frequency cause: fixed-width column padding makes any one-row edit re-pad every row, so git sees the whole table as one hunk. The surviving evidence (four merges on PR #1430 each reporting success while duplicating the entire open-items table) lives there too. Verified: check:outstanding-issues 138 rows, 66 open / 72 archived (was 67/71 — moved, not copied), unique ids, next-id=141, no merge driver; docs:check-links 1361 references resolve; prettier clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XrPbbfU9yWuEjEVypCr4ZQ * docs: record duplicate issue review --------- Co-authored-by: Claude <noreply@anthropic.com>
The merge of origin into this branch hit exactly the damage #133/#140 describe: merge=union concatenated both sides of the ledger rather than merging it. Two collisions, both repaired without dropping either side's rows: - Another agent had already allocated #141-#144 on main for different items while this branch used #141-#143. The incoming rows renumber, per the ledger rule, so the capture becomes #145 (adopt a consolidated answer-home notice block), #146 (answer mode ships no verify-before-use caveat) and #147 (verify:pr-local exits 0 when its build step refuses to run). Their cross-references were updated to match, and the two duplicated next-id markers collapse to one at 148. - #140 appeared in both tables: it was closed on main as a duplicate of #133 (PR #1444) while this branch still carried it open. The resolution is honoured — the stale open row goes, the archive row stays. check:outstanding-issues: 145 rows (72 open, 73 archived), unique ids, next-id=148 above the highest, no merge driver. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NPyFcMfn1jMmphr6AqiWBg
Resolves the one conflicted file, docs/outstanding-issues.md. With merge=union removed by #1444 the ledger now conflicts honestly instead of silently doubling, so this is a real resolution rather than a repair. main had allocated #145 for "a branch sync during in-flight CI cancels a near-complete run" while this branch used #145-#147 for the answer-home notice capture. Per the ledger's own rule the incoming rows renumber: main's #141-#145 are kept verbatim and the capture becomes #146 (adopt a consolidated answer-home notice block), #147 (answer mode ships no verify-before-use caveat) and #148 (verify:pr-local exits 0 when its build step refuses to run), with their cross-references updated to match and the marker lifted to 149. No row was dropped from either side — main's #141-#145 each appear exactly once, verified against origin/main. check:outstanding-issues: 146 rows (73 open, 73 archived), unique ids, next-id=149 above the highest, no merge driver. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NPyFcMfn1jMmphr6AqiWBg
…ge gaps (#1496) * issues: close#130 by decision; capture the stale-install and reviewer gaps #130 is archived because the owner decided not to pursue it, NOT because the acceptance was run. The row says so in its first sentence, because a "CLOSED" line is exactly the thing a future reader could mistake for evidence: docs/phone-chrome-physical-acceptance.md on main is still the blank template, and nothing in CI covers what it covers — headless Chromium was explicitly stated as unable to certify Safari chrome-minimisation or cold-launch PWA paint. Residual risk is unchanged and unmeasured, and the row says to open a fresh one if phone-chrome misbehaves on a real device. Two risks that surfaced during today's work were never written down, and neither had an existing row (checked before adding, after creating a duplicate earlier today by not checking): #149 — the SessionStart hook reported "node_modules matches the lockfile" while brace-expansion was installed at 1.1.16 against a lockfile pinning 1.1.18, the CVE-2026-14257 patch. Every local gate run in that window was weaker than reported, including verify:cheap runs called green during #1430/#1444. It surfaced only because installed-lock-parity asserts the patched behaviour directly, and its failure was first misread as a pre-existing repo defect because it also failed on clean main in the same stale container. Notes that check:installed-lock-parity is only in verify:ui, so the broad local gate cannot catch its own stale foundation. #150 — CodeRabbit reviewed none of #1404, #1430, #1444, #1445 or #1479; every comment was a spending-cap rate-limit notice that renders as ordinary bot activity. Codex was the sole substantive reviewer and found three real defects that survived local gates and self-review. Fast checks run against this tree: check:runtime, check:installed-lock-parity (now passing, confirming the drift is resolved), check:outstanding-issues, check:branch-review-ledger and check:migration-role all PASS. check:supabase-project and check:production-readiness fail only on absent provider credentials, which AGENTS.md states is expected in demo mode. Verified: check:outstanding-issues 148 rows (57 open, 91 archived), unique ids, next-id=151, no merge driver, no ids deleted from base; docs:check-links clean; whole-tree prettier clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XrPbbfU9yWuEjEVypCr4ZQ * issues: correct #149 — parity check scope, not its placement Codex review is right on both counts and this rewrites the row rather than patching around it. The earlier text said check:installed-lock-parity lives only in verify:ui and proposed adding it to verify:cheap; verify:cheap:internal already runs it, so that follow-up was a no-op — and it ran green throughout the stale window. The real gap is scope. criticalInstalledPackages lists seven top-level names (next, react, react-dom, eslint, playwright, typescript, vitest), so a stale transitive dependency is invisible. Proved by faking node_modules/brace-expansion back to 1.1.16 against the 1.1.18 lockfile pin: check:installed-lock-parity exits 0. That also explains why both the hook and the gate reported parity while the tree was stale — neither was looking at the package that drifted. Next step is now to broaden the check (full tree, or a lockfile-hash install stamp) rather than move it, and the row warns against reading a green parity run as proof the install is current. Verified: check:outstanding-issues 148 rows, unique ids, next-id=151, no ids deleted from base; docs:check-links 1413 references; prettier clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XrPbbfU9yWuEjEVypCr4ZQ --------- Co-authored-by: Claude <noreply@anthropic.com>
* issues: capture the unreadable-CI token, at-risk worktree work, and the unpushed hook fix Three findings from the 2026-07-30 organisation session that were recorded nowhere durable: - #149 the session GitHub PAT lacks Checks: Read, so no agent can confirm a PR is green. The endpoint that does work returns an empty result rather than an error, so it reads like an absence of checks rather than an absence of permission. - #150 four worktrees on already-merged branches hold uncommitted work that exists in no branch and no PR, the largest being +395/-200 across 19 files including CI config. - #151 the pre-commit fail-open for #143 lives only on a never-pushed local branch, which is also 17 behind main and conflicts on the file whose count sentence main's new docs:update generator now owns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(ledger): record the session-followup capture review for PR #1490 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(ledger): record #143/#151/#149 reconciliation for PR #1490 Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com> * docs(ledger): supersede PR #1490 reconciliation after remote sync Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com> * issues: record the worktree snapshots and redirect #151 to PR #1494#150 — the four at-risk worktrees were snapshotted onto their own already-merged branches (748ef018f, 5dbd9f965, b7eae51a4, d949859c3), so the work survives a worktree reclaim. All four are clean now. None is pushed or reviewed; the next action is per-snapshot promote-or-reset. #151 — the never-pushed branch is superseded rather than salvageable: its script and hook reached main by other routes, so the fail-open guard was applied to main's committed hook in PR #1494 instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: remove credential metadata and correct audit dates * docs: consolidate session follow-up findings * docs: record consolidated follow-up review * issues: record that #101 hydration shipped PR #1463 merged as dba7356, so #86's "Next X3 unit — rag-hydration.ts" is now stale. The row records the extraction as shipped and keeps the corrected boundary: hydration re-homed only two of prepareCoverageGateResults's five rag.ts-only dependencies, so it did not unblock that function — exactly as the Codex review on PR #1461 predicted. This row was deliberately dropped from #1463 itself (commit 6290d02) after docs/outstanding-issues.md conflicted on five consecutive main syncs. Recording it separately here is the same pattern used for #1454 via #1461. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGEBHp4Seoh1jK1vGTNtYS * docs(ledger): record the landed X3 hydration review Appended with npm run ledger:append (never hand-written), keyed to the squash commit dba7356 so ledger:lookup can resolve it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGEBHp4Seoh1jK1vGTNtYS * docs: fix the #101 mislabel and key the ledger row to a resolvable ref Both defects were raised by Codex on PR #1495 and both are real; verified against the files before accepting. 1. #101 is NOT this extraction. docs/outstanding-issues.md:138 shows #101 is "Canary-gated retrieval parallelisation candidates" (P3, rec) — a separate, still-open recommendation gated on a live canary pair. Calling the hydration extraction "#101" marked that unrelated work as shipped and could have caused the live-evaluation work to be skipped. The label came from the original task brief and was propagated without checking it against the ledger. Both the #86 row and the X3 work-order entry now identify the change as the X3 hydration unit (PR #1463) instead. #101's own row is untouched and still open. 2. The ledger row did not resolve. `npm run ledger:lookup -- dba7356` returned NOT REVIEWED, because the ref cell held only the slash-form branch token and that branch no longer resolves locally, so the throttling record could not prevent a repeat review. Appended a superseding record keyed to the landed SHA; the same lookup now returns ALREADY REVIEWED. The original row is retained, per the ledger's append-only rule. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGEBHp4Seoh1jK1vGTNtYS * docs: record consolidated PR reviews * docs: record ingestion recovery review * docs(visual): document the platform-scoped baseline layout and how to seed it `playwright.visual.config.ts` records snapshots under `__screenshots__/{platform}/`, so a baseline taken on Windows lands in `win32/` and is never consulted by the `ubuntu-24.04` CI job, which reads `linux/`. Nothing said so, and committing `win32/` images looks like protection while providing none. Records the constraint, names the CI artifact as the supported recorder for `linux/` baselines, and notes that comparison stays advisory until the jobs come off `continue-on-error`. Also creates the tracked directory `.gitignore` already claims exists, which sets `ui_changed=true` (`scripts/ci-change-scope.mjs`) so the visual job can run and produce that first artifact. No baselines are added here — they cannot be produced on this platform. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: correct visual baseline adoption steps * docs: record visual baseline guidance review * fix(ui): repair mockup accent token references * docs: record token-reference repair review * docs: archive advisory UI scoping task * docs: record advisory UI closure review * issues: archive #151 after #1494 and mark #143 fully resolved PR #1494 landed the fail-open guard on main, so close the open salvage row and update the #143 archive from PARTIAL to resolved across #1442 and #1494. Also carries the merge of origin/main that cleared the GitHub DIRTY mergeability state. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com> * docs(ledger): record PR #1490 main-sync and #151 closeout Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com> * docs(ledger): record #1496 id-collision renumber for PR #1490 Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com> * issues: record the withdrawn live-region finding as #151 so it is not re-filed Archive-only row. There is no defect and no work to do — the row exists purely as a guard rail against repeating a misreading that already happened once. search-results-header-band.tsx sets aria-live={faulted ? "off" : "polite"} on its count/status span, which reads like a silenced failure announcement. It is not: the band mounts a separate fault panel with role="alert" carrying the failure title, body and Retry, and the mute is deliberate so the two do not both speak. The reasoning is in a comment directly above the attribute, and tests/search-results-header-band.dom.test.tsx pins it with singular role queries that throw on duplicates. During session 2026-07-30 (PR #1481) this was filed as a real P2 defect on the strength of the attribute alone, and the proposed fix — escalating the count span to role="alert"/aria-live="assertive" — would have produced a duplicate announcement and a red test, making it worse than no change. Codex caught it. An earlier withdrawal row was then lost to the squash that merged #1481, which is the row-deletion shape #148 now guards against. Also records that the mockup's escalation is correct in the mockup and must not be ported: search-refine-adaptive-mockups.tsx has no fault panel, so there the count span is the only announcement channel. #148 needed no work — the merge-base deletion check landed on main independently, and its output now reports the base it compared against. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JdPa3mHCX5ZQZZvU5GHU3r * docs(rag): record refuted lexical probe collapse (#98) * issues: capture the residual id-allocation hazard as #151#133 is resolved: #1444 removed merge=union and #1479 excluded the ledger from Prettier, which together fixed conflict frequency. Neither changes id allocation, which is still read-modify-write against the next-id marker, so concurrent branches still claim the same number. Measured on PR #1451: one row was renumbered #135 -> #141 -> #145 -> #147 -> #149 across four sync cycles. The sharper finding is that GitHub's Update-branch button resolved one such collision into duplicate #141 rows with the marker left below main's highest id — git reported success and only check:outstanding-issues caught it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(issues): attribute the mobile CLS breach — a 128px reserve round trip #147 asked which elements shift. Driving Chromium against the same offline production build with a PerformanceObserver on layout-shift (Lighthouse mobile emulation, reading entry.sources[].node) gives one dominant cause on all four breaching routes: the entire main content region moves down 128px and straight back up 128px within 15-60ms. Both moves score, so it is pure cost with zero net movement — 100% of /documents/search's 0.220 and about 75% of /dsm's. The shifting element is the max-sm:pt-[var(--phone-overlay-chrome-h)] wrapper around <main>. A MutationObserver timeline on the root style attribute pins the mechanism rather than inferring it: the property goes CSS seed -> 200px -> 72px, and the 200px is written when the header stack ALREADY measures 72px (t=1552ms reserve=200px stack=72, corrected at t=1612ms). usePhoneOverlayChromeReserve reads stack.offsetHeight while the stack is transiently tall, publishes a value that is stale by the time it lands, and its ResizeObserver then corrects it. The CSS seed at globals.css:375 is correct for the settled stack, which corrects the mechanism recorded on the now-archived #130 — that framed the defect as the seed under-reserving by 0-8px. Measured, the driver is a 128px transient over-reserve written by the hook, not the seed. / is the control: it never writes the property and is the one clean route. Variance is stated rather than smoothed: /dsm measured 0.363 and 0.219 across two runs, and this harness has no network throttling so /forms and /therapy-compass run high locally. Only /dsm, /documents/search and / reproduced the live dispatch exactly. Also recorded: attaching a MutationObserver to document.documentElement inside a Playwright addInitScript throws before the document element exists, silently killing the CLS observer and reporting a uniform CLS=0.000 — a false clean bill that voided one run of this harness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF * docs(ledger): record the #151 capture review for PR #1506 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(review): clarify snapshot branch state * docs(ledger): record PR #1490 main sync after snapshot wording Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com> * docs: archive rendered style contract task * docs: record style contract closure review * docs: record synced style contract review * docs: record post-121 style closure review * docs: normalize style review ledger after sync * docs: record post-1490 style closure review * docs: record consolidated PR 1490 review * docs: record replacement consolidation review * docs: record reconciled consolidation review * docs: record post-1511 consolidation review * docs: normalize PR 1510 ledger after main sync * docs: record PR 1510 post-sync review * docs: correct false #98 canary evidence and NOTES triage Remove the incorrect probe-collapse canary attribution from #98 and point the unread --med-accent-soft note at #157 without breaking the seven-token TOKENS_MISSING accounting. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com> * docs(ledger): record PR #1510 evidence-correction review Supersede the prior approve-with-no-findings row after correcting the false #98 canary attribution and NOTES triage drift. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com> * docs: keep concurrency note inside issue table * docs: record post-1513 consolidation review * docs: address CodeRabbit notes on PR #1510 Fix the computed-value-time wording in design-sync notes, give #33 a unique recommended-queue order, and drop the duplicated #98 Done block. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com> * docs(ledger): record PR #1510 CodeRabbit fix review Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Summary
merge=unionfromdocs/outstanding-issues.mdand inverts the gate's attribute check so the driver reappearing is a red gate rather than a requirement.#133already stated it: union "concatenates conflicting hunks, so two sides each bumping the marker produce twonext-idlines, corrupting the file silently where a conflict would fail loudly." But.gitattributes:19still set it (PR process: anti-conflict operating procedure and silent-CI signal #1416) andcheck-outstanding-issues.mjs:423required it — so a tested conclusion was contradicted by the config it was about.AGENTS.md,docs/process-hardening.md,.claude/skills/issues/SKILL.md,docs/scripts-index.md.#133's driver half resolved, leaving its still-open half — fixed-width column padding, which makes any one-row edit re-pad every row so git sees the whole table as a single hunk — explicitly intact.Why union was the wrong driver here
docs/branch-review-ledger.mdkeeps its custommerge=ledgerdriver (union plus exact-row dedupe); this change does not touch it. The difference is ID allocation: that ledger is append-only with self-describing rows, while this one allocates IDs from aissues:next-idmarker by read-modify-write. So concurrent appends need manual renumbering whatever the driver does — hit twice on 2026-07-30, the#125and#135collisions. Union bought nothing and only hid the overlap.Measured cost on PR #1430: four merges in one session, each reporting success while duplicating the entire open-items table —
— each needing a manual rebuild from
origin/main.check:outstanding-issuescaught the result every time, which is the gate working; but the driver converted a resolvable conflict into a guaranteed manual repair. Union also makesgit merge-tree --write-treereport a clean tree, so the pre-merge conflict check inpr-mergeabilitycannot warn about it either.Default 3-way merge conflicts honestly instead: clean when edits do not overlap, conflict markers when they do, and never a silently doubled file.
Verification
docs/outstanding-issues.md merge=unionfails withmust have NO merge driver (found merge=union) — remove it from .gitattributes so overlapping edits conflict loudly instead of silently concatenating both sides (ledger #133). Removing it passes withunique ids, next-id=140 above the highest, no merge driver.git check-attr merge docs/outstanding-issues.md→merge: unspecified.npm run verify:cheap— exit 0,Test Files 434 passed (434),Tests 4563 passed | 4 skipped (4567), includingcheck:outstanding-issues,check:gate-manifest,check:ci-scope,docs:check-scriptsanddocs:check-links.npx prettier --check .—All matched files use Prettier code style!Risk and rollout
src/changes, no runtime behaviour.merge=unionand the gate condition together, so the two cannot drift apart.Clinical Governance Preflight
Not applicable — no clinical-risk path is touched.
scripts/pr-policy.mjsclassifies nosrc/lib/**retrieval/answer/privacy surface, nosupabase/**, nosrc/app/api/**, and no RAG-ranking protected surface, so noRAG impact:declaration is required.Notes
scripts/merge-branch-review-ledger.mjs, not stock union. I considered writing one here and did not: it means new three-way merge logic inside a gate that every PR depends on, and the evidence says the frequency problem is the fixed-width padding (#133), not the merge strategy. Fixing the padding would make one-row edits one-line diffs and remove most of the overlap at source.Generated by Claude Code