16 KiB
Open contradictions
Opened 2026-07-29 by phase 0, against commit
c99e3f4. Phase 0 only finds these. It resolves none of them. Last updated 2026-07-30 by phase 2, against commitd3ec723: C-2, C-6, C-7, C-8 and C-9 resolved, C-3's contract-layer half resolved, C-15 and C-16 added.
Each row is claim A vs claim B with a location for both, and the phase that owns the resolution.
When a phase resolves one, it moves the row to Resolved at the bottom with the decision, and — per
the chain's non-negotiable #5 — records the decision in docs/status/decisions.md.
Three of the five contradictions the plan seeded turned out to be narrower than stated. Those are written up as they actually are, with the evidence, in § Corrections to the seeded list.
Open
| # | Claim A | Claim B | Owner | Status |
|---|---|---|---|---|
| C-1 | Set the crypto keys with dotnet user-secrets — manual-testing-plan.md:22, :233 |
user-secrets is not used; <UserSecretsId> was removed so the store is not read — CLAUDE.md:90, DEPLOY.md:20, server/CLAUDE.md:70, and Baya.Web.Api.csproj (no UserSecretsId element) |
3 | open |
| C-3 | The server listens on https://localhost:5002 — CLAUDE.md:62, :135, api-conventions.md:6, RUNBOOK.md:7, :84, :112, + 12 more |
It listens on http://localhost:5002 — launchSettings.json:25 ("applicationUrl": "http://localhost:5002"), and client/.env.development:20 (NEXT_PUBLIC_API_URL = http://localhost:5002) |
2 + 3 | partly resolved by phases 1 + 2 — the three rule-file occurrences now say http; phase 2's api-contract.md states http and banner-marks api-conventions.md as history. Phase 2 also found a new occurrence the survey missed: client/.env.sample:20, the file a fresh clone copies. RUNBOOK.md + the remaining occurrences are phase 3 |
| C-4 | RUNBOOK's dotnet dev-certs https --trust step is required, because the browser would otherwise reject the API — RUNBOOK.md:26 |
The API is plain HTTP locally (C-3's B side), so there is no certificate to trust | 3 | open — likely a dead step |
| C-5 | Bring-up starts a local SQL Server in Docker on localhost:1433 — RUNBOOK.md:18, :35–48, :63 |
The committed dev config points at a remote SQL Server — appsettings.Development.json (Server=87.107.152.16,1433), and manual-testing-plan.md:20 calls the remote one "currently" the target |
3 | open — the two bring-up paths give different worlds: the remote DB is already seeded, a fresh local one is not |
| C-10 | 18 hardening items are open — issues.md, 18 of 18 checkboxes unticked, last touched 2026-07-17 | Fourteen UI phases, two manual-testing iterations, a Telegram integration and a deploy commit ran afterwards (12ce7fa → 96b57eb, 07-20 → 07-28) without ticking any box |
4 | open — Phase 4 must re-verify each item against code, not trust the checkbox |
| C-12 | The skill is current | It is exactly one iteration behind the code. SKILL.md's last commit is baa3cc6 ("manual improvement 1"); client/CLAUDE.md's is e6a8f93 ("manual improvement 2"), which changed 44 files, +3419/−2449 under client/src. Anything iteration 2 changed is absent from the skill |
7 | partly resolved by phase 1 — the design-language half is corrected (see R-2); phase 7 still owns the skill's own workflow/procedure content |
| C-13 | dev/ is "the plan for building Balinyaar", written in the imperative — dev/README.md:3 |
It is a record of work already done. dev/phases/ last touched 2026-06-28; the code it describes shipped weeks ago |
6 | open — resolved by the archive banner, not by editing 199 files |
| C-14 | temp/swagger.json is a stale committed duplicate — _plan/README.md diagnosis table |
It is not committed at all — .gitignore:1 ignores temp, and git ls-files temp/ is empty. It is local clutter, not repo content |
0 | see § Corrections |
| C-15 | ui-phase-11-report.md:235 records "REQ-061…064 appended" to the frontend request ledger |
REQ-061 was never appended. The ledger goes 060 → 062; ten live client files cite REQ-061 for the admin user directory (admin_users/search, admin_users/lookup), which are 2 of the 24 phantom endpoints |
4 | found by phase 2 — phase 4 must file it, not assume it is tracked |
| C-16 | Five REQs' filed status — REQ-050, REQ-063, REQ-066, REQ-067 (open, as filed) and the reasons admin/constants.ts gives for the admin mock |
All five are wrong or narrower than filed, checked against the live swagger rather than against another document. variantLabel already ships; tickets/close+reopen already ship; search/nurses and nurses/{id}/profile are already anonymous; REQ-029/030 already shipped |
4 | found by phase 2 — the table is in phase-2's handoff § B |
OpenAPI drift
The fresh snapshot is at docs/integration/openapi/swagger.v1.json.
See its README for provenance. Measured against the 2026-07-13 snapshot
at dev/contracts/openapi/swagger.v1.json:
| 2026-07-13 | 2026-07-29 | |
|---|---|---|
| paths | 177 | 178 |
| operations | 185 | 186 |
| component schemas | 339 | 339 |
Added (1)
POST /api/v1/webhooks/payouts/{provider}
Removed (0) · Methods changed on an existing path (0) · Schemas added/removed (0)
Schema definitions changed (1)
GeneratePayoutBatchCommand— gainedsystemInitiated: boolean(C-7, resolved)
This was Phase 2's worklist for the machine contract, and it was two lines long. The 16-day gap between the snapshot and the code produced almost no wire-level drift. Phase 2 therefore spent its budget on the hand-written contract docs instead. See § Corrections C-14b.
What phase 2 found once it looked
Re-scoping was right, but the conclusion "the hand-written docs are where the drift is" turned out to be only half true, and the more useful axis was a third one nobody had checked:
| Axis | Result |
|---|---|
| Old contract docs → live swagger, at route level | Clean. Every route named across the 17 files exists in the live API (the two apparent misses are prose counter-examples: "X, not Y") |
| Old contract docs → live swagger, at convention level | Drifted. Body casing ("typically snake_case" vs. proven camelCase), the server's local URL scheme, the envelope's field count (5 vs. 6 — code was added), and the enum vocabularies |
| Client's real API clients → live swagger | 24 phantom routes — calls to endpoints the server does not expose, 2 of them reachable today. This axis had never been checked and is where the real gap was |
The third row is the phase's main finding, and it is in phase-2's handoff § A.
Corrections to the seeded list
The plan seeded five contradictions. Two hold as written; three are narrower than stated. Recording this here so later phases do not budget for work that is already done.
Holds — user-secrets. The plan says 18 files still instruct you to use it. 32 mentions across 18
files is right, but the split matters: 6 files document the removal correctly (root CLAUDE.md,
DEPLOY.md, server/CLAUDE.md, server/CONVENTIONS.md, and RUNBOOK.md twice — including a
troubleshooting row that names the error you now get). 12 are historical — refinement prompts,
handoffs and reports written before the removal, all headed for archive/. Only one live document
still gives the wrong instruction: manual-testing-plan.md
(last commit bd06ef0, the day before the removal in 5885280), plus the archived
telegram-otp-bot/INTEGRATION-PROMPT.md.
→ The plan's statement that "both manual-testing-plan.md and RUNBOOK.md are stale on secrets
handling" is half wrong. RUNBOOK.md is current. Phase 3 fixes one file, not two.
Holds — hardening. C-10 above. 18 of 18 unticked, four later chains, nothing reconciled.
Narrower — contract drift (C-14b). "dev/contracts/ frozen 07-13 vs server/src through 07-28"
is true by date but nearly empty in substance at the OpenAPI layer: 1 endpoint, 1 schema. The drift
that matters, if any, is in the 17 hand-written domain markdown files. Phase 2 should re-scope
accordingly.
Narrower — the design-language skill. The plan says the skill "predates that overhaul" (mobile-scoped
shell, bottom nav, react-hook-form, new icon set). It does not: SKILL.md was rewritten at baa3cc6
during iteration 1 and already covers AppFrame (4×), BottomBar, APP_FRAME_MAX_WIDTH (2×) and the
Lucide-only icon registry with @mui/icons-material removed. react-hook-form is absent from the skill,
but it is an engineering rule that client/CLAUDE.md §Forms owns — not a design-language gap. The real
gap is C-12: the skill stopped at iteration 1 while the code went on to iteration 2.
Wrong — temp/swagger.json. C-14 above. It is .gitignored and untracked, so it was never part of
the repo's documentation surface. Phase 0's brief says to delete it; that deletes a local file only.
Resolved
Each row records the decision. These decisions still need folding into docs/status/decisions.md
when phase 4 creates it — that file does not exist yet, so this table is their only home.
| # | Was | Decision | By |
|---|---|---|---|
| C-11 | The frontend-designer skill and client/CLAUDE.md both claimed the design language, with no stated precedence |
Precedence is now stated in both directions. The skill is the design contract (brand, tone, logo construction, the visual decisions, and the workflow for turning a design into a screen); docs/rules/client/ is the engineering contract and wins on every overlap — tokens, typography, the component library, shells, icons. SKILL.md's header carries the precedence statement plus a table pointing at the four files it defers to, and the overlapping detail was removed from the skill rather than duplicated. client/CLAUDE.md no longer restates design content at all. |
phase 1 |
| C-2 | The placeholder SET_VIA_USER_SECRETS_OR_ENV names a store that was removed, so its name instructs a reader to use a removed mechanism |
The name stays; the mechanism is documented authoritatively elsewhere. The string is a load-bearing sentinel in seven live files — appsettings.json (×6), StartupSecretsGuard.PlaceholderMarkers, .githooks/pre-commit, .githooks/README.md, Baya.Test.Api/StartupSecretsGuardTests.cs (×2), docs/rules/shared/git-and-gates.md, docs/rules/server/structure.md. Renaming it is a server-code + git-hook + test change needing dotnet build/dotnet test to prove the gate still fires — outside a documentation phase's scope. config-matrix.md is now the single authoritative statement that config lives in appsettings files and environment variables and user-secrets is not read, and DEPLOY.md says the same. The rename is filed for phase 4 with that exact seven-file worklist. |
phase 2 |
| C-6 | POST /api/v1/webhooks/payouts/{provider} was absent from the frozen contract |
It exists and is now documented — the payout transferor's reconciliation callback, anonymous, webhook rate-limit policy, in domains/payouts.md. |
phase 2 |
| C-7 | GeneratePayoutBatchCommand changed shape between the two snapshots, unexplained |
It gained systemInitiated: boolean alongside periodStart/periodEnd. That is the refinement-phase-7 scheduler flag distinguishing a job-generated draft batch from an admin-generated one — which is what keeps the "generation may be automatic, processing is always an explicit admin action" rule auditable. Recorded in domains/payouts.md. |
phase 2 |
| C-8 | Two live contract files for one domain: a headerless 851-byte messaging.md silently amending the 10.6 K messaging-notifications-admin.md |
Merged and split three ways along the client's real domain boundaries — tickets → domains/tickets.md, the notification feed → domains/notifications.md, config/holidays/audit/alerts → domains/admin.md. Every REQ-028 amendment is folded in as current fact, not as a change log. Both old files carry a merged-into banner so neither reads as live; phase 6 archives them. | phase 2 |
| C-9 | The OpenAPI folder claimed documents v1 and v1.1; only v1 was ever committed |
Both documents genuinely are registered (AddSwagger("v1","v1.1") in Program.cs), so /swagger/v1.1/swagger.json is served — and it contains zero paths. ApiVersionDocumentProcessor drops every path whose URL lacks the document's version segment, and all 55 controllers are [ApiVersion("1")] on the route template api/v{version:apiVersion}/…. The old claim was literally true and substantively empty. v1 is the contract; only v1 is worth committing. Resolved by reading the code — no server boot needed, so the UNVERIFIED marker is retired. |
phase 2 |
Corrections landed by phase 1 that were not on the seeded list
Six rule statements were false against the code, not merely duplicated. Each was rewritten against
reality rather than carried over. They are recorded here because a future reader of dev/'s history will
find the old wording and needs to know it was checked.
| R- | The stale claim | Reality | Where it was |
|---|---|---|---|
| R-1 | "Use ColorSchemeScript from @/theme" |
No such export exists. The no-flash boot is CSS-only; theme/index.ts exports only ThemeProvider, getDirection, APP_THEME_* |
client/CLAUDE.md:857 |
| R-2 | A Storage.prototype.setItem intercept writes the theme cookie (3 occurrences) |
There is no such patch. ColorSchemeCookieSync — a useColorScheme() effect in ThemeProvider.tsx — writes it via setClientCookie |
client/CLAUDE.md:698, 711, 741 |
| R-3 | AppImage is part of the component library |
No such component under src/components/ |
SKILL.md:154 |
| R-4 | CONTENT_MAX_WIDTH = 800 |
It is 480, mirroring APP_FRAME_MAX_WIDTH — iteration 1 changed it and the skill was never updated (this is C-12's concrete shape) |
SKILL.md:159 |
| R-5 | AppFrame's header/<main>/footer are flex siblings, so a top bar is position: static |
Iteration 2 pinned both bars position: absolute over a single scrolling <main>, which reserves their height as padding and publishes --bal-chrome-top/-bottom |
SKILL.md:181–186 |
| R-6 | DarkModeButton.tsx is the common namespace's consumer |
Component deleted; ThemeModeSetting (a three-way segmented control in SettingsPanel) replaced it |
client/CLAUDE.md:507 |
Two counts were also wrong and are corrected in the new docs: client/CLAUDE.md:925 said "14 domains are
now REAL" and then listed 15 (verified against services/*/constants.ts: 15 real, 7 mocked), and
:61 described npm run check as "type then lint", omitting lint:copy.