Skip to content

Commit a757635

Browse files
committed
[M3] sprint-log: brutally honest review of Round 3 MedCareMembraneGate (sprint closure)
Meta-3 final review surfaces 5 findings; ZERO CRITICAL: HIGH #1: Action operations (Operation::Act) unreachable via gate → Doc note recommended; orchestration layer is the right home for action gating HIGH #2: BtM Escalate "v1 limitation" tests use loose is_allowed() assertions → Recommend tightening to explicit assert_eq!(AccessDecision::Allow) for clearer future test-failure messages MEDIUM #3: Three name paths for Policy (rbac, gate, lib) → Backlog; doc-only canonicalization MEDIUM #4: 20-200 ns gate decision claim unbenchmarked → Backlog; criterion-based gate-bench follow-up LOW #5: TD-MEMBRANE-FIRST-VS-ANY untested → Backlog; vacuous in v1 without divergence case Sprint-wide closure: - Round 1 (medcare-rbac): 26 tests, solid, 2 CRITICAL fixes applied - Round 2 (medcare-realtime skeleton): 5 tests, 1 CRITICAL casing+HIPAA fix - Round 3 (gate impl): 33 tests, 2 HIGH documentation gaps - Total: 64 tests across 3 crates VERDICT: Ship. POLICY-1 medcare-side seam CLOSED for v1. Topology I-1/I-2/I-3/I-4 upheld. PR #29 three TD caveats honestly carried forward.
1 parent 5560235 commit a757635

1 file changed

Lines changed: 225 additions & 0 deletions

