Commit Graph
18 Commits
Author SHA1 Message Date
ehoandClaude Opus 5 edd20c06df docs(backlog): mark RB-23 done after merge
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 19:02:09 +02:00
ehoandClaude Opus 5 d0fda08bcc fix(brief): make GET /brief a pure query, 404 when absent (RB-23)
GET /brief allocated a row on first call (BriefStore.GetOrCreate) — the
one endpoint in the backend where a read performed a persisted write.
The FE retries GETs automatically, so a transient failure could enter
the create path more than once; a lock prevented a duplicate row, but
the safety depended on the lock, not on the endpoint being a query.

Split GetOrCreate into Get (a pure query) and the already-existing
ResetAndCreate (POST /brief/reset owns creation). GET /brief now 404s
when the owner has no brief yet. GET /brief/preview used GetOrCreate
too, so it gets the same Get + 404 treatment, forced by the split.

RB-22 already made BriefStore.load() on the FE tolerate a 404 by
calling reset() once; this ticket is what makes that branch live.

Updated the brief/preview/org-template backend tests that assumed
GET seeded a brief on first call to create one explicitly first, and
added a test that GET 404s and writes no row without the fix (verified
red beforehand). Regenerated the API client (npm run gen:api).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 19:01:06 +02:00
eho 05dff974bf Merge RB-22 — tolerate a 404 on GET /brief with a one-shot reset
CQ-007 expand half. BriefStore.load() treats a 404 as 'no brief yet' and calls
the existing reset() command once. load()'s error channel becomes the
BriefLoadFailure union, because runResult folds the HTTP status away and the
store needs it. Today's backend never 404s, so the branch is a no-op until
RB-23 lands the contract half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

# Conflicts:
#	docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md
#	libs/shared/docs/behaviour-spec.mdx
2026-08-27 18:44:20 +02:00
ehoandClaude Opus 5 7a29f5facc feat(brief): tolerate a 404 on GET /brief with a one-shot reset (RB-22)
BriefStore.load() now treats a 404 from GET /brief as "no brief exists
yet" and calls the existing reset() command once, instead of showing
the generic load-failed error. BriefAdapter.load() gains a
BriefLoadFailure error channel (notFound | error) so the store can
tell a 404 apart from every other failure; every other adapter method
stays on runSubmit, unchanged.

The once-only bound is a field on the store, not a comment: a second
404 (from a later load() call) always falls through to the ordinary
error path, and the recovery path never calls load() again, so no
loop can form.

This is the expand half of CQ-007's split (04-cqrs-light.md). Today's
backend never 404s GET /brief, so the new branch is dead code until
RB-23 (the backend contract half) ships in a later merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 18:43:27 +02:00
eho 6c5c4cb249 Merge RB-20 — route cancel and delete through runSubmit, surface the error
CQ-002: ApplicationsStore.cancel and AdminCasesStore.delete reached the raw
ApiClient and swallowed the failure in a bare catch, so a failed cancel made the
row reappear with no message. Both now fold through runSubmit and expose
lastError, which the two pages render.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

# Conflicts:
#	docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md
#	libs/shared/docs/behaviour-spec.mdx
2026-08-27 18:33:38 +02:00
eho 9666790d65 Merge RB-18 — key IdempotencyStore on caller plus idem key
BIO-018: the store was a process-global dictionary keyed on the client-supplied
Idempotency-Key alone, so one caller could replay another caller's key and
receive their cached response. The key is now scoped with the caller SubjectId.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

# Conflicts:
#	docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md
#	libs/shared/docs/behaviour-spec.mdx
2026-08-27 18:33:12 +02:00
ehoandClaude Opus 5 7def4a7552 fix(ssp): route cancel/delete through runSubmit, surface the error (RB-20)
ApplicationsStore.cancel and AdminCasesStore.delete rolled an optimistic
write back on failure but showed no message — a bare catch with no
Result and no error channel (CQ-002). Both now call runSubmit and set a
lastError signal on failure, mirroring createSubmitChangeRequest in the
same folder. Each page renders the error with the existing app-alert
atom, the same pattern brief.page.ts already uses for lastError.

