Skip to content

Establish independent Nightingale patient product foundation - #92

Closed
sudoshi wants to merge 43 commits into
mainfrom
codex/nightingale-patient-product
Closed

sudoshi wants to merge 43 commits into
mainfrom
codex/nightingale-patient-product

Conversation

@sudoshi

@sudoshi sudoshi commented Jul 26, 2026 •

Copy link
Copy Markdown
Owner

Outcome

Establishes Nightingale as an independent patient-facing product while preserving Hummingbird as the staff operations product and retaining the former Hummingbird Patient app only as deprecated migration/reference evidence.

  • Current head: 7967931d642bab16807bd7a9fa1bd3c2283d3b28
  • Exact-SHA CI: run 30306442357, 23/23 jobs passed
  • Master workstream checklist: 42/54 bounded items complete (77.78%)
  • PR state: draft; no merge, deployment, migration, distribution, pilot, or activation is authorized

Product and brand boundary

  • Adds independent nightingale/iosApp and nightingale/androidApp roots using net.acumenus.nightingale.
  • Keeps staff Hummingbird on net.acumenus.hummingbird and restores the deprecated reference patient app to net.acumenus.hummingbird.patient.
  • Applies the supplied Hummingbird artwork to Hummingbird staff icons and the supplied Nightingale artwork to Nightingale icons.
  • Adds verified iOS, Android adaptive, monochrome/themed, splash, launcher, and source-predecessor upgrade surfaces.
  • Governs the seven supplied Nightingale backgrounds with source lineage, exact metadata-stripped derivatives, stable cross-platform daily selection, opaque/readable content surfaces, no accessibility semantics, and user/system imagery-suppression controls.
  • Adds a fail-closed, catalog-digest/source-hash-bound background-rights review; all seven images remain held and are not approved for external distribution.

Fail-closed background rights review

  • Adds a v0 review bound to all seven exact catalog assets, filenames, dimensions, source hashes, and the catalog digest.
  • Corroborates two exact Unsplash photo pages; one provider download is a byte-for-byte source match, while the second is a strong visual match after resize/re-encoding and is explicitly not claimed as binary identity.
  • Records five unresolved sources, zero durable source archives, zero durable license snapshots, zero rights-cleared assets, and zero distribution-eligible assets.
  • Enforces release hold, no automated legal approval, and 20 negative mutation cases in CI.
  • This is engineering provenance evidence, not legal clearance; the parent distribution-rights checklist item remains open.

Patient-safe offline foundation

  • Provides an intentionally offline, no-live-access shell with no Nightingale route, client networking, patient-source query, or clinical disclosure.
  • Implements lifecycle privacy cover, Android FLAG_SECURE, backup/transfer exclusions, cleartext denial, an exact iOS privacy manifest, and runtime scans rejecting networking, tracking, logging, clipboard, and Release test-hook drift.
  • Adds dormant, caller-free iOS Keychain and Android Keystore primitives with fail-closed deletion and tamper tests using removable synthetic canaries.
  • Adds Nightingale-only presentation preferences for reduced motion and decorative imagery; stronger system settings win and essential content remains available.
  • Hardens the shell for light/dark appearance, contrast, large text, landscape reflow, semantic order, minimum target size, restrained announcements, double-length text, and Debug-only RTL/pseudolocale readiness.
  • Adds a draft foundation model covering 27 bounded controls, 22 security/privacy threats, 22 clinical hazards, 18 abuse cases, 20 activation gates, and 17 open risks. This remains engineering evidence, not an approved safety case.
  • Adds a source-hash-bound dependency inventory: seven direct Android Release declarations, 83 resolved components, 457 edges, zero iOS third-party packages, and five Apple system modules. It is not represented as a complete SBOM or vulnerability/license/provenance approval.

