Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
08c38a2
ci: make the Ward Flow browser journeys block
claude Sep 7, 2026
fc24258
Merge remote-tracking branch 'origin/main' into claude/vibrant-heisen…
claude Sep 7, 2026
cff171f
Merge branch 'main' into claude/vibrant-heisenberg-u9sadb
BigSimmo Sep 8, 2026
ffa7bf7
Merge branch 'main' into claude/vibrant-heisenberg-u9sadb
BigSimmo Sep 8, 2026
e3911f5
issues: queue eight ledger requests from the Ward Flow artifact review
claude Sep 9, 2026
d2a1622
Consolidate Ward journey gate and corrected artifact review requests
BigSimmo Sep 10, 2026
1f28dc4
Merge branch 'main' into claude/vibrant-heisenberg-u9sadb
BigSimmo Sep 12, 2026
a794066
Merge branch 'main' into claude/vibrant-heisenberg-u9sadb
BigSimmo Sep 12, 2026
7c11790
Merge branch 'main' into claude/vibrant-heisenberg-u9sadb
BigSimmo Sep 12, 2026
a661e7e
Merge branch 'main' into claude/vibrant-heisenberg-u9sadb
BigSimmo Sep 12, 2026
813f7f8
Merge branch 'main' into claude/vibrant-heisenberg-u9sadb
BigSimmo Sep 12, 2026
2da0609
Merge branch 'main' into claude/vibrant-heisenberg-u9sadb
BigSimmo Sep 12, 2026
c9c8ff5
Merge branch 'main' into claude/vibrant-heisenberg-u9sadb
BigSimmo Sep 12, 2026
4b7a034
Merge branch 'main' into claude/vibrant-heisenberg-u9sadb
BigSimmo Sep 12, 2026
a3f774f
Merge branch 'main' into claude/vibrant-heisenberg-u9sadb
BigSimmo Sep 12, 2026
61d4dd0
Merge branch 'main' into claude/vibrant-heisenberg-u9sadb
BigSimmo Sep 12, 2026
e90815c
Merge branch 'main' into claude/vibrant-heisenberg-u9sadb
BigSimmo Sep 12, 2026
387b572
Merge branch 'main' into claude/vibrant-heisenberg-u9sadb
BigSimmo Sep 12, 2026
bb2826a
fix(ci): require Ward Flow journeys on merge_group too
BigSimmo Sep 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 25 additions & 30 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -803,42 +803,37 @@ jobs:
# The seven Ward Flow browser journeys, and this lane BLOCKS.
#
# They live in the `chromium-mockups` project, which `test:e2e:pr` excludes with
# `--grep-invert @mockup`, so before this job the only lane that ran them was
# `ui-advisory` below — and that carries `continue-on-error: true`. A broken ward
# journey was therefore executed, reported, and passed anyway. Nothing in the
# repository could go red because of them.
# `--grep-invert @mockup`, so the only other lane running them is `ui-advisory`
# below — and that carries `continue-on-error: true`. A broken ward journey was
# therefore executed, reported, and passed anyway; nothing in the repository could
# go red because of them. Not hypothetical: MERGE 01-03 (2026-09-05) turned five
# ward routes into redirect stubs, these journeys went red, and the drift sat
# unnoticed for a day because no required check could see it.
#
# They are kept separate from `ui-advisory` rather than by removing its
# `continue-on-error`, because that lane also runs `@quarantine` — tests that are
# quarantined precisely because they are flaky, and which must stay non-blocking.
# None of the seven is quarantined and all seven are tagged `@mockup`; checked
# against `tests/flake-ledger.json`, which holds one entry and it is not a ward spec.
#
# 🔴 INERT UNTIL SOMEBODY TURNS IT ON, AND THAT IS THE WHOLE SAFETY PROPERTY.
# ENABLED 2026-09-06 by owner decision. Until then this job was gated behind
# `vars.WARD_JOURNEYS_BLOCKING`, a repository variable that was never set, so it
# always skipped. That gate existed for a real reason — an already-red lane made
# blocking stops every UI pull request in the REPOSITORY, a blast radius entirely
# outside Ward Flow — and it was to be lifted only with a green run in hand. That
# run: `npm run test:e2e:ward-journeys` at 42c4dec, `3 skipped, 71 passed`. The
# three skips are the parked morning-page pair (see `tests/ui-ward-morning.spec.ts`)
# and one inconclusive forced-colours probe; none is a failure.
#
# These seven have never gated anything, so nothing has kept them passing. A lane
# that is already red, made blocking, stops every UI pull request in the REPOSITORY
# — a blast radius entirely outside Ward Flow. The first draft relied on a comment
# saying "do not merge until they are seen green once", and that was not good
# enough: this reaches `main` inside a ward pull request measured at 1,629 commits
# and 429 files, of which `ci.yml` is one. A reviewer approving 429 files of ward
# work is not making a decision about CI policy; they are not even seeing one.
#
# So merging it does NOTHING. `vars.WARD_JOURNEYS_BLOCKING` is unset by default, the
# job is skipped, and `pr-required` records it as skipped-and-fine. Turning it on is
# one deliberate act in repository settings, at the moment somebody has a green run
# of the seven in front of them.
#
# ⚠️ A rule enforced only by a comment is not enforced. Splitting this onto its own
# branch was considered and is weaker — it still depends on somebody remembering why
# the branch is separate. This removes the dependency on anyone reading anything.
# ⚠️ The `pr-required` aggregate carries the matching `require_success` entry and
# its condition mirrors this `if:` exactly. The two have to move together; a future
# edit to one is an edit to both.
ui-ward-journeys:
name: Ward Flow browser journeys
needs: changes
if: >
vars.WARD_JOURNEYS_BLOCKING == 'true' &&
github.event_name == 'pull_request' &&
needs.changes.outputs.ui_changed == 'true' &&
(github.event_name == 'pull_request' || github.event_name == 'merge_group') &&
github.event.pull_request.draft != true
Comment thread
BigSimmo marked this conversation as resolved.
runs-on: ubuntu-24.04
timeout-minutes: 30
Expand Down Expand Up @@ -1372,7 +1367,6 @@ jobs:
UI_FAST_RESULT: ${{ needs.ui-critical-fast.result }}
UI_RESULT: ${{ needs.ui-critical.result }}
WARD_JOURNEYS_RESULT: ${{ needs.ui-ward-journeys.result }}
WARD_JOURNEYS_BLOCKING: ${{ vars.WARD_JOURNEYS_BLOCKING }}
LIGHTHOUSE_RESULT: ${{ needs.lighthouse-budget.result }}
DB_RESULT: ${{ needs.db-reset-verify.result }}
CARING_CONTACTS_DB_RESULT: ${{ needs.caring-contacts-db.result }}
Expand Down Expand Up @@ -1488,13 +1482,14 @@ jobs:
# The seven Ward Flow browser journeys. Required here as well as blocking in their own
# job: without an entry in this aggregate a failing lane is reported and merged past,
# which is the same hole `continue-on-error: true` left. Gated to match that job's own
# `if:` exactly — pull_request, ui_changed, not draft — so an out-of-scope run reports
# `if:` exactly — pull_request|merge_group, ui_changed, not draft — so an out-of-scope run reports
# skipped-and-fine rather than a false "result was skipped" failure.
# ⚠️ `WARD_JOURNEYS_BLOCKING` FIRST, and it mirrors the job's own `if:` exactly. Without it
# here, the aggregate would demand success from a job the workflow skipped by design, and
# the flag would fail closed on every UI pull request — the opposite of inert. The two
# conditions have to move together; a future edit to one is an edit to both.
if [ "$WARD_JOURNEYS_BLOCKING" = "true" ] && [ "$UI_CHANGED" = "true" ] && [ "$PR_DRAFT" != "true" ] && [ "$EVENT_NAME" = "pull_request" ]; then
# ⚠️ This mirrors the job's own `if:` exactly. The two conditions have to move together;
# a future edit to one is an edit to both. Both carried a leading
# `WARD_JOURNEYS_BLOCKING` term until 2026-09-06, when the lane was enabled and the term
# was dropped from both in one commit — see that job's own comment for the green run it
# was lifted against.
if [ "$UI_CHANGED" = "true" ] && [ "$PR_DRAFT" != "true" ] && { [ "$EVENT_NAME" = "pull_request" ] || [ "$EVENT_NAME" = "merge_group" ]; }; then
require_success "ward-flow-journeys" "$WARD_JOURNEYS_RESULT"
Comment thread
BigSimmo marked this conversation as resolved.
else
require_skipped_or_success "ward-flow-journeys" "$WARD_JOURNEYS_RESULT"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": 2,
"id": "443423fd-e6c5-4770-9453-c17ea545bedb",
"createdOn": "2026-09-09",
"action": "add",
"payload": {
"pri": "P2",
"type": "issue",
"summary": "Ward Flow: the Command mockup emits ten eligibility gates, the engine twelve, and nothing keeps the two lists together",
"detail": "FOUND 2026-09-09. Command's gatesFor() emits allocatable_bed, capability, security, sex_mix, acuity, specialling, authorisation, catchment, prior_decline, freshness. ELIGIBILITY_GATES in ward-eligibility.ts is age, allocatable_bed, authorisation, capacity_freshness, cohort, forensic, legal_status, prior_decline, security, sex_designation, sex_mix, specialling. THE DIFFERENCES: the engine carries age, forensic and legal_status, which the mockup has no equivalent of; it splits sex_designation out as its own gate where the mockup folds designation into sex_mix; and it has no acuity or catchment gate, both of which the mockup shows to the coordinator as first-class checks with their own sentences. 'capability' and 'cohort' are the same check under two names. WHY IT MATTERS: the third-edition standard governs colour, type, wording and behaviour in close detail and says nothing at all about the rule model, so there is no mechanism holding the mockup's clinical logic to the engine's. The two have already drifted in both directions, and the mockup is what a clinician is shown when the design is reviewed. NEXT ACTION: add a short subsection to section 8 naming ELIGIBILITY_GATES as the source of truth for what a gate is, and one line in the definition of done requiring a mockup's gate list to be a subset of it under the engine's own names. Cheap to state, and it stops the next mockup inventing an eleventh gate.",
"source": "Artifact review 2026-09-09 (d821a155 vs ward-eligibility.ts)",
"issueUlid": "01M22T8P4BS6PNK999E9RGZ79W"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": 2,
"id": "50590ddf-5cb2-4e57-b4ca-7925796a99a2",
"createdOn": "2026-09-09",
"action": "add",
"payload": {
"pri": "P1",
"type": "issue",
"summary": "Ward Flow: enforce hospital authorisation as a candidate destination constraint",
"detail": "FOUND 2026-09-09 by review of artifacts d821a155 and e7f7b6f2 against src/components/ward-management/. Both artifacts omit authorisation from ABSOLUTE_GATES, and ward-flow-reducer.ts places it in SUITABILITY_GATES. The canonical destination contract in docs/ward-flow-context.md and docs/ward-management-decisions.md ADR 1 distinguishes lawful detention under Forms 3A/3B in an unauthorised general hospital emergency department from eligibility for an involuntary inpatient destination. A patient's current-location detention must not be presented as unlawful merely because that site is unauthorised. Candidate involuntary inpatient destinations must satisfy the hospital-authorisation requirement; a coordinator's recorded reason cannot waive it. NEXT ACTION: reconcile the reducer and mockup destination gates with that existing contract, keep the current-location and destination cases separate in tests, and document the distinction beside the gate. The separate security/locked-bed classification still needs assessment against the actual destination requirements; it is not resolved by this authorisation finding.",
Comment thread
BigSimmo marked this conversation as resolved.
"source": "Artifact review 2026-09-09 (d821a155, e7f7b6f2, ward-flow-reducer.ts)",
"issueUlid": "01M22T7W66MF6MZ7NDHPMMMANG"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": 2,
"id": "6273682d-423c-4b2f-8b99-00c5ec4e9b15",
"createdOn": "2026-09-09",
"action": "add",
"payload": {
"pri": "P2",
"type": "issue",
"summary": "ward-flow-reducer.ts contradicts itself on capacity_freshness: its comment calls it a non-overridable world fact, its array lists it as overridable",
"detail": "FOUND 2026-09-09. In src/components/ward-management/ward-flow-reducer.ts, the classification comment at line 440 reads: 'THE WORLD — is there a bed at all? allocatable_bed, capacity_freshness, specialling. Already refused separately, above, and NOT overridable: no reason typed into a form creates a bed.' Forty-seven lines below, SUITABILITY_GATES includes capacity_freshness, carrying its own comment that it was ADDED BY OWNER RULING on 2026-09-02 and is the only gate ever moved out of the world-fact group, on the reasoning that a stale bed count is information rather than a wall and 'I have confirmed the current bed state with the ward directly' is a named person taking responsibility. The array is correct and current; the comment above it was not updated when the ruling landed. WHY IT MATTERS: the two sit in one file and give opposite answers to 'can a recorded reason get past a stale bed count'. The prose comment is the part a reader meets first and is written in the tone of a safety boundary, so it is the one that gets believed. It has already propagated: ledger row #Q6WD1M repeats the stale grouping in its reconstructed detail, which means the error is now in two places. NEXT ACTION: correct line 440 to list capacity_freshness with the suitability gates and note the ruling, then correct the same sentence in #Q6WD1M. A one-line change, but it removes a documented contradiction inside a clinical-safety classification.",
"source": "Artifact review 2026-09-09 (ward-flow-reducer.ts lines 440 and 487)",
"issueUlid": "01M22T8PEHKHGJ127YYBQ6XJQ7"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": 2,
"id": "6fbe61cf-75fc-4c2e-b003-22b51f66a734",
"createdOn": "2026-09-09",
"action": "update",
"payload": {
"id": "#G4YPNE",
"detail": "Unchanged and unchangeable from inside the project: it requires Aboriginal health practitioners. Recorded here as a Ward Flow gate in its own right rather than relying on the Caring Contacts entry (#1S81R8), because Ward Flow is a separate surface — bed allocation, transport and detention-adjacent legal status — with its own cultural safety questions, and an item filed under another feature will not be found by someone preparing Ward Flow for use. HARD GATE BEFORE ANY REAL-PATIENT USE. Not a blocker on further prototype building. CONFIRMED 2026-09-09 across the design surface as well as the code: neither the Command mockup (artifact d821a155) nor the third-edition design system (artifact e7f7b6f2) contains a rule, a field, a gate or a note on Aboriginal cultural safety, in roughly 27,500 lines between them. The standard is otherwise scrupulous about recording what it has decided and what it has deliberately left out, with a whole section for decisions taken and decisions for the owner, which makes the silence read as nothing having been considered rather than as work owned elsewhere. NEXT ACTION UNCHANGED and still outside this team, but one cheap thing helps now: add a line to the standard's decisions-for-the-owner section recording that the review is a known gate owned by Aboriginal health practitioners, so each of the eighteen mockup migrations does not rediscover the gap independently.",
"baseRowFingerprint": "26c3f2f27a4db03535ecb30a81e37bc6cacf4e262dc73d52b8b86a559439e09b"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": 2,
"id": "7b4cbd9f-8736-42c5-baf3-722870b9e4c4",
"createdOn": "2026-09-09",
"action": "update",
"payload": {
"id": "#6DT5K7",
"detail": "MEASURED 2026-09-04: the rule is live in 8 files, 4 of them source — escalation/page.tsx, escalation-board.tsx, referrals/referral-destination-options.ts, ward-derivations.ts — plus four planning documents. It is written in capitals with reasoning ('THIS BOARD RECORDS AND SHOWS. IT SUGGESTS NOTHING — no least-bad options, no ranking of wards the patient does not fit, no near-miss computation') and reads exactly like a safety principle, so every session that meets it obeys it. Ward Builder Two reports the owner reversed it in conversation, wanting active patient-to-bed matching with the clinician making the final acceptance. THE REVERSAL IS NOT IN docs/ward-flow/owner-rulings-2026-09-04.md, whose last change predates the conversation, so it exists only in one chat transcript. WHY THIS IS P1: the cost is not the unbuilt feature, it is that the codebase actively instructs the next session to build the opposite, confidently. Also note the regulatory dimension flagged on PR #2597 and left unresolved there: a board that ranks wards for a patient is closer to clinical decision support than one that records, which is the TGA/SaMD classification question. Needs an owner decision, not an integrator's. ADDED 2026-09-09, and it changes the picture: THE REVERSAL IS ALREADY BUILT IN THE MOCKUP. Command (artifact d821a155) presents an explainable shortlist whose shortlistFor() sorts every ward in the network for the selected patient, first by availability class in the fixed order eligible, overridable, declined, no_specialling, no_bed, then by ready beds descending. That is active patient-to-bed matching with a ranked list, presented as the screen's third column, which is the behaviour the owner asked for in conversation and the opposite of what the four source files still instruct. So the codebase and the reference mockup now teach opposite rules, and the mockup is the artifact a clinician is shown at review. THE STANDARD IS SILENT ON IT, which is the part to fix first and is nearly free: section 8.4 is careful that no verdict is ever drawn about a person, and says nothing about whether ordering the wards is a suggestion. One sentence settles it either way. The TGA/SaMD point recorded above applies with more force now that a ranked list exists in a built artifact rather than only in conversation.",
"baseRowFingerprint": "362e8d9e3fda58809cdd33e2a795a00866b0d1b3aec25a5dff820d8fc3cc372b"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": 2,
"id": "86df0514-1263-40ed-a511-c94b44367fef",
"createdOn": "2026-09-09",
"action": "update",
"payload": {
"id": "#BAY1TY",
"detail": "MEASURED 2026-09-04: ward-model.ts declares 'sex: Sex' and no gender field anywhere. ward-eligibility.ts consumes it directly — unit.sexMix[movement.sex] and sexDesignationAccepts(unit.sexDesignation, movement.sex) — so a patient's bed eligibility is computed from that one value. There is currently no way to record that a patient's gender identity differs from their recorded sex, and therefore no way for the board to represent, let alone match on, that distinction. Harmless today (synthetic prototype, no real patients) and expensive later: the data model is the costly thing to change, the matching rule is cheap. Ward Builder Two relayed the owner's view that this goes to a clinician who works in this area rather than being settled internally, and that is right. TREAT AS A GATE BEFORE ANY REAL-PATIENT USE, not as an emergency. ADDED 2026-09-09 by review of the Command mockup (artifact d821a155) and the third-edition standard (artifact e7f7b6f2). The prototype carries the same single field and hardens it: a movement holds sex Male or Female, a ward holds sexDesignation plus sexMix as a two-key object of Female and Male, and the sex_mix gate reads both. MORE THAN THAT, THE PAGE'S OWN RECONCILE CHECK ENFORCES THE BINARY: it fails the ward unless sexMix.Female plus sexMix.Male equals occupied exactly, so a third value cannot be added to the fixture without breaking the screen's consistency line. The design system says nothing about sex or gender anywhere in its ten rules, its wording section or its definition of done, so nothing stops the next of the eighteen mockups copying the binary field forward. TWO SMALL THINGS WORTH DOING BEFORE THE CLINICIAN REVIEW LANDS, neither of which pre-empts it: record in section 8 of the standard that the modelling question is open and owned outside the build, and stop new mockups inheriting the field by copy.",
"baseRowFingerprint": "d90500f16865b5545e5724ce44877e5531512f2a280aa0dec0b0587c0eb53fb2"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": 2,
"id": "bb829d78-5b96-4216-b9bd-e94e76db6bf4",
"createdOn": "2026-09-09",
"action": "add",
"payload": {
"pri": "P2",
"type": "issue",
"summary": "Ward Flow standard states 23 wards across 17 sites, and its own reference build carries 16 across 9",
"detail": "FOUND 2026-09-09. Section 8.3 of the third-edition standard (artifact e7f7b6f2) states as a data rule: 'There are eight emergency departments and Joondalup Health Campus and Peel Health Campus have no inpatient ward in the data. There are twenty-three wards across seventeen sites. A page reads the collection and never carries a hard-coded nine.' MEASURED in artifact d821a155, the reference build the standard points at: UNITS holds 16 wards across 9 sites (ARM, BTY, FRE, FSH, GRY, RGH, RPH, SCGH, SJGM), and EDS holds 8 departments. The department figure is right. The ward and site figures are not. WHY IT MATTERS BEYOND A TYPO: section 6 of the standard renders every one of its component examples live from Command's own stylesheet and data, so the standard asserts a figure its own reference build contradicts on the same page. Anyone building the next mockup takes twenty-three from the prose and finds sixteen in the data. Note the standard's WACHS handling is not the defect: no WA Country ward exists in the data and that absence is deliberately and correctly stated by the 'WA Country states its absences' rule. NEXT ACTION: decide which number is the network's real size, then either extend Command's UNITS to match or restate 8.3 as the real figure with a sentence saying Command carries a subset. Check the same sentence in the repository's own ward-sites.ts before choosing, since a third figure there would make this a three-way disagreement.",
"source": "Artifact review 2026-09-09 (e7f7b6f2 section 8.3 vs d821a155 UNITS)",
"issueUlid": "01M22T8P9KA2TNT9NXNYZ2M1DA"
}
}
Loading
Loading