cleanup phase 7
This commit is contained in:
@@ -0,0 +1,167 @@
|
|||||||
|
---
|
||||||
|
name: backend-feature
|
||||||
|
description: >-
|
||||||
|
Add a feature to the Balinyaar .NET server — a command, a query, or both — end to end: the Application
|
||||||
|
slice, the controller, EF configuration/migration if it touches a table, tests, and the doc updates it
|
||||||
|
triggers. Use when implementing a new endpoint or extending an existing one anywhere under server/.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Balinyaar Backend Feature
|
||||||
|
|
||||||
|
The sequence for shipping one CQRS slice, from the Application layer to a green gate.
|
||||||
|
|
||||||
|
**Precedence.** This skill is the **procedure** — what order to do things in. The **rules** within each
|
||||||
|
step live in `docs/rules/server/` and this skill defers to them; it doesn't restate them.
|
||||||
|
|
||||||
|
| For | Read |
|
||||||
|
|-----|------|
|
||||||
|
| The dispatcher, folder shape, `OperationResult`, the controller skeleton, authorization | [docs/rules/server/cqrs.md](../../../docs/rules/server/cqrs.md) |
|
||||||
|
| Projects, layers, the seam catalogue, startup wiring | [docs/rules/server/structure.md](../../../docs/rules/server/structure.md) |
|
||||||
|
| EF Core, migrations, soft-delete, audit, config-as-rows, state machines, snapshots, uniqueness | [docs/rules/server/persistence.md](../../../docs/rules/server/persistence.md) |
|
||||||
|
| Anything on the money path — ledger, refunds, BNPL, payouts, invoices | [docs/rules/server/money.md](../../../docs/rules/server/money.md) |
|
||||||
|
| Auth, JWE, sessions, field encryption, tenancy | [docs/rules/server/identity.md](../../../docs/rules/server/identity.md) |
|
||||||
|
| C# style, naming, async, testing | [docs/rules/server/conventions.md](../../../docs/rules/server/conventions.md) |
|
||||||
|
| The gate, and what "done" means | [docs/rules/shared/git-and-gates.md](../../../docs/rules/shared/git-and-gates.md) |
|
||||||
|
|
||||||
|
**Stack:** ASP.NET Core (.NET 10), Clean Architecture, CQRS on `martinothamar/Mediator` (a source generator —
|
||||||
|
**not MediatR**; there is no `IMediator` anywhere in this codebase), EF Core, FluentValidation, Mapster.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Scope it before writing anything
|
||||||
|
|
||||||
|
- **Which area?** The Application feature areas mirror the Domain entity folders — `Identity`, `Geography`,
|
||||||
|
`Catalog`, `Verification`, `Search`, `Booking` (singular, pre-payment) / `Bookings` (plural, post-payment),
|
||||||
|
`Payments`, `Refunds`, `Invoices`, `Bnpl`, `Payouts`, `Reviews`, `PatientCareRecords`, `Messaging`,
|
||||||
|
`PartnerCenters`, `Configuration`, `Audit`, `Analytics`, `Holidays`, `Notifications`, `SupportAlerts`. Full
|
||||||
|
list and the schema-per-area mapping: [structure.md](../../../docs/rules/server/structure.md) §2.
|
||||||
|
- **Command or query, or both?** A command mutates; a query reads. Most features are a matched pair (create
|
||||||
|
+ get, or update + list).
|
||||||
|
- **Find a sibling to mirror.** Grep the area's existing folder —
|
||||||
|
`Features/<Area>/{Commands,Queries}/` — for a feature shaped like the one you're adding. Copying a live
|
||||||
|
pattern beats inventing a new one.
|
||||||
|
- **Does it touch money?** (ledger, refunds, invoices, BNPL, payouts) → read
|
||||||
|
[money.md](../../../docs/rules/server/money.md) **first**. The invariants there (integer IRR, balanced
|
||||||
|
ledger postings, webhook idempotency, snapshot-at-compute-time) are not suggestions.
|
||||||
|
- **Does it add or change a table?** → read [persistence.md](../../../docs/rules/server/persistence.md) §5–7
|
||||||
|
before modeling it (soft-delete filters, forward-only status machines, snapshot fields, uniqueness
|
||||||
|
patterns all have a house pattern — don't reinvent one).
|
||||||
|
- **Does it need a new external dependency** (a vendor, a rail)? It becomes an interface in
|
||||||
|
`Application/Contracts/`, mock in `CrossCutting/Seams/`, real in `CrossCutting/Seams/Real/`, selected by a
|
||||||
|
`Seams:<rail>:Provider` config key that **falls closed to the mock**. See
|
||||||
|
[structure.md](../../../docs/rules/server/structure.md) §3.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. The Application slice
|
||||||
|
|
||||||
|
```
|
||||||
|
Baya.Application/Features/<Area>/
|
||||||
|
├── Commands/<VerbNoun>Command/
|
||||||
|
│ ├── <VerbNoun>Command.cs record : IRequest<OperationResult<T>>
|
||||||
|
│ ├── <VerbNoun>Command.Handler.cs internal sealed class : IRequestHandler<…>
|
||||||
|
│ └── <VerbNoun>Command.Validator.cs AbstractValidator<Command> (omit when there is nothing to validate)
|
||||||
|
└── Queries/<VerbNoun>Query/
|
||||||
|
├── <VerbNoun>Query.cs
|
||||||
|
├── <VerbNoun>Query.Handler.cs
|
||||||
|
└── <VerbNoun>Query.Result.cs record Result(…) ← the DTO returned
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Create the folder, one type per file, file name matching the type name.
|
||||||
|
2. The request is a `record`; the handler is `internal sealed`; return `OperationResult<T>` — never throw
|
||||||
|
for an expected failure (`SuccessResult`/`FailureResult`/`NotFoundResult`/`ConflictResult` map to
|
||||||
|
200/400/404/409). Let a genuinely unexpected exception propagate to the global `ExceptionHandler`.
|
||||||
|
3. Add a FluentValidation validator if the request takes input. **Never validate a route-supplied id in the
|
||||||
|
body command** — route values aren't bound into it.
|
||||||
|
4. Query: `AsNoTracking()` + `.Select()` straight to the DTO — never hydrate an entity graph to map it in
|
||||||
|
memory. Command: use `Include` only when you need navigation properties loaded to mutate the aggregate,
|
||||||
|
access the DB through `IUnitOfWork`, and `CommitAsync` once at the end.
|
||||||
|
|
||||||
|
Full rules and the validator/OperationResult examples: [cqrs.md](../../../docs/rules/server/cqrs.md) §1–3.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Persistence — only if you added or changed a table
|
||||||
|
|
||||||
|
1. One `IEntityTypeConfiguration<T>` in `Persistence/Configuration/<Area>Config/`.
|
||||||
|
2. A soft-deletable entity **must** declare `HasQueryFilter(o => !o.IsDeleted)` — without it, deleted rows
|
||||||
|
leak into every query that doesn't explicitly exclude them.
|
||||||
|
3. A lifecycle `status` column is a forward-only machine: `const string` codes, a private setter, cohesive
|
||||||
|
transition methods, a static allowed-edges table. The handler pre-checks and returns a clean `409` — it
|
||||||
|
never throws for "already moved."
|
||||||
|
4. A row that represents a past agreement (a price, an address, a policy, a deadline) is a **snapshot** —
|
||||||
|
frozen at compute time, never re-derived from a later edit to its source.
|
||||||
|
5. Money-critical constants (rates, deadlines, tolerances) are read via `IPlatformConfig.GetConfig<T>` —
|
||||||
|
**never hardcoded**, and never re-read for an already-priced row.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet ef migrations add <Name> --project src/Infrastructure/Baya.Infrastructure.Persistence --startup-project src/API/Baya.Web.Api
|
||||||
|
dotnet ef database update --project src/Infrastructure/Baya.Infrastructure.Persistence --startup-project src/API/Baya.Web.Api
|
||||||
|
```
|
||||||
|
|
||||||
|
Full patterns, with the exact uniqueness/snapshot/state-machine tables:
|
||||||
|
[persistence.md](../../../docs/rules/server/persistence.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. The controller
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[ApiVersion("1")]
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/v{version:apiVersion}/[controller]")]
|
||||||
|
[Display(Description = "One-line description shown in Swagger")]
|
||||||
|
[Authorize(ConstantPolicies.DynamicPermission)] // or [Authorize], or omit for public
|
||||||
|
public sealed class MyFeatureController(ISender sender) : BaseController
|
||||||
|
{
|
||||||
|
[HttpPost("[action]")]
|
||||||
|
[ProducesOkApiResponseType<MyCommandResult>]
|
||||||
|
public async Task<IActionResult> CreateSomething(MyCommand command, CancellationToken ct)
|
||||||
|
=> OperationResult(await sender.Send(command, ct));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `sealed`, inject `ISender` via the primary constructor, always `base.OperationResult(result)` — never
|
||||||
|
`Ok()`/`BadRequest()`/`NotFound()` directly.
|
||||||
|
- Never hardcode a route string. If the method name doesn't read cleanly as the URL segment
|
||||||
|
`SnakeCaseParameterTransformer` will produce, rename the method instead.
|
||||||
|
- Pick the narrowest authorization that fits: none (truly public) → `[Authorize]` (any authenticated user) →
|
||||||
|
`[Authorize(ConstantPolicies.DynamicPermission)]` (role/claim-gated admin action). Table and rate-limiting
|
||||||
|
notes: [cqrs.md](../../../docs/rules/server/cqrs.md) §4.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Tests
|
||||||
|
|
||||||
|
1. **Handler unit test** (xUnit + NSubstitute + FluentAssertions), Arrange-Act-Assert, named
|
||||||
|
`{MethodUnderTest}_{Scenario}_{ExpectedOutcome}`. Test the handler directly, not the controller.
|
||||||
|
2. **At least one `WebApplicationFactory<Program>` integration test** in `Baya.Test.Api` for the area,
|
||||||
|
covering: happy path → 200, unauthenticated → 401, validation failure → 400 with field detail.
|
||||||
|
3. The recurring-job scheduler is dormant under `Testing`, so a background tick can't make an integration
|
||||||
|
test flaky — you don't need to account for it.
|
||||||
|
|
||||||
|
Examples and the full testing convention: [conventions.md](../../../docs/rules/server/conventions.md) §8.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Docs this feature triggers — in the same change
|
||||||
|
|
||||||
|
- **[`docs/integration/domains/<domain>.md`](../../../docs/integration/domains/index.md)** — add the new
|
||||||
|
endpoint with its verdict (`wired`/`unwired`/`phantom`), matching the client `services/` domain it belongs
|
||||||
|
to.
|
||||||
|
- **The OpenAPI snapshot** — regenerate `docs/integration/openapi/swagger.v1.json` per
|
||||||
|
[openapi/README.md](../../../docs/integration/openapi/README.md) and update its provenance table (date,
|
||||||
|
commit, path/operation counts) in the same change. A snapshot with stale provenance is what that
|
||||||
|
convention exists to prevent.
|
||||||
|
- **[`docs/status/backlog.md`](../../../docs/status/backlog.md)** — tick the row if this closes a filed
|
||||||
|
item. Never delete a row; a ticked row is the record that it shipped.
|
||||||
|
- **A reference file in `docs/rules/server/`** — only if the feature introduces a genuinely new reusable
|
||||||
|
pattern, seam, or base class. Don't add prose for a feature that just follows the existing pattern.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Before you call it done
|
||||||
|
|
||||||
|
Run the server gate — `dotnet build Baya.sln` (**zero new warnings**) and `dotnet test Baya.sln` — and read
|
||||||
|
your diff as if reviewing the PR. Full "what done means" checklist:
|
||||||
|
[git-and-gates.md](../../../docs/rules/shared/git-and-gates.md) §2.
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
---
|
||||||
|
name: flow-testing
|
||||||
|
description: >-
|
||||||
|
Boot both sides of Balinyaar locally and walk a real user journey end to end — the right seeded account,
|
||||||
|
the right flow doc, and knowing whether you just proved the real path or a mock answering. Use before
|
||||||
|
claiming a fix or feature works, or when asked to test, verify, or demo a flow.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Balinyaar Flow Testing
|
||||||
|
|
||||||
|
Exercising a flow proves something only if you know which half of the stack actually answered. This is the
|
||||||
|
procedure; the facts it points at (ports, accounts, known failure modes) live in
|
||||||
|
[docs/flows/testing-setup.md](../../../docs/flows/testing-setup.md) and are kept current there — don't copy
|
||||||
|
them here, they will drift.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Boot it
|
||||||
|
|
||||||
|
The five-minute path, verbatim from [testing-setup.md](../../../docs/flows/testing-setup.md#the-five-minute-path):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# API — mock SMS or request_otp 500s
|
||||||
|
cd server
|
||||||
|
Seams__Sms__Provider=mock dotnet run --project src/API/Baya.Web.Api/Baya.Web.Api.csproj
|
||||||
|
|
||||||
|
# client
|
||||||
|
cd client && npm install && npm run dev # http://localhost:3000/fa
|
||||||
|
|
||||||
|
# read the OTP — the console does NOT print it
|
||||||
|
curl http://localhost:5002/api/v1/dev/last_otp/09120000010
|
||||||
|
```
|
||||||
|
|
||||||
|
No database setup: the committed dev config points at an already-seeded remote SQL Server. If anything here
|
||||||
|
doesn't match reality when you run it, **testing-setup.md is wrong and needs a fix in the same change** — it
|
||||||
|
carries a `Last verified` stamp for exactly this reason.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Check the mock-vs-real map *before* you conclude anything
|
||||||
|
|
||||||
|
A flow "working" through a mocked domain proves the UI, not the server. Before testing:
|
||||||
|
|
||||||
|
1. Open [docs/integration/domains/index.md](../../../docs/integration/domains/index.md) — the census table
|
||||||
|
names which of the 22 client `services/` domains are real vs **mock** (currently 15 real, 7 mock:
|
||||||
|
`admin`, `bnpl`, `partnerCenter`, `patientRecords`, `payouts`, `refunds`, `verification`).
|
||||||
|
2. A mocked domain is a `USE_<DOMAIN>_MOCK` flag in `client/src/services/<domain>/constants.ts` — check it
|
||||||
|
directly if you need certainty for the exact domain you're touching.
|
||||||
|
3. State your finding in terms of which one you exercised: "the booking flow works end-to-end against the
|
||||||
|
real server" is a different claim from "the admin console renders correctly against its mock" — never
|
||||||
|
report the second as if it were the first.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Pick the right seeded account
|
||||||
|
|
||||||
|
Demo accounts, their roles, and what each one demonstrates are tabulated in
|
||||||
|
[testing-setup.md](../../../docs/flows/testing-setup.md#demo-accounts) — read it there rather than assuming
|
||||||
|
a phone number. One standing gap to route around: **the seeded admin accounts (`…020` `super_admin`,
|
||||||
|
`…021` `finance`) get 403 on every real admin endpoint** (a `DynamicPermission` / role-literal mismatch).
|
||||||
|
The admin backoffice is only testable against the client's mock; don't spend time trying to walk it against
|
||||||
|
the real API without first checking whether that gap has been closed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Walk the flow
|
||||||
|
|
||||||
|
[docs/flows/index.md](../../../docs/flows/index.md) is the atlas — one file per user-meaningful journey,
|
||||||
|
each answering exactly three questions: what it does, what's mocked *for that journey specifically*, and how
|
||||||
|
to test it. Open the one file that matches what you're testing rather than guessing the steps; it's the
|
||||||
|
one place gap numbers and REQ references for that journey are tracked.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Two things that will silently invalidate your test
|
||||||
|
|
||||||
|
- **The scheduler is live while you test.** `booking_request_expiry` runs every 60 seconds (hardcoded) and
|
||||||
|
flips an un-actioned request to `expired_no_response` / `payment_deadline_expired` out from under you. Act
|
||||||
|
on a request promptly, or create a fresh one rather than trying to reuse an old test artifact.
|
||||||
|
- **The OTP endpoints are rate-limited together.** `request_otp` and `verify_otp` share one bucket, 5 calls
|
||||||
|
per 60 s per IP — a login is 2 calls, so that's **two logins per minute, total**. Space scripted logins
|
||||||
|
≥ 40 s apart (see [testing-setup.md](../../../docs/flows/testing-setup.md#scripting-logins) for a working
|
||||||
|
script) or you'll 429 and misread it as a bug.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. When the seeded world has aged out
|
||||||
|
|
||||||
|
There is no in-app reseed — both seeders guard on natural keys, so re-running never refreshes stale dates.
|
||||||
|
If the scenario you need (an "upcoming" booking, an open dispute window, a pending request) no longer exists
|
||||||
|
because the world was seeded days ago:
|
||||||
|
|
||||||
|
- **Fastest fix:** create the scenario fresh yourself (customer → search → booking request → accept → pay) —
|
||||||
|
this is the intended way to exercise booking-request and checkout-and-payment anyway.
|
||||||
|
- **Full reseed:** only against a **local** database — `docker compose down -v && docker compose up -d` under
|
||||||
|
`server/`, then boot. **Never drop the shared remote database** casually; it backs the live demo deployment
|
||||||
|
and other people's sessions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Report what you actually saw
|
||||||
|
|
||||||
|
Name the account you used, the domain's mock/real status, and the exact response (status code, error
|
||||||
|
message) rather than "it worked" — the troubleshooting table in
|
||||||
|
[testing-setup.md](../../../docs/flows/testing-setup.md#troubleshooting) exists because several failure
|
||||||
|
modes here look identical to an unrelated bug (a rate limit looks like a crash; `/healthz/ready` failing on
|
||||||
|
Windows looks like the app is down). Check it before filing something as a new defect.
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# Git hooks
|
|
||||||
|
|
||||||
Repo-managed git hooks (they live in version control, unlike `.git/hooks`).
|
|
||||||
|
|
||||||
## Enable (once per clone)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git config core.hooksPath .githooks
|
|
||||||
```
|
|
||||||
|
|
||||||
## `pre-commit` — secret scan
|
|
||||||
|
|
||||||
A fast, dependency-free backstop against a credential leaking into a file that shouldn't hold one
|
|
||||||
(refinement-phase-5). It rejects a commit that stages:
|
|
||||||
|
|
||||||
- the retired hardcoded admin password `qw123321`, anywhere,
|
|
||||||
- private-key material or an AWS access-key id, anywhere,
|
|
||||||
- the deployment's SQL Server host `87.107.152.16` **outside the declared config files**,
|
|
||||||
- a **real** connection-string password in any `appsettings*.json` **outside the declared config files**
|
|
||||||
(elsewhere only the `SET_VIA_USER_SECRETS_OR_ENV` placeholder is allowed).
|
|
||||||
|
|
||||||
**Declared config files.** The pre-launch demo deployment configures itself from committed files rather
|
|
||||||
than a secret store ([DEPLOY.md](../DEPLOY.md)), so a short allow-list — `appsettings.Development.json`,
|
|
||||||
`docker-compose.yml`, `telegram-otp-bot/.env.example`, `DEPLOY.md` — is exempt from the last two checks.
|
|
||||||
The list is maintained in the `declared_config` function in the hook and is the honest record of where the
|
|
||||||
repo's secrets are. **Shrink it, never grow it**: once real users exist, those values must be rotated and
|
|
||||||
moved out of git.
|
|
||||||
|
|
||||||
It scans only staged additions, so it is quick. It is **not** a replacement for a full scanner
|
|
||||||
(gitleaks / trufflehog) in CI — it is the local first line of defence.
|
|
||||||
|
|
||||||
Bypass a false positive with `git commit --no-verify` (use sparingly, and only when you are certain the
|
|
||||||
flagged line is not a secret).
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
#
|
|
||||||
# Balinyaar secret-scanning pre-commit hook (refinement-phase-5).
|
|
||||||
# Blocks a commit that stages an obvious credential. This is a fast, dependency-free backstop for the
|
|
||||||
# root CLAUDE.md rule "Never commit secrets" — not a replacement for gitleaks/trufflehog in CI.
|
|
||||||
#
|
|
||||||
# Enable once per clone: git config core.hooksPath .githooks
|
|
||||||
# Bypass a false positive: git commit --no-verify (use sparingly, and only when you are certain)
|
|
||||||
#
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Committed placeholders are allowed — real values are not. Keep in sync with StartupSecretsGuard.
|
|
||||||
PLACEHOLDER='SET_VIA_USER_SECRETS_OR_ENV'
|
|
||||||
|
|
||||||
# Files that deliberately carry live deployment credentials, because the pre-launch demo deployment
|
|
||||||
# configures itself from committed files rather than a secret store (see DEPLOY.md). They are exempt from
|
|
||||||
# the connection-string and known-host checks ONLY — the private-key and AWS-key checks still apply to
|
|
||||||
# them, and every other file in the repo is scanned exactly as strictly as before.
|
|
||||||
#
|
|
||||||
# This list is the honest record of where the repo's secrets are. Shrink it, never grow it: the moment
|
|
||||||
# real users exist, these values must be rotated and moved out of git.
|
|
||||||
declared_config() {
|
|
||||||
case "$1" in
|
|
||||||
server/src/API/Baya.Web.Api/appsettings.Development.json) return 0 ;;
|
|
||||||
docker-compose.yml) return 0 ;;
|
|
||||||
telegram-otp-bot/.env.example) return 0 ;;
|
|
||||||
DEPLOY.md) return 0 ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# Only scan added/changed lines in text files that are staged.
|
|
||||||
staged=$(git diff --cached --name-only --diff-filter=ACM)
|
|
||||||
[ -z "$staged" ] && exit 0
|
|
||||||
|
|
||||||
violations=0
|
|
||||||
report() { printf ' ✖ %s\n' "$1"; violations=$((violations + 1)); }
|
|
||||||
|
|
||||||
while IFS= read -r file; do
|
|
||||||
# Skip this hook, lockfiles, and binaries.
|
|
||||||
case "$file" in
|
|
||||||
.githooks/*) continue ;;
|
|
||||||
*.png|*.jpg|*.jpeg|*.gif|*.ico|*.pdf|*.dll|*.exe|*.snk) continue ;;
|
|
||||||
esac
|
|
||||||
[ -f "$file" ] || continue
|
|
||||||
|
|
||||||
added=$(git diff --cached -U0 -- "$file" | grep '^+' | grep -v '^+++' || true)
|
|
||||||
[ -z "$added" ] && continue
|
|
||||||
|
|
||||||
# The retired hardcoded admin password. Applies everywhere, no exemptions.
|
|
||||||
echo "$added" | grep -Eq 'qw123321' && report "$file: hardcoded admin password 'qw123321'"
|
|
||||||
|
|
||||||
if ! declared_config "$file"; then
|
|
||||||
# The deployment's SQL Server host — outside the declared config files it is a leak.
|
|
||||||
echo "$added" | grep -Eq '87\.107\.152\.16' && report "$file: SQL Server host 87.107.152.16 outside the declared config files"
|
|
||||||
|
|
||||||
# A real (non-placeholder) connection-string password in a committed appsettings file.
|
|
||||||
case "$file" in
|
|
||||||
*appsettings*.json)
|
|
||||||
echo "$added" \
|
|
||||||
| grep -Ei 'Password=[^;"'"'"' ]+' \
|
|
||||||
| grep -viq "Password=${PLACEHOLDER}" \
|
|
||||||
&& report "$file: connection-string password must be '${PLACEHOLDER}' (see DEPLOY.md for where real values live)"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Private keys and common cloud tokens, anywhere.
|
|
||||||
echo "$added" | grep -Eq -- '-----BEGIN (RSA|EC|OPENSSH|PRIVATE) .*PRIVATE KEY-----' && report "$file: private key material"
|
|
||||||
echo "$added" | grep -Eq 'AKIA[0-9A-Z]{16}' && report "$file: AWS access key id"
|
|
||||||
done <<< "$staged"
|
|
||||||
|
|
||||||
if [ "$violations" -gt 0 ]; then
|
|
||||||
echo ""
|
|
||||||
echo "Commit blocked: $violations potential secret(s) staged. Real values belong in one of the declared"
|
|
||||||
echo "config files (see the 'declared_config' list in this hook, and DEPLOY.md); everything else commits"
|
|
||||||
echo "only the '${PLACEHOLDER}' placeholder."
|
|
||||||
echo "To override a false positive: git commit --no-verify"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -8,7 +8,7 @@ side of the stack lives in that project's own `CLAUDE.md`.
|
|||||||
> Working in `server/`? Read [server/CLAUDE.md](server/CLAUDE.md).
|
> Working in `server/`? Read [server/CLAUDE.md](server/CLAUDE.md).
|
||||||
> You almost never need both. A frontend change does not touch server files, and vice-versa.
|
> You almost never need both. A frontend change does not touch server files, and vice-versa.
|
||||||
|
|
||||||
> Last verified: 2026-08-02 against commit `e2db973`.
|
> Last verified: 2026-08-02 against commit `51e86a1`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -55,7 +55,6 @@ package, or solution — each project is built, linted, and run on its own.
|
|||||||
| [`archive/`](archive/README.md) | The executed build-chain (was `dev/`) and the cleanup plan that produced `docs/`. **History, not instruction** — nothing to build from it | Markdown | [archive/README.md](archive/README.md) |
|
| [`archive/`](archive/README.md) | The executed build-chain (was `dev/`) and the cleanup plan that produced `docs/`. **History, not instruction** — nothing to build from it | Markdown | [archive/README.md](archive/README.md) |
|
||||||
| [`telegram-otp-bot/`](telegram-otp-bot/) | OTP relay (standalone, the pre-launch demo rail) | Node 18+, zero deps | [telegram-otp-bot/README.md](telegram-otp-bot/README.md) |
|
| [`telegram-otp-bot/`](telegram-otp-bot/) | OTP relay (standalone, the pre-launch demo rail) | Node 18+, zero deps | [telegram-otp-bot/README.md](telegram-otp-bot/README.md) |
|
||||||
| [`deploy/`](deploy/) | Reverse-proxy config | Caddyfile | [DEPLOY.md](DEPLOY.md) |
|
| [`deploy/`](deploy/) | Reverse-proxy config | Caddyfile | [DEPLOY.md](DEPLOY.md) |
|
||||||
| [`.githooks/`](.githooks/README.md) | Repo-managed git hooks (the pre-commit secret scan) | shell | [docs/rules/shared/git-and-gates.md](docs/rules/shared/git-and-gates.md) |
|
|
||||||
|
|
||||||
`AGENTS.md` files in this repo are thin pointers to the `CLAUDE.md` in the same folder. **`CLAUDE.md` is the
|
`AGENTS.md` files in this repo are thin pointers to the `CLAUDE.md` in the same folder. **`CLAUDE.md` is the
|
||||||
single source of truth at every level.**
|
single source of truth at every level.**
|
||||||
@@ -82,7 +81,7 @@ are touching.
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| **Hard rules** | this file · [client/CLAUDE.md](client/CLAUDE.md) · [server/CLAUDE.md](server/CLAUDE.md) | Constraints whose violation breaks the build, the gate, or a business invariant |
|
| **Hard rules** | this file · [client/CLAUDE.md](client/CLAUDE.md) · [server/CLAUDE.md](server/CLAUDE.md) | Constraints whose violation breaks the build, the gate, or a business invariant |
|
||||||
| **Reference** | [`docs/rules/`](docs/rules/index.md) | The *how* and the *why*, read on demand — 3 shared files, 8 client, 6 server, plus the documentation convention |
|
| **Reference** | [`docs/rules/`](docs/rules/index.md) | The *how* and the *why*, read on demand — 3 shared files, 8 client, 6 server, plus the documentation convention |
|
||||||
| **Procedure** | `.claude/skills/` | Playbooks. The **frontend-designer** skill is the design contract for `client/` UI |
|
| **Procedure** | `.claude/skills/` | Playbooks: **frontend-designer** (the design contract for `client/` UI), **backend-feature** (adding a server feature), **flow-testing** (walking a flow end to end) |
|
||||||
|
|
||||||
Start at [docs/rules/index.md](docs/rules/index.md) — it maps "working on X" to the one file to open.
|
Start at [docs/rules/index.md](docs/rules/index.md) — it maps "working on X" to the one file to open.
|
||||||
|
|
||||||
@@ -157,7 +156,4 @@ cd client && npm install && npm run dev # http://localhost:3000
|
|||||||
|
|
||||||
# Backend
|
# Backend
|
||||||
cd server && dotnet run --project src/API/Baya.Web.Api/Baya.Web.Api.csproj # http://localhost:5002/swagger
|
cd server && dotnet run --project src/API/Baya.Web.Api/Baya.Web.Api.csproj # http://localhost:5002/swagger
|
||||||
|
|
||||||
# Once per clone — enable the repo's git hooks
|
|
||||||
git config core.hooksPath .githooks
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ machine is now inert and can be deleted. Every value lives in a file in the repo
|
|||||||
| Telegram relay config (bot token, chat ids, API key, proxy) | `docker-compose.yml` → `otp-relay.environment` |
|
| Telegram relay config (bot token, chat ids, API key, proxy) | `docker-compose.yml` → `otp-relay.environment` |
|
||||||
|
|
||||||
The placeholder string `SET_VIA_USER_SECRETS_OR_ENV` in the base `appsettings.json` names that removed
|
The placeholder string `SET_VIA_USER_SECRETS_OR_ENV` in the base `appsettings.json` names that removed
|
||||||
store; the *name* is a historical artifact, kept only because it is the sentinel `StartupSecretsGuard` and
|
store; the *name* is a historical artifact, kept only because it is the sentinel `StartupSecretsGuard`
|
||||||
the pre-commit hook both reject. **The mechanism is appsettings files and environment variables** — see
|
rejects. **The mechanism is appsettings files and environment variables** — see
|
||||||
[docs/integration/config-matrix.md](docs/integration/config-matrix.md), which lists every key, its default,
|
[docs/integration/config-matrix.md](docs/integration/config-matrix.md), which lists every key, its default,
|
||||||
and who reads it.
|
and who reads it.
|
||||||
|
|
||||||
|
|||||||
+7
-5
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
The entry point. Start here, follow one link, stop reading.
|
The entry point. Start here, follow one link, stop reading.
|
||||||
|
|
||||||
> **Built by the clarify chain**, phases 0–6, 2026-07-29 → 2026-08-02. The chain's own plan — inventory,
|
> **Built by the clarify chain**, phases 0–7, 2026-07-29 → 2026-08-02. The chain's own plan — inventory,
|
||||||
> contradiction log, phase files, and their progress table — is now history, kept at
|
> contradiction log, phase files, and their progress table — is now history, kept at
|
||||||
> [`archive/clarify-chain/`](../archive/clarify-chain/README.md). Phase 7 (skills & guardrails) is still
|
> [`archive/clarify-chain/`](../archive/clarify-chain/README.md). Phase 7 (skills & guardrails) is done:
|
||||||
> open.
|
> three playbooks live in [`.claude/skills/`](../.claude/skills/); the anti-drift convention below is
|
||||||
|
> enforced by review rather than a git hook (an MVP-stage call — see
|
||||||
|
> [decisions.md](status/decisions.md)).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -55,5 +57,5 @@ Two documents stay outside this tree on purpose:
|
|||||||
it would defeat that.
|
it would defeat that.
|
||||||
4. **English throughout**, including in files that describe Persian UI copy.
|
4. **English throughout**, including in files that describe Persian UI copy.
|
||||||
|
|
||||||
The full convention is in [docs/rules/documentation.md](rules/documentation.md), to be enforced by a
|
The full convention is in [docs/rules/documentation.md](rules/documentation.md) — enforced by review,
|
||||||
pre-commit warning (phase 7).
|
not tooling (an MVP-stage call; see [git-and-gates.md](rules/shared/git-and-gates.md)).
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Every configuration key on both sides of the seam, plus docker and the OTP relay, with where it is set and
|
Every configuration key on both sides of the seam, plus docker and the OTP relay, with where it is set and
|
||||||
who reads it.
|
who reads it.
|
||||||
|
|
||||||
> Last verified: 2026-07-30 against commit `d3ec723`. Built by **mechanically enumerating** every leaf key
|
> Last verified: 2026-08-02 against commit `51e86a1`. Built by **mechanically enumerating** every leaf key
|
||||||
> in both `appsettings*.json`, every `environment:` entry in `docker-compose.yml`, every assignment in the
|
> in both `appsettings*.json`, every `environment:` entry in `docker-compose.yml`, every assignment in the
|
||||||
> three `client/.env*` files and `telegram-otp-bot/.env.example`, and every `process.env.*` read under
|
> three `client/.env*` files and `telegram-otp-bot/.env.example`, and every `process.env.*` read under
|
||||||
> `client/src/` — then diffing the sets. The gaps that diff found are in
|
> `client/src/` — then diffing the sets. The gaps that diff found are in
|
||||||
@@ -79,7 +79,7 @@ Every one has a working default, so nothing is broken — but none is discoverab
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `OpenTelemetry:Otlp:Endpoint` | `SetupOpenTelemetry` | **OTLP export is not wired at all.** Prometheus `/metrics` still works | Opt-in by design, so no exporter spams an absent collector |
|
| `OpenTelemetry:Otlp:Endpoint` | `SetupOpenTelemetry` | **OTLP export is not wired at all.** Prometheus `/metrics` still works | Opt-in by design, so no exporter spams an absent collector |
|
||||||
| `Search:Backend` | `AddPersistenceServices` | `SqlNurseSearch` | Any value other than `sql`/empty **throws at startup** — Elasticsearch is deferred and fails loudly |
|
| `Search:Backend` | `AddPersistenceServices` | `SqlNurseSearch` | Any value other than `sql`/empty **throws at startup** — Elasticsearch is deferred and fails loudly |
|
||||||
| `Seed:AdminUsername` / `:AdminPassword` / `:AdminEmail` | `SeedDataBase` | no break-glass admin is seeded | The hardcoded `admin`/`qw123321` was removed in refinement-phase-5 and the pre-commit hook blocks its return |
|
| `Seed:AdminUsername` / `:AdminPassword` / `:AdminEmail` | `SeedDataBase` | no break-glass admin is seeded | The hardcoded `admin`/`qw123321` was removed in refinement-phase-5 |
|
||||||
| `Seams:<rail>:Provider` (×11) | `AddCrossCuttingSeams` | **`mock`** | The seam selectors, below |
|
| `Seams:<rail>:Provider` (×11) | `AddCrossCuttingSeams` | **`mock`** | The seam selectors, below |
|
||||||
|
|
||||||
### The seam selectors
|
### The seam selectors
|
||||||
@@ -252,13 +252,11 @@ places where the config is not discoverable from the config files.
|
|||||||
### The placeholder's name
|
### The placeholder's name
|
||||||
|
|
||||||
`SET_VIA_USER_SECRETS_OR_ENV` names a store that no longer exists (contradiction **C-2**). The *behaviour*
|
`SET_VIA_USER_SECRETS_OR_ENV` names a store that no longer exists (contradiction **C-2**). The *behaviour*
|
||||||
is correct — it is a sentinel that `StartupSecretsGuard` and the pre-commit hook both reject — but the name
|
is correct — it is a sentinel that `StartupSecretsGuard` rejects — but the name instructs a reader to use a
|
||||||
instructs a reader to use a removed mechanism.
|
removed mechanism.
|
||||||
|
|
||||||
It was **not renamed in this phase**, because the string is load-bearing in seven live files:
|
It was **not renamed in this phase**, because the string is load-bearing in several live files:
|
||||||
`appsettings.json` (×6), `StartupSecretsGuard.cs`, `.githooks/pre-commit`, `.githooks/README.md`,
|
`appsettings.json` (×6), `StartupSecretsGuard.cs`, `Baya.Test.Api/StartupSecretsGuardTests.cs` (×2), and
|
||||||
`Baya.Test.Api/StartupSecretsGuardTests.cs` (×2), `docs/rules/shared/git-and-gates.md` and
|
`docs/rules/server/structure.md`. Renaming it is a server-code + test change requiring `dotnet build` and
|
||||||
`docs/rules/server/structure.md`. Renaming it is a server-code + git-hook + test change requiring
|
`dotnet test` to prove the gate still fires — out of scope for a documentation phase. **This section is the
|
||||||
`dotnet build` and `dotnet test` to prove the gate still fires — out of scope for a documentation phase.
|
authoritative statement of the mechanism**; the rename is filed for Phase 4 with that worklist.
|
||||||
**This section is the authoritative statement of the mechanism**; the rename is filed for Phase 4 with that
|
|
||||||
exact seven-file worklist.
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Deferred — recorded, not re-decided
|
# Deferred — recorded, not re-decided
|
||||||
|
|
||||||
> Last verified: 2026-08-02 against commit `cd8144e`. Populated by phase 5 of the
|
> Last verified: 2026-08-02 against commit `51e86a1`. Populated by phase 5 of the
|
||||||
> [documentation clean-up chain](../../archive/clarify-chain/README.md).
|
> [documentation clean-up chain](../../archive/clarify-chain/README.md).
|
||||||
|
|
||||||
**51 items** carry deferred status in [backlog.md](../status/backlog.md): the 43 in its dedicated
|
**51 items** carry deferred status in [backlog.md](../status/backlog.md): the 43 in its dedicated
|
||||||
@@ -96,7 +96,7 @@ Correct for a single instance / MVP load; each has a concrete, measurable trigge
|
|||||||
|
|
||||||
| Item | Why deferred | Pull-trigger | Size | Decided |
|
| Item | Why deferred | Pull-trigger | Size | Decided |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| [BL-219](../status/backlog.md#minor-115) — rename the `SET_VIA_USER_SECRETS_OR_ENV` placeholder sentinel | The name is a load-bearing sentinel `StartupSecretsGuard` and the pre-commit hook both check for; renaming touches 7 files for a cosmetic gain | None — cleanup-of-convenience | S | phase 2 (`open-contradictions.md` C-2) |
|
| [BL-219](../status/backlog.md#minor-115) — rename the `SET_VIA_USER_SECRETS_OR_ENV` placeholder sentinel | The name is a load-bearing sentinel `StartupSecretsGuard` checks for; renaming touches several files for a cosmetic gain | None — cleanup-of-convenience | S | phase 2 (`open-contradictions.md` C-2) |
|
||||||
| [BL-250](../status/backlog.md#deferred-43) — the ESLint unused-vars gate is a repo-wide no-op | Config patches an export path that doesn't carry the rule; fixing it is a dedicated infra task, not a drive-by | A dedicated infra task | S | frontend-phase-13 follow-up |
|
| [BL-250](../status/backlog.md#deferred-43) — the ESLint unused-vars gate is a repo-wide no-op | Config patches an export path that doesn't carry the rule; fixing it is a dedicated infra task, not a drive-by | A dedicated infra task | S | frontend-phase-13 follow-up |
|
||||||
| [BL-253](../status/backlog.md#deferred-43) — payment-webhook confirm path uses two DB commits instead of one transaction | Kept safe today via idempotency + a forward-only guard; a real fix needs `IUnitOfWork` to grow a transaction scope first | `IUnitOfWork` grows a transaction scope | M | backend-phase-10 follow-up |
|
| [BL-253](../status/backlog.md#deferred-43) — payment-webhook confirm path uses two DB commits instead of one transaction | Kept safe today via idempotency + a forward-only guard; a real fix needs `IUnitOfWork` to grow a transaction scope first | `IUnitOfWork` grows a transaction scope | M | backend-phase-10 follow-up |
|
||||||
| [BL-255](../status/backlog.md#deferred-43) — `Bookings`/`Invoices.partner_center_id` have no DB-level FK | Only `nurse_profiles.partner_center_id` got one, per that phase's own Definition of Done | A data-integrity pass on partner-center columns | S | backend-phase 11/15 follow-ups |
|
| [BL-255](../status/backlog.md#deferred-43) — `Bookings`/`Invoices.partner_center_id` have no DB-level FK | Only `nurse_profiles.partner_center_id` got one, per that phase's own Definition of Done | A data-integrity pass on partner-center columns | S | backend-phase 11/15 follow-ups |
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
How this repository keeps its own docs from lying. Read before writing or editing any `.md`.
|
How this repository keeps its own docs from lying. Read before writing or editing any `.md`.
|
||||||
|
|
||||||
> Last verified: 2026-07-30 against commit `d3ec723`.
|
> Last verified: 2026-08-02 against commit `51e86a1`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -43,8 +43,8 @@ regenerate — never hand-edit the HTML. If you add or rename a `.md`, update th
|
|||||||
|
|
||||||
## 2. What to update when X changes
|
## 2. What to update when X changes
|
||||||
|
|
||||||
This is the anti-drift contract. Each row is enforced by review, and (from phase 7) warned about by the
|
This is the anti-drift contract. Each row is enforced by review — there is no pre-commit tooling behind
|
||||||
pre-commit hook.
|
it (a deliberate MVP-stage call; see [git-and-gates.md](shared/git-and-gates.md)).
|
||||||
|
|
||||||
| When you change… | Update, in the same change |
|
| When you change… | Update, in the same change |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
|||||||
+3
-3
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
What must never be broken, and nothing else.
|
What must never be broken, and nothing else.
|
||||||
|
|
||||||
> Last verified: 2026-07-30 against commit `d3ec723`.
|
> Last verified: 2026-08-02 against commit `51e86a1`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ Three tiers, and a rule lives in exactly one of them.
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| **Hard rules** | [root CLAUDE.md](../../CLAUDE.md) · [client/CLAUDE.md](../../client/CLAUDE.md) · [server/CLAUDE.md](../../server/CLAUDE.md) | Constraints whose violation breaks the build, the gate, or a business invariant. Imperative, no explanation. | ≤250 lines each |
|
| **Hard rules** | [root CLAUDE.md](../../CLAUDE.md) · [client/CLAUDE.md](../../client/CLAUDE.md) · [server/CLAUDE.md](../../server/CLAUDE.md) | Constraints whose violation breaks the build, the gate, or a business invariant. Imperative, no explanation. | ≤250 lines each |
|
||||||
| **Reference** | `docs/rules/{shared,client,server}/*.md` — here | The *how* and the *why*. Read on demand when you are working in that area. | ≤400 lines per file |
|
| **Reference** | `docs/rules/{shared,client,server}/*.md` — here | The *how* and the *why*. Read on demand when you are working in that area. | ≤400 lines per file |
|
||||||
| **Procedure** | `.claude/skills/` | Step-by-step playbooks for recurring tasks. The [frontend-designer](../../.claude/skills/frontend-designer/SKILL.md) skill is the design playbook. | — |
|
| **Procedure** | `.claude/skills/` | Step-by-step playbooks for recurring tasks: [frontend-designer](../../.claude/skills/frontend-designer/SKILL.md) (design), [backend-feature](../../.claude/skills/backend-feature/SKILL.md) (adding a server feature), [flow-testing](../../.claude/skills/flow-testing/SKILL.md) (walking a flow end to end). | — |
|
||||||
|
|
||||||
The test: **a rule that only matters once you are already editing theme code is reference.** A rule like
|
The test: **a rule that only matters once you are already editing theme code is reference.** A rule like
|
||||||
"never change `Seams:FieldEncryption:Key`" is hard — it belongs inline where nobody can miss it.
|
"never change `Seams:FieldEncryption:Key`" is hard — it belongs inline where nobody can miss it.
|
||||||
@@ -31,7 +31,7 @@ touching. Not both trees, not every file.
|
|||||||
| File | Covers |
|
| File | Covers |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| [shared/naming.md](shared/naming.md) | `Baya*` vs `balinyaar-client`, the `@/*` alias, file and directory conventions |
|
| [shared/naming.md](shared/naming.md) | `Baya*` vs `balinyaar-client`, the `@/*` alias, file and directory conventions |
|
||||||
| [shared/git-and-gates.md](shared/git-and-gates.md) | Branches, commits, the pre-commit secret scan, what "done" means per project |
|
| [shared/git-and-gates.md](shared/git-and-gates.md) | Branches, commits, what "done" means per project |
|
||||||
| [shared/code-quality.md](shared/code-quality.md) | No dead code, comment the *why*, no starter scaffolding, the seam rule for mocks |
|
| [shared/code-quality.md](shared/code-quality.md) | No dead code, comment the *why*, no starter scaffolding, the seam rule for mocks |
|
||||||
|
|
||||||
### Client — `client/`
|
### Client — `client/`
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Auth, JWE, sessions, field encryption, tenancy, and the two-stage clinical disclosure rule.
|
Auth, JWE, sessions, field encryption, tenancy, and the two-stage clinical disclosure rule.
|
||||||
|
|
||||||
> Last verified: 2026-07-30 against commit `d3ec723`.
|
> Last verified: 2026-08-02 against commit `51e86a1`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -101,8 +101,8 @@ The full vocabulary is in `Domain/Entities/User/RoleNames`.
|
|||||||
|
|
||||||
- **`SeedDataBase` always seeds the roles**, and seeds a **bootstrap admin only when
|
- **`SeedDataBase` always seeds the roles**, and seeds a **bootstrap admin only when
|
||||||
`Seed:AdminUsername`/`Seed:AdminPassword` are configured** — break-glass only. There is no committed
|
`Seed:AdminUsername`/`Seed:AdminPassword` are configured** — break-glass only. There is no committed
|
||||||
`admin`/`qw123321` any more (the pre-commit hook rejects that string outright). Day-to-day admins come from
|
`admin`/`qw123321` any more. Day-to-day admins come from the phone-OTP demo seeds or are provisioned
|
||||||
the phone-OTP demo seeds or are provisioned out-of-band.
|
out-of-band.
|
||||||
- **`customer` and `nurse` are self-selectable** via `POST me/select_role` — audited (`granted_by`,
|
- **`customer` and `nurse` are self-selectable** via `POST me/select_role` — audited (`granted_by`,
|
||||||
`granted_at`), idempotent, and **both can be held** by one user (a dual session moves freely between the
|
`granted_at`), idempotent, and **both can be held** by one user (a dual session moves freely between the
|
||||||
family and nurse apps).
|
family and nurse apps).
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
What must pass before work is done, and what the repo refuses to let you commit.
|
What must pass before work is done, and what the repo refuses to let you commit.
|
||||||
|
|
||||||
> Last verified: 2026-07-30 against commit `d3ec723`.
|
> Last verified: 2026-08-02 against commit `51e86a1`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -63,40 +63,15 @@ A change that doesn't pass its own gate is **not done**, regardless of how compl
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. The pre-commit secret scan
|
## 3. No pre-commit secret scan (for now)
|
||||||
|
|
||||||
Repo-managed hooks live in `.githooks/` (in version control, unlike `.git/hooks`). **Enable them once per
|
There is no git hook enforcing anything in this repo — `.githooks/` was removed in phase 7 as an
|
||||||
clone:**
|
MVP-stage call: this is a pre-launch demo project and the mechanical backstop wasn't worth the overhead
|
||||||
|
yet. The underlying rule is unchanged — **never commit a real secret** — it's just unenforced by tooling.
|
||||||
```bash
|
Root [CLAUDE.md](../../../CLAUDE.md) §6 already documents the repo's actual trade: config lives in
|
||||||
git config core.hooksPath .githooks
|
committed files, including live credentials, until real users exist (see
|
||||||
```
|
[DEPLOY.md](../../../DEPLOY.md) "Going to Production" for the rotation step that unblocks that). Revisit
|
||||||
|
adding a hook — or a CI scanner (gitleaks, trufflehog) — if that trade changes before this one does.
|
||||||
`pre-commit` is a fast, dependency-free backstop against a credential leaking into a file that shouldn't
|
|
||||||
hold one. It scans **only staged additions**, so it is quick. It rejects a commit that stages:
|
|
||||||
|
|
||||||
- the retired hardcoded admin password `qw123321`, anywhere;
|
|
||||||
- private-key material or an AWS access-key id, anywhere;
|
|
||||||
- the deployment's SQL Server host `87.107.152.16` **outside the declared config files**;
|
|
||||||
- a **real** connection-string password in any `appsettings*.json` **outside the declared config files**
|
|
||||||
(elsewhere only the `SET_VIA_USER_SECRETS_OR_ENV` placeholder is allowed).
|
|
||||||
|
|
||||||
### The declared-config allow-list
|
|
||||||
|
|
||||||
The pre-launch demo deployment configures itself from committed files rather than a secret store (see
|
|
||||||
[`DEPLOY.md`](../../../DEPLOY.md)), so a short allow-list is exempt from the last two checks:
|
|
||||||
|
|
||||||
`appsettings.Development.json` · `docker-compose.yml` · `telegram-otp-bot/.env.example` · `DEPLOY.md`
|
|
||||||
|
|
||||||
It is maintained in the `declared_config` function in the hook, and it is **the honest record of where
|
|
||||||
this repo's secrets are**. **Shrink it, never grow it.** Once real users exist, those values must be
|
|
||||||
rotated and moved out of git.
|
|
||||||
|
|
||||||
### Limits
|
|
||||||
|
|
||||||
This is the local first line of defence, **not** a replacement for a full scanner (gitleaks, trufflehog)
|
|
||||||
in CI. Bypass a false positive with `git commit --no-verify` — sparingly, and only when you are certain
|
|
||||||
the flagged line is not a secret.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Backlog — every open item, triaged
|
# Backlog — every open item, triaged
|
||||||
|
|
||||||
> Last verified: 2026-08-02 against commit `b876490`.
|
> Last verified: 2026-08-02 against commit `51e86a1`.
|
||||||
|
|
||||||
Reconciled from five ledgers — [hardening/issues.md](../../archive/post-phase/hardening/issues.md) (18 items),
|
Reconciled from five ledgers — [hardening/issues.md](../../archive/post-phase/hardening/issues.md) (18 items),
|
||||||
[for-backend.md](../../archive/build-chain/working-context/frontend/requests/for-backend.md) (67 REQs), 53
|
[for-backend.md](../../archive/build-chain/working-context/frontend/requests/for-backend.md) (67 REQs), 53
|
||||||
@@ -251,7 +251,7 @@ log is in [decisions.md](decisions.md). The business-area overlay is in [impleme
|
|||||||
| BL-216 | contract | No typed address/variant snapshot objects — `BookingDetailDto` still carries opaque JSON strings, forcing the client's defensive multi-key parse in three separate places. | REQ-045, ui-phase-6 follow-up 2, ui-phase-7 follow-up 2 | open | booking-lifecycle-evv, checkout-and-payment |
|
| BL-216 | contract | No typed address/variant snapshot objects — `BookingDetailDto` still carries opaque JSON strings, forcing the client's defensive multi-key parse in three separate places. | REQ-045, ui-phase-6 follow-up 2, ui-phase-7 follow-up 2 | open | booking-lifecycle-evv, checkout-and-payment |
|
||||||
| BL-217 | client | Two admin-only multi-field dialogs (`GrantRoleDialog` in `admin/roles`, `PreviewBatchDialog` in `admin/payouts`) still hold raw `useState` instead of react-hook-form — the only genuine residue of the app-wide form migration. | iteration-2 #7 | open | admin-backoffice |
|
| BL-217 | client | Two admin-only multi-field dialogs (`GrantRoleDialog` in `admin/roles`, `PreviewBatchDialog` in `admin/payouts`) still hold raw `useState` instead of react-hook-form — the only genuine residue of the app-wide form migration. | iteration-2 #7 | open | admin-backoffice |
|
||||||
| BL-218 | client | `H-01` residue: the auth-gate root-cause fix landed, but `client/middleware.ts` was never migrated to Next 16's `src/proxy.ts` as the original fix prescribed, and `outputFileTracingRoot` was never added alongside `turbopack.root` — a production build may still warn from the stray root-level lockfile. | H-01 | open | auth-login-otp |
|
| BL-218 | client | `H-01` residue: the auth-gate root-cause fix landed, but `client/middleware.ts` was never migrated to Next 16's `src/proxy.ts` as the original fix prescribed, and `outputFileTracingRoot` was never added alongside `turbopack.root` — a production build may still warn from the stray root-level lockfile. | H-01 | open | auth-login-otp |
|
||||||
| BL-219 | docs | Rename the `SET_VIA_USER_SECRETS_OR_ENV` placeholder sentinel now that `user-secrets` is confirmedly removed — filed by phase 2 with an exact 7-file worklist (`appsettings.*.json`, `StartupSecretsGuard.PlaceholderMarkers`, `.githooks/pre-commit`, `.githooks/README.md`, `StartupSecretsGuardTests.cs`, `docs/rules/shared/git-and-gates.md`, `docs/rules/server/structure.md`). Optional cleanup, not urgent — the name is a load-bearing sentinel and the mechanism is already documented correctly elsewhere. | C-2 (open-contradictions.md) | deferred (trigger: none — cleanup-of-convenience) | — |
|
| BL-219 | docs | Rename the `SET_VIA_USER_SECRETS_OR_ENV` placeholder sentinel now that `user-secrets` is confirmedly removed — filed by phase 2 with a worklist (`appsettings.*.json`, `StartupSecretsGuard.PlaceholderMarkers`, `StartupSecretsGuardTests.cs`, `docs/rules/server/structure.md`); the two `.githooks/*` files in the original worklist no longer exist (phase 7 dropped the pre-commit hook — see [decisions.md](decisions.md)). Optional cleanup, not urgent — the name is a load-bearing sentinel and the mechanism is already documented correctly elsewhere. | C-2 (open-contradictions.md) | deferred (trigger: none — cleanup-of-convenience) | — |
|
||||||
|
|
||||||
## Deferred (43)
|
## Deferred (43)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Decisions — the distilled engineering decision log
|
# Decisions — the distilled engineering decision log
|
||||||
|
|
||||||
> Last verified: 2026-08-02 against commit `b876490`.
|
> Last verified: 2026-08-02 against commit `51e86a1`.
|
||||||
|
|
||||||
Non-obvious decisions with a reason, extracted from `dev/`'s ~3MB of build history so they survive
|
Non-obvious decisions with a reason, extracted from `dev/`'s ~3MB of build history so they survive
|
||||||
`dev/`'s move to `archive/` in phase 6. **`product/` wins for business rules** — this file is for
|
`dev/`'s move to `archive/` in phase 6. **`product/` wins for business rules** — this file is for
|
||||||
@@ -91,10 +91,20 @@ Decided ui-phase-13, chosen specifically so `/` stays a stable, shareable, index
|
|||||||
## Engineering decisions
|
## Engineering decisions
|
||||||
|
|
||||||
**Phase 2 → Phase 4 handoff, "the rename is filed."** The placeholder secret sentinel
|
**Phase 2 → Phase 4 handoff, "the rename is filed."** The placeholder secret sentinel
|
||||||
`SET_VIA_USER_SECRETS_OR_ENV` keeps its name — renaming it needs a server-code + git-hook + test change that
|
`SET_VIA_USER_SECRETS_OR_ENV` keeps its name — renaming it needs a server-code + test change that is out of
|
||||||
is out of a documentation phase's scope, and the string is load-bearing across 7 live files. Filed as
|
a documentation phase's scope, and the string is load-bearing across several live files. Filed as
|
||||||
[backlog.md](backlog.md) BL-219, deferred (cleanup-of-convenience, no urgency).
|
[backlog.md](backlog.md) BL-219, deferred (cleanup-of-convenience, no urgency).
|
||||||
|
|
||||||
|
**Phase 7 dropped the pre-commit secret-scan hook — MVP stage, no need for it yet.** `.githooks/pre-commit`
|
||||||
|
(the `qw123321`/private-key/AWS-key/SQL-host/connection-string scan) and `.githooks/README.md` were deleted
|
||||||
|
outright, along with every doc reference to them (root `CLAUDE.md`'s repo-layout table and quick start,
|
||||||
|
[git-and-gates.md](../rules/shared/git-and-gates.md), [documentation.md](../rules/documentation.md),
|
||||||
|
[identity.md](../rules/server/identity.md), [config-matrix.md](../integration/config-matrix.md),
|
||||||
|
[DEPLOY.md](../../DEPLOY.md)). The underlying trade this repo already made — committed live credentials,
|
||||||
|
config in files not a secret store (root [CLAUDE.md](../../CLAUDE.md) §6) — is unchanged; this only removes
|
||||||
|
the local mechanical backstop against a *new* leak. Revisit before onboarding real users, alongside the
|
||||||
|
credential rotation already required by that trade.
|
||||||
|
|
||||||
**Hardening ledger re-verification (C-10) confirms the ledger was right to distrust its own checkboxes.**
|
**Hardening ledger re-verification (C-10) confirms the ledger was right to distrust its own checkboxes.**
|
||||||
All 18 hardening items were re-traced against `b876490` rather than trusted as-filed: 3 were already fixed
|
All 18 hardening items were re-traced against `b876490` rather than trusted as-filed: 3 were already fixed
|
||||||
(client-only, landed in the "manual improvement" commits well before this doc chain started), 4 are
|
(client-only, landed in the "manual improvement" commits well before this doc chain started), 4 are
|
||||||
|
|||||||
Reference in New Issue
Block a user