Namespace and activation-state separation

  • Adds a deterministic namespace manifest bound to 12 exact native sources and inventories ten shared accessibility IDs, four iOS Debug hooks, ten fully qualified persistent identifiers, zero telemetry events, and zero diagnostic channels.
  • Reissues all current persistent identifiers under net.acumenus.nightingale, including Android presentation keys and both native future-session-binding identifiers.
  • Reproduces the namespace manifest byte-for-byte, validates source SHA-256 values, rejects eight adversarial mutations, rejects legacy patient namespaces, and rejects unregistered logging/analytics primitives.
  • Adds separate typed states for institutional clinical approval, patient-content release, product feature activation, pilot enrollment, and source-connector deployment. All five default negative and have no environment activation hooks.
  • Exhaustively proves all 32 combinations: 31 incomplete combinations return hold; the one all-positive combination can only continue to later operation-specific release evaluation.
  • That continuation is not identity, authorization, disclosure, clinical release, production, or pilot approval. No route or runtime caller registers the gate.

Contract, parity, and migration evidence

  • Keeps the executable Nightingale OpenAPI contract at zero paths with all activation fields false.
  • Reserves /api/nightingale/v1 and the held /inpatient-contexts operation while mechanically rejecting aliases, proxies, redirects, and legacy API fallbacks.
  • Defines non-runnable, default-deny identity/session/recovery and current-inpatient-source candidates without choosing a provider, credential workflow, database adapter, or freshness threshold.
  • Defines held encounter-access, Today, journey, and communication candidates with opaque handles, field-level release/freshness/uncertainty/language/correction/offline semantics, generic non-disclosure, and durable content-free audit requirements.
  • Verifies 42 encounter cases, 64 identity cases, 42 source cases, 68 Today cases, 24 Today adversarial mutations, and 27 synthetic journey cases across 15 required families with 23 adversarial mutations.
  • Classifies and SHA-256 pins 65 identity/source files, 130 communication/notification files, and a final 134-file journey/preference/presentation/release slice.
  • Mechanically proves coverage of the 256-source legacy patient-product universe: 122 unique sources from the earlier ledgers plus 134 final-slice sources. Classification is evidence only and does not approve migration or runtime reuse.
  • Documents material deltas including missing recovery, unsafe first-record selection, native error divergence, absent patient push/automatic refresh, retry duplication risk, acceptance-versus-delivery ambiguity, native communication-state gaps, preference mismatch, and journey-context drift.

Default-off controlled-pilot manifest

  • Adds a deterministic, non-runnable candidate for facility, unit, cohort, language/interpreter coverage, exclusions, support hours, validity, prerequisites, named approvals, audit, rollback, and kill-switch evidence.
  • Keeps the committed template entirely inactive: revision zero, no scope values, zero enrollment, no validity window, no approvals, no review request, an engaged kill switch, and no runtime activation.
  • Enforces opaque scope handles, separately released inclusion/exclusion policies, a 25-enrollment technical ceiling, no automatic enrollment, fail-closed unknown inputs, IANA timezone rules, non-overlapping support windows, and a maximum 168-hour authorization lifetime.
  • Requires eight prerequisite records, seven distinct named approval roles, nine content-free append-only audit events, and exact rollback/kill-switch evidence before external go/no-go review.
  • Reproduces both JSON artifacts byte-for-byte, binds four sources by SHA-256, proves the executable contract still has zero paths, and evaluates 34 synthetic cases plus 25 adversarial mutations.
  • Exactly 33 cases hold; the one structurally complete synthetic case may proceed only to external go/no-go review, never activation, enrollment, disclosure, deployment, or clinical release.
  • No evaluator, caller, route, query, audit sink, enrollment service, support service, database table, native client, or deployment was added.

