Files
baya-monorepo/archive/clarify-chain/open-contradictions.md
T
2026-08-02 18:48:32 +03:30

144 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Open contradictions
> Opened 2026-07-29 by [phase 0](phase-0-inventory-and-scaffold.md), against commit `c99e3f4`.
> **Phase 0 only finds these. It resolves none of them.**
> Last updated 2026-07-30 by [phase 2](phase-2-integration.md), against commit `d3ec723`:
> **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](#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](../../dev/post-phase/manual-testing-plan.md), [:233](../../dev/post-phase/manual-testing-plan.md) | **`user-secrets` is not used; `<UserSecretsId>` was removed so the store is not read** — [CLAUDE.md:90](../../CLAUDE.md), [DEPLOY.md:20](../../DEPLOY.md), [server/CLAUDE.md:70](../../server/CLAUDE.md), and `Baya.Web.Api.csproj` (no `UserSecretsId` element) | 3 | open |
| C-3 | **The server listens on `https://localhost:5002`** — [CLAUDE.md:62](../../CLAUDE.md), [:135](../../CLAUDE.md), [api-conventions.md:6](../../dev/contracts/conventions/api-conventions.md), [RUNBOOK.md:7](../../dev/post-phase/refinement/RUNBOOK.md), [:84](../../dev/post-phase/refinement/RUNBOOK.md), [:112](../../dev/post-phase/refinement/RUNBOOK.md), + 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](../integration/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](../../dev/post-phase/refinement/RUNBOOK.md) | **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](../../dev/post-phase/refinement/RUNBOOK.md), [:3548](../../dev/post-phase/refinement/RUNBOOK.md), [:63](../../dev/post-phase/refinement/RUNBOOK.md) | **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](../../dev/post-phase/manual-testing-plan.md) 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](../../dev/post-phase/hardening/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](../../dev/README.md) | **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](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](#corrections-to-the-seeded-list) |
| 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](phase-2-integration.md) |
---
## OpenAPI drift
The fresh snapshot is at [`docs/integration/openapi/swagger.v1.json`](../integration/openapi/swagger.v1.json).
See its [README](../integration/openapi/README.md) 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` — gained `systemInitiated: 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](#corrections-to-the-seeded-list) 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](phase-2-integration.md).
---
## 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](../../dev/post-phase/manual-testing-plan.md)
(last commit `bd06ef0`, the day *before* the removal in `5885280`), plus the archived
[telegram-otp-bot/INTEGRATION-PROMPT.md](../../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 `.gitignore`d 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/`](../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](../integration/config-matrix.md#the-placeholders-name) 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](../integration/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](../integration/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](../integration/domains/tickets.md), the notification feed → [domains/notifications.md](../integration/domains/notifications.md), config/holidays/audit/alerts → [domains/admin.md](../integration/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:181186` |
| 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`**.