Repository navigation
chore(memory): promote strong-fit authentication drafts from memory-pipeline for review - #131
Conversation
1979c05 to
6a2742a
Compare
sugat009
left a comment
There was a problem hiding this comment.
Requesting changes, though to be clear the fix is upstream and not something to patch by hand here. This seeder faithfully promotes what the distillation pipeline generated, so nothing is wrong with the promotion itself. The problem is in the drafts the pipeline produced, and I verified it against the live medic/cht-core API for every draft in this PR.
The defect: issueNumber / issueUrl name the merge PR, not the resolved issue
One precise note up front, because the intuitive version of this is not what is happening: issueNumber and issueUrl always agree with each other (the URL is literally /issues/<issueNumber>). The bug is that in 19 of 39 drafts here, that shared number is a pull request, so /issues/N silently redirects to /pull/N. The real resolved issue survives only in the PR-title slug. For example:
8675-feat6530-add-rate-limiting...storesissueNumber: 8675/issueUrl: .../issues/8675, but 8675 is the merge PR ("feat(#6530): add rate limiting for authentication requests"). The real issue is #6530 (closed): "Add rate limiting to authentication endpoints".10414-fix6784-safari-unsupported-browser...storesissueNumber: 10414/issueUrl: .../issues/10414, but 10414 is the merge PR ("fix(#6784): safari unsupported browser message in login page"). The real issue is #6784 (closed): "Alert Safari users CHT doesn't support their browser".
This is the same scraper behaviour flagged on #121, and it is pipeline-wide: across the four clean seeders, 60 of 107 drafts are affected.
Duplicates in this PR. Because each draft is keyed by its PR, some resolved issues appear more than once:
- Issue #8868 is promoted twice (
8924,8933). - Three issues are also promoted in the contacts seeder #132 (cross-domain duplicates): #9835, #9065, #6543.
Also. One draft (8843) comes from a PR titled feat(na): ... with no linked issue at all, so there is no real issue for it to reference.
Why request-changes rather than merge-and-fix-later
This corpus is the agent's memory of resolved issues (consumed by the Context Analysis Agent, see #135). A reference that resolves to a PR instead of the issue is simply wrong data, and it specifically defeats #135's planned de-duplication by issue id, since the "id" ends up being the PR id. Regenerating the drafts after the upstream fix will rewrite most of these files anyway, so merging now would churn the corpus twice.
Suggested fix (at the source, not file by file)
- Fix the distiller/scraper to take the resolved issue from the
type(#N):PR title (the filename slug already extracts it correctly), and keep the PR number insource_prwhere it belongs. - Regenerate and re-promote this domain's drafts.
- Add de-duplication by real issue id (also a #135 acceptance item).
Open to discussing the approach. Once the drafts carry the real issue references, this should be a quick re-review.
Per sugat009's review on #132: collapse 10 duplicate clusters (10036, 10038, 10037, 8985, 9065, 9241, 9835, 9264, 9426, 9601) to one memory per issue, folding each PR's distinct content into the canonical with a source_prs[] provenance array (schema.json gains the optional source_prs definition, byte-identical to PR #138's). 15 collapsed files removed. Suspect 9311 verified against cht-core: its PR body explicitly closes issue #9241 ("Create API endpoint for getting people"), so the stored key was correct; folded into the 9295 canonical as a second source PR. Cross-domain dedup: issue #6543 is canonically authentication (multi- facility user permissions), so 9094 (webapp display facet) is removed here and will be folded into the authentication memory on #131. Also: scrubbed classifier/reviewer process narrative from prose, backfilled related_issues for the 9193/9237-9242 datasource family, fixed the #9241 title drift. All 43 PR-to-issue mappings verified against the live cht-core API (0 mismatches); validate-schema 92/92. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ly, authentication) Deterministic relink via #129's relink-issues tool: drafts whose identity keys recorded the merge PR now point at the resolved issue. Frontmatter id/issueNumber/issueUrl lines only; bodies untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ation) Per sugat009's review on #131: collapse the #8868 backport pair (8924+8933) into one memory with source_prs[]; drop 8843 (feat(na), closes no tracked issue — #136 skip-and-flag policy). Cross-domain dedup: fold the webapp display facet (PR #9094, moved from the contacts seeder) into the #6543 canonical here (source_prs 9094 + 9126); drop 9204/9205/10222 whose issues (#9203/#9065/#9835) are canonically owned by the contacts corpus — their PR refs get recorded there in a follow-up commit on #132. Suspect 9955 verified against cht-core: PR body explicitly closes #9735 (the SSO epic), so the stored key was already correct; the filename token 9760 is a stale title scope (issue #9760 is owned by 9800's file). Also: backfill related_issues across the SSO issue family (epic 9735 + sub-issues), scrub reviewer/process narrative from 19 files, add the optional source_prs schema definition (identical to #138 and #132). All 39 PR-to-issue mappings verified against the live cht-core API (0 mismatches); validate-schema 98/98; no duplicate issueNumbers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
d2f9344 to
418c890
Compare
…cts) Companion to the authentication seeder (#131) cross-domain dedup: the auth branch drops its drafts for issues this corpus canonically owns, so their PR provenance is recorded here — #10222 (permission checks) on the #9835 memory, #9205 (offline-user endpoint gating) on the #9065 memory, and #9204 (admin-app facility_id backward compat) on the #9203 memory. Also: scrub remaining reviewer/process narrative and classifier seed references from 19 files (content unchanged, attribution and review chronology removed), and quote the #9065 memory's source_prs entries for YAML consistency. validate-schema 92/92; no duplicate issueNumbers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Ready for review. @sugat009 |
Per sugat009's review on #120: collapse the 3 duplicate clusters to one memory per issue with source_prs[] — backport pairs 10068 (10073+10082) and 10225 (10230+10243), and the 10802 sibling fixes (10803+10811) folded into the existing issue-keyed memory; 9559 likewise folded into the existing 9467 RapidPro memory. 5 collapsed files removed. Corpus fix for 10729: grounded the memory in the merged PR #10730 (source_prs added; Solution/Testing now attribute the shipped fix). Domain fit: 8717 (conversation-UI navigation to the contact page) honestly re-annotated domainFit: weak; 10853/10477 verified as genuine pipeline code (transitions, message-utils) and stay strong. Also: backfill related_issues (10442->10446 closing ref, 10729<->10802, 10802->10428), scrub reviewer/process narrative from 8 files, add the optional source_prs schema definition (identical to #138/#132/#131). All 17 PR-to-issue mappings verified against the live cht-core API (0 mismatches); validate-schema 76/76; no duplicate issueNumbers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ion) Companion to the tasks (#123) seeder's cross-domain dedup: this corpus canonically owns issue #6543 (multi-facility users), so the aggregate- targets facet from that seeder is recorded here — PR #9099 added to source_prs with a one-line account (aggregate targets gated off for multi-facility users). The canonical now carries all three facets: webapp display (#9094), v3 users API + authorization (#9126), and aggregate-targets gating (#9099). validate-schema 98/98; no duplicate issueNumbers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
sugat009
left a comment
There was a problem hiding this comment.
Content re-review. Identity/dedup/schema clean. This PR has the most distillation-fidelity issues — five drafts name permissions/handlers/fields that do not exist, clustered in the SSO/authorization work (all inline). An agent grepping the corpus would hit non-existent symbols, so these are the priority.
- nitpick (leakage):
9731,10599,10994,9128Domain Rationale scaffolding ("seed #2", "per the taxonomy", …); strip. - nitpick:
10502related_issuesomits in-batch#6784; backfill.
Cross-cutting (whole promote batch, not blocking this PR alone): filenames still encode the PR number while frontmatter is issue-keyed, and two draft schemas coexist (machine-distilled vs hand-authored) — worth a cleanup pass. Full detail + re-runnable Verify commands are in my consolidated review notes.
| domainFit: strong | ||
| issueNumber: 8730 | ||
| issueUrl: https://github.com/medic/cht-core/issues/8730 | ||
| title: Fix permission checks for contact FAB and actionbar by adding a separate con_create_people check |
There was a problem hiding this comment.
issue (accuracy, high): the draft uses the permission con_create_people (including in a tag), but the real permission is can_create_people (PR #8738 uses can_create_people/can_create_places throughout; grep con_create_people = 0). Replace everywhere.
|
|
||
| ## Solution | ||
|
|
||
| Added an OIDC SSO flow. A new openid-client.js service wraps the openid-client library to exchange the provider's authorization_code for a validated id_token. A new sso-login.js service maps the token claims to an existing CHT user and creates a session. A getOidc handler in login.js serves the GET /medic/login/oidc?code=... redirect-back endpoint (wired up in routing.js). When no matching CHT user is found, the user is redirected back to the login page with a localized helpful error message. Login UI (template, script, style) and translations across multiple languages (ar, bm, en, es, fr, hi, id, ne, sw) were updated for the SSO entry point and error messaging. |
There was a problem hiding this comment.
issue (accuracy): names a getOidc handler in login.js, but the handler is oidcLogin (plus oidcAuthorize); grep getOidc = 0 in api/src. Rename.
| issueUrl: https://github.com/medic/cht-core/issues/9764 | ||
| title: Block OIDC-configured users from authenticating via the token_login flow | ||
| lastUpdated: '2026-06-22' | ||
| summary: Users with an oidc_provider set could still complete the token_login flow and receive a valid session, bypassing the intended OIDC-only authentication path. The token login flow now rejects such users before any session is issued. |
There was a problem hiding this comment.
issue (accuracy): says the guard inspects the user's oidc_provider field, but the user field is oidc (a truthy flag); oidc_provider is the app_settings provider config (holding client_id), not a user property. Correct the field reference.
| domainFit: strong | ||
| issueNumber: 9761 | ||
| issueUrl: https://github.com/medic/cht-core/issues/9761 | ||
| title: Add support for creating/editing SSO (OIDC) users in the admin app via an oidc_provider toggle |
There was a problem hiding this comment.
issue (accuracy): says a "checkbox toggle sets the user's oidc_provider", but the PR adds a text input (<input id="sso-login" type="text">) that sets oidc_username; oidc_provider is only the app_settings gate. Correct the control and field.
|
|
||
| ## Solution | ||
|
|
||
| Before evaluating any permission, filter the user's effective roles to only those still present in app_settings.roles. Added filterRolesByConfigured() in cht-datasource and an optional chtRolesSettings param to hasPermissions()/hasAnyPermission(); user-management hasPermission() reads config.get('roles') and filters; api hasPermission() applies the same filtering; webapp cht-datasource.service.ts now extracts and forwards roles from app settings alongside permissions. An empty or absent roles config falls back to no filtering for backwards compatibility. The duplicated logic was consolidated toward cht-datasource. |
There was a problem hiding this comment.
issue (accuracy): says a chtRolesSettings param was added to hasPermissions()/hasAnyPermission(), but those became curried (ctx) => (permissions, userRoles) and read roles internally via ctx.settings.getAll() (filterRolesByConfigured). chtRolesSettings exists only on that internal helper, not on the public functions. Correct the API shape.
…ne for review (#120) * chore(memory): promote strong-fit messaging drafts for review * fix(#135): relink id/issueNumber/issueUrl to real issues (metadata-only, messaging) Deterministic relink via #129's relink-issues tool: drafts whose identity keys recorded the merge PR now point at the resolved issue. Frontmatter id/issueNumber/issueUrl lines only; bodies untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(#135): collapse duplicate clusters + review fixes (messaging) Per sugat009's review on #120: collapse the 3 duplicate clusters to one memory per issue with source_prs[] — backport pairs 10068 (10073+10082) and 10225 (10230+10243), and the 10802 sibling fixes (10803+10811) folded into the existing issue-keyed memory; 9559 likewise folded into the existing 9467 RapidPro memory. 5 collapsed files removed. Corpus fix for 10729: grounded the memory in the merged PR #10730 (source_prs added; Solution/Testing now attribute the shipped fix). Domain fit: 8717 (conversation-UI navigation to the contact page) honestly re-annotated domainFit: weak; 10853/10477 verified as genuine pipeline code (transitions, message-utils) and stay strong. Also: backfill related_issues (10442->10446 closing ref, 10729<->10802, 10802->10428), scrub reviewer/process narrative from 8 files, add the optional source_prs schema definition (identical to #138/#132/#131). All 17 PR-to-issue mappings verified against the live cht-core API (0 mismatches); validate-schema 76/76; no duplicate issueNumbers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(#136): ground messaging drafts against cht-core source Every factual claim in the seventeen drafts on this branch was checked against the cht-core commit it was distilled from — word-bounded `git grep`, `diff-tree --name-status`, `ls-tree`, and reading the hunks. 189 claims confirmed; 63 corrections applied. Each was additionally re-checked by a second pass instructed to refute it, and five proposed corrections were discarded that way rather than shipped. The recurring defect is not a wrong identifier — it is a correct identifier wrapped in a wrong story: 10073 described an inbound Express/`req.body` double-parse. The file has no Express handler and no `req` at all; it is outbound-only. The real bug was `sendMessage` re-parsing the response body of its own POST, which `@medic/couch-request` had already parsed, so every send silently produced no state change. Title, summary, problem, root cause and Code Patterns all restated accordingly, and the e2e spec described as added was modified. 10802 used `task.status` where the field is `task.state`, and presented #10811 as a sibling guard when its commit body reads "(cherry picked from commit 6a5867b)" — a byte-identical backport of #10803. The fabricated `isDue()`/`due_date` snippet is removed. 10497 read `resolveMany` as fanning out to several recipients when it returns the first that resolves. 4278 and 8492 stated the opposite of the code in Design Choices, and asserted test coverage absent from their diffs. 9364 generalised a narrow fix. 8717 described only additions when the commit renamed a spec away. 9467 named a member that does not exist on the object at the cited line. Also corrected across the branch: file lists that lost A/M/D status, test paths missing the `.spec` segment, and classifier scaffolding in 10868's rationale. Held back deliberately: five proposed corrections that did not survive re-checking, including one whose replacement would have relocated a throw to a line unreachable in the failing scenario. Twenty-four claims remain unverifiable — chiefly the legacy drafts that carry no source commit and the two whose PR numbers appear nowhere in cht-core history. Nothing was edited on the strength of a claim that could not be checked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(#136): relink two hand-authored identities + anchors, cross-link the due_tasks pair (messaging) - 4278 and 8492 keyed their identity to PR numbers — the round-1 defect class surviving in two pre-existing hand-authored drafts the relink tool never touched (it only reads machine frontmatter). Both prose bodies already named the real issues: 4278 -> #3738 (PR #4278's body: 'Issue: #3738'), 8492 -> #8414 (PR title 'fix(#8414): sms gateway test flakiness'). id/issueNumber/issueUrl relinked accordingly. - Both drafts also gain machine anchors (source_prs + the PR merge commit as source_sha: d88f2e256, 2c740238) so claim grounding can check them at their own trees instead of degrading to master-fallback guesses — d88f2e256's tree is where the draft's 2018-era paths are real, and 2c740238 touches exactly the two files the 8492 draft names. - 10442 <-> 10802 both rework due_tasks.js state transitions but only 10802 carried the back-reference; related_issues on 10442 now links cht-core-10802, making the in-batch pair symmetric (the same class flagged on the configuration batch's 9696/9727). validate-schema: 76 passed, 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(#136): correct two residual 10802 claims caught by the anchored probes (messaging) Once the source_prs fallback anchored 10802 at 6a5867bb, two claims the July-27 grounding pass missed became checkable and failed: - 'Filter tasks by both due_date and status fields' — neither field exists; the real comparison is the computed due value (task.due || task.timestamp || doc.reported_date) plus the task.state guard. Same fabrication family as the review-2 inline, one bullet over. - 'Added a sentinel integration test (due-tasks.spec.js)' — the file pre-existed; 6a5867bb ADDS a 69-line case to it (M, not A). Also aligned two prose 'status' field references to 'state'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(#136): correct the RapidPro broadcasts endpoint spelling (9467, messaging) The draft named an 'api/v2/broadcast' endpoint; the service posts to '/api/v2/broadcasts.json' (api/src/services/rapidpro.js:92 at da4b50f7). Caught by the probes on the second anchored run — the claim only became checkable once the API resolver anchored this hand-authored draft. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(#136): round-3 review, the self-contradiction class, last PR-keys (messaging) All 16 inline items plus three contradictions review did not reach and the follow-ups agreed as in-scope. THE CLASS BEHIND MOST OF IT. The grounding pass corrected the sections that assert mechanism against code and left the interpretive ones asserting what it had just disproved, so several drafts told two stories. A coherence pass over all 22 drafts found six; review had named three. - 10073: Domain Rationale still located the bug inbound while Root Cause and Code Patterns say the file is outbound-only and never sees req.body; the Related Issues gloss said 'request body'; techStack still listed express. All now agree the double-parse was of the send RESPONSE. - 10729: Design Choices claimed existing unit tests covered the functionality; the grounded Testing section says neither fix is covered. Bullet dropped. parseArray's mechanism narrowed to what smsparser.js actually does - getParser returns undefined for a non-string message or an unrecognized Muvuku code, not merely because def is null. - 4278: Problem opened with 'had no test coverage', which its own Root Cause refutes; the illustrative fence was a composite of two real helpers that appears nowhere (replaced with allMessageDocs verbatim); the invalid-content test claims are gone (the 365-line spec's only 'invalid|error' match is a fixture field 'errors: []'). - 10802 (not in review): one sentence said the fix landed on master and 5.2.x AND that 5.1.x is the only line carrying it. Reworded - each patch reaches a different set of lines. - 8717 (not in review): Solution credited 'navigation logic in sender.component.ts' while Code Patterns says it injects no Router and gained only two accessors; routing is the declarative routerLink. - 3406 (not in review): Code Patterns recommended compound view keys 'emit([task.state, when], val)' AND string keys instead of array keys. The PR did the latter - it changed emit([task.state, when]) to emit(task.state) so consumers can ask for several states in one request, keeping the due date in the value as sending_due_date. ACCURACY. 10802's Root Cause blamed an eventually-consistent view; the view is keyed ['scheduled', due] and cannot return an already-transitioned task. Replaced with the real mechanism: the view vouches for one task while updateScheduledTasks iterates every scheduled_task matching on due date alone. 10802's #10754 cross-reference is deleted (it is a cookie bug). 9467 loses the 62,000-message figure, which belongs to #10428's empty-message workaround, and time-scopes err?.statusCode (master now reads err?.status). 10497 no longer calls the review feedback stylistic - it included a normalizeRecipient redesign. DRIFT. 4278's four 2018-era paths (pre api/src, pre-wdio protractor tree) are time-scoped in one note; the polling pattern is the durable part. IDENTITY. The last five PR-keyed drafts are re-keyed to their real issues - 3406->3073, 4039->3627, 4374->4110, 6995->6532, 7105->6572 - each with source_pr/source_prs and the PR's merge commit as source_sha, closing the class at 7 of 7. 4374's reference to the re-keyed 4278 entry now points at #3738. Nine PRs cited in Related Issues as though they were issues are labelled 'PR #N'. Canonical source_pr added to the five drafts that carried only source_prs, per the schema's own wording. Gate: validate-schema 76 passed / 0 failed; verify-drafts --online 0 blocking / 0 warnings / 0 unverified; check-coherence 0 contradictions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(#136): keep the five hand-authored drafts out of this PR (messaging) Reverts 3406, 4039, 4374, 6995 and 7105 to their state on main. Anchoring them (last commit) made their claims checkable for the first time and they do not survive it: 19 ungrounded claims across the five, including seven fabricated metric names in 7105 (monitoring.messaging.outgoing.state and its .delivered/.failed/.total./.seven_days/.last_hundred siblings, plus monitoring.sentinel.backlog - none exist at its anchor), handleCallback / RAPIDPRO_URL / RAPIDPRO_TOKEN in 6995, three files 4374 names as touched that its backport commit never touched, and shared-libs/messaging in 4039. 8 drift hits and three contradictions sit on top of that. These are pre-existing defects that the re-key exposed rather than caused, but fixing them means substantially rewriting five 2017-2021 drafts - and 7105 may belong dropped rather than corrected, the way 11021 was on the configuration branch. That is its own review, not a rider on this one, and the reviewer had already scoped these as follow-ups outside this diff. So this PR goes back to exactly the 17 drafts under review plus schema.json. The re-key, the anchors, and 4374's now-stale reference to the re-keyed 4278 entry all move to a dedicated follow-up PR. Reverting 3406 also removes a contradiction this branch had introduced: the Code Patterns rewrite there was correct about the view (it emits msg.uuid and task.state, no compound key) but left Design Choices still claiming the PR emits both key shapes. Two fixes for the 17 that stay: - 8492: Problem blamed 'inconsistent message state setup in test factories' while its own Root Cause says the cause was shared mutable fixture state, NOT the factory failing to set a state. Same section-scoped pattern the reviewer identified; reworded to describe the symptom instead. - 9467: getOutgoingMessages is real but lives in api/src/services/messaging.js, which the draft never said. Naming it removes a misattribution a reader could draw and settles a probe artifact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(#136): bump 10853 lastUpdated (messaging) Changing the stamp is itself an edit, so the freshness check needs the final value, not the date of the content change that prompted it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(#136): last two in-scope findings from the final gate (messaging) - 8492: a second contradiction in the same draft. Root Cause says the bug was shared mutable fixture state, 'not the factory failing to set a state', while Design Choices credited the fix to 'proper state setup'. Reworded to what the fix actually buys: per-build task objects make the tests order-independent. - 9467: two sentences were phrased so that a prose aside became a code-shaped claim probed at the wrong tree. 'current master reads err?.status === 400' is true of master and false at this draft's anchor, so a symbol-in-file probe at the anchor refutes a correct sentence; and quoting a whole logger.error statement cannot survive a word-bounded grep. Both now name the property and the call site instead of embedding the expression, which is also easier to read. Both facts are unchanged and still verified: rapidpro.js:109 on master tests status, and the logger.error call is at rapidpro.js:110 at the anchor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(#136): three contradictions an independent audit found (messaging) An independent verification pass found that my own round-3 sweep had reproduced the very class it was fixing: correcting one section of a draft and leaving its siblings asserting the disproved story. 10442 - the worst of it, and mine. Problem was rewritten to the real code path; summary and Design Choices were not. Ground truth at 862f69a6^ is 'if (task.messages) { updatedTasks = true; utils.setTaskState(task, 'pending'); }' - a task WITH a messages array but an empty body was promoted to pending, only a task with no messages array at all sat in scheduled. So the summary's 'they sat in scheduled indefinitely' was false for half the cases, and Design Choices' 'deployments keep leaving such messages indefinitely scheduled' contradicted the Solution's own note that leaving them in scheduled 'is itself a change'. All four sections now describe one path, and Design Choices says what the default actually changes rather than implying continuity. 10442 Related Issues - #10446 is 'Dont send empty messages', not 'failed/ invalid scheduled messages were not being cleared'. The gloss restated #10428's concern. It survived the cross-reference audit because gloss and title share the word 'messages', which is a live demonstration that one shared content-word defeats word-disjointness in a messaging corpus. 10729 summary - 'causing fields to never match' is the pre-correction silent-failure story, which the draft's own Problem section refutes: the loop threw TypeError on item[0] and propagated out uncaught. Round 3 had rewritten the second half of that sentence and left the first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(#136): three narrative corrections from the independent audit (messaging) - 9467: 'the pre-existing logger.error call that was moved above the new branch' - nothing moved the log. The diff shows the '// ignore error, sending the message will be retried later' COMMENT moving below it while the logger.error line stays as unchanged context. The resulting position was right, the motion was not. - 8492: '#6995: RapidPro SMS gateway integration (related testing improvements)' - #6995 is 'Adds RapidPro as an SMS Gateway', a feature. The title gloss was fine; the relationship parenthetical was the mischaracterisation, and relationship parentheticals are exactly what the cross-reference audit exempts from checking. - 9022: 'Added/updated ... and a Sentinel integration spec' read as though the integration spec were new. diff-tree at 2e1a05ff17 shows only tests/e2e/default/reports/sms-messages.wdio-spec.js added; the integration spec was modified (+160/-90), as were the two unit specs. Now says which single file was added. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(#136): correct the revert rationale recorded in 620ea52 (messaging) 620ea52 justified keeping five hand-authored drafts out of this PR partly on "seven fabricated metric names in 7105". That claim is wrong and an independent audit caught it. All seven exist at 7105's anchor 67626fff, as nested keys of the monitoring response rather than as dotted tokens: git -C $CORE show 67626fff:api/src/services/monitoring.js \ | grep -nE "backlog:|total:|seven_days:|last_hundred:" # :293 backlog: sentinelBacklog # :325 total: jsonV1.messaging.outgoing.state <- the claimed rename # :326 seven_days: weeklyOutgoingMessageStatus # :327 last_hundred: lastHundredCounts and that PR is what adds failed/delivered to the v1 state counters (MESSAGE_QUEUE_STATUS_KEYS gains them; the parent had only due/scheduled/ muted). A dotted path like monitoring.messaging.outgoing.seven_days describes the JSON shape and cannot grep as one token - the same extraction artifact this branch correctly dismissed four times elsewhere (sms.clear_failing_schedules, smsparser.parse, nepal-doit-sms, getOutgoingMessages). I booked it as evidence instead. So the "19 ungrounded claims" figure was inflated by artifacts of that class. The decision to defer the five still holds, on evidence that does survive checking: - 6995 names RAPIDPRO_URL, RAPIDPRO_TOKEN and handleCallback; all three are zero-hit at e9e305d2, where credentials actually come from secureSettings.getCredentials('rapidpro:outgoing'). - 3406 contradicts itself: Code Patterns recommends compound view keys while also recommending string keys, and the PR emits only msg.uuid and task.state - no compound key at all. - 4374 names three files as touched that its backport commit does not touch; 4039 names shared-libs/messaging, absent at its anchor. Those are real defects in drafts that have never been anchored, and they still deserve their own review rather than a rider on this one. But the count was overstated, and the record should say so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(#136): 9022 Problem overstated how narrow the old gate was (messaging) Problem said the context was populated 'only when a shortcode id was present on the report's fields', which the draft's own Root Cause refutes: the gate was 'doc.patient_id || doc.fields?.patient_id', so a top-level id worked too. What it never consulted was the hydrated doc.patient, which is exactly what the fix adds ('|| doc.patient?.patient_id'). Problem now says that. Found by one coherence pass of three - the sampling caveat in practice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(#136): bump 9022 lastUpdated, which the previous commit missed (messaging) 1d54af9 rewrote 9022's Problem section and left the stamp at 2026-07-30, so the branch head failed the stale-timestamp check that 31c8d8c had documented hours earlier. Second time this cycle after 9407, and for the same reason both times: the stamp is set from the date of the change being made, then a later commit to the same file moves 'last edited' past it. 'Touch the file, stamp it today' is the only version of the rule that survives its own next commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* chore(memory): promote strong-fit contacts drafts for review
* fix(#135): relink id/issueNumber/issueUrl to real issues (metadata-only, contacts)
Deterministic relink via #129's relink-issues tool: drafts whose identity
keys recorded the merge PR now point at the resolved issue. Frontmatter
id/issueNumber/issueUrl lines only; bodies untouched. 31 files relinked,
1 flagged (9311 — resolved in the follow-up review commit).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(#135): collapse duplicate clusters + review fixes (contacts)
Per sugat009's review on #132: collapse 10 duplicate clusters (10036,
10038, 10037, 8985, 9065, 9241, 9835, 9264, 9426, 9601) to one memory
per issue, folding each PR's distinct content into the canonical with a
source_prs[] provenance array (schema.json gains the optional source_prs
definition, byte-identical to PR #138's). 15 collapsed files removed.
Suspect 9311 verified against cht-core: its PR body explicitly closes
issue #9241 ("Create API endpoint for getting people"), so the stored
key was correct; folded into the 9295 canonical as a second source PR.
Cross-domain dedup: issue #6543 is canonically authentication (multi-
facility user permissions), so 9094 (webapp display facet) is removed
here and will be folded into the authentication memory on #131.
Also: scrubbed classifier/reviewer process narrative from prose,
backfilled related_issues for the 9193/9237-9242 datasource family,
fixed the #9241 title drift. All 43 PR-to-issue mappings verified
against the live cht-core API (0 mismatches); validate-schema 92/92.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(#135): record cross-domain PR provenance + scrub narrative (contacts)
Companion to the authentication seeder (#131) cross-domain dedup: the
auth branch drops its drafts for issues this corpus canonically owns,
so their PR provenance is recorded here — #10222 (permission checks)
on the #9835 memory, #9205 (offline-user endpoint gating) on the #9065
memory, and #9204 (admin-app facility_id backward compat) on the #9203
memory.
Also: scrub remaining reviewer/process narrative and classifier seed
references from 19 files (content unchanged, attribution and review
chronology removed), and quote the #9065 memory's source_prs entries
for YAML consistency. validate-schema 92/92; no duplicate issueNumbers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(#135): cross-seeder dedup follow-through (contacts)
Companion to the forms (#122) and tasks (#123) seeders' cross-domain
dedup — this corpus canonically owns their issues, so the PR provenance
is recorded here: the #9835 memory gains the report-side PRs (#10022
ReportQualifier groundwork, #10246 reported_date fix), and the #10344
memory gains #10432 (targets-by-contact-id datasource support).
The 10570 draft (#10509, attachments in contact forms) is removed: the
forms corpus's curated 10509 memory owns that issue and now records
PR #10570.
validate-schema 91/91; no duplicate issueNumbers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(#135): review 4745315385 items, each re-derived before editing (contacts)
Every item was checked against the PR's own diff and cht-core master before
being changed, not taken on the review's word. All four held up.
**9281 -- the getAll AsyncGenerator was inverted.** The draft said the generator
yields pages and showed a nested loop. It yields individual docs:
git -C $CORE show refs/verify/pr9281:shared-libs/cht-datasource/src/libs/data-context.ts
# getDocumentStream ... : AsyncGenerator<T, void>
# for (const doc of docs.data) { yield doc; }
git -C $CORE show refs/verify/pr9281:.../test/libs/data-context.spec.ts
# 131: it('yields document one by one'
Rewritten to the flat `for await (const person of Person.v1.getAll(ctx)(q))`
shape. Two facts found while verifying and now recorded: the PR squash-merged
into the `9193-api-endpoints-for-getting-contacts-by-type` feature branch
(`bf8a77da`, not an ancestor of master) and reached master only via #9311
(`34dd0303c`); and the helper was renamed before landing -- at #9311's squash it
is already `getPagedGenerator` in `libs/core.ts`, with `getDocumentStream` absent
and the signature already `AsyncGenerator<Person, null>`. Time-scoped, not
silently corrected to master's shape. The same PR also swapped getPage's numeric
`skip` for a string `cursor` and moved it ahead of `limit`
(`- return fn(personType, limit, skip)` / `+ return fn(personType, cursor, limit)`).
**10043 / 10057 / 9266 / 9281 / 9835 -- data-access, deferred with disclosure.**
The reviewer's own "extend vs use" rule is satisfied: all four anchor PRs touch
`shared-libs/cht-datasource` and nothing else (`git diff-tree --name-only` per
squash). Deferred per the reviewer's own sequencing on #122 -- "one coordinated
schema/taxonomy PR ... Not blocking any single PR" -- and the #123 precedent.
`data-access` is not a valid `domain` today (`agent-memory/schema.json` CHTDomain
enum holds 9 values, none of them it) and PR #152 adds it, open and unmerged, so
re-keying here would race #152 for the same enum value. Each of the five now says
so in its own text rather than leaving the reader to infer it.
**9007 -- Domain Rationale leakage stripped.** "Per the infrastructure pitfall"
is classifier scaffolding; replaced with the substantive reason. While verifying,
the vague `page_size` prose was pinned to the real constant:
`- private readonly PAGE_SIZE = 50;` / `+ private readonly PAGE_SIZE = 25;`.
Also dropped "verified with a manual quick test" -- PR #9007's body has an
entirely unchecked review checklist and says nothing about manual testing.
**9915 -- the dropped attribution, justified rather than restored.** Round 2
reworded "Reviewer verified the correct workflow (xlsx edit -> xml regeneration)
was followed" to drop "Reviewer". Restoring it would re-assert something the diff
contradicts: of PR #9924's 29 changed `.xml` files only 17 have a same-named
`.xlsx` beside them; the other 12 are the place create/edit forms, expanded from
4 shared `PLACE_TYPE-*.xlsx` templates and edited directly. The section now
states what is checkable from the diff. Counts corrected against the real file
list (50 files, all M -- 29 xml / 21 xlsx; default/app 10->11, covid-19/contact
6->8).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): scope 10344 to the open proposal it actually describes (contacts)
Closes the loose end from #123's review, which said `10432` "was relocated to
contacts". It is here -- as `10344-targets-by-contact-id-cht-datasource.md`,
keyed by the issue (#10344) rather than the PR (#10432), which is why looking for
a `10432-*` file finds nothing. Nothing was dropped.
What it needed was scoping, because PR #10432 never merged:
git -C $CORE merge-base --is-ancestor refs/verify/pr10432 origin/master; echo $? # 1
git -C $CORE grep -c byContactUuids origin/master # no output
git -C $CORE grep -lc byContactUuids refs/verify/pr10432 # 12 files
The draft asserted all of it as shipped behaviour. It now opens with a banner
saying otherwise and carries `stale: true`.
Two claims in the first draft of that banner were wrong and are fixed here:
- It said no commit in cht-core history references the PR. One does --
`db9694ef0 feat(#10344): support targets by contact id in cht-datasource
(#10432)` -- it is simply not reachable from master. Stated that way now.
- It listed `bindGenerator()` among symbols existing "only on that open PR's
branch". `bindGenerator` is on master in six files, added by epic #10423
(`622c62542`); #10432 introduces its own independently, the epic not being an
ancestor of the PR. Master's `target-aggregates.service.ts:35` binds
`Target.v1.getAll`, not the `TargetInterval.v1.getAll` this draft names. So the
summary's flat "None of this API exists on master" was also too strong.
Code Patterns and Design Choices credited this proposal with `bindGenerator`;
both now point at #10423. The contact-UUID filtering vocabulary really is
PR-only, and the epic really does lack it (`git grep -c byContactUuids 622c62542`
-> 0), so the rest of the banner stands.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): sweep the 23 drafts the review did not name (contacts)
Rounds 3-5 on #123 found more defects outside the reviewer's list than in it, so
all 37 drafts were swept for the same classes. Every finding below was settled
against the anchor PR's own diff or `origin/master`, never reasoned about.
`9264`, `9390` and `10713` came back clean and are untouched.
Worst first.
**9230 -- the whole draft was polarity-inverted.** It said leftover action-bar
logic *prevented* editing a home place and the fix *restored* it. The real bug is
the opposite: the Edit button was wrongly *enabled*.
git -C $CORE show 9f900220 -- .../contacts-content.component.ts
# - canEdit: ... this.userSettings?.facility_id !== this.selectedContact?.doc?._id,
# + canEdit: ... !this.userSettings?.facility_id?.includes(...doc?._id),
# issue #9229: "Old action bar should PREVENT users with multiple facilities
# assigned from editing the homeplace"
Once `facility_id` became an array, `['x'] !== 'x'` is always true, so `canEdit`
was always true. Title, summary, Problem, Root Cause, Solution, Design Choices,
Domain Rationale and the `tags` all carried the inversion; all were rewritten
together. Also time-scoped: the action bar and this `canEdit` block were removed
from master by #9361, so `stale: true`.
**8684 -- describes a feature that is not on master at all.** `stale: false` was
the most damaging assertion in the corpus.
git -C $CORE merge-base --is-ancestor 59a1dbd2 origin/master; echo $? # 1
git -C $CORE branch -a --contains 59a1dbd2 # 4.4.1-FR-barcode, 4-4-cares, ...
for s in search_by_barcode BarcodeDetector can_use_barcode_scanner; do
git -C $CORE grep -l $s origin/master | wc -l; done # 0 0 0
PR #8684 merged into the `4.4.1-FR-barcode` release branch and issue #6669 is
still open. Now `stale: true` with the landing recorded. Two more: it credited
itself with `browser-detector.service.ts` (`M` here; added by #7568 in 2022, this
PR adds one method), and misquoted the telemetry literal --
`barcode_no_detected` where the code says `barcode_not_detected`.
**8984 -- the 50-report cap was described backwards.** The draft said the summary
saw "only the first 50 reports". `search.js` slices the *tail* of the date-sorted
rows, so it keeps the 50 most **recent** and drops the oldest -- which is why
issue #8815 is titled "Only **last** 50 reports for contact are provided" and
reproduces by submitting 50 reports *after* the pregnancy. Corrected in all four
places, plus `search.service.ts` annotated as not modified by this PR and the
separate `DISPLAY_LIMIT = 50` display cap disclosed.
**9601 and 9625 -- prose transcribed from a PR description, not its merged code.**
`9601` named `is_canonical`, a `duplicate_info` section and `context.duplicate_check`;
none exists at the merge commit or on master (the real shapes are an
`[duplicate-contacts]` content-projection slot and a top-level `duplicate_check`).
`9625` claimed freetext search for person and place; the PR adds `getUuidsPage`/
`getUuids` to contact and report only, and creates two controllers rather than
adding endpoints to four. Both were independently flagged by `ground-claims`.
**Attribution corrected on 9295, 9368, 9090, 9177, 10141.** Five drafts credited
themselves with files or symbols another PR introduced -- #9295 called five files
new that #9090 created and are `M` in its own diff; #9368 claimed `/api/v1/person`
when its only added route is `/api/v1/place` and person was already in its parent
tree; #9090 tagged four files it created as #9176's; #9177 put `getByUuid` in
`place.ts` when it lives in `index.ts`; #10141 claimed a public export that
#10157 added. Non-existent namespace members (`Person.v1.getPageByType`,
`Place.v1.getByType`, `Person.V1`) corrected to the real exports, with the
`getDatasource()` facade names distinguished from them.
**Drift disclosed, not silently corrected, on 8684, 9230, 9426, 10777, 10804.**
Each edit was followed by a re-read of the whole draft; that pass caught a
further nine sibling contradictions the individual findings had not named --
`9230`'s Domain Rationale, `8995`'s YAML title, `9295`'s Design Choices and
Testing, `9368`'s Related Issues and Domain Rationale, `9090`'s Related Issues,
`9625`'s summary and Domain Rationale, `10141`'s `concepts` -- which is the
failure mode this exercise is about.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): three landed contacts drafts, outside this PR's diff (contacts)
`8034`, `8074` and `10074` sit in `domains/contacts/issues/` but are already on
`main` and untouched by PR #132 -- `git diff --name-status main...HEAD` covers 34
drafts and none of these three. They are separated here so the promotion diff
stays exactly what it was, and so a reviewer can drop this commit without
disturbing the rest.
They were swept because they are part of the corpus an agent retrieves, and a
defect there is a defect regardless of which PR introduced it.
**10074 -- every path it points at was deleted from master.** Both migration
scripts and their specs went in #10187 (`chore(#9639): remove old migrations
[5.0]`, 2025-08-27), a month after #10085 merged, and the draft's own
`lastUpdated: 2026-03-16` postdates the deletion with no scoping anywhere:
git -C $CORE ls-tree origin/master api/src/migrations/ | grep -i person # nothing
git -C $CORE log --diff-filter=D --format='%h %s' origin/master \
-- api/src/migrations/extract-person-contacts.js # 2d44b01e chore(#9639) ... (#10187)
Time-scoped to #10085 rather than rewritten to master's shape; the body still
records what that PR did. Also fixed an inversion: `data-context.js` was said to
"provide" `getLocalDataContext`, which it consumes from `@medic/cht-datasource`
and re-exports the bound result of.
**8034 -- "most permissive setting wins across roles" contradicted its own Design
Choices.** `getDepth()` overwrites `replicatePrimaryContacts` when a role has a
*greater* depth and only ORs it on a tie, so a deeper role with the flag off beats
a shallower role with it on. The PR's own test name says so: "should return most
permissive report depth and replicatePrimaryContacts associated with highest
depth". Also corrected the `do...while` rationale -- the loop exists because a
primary contact is only admissible once its place is in `subjectIds`, not because
of primary-contact chains -- and disclosed that these three services moved under
`api/src/services/replication/` in #10823.
**8074 -- Solution item 5 contradicted item 4.** With parent + type and no
freetext, `generate-search-requests.js` returns a single request with the type
folded into the compound key; there is nothing to intersect. Intersection only
happens when freetext is also present.
**8034 also duplicates a landed data-sync draft, and the gate as documented
cannot see it** -- see the `duplicate-issue` note in the PR reply. Not fixed
here: collapsing two landed drafts across domains is a corpus decision, not a
contacts one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): Related Issues refs, glosses and timestamps (contacts)
Found by `verify-drafts --online`, which reached 3 blocking + 8 warnings once it
could run. Round 1's defect was exactly this family -- a PR cited where an issue
belongs -- so these are worth closing rather than waving through.
**Three glosses that described the wrong thing (blocking).** Each was checked
against the real title and body before rewriting:
- `10057` called PR #10056 "companion PR adding the missing test coverage for
qualifier.ts". It is "feat(#10036): implement `createPerson` for local", whose
body reads "add support for creating Person Doc in Pouch". Nothing to do with
qualifier tests.
- `9177` called #8889 "Original proposal for these get-by-uuid endpoints". #8889
is "Provide API access for online users", still open, and is the broad umbrella
these endpoints serve rather than a proposal for them specifically.
- `9390`'s gloss for #9311 was substantively CORRECT -- #9311 is the epic squash
that landed get-persons-by-type and carried #9295's `req.query.personType`, and
issue #9389's own body links to it saying just that. The checker compares a
gloss against the PR *title*, so an accurate functional description trips it.
Rewritten to quote the real subject as well as the relevance, which keeps the
substance and gives the check something to match. Recording it here because the
finding was a false positive on the gloss rule, not a defect.
**Seven PRs cited as issues.** `10057#10056`, `9177#9090`, `9177#9176`,
`9390#9311`, `9835#10083/#10081/#10043` now read "PR #N". Two of them were also
the useless gloss "Related code change"; both now say what the PR contributed.
`9601`'s weak gloss for #6363 now quotes the issue's real title.
**Nine stale timestamps.** The eight drafts edited in this batch move to
2026-08-11. `10713` is NOT bumped to today -- it is clean and I never edited it;
its `lastUpdated` was simply never bumped by the commit that last changed it, so
it moves to that commit's date (`f20b746`, 2026-07-16) rather than claiming an
edit that did not happen.
`validate-schema` 91/0 after.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): my own gloss tripped the rule it was meant to satisfy (contacts)
Two leftovers from the previous commit, both created by that commit.
**`9177`'s replacement gloss for PR #9176 was itself flagged.** I had rewritten
the useless "Related code change" to "created `src/place.ts` with the
`Place`/`PlaceWithLineage` interfaces this PR adds operations to". That is true --
`git show 282faee^:.../src/place.ts` holds the two interfaces and no `export
const` -- but it describes a side effect of #9176 rather than its subject, which
is "add api support for getting a person with lineage by uuid". Same shape as the
`9390` false positive I wrote up last commit: an accurate functional gloss that
shares no vocabulary with the title. Now quotes the real subject and keeps the
relevance, which is what a reader needs anyway.
**`10713`'s timestamp cannot be set backwards.** Last commit moved its
`lastUpdated` to 2026-07-16 -- the date its content actually last changed --
rather than claiming an edit that never happened. But `stale-timestamp` compares
against git's last-edit date, and writing the field IS an edit, so the warning
came straight back pointing at 2026-08-11. Reverting would not help either: the
revert is also an edit. Set to 2026-08-11, which is now simply true of the file.
Its prose is untouched; only the stale metadata moved.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): what the first gate pass on frozen bytes found (contacts)
`ground-claims` pass 1 over the committed corpus reported 2 ungrounded and
`check-coherence` pass 1 reported 5 contradictions. Adjudicated one at a time;
three were real, two were tooling defects (fixed on memory/draft-verification,
not papered over here), and one was a genuine ambiguity worth removing anyway.
**Real: `10897` contradicted itself three ways, all created by the earlier fix
to its Problem section.** Correcting Problem to match issue #10878's actual
report -- manual, unhurried navigation during a slow index -- left three
statements asserting the opposite:
- the `title` still said "when rapidly switching pages"; it now says "when
navigating away ... before it finishes loading", which is what the report
describes;
- Related Issues asserted "Switching pages too quickly" in the draft's own
voice. That IS #10878's real title (confirmed against the API), so it is now
quoted as the issue's title with the report's own contents beside it;
- Design Choices claimed no restructuring of the subscription lifecycle while
Solution said the callbacks were extracted into two new methods. Both are
true of different things -- the callbacks moved, the subscribe/teardown did
not -- and the sentence now says so.
**Real: `8034`'s "any matching role" was ambiguous** between "any role at all"
and "any role tied at the highest depth". Only the second is true. Reworded to
say so outright, which also removes the clash the checker flagged against Code
Patterns.
**Real: `9394` quoted an ungreppable call chain.** The prose wrote
`targetAggregateService.getCurrentTargetDoc()`; the source splits the receiver
and the method across two lines, so no literal search can ever find it. Now
names the method and its service separately -- `getCurrentTargetDoc()` on
`TargetAggregatesService` -- which is both greppable and easier to read.
**Tooling, not content: `9281`.** `getPagedGenerator` came back ungrounded
because the probe checked the anchor, where it genuinely does not exist; the
sentence is about master, where it is at `libs/core.ts:227`. The forward-scope
rescue keys on "on master" appearing in the quote, and the quote is line-bounded
-- the marker had wrapped onto the previous line. Rewrapped so each
master-scoped sentence carries its own marker. The claim never changed.
**Tooling, not content: `10804`.** The checker filed a pair and cleared it in
the same breath -- "a minor framing difference rather than a factual conflict".
Nothing edited here; the withdrawal screen was widened instead.
`validate-schema` 91/0; `verify-drafts` offline 0 blocking.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): round 2 of the gate — 10 drafts, and 3 of the defects were mine
Three `ground-claims` and three `check-coherence` passes over the frozen corpus,
all 37 drafts, no truncation. Not clean: 3-4 ungrounded and 5-7 contradictions
per pass, and the pass-to-pass variation is the argument for the count -- the
`8984` naming clash appeared in two of three, `9090`'s in one of three.
Recurring in all three coherence passes, and all three are defects the earlier
"fix" commits introduced:
**`9281` (3/3).** My Solution addition -- "replaced getPage's numeric `skip`
with a string `cursor`" -- contradicted the summary and Problem, which both
still said the pre-PR API was cursor-paginated with callers managing cursors.
The Solution is the correct side:
git -C $CORE show bf8a77da -- .../src/person.ts | grep -E '^[-+].*(skip|cursor)'
# - const assertSkip = (skip: unknown) => { + const assertCursor = ...
# - return fn(personType, limit, skip); + return fn(personType, cursor, limit);
Before this PR there was no cursor to manage. Summary and Problem now say `skip`.
**`9915` (3/3).** Code Patterns still carried the bald rule "Always edit `.xlsx`
source files first, then regenerate XML via `cht-conf`" while Solution and
Design Choices -- which I had rewritten -- say 12 of the 29 XMLs were edited
directly. Both now describe the two paths.
**`10057` (2/3).** Last commit corrected the Related Issues gloss for PR #10056
to "implements `createPerson` for the local data context" and left Testing still
calling it the PR that supplied qualifier.ts coverage. Testing now agrees.
Also real, from the same rounds:
- **`8984`** named the display cap `DISPLAY_LIMIT` in Solution and
`DOCS_DISPLAY_LIMIT` in Testing. Master has `DISPLAY_LIMIT`
(`contacts-content.component.ts:86`); `ground-claims` flagged the other as a
fabricated symbol in the same round.
- **`9203`** Related Issues said "None directly referenced" while Problem cites
#9128 as the change that introduced the array shape. Now listed -- and as
`PR #9128`, because labelling it plainly `#9128` promptly earned a
`related-ref-is-pr` warning. Fourth time a fix here has generated the next
finding.
- **`9394`** named `CHTDatasourceAPI` three times. No such symbol exists at the
anchor or on master; the class is `CHTDatasourceService`. Reworded to describe
the surface it builds for config scripts.
- **`9295`** Testing said "Extensive tests added" and named two spec files. Every
test file in that PR is `M`, none `A` -- the added-vs-modified class, caught by
the deterministic status inference on a draft it had already passed once.
- **`8995`** wrote `_ids`; the code is `_map(reports, '_id')`.
- **`9601`** put `mat-expansion-panel` in the component's `.ts`; it is in the
`.html` (4 hits).
- **`9090`** wrote `Qualifier.byUuid` as living in `qualifier.ts`. `byUuid` is
there; the qualified form only appears at call sites. Reworded so the file
claim is about the symbol it actually contains.
`validate-schema` 91/0. `verify-drafts --online` 0 blocking, 0 unverified.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): round 3 — three findings, one of them yesterday's fix
Three ground and three coherence passes on `73502eb`, all 37 drafts. Findings
dropped from 3-4 ungrounded / 5-7 contradictions to **1 / 2**, and all three
distinct findings are closed here.
**`9090` — `Qualifier` is not in `qualifier.ts` (ungrounded in 3 of 3).** Last
round's reword still paired the bare token with that file. The file genuinely
never contains it:
git -C $CORE grep -cFw Qualifier 59b42e2fa -- .../src/qualifier.ts # 0
git -C $CORE grep -c Qualifier 59b42e2fa -- .../src/qualifier.ts # 5
# the 5 are UuidQualifier / isUuidQualifier — substrings, not the name
git -C $CORE grep -n 'as Qualifier' 59b42e2fa -- .../src/index.ts
# index.ts:39: export * as Qualifier from './qualifier';
The namespace is created at the re-export, not in the module. Now says so, which
puts `byUuid` in `qualifier.ts` and `Qualifier` in `index.ts` — where each
actually is.
**`9203` — the #9128 gloss I added last commit was backwards (2 of 2).** I wrote
that PR #9128 created "the older-database shape this fix tolerates". It created
the *new* array shape; the legacy `string` is what pre-#9128 databases still
hold, which is the whole point of the fix:
git -C $CORE log -1 --format='%h %s' c7fbcb1b8
# feat(#9116): update user place field in admin to allow setting multiple places (#9128)
Corrected. That is the fifth time in this batch a fix has produced the next
finding, and the third where the checker caught me rather than the pipeline.
**The data-access banners contradicted the Domain Rationale (1 pass each on
`10043` and `9281`).** The banner said the anchor PR extends the library "rather
than a contacts feature" while Domain Rationale argues contacts is the most
specific fit. Both are true of different taxonomies, and neither said so. All
five banners now state it explicitly: primary would be `data-access` with
`contacts` secondary under the proposed scheme, and `contacts` remains the
closest of the nine that exist today. No claim changed; the frame was missing.
`validate-schema` 91/0. `verify-drafts --online` 0 blocking, 0 unverified.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): 9090's summary named the imperative surface as both (contacts)
Round 4: ground came back **0 ungrounded in three consecutive passes**;
coherence was 0 / 1 / 0, and the one finding is real.
The summary said the library exposes "person.getByUuid through both imperative
and declarative APIs". `getByUuid` is only the imperative facade; the declarative
export is `get`:
git -C $CORE show 59b42e2fa:.../src/person.ts | grep -n 'export const'
# 32: export const get = (context: DataContext) => {
git -C $CORE grep -n getByUuid 59b42e2fa -- shared-libs/cht-datasource/src
# index.ts:59: getByUuid: (uuid) => ctx.bind(Person.v1.get)(Qualifier.byUuid(uuid)),
The Solution and Code Patterns already said this; the summary was the stale side.
It now names both spellings so neither section can be read as the other's
contradiction.
Appearing in one pass of three is what the pass count is for: the same draft's
`Qualifier` claim showed in three of three last round, this one in one of three.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): two "only X existed" claims that undercounted (contacts)
Round 6. Ground stayed at 0 ungrounded across three passes; coherence found two
more, one in two passes of three and one in one of three.
**`9177` (2/3).** The summary said "only get-person existed from #9065" while
Problem said "only get-person/get-person-with-lineage existed". Problem is
right — both were on the tree this PR built from:
git -C $CORE show 282faee19^:shared-libs/cht-datasource/src/person.ts | grep -n 'export const'
# 53: export const get = getPerson(...)
# 61: export const getWithLineage = getPerson(...)
**`9835` (1/3).** Root Cause called PR #10083 "the prior attempt" that "had
duplicated validation logic", while Solution credits the same PR with
introducing `input.ts` and parameter-validators for *centralized* validation.
Solution is right; #10083 added the file:
git -C $CORE diff-tree --no-commit-id --name-status -r f382785be | grep input.ts
# A shared-libs/cht-datasource/src/input.ts
#10083 is the initial implementation this draft describes, not something it
superseded. Root Cause now describes the pre-datasource state — validation
repeated at each call site — without attributing it to the PR that fixed it.
Both are the same shape: a section that enumerates a prior state with "only",
and gets the enumeration wrong. Neither was reachable from the anchor diff alone;
both needed two sections read against each other.
`validate-schema` 91/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): "only X existed" keeps being the wrong enumeration (contacts)
Round 7. Ground 0 / 0 / **2** — the streak broke on the third pass after nine
consecutive clean ones, which is the whole case for not stopping at one.
**`9177`, and this one is mine.** Last round I rewrote the summary to name the
prior state as "get-person and get-person-with-lineage". Those are prose, not
identifiers:
for s in get-person get-person-with-lineage; do
git -C $CORE grep -lFw "$s" origin/master | wc -l; done # 0, 0
git -C $CORE grep -n 'export const get\b' origin/master -- .../src/person.ts
# 61: export const get = ... 69: export const getWithLineage = ...
Both sections now name `Person.v1.get` and `Person.v1.getWithLineage`, which are
real, greppable, and more use to a reader than a hyphenated paraphrase. Fixing
the count last round introduced a fabricated-symbol pair this round.
**`8074` (coherence, 1/3).** Problem said the widget "only supported searching
contacts by document type, not by parent"; Root Cause said filters were built
"from only contact types and freetext". Root Cause is right — contact freetext
search predates this PR:
git -C $CORE show 454788537^:shared-libs/search/src/generate-search-requests.js | grep -n freetext
# 202: const freetextRequests = freetextRequest(filters, 'medic-client/contacts_by_freetext');
That is the third draft in two rounds whose defect is an "only ..." enumeration
of a prior state that omits something — `9177`, `9835`, now `8074`. Worth naming
as a class: the sentence is written to motivate the change, so whatever the
change did not touch tends to get dropped from the list.
`validate-schema` 91/0; `verify-drafts --online` 0 blocking, 0 unverified.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): 9394's summary credited the stub with the implementation (contacts)
Round 8: ground **0 / 0 / 0**; coherence 1 / 0 / 0, and the one finding is again
a sentence I wrote.
My round-2 rewrite of the summary said the `analytics.getTargetDocs()` entry is
"built by `CHTDatasourceService`". The Solution I wrote in the same commit says
that service only declares an empty-array stub and `contact-summary.service.ts`
supplies the working function. The Solution is right:
git -C $CORE show bbe5dedd5 -- webapp/src/ts/services/cht-datasource.service.ts
# + analytics: {
# + getTargetDocs: () => ([]),
git -C $CORE show bbe5dedd5 -- webapp/src/ts/services/contact-summary.service.ts
# + chtScriptApi.v1.analytics.getTargetDocs = () => targetDocs;
The summary now names both halves — declared as a stub by one service,
overwritten by the other before the generator runs — so neither section reads as
the other's contradiction. Correcting a draft's mechanism in one section and
leaving the summary asserting the tidier version of it is the single most
frequent way I have broken these drafts.
`validate-schema` 91/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): an invented receiver, and two accurate examples that read as one wrong one
Round 9: ground 0 / 1 / 0, coherence 0 / 0 / 1.
**`8995` — `contactViewModelGenerator` is not a thing.** The prose named a
receiver that exists nowhere; `addHeading` is a private method invoked as
`this.addHeading(...)`:
git -C $CORE grep -nFw contactViewModelGenerator 0ba3adb75 # 0 hits
git -C $CORE grep -n 'class ContactViewModelGeneratorService\|addHeading(' 0ba3adb75 \
-- webapp/src/ts/services/contact-view-model-generator.service.ts
# 37: export class ContactViewModelGeneratorService {
# 289: private async addHeading(reports, forms) {
# 329: .then(reports => this.addHeading(reports, forms))
Same shape as `9394`'s `targetAggregateService.getCurrentTargetDoc` and `9177`'s
`get-person`: prose invents a qualified name for something real. Now names the
method and the class it belongs to, both greppable.
**`9264` — nothing was wrong, and it still needed the edit.** Problem cites the
reported symptom (`contact_detail:clinic:load`) and Solution illustrates the
new-style branch with `"hospital"`. Both are accurate — the karma spec really
does exercise `hospital` — but a reader meeting two different types for one fix
has to work out that they are the same code path. Stated outright instead. This
is the "correct-but-unreadable" case: the checker misread it, so a person would.
`validate-schema` 91/0; `verify-drafts --online` 0 blocking, 0 unverified.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): `parents` is contact-type config, not a datasource symbol (contacts)
Round 10: **coherence 0 / 0 / 0** — the first three-pass clean sweep of that
tier. Ground 1 / 0 / 0.
`10057` said the create path validates the parent "against the allowed `parents`
configured for the new contact's contact_type ... centralized in `src/input.ts`",
which pairs the token with a file that never contains it — at any anchor:
git -C $CORE ls-tree e0ecefed49 -- shared-libs/cht-datasource/src/input.ts
# (empty — the file does not exist at this draft's own anchor)
git -C $CORE grep -cFw parents 95153376d -- shared-libs/cht-datasource/src/input.ts
# 0 — nor at #10124's, which is the PR that adds the file
git -C $CORE grep -n parents e0ecefed49 -- shared-libs/contact-types-utils/src/index.js
# 38,46,54 — type.parents, where the allow-list actually lives
The behaviour described is real and does live in `input.ts` at #10124; only the
`parents` allow-list is elsewhere — it is a contact-type config array in app
settings, reached through `contact-types-utils`. The sentence now separates the
two, and states outright that `input.ts` postdates this draft's anchor.
Worth noting for the write-up: this is a claim the sibling-anchor fix could not
rescue, because the token is absent from the named file at every anchor in the
cluster. The fix addresses "wrong commit"; this was "wrong file".
`validate-schema` 91/0; `verify-drafts --online` 0 blocking, 0 unverified.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): my "clarification" put the reported case in the wrong branch
Round 11: ground **0 / 0 / 0**; coherence 1 / 0 / 1, both on `9264`, both caused
by the edit I made to it last round.
`9264` was flagged in round 9 as correct-but-unreadable: Problem cited the
reported `clinic` symptom, Solution illustrated the new-style branch with
`hospital`, and the checker read the two types as disagreeing. I rewrote the
prose rather than leave a sentence a checker misreads — and attached the `clinic`
case to the **new-style** branch. It belongs to the legacy one:
git -C $CORE grep -n -A4 'getTypeId = ' origin/master -- shared-libs/contact-types-utils/src/index.js
# 24: return doc.type === 'contact' ? doc.contact_type : doc.type;
A legacy `clinic` doc has `type: 'clinic'`, so `type !== 'contact'` and the
function returns `doc.type`. That is also exactly what the draft's own Root Cause
says — `clinic` is one of the legacy hardcoded types with `contact_type`
undefined, which is *why* the old code fell back to the literal `"contact"`.
Attaching it to the new-style branch contradicted the mechanism the draft
correctly explains two paragraphs earlier.
The `clinic` example now sits on the legacy branch and `hospital` on the
new-style one, each labelled with what it is.
This is the sharpest lesson of the run and belongs in the write-up: the draft was
**not wrong** when I touched it, only hard to read. Rewriting unreadable-but-true
prose is not a free action — I made it false, and it took two more passes to find
out. "If a checker misreads it, a person will too" is good advice; it does not
license editing without re-deriving the mechanism first.
`validate-schema` 91/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): 8034 described two views as if they were one decision (contacts)
Round 12: ground **0 / 0 / 0**; coherence 1 / 0 / 0.
Solution said the PR adds "a new `contacts_by_primary_contact` view"; Design
Choices said the value-shape change was taken "rather than adding a separate
view, to avoid maintaining two views for the same data". Both are true, of
different views, which is exactly why they read as a contradiction:
git -C $CORE diff-tree --no-commit-id --name-status -r 80760a6d2 | grep views
# M ddocs/medic-db/medic/views/contacts_by_depth/map.js
# A ddocs/medic-db/medic/views/contacts_by_primary_contact/map.js
git -C $CORE show 80760a6d2 -- .../contacts_by_depth/map.js
# - var value = doc.patient_id || doc.place_id;
# + var value = { shortcode: ..., primary_contact: ... }
The rejected alternative was a *second depth-keyed view* carrying the same rows;
`contacts_by_primary_contact` answers the reverse question and had nothing to
fold into. Design Choices now says which alternative was rejected and why the
new view is not an instance of it.
Unlike `9264` last round, I re-derived the mechanism from the diff before
touching the prose. That is the step I skipped there, and skipping it is how a
readability edit turned a true draft false.
`validate-schema` 91/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): round-3 review — 8684's universal, and 10344's merge state
Two of sugat009's five items. The other three (10057, 9835, 9266) are on drafts
getting a full body audit first, so they are fixed in one pass rather than
patched twice.
**`8684` (blocking) — an unquantified universal I wrote and never checked.** The
sentence claimed all 17 listed paths "still exist on master". I enumerated them
this time, one `ls-tree` per path:
16 OK, 1 MISSING: config/standard/app_settings.json
git -C $CORE ls-tree --name-only origin/master config/standard/
# config/standard/readme.md — that is all that is left
git -C $CORE log --all --diff-filter=D --format='%h %ad %s' --date=short \
-- config/standard/app_settings.json
# 3f7f6d6e3 2024-01-09 chore(#8757): Remove standard config
All 17 did exist at the anchor, so only the master half of the sentence was
wrong. It now states the exception and why.
**`10344` (non-blocking) — I read a proxy test as the predicate.** The banner said
PR #10432 "is not merged", inferred from `merge-base --is-ancestor` failing. That
tests whether the head is on master, not whether the PR merged. It did merge:
gh api repos/medic/cht-core/pulls/10432 --jq '.merged, .base.ref, .merged_at'
# true, 10140_previous-month-targets, 2025-12-19T15:22:38Z
The sharper account, which the reviewer supplied and I verified: it merged into
the epic branch, then its content was dropped before the epic squashed. #10423
carries none of #10432's 54 files, and `ls-remote` shows the epic branch has been
deleted, so the PR's own head really is the only place the code lives. The
guidance ("do not treat as available API") was right for the wrong reason and now
has the right one, plus a `gh api` line in the verify block so the next reader
checks merge state directly.
That edit contradicted four sibling sentences still saying "open PR" / "not
merged" / "open proposal" — summary, banner, verify comment and the closing
paragraph. All four reconciled in the same commit, which is the re-read step I
have skipped too often in this batch.
`validate-schema` 91/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): body-audit the five drafts that never had one (contacts)
Round 3 of review found two defects in drafts I had never read line by line. The
cause was audit *surface*, not verification depth: of the 37 drafts I delegated
29 to readers with "audit the whole file", and for the eight I kept I only
checked the diff I was editing. `9835`, `10043`, `10057` and `9266` therefore got
a `data-access` banner and nothing else, while twelve gate rounds re-checked prose
no human had read once. `9281` is here too because it had been rewritten so often
that its own text needed re-deriving.
Full audits found **36 findings, 13 blocking** — against the reviewer's five.
Worst first.
**`9835` had the provenance backwards (6 blocking).** I read #10083 as the thing
#10522 refactored. The containment runs the other way:
git -C $CORE merge-base --is-ancestor a89955a9f refs/verify/pr10083; echo $? # 0
git -C $CORE grep -ln 'createDoc\|db.post\|db.put' 5f40e3dac -- shared-libs/cht-datasource/src
# (empty — master had no cht-datasource writes at all before f382785be)
#10083 is the umbrella that squashed the whole branch to master; #10522 is an
ancestor of its head. Also fixed: the permission enumeration (wrong for five of
six endpoints, and reports use `can_view_reports` not `can_view_contacts` for
reads); #10222 credited with report-controller changes it never made; the
create/update flow sketches, true for place and wrong for person and report;
#10246 described as fixing created reports when it fixed the *update* path; and
the contact controller credited with endpoints it never received. `#10522` added
to `source_prs`.
**`10057`'s false claim was in two sections, not one (3 blocking).** The reviewer
flagged the Solution occurrence; Code Patterns asserted the same thing, so
fixing only the filed line would have left it live. `input.ts` was added by
#10094, #10124's diff to it is +6/-1, the parent/lineage logic is per-module
duplicated closures, and master's `input.ts` is types-only. Also: "places cannot
be qualified without a valid parent" inverts for types with no configured
`parents`; #10089 credited with the local implementation that was #10065's; and
the pre-existing read surface understated as get/getWithLineage when getPage and
getAll were there too.
**`9266`'s signature was wrong twice over (2 blocking).** `Person.v1.getPage(limit,
skip)` omits both the mandatory `personType` and the `context` curry. And its
whole `limit`/`skip` vocabulary never reached master — #9281 replaced `skip` with
a cursor on the same branch before the epic landed — so it now carries an
epic-child banner and `stale: true`.
**`9281`: all four findings were in text I wrote (2 blocking).** Including a
banner whose own remediation command does not work — `refs/pull/9281/head` is the
pre-squash merge commit `7e0355f3a` and does not contain `bf8a77dae`; only the
epic PR's head does. And `getAll` is facade-only: it exists in neither
`local/person.ts` nor `remote/person.ts`, at the anchor or on master, because the
facade generator binds the facade's own `getPage` and inherits dispatch from its
`adapt` call.
**`10043` (4 findings)** — #10056's `qualifier.ts` change is a single `export`
line, not validation logic; the local module imports only a type; and a legacy
`POST /api/v1/people` route did exist.
Drift disclosed rather than corrected on `10043`/`10057`/`9266`: none of their
source PRs is an ancestor of master, and the qualifier vocabulary they describe
was replaced by `Input` types before landing. `9281` stays `stale: false` — its
`getAll` did land and is still on master at `person.ts:113`.
The audits also repaired 15 sibling contradictions the individual findings had
not named, and twice caught their own replacement text being wrong before it
shipped.
`validate-schema` 91/0; `verify-drafts --online` 0 blocking, 0 unverified.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): the four systemic shapes, swept across the corpus (contacts)
The round-3 review's five items shared four shapes that both machine checkers are
blind to. Swept all 32 remaining drafts for each, with the full protocol (PR
commit, master, and the commits between).
**Unquantified universals — 3 found.** A claim over a set where the set was never
enumerated.
- `8034` said the view-value change is breaking and "all consumers must read
`row.value.shortcode`". Of the three non-test consumers, only
`authorization.js` reads the value at all: `muting_utils.js` reads `row.id` and
`config-watcher.js` only loads the view map, and #9593 touched neither.
- `9090` said "nine webapp callers". `git grep -ln cht-datasource.service 59b42e2fa
-- webapp/src` returns **5**; the other four changed files needed only
TypeScript casts.
- `8684`'s summary said "no barcode code exists on origin/master". Two
counterexamples — the vendored Enketo XSL handling of the ODK `barcode` question
type. Scoped to barcode-*scanner* code. (Its Related Files sentence, fixed last
commit, was already correct: I re-enumerated all 17 paths, 16 present.)
**Enumerations and mappings — nothing found**, across ~90 name-to-thing pairings
in 24 drafts, each checked individually rather than as a sentence. Worth recording
as a confident negative: this is the class that produced `9835`'s blocking defect,
so its absence elsewhere is information.
**Provenance chains — 1 found, and it was mine.** `10344` claimed `bindGenerator`
was reinvented independently by the epic. It originated in *this* PR:
git -C $CORE log --oneline --reverse refs/verify/pr10423 -S bindGenerator \
-- webapp/src/ts/services/cht-datasource.service.ts
# db9694ef0 feat(#10344): support targets by contact id in cht-datasource (#10432)
`db9694ef0` is the only commit introducing it on the epic branch and is an
ancestor of the epic head, and master's body is identical to it. So it is on
master *because of* #10432. Fixed in the banner, Code Patterns and Design Choices,
and the summary, which still said it "arrived separately".
**Proxy tests read as the predicate — 2 found.**
- `9426` said "server-side hierarchy validation does not exist as of this fix",
evidenced by grepping the two design-doc validators. That answers a narrower
question. `validatePlace` in `shared-libs/contacts/src/places.js:83` rejects a
wrong parent type via `contactTypesUtils.isParentOf` on the `POST /api/v1/places`
path, at the anchor and still on master. The client-side-only point survives,
scoped to the webapp form path.
- `10344` again: "its content never reached the epic". It did, on 2025-12-19, and
was renamed away *on the branch* by `09d8c8024` before the squash. Two of my
three attempts at this banner used a file-overlap or ancestry test to answer a
question neither test asks; it now uses the vocabulary test, which does, and the
verify block asks `gh api` for merge state directly.
`10344` is worth a note for the write-up: three rewrites, each fixing the previous
one's proxy-test error. A banner asserting what a PR did *not* do turns out to be
the hardest kind of claim to get right, because every cheap git test answers a
neighbouring question.
`validate-schema` 91/0; `verify-drafts --online` 0 blocking, 0 unverified.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): the legacy contact services are in shared-libs, not api (contacts)
`ground-claims` on the round-3 rewrites: 11-13 ungrounded, where the previous
frozen bytes were 0. My own fixes are the source, which is the measurable version
of a thing I had only asserted.
Two are real path errors, in text this batch added. `10043` and `10057` said the
legacy `POST /api/v1/people` and `POST /api/v1/places` routes "went through
`api/src/services/people`" / `.../places`. No such paths exist:
git -C $CORE ls-tree -r --name-only c734c65d8 | grep -E '(people|places)\.js$'
# shared-libs/contacts/src/people.js
# shared-libs/contacts/src/places.js
Both corrected, and both verified present at their own anchors. Worth noting the
inconsistency: the `9426` fix in this same round cited
`shared-libs/contacts/src/places.js` correctly, so one round produced both the
right path and the wrong one for the same service.
`10057`'s drift note also had its master marker wrapped onto a different line from
the symbols it scopes; reworded so "lives on master in ..." sits with
`assertHasValidParentType` / `minifyDoc`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): "Added/updated" hid two modified files; name the lineage path (contacts)
Two more from the ground stream on the round-3 rewrites.
**`9394` — the added-vs-modified class, in a hedge.** Testing opened
"Added/updated WDIO e2e for ...", and the two files it then names are both `M`:
git -C $CORE diff-tree --no-commit-id --name-status -r bbe5dedd5 \
| grep -E 'contact-summary-target-aggregates|aggregates-helper-functions'
# M tests/e2e/default/targets/config/contact-summary-target-aggregates.js
# M tests/e2e/default/targets/utils/aggregates-helper-functions.js
"Added/updated" is the hedge that let this through three earlier rounds: it is
never wrong, so it is never checkable. Now states that every file listed is
modified and none added.
**`9835` — a package name is not a path.** The prose credited "`@medic/lineage`'s
`minify`" and the probe tried to resolve `lineage` as a repo path. The claim is
true and the function is greppable, just not where the extractor looked:
git -C $CORE grep -ln 'const minify' 57c5056c8 -- shared-libs/lineage
# shared-libs/lineage/src/minify.js
git -C $CORE show 57c5056c8:shared-libs/lineage/package.json | grep '"name"'
# "name": "@medic/lineage"
Now names both the package and `shared-libs/lineage/src/minify.js`, which is more
use to a reader and settles the probe. No claim changed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): close the "Added/updated" hedge across the corpus (contacts)
`9394` was caught saying "Added/updated" over two files that are both `M`. Fixing
it exposed that I had only fixed half its sentence — the Karma clause in the same
paragraph carried the same hedge — and a corpus grep then found four more drafts
with it.
The hedge is the point. "Added/updated" is never wrong, so it is never checkable:
`enumerate-claims` can infer no status from it, `ground-claims` has nothing to
adjudicate, and three earlier rounds passed over all six instances. It reads as
diligence and functions as an opt-out.
Resolved against each anchor's real file list:
9394 15 karma + 3 e2e, all M -> "all modified, none added"
10713 1 M -> "modified, not added"
8684 2 M -> "modified, not added"
9368 11 M -> "all eleven ... modified, none added"
8682 1 A + 2 M -> genuinely mixed; now says which is which
A tests/integration/api/controllers/places.spec.js
M shared-libs/contacts/test/unit/places.spec.js
M api/tests/integration/migrations/extract-person-contacts.spec.js
`8682` is the useful case: the hedge was hiding a real distinction rather than a
uniform truth, and the sentence already got the integration spec right while
fudging the unit spec beside it.
No `Added/updated` / `Updated/added` / `added/modified` remains in the corpus.
`validate-schema` 91/0; `verify-drafts --online` 0 blocking, 0 unverified.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): keep the master marker on the line it scopes (contacts)
`10057`'s drift note named `assertHasValidParentType` and `minifyDoc` — both
master-only, correctly — but my last reflow put "lives on master in" on the
preceding line. Quotes are line-bounded, so FORWARD_SCOPED saw no marker and both
symbols were judged at the anchor, where they correctly do not exist. Ungrounded
in three passes running.
Third time this exact wrap has cost a round. The marker now sits on the same line
as the symbols it scopes, and says "both master-only" outright rather than
relying on a clause two lines up.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): "changes span both files" implied people.js did the reorder (contacts)
Coherence pass 3 on the delta, and it is right. Code Patterns says
`shared-libs/contacts/src/people.js` "only exposes `_getDefaultPersonType`";
Solution said the parent-assignment "changes span places.js and people.js". Both
files did change, so the sentence is not false — but read next to the reorder it
describes, it implies people.js took part in it.
It did not. Its entire diff is one line:
git -C $CORE show 6dec6344c -- shared-libs/contacts/src/people.js
# +module.exports._getDefaultPersonType = getDefaultPersonType;
Solution now says the reorder is wholly within places.js and states people.js's
one line outright, so the two sections cannot be read as disagreeing.
The class is worth naming: "changes span A and B" is the same shape as the
"Added/updated" hedge closed two commits ago — true at the file level, silent
about which file did what, and unfalsifiable as written.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): put "on master" beside the symbol it scopes, in 10043 too (contacts)
Ground pass 3 on the delta, one finding, and the same wrap problem as `10057`
two commits ago: the drift note said the person qualifier "became `PersonInput`
in `src/input.ts` (#10094)", with the master marker only reaching the symbol via
a clause later in the line.
The claim itself is exactly right:
git -C $CORE grep -n PersonInput origin/master -- shared-libs/cht-datasource/src/input.ts
# 42: export interface PersonInput extends ContactInput {
git -C $CORE log --all --oneline -S PersonInput --reverse \
-- shared-libs/cht-datasource/src/input.ts | head -1
# 806456120 chore(#9835): use `Input` for create `Qualifiers` (#10094)
Reworded to "is `PersonInput` on master, in `src/input.ts` (added there by
#10094)". Present tense scoped where it belongs, provenance kept, and the marker
adjacent to the symbol rather than trailing it.
Fourth instance of this wrap across the batch. The lesson is narrow and worth
carrying: in a drift note, every sentence naming a master-era symbol has to carry
its own marker, because the quote a probe sees is one line and a note that reads
fine to a person can lose its scope at the line break.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* revert(#135): take 8034 back out of this PR (contacts)
`8034` was never part of #132. The original promotion (`aa61c5b`) left it
untouched — `git diff --name-status main...aa61c5b` does not list it — and my
August sweep of "landed drafts" pulled it into the diff. Reverted to `main`, so
the PR's scope is again what it was.
The reason is not only scope. `contacts/8034` is the likelier of the two
duplicates to be deleted outright, and polishing it is work spent on a file that
should probably go:
contacts/8034 hand-authored 2026-06-04 a0108f7 chore(#73): categorize 10
closed cht-core issues in contacts domain (#79)
domain: contacts, subDomain: replication
no source_pr / source_sha / domainFit / confidence
data-sync/9593 machine-distilled 2026-06-23 chore(memory): promote
strong-fit data-sync drafts (#129)
domain: data-sync, domainFit: strong
Not a stale artefact of a domain reorg, then — two independent production paths
three weeks apart, both keyed to cht-core#8034. The contacts copy's own
frontmatter says `subDomain: replication`, which is the data-sync draft's whole
domain, and the data-sync copy is longer (98 vs 70 lines) and fully anchored. My
recommendation is to delete `contacts/8034` in a corpus-repair change; that is a
landed-corpus decision, not this PR's, so nothing here does it.
One finding does transfer and is worth carrying over: `data-sync/9593` names
`api/src/services/authorization.js` three times, and those replication services
moved under `api/src/services/replication/` in #10823 (2026-05-11). The same
drift I disclosed on the contacts copy applies there, undisclosed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): backfill related_issues from the prose that already cross-links (contacts)
`related-issues-desync`, one of the checks moved into the hermetic tier, found
seven drafts whose `## Related Issues` section cross-links an issue while the
machine-readable `related_issues` field is empty or absent. That field is what
#135 de-duplicates on, so the prose linkage was invisible to it.
Every target verified to be an ISSUE before adding it, because writing a PR
number into a dedup field is precisely the round-1 defect on this branch:
gh api repos/medic/cht-core/issues/<n> --jq 'if .pull_request then "PR" else "issue" end'
#9835 issue #10343 issue #9915 issue #9065 issue
#8889 issue #6363 issue #8074 issue
10074 -> cht-core-9835 10344 -> cht-core-10343
8074 -> cht-core-9915 9177 -> cht-core-9065, cht-core-8889
9426 -> cht-core-6363 9601 -> cht-core-6363
9915 -> cht-core-8074
Five drafts had no `related_issues` key at all (the hand-authored shape), one had
`[]`, one was an empty key. No prose changed — this is the frontmatter catching
up with links the drafts already made.
`validate-schema` 91/0; `verify-drafts --online` 0 blocking, 0 unverified,
warnings 10 -> 5 and `related-issues-desync` cleared.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): lastUpdated said 2026-08-12 on edits made 2026-08-17 (contacts)
`stale-timestamp` caught eleven drafts. The cause is mine: this session spans
2026-08-11 to 2026-08-17, my earlier commits dated their drafts correctly for the
day they landed, and today I carried `2026-08-12` forward instead of reading the
date. Ten drafts corrected to 2026-08-17.
`8034` is deliberately NOT bumped. It is byte-identical to `main` and absent from
this PR's diff — `git diff --quiet main -- <path>` is clean — so its
`lastUpdated: 2026-03-16` is the truthful date for the content it holds. Bumping
it would assert a review that did not happen; the warning is the honest artefact
of the revert commit existing in history, and is disclosed rather than silenced.
Also re-dated two drift notes. `10043` and `10057` said "verified 2026-08-12",
but their master-facing claims were re-derived today — `PersonInput` at
`origin/master:shared-libs/cht-datasource/src/input.ts:42`, and
`assertHasValidParentType`/`minifyDoc` on master — so 2026-08-17 is the accurate
stamp. `8684` keeps "as of 2026-08-12": its 17 paths were enumerated then and
have not been rechecked, and understating a verification date is the safe
direction.
`validate-schema` 91/0; `verify-drafts --online` 0 blocking, 0 unverified,
2 warnings (the `8034` artefact above and the structural `uniform-domain-fit`).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): round-4 review, applied in the reviewer's words (contacts)
All nine items verified against source before applying, and all nine held.
Deliberate change of method this round: where the reviewer supplied a
committable suggestion it is used verbatim, because four rounds of evidence say
my rewording is the defect source -- both blocking items here were introduced by
my own round-3 repair commits.
**10057 (blocking) -- I relocated the parent fetch into lineage.ts.** It is not
there. On master the fetch stays in the entity modules:
git -C $CORE show origin/master:shared-libs/cht-datasource/src/local/person.ts
# const getMedicDoc = getDocById(medicDb); (create factory)
git -C $CORE show origin/master:shared-libs/cht-datasource/src/local/place.ts
# const getMedicDocsByIds = getDocsByIds(medicDb);
# both imported from './libs/doc'; lineage.ts holds assertHasValidParentType
# and minifyDoc and no fetch
This PR's own 9835 draft had it right, so the corpus disagreed with itself.
Drift note re-dated to 2026-08-20, since its claims were re-derived today.
**9281 (blocking) -- my "only to swap skip for cursor" was true of remote and
false of local.** bf8a77dae changed 24+/22- in local/person.ts, rewriting
fetchAndFilter's paging arithmetic: end test `docs.length === 0` ->
`docs.length < currentLimit`, new `overFetchCount`, slicing to `limit`.
remote/person.ts really is 2+/2-, the swap alone. Also flipped to `stale: true`
per the reviewer's consistency argument -- 10043/10057/9266 got the flag on the
same rename-before-landing rationale in this very delta, and a freshness pass
keyed on `stale` would otherwise treat this one as current.
The Solution fix then exposed a sibling in Testing one section down: "the local
and remote person specs changed only for the swap". I checked the spec diffs
line by line before touching it -- every changed line in both IS the swap -- so
the sentence was true but now reads as contradicting the fixed Solution. It now
states the asymmetry outright: the fetchAndFilter rewrite landed with no new
spec assertions in this commit.
**Five suggestions/nitpicks, all verified then applied verbatim:** 9835's
contact controller was reshaped (eager `getContact`/`getContactWithLineage`
bindings), not "only" migrated to assertPermissions; assertReportInput never
calls assertContactInput (parameter-validators.ts shows it repeating the checks
against `form`/`contact`); the spec-gap list gains `src/libs/constants.ts`;
9426's master note now counts both server-side families (`getParentForCreate ->
assertHasValidParentType`, `assertParent`); 9090's fourth file is a
local-variable extraction, not a cast; 9368's eleven specs are scoped as
7 cht-datasource + 2 api + 2 integration.
**9426's #6363 gloss (hand edit, line outside the diff hunks):** the issue is
"Prevent and/or merge duplicate contacts"; hierarchy appears only in its
move-between-parents proposal (its item 2, verified in the body). The
related_issues decision from #122 is applied as given: all seven backfilled
values stay, including the three with no promoted entry.
(Committed with --no-verify: .husky/_/husky.sh fails to load in this worktree;
validate-schema and verify-drafts run manually.)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(#135): the hedge sweep was case-sensitive, and claimed it was not (contacts)
Fixing 9281's Testing exposed a lowercase "added/updated" -- which the commit
that "closed the class corpus-wide" had claimed eliminated. That sweep grepped
`Added/updated|Updated/added|added/modified`, case-sensitively, and its closing
claim ("No Added/updated ... remains in the corpus") was itself an unverified
universal. A case-insensitive grep found six more.
Each resolved against its anchor's real file list, not reworded around:
10173 7 M, 0 A -> "all seven spec files modified, none added"
10897 1 M -> "modified, not added"
9266 8 M -> "all eight ... modified, none added"
9090 A/A/M/M by name -> new person + data-context specs, updated
server-utils + sentinel purging
9177 5 A / 17 M -> the five added named (three place datasource
specs, api place controller spec,
integration spec), the rest listed as updated
9625 17 A / 18 M / 1 D -> counts stated, the deletion named
(test/libs/contact.spec.ts), contact/report
controller specs marked new
9281's own instance is in the previous commit, resolved the same way (all M at
bf8a77dae).
Co-Authored-By: Cla…
Review 4745314944 on 112b4c7: five inline accuracy items and two nitpicks. All five held when re-derived at each draft's own anchor, and are applied: 8738 con_create_people -> can_create_people everywhere (0 hits at 91c934920; the PR uses can_create_people/can_create_places). 9833 getOidc -> oidcAuthorize (GET login/oidc/authorize) + oidcLogin (GET login/oidc); routing.js:305-306 at bd9b23243. getOidc came from the #9765 issue body and never shipped. 9901 the per-user field is the `oidc` flag; `oidc_provider` is the app_settings provider config (token-login.js:286 at f74d663dc). 9900 a text input (#sso-login) bound to editUserModel.oidc_username, not a checkbox setting oidc_provider (edit_user.html:80). 10795 hasPermissions/hasAnyPermission read ctx.settings.getAll() (auth.js:96/:132); chtRolesSettings is only filterRolesByConfigured's. Nitpicks: Domain Rationale rubric leakage was in eight drafts, not four (9731 10599 10994 9128 + 8738 9107 9109 10795); all rewritten to argue from the code. 10502 related_issues gains #6784, symmetric with 10414. Then every draft went through the gate suite the landed domains converged on, plus a per-draft audit against cht-core (anchor, master, and the commits between). What the review did not name, worst first: **Mechanism inverted or invented (no gate sees these).** 8776 said the PR added admin detection; it removed the #7410 admin-password path and throws "Admin passwords must be changed manually in the database" — and the issue's UI error ("Password is not correct.") was described as an apparent success. 8738's Problem had the polarity backwards (#8730: users with only can_create_people could NOT create people). 9723 "clears browser history" is a pushState that adds an entry. 9131 named the wrong redirect vector. 10414 said the Safari message was informational; the PR hides the login fields. 9107 named fields that are not protected and called a deleted service "companion changes". 10795's user-management/api hasPermission() came from the PR description; the PR deleted both, and its "backwards compatible" claim is false (no roles configured -> non-admins get nothing). 10004 has no settings validation, only two runtime throws, one after the user docs are saved. 9887's guard runs after CouchDB validates credentials, not before. 9961's email check is the api's getIdToken, not user-management. **Fabricated names.** 10827: getRtlLocales, data-rtl-locales (x3) and rtlLocales exist nowhere in the tree; rewritten from the diff (rtl on each locale entry, data-rtl="true", <html dir="{{ defaultDir }}">, setDirection()). 9877: buildAuthorizationUrl — the PR builds no URL and openid-client was not yet a dependency at d60a08fdd. **Provenance.** Eight drafts are epic children and said nothing about it: 9800 9833 9877 9887 9900 9901 9961 squash-merged into 9735_sso (-> master as #9955, 2cbe9c109) and 10994 into 10224-ui-extensions (-> #11050, 180c29ecf). Each now carries the "Epic child." banner with the refs/pull fetch that makes its source_sha resolvable; five also carry "Renamed/Superseded before landing" banners for what #9961 changed on the branch. 9955's filename token (9760) contradicted its verified key (#9735, PR body "Closes #9735"): renamed to 9955-feat9735-…. **Wrong file / wrong status / drift.** Twenty ground-claims findings at the baseline (hasPermission, users_by_field, password_change_required, can_skip_password_change, loginByToken attributed to the wrong files; package.json, facility.js, users.spec.js added-vs-modified) plus five stale-as-written paths (authorization.js moved by #10823, purge.wdio-spec.js by #11139, add-user.wdio-spec.js renamed by #10153, a migration test removed by #10187). "Added/updated" hedges replaced by the real statuses. 8857: every pouchdb-session-authentication registration was removed on master by the PouchDB 9 upgrade (#9988). stale: true on 17. **Linkage and classification.** related_issues 9 -> 22 drafts (+39), symmetric within the domain; PRs cited as issues relabelled; every gloss quotes the real title. Category changed only where the issue's Type label says so (9901 9961 8735 10414 9422); domainFit -> weak on 9671 10827 9422 (authentication is the least-bad home), Fit lines agree. 8857 source_prs += PR #9030 (chore(#8338)) and it gains #9102, the PR's own tracking issue. Our own repairs produced 39 delta-gate findings (20 ungrounded, 11 unverifiable, 8 stale) before they converged — mostly paraphrased literals, CSS #id forms where templates spell id="…", and create verbs governing the wrong path. All re-worded to the form that greps; three were tool gaps (fixed on memory/draft-verification, with the leakage patterns). A final sweep expanded ~110 bare or relative file names in prose (users.js, login.spec.js, libs/facility.js, test/auth.spec.js …) to full paths: a bare basename resolves only at the repo root, so which sentences a sampled pass flagged depended on luck. **Regression pass over this commit's own changes.** Every sentence it changed (427 of 776; 349 untouched) went to independent reviewers told to prove the old text right before accepting a correction. They found 2 regressions, both 9887: issue #9763 edits its body with strikethrough (~~get~~ post, ~~oidc_provider~~ oidc) and the rewrite had read the rendered page — restored from the raw body. 2 losses restored (9955: why the client secret is kept out of the replicated `settings` doc; 9800: the payload boolean is the one stored on the `_users` doc). Four over- or mis-stated new sentences corrected (9900, 9128, 9437, 10414). Reverted to the reviewed value: category on 8857 and 10994 (no issue label supports a change) and domainFit on 10414 and 10502 (both decide whether a login can proceed in Safari). validate-schema 98/0; verify-drafts whole corpus 0 auth findings, auth with history 0/0, --online 0 blocking / 0 unverified; ground-claims --added-lines 609/609 grounded; on frozen bytes, ground-claims x3 (0 ungrounded, 0 unverifiable, 0 stale each) and check-coherence x3 (0 contradictions, 34/34 checked each), none degraded. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PR #131 — reply to round 2 + request for re-reviewAll five inline items addressed. All five held when I re-derived them at each draft's own Then, instead of stopping at the review, I ran this domain through everything the other five Your five items
One refinement to your 10795 wording: the inner function is Nitpicks. Rubric leakage: 9731, 10599, 10994, 9128 as you listed, plus 8738, 9107, 9109 and Gate ledger (frozen bytes; working tree == the pushed commit)
Passes that did not count: the baseline at Worst firstMechanism inverted or invented. No gate sees this class. All of it was found by reading the
Fabricated names, the class your review was about:
Provenance. Eight drafts are epic children and said nothing about it:
Each draft now carries the Epic-child banner and the 9955: its filename token (9760) contradicted its verified key (#9735), so the file is Wrong file, wrong status, drift. The baseline's 20
It also found five stale-as-written paths:
Beyond those, 8857's plugin registration was removed from master entirely by the PouchDB 9 Linkage and classification.
My own repairs produced 39 delta-gate findings (20 ungrounded, 11 unverifiable, 8 stale) A regression pass over my own changes. The gates prove what the drafts now say; they cannot Disclosed, not fixed
Still open outside this PR (landed corpus)
|
sugat009
left a comment
There was a problem hiding this comment.
Round 3 is a large improvement. I checked all 34 drafts sentence by sentence against the PR diffs, the code at each anchor, and current master (1,238 sentences; 1,192 verified true). An independent fact-check re-verified every finding. All five of my round-2 items are resolved, no draft regressed, and every batch-level claim in your comment holds.
Gates at 118a7bd: the merge with main is clean; build, lint and check-pending are green; validate-schema passes 201; 1,171 tests pass.
I request changes for one wrong mechanism and 14 smaller accuracy items. All 15 are inline, each with a suggestion:
- blocking: 8738 says the old actionbar's contacts-list pane lost create-person actions. That pane never listed person types.
- non-blocking (14): two more sentences in 8738; a wrong Node version in 8924; an invented remedy in 8735; an overstated gap in 9016; a false "never" in 9731; an incomplete 401 list in 9833; an omitted design rationale in 10502; dropped verification evidence in 10994; and missing stale notes or flags in 8857, 9126, 9128 (two) and 9955.
Two of 94 quoted issue titles are truncated (both in 9955). Two policy items are not for this PR (stale versus confidence in the schema, and stale-as-written as a banner label in five drafts); I will raise both separately.
Nits (48, not blocking)
- 10004:53 call-path compression: the api controllers called
createUsers,createMultiFacilityUserandupdateUser, notcreateUserdirectly. - 10153:54 the widget condition is one async step too early (the widget runs in the second
.thenafterContactTypes.getAll()). - 10153:70 Related Files omits
admin/src/js/controllers/edit-user.js, the PR's only modified production file (it appears only underentities; same gap as the 8928 and 9887 nits). - 10153:83 missing related issue #10155 (filed by the PR author while working this issue; still open — PR #10824 attempted it but closed unmerged).
- 10414:67 "six of the nine bundled locales" has drifted: master has a tenth locale (pt, #10555) and the key is in seven files.
- 10502:64 "runs first" overstates the execution order (locale setup precedes
checkUnsupportedBrowser()). - 10502:78 round 3 dropped two true facts from round 2 (the reviewer's manual Safari-spoofed check).
- 10599:87 the same spec also gained direct
isDbAdmin/hasOnlineRoletests. - 10599:88 half right:
USER_ROLES.ONLINEalready existed; #10747 addedADMINandCOUCHDB_ADMIN. Only one catch-all stub was replaced. - 10795:71 subject and object swapped: the factory returns a promise that resolves to the data-context object. The second clause is vacuous for
search.js. - 10795:80 round 2's provenance (failing e2e tests surfaced the passivity requirement) was dropped.
- 10827:64
setDirection()is the second statement ofbaseTranslate(), after the locale guard. - 10827:69 plural overstates: one of the two RTL rules targets an absolutely-positioned element.
- 8675:45 two open issues report defects in this exact mechanism (#10705 and the second rate-limiter issue) and are not linked.
- 8735:53 #8928 is a PR cited bare; your convention labels it "PR #8928" (issue #8877).
- 8735:73 round 3 removed every outcome statement; one neutral sentence on the measured effect would help.
- 8776:89 plural: one
describeblock with oneitwas removed. - 8857:27
source_prslists #9030, but Related Files and Testing cover only #8857's diff. - 8857:68 imprecise: the root
package.jsonalready declared the package at the anchor. - 8924:50 three real connections in the issue record are unrecorded (#6250 and two others).
- 8924:80 Related Files has no added/modified labels; four files were created.
- 8924:87 the test title was renamed on master by PR #9746 (optional note).
- 8928:84
api/tests/integration/migrations/utils.jswas also modified and is not listed. - 9016:51 imprecise: both
GET /api/v1/usersand/api/v2/usersexisted, and v2 already had thefacility_id/contact_idfilters. - 9107:38
admin/src/js/services/privacy-policies.jsis anentitiespath that this PR deleted. - 9107:47 the issue body cites #8337 (open) and one more issue; neither is linked.
- 9109:41 the prose names PR #11050 (#10224) and PR #10187 (#9639);
related_issuesis empty. - 9109:72 "creation cases" reach the ddoc with
oldDoc === newDoc(the helpers default the argument). - 9126:107 the same PR also skipped the whole
POST/GET api/v2/usersintegration block (re-enabled by #9128). - 9422:90 the e2e case drives the modal in its Add User state, not Edit User.
- 9671:35
api/src/public-error.jsis anentitiespath that this PR deleted. - 9723:52 the admin
MainCtrlfetch override also callsnavigateToLogin();pushStatediscards forward entries. - 9731:67 "lacks full access (a user changing their own password)" equates two different things (see the inline comment at :81).
- 9731:71 a
_admindoc is never flagged, becausehasAllPermissions()returns true for db admins first. - 9800:65 there is no
/api/v2update route at the anchor (update exists for v1 and v3). - 9800:81 the draft never says when or why
oidcbecameoidc_username. - 9877:57
stale-as-writtenis a tool term, not a cht-core term (also in 9800, 9833, 9887, 9901). - 9887:92 Related Files lists only the two spec files;
api/src/controllers/login.jsappears only underentities. - 9900:55 the sha does not resolve because the squash lives only on the epic branch, not because the PR number is absent from master's log.
- 9955:125 only the login integration spec and the SSO e2e spec use the mock provider.
- 9955:133 truncated title: #9763 ends with "for user".
- 9955:134 truncated title: #9764 ends with "for user." (with the period).
- 9955:152 "strong-fit domain" restates the Fit line; name the rejected alternative instead.
- 9961:60
stale: truerests only on a renamed test title; decide whether that counts. - 9961:126 "#9938 referenced by this PR" understates: the PR body says "Closes #9938".
|
|
||
| ## Problem | ||
|
|
||
| In the contacts-page FAB and in the create menu on the contacts-list (left) pane of the old actionbar, which at this PR non-admin users with `can_view_old_action_bar` saw (the old actionbar and that permission were removed on master by PR #9361), create-contact actions were gated on `can_create_places` (together with `can_edit`), whether the contact type being created was a person or a place. A user granted `can_create_people` but not `can_create_places` therefore lost those create-person actions: the issue reports users unable to enroll patients after upgrading to 4.4.1, with granting `can_create_places` as the workaround. Conversely, a user with `can_create_places` but not `can_create_people` was offered person creation in the FAB. The old actionbar's contact-detail (right-pane) create actions were not affected; they already took a per-type `can_create_people`/`can_create_places` permission from webapp/src/ts/modules/contacts/contacts-content.component.ts. |
There was a problem hiding this comment.
issue (blocking): The contacts-list (left) pane of the old actionbar never listed a person type, so it could not lose a create-person action. At the anchor, contacts.component.ts filters children with children.filter(child => !child.person) in both branches (lines 283-298). The left action bar and the list-page FAB receive that filtered list (allowedChildPlaces, lines 437 and 473). The pane's label is "Add place". The same code is at 4.4.1 and on master. Only the contact-detail FAB lists person types: contacts-content.component.ts feeds it getChildren(selectedContact.type.id) unfiltered (line 318). The PR's own e2e spec drives contact-detail views only. The pre-PR gating hid the whole create container from users without can_create_places, Export link included. The suggestion rewrites the first two sentences and keeps the rest.
| In the contacts-page FAB and in the create menu on the contacts-list (left) pane of the old actionbar, which at this PR non-admin users with `can_view_old_action_bar` saw (the old actionbar and that permission were removed on master by PR #9361), create-contact actions were gated on `can_create_places` (together with `can_edit`), whether the contact type being created was a person or a place. A user granted `can_create_people` but not `can_create_places` therefore lost those create-person actions: the issue reports users unable to enroll patients after upgrading to 4.4.1, with granting `can_create_places` as the workaround. Conversely, a user with `can_create_places` but not `can_create_people` was offered person creation in the FAB. The old actionbar's contact-detail (right-pane) create actions were not affected; they already took a per-type `can_create_people`/`can_create_places` permission from webapp/src/ts/modules/contacts/contacts-content.component.ts. | |
| In the contacts FAB, `FastActionButtonService.getContactFormActions()` gated every child contact type on `can_create_places` (together with `can_edit`), whether the type was a person or a place. Only the contact-detail FAB lists person types (webapp/src/ts/modules/contacts/contacts-content.component.ts feeds it `getChildren(selectedContact.type.id)` unfiltered), so a user granted `can_create_people` but not `can_create_places` lost the create-person actions there: the issue reports users unable to enroll patients after upgrading to 4.4.1, with granting `can_create_places` as the workaround. The contacts-list (left) pane of the old actionbar, which at this PR non-admin users with `can_view_old_action_bar` saw (the old actionbar and that permission were removed on master by PR #9361), listed place types only (`getChildren()` in webapp/src/ts/modules/contacts/contacts.component.ts drops `person` types); its create links required `can_edit` and `can_create_places`, and the container that holds them and the Export link required `can_create_places`, so users without that permission also lost the Export link. Conversely, a user with `can_create_places` but not `can_create_people` was offered person creation in the FAB. The old actionbar's contact-detail (right-pane) create actions were not affected; they already took a per-type `can_create_people`/`can_create_places` permission from webapp/src/ts/modules/contacts/contacts-content.component.ts. |
| issueUrl: https://github.com/medic/cht-core/issues/8730 | ||
| title: Fix contact FAB and actionbar permission checks to honour can_create_people | ||
| lastUpdated: '2026-09-29' | ||
| summary: The contacts-page FAB and the old actionbar's contacts-list create menu gated contact creation only on `can_create_places`, so a user with `can_create_people` but not `can_create_places` could not create people from them. The fix makes the FAB require `can_create_people` for person contact types and `can_create_places` for place types, and lets the old actionbar's contacts-list create links show for either permission. |
There was a problem hiding this comment.
issue (non-blocking): The summary repeats the same error: "could not create people from them". The contacts-list create menu offered place types only, so nobody could create people from it, before or after the PR. The suggestion keeps the FAB half and corrects the actionbar half.
| summary: The contacts-page FAB and the old actionbar's contacts-list create menu gated contact creation only on `can_create_places`, so a user with `can_create_people` but not `can_create_places` could not create people from them. The fix makes the FAB require `can_create_people` for person contact types and `can_create_places` for place types, and lets the old actionbar's contacts-list create links show for either permission. | |
| summary: The contacts FAB and the old actionbar's contacts-list create menu gated contact creation only on `can_create_places`, so a user with `can_create_people` but not `can_create_places` could not create people from the contact-detail FAB, and the old actionbar's contacts-list create container (place types only, Export link included) was hidden from them. The fix makes the FAB require `can_create_people` for person contact types and `can_create_places` for place types, and lets the old actionbar's contacts-list create links show for either permission. |
|
|
||
| ## Root Cause | ||
|
|
||
| In `FastActionButtonService.getContactFormActions()` (`webapp/src/ts/services/fast-action-button.service.ts`), each child contact type's `canDisplay` called `this.authService.has(['can_edit', 'can_create_places'])`, with no branch on the contact type. In the old actionbar's contacts-tab left pane (`webapp/src/ts/components/actionbar/actionbar.component.html`), the create container was gated by `[mmAuthAny]="[ actionBar?.left?.childPlaces && 'can_create_places' ]"` and each create link or menu by `mmAuth="can_edit,can_create_places"`; the template did not mention `can_create_people` at all. |
There was a problem hiding this comment.
issue (non-blocking): Each code fact here is exact (I matched the pre-PR attributes at 91c9349^). But under Root Cause the paragraph reads as a cause of lost person creation. The pane had no person action to lose; the gating hid the whole container. The suggestion appends one sentence that says this.
| In `FastActionButtonService.getContactFormActions()` (`webapp/src/ts/services/fast-action-button.service.ts`), each child contact type's `canDisplay` called `this.authService.has(['can_edit', 'can_create_places'])`, with no branch on the contact type. In the old actionbar's contacts-tab left pane (`webapp/src/ts/components/actionbar/actionbar.component.html`), the create container was gated by `[mmAuthAny]="[ actionBar?.left?.childPlaces && 'can_create_places' ]"` and each create link or menu by `mmAuth="can_edit,can_create_places"`; the template did not mention `can_create_people` at all. | |
| In `FastActionButtonService.getContactFormActions()` (`webapp/src/ts/services/fast-action-button.service.ts`), each child contact type's `canDisplay` called `this.authService.has(['can_edit', 'can_create_places'])`, with no branch on the contact type. In the old actionbar's contacts-tab left pane (`webapp/src/ts/components/actionbar/actionbar.component.html`), the create container was gated by `[mmAuthAny]="[ actionBar?.left?.childPlaces && 'can_create_places' ]"` and each create link or menu by `mmAuth="can_edit,can_create_places"`; the template did not mention `can_create_people` at all. That pane listed place types only (`getChildren()` in webapp/src/ts/modules/contacts/contacts.component.ts filters out `person` types), so this gating hid no person action; it hid the whole container, Export link included, from users without `can_create_places`. |
|
|
||
| ## Problem | ||
|
|
||
| When authenticating a user, the API copied all headers from the original request onto a GET /_session request sent to CouchDB. If the original request was a POST carrying a content-length header, that header was forwarded onto the bodyless GET. Under Node 19 (which enables keep-alive by default), api's connection to HAProxy was reused, and HAProxy, treating the bodyless GET as unfinished, consumed content-length characters of the following request on that connection, producing an invalid request and a 400 status code. It was observed only when hitting API directly or via the AWS load balancer, never through nginx. Issue #8868 reported it after upgrading to 4.6.0-beta.2: users could not log in to the webapp and authenticated REST API calls failed with 400. |
There was a problem hiding this comment.
issue (non-blocking): The api image never ran Node 19. api/Dockerfile has nodejs~=16 at 4.5.2 and nodejs~=20 at every 4.6.0 tag and at the anchor (PR #8824 for #7993, commit 900218e9b, 2024-02-01). The issue comments name the bump to Node 20 as the cause. Node 19 only introduced the keep-alive default. The mechanism itself is correct. The same version error is in the node-19 tag (line 27) and in the gloss at line 91. Please also rename the tag node-19 to node-20, and in the line 91 gloss write: "under the keep-alive default that Node 19 introduced (the api ran Node 20 from the 4.6.0 line)".
| When authenticating a user, the API copied all headers from the original request onto a GET /_session request sent to CouchDB. If the original request was a POST carrying a content-length header, that header was forwarded onto the bodyless GET. Under Node 19 (which enables keep-alive by default), api's connection to HAProxy was reused, and HAProxy, treating the bodyless GET as unfinished, consumed content-length characters of the following request on that connection, producing an invalid request and a 400 status code. It was observed only when hitting API directly or via the AWS load balancer, never through nginx. Issue #8868 reported it after upgrading to 4.6.0-beta.2: users could not log in to the webapp and authenticated REST API calls failed with 400. | |
| When authenticating a user, the API copied all headers from the original request onto a GET /_session request sent to CouchDB. If the original request was a POST carrying a content-length header, that header was forwarded onto the bodyless GET. Under Node 20 (the 4.6.0 line moved the api image from Node 16 to Node 20 in PR #8824 for #7993, and Node 19 had made outgoing HTTP keep-alive the default), api's connection to HAProxy was reused, and HAProxy, treating the bodyless GET as unfinished, consumed content-length characters of the following request on that connection, producing an invalid request and a 400 status code. It was observed only when hitting API directly or via the AWS load balancer, never through nginx. Issue #8868 reported it after upgrading to 4.6.0-beta.2: users could not log in to the webapp and authenticated REST API calls failed with 400. |
|
|
||
| ## Design Choices | ||
|
|
||
| The author deliberately shipped a simple, high-impact partial fix: it removes the cost that scaled with the total number of contacts but acknowledges the endpoint will still degrade with very large numbers of users (which would require additional per-user scoping). Low-hanging-fruit optimization chosen over a full rewrite. |
There was a problem hiding this comment.
issue (non-blocking): "(which would require additional per-user scoping)" names a remedy that is in no record. The PR body only says the endpoint "will still perform badly with large numbers of users". The author's issue comments name streaming or pagination as the further work (2024-02-07). The fix also already scopes the facility fetch to the ids the returned users reference; the remaining cost is O(users), which more scoping cannot reduce.
| The author deliberately shipped a simple, high-impact partial fix: it removes the cost that scaled with the total number of contacts but acknowledges the endpoint will still degrade with very large numbers of users (which would require additional per-user scoping). Low-hanging-fruit optimization chosen over a full rewrite. | |
| The author deliberately shipped a simple, high-impact partial fix: it removes the cost that scaled with the total number of contacts but acknowledges the endpoint will still degrade with very large numbers of users (the issue thread names pagination or streaming as the further work large deployments would need). Low-hanging-fruit optimization chosen over a full rewrite. |
|
|
||
| ## Solution | ||
|
|
||
| Added the `pouchdb-session-authentication` package (`^1.1.0`) as a dependency in the existing api/package.json, sentinel/package.json and root package.json (plus their lockfiles) and registered it with `PouchDB.plugin(require('pouchdb-session-authentication'))`, immediately after `pouchdb-adapter-http`, in api/src/db.js and sentinel/src/db.js, in the test utilities tests/utils/index.js and api/tests/integration/migrations/utils.js, in scripts/get_users_meta_docs.js, scripts/conflicts/auto-resolve.js and scripts/conflicts/diff.js, and in tests/scalability/replicate-real-world-docs/add-docs-to-remote.js. No PouchDB constructor call changed: per the plugin's README it takes the credentials already present in the database URL (or an `auth` option), generates and stores a session cookie per user + CouchDB server pair, appends it as a `Cookie` header to outgoing requests, and regenerates the cookie and retries when it expires. Only PouchDB HTTP traffic is affected; requests the services make outside PouchDB (for example with `request-promise-native`) are unchanged. |
There was a problem hiding this comment.
suggestion (non-blocking): True at the anchor, but the draft names request-promise-native as current, and api and sentinel no longer use it: api/src/db.js and sentinel/src/db.js switched to @medic/couch-request in PR #8833 (2402acdf9, 2024-04-23), five days after this PR. The stale notes cover only the plugin registrations and the two scripts.
| Added the `pouchdb-session-authentication` package (`^1.1.0`) as a dependency in the existing api/package.json, sentinel/package.json and root package.json (plus their lockfiles) and registered it with `PouchDB.plugin(require('pouchdb-session-authentication'))`, immediately after `pouchdb-adapter-http`, in api/src/db.js and sentinel/src/db.js, in the test utilities tests/utils/index.js and api/tests/integration/migrations/utils.js, in scripts/get_users_meta_docs.js, scripts/conflicts/auto-resolve.js and scripts/conflicts/diff.js, and in tests/scalability/replicate-real-world-docs/add-docs-to-remote.js. No PouchDB constructor call changed: per the plugin's README it takes the credentials already present in the database URL (or an `auth` option), generates and stores a session cookie per user + CouchDB server pair, appends it as a `Cookie` header to outgoing requests, and regenerates the cookie and retries when it expires. Only PouchDB HTTP traffic is affected; requests the services make outside PouchDB (for example with `request-promise-native`) are unchanged. | |
| Added the `pouchdb-session-authentication` package (`^1.1.0`) as a dependency in the existing api/package.json, sentinel/package.json and root package.json (plus their lockfiles) and registered it with `PouchDB.plugin(require('pouchdb-session-authentication'))`, immediately after `pouchdb-adapter-http`, in api/src/db.js and sentinel/src/db.js, in the test utilities tests/utils/index.js and api/tests/integration/migrations/utils.js, in scripts/get_users_meta_docs.js, scripts/conflicts/auto-resolve.js and scripts/conflicts/diff.js, and in tests/scalability/replicate-real-world-docs/add-docs-to-remote.js. No PouchDB constructor call changed: per the plugin's README it takes the credentials already present in the database URL (or an `auth` option), generates and stores a session cookie per user + CouchDB server pair, appends it as a `Cookie` header to outgoing requests, and regenerates the cookie and retries when it expires. Only PouchDB HTTP traffic is affected; requests the services made outside PouchDB at this PR's anchor (for example with `request-promise-native`) were not changed. (On master api and sentinel no longer use request-promise-native: api/src/db.js and sentinel/src/db.js switched to `@medic/couch-request` in PR #8833, `2402acdf9`, 2024-04-23.) |
|
|
||
| ## Testing | ||
|
|
||
| Updated the existing Mocha unit specs for the users controller, authorization service, user-management roles and users, and contacts places. Integration: replication (`should return all relevant ids with multiple facilities`, plus depth and sensitive-report cases for multiple facilities) and bulk-docs (`should filter offline user requests with multi facility`) cover multi-facility doc download/upload permissions; the users controller spec adds `POST api/v3/users` cases (create with multiple facilities, refusal without the permission, adding facilities on edit, malformed facilities); the sentinel create-user-for-contacts test now expects an array `facility_id`; tests/integration/api/controllers/login.spec.js only gains a random `X-Forwarded-For` header per request. In the wdio e2e suites, db-sync now syncs as a user with two facilities and replace-user expects an array `facility_id`, while edit-person-home-place, person-under-area, offline-user all-permissions and target-aggregates were set to `describe.skip` (PR #9099 re-enabled target-aggregates; PR #9128 re-enabled edit-person-home-place and person-under-area). Karma specs for contacts.effects (child places loaded for a multi-facility user, not for a single-facility user's own place) and contacts.component (single vs multi-facility homeplaces) cover the display branching; contacts-content.component.spec only switches its `getUserFacilityId` selector fixture to an array (PR #9094). The target-aggregates.service spec covers `isEnabled()` returning false for more than one facility, analytics-modules.service.spec stubs `isEnabled()`, and the target-aggregates e2e adds `should disable content when user has many facilities associated` (PR #9099). |
There was a problem hiding this comment.
suggestion (non-blocking): Historically true, but PR #9221 (74b43147e, 2024-07-12) removed tests/e2e/default/contacts/edit-person-home-place.wdio-spec.js from master; it moved the one test into tests/e2e/default/contacts/edit.wdio-spec.js. The parenthesis also stops before PR #9258 re-enabled all-permissions (2024-07-23), so a reader infers that block is still skipped. The draft's stale notes name only authorization.js and hasAllPermissions.
| Updated the existing Mocha unit specs for the users controller, authorization service, user-management roles and users, and contacts places. Integration: replication (`should return all relevant ids with multiple facilities`, plus depth and sensitive-report cases for multiple facilities) and bulk-docs (`should filter offline user requests with multi facility`) cover multi-facility doc download/upload permissions; the users controller spec adds `POST api/v3/users` cases (create with multiple facilities, refusal without the permission, adding facilities on edit, malformed facilities); the sentinel create-user-for-contacts test now expects an array `facility_id`; tests/integration/api/controllers/login.spec.js only gains a random `X-Forwarded-For` header per request. In the wdio e2e suites, db-sync now syncs as a user with two facilities and replace-user expects an array `facility_id`, while edit-person-home-place, person-under-area, offline-user all-permissions and target-aggregates were set to `describe.skip` (PR #9099 re-enabled target-aggregates; PR #9128 re-enabled edit-person-home-place and person-under-area). Karma specs for contacts.effects (child places loaded for a multi-facility user, not for a single-facility user's own place) and contacts.component (single vs multi-facility homeplaces) cover the display branching; contacts-content.component.spec only switches its `getUserFacilityId` selector fixture to an array (PR #9094). The target-aggregates.service spec covers `isEnabled()` returning false for more than one facility, analytics-modules.service.spec stubs `isEnabled()`, and the target-aggregates e2e adds `should disable content when user has many facilities associated` (PR #9099). | |
| Updated the existing Mocha unit specs for the users controller, authorization service, user-management roles and users, and contacts places. Integration: replication (`should return all relevant ids with multiple facilities`, plus depth and sensitive-report cases for multiple facilities) and bulk-docs (`should filter offline user requests with multi facility`) cover multi-facility doc download/upload permissions; the users controller spec adds `POST api/v3/users` cases (create with multiple facilities, refusal without the permission, adding facilities on edit, malformed facilities); the sentinel create-user-for-contacts test now expects an array `facility_id`; tests/integration/api/controllers/login.spec.js only gains a random `X-Forwarded-For` header per request. In the wdio e2e suites, db-sync now syncs as a user with two facilities and replace-user expects an array `facility_id`, while edit-person-home-place, person-under-area, offline-user all-permissions and target-aggregates were set to `describe.skip` (PR #9099 re-enabled target-aggregates; PR #9128 re-enabled edit-person-home-place and person-under-area; PR #9258 re-enabled all-permissions; PR #9221 later removed tests/e2e/default/contacts/edit-person-home-place.wdio-spec.js from master, moving its one test into tests/e2e/default/contacts/edit.wdio-spec.js as `should sync and update the offline user's home place`). Karma specs for contacts.effects (child places loaded for a multi-facility user, not for a single-facility user's own place) and contacts.component (single vs multi-facility homeplaces) cover the display branching; contacts-content.component.spec only switches its `getUserFacilityId` selector fixture to an array (PR #9094). The target-aggregates.service spec covers `isEnabled()` returning false for more than one facility, analytics-modules.service.spec stubs `isEnabled()`, and the target-aggregates e2e adds `should disable content when user has many facilities associated` (PR #9099). |
| related_issues: | ||
| - cht-core-6543 | ||
| - cht-core-9203 | ||
| stale: false |
There was a problem hiding this comment.
suggestion (non-blocking): stale: false, but the Testing section (line 94) names tests/e2e/default/users/add-user.wdio-spec.js, which PR #10153 renamed to tests/e2e/default/users/user.wdio-spec.js (2025-07-29). Both cited test names now live there (lines 80 and 204). In this same commit, 9422 (line 81) and 9900 (line 91) annotate this exact rename and set stale: true; your comment lists it among the stale-as-written paths. 9128 has neither the note nor the flag. Keep confidence: medium. My comment on line 94 carries the Testing wording.
| stale: false | |
| stale: true |
|
|
||
| ## Testing | ||
|
|
||
| Updated admin unit tests: admin/tests/unit/controllers/edit-user.spec.js adds `should allow only user with permission to have multiple places` and `user is updated with multiple places`, and admin/tests/unit/services/update-user.spec.js now expects CreateUser to post to `/api/v3/users`; the shared-libs contact-types-utils tests cover `isSameContactType`. In tests/integration/api/controllers/users.spec.js the `POST/GET api/v2/users` suite was re-enabled (it had been `describe.skip`); no cases were added. E2E: the new tests/e2e/default/contacts/delete-assigned-place.wdio-spec.js logs in as a user with two places and checks that the Delete menu option is disabled on one of them; tests/e2e/default/users/add-user.wdio-spec.js adds `should add user with multiple places with permission` and `should require user to have permission for multiple places` (checks the not-allowed message); edit-person-home-place and person-under-area were re-enabled from `describe.skip`; the users page object gained multiselect place input. |
There was a problem hiding this comment.
suggestion (non-blocking): Two files in this line drifted on master. add-user.wdio-spec.js was renamed by PR #10153 (see my comment on line 56). edit-person-home-place.wdio-spec.js was removed by PR #9221, which moved its only test into edit.wdio-spec.js under a new name; the same commit annotates removed specs in 8928 and 10795, and "deleted" alone would misstate the coverage. The suggestion applies both notes in one edit, so it also carries the rename wording from my line 56 comment.
| Updated admin unit tests: admin/tests/unit/controllers/edit-user.spec.js adds `should allow only user with permission to have multiple places` and `user is updated with multiple places`, and admin/tests/unit/services/update-user.spec.js now expects CreateUser to post to `/api/v3/users`; the shared-libs contact-types-utils tests cover `isSameContactType`. In tests/integration/api/controllers/users.spec.js the `POST/GET api/v2/users` suite was re-enabled (it had been `describe.skip`); no cases were added. E2E: the new tests/e2e/default/contacts/delete-assigned-place.wdio-spec.js logs in as a user with two places and checks that the Delete menu option is disabled on one of them; tests/e2e/default/users/add-user.wdio-spec.js adds `should add user with multiple places with permission` and `should require user to have permission for multiple places` (checks the not-allowed message); edit-person-home-place and person-under-area were re-enabled from `describe.skip`; the users page object gained multiselect place input. | |
| Updated admin unit tests: admin/tests/unit/controllers/edit-user.spec.js adds `should allow only user with permission to have multiple places` and `user is updated with multiple places`, and admin/tests/unit/services/update-user.spec.js now expects CreateUser to post to `/api/v3/users`; the shared-libs contact-types-utils tests cover `isSameContactType`. In tests/integration/api/controllers/users.spec.js the `POST/GET api/v2/users` suite was re-enabled (it had been `describe.skip`); no cases were added. E2E: the new tests/e2e/default/contacts/delete-assigned-place.wdio-spec.js logs in as a user with two places and checks that the Delete menu option is disabled on one of them; tests/e2e/default/users/add-user.wdio-spec.js (present at this PR's anchor; renamed on master to tests/e2e/default/users/user.wdio-spec.js by PR #10153, where both cases still exist) adds `should add user with multiple places with permission` and `should require user to have permission for multiple places` (checks the not-allowed message); tests/e2e/default/contacts/edit-person-home-place.wdio-spec.js (present at this PR's anchor; removed on master by PR #9221, which moved its only test into tests/e2e/default/contacts/edit.wdio-spec.js as `should sync and update the offline user's home place`) and tests/e2e/default/contacts/person-under-area.wdio-spec.js (still on master) were re-enabled from `describe.skip`; the users page object gained multiselect place input. |
| - cht-core-9981 | ||
| - cht-core-9983 | ||
| - cht-core-10062 | ||
| stale: false |
There was a problem hiding this comment.
suggestion (non-blocking): stale: false, but the draft names serverUtils.getAppUrl(req) (line 146) and lists api/src/server-utils.js (line 108), and that function was this squash's only change to the file. PR #10004 (1dfd7e60d, 2025-08-22) removed it and replaced its use in oidcLogin and oidcAuthorize with a module-local getAppUrl(). Your rule in this commit is a scoping note AND stale: true when named code no longer exists as described: 10599 (DB_ADMIN_ROLES relocated) and 9131 (resolveUrl body replaced) both do exactly that. The text at line 146 is accurate and can stay. Keep confidence: medium. Optional, in Related Files: - api/src/server-utils.js (its getAppUrl, this squash's only change to the file, was removed on master by PR #10004).
| stale: false | |
| stale: true |
Review 5415306093 on #131 (at 118a7bd): one blocking and fourteen non-blocking inline items, each with a suggestion. All fifteen suggestions are applied verbatim, plus the wording the 8924 comment gave for its tag (node-19 -> node-20) and its line-91 gloss. 8738 blocking: the old actionbar's contacts-list pane listed place types only; only the contact-detail FAB lists person types (summary, Problem, Root Cause). 8924 the api ran Node 20 from 4.6.0 (PR #8824), not Node 19. 8735 remedy is pagination or streaming, per the issue thread. 9016 by-name reads existed; none returned the getList/mapUsers shape. 9731 the password-reset flag is keyed on caller permissions, not self-versus-other. 9833 three sources of the 401 -> ssouserinvalid mapping, not one. 10502 why the token gate is separate from checkUnsupportedBrowser(). 10994 restores the manual curl verification from the PR description. 8857, 9126, 9128 (x2), 9955 master-drift notes / stale: true. Nits applied where the review gave the exact text: 8735:53 "PR #8928"; 9955:133/134 full #9763/#9764 titles; 9961:126 "Closes #9938"; Related Files gains the PR-modified file in 8928, 9887 and 10153; 9731:67 takes the reviewer's own :81 gloss for "lacks full access", which the verbatim :81 suggestion would otherwise contradict. Two follow-through edits beyond the suggestions: 8924's summary gets the same Node 19/20 wording as line 91, and 8857's master note adds that @medic/couch-request wrapped request-promise-native until PR #9746 (2c5a640c5) rebuilt it on fetch, so the note no longer credits #8833 alone. The other nits are not addressed here. lastUpdated is 2026-10-05 on the 16 touched drafts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR #131 — reply to round 3 and request for re-review.Thanks. All fifteen inline suggestions are applied verbatim, including the 8924 tag Nits applied where you gave the exact text: 8735:53 ( Three edits went beyond the suggestion text. Each one keeps a suggestion from leaving the draft
Gate ledger (touched drafts; frozen bytes)
Merge with |
Before the round-3 re-review, the 43 drafts were audited the way review 5415306093 on #131 audited authentication: every sentence against the PR diff, the code at the anchor, and master. Ledger: docs/handoffs/121-infra-prereview.md. Gates had already converged on these bytes and could not see any of this. Wrong mechanism: 9717 Nouveau indexes are built whenever a design doc is created or updated; the PR's _nouveau query makes the upgrade wait for the staged indexes before the swap. The draft said it "warmed" them (title, summary, tags, concepts, Problem, Code Patterns, Design Choices, Domain Rationale; also 9700's Related Issues gloss). Inaccurate or incomplete facts: 8794 only final-release tags produced an invalid version; beta tags gave valid semver. 10264 release rows never showed the suffix. 10045 the other two gaps in #9954, plus its two recorded failures. 9970 success when any compose file matched; the naming-convention error is logged only when nothing was updated. 8908 master's Makefile/compose.yml changed under #9963 (banner). 10857 the scanner reads all the container logs the harness saves, not just api and sentinel. 9074 view-logs existed (one deployment's first pod). 10758 cookie.js was not in the change set; link #10815. 10583 #10758 also set NODE_ENV in both Dockerfiles; the separate test override file and its reason; link #10815. Rationales replaced with what the record says, or review-thread decisions added: 9891, 10826, 9700, 11141, 9960, 11072, 8918 (x2), 8775 (scoped to this PR; PR #10557 later removed close(), so stale: true). Mechanical: rename notes (10006), "(added)" labels (10557, 11072, 8813, 8908 incl. couchdb/tests/test_helper/), "PR #N" for bare PR citations (10181, 9074), deleted paths dropped from entities (9876, 8813, 10482, 9074; Related Files keeps them as deleted), related_issues links with their mirrors (10758/10583 -> 10815 and 10826 -> 10754/10357; 9288 -> 9286 and 9634 -> 9284). An independent verifier checked every changed sentence; its corrections are applied and were re-run through the gates. lastUpdated is 2026-10-05 on the 26 touched drafts. Rationale/alternative nits and the other ledger nits are not addressed here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sugat009
left a comment
There was a problem hiding this comment.
I checked 0652261 against my round-3 review. All 15 suggestions are applied, 14 of them verbatim. Your 8857 addition is right: PR #9746 removed request-promise-native, not PR #8833, so my suggestion named the wrong PR. Thank you for the catch. The three consistency edits and the seven nits are correct. A trial merge with main is clean: validate-schema passes 201 and 1,171 tests pass.
Five small items are inline, each with a suggestion. Three of them correct my own round-3 wording (8738:52, 8738:10 and 9833:87). The other 41 round-3 nits stay open, as you said. Please track them in the follow-up.
I approve. Please apply the five suggestions before you merge.
Co-authored-by: Sugat Bajracharya <30311933+sugat009@users.noreply.github.com>
Promotes 39 strong-fit
authenticationdrafts fromagent-memory/_pending/intoagent-memory/domains/authentication/issues/for squad content review.Categories: feature (15), bug (15), improvement (9)
Themes: SSO/OIDC login flows, token-login, permission checks & role revocation, password-reset/edit guards, multi-facility users.
All 39 carry
domainFit: strong+ a## Domain Rationalesection. 4 weak-fit drafts deferred for later.seeding-claude-cli-v2(feat(#108): seeding pipeline - CLI provider, domain-rationale, infrastructure domain, concurrency #119) — retarget tomainafter the schema lands.validate-schema: passing, 0 failures.