Generic non-disclosure foundation

  • Adds a route-free, framework-independent Nightingale domain gate for relationship, current-encounter binding, resource release, and prerequisite state.
  • Exhaustively evaluates all 40 combinations: one all-positive row may continue to later governed projection evaluation; the other 39 return the same identifier-free 404 / not_found / private, no-store, max-age=0 tuple.
  • Unknown, revoked, expired, cross-principal, wrong-encounter, omitted-resource, and failed-precondition cases are externally indistinguishable.
  • Backend proof passes 23 tests / 149 assertions, and an independent dependency-free verifier repeats disclosure and activation vocabularies, tuples, cardinalities, defaults, and non-registration checks.
  • There is still no route, controller, provider, query, patient content, audit writer, native client, or authorization grant.

Explicitly authorized production sample patient

The operator explicitly authorized one bounded production sample derived from the deprecated Hummingbird Patient reference patient. Evidence is retained in docs/evidence/nightingale/production-sample-patient-2026-07-27/README.md.

  • Exactly one synthetic operational encounter exists: demo-nightingale-reference-inpatient, active/not discharged, medical-surgical unit 85, no bed, acuity 2.
  • Exactly one fresh Nightingale patient principal exists: Nightingale Reference Patient, synthetic, product nightingale, status pending, inactive, and without email, phone, password, verification, or authentication history.
  • Only the safe Hummingbird template shape was copied: principal type, locale/timezone, unit, acuity, and lifecycle shape.
  • Exact lineage is recorded: source_template_product=hummingbird_patient and source_template_owner=hummingbird-patient-reference-identity-provisioner-v1.
  • Hummingbird credentials, sessions, challenge material, identity links, grants, HMAC digests, encrypted references, external identifiers, projections, and clinical content were not copied.
  • Fresh repeatable-read/read-only reconciliation proves one Nightingale encounter and one principal, with zero Nightingale identity links, grants, challenges, sessions, access-audit events, and notification devices.
  • The original Hummingbird sample encounter/principal/identity/grant cardinalities remain unchanged at one/one/one/one.
  • The sample cannot log in, reach patient data, or be reached by either native app; there is no identity-to-encounter binding, source adapter, projection, route, or networking client.
  • No duplicate or additional production write was performed during current-main reconciliation.

Required investor-demo patient cohort

  • Adds a required follow-on milestone for five distinct synthetic Nightingale patients with full governed DRG care pathways and one patient-scoped login per patient.
  • Reserves operator-selected usernames demo1 through demo5; the shared runtime password is intentionally omitted from source, documentation, CI, and evidence and must be stored only as a one-way hash.
  • Requires demo-only cohort labeling, exactly one principal/encounter binding per account, least-privilege patient authorization, no real-patient data, repeatable reset, pre-demo validation, access audit, and immediate revocation/rotation controls.
  • This cohort is a requirement only at this head: no additional production patient, login, password hash, grant, clinical projection, or route has been created by this slice.

Current-main reconciliation

Evidence: docs/evidence/nightingale/current-main-reconciliation-2026-07-27/README.md.

  • Merged current origin/main at ea14d2bd2be9656555dfba35da2dbf28e74730e3 in merge commit bcaae3da.

  • Preserved the unrelated Flow 4D plan update from f63e5994.

  • Found that main's PR Rename the patient product from Hummingbird Patient to Nightingale #101 rename repurposed the deprecated Hummingbird Patient roots and identities as Nightingale, conflicting with the accepted independent-product architecture.

  • Forward-reverted only that incompatible rename in c11d462cb5c62e399139caf0538ed9def1f1d441; published history was not rewritten.

  • Exact tree proof at c11d462c shows the reconciled branch differs from verified pre-merge head 21e4de17 in exactly one path, the unrelated Flow 4D plan, and that path is byte-identical to f63e5994.

  • Revalidated independent Nightingale iOS/Android roots and net.acumenus.nightingale identity, deprecated Hummingbird Patient roots and net.acumenus.hummingbird.patient identity, route-free Nightingale backend, and independent distribution source.

  • This reconciliation performed no production database access or mutation and closes no additional functional checklist item.

  • Subsequently merged current main through 98d9bf98f2e811f6b39c0d14bb5e0ea1b5ea7317, including the independent frontend-build CI job and its documentation. The runtime, contract, native-client, fixture, and sample-patient trees remained unchanged after emulator acceptance.

  • The first exact-SHA run exposed one Gitleaks 8.28 false positive in the new upstream devlog: the ordinary prose phrase notification/delivery following API paths. Extended the existing exception only to that exact path and exact captured phrase.

  • A checksum-verified pinned Gitleaks 8.28 scan then passed all 1,083 locally visible commits and a clean archived checkout. The generic credential rule remains active everywhere else.