Added a spec file for ApplicationsStore (none existed) and extended
AdminCasesStore's spec, each asserting the rollback AND the surfaced
error. Verified both new assertions fail without the fix (an Edit
undo/redo of the store method, not git checkout).

Regenerated libs/shared/docs/behaviour-spec.mdx (gen:behaviour-spec) to
pick up the new/renamed test names. Marked RB-20 done in 99-backlog.md
and recorded the change in implementation/rb-20.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 18:32:51 +02:00
ehoandClaude Opus 5 0adf831fb9 Merge RB-21 — extract the read half of createDraftSync into find-concept.ts
CQ-001: createDraftSync was the longest function in the repo and owned three
query paths next to its write path. findConcept and loadConcept are now free
functions that take the adapter, so they have a direct spec without TestBed.
The closure state (id, ensuring, resumeGate) stays where it was, because the
coupling is load-bearing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 18:29:20 +02:00
ehoandClaude Opus 5 25a5d415a5 docs(adr): land ADR-C-001, ADR-C-003, ADR-C-007 and ADR-C-009
The architect approved the four ADR-fix tickets. All four change what the
architecture documents claim. No code changes.

ADR-0001, ADR-C-001: the worked example claimed the POC has no real backend.
It rewrites against `backend/src/BigRegister.Api`. Every path it named is
repointed. The out-of-scope list drops two discharged bullets: 33 `parse*`
boundaries exist, and `npm run gen:api` is real.

ADR-0001, ADR-C-003: a new section states that the generated client is the wire
contract. A hand-written `contracts/*.dto.ts` is the exception for two cases
only. The four survivors stay, because NSwag emits every property as optional
and flattens `RegistrationStatusDto` into five optional strings. The `parse*`
trust boundary stays mandatory, because a generated type is a compile-time
claim about the wire and not a runtime guarantee.

ADR-0003, ADR-C-007: four paths moved in WP-67 and are repointed. Point 4 kept
the principle and changed its example to `skeleton` and `spinner`. Two of its
claims were false and the amendment says so: `app-alert` wraps the vendored
`.feedback` classes, and `site-header` composes the vendored `.titlebar`.

ADR-0004, ADR-C-009: the exception section states a four-part test instead of
one named exception. `OrgTemplateStore` and `FeatureFlagStore` both pass it. RB-07
gated this ticket, because clause 4 needs an audited allow path. RB-07 landed
that, so the ADR does not ratify a control that the code lacks.

Three tickets need a matching CLAUDE.md correction in the same diff. CLAUDE.md
section 2 loses the false `alert` example. Section 4 gets the generated-client
rule and the four-part test.

Two findings were wrong. ADR-C-001 asked to keep an out-of-scope bullet that
reads "SessionStore is in-memory". The session persists to `localStorage` now,
so the bullet covers multi-tab sync only. ADR-C-007 flagged one half of point 4
and missed that the other half is equally false.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 18:29:05 +02:00
ehoandClaude Opus 5 4631556e68 fix(backend): key IdempotencyStore on caller + idem key (RB-18)
IdempotencyStore keyed a replayed submission on the raw Idempotency-Key
header alone. Two different callers who send the same header value
shared one cache slot: the second caller received the first caller's
cached reference instead of running its own submission.

Program.cs now composes the key as "{SubjectId}:{idemKey}" in the
Submit helper, so the cache is scoped per caller. Add a test that
proves a caller cannot replay another caller's idempotency key and
receive their cached result.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 18:27:52 +02:00
ehoandClaude Opus 5 d518a1466c refactor(registratie): extract the read half of createDraftSync (RB-21)
createDraftSync mixed a read path (findConcept, load, the read half of
resume) with its write path (ensureId, flush, submit, reset) in one
187-line function -- CQ-001's finding. Move findConcept and loadConcept
into a new application/find-concept.ts as free functions that take the
adapter, so they get a direct spec with no Angular TestBed.

