Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
a2c3f27
feat: establish Nightingale patient product foundation
sudoshi Jul 26, 2026
525f690
feat: harden Nightingale privacy foundation
sudoshi Jul 26, 2026
cad308d
feat: verify Nightingale and Hummingbird product identity
sudoshi Jul 26, 2026
1194a1c
Merge remote-tracking branch 'origin/main' into codex/nightingale-pat…
sudoshi Jul 26, 2026
e2ec680
feat: establish Nightingale protected-state foundation
sudoshi Jul 26, 2026
443b367
Merge remote-tracking branch 'origin/main' into codex/nightingale-pat…
sudoshi Jul 26, 2026
a1cc3a8
feat: enforce Nightingale contract governance boundary
sudoshi Jul 26, 2026
d918ecd
feat: govern Nightingale encounter access candidate
sudoshi Jul 26, 2026
c4eb08c
feat: establish Nightingale backend boundary
sudoshi Jul 26, 2026
0644114
feat: define Nightingale identity and source candidates
sudoshi Jul 26, 2026
bfd4a80
test: ratify mobile brand boundaries
sudoshi Jul 26, 2026
b107840
fix: make Nightingale checks runner portable
sudoshi Jul 26, 2026
be8405a
docs: classify Nightingale identity and first-read sources
sudoshi Jul 26, 2026
e8f2b33
docs(nightingale): ratify communication and notification sources
sudoshi Jul 26, 2026
08837f8
docs(nightingale): complete patient source classification
sudoshi Jul 26, 2026
eb6bf75
docs(nightingale): define held Today contract
sudoshi Jul 26, 2026
c81f048
Add Nightingale display comfort foundation
sudoshi Jul 26, 2026
c432b58
Harden Nightingale accessibility foundation
sudoshi Jul 26, 2026
80c29e9
Prove Nightingale iOS landscape contract
sudoshi Jul 26, 2026
b772b0e
Model Nightingale foundation threats and hazards
sudoshi Jul 26, 2026
60e599d
Inventory Nightingale foundation dependencies
sudoshi Jul 26, 2026
20a12ab
feat(nightingale): govern shared patient backgrounds
sudoshi Jul 27, 2026
1e5bb6f
feat(nightingale): harden offline privacy foundation
sudoshi Jul 27, 2026
e3593e3
feat(nightingale): harden accessible language readiness
sudoshi Jul 27, 2026
40a71e8
Merge remote-tracking branch 'origin/main' into codex/nightingale-pat…
sudoshi Jul 27, 2026
85316fb
fix(nightingale): restore independent iOS release boundary
sudoshi Jul 27, 2026
45ddf90
test(nightingale): tolerate automatic locale relaunch
sudoshi Jul 27, 2026
a825db8
docs(nightingale): revalidate current-main product boundary
sudoshi Jul 27, 2026
e72abaf
docs(nightingale): define held patient journey scenarios
sudoshi Jul 27, 2026
a3c5a83
ci(security): scope Nightingale prose false positive
sudoshi Jul 27, 2026
cb56d13
feat(nightingale): prove generic non-disclosure boundary
sudoshi Jul 27, 2026
9f93e5a
docs(nightingale): normalize evidence whitespace
sudoshi Jul 27, 2026
bdd8bec
Merge remote-tracking branch 'origin/main' into codex/nightingale-pat…
sudoshi Jul 27, 2026
21e4de1
feat(nightingale): separate namespace and activation gates
sudoshi Jul 27, 2026
bcaae3d
Merge remote-tracking branch 'origin/main' into codex/nightingale-pat…
sudoshi Jul 27, 2026
c11d462
Revert "Rename the patient product from Hummingbird Patient to Nighti…
sudoshi Jul 27, 2026
b333ebf
docs(nightingale): record main reconciliation evidence
sudoshi Jul 27, 2026
487d1c7
feat(nightingale): define controlled pilot manifest
sudoshi Jul 27, 2026
6ec76d3
Merge remote-tracking branch 'origin/main' into codex/nightingale-pat…
sudoshi Jul 27, 2026
08c0446
docs(nightingale): record CI reconciliation
sudoshi Jul 27, 2026
c116deb
Merge remote-tracking branch 'origin/main' into codex/nightingale-pat…
sudoshi Jul 27, 2026
5a2f8c3
ci(security): scope devlog prose exception
sudoshi Jul 27, 2026
7967931
chore(nightingale): enforce background rights hold
sudoshi Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .appledeploy.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ HUMMINGBIRD_SCHEME=Hummingbird
NIGHTINGALE_NAME="Zephyrus-Nightingale"
NIGHTINGALE_APP_ID=
NIGHTINGALE_BUNDLE_ID=net.acumenus.nightingale
NIGHTINGALE_DIR=hummingbird/iosPatientApp
NIGHTINGALE_DIR=nightingale/iosApp
NIGHTINGALE_XCODEPROJ=Nightingale.xcodeproj
NIGHTINGALE_SCHEME=Nightingale