Validation

Local reacceptance

  • Full Nightingale 15-stage contract, candidate, classification, threat/hazard, dependency, namespace, controlled-pilot, backend, and native product-boundary chain passed, including negative self-tests.
  • Focused Laravel foundation suite: 23 tests / 149 assertions passed.
  • Nightingale iOS on a fresh iPhone 16e Simulator / iOS 26.3.1: 11/11 unit tests and 6/6 UI journeys passed, with zero failures or skips; Release artifact boundary passed and the simulator was shut down.
  • Nightingale Android: clean build/lint/Debug+Release assembly passed; forced uncached 8/8 Debug JVM, 8/8 Release JVM, and 10/10 installed API 35 journeys passed with zero failures, errors, or skips. A separate read-only emulator on port 5556 was used and terminated; the pre-existing port-5554 emulator was preserved.
  • Deprecated Hummingbird Patient iOS generated-project/source boundary and Android source/Gradle product boundary passed.
  • Checklist count, 137 changed-document links, Prettier 3.6.2, syntax/YAML parsing, whitespace checks, and pinned Gitleaks 8.28 scans over 1,083 commits plus a clean archived checkout passed.

Hosted exact-SHA CI

CI run 30306442357 completed successfully on exact head 7967931d642bab16807bd7a9fa1bd3c2283d3b28: 23/23 jobs succeeded, including the independent frontend build, frontend tests, backend quality and five backend shards, security, Arena, DAST, browser, mobile brand assets, Nightingale contract, both Nightingale native apps, both Hummingbird staff apps, and both deprecated Hummingbird Patient apps.

Explicit holds

Apart from the single recorded inert production sample exception above, this draft does not authorize merge, application deployment, migration, additional database mutation, additional patient creation, identity/provider selection, enrollment or recovery workflow, identity-to-encounter binding, patient-source query, clinical content, live Today/My Path/Care Team rendering, representatives, messaging, notifications, signed distribution, store submission, pilot, or production activation.

Still required are released operation implementations; named identity/source and field-level clinical/content approvals; patient-advisor, accessibility, language/interpreter, privacy/security, clinical-safety, legal/HIM, nursing, medical-staff, pharmacy, support, operations, and release review; physical-device and assistive-technology validation; full WCAG 2.2 AA work; penetration/red-team/tabletop and supply-chain review; distribution rights; production-like integration/failover testing; and a separately authorized pilot go/no-go record.

sudoshi added 30 commits July 26, 2026 00:55
sudoshi added a commit that referenced this pull request Jul 27, 2026
PR #92 ("Establish independent Nightingale patient product foundation") already
builds nightingale/backgrounds/ properly: a README, a backgrounds.v1.json
manifest, and optimised drawable-nodpi derivatives. These seven raw files were
undocumented candidates with uneven provenance — two Unsplash-named, the rest
wallpaper-site downloads with no recorded licence — so keeping them would add a
second, worse copy of work that is already done.

The paper, plan, devlog, and brand renders are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sudoshi

sudoshi commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

⚠️ Bundle-ID collision with #101 — this PR merges green but produces two Nightingale apps

#101 ("Rename the patient product from Hummingbird Patient to Nightingale")
merged on 2026-07-27 and reached the same destination as this PR by the
opposite route. Both now claim net.acumenus.nightingale.