createDraftSync keeps the closure state (id, ensuring, resumeGate) and
the whole write path unchanged -- this is a move, not a redesign. The
resumeGate coupling that lets the write path wait for the read path
stays exactly where it was.

createDraftSync shrinks from 187 to 169 lines. draft-sync.spec.ts is
unchanged -- it never called resume()/load() directly, and its 409
recovery test for submit() still exercises the extracted findConcept
through ensureId's catch branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 18:23:13 +02:00
ehoandClaude Opus 5 bc5b2c4b2d docs(backlog): CD batch 3 complete
All six merged, gate green (14 steps, backend 260/260). Records the three
tickets that could not be built as written — RB-12's wrapper/public binary
does not fit the route table, RB-14's command exits 0 on a High advisory, and
RB-15 needed a third environment name because RB-09 makes Production fail to
boot — plus RB-13's measured duplication drop (168 -> 32 lines per side).

Adds a section on dispatching implementation agents. Four of six agent-runs
were handed a worktree branched from a stale ancestor; batch 3 was three for
three. That, the background-task parking, and the git-checkout-destroys-work
trap are all cheap to prevent in the prompt and expensive to discover.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 17:01:41 +02:00
ehoandClaude Opus 5 988612cd7e docs(backlog): CD batch 2 complete
All five tickets merged and green on the fixed gate (13/13 steps, exit 0).
RB-07 unblocks ADR-C-009; RB-09 unblocks RB-13 in batch 3.

Adds a "Gate integrity" section recording that every earlier "ci green" in
this file predates the ci-local.sh errexit fix and is weaker than it reads.
Batch 1 has not been re-verified under the honest gate, and the note says so
rather than leaving a reader to assume it was.

Also records what batch 2 leaves open: RB-01's residual is NOT solved by
RB-09 (the upload-content link is still a plain browser navigation with no
credential), and a non-Development non-Production environment fails fast at
GetRequiredService rather than at RB-09's deliberate throw.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:37:20 +02:00
ehoandClaude Opus 5 c6bc6dd4c3 docs(backlog): RB-11 done
Also records what RB-11 turned up: BIO-012 was factually wrong that the
proefbrief error mapping was already a separate function (it was inlined in a
try/catch), and the step-up literal is still a literal, moved one layer up to
the only caller rather than eliminated — BIO-006(c) stays a production gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:29:50 +02:00
ehoandClaude Opus 5 b5432d2c63 docs(backlog): RB-07 and RB-10 done, batch 2 in progress
RB-07 unblocks signing ADR-C-009 (its clause 4, "writes are
admin-capability-gated and audited", now holds) and closes CQ-004's
outstanding half. RB-10 landed parseStoredSession twice, once per app,
deliberately — recorded so a later reader does not file it as duplication.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:15:43 +02:00
ehoandClaude Opus 5 d682f91b54 docs(backlog): lift the HALT, record CD batch 1 as complete
The operator approved 99-backlog.md, so Phase 3 started. RB-01..RB-06 are
marked done in the table, _status.md gains a Phase 3 section tracking all six
batches, and the halt notice is replaced by what batch 1 leaves for later
batches to pick up:

- RB-01's residual is RB-09's problem — the document-content endpoint is
  reached by a plain browser navigation with no identity header, so it
  resolves to the seeded citizen and works only because one citizen owns
  every document in the POC. That is BIO-002.
- Pii.MaskTail now lives in Domain/People/Pii.cs; RB-11 should use it rather
  than hand-rolling a second masker.
- RB-06 additionally deleted SubmissionRules.RejectRegistratie, which its row
  did not ask for.

Also records the standing OpenZaakIntegrationTests failure, which needs a
live container and is unrelated to any of these tickets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 11:06:10 +02:00
ehoandClaude Opus 5 176e5baef8 docs: BIO2 compliance pass + consolidated backlog (agents 07, 08)
Completes the pipeline's analysis phase. Agent 07 (BIO2/ISO 27002:2022,
control set stated as an assumption since none was supplied) produced 20
findings — 12 "defect now", 8 "production gate" — and agent 08 consolidated
all 47 findings across 00/02/04/06/07 into 33 tickets, 5 ADR-fixes and a
release checklist.

Two findings are live defects rather than refactoring candidates, both
verified directly:

- RB-01/BIO-004: GET /uploads/{documentId}/content takes only (string
  documentId) — no HttpContext, so no authorization is possible. It streams
  diploma and identity scans, protected by GUID unguessability alone, while
  DELETE on the same resource is owner-scoped.
- RB-02/BIO-008: Program.cs:674 concatenates the caller's BSN into the authz
  audit Resource column, which is persisted to SQLite and rendered by the
  admin audit page. Four doc comments claim that store holds no PII; the test
  cited as enforcing it asserts on column names, so a BSN inside a column
  called Resource is invisible to it.

07 also answered the handoff from 06: in a production behandelportal build no
X-Medewerker is sent, so StubIdentityProvider returns the seeded citizen. It
fails closed on backoffice capabilities but open on citizen-scoped ones,
including CanRevealBigNummer. Root cause is IIdentityProvider.Resolve
returning a non-nullable CallerIdentity — the interface cannot express "no
identity", so any provider must invent one.

08's gate was relaxed from all-seven to the four agents that ran; _status.md
records why 01/03/05 were skipped, and the backlog carries a "Coverage"
note naming what those skips leave unowned. It caught two errors in the
orchestrator's handoff: CQ-002 is not fixed (ApplicationsStore.cancel and
AdminCasesStore.delete still swallow errors -> RB-20), and CQ-004 shipped
with half its compliance criterion unmet (PUT /admin/flags/{key} writes no
audit row -> RB-07, which blocks signing ADR-C-009).

Both agents preserved a "verified clean — do not fix" list, so a later pass
does not re-spend effort on the controls that already hold.

Consolidation halted for human approval per its spec. No source file changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 09:52:13 +02:00
ehoandClaude Opus 5 664a43bf2d docs: refactoring-backlog workspace — baseline + 3 Phase 1 agents
Runs the multi-agent refactoring-backlog pipeline in docs/project/
refactor-backlog-setup/ up to and including three of the seven Phase 1
agents.

00-baseline.md establishes the metrics every later agent must cite, using
only tooling already in the repo (vitest lcov, coverlet cobertura, ESLint's
core `complexity` rule at threshold 0 for a full distribution, depcruise
--metrics). Duplication and C# complexity had no tooling, so
tools/baseline-scan.mjs adds a deterministic ~200-line text scan rather
than a new dependency; the approximations are labelled as such.

Headline: FE 75.1% line coverage but only over the 98 of 220 source files a
spec loads; BE 97.6% line / 79.6% branch; 0 layering violations; 7.1%
duplication; 25 of 2085 TS functions over CC 10.

Then 02-testability, 04-cqrs-light and 06-adr-conformance (27 findings).
01/03/05 were skipped deliberately — the baseline shows little for them to
find; 07 (BIO2) and 08 (consolidation) are still open.

Each agent corrected a baseline observation of mine, and in every case the
error was in something derived rather than measured:

- BL-007 counted ~13 adapter "mutations" from the `runSubmit` helper name;
  5 of those call sites are reads. It also missed 3 real mutations that
  reach the raw ApiClient and never return a Result.
- BL-002 diagnosed the 100%-duplicated auth folders as ADR-0002's
  divergence prediction failing. It never had a chance to fail: §3's
  `Principal` union was never built.
- BL-004 named libs/shared/domain and libs/beheer/contracts as coverage
  gaps; both are pure type declarations where 0% is unimprovable.

All three corrections are recorded inline in 00-baseline.md §10, so agent
08 does not inherit the bad numbers.

.prettierignore excludes the agent prompt directories — reflowing their
markdown would edit the prompt text itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 16:44:32 +02:00