Expand Down
309 changes: 287 additions & 22 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

27 changes: 10 additions & 17 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,22 +85,15 @@ paths = [
regexes = ['''^drgcp-[a-z0-9-]{26,65}$''']

[[allowlists]]
# Nightingale patient-journey devlog: the generic API-key rule captures the
# English phrase "notification/delivery" out of a prose sentence, because the
# word "API" appears inside its keyword window:
#
# "... concrete identifiers or API paths, notification/delivery
# overstatement, offline PHI/queue/stale-current behavior ..."
#
# There is no credential here — the captured value is two ordinary words and a
# slash. Scoped to the exact devlog and that exact phrase; the two SHA-256
# evidence digests recorded elsewhere in the same file, and every other value
# in it, remain scanned.
#
# The history scan walks all refs, so this fires from any branch for as long as
# codex/nightingale-patient-product exists. The allowlist therefore has to live
# on main rather than only on the branch that introduced the file.
description = "Nightingale devlog prose false positive (notification/delivery)"
# Historical devlog prose used the adjacent words "API paths, notification",
# which the generic-api-key rule parsed as a key/value pair and captured the
# ordinary phrase "notification/delivery". Keep the history exception
# AND-scoped to the two exact documents and exact captured phrase so no
# credential-shaped value is exempted.
description = "Devlog API-path prose false positives"
condition = "AND"
paths = ['''^docs/devlog/DEVLOG-nightingale-patient-product-2026-07-26\.md$''']
paths = [
'''^docs/devlog/DEVLOG-nightingale-patient-product-2026-07-26\.md$''',
'''^docs/DEVLOG-browser-chain-decouple-2026-07-27\.md$''',
]
regexes = ['''^notification/delivery$''']
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace App\Console\Commands;

use App\Services\Patient\Demo\NightingaleReferenceProjectionDraftProvisioner;
use App\Services\Patient\Demo\HummingbirdPatientReferenceProjectionDraftProvisioner;
use Illuminate\Console\Command;
use Throwable;

Expand All @@ -17,16 +17,16 @@ class HummingbirdDraftReferencePatientProjectionsCommand extends Command

protected $description = 'Preview or create non-visible draft projections for the pending Hummingbird reference patient';

public function handle(NightingaleReferenceProjectionDraftProvisioner $provisioner): int
public function handle(HummingbirdPatientReferenceProjectionDraftProvisioner $provisioner): int
{
if ($this->option('commit')
&& ! hash_equals(
NightingaleReferenceProjectionDraftProvisioner::CONFIRMATION,
HummingbirdPatientReferenceProjectionDraftProvisioner::CONFIRMATION,
(string) $this->option('confirm-draft-only'),
)) {
$this->error(
'--commit requires --confirm-draft-only='
.NightingaleReferenceProjectionDraftProvisioner::CONFIRMATION,
.HummingbirdPatientReferenceProjectionDraftProvisioner::CONFIRMATION,
);

return self::INVALID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace App\Console\Commands;

use App\Services\Patient\Demo\NightingaleReferenceIdentityProvisioner;
use App\Services\Patient\Demo\HummingbirdPatientReferenceIdentityProvisioner;
use Illuminate\Console\Command;
use Throwable;

Expand All @@ -17,7 +17,7 @@ class HummingbirdProvisionReferencePatientIdentityCommand extends Command

protected $description = 'Dry-run or idempotently bind the command-owned synthetic inpatient to Hummingbird Patient enrollment';

public function handle(NightingaleReferenceIdentityProvisioner $provisioner): int
public function handle(HummingbirdPatientReferenceIdentityProvisioner $provisioner): int
{
if ($this->option('show-secrets') && ! $this->option('commit')) {
$this->error('--show-secrets requires --commit because enrollment material is generated only once.');
Expand Down
4 changes: 2 additions & 2 deletions app/Http/Controllers/Api/Patient/MessagingController.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ public function store(CreateMessageThreadRequest $request, string $encounterUuid
$principal = $request->user();
$input = $request->validated();
$result = match ($input['topic_code'] ?? null) {
'care_preference' => (bool) config('nightingale.features.care_preferences')
'care_preference' => (bool) config('hummingbird-patient.features.care_preferences')
? $this->carePreferences->submit($request, $principal, $encounterUuid, $input)
: $this->messaging->createThread($request, $principal, $encounterUuid, $input),
'patient_goal' => (bool) config('nightingale.features.patient_goals')
'patient_goal' => (bool) config('hummingbird-patient.features.patient_goals')
? $this->patientGoals->submit($request, $principal, $encounterUuid, $input)
: $this->messaging->createThread($request, $principal, $encounterUuid, $input),
default => $this->messaging->createThread($request, $principal, $encounterUuid, $input),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureNightingaleEnabled
class EnsureHummingbirdPatientEnabled
{
public function __construct(private readonly PatientHmac $hmac) {}

public function handle(Request $request, Closure $next): Response
{
if (! config('nightingale.enabled', false)) {
if (! config('hummingbird-patient.enabled', false)) {
return $this->notFound();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureNightingaleFeatureEnabled
class EnsureHummingbirdPatientFeatureEnabled
{
public function handle(Request $request, Closure $next, string $feature): Response
{
if (! config("nightingale.features.{$feature}", false)) {
if (! config("hummingbird-patient.features.{$feature}", false)) {
return $this->notFound();
}

Expand Down
8 changes: 4 additions & 4 deletions app/Http/Middleware/EnsurePatientStaffMessagingEnabled.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ class EnsurePatientStaffMessagingEnabled
{
public function handle(Request $request, Closure $next): Response
{
if (! (bool) config('nightingale.enabled', false)
|| ! (bool) config('nightingale.features.messaging', false)
|| ! (bool) config('nightingale.staff_messaging.enabled', false)
|| config('nightingale.staff_messaging.governance_status') !== 'approved'
if (! (bool) config('hummingbird-patient.enabled', false)
|| ! (bool) config('hummingbird-patient.features.messaging', false)
|| ! (bool) config('hummingbird-patient.staff_messaging.enabled', false)
|| config('hummingbird-patient.staff_messaging.governance_status') !== 'approved'
) {
return $this->notFound();
}
Expand Down
8 changes: 4 additions & 4 deletions app/Http/Middleware/HandleInertiaRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,10 @@ public function share(Request $request): array
'home_hospital' => (bool) config('home_hospital.enabled'),
// The staff workspace is visible only when the whole patient
// messaging chain and its independent governance gate are live.
'patient_communications' => (bool) config('nightingale.enabled', false)
&& (bool) config('nightingale.features.messaging', false)
&& (bool) config('nightingale.staff_messaging.enabled', false)
&& config('nightingale.staff_messaging.governance_status') === 'approved',
'patient_communications' => (bool) config('hummingbird-patient.enabled', false)
&& (bool) config('hummingbird-patient.features.messaging', false)
&& (bool) config('hummingbird-patient.staff_messaging.enabled', false)
&& config('hummingbird-patient.staff_messaging.governance_status') === 'approved',
],
'workflow' => $request->session()->get('workflow'),
'flash' => [
Expand Down
15 changes: 15 additions & 0 deletions app/Nightingale/Activation/NightingaleActivationDisposition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace App\Nightingale\Activation;

/**
* Route-free result of evaluating the five independent activation gates.
*
* ContinueToOperationSpecificReleaseEvaluation is deliberately weaker than
* access, authorization, disclosure, pilot approval, or production readiness.
*/
enum NightingaleActivationDisposition: string
{
case Hold = 'hold';
case ContinueToOperationSpecificReleaseEvaluation = 'continue_to_operation_specific_release_evaluation';
}
32 changes: 32 additions & 0 deletions app/Nightingale/Activation/NightingaleActivationGate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Nightingale\Activation;

/**
* Keeps approval, content, feature, enrollment, and source deployment
* independent and conjunctive.
*
* This pure gate accepts no identifiers or records and has no route, container
* binding, configuration reader, source query, audit writer, or native caller.
*/
final class NightingaleActivationGate
{
public function evaluate(
NightingaleClinicalApprovalState $clinicalApproval,
NightingaleContentReleaseState $contentRelease,
NightingaleFeatureActivationState $featureActivation,
NightingalePilotEnrollmentState $pilotEnrollment,
NightingaleSourceConnectorState $sourceConnector,
): NightingaleActivationDisposition {
if (! $clinicalApproval->permitsFurtherEvaluation()
|| ! $contentRelease->permitsFurtherEvaluation()
|| ! $featureActivation->permitsFurtherEvaluation()
|| ! $pilotEnrollment->permitsFurtherEvaluation()
|| ! $sourceConnector->permitsFurtherEvaluation()
) {
return NightingaleActivationDisposition::Hold;
}

return NightingaleActivationDisposition::ContinueToOperationSpecificReleaseEvaluation;
}
}
21 changes: 21 additions & 0 deletions app/Nightingale/Activation/NightingaleClinicalApprovalState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace App\Nightingale\Activation;

/**
* Institution-owned clinical approval prerequisite.
*
* Recorded may be emitted only by a future adapter that validates an exact,
* current, in-scope approval record. It is not inferred from source data,
* pathway verification, clinician use, or another activation prerequisite.
*/
enum NightingaleClinicalApprovalState: string
{
case Absent = 'absent';
case Recorded = 'recorded';

public function permitsFurtherEvaluation(): bool
{
return $this === self::Recorded;
}
}
21 changes: 21 additions & 0 deletions app/Nightingale/Activation/NightingaleContentReleaseState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace App\Nightingale\Activation;

/**
* Patient-content release prerequisite.
*
* Released means only that a future content service validated an exact,
* audience-specific release. It does not imply clinical approval, feature
* activation, patient enrollment, connector deployment, or disclosure.
*/
enum NightingaleContentReleaseState: string
{
case Unreleased = 'unreleased';
case Released = 'released';

public function permitsFurtherEvaluation(): bool
{
return $this === self::Released;
}
}
20 changes: 20 additions & 0 deletions app/Nightingale/Activation/NightingaleFeatureActivationState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Nightingale\Activation;

/**
* Product feature-switch prerequisite.
*
* Enabled is an independently governed state and cannot substitute for any
* clinical, content, enrollment, source, identity, or authorization decision.
*/
enum NightingaleFeatureActivationState: string
{
case Disabled = 'disabled';
case Enabled = 'enabled';

public function permitsFurtherEvaluation(): bool
{
return $this === self::Enabled;
}
}
20 changes: 20 additions & 0 deletions app/Nightingale/Activation/NightingalePilotEnrollmentState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Nightingale\Activation;

/**
* Patient/cohort pilot-enrollment prerequisite.
*
* Enrolled may be emitted only after a future pilot service validates scope,
* expiry, exclusions, consent, support coverage, and withdrawal state.
*/
enum NightingalePilotEnrollmentState: string
{
case NotEnrolled = 'not_enrolled';
case Enrolled = 'enrolled';

public function permitsFurtherEvaluation(): bool
{
return $this === self::Enrolled;
}
}
20 changes: 20 additions & 0 deletions app/Nightingale/Activation/NightingaleSourceConnectorState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Nightingale\Activation;

/**
* Authoritative-source connector deployment prerequisite.
*
* Deployed may be emitted only after a future source boundary validates the
* approved adapter, facility scope, health, version, and rollback posture.
*/
enum NightingaleSourceConnectorState: string
{
case Undeployed = 'undeployed';
case Deployed = 'deployed';

public function permitsFurtherEvaluation(): bool
{
return $this === self::Deployed;
}
}
35 changes: 35 additions & 0 deletions app/Nightingale/Disclosure/NightingaleDisclosureDisposition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace App\Nightingale\Disclosure;

/**
* Publicly observable disposition of the route-free disclosure foundation.
*
* ContinueToGovernedProjectionEvaluation is not an authorization or release
* decision. A future operation must still pass source, field-release,
* freshness, language, correction, audit-before-disclosure, and
* serialization-boundary recheck gates.
*/
enum NightingaleDisclosureDisposition: string
{
case WithholdNotFound = 'withhold_not_found';
case ContinueToGovernedProjectionEvaluation = 'continue_to_governed_projection_evaluation';

/**
* The complete public failure tuple. It intentionally has no message,
* internal reason, identifier, retry hint, redirect, or variable field.
*
* @return null|array{status: int, code: string, cache_control: string}
*/
public function publicFailure(): ?array
{
return match ($this) {
self::WithholdNotFound => [
'status' => 404,
'code' => 'not_found',
'cache_control' => 'private, no-store, max-age=0',
],
self::ContinueToGovernedProjectionEvaluation => null,
};
}
}
20 changes: 20 additions & 0 deletions app/Nightingale/Disclosure/NightingaleEncounterBindingState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Nightingale\Disclosure;

/**
* Whether the requested opaque handle remains bound to the request-scoped
* current inpatient context at the disclosure boundary.
*
* No raw handle or encounter identifier crosses this domain seam.
*/
enum NightingaleEncounterBindingState: string
{
case MatchesCurrentContext = 'matches_current_context';
case WrongEncounter = 'wrong_encounter';

public function permitsGovernedEvaluation(): bool
{
return $this === self::MatchesCurrentContext;
}
}
Loading
Loading