File tree

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# Meta-3 Review — MedCareMembraneGate (Round 3, Stage 3)
2+
3+
**Reviewer:** Meta agent 3 of 3 (final review pass)
4+
**Scope:** medcare-rs/crates/medcare-realtime/src/gate.rs + tests/{integration,regulatory}.rs
5+
**Method:** read W9-W12 commits + log entries; cross-check against
6+
PR #29 reference impl + Meta-1/Meta-2 carry-forwards + v1 boundary
7+
honesty.
8+
9+
> **Tone:** brutally honest. This is the last review pass before sprint
10+
> closure. Findings either block ship or document v1 limits explicitly
11+
> for future sessions. No filler.
12+
13+
---
14+
15+
## Verdict
16+
17+
**Ship Round 3 + close sprint.** Zero CRITICAL findings. Two HIGH
18+
findings are honest documentation gaps that the W12 author has
19+
already partially captured; meta surfaces them more sharply. Two
20+
MEDIUM findings deferred to follow-up sprints. The v1 surface is
21+
the right shape; it's just smaller than ambition.
22+
23+
| # | Severity | Finding | Action |
24+
|---|---|---|---|
25+
| 1 | HIGH | Action operations (Operation::Act) unreachable via gate — gate routes only Read/Write | Doc note in gate.rs module head + tests/regulatory.rs; orchestration layer is the right home for action gating |
26+
| 2 | HIGH | BtM Escalate "v1 limitation documented" tests will silently pass even if the gate IS later updated to return Escalate — the assertion is `is_allowed()` | Tighten future-spec assertions: explicit `assert_eq!(decision, AccessDecision::Allow)` so future Escalate flip is a real test failure |
27+
| 3 | MEDIUM | Three name paths for `Policy` (rbac, gate, lib) | Choose one canonical; document the others as legacy aliases |
28+
| 4 | MEDIUM | No bench harness validates 20-200 ns gate decision claim | Add to backlog; gate-bench follow-up sprint |
29+
| 5 | LOW | Module-head TD-MEMBRANE-FIRST-VS-ANY caveat carries forward but no test exercises the divergence case (predicate-specific RLS) | Backlog: write a unit test once a real divergence case is identified |
30+
31+
---
32+
33+
## HIGH #1 — Action operations unreachable via gate
34+
35+
**Finding.** `MedCareMembraneGate::should_emit` and `evaluate` route
36+
`gate_commit: bool` to `Operation::Read { depth }` (false) or
37+
`Operation::Write { predicate }` (true). Neither path reaches
38+
`Operation::Act { action }`.
39+
40+
This means actions like:
41+
- `Diagnosis.classify` / `finalize` / `retract`
42+
- `Prescription.issue` / `renew` / `revoke`
43+
- `Anamnese.append` (the ONLY mutation path for Anamnese per BMV-Ä §57)
44+
- `Ueberweisung.send` / `accept` / `decline`
45+
- `Patient.merge` / `anonymize` / `delete`
46+
47+
...cannot be gated through `MedCareMembraneGate`. The orchestration
48+
layer must call `medcare_rbac::Policy::evaluate(role, entity,
49+
Operation::Act { action })` directly.
50+
51+
**This is intentional from PR #29's design** — the upstream
52+
`MembraneGate` trait shape is `(commit: bool)` only. But it's a
53+
substantial v1 limit that medcare's append-only Anamnese semantic
54+
relies on.
55+
56+
**Super-helpful solution.** Add an explicit doc note to gate.rs
57+
module head:
58+
59+
```rust
60+
//! # Action operations not gated here
61+
//!
62+
//! `MedCareMembraneGate` routes only Read/Write per upstream's
63+
//! `(gate_commit: bool)` shape. Action operations (`Diagnosis.classify`,
64+
//! `Prescription.issue`, `Anamnese.append`, etc.) must go through
65+
//! `medcare_rbac::Policy::evaluate(role, entity, Operation::Act
66+
//! { action })` at the orchestration layer.
67+
//!
68+
//! This is by upstream design — `MembraneGate::should_emit` is the
69+
//! wire-shape of "is this projection allowed to leave the membrane",
70+
//! which is a Read/Write question. Action authorization (issue this
71+
//! prescription, finalize this diagnosis) is an orchestration-layer
72+
//! concern that fires before the eventual Read/Write projection.
73+
```
74+
75+
**Action.** Update gate.rs doc — small follow-up commit. Or carry
76+
this into the sprint summary as documented v1 limit.
77+
78+
---
79+
80+
## HIGH #2 — BtM Escalate "limitation documented" tests are too weak
81+
82+
**Finding.** W12's regulatory tests for the BtM/finalize/anonymize
83+
limitation:
84+
85+
```rust
86+
#[test]
87+
fn btm_escalate_path_documented_as_v1_limitation() {
88+
let gate = MedCareMembraneGate::from_medcare_policy("doctor", "Prescription");
89+
let decision = gate.evaluate(true);
90+
assert!(decision.is_allowed()); // ← too loose
91+
}
92+
```
93+
94+
`is_allowed()` returns true only for `AccessDecision::Allow`. If a
95+
future commit lands the Escalate wrapping (the documented future
96+
behavior), `decision` becomes `Escalate { reason: "..." }`, which is
97+
NOT `is_allowed()`, so the test FAILS. That's the desired flip — the
98+
test fails until the spec is updated.
99+
100+
But the failure message will be cryptic: "expected true, got false".
101+
The reader has to figure out whether is_allowed false means Deny,
102+
Escalate, or some other variant.
103+
104+
**Super-helpful solution.** Tighten the assertion:
105+
106+
```rust
107+
#[test]
108+
fn btm_escalate_path_documented_as_v1_limitation() {
109+
let gate = MedCareMembraneGate::from_medcare_policy("doctor", "Prescription");
110+
let decision = gate.evaluate(true);
111+
// v1: Allow uniformly (gate doesn't see btm_flag).
112+
// FUTURE: when row-context lands, this should become
113+
// AccessDecision::Escalate { reason: "BtM second signature required" }
114+
// for btm_flag=true rows. This explicit assert_eq! makes the future
115+
// flip a clear test failure: "expected Allow, got Escalate { ... }"
116+
// is much more readable than "expected true, got false".
117+
assert_eq!(decision, AccessDecision::Allow);
118+
}
119+
```
120+
121+
This applies to all three v1-limitation tests (BtM, finalize/retract,
122+
anonymize). Same pattern.
123+
124+
**Action.** Optional W12-revision-2 to tighten three assertions. Not
125+
blocking — the loose assertions still flip when future changes land.
126+
But the failure message clarity matters for someone diagnosing a CI
127+
break six months from now.
128+
129+
---
130+
131+
## MEDIUM #3 — Three name paths for `Policy`
132+
133+
**Finding.** Same `Policy` type reachable via:
134+
- `medcare_rbac::policy::Policy` (canonical home)
135+
- `medcare_realtime::gate::Policy` (re-exported via `pub use`)
136+
- `medcare_realtime::Policy` (lib.rs crate-root re-export)
137+
138+
Compilation-equivalent. Cognition-confusing. Future "import Policy"
139+
may grab any of three depending on which path the IDE auto-suggests.
140+
141+
**Super-helpful solution.** Pick one canonical path; document others
142+
as legacy aliases. Recommended canonical: `medcare_realtime::Policy`
143+
(crate-root) — same as smb-realtime's pattern. Other two paths stay
144+
for backward-compat but aren't documented as primary.
145+
146+
**Action.** Backlog. Doc-only update; no behavior change.
147+
148+
---
149+
150+
## MEDIUM #4 — No bench harness for 20-200 ns claim
151+
152+
**Finding.** Gate doc claims "decisions run at L1 inner speed
153+
(~20-200 ns)". v1 has zero benchmarks validating this.
154+
155+
**Super-helpful solution.** Backlog item: `gate-bench-v1` follow-up
156+
adding `criterion`-based microbenchmarks for:
157+
- `should_emit(_, _, _, true)` — allow path
158+
- `should_emit(_, _, _, true)` — deny path (unknown role)
159+
- `should_emit(_, _, _, false)` — read path
160+
- `evaluate(true)` (Escalate path when future row-context lands)
161+
162+
Targets: <500 ns p99 for current sync impl. If we can't hit that, the
163+
"20-200 ns" claim in the topology doc needs revision.
164+
165+
---
166+
167+
## LOW #5 — TD-MEMBRANE-FIRST-VS-ANY untested
168+
169+
**Finding.** PR #29 caveat #3 says `writable_predicates.first()` may
170+
deny when "any" should allow IF predicate-specific RLS conditions
171+
exist. medcare-realtime carries the caveat forward in module-head
172+
docs but writes no test.
173+
174+
**Super-helpful solution.** Backlog: when a real divergence case is
175+
identified (e.g. a Patient predicate where `Operation::Write { predicate }`
176+
has different RLS conditions per predicate), write a regression test.
177+
v1 doesn't have such a case, so the test would be vacuous.
178+
179+
---
180+
181+
## Sprint-wide closure assessment
182+
183+
**Round 1 (medcare-rbac):** Solid. 26 tests, 2 CRITICAL fixes applied
184+
in revision-2. Surface mirrors lance-graph-rbac with medcare entities.
185+
186+
**Round 2 (medcare-realtime skeleton):** Solid. 5 tests, 1 CRITICAL
187+
casing fix + HIPAA-grade values applied in W7-revision-2. Workspace
188+
registration clean.
189+
190+
**Round 3 (MedCareMembraneGate):** Solid. 33 tests across gate.rs +
191+
integration.rs + regulatory.rs. Two HIGH gaps (action ops unreachable,
192+
v1-limit assertions loose) are honest documentation issues, not
193+
correctness blockers.
194+
195+
**Total tests:** 64 across all three crates (medcare-rbac 26 +
196+
medcare-realtime 38).
197+
198+
**Compilation expectation:** medcare-rs root `cargo build` should
199+
work assuming:
200+
1. lance-graph submodule symlink is functional (vendor/lance-graph)
201+
2. lance-graph workspace builds (lance-graph-contract +
202+
lance-graph-callcenter + medcare-rbac symbol resolution)
203+
3. `StepDomain::Medcare` profile values match upstream (verified
204+
in W7-rev2)
205+
206+
If any of (1)/(2) fails on a fresh checkout, that's a vendor/submodule
207+
hygiene issue — not a sprint deliverable issue.
208+
209+
**Recommended follow-up sprint scope (smaller than this one):**
210+
1. Apply HIGH #1 doc note (5 min)
211+
2. Apply HIGH #2 assertion tighten (10 min)
212+
3. Bench harness for gate decisions (HIGH-MEDIUM, ~2 hours)
213+
4. Action-operation orchestration layer wrapper (HIGH-HIGH, half day)
214+
215+
---
216+
217+
## Verdict reaffirmed
218+
219+
**Ship.** v1 surface is correct and honest about its limits. Two HIGH
220+
findings are documentation/test-clarity issues, not correctness
221+
issues. Three MEDIUM/LOW findings go to backlog.
222+
223+
POLICY-1 / MEMBRANE-GATE-1 medcare-side seam is **CLOSED** for v1.
224+
Topology I-1 / I-2 / I-3 / I-4 invariants are upheld. PR #29's three
225+
TD caveats are honestly carried forward to the medcare side.

0 commit comments

Comments
 (0)