This PR (#92) #101 (merged, shipped)
Strategy New nightingale/iosApp + nightingale/androidApp Renamed hummingbird/iosPatientApp in place
Apple bundle ID net.acumenus.nightingale net.acumenus.nightingale
Old patient app Deletes it Keeps it, renamed

The merge does not conflict, and that is the problem

git merge-tree origin/main <this branch> reports 0 conflicting paths. The
deletions here target paths that no longer exist under those names — #101
renamed them — so git treats them as no-ops. Inspecting the resulting tree:

nightingale/iosApp                19 files   ← added by this PR
nightingale/androidApp            50 files   ← added by this PR
hummingbird/androidPatientApp     64 files   ← survives, renamed by #101

Merging as-is leaves two Nightingale applications in the repository sharing one
bundle identifier, with the App Store Connect record (app id 6794950746) and
live TestFlight build 202607271938 already bound to the #101 tree.

Context, not a request to change direction

For what it is worth, this PR is the better-engineered approach — an
independent product boundary, the identity registry in
PRODUCT-IDENTITY-AND-SUPPORT-NAMING-CHECKLIST-2026-07-26.md, and
provenance-tracked backgrounds. #101 was the expedient path to a shippable
build. If this PR becomes canonical, the reconciliation is: delete the renamed
hummingbird/iosPatientApp and hummingbird/androidPatientApp, and repoint
.appledeploy (NIGHTINGALE_DIR, NIGHTINGALE_XCODEPROJ, NIGHTINGALE_SCHEME)
at nightingale/iosApp before the next ./testflight.sh ship nightingale.

Also note #110 originally added raw nightingale/backgrounds/*.jpg; those have
been removed from that PR in favour of the curated set here.

Flagging rather than resolving — which of the two architectures wins is a
product decision, not a merge-conflict one.

sudoshi added a commit that referenced this pull request Jul 27, 2026
Neither shipped iOS app carries a PrivacyInfo.xcprivacy. Apple requires a
required-reason API manifest for App Store submission; TestFlight tolerates its
absence, the store does not. PR #92 had written one, so this salvages it — with
a correction.

That version declared an empty NSPrivacyCollectedDataTypes. True of its
foundation build, which had no networking at all; false here, where the app
authenticates and exchanges clinical content. Copying it verbatim would have
filed an inaccurate declaration with Apple, which is worse than filing none.

Verified against the source rather than assumed:
  - UserDefaults is the only required-reason API category used
    (Networking/PatientSecureStore.swift). No FileManager timestamp, disk
    space, boot time, or active-keyboard API appears anywhere in the target,
    so CA92.1 alone is correct.
  - NSPrivacyTracking false is accurate; scripts/check-no-tracking-technologies.sh
    already enforces the absence of tracking SDKs in CI.

The collected-data entries reflect what the client demonstrably moves: a
patient context identifier and bearer credentials, released clinical
projections, and patient-authored message content. Whether each counts as
"collected" also depends on backend retention, so they are deliberately
conservative and carry an in-file note that they need product and privacy
sign-off before submission. Over-declaring is safe; under-declaring is a
misrepresentation.

xcodegen bundles it into the target (4 references in the generated project);
the xcode-project, patient-boundary, and tracking-technology guards pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sudoshi added a commit that referenced this pull request Jul 27, 2026
63 documents: threat and hazard model, privacy release controls, accessibility
layout and language-readiness matrices, foundation dependency inventory,
contract ownership and authorization matrix, identity recovery and
protected-state decisions, background asset governance and rights review, the
controlled pilot manifest, and the product identity and support naming
checklist — plus the release evidence under docs/evidence.

These are the decision records that make a patient-facing clinical product
defensible under review, and they survive the architecture change that
superseded the PR they came from.

Nine of them cite nightingale/iosApp and nightingale/androidApp paths that were
never adopted. Rather than edit sixty-three documents or drop the nine, the
README carries a substitution table and states plainly which parts of #92 were
salvaged and which were not, so a reader is not misled about where the code
actually lives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sudoshi added a commit that referenced this pull request Jul 27, 2026
On 2026-07-27 two independent efforts each declared net.acumenus.nightingale:
#101 renamed the existing patient app in place, #92 built a new foundation
tree. Both were internally consistent. Nothing compared them, and the merge
produced zero conflicts — git cannot see that two project.yml files claim the
same App Store record. The collision was found by reading diffs by hand, after
one of them had already shipped to TestFlight.

Enforces per-platform uniqueness rather than global uniqueness: one product
legitimately declares the same identifier once for iOS and once for Android.
What must never happen is the same identifier twice within a platform, because
that is two applications competing for one store listing.

Verified both directions. Against the current tree it passes, reporting 7 iOS
bundle identifiers and 2 Android applicationIds. Against a reconstruction of
the #92-plus-#101 merged tree it exits 1 and names all four collisions with the
declaring files, which is the failure that should have happened in July.

Runs alongside the existing transport-security and tracking-technology guards
in the Backend quality job.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sudoshi

sudoshi commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #101 (shipped) and #111 (salvage).

The architecture decision went the other way: the patient app was renamed in
place rather than rebuilt independently, because the existing app was already
feature-complete — seven feature modules and a working API client — while this
branch's nightingale/iosApp is a 19-file foundation with no features and no
networking. Adopting it would have replaced a shipping application with a
launch screen.

Closing rather than merging, because merging was actively unsafe. It reports
zero conflicts against current main while producing two applications sharing
net.acumenus.nightingale, and its 44-line config/nightingale.php would have
silently replaced the 271-line live config the deployed backend reads for 48
environment variables.

The work here was not wasted. #111 salvages:

  • App\Nightingale\Activation and App\Nightingale\Disclosure — the
    conjunctive activation gate and generic non-disclosure gate, with tests
  • the foundation namespace keys, merged into the live config rather than
    replacing it
  • PrivacyInfo.xcprivacy, corrected for an app that actually has networking
  • all 63 governance documents
  • verify-app-identity-uniqueness.sh, which would have caught this collision
    in CI before either side shipped

Not salvaged: the two app scaffolds, and the deletions of the patient app trees.

@sudoshi sudoshi closed this Jul 27, 2026
sudoshi added a commit that referenced this pull request Jul 28, 2026
PR #92 built an independent Nightingale product foundation, then fell behind
main when the patient product was renamed in place instead (#101). The app tree
it added is a scaffold with no features or networking, so it is superseded — but
its backend governance layer is not, and is worth keeping.

Ports 20 pure classes plus their test:

  App\Nightingale\Activation    a conjunctive gate over five independent
                                preconditions — clinical approval, content
                                release, feature activation, pilot enrollment,
                                source connector — any one of which holds the
                                whole evaluation
  App\Nightingale\Disclosure    a generic non-disclosure gate over relationship,
                                encounter binding, and resource state
  App\Nightingale\EncounterAccess, Identity, Inpatient
                                precondition gates and unconfigured-by-default
                                boundaries

These are pure: no route, container binding, config reader, source query, audit
writer, or native caller. They model exactly the separation that was missing
when tracing why no projection reaches a patient — clinical approval and
content release as independent conditions rather than one flag.

Note the config collision this avoids. #92 adds a 44-line config/nightingale.php
declaring a foundation namespace; main already has a 271-line
config/nightingale.php, the live file the deployed backend reads for 48
environment variables. Merging #92 as-is would have had one silently replace the
other. The foundation keys are merged into the existing file under a clearly
marked section instead, leaving every serving value untouched.

Verified: 23 salvaged tests pass (149 assertions); the patient suite still
passes 301 tests (4,290 assertions); config:show confirms the live values are
unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sudoshi added a commit that referenced this pull request Jul 28, 2026
Neither shipped iOS app carries a PrivacyInfo.xcprivacy. Apple requires a
required-reason API manifest for App Store submission; TestFlight tolerates its
absence, the store does not. PR #92 had written one, so this salvages it — with
a correction.

That version declared an empty NSPrivacyCollectedDataTypes. True of its
foundation build, which had no networking at all; false here, where the app
authenticates and exchanges clinical content. Copying it verbatim would have
filed an inaccurate declaration with Apple, which is worse than filing none.

Verified against the source rather than assumed:
  - UserDefaults is the only required-reason API category used
    (Networking/PatientSecureStore.swift). No FileManager timestamp, disk
    space, boot time, or active-keyboard API appears anywhere in the target,
    so CA92.1 alone is correct.
  - NSPrivacyTracking false is accurate; scripts/check-no-tracking-technologies.sh
    already enforces the absence of tracking SDKs in CI.

The collected-data entries reflect what the client demonstrably moves: a
patient context identifier and bearer credentials, released clinical
projections, and patient-authored message content. Whether each counts as
"collected" also depends on backend retention, so they are deliberately
conservative and carry an in-file note that they need product and privacy
sign-off before submission. Over-declaring is safe; under-declaring is a
misrepresentation.

xcodegen bundles it into the target (4 references in the generated project);
the xcode-project, patient-boundary, and tracking-technology guards pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sudoshi added a commit that referenced this pull request Jul 28, 2026
63 documents: threat and hazard model, privacy release controls, accessibility
layout and language-readiness matrices, foundation dependency inventory,
contract ownership and authorization matrix, identity recovery and
protected-state decisions, background asset governance and rights review, the
controlled pilot manifest, and the product identity and support naming
checklist — plus the release evidence under docs/evidence.

These are the decision records that make a patient-facing clinical product
defensible under review, and they survive the architecture change that
superseded the PR they came from.

Nine of them cite nightingale/iosApp and nightingale/androidApp paths that were
never adopted. Rather than edit sixty-three documents or drop the nine, the
README carries a substitution table and states plainly which parts of #92 were
salvaged and which were not, so a reader is not misled about where the code
actually lives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sudoshi added a commit that referenced this pull request Jul 28, 2026
On 2026-07-27 two independent efforts each declared net.acumenus.nightingale:
#101 renamed the existing patient app in place, #92 built a new foundation
tree. Both were internally consistent. Nothing compared them, and the merge
produced zero conflicts — git cannot see that two project.yml files claim the
same App Store record. The collision was found by reading diffs by hand, after
one of them had already shipped to TestFlight.

Enforces per-platform uniqueness rather than global uniqueness: one product
legitimately declares the same identifier once for iOS and once for Android.
What must never happen is the same identifier twice within a platform, because
that is two applications competing for one store listing.

Verified both directions. Against the current tree it passes, reporting 7 iOS
bundle identifiers and 2 Android applicationIds. Against a reconstruction of
the #92-plus-#101 merged tree it exits 1 and names all four collisions with the
declaring files, which is the failure that should have happened in July.

Runs alongside the existing transport-security and tracking-technology guards
in the Backend quality job.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sudoshi added a commit that referenced this pull request Jul 28, 2026
…collisions recurring (#111)

* Salvage the Nightingale activation and disclosure gates from #92

PR #92 built an independent Nightingale product foundation, then fell behind
main when the patient product was renamed in place instead (#101). The app tree
it added is a scaffold with no features or networking, so it is superseded — but
its backend governance layer is not, and is worth keeping.

Ports 20 pure classes plus their test:

  App\Nightingale\Activation    a conjunctive gate over five independent
                                preconditions — clinical approval, content
                                release, feature activation, pilot enrollment,
                                source connector — any one of which holds the
                                whole evaluation
  App\Nightingale\Disclosure    a generic non-disclosure gate over relationship,
                                encounter binding, and resource state
  App\Nightingale\EncounterAccess, Identity, Inpatient
                                precondition gates and unconfigured-by-default
                                boundaries

These are pure: no route, container binding, config reader, source query, audit
writer, or native caller. They model exactly the separation that was missing
when tracing why no projection reaches a patient — clinical approval and
content release as independent conditions rather than one flag.

Note the config collision this avoids. #92 adds a 44-line config/nightingale.php
declaring a foundation namespace; main already has a 271-line
config/nightingale.php, the live file the deployed backend reads for 48
environment variables. Merging #92 as-is would have had one silently replace the
other. The foundation keys are merged into the existing file under a clearly
marked section instead, leaving every serving value untouched.

Verified: 23 salvaged tests pass (149 assertions); the patient suite still
passes 301 tests (4,290 assertions); config:show confirms the live values are
unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Add the Apple privacy manifest the shipped patient app was missing

Neither shipped iOS app carries a PrivacyInfo.xcprivacy. Apple requires a
required-reason API manifest for App Store submission; TestFlight tolerates its
absence, the store does not. PR #92 had written one, so this salvages it — with
a correction.

That version declared an empty NSPrivacyCollectedDataTypes. True of its
foundation build, which had no networking at all; false here, where the app
authenticates and exchanges clinical content. Copying it verbatim would have
filed an inaccurate declaration with Apple, which is worse than filing none.

Verified against the source rather than assumed:
  - UserDefaults is the only required-reason API category used
    (Networking/PatientSecureStore.swift). No FileManager timestamp, disk
    space, boot time, or active-keyboard API appears anywhere in the target,
    so CA92.1 alone is correct.
  - NSPrivacyTracking false is accurate; scripts/check-no-tracking-technologies.sh
    already enforces the absence of tracking SDKs in CI.

The collected-data entries reflect what the client demonstrably moves: a
patient context identifier and bearer credentials, released clinical
projections, and patient-authored message content. Whether each counts as
"collected" also depends on backend retention, so they are deliberately
conservative and carry an in-file note that they need product and privacy
sign-off before submission. Over-declaring is safe; under-declaring is a
misrepresentation.

xcodegen bundles it into the target (4 references in the generated project);
the xcode-project, patient-boundary, and tracking-technology guards pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Salvage the Nightingale governance documentation from #92

63 documents: threat and hazard model, privacy release controls, accessibility
layout and language-readiness matrices, foundation dependency inventory,
contract ownership and authorization matrix, identity recovery and
protected-state decisions, background asset governance and rights review, the
controlled pilot manifest, and the product identity and support naming
checklist — plus the release evidence under docs/evidence.

These are the decision records that make a patient-facing clinical product
defensible under review, and they survive the architecture change that
superseded the PR they came from.

Nine of them cite nightingale/iosApp and nightingale/androidApp paths that were
never adopted. Rather than edit sixty-three documents or drop the nine, the
README carries a substitution table and states plainly which parts of #92 were
salvaged and which were not, so a reader is not misled about where the code
actually lives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Fail CI when two applications claim one store identifier

On 2026-07-27 two independent efforts each declared net.acumenus.nightingale:
#101 renamed the existing patient app in place, #92 built a new foundation
tree. Both were internally consistent. Nothing compared them, and the merge
produced zero conflicts — git cannot see that two project.yml files claim the
same App Store record. The collision was found by reading diffs by hand, after
one of them had already shipped to TestFlight.

Enforces per-platform uniqueness rather than global uniqueness: one product
legitimately declares the same identifier once for iOS and once for Android.
What must never happen is the same identifier twice within a platform, because
that is two applications competing for one store listing.

Verified both directions. Against the current tree it passes, reporting 7 iOS
bundle identifiers and 2 Android applicationIds. Against a reconstruction of
the #92-plus-#101 merged tree it exits 1 and names all four collisions with the
declaring files, which is the failure that should have happened in July.

Runs alongside the existing transport-security and tracking-technology guards
in the Backend quality job.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant