The topology is free. The node kinds are not. Every site is a tree built from five fixed kinds, so a leg is always (parent, child) and loss-by-leg reporting works at any depth without special-casing. Adding a floor substore later is a config change plus an opening deployment movement — never a migration.
| Kind | Stock | Custodian | Attests |
|---|---|---|---|
| WAREHOUSE | yes | Company A | yes |
| DEPOT | yes | Company A | yes |
| SUBSTORE | yes | either | yes |
| CONSUMPTION | no | — | receiver only |
| PROCESSOR | transit | vendor | yes |
Three enforcement levels, not two. Warn is the one that keeps this from feeling like another thing to do — most operational edge cases aren't abuse, they're Tuesday. Blocking them teaches staff to work around the app; recording them with a reason gives Company A the data to loosen or tighten later.
| Reason | Count | Policy |
|---|---|---|
| Cross-depot cover during shift gap | 14 | lateral_issue |
| Ward emergency, no return available | 9 | one_way_issue |
| Count mismatch under tolerance | 31 | variance_tol |
| Soiled beyond wash recovery | 6 | condemn_value |
Anything switched off does not appear anywhere in the UI — not greyed out, absent. A hotel should never see the words "infected linen" on any screen. But every module needs a defined behaviour when it is off, decided now rather than discovered in month three.
Condemnation approval. Off means the supervisor self-approves write-offs. That is the drain that hides theft — so it is non-optional above a value threshold Company A sets centrally.
Rewash tracking. Off means stained returns land in clean stock and the laundry's return rate looks perfect while the depot starves. A site can only switch it off if it accepts a variance tolerance instead.
Company A owns the shape. The hospital owns the people. This is the one screen the hospital administers itself — add, suspend, reassign — because housekeeping turnover is high enough that a Company A approval loop would jam the whole system inside a month.
| Name | Area | Shift | Code | Attestations 30d |
|---|
| issue | recv | attest | dispatch | condemn | approve | |
|---|---|---|---|---|---|---|
| Depot supervisor A | ✓ | ✓ | ✓ | ✓ | ✓ | — |
| Floor custodian H | ✓ | ✓ | ✓ | — | req | — |
| Housekeeping H | — | ✓ | ✓ | — | — | — |
| Laundry driver V | — | ✓ | ✓ | ✓ | — | — |
| Regional mgr A | — | — | — | — | — | ✓ |
Every movement has two parties. Where they share an employer, the movement is recorded but flagged as internal — it moves stock without transferring custody.
Nobody approves their own condemnation. At any depth, in any configuration, in an emergency. This is the single rule that stops write-off becoming the exit route for shrinkage.
A forbid policy without a route to request an exception is a dead end, and dead ends are how a system gets abandoned. Here the block is real, but there is always a next step — and whatever gets granted becomes part of the record rather than a favour nobody can trace.
Sahyadri Nashik runs two depots with separate par calculations. Lateral issue makes each depot's consumption rate meaningless, which breaks days-of-cover and therefore breaks emergency allocation.
At other sites the same policy is warn — the movement goes through with a reason code and shows up as its own leg in reconciliation.
Same code, different posture. That is the whole argument for policy-as-data rather than policy-as-if-statement.
Grant-once auto-expires. It cannot quietly become the new normal.
A window auto-expires too, and the site gets a notice three days before it lapses.
Permanent is not a grant at all — it forces the policy to change, with a named approver and a dated record. That is deliberate: it stops silent exceptions accumulating until nobody remembers who approved what.
| Ref | Policy | Scope | Outcome |
|---|---|---|---|
| X-0071 | lateral_issue | Once | open |
| X-0064 | lateral_issue | Once | granted |
| X-0058 | lateral_issue | Once | granted |
| X-0041 | condemn_value | Window | expired |
| X-0033 | one_way_issue | Permanent | declined |
The grant is itself attested and lands in the ledger, tagged to the movements it authorised. So the reconciliation statement carries the approval inline: "Room C-North issued 40 sheets to a South floor on 3 Aug, under G-0071, granted by V. Rane."
Without that, an exception is a loophole. With it, an exception is just another attested fact.
Supervisor at Room C is notified and the previously blocked action is now available — for exactly one movement.
Configuration changes are movements too. Policy edits, exception grants, roster suspensions and topology changes all land in the same append-only record as linen — because "who allowed this, and when" is exactly the question a disputed month turns on.
| Ref | Timestamp | Type | Detail | Qty | Under | Attested |
|---|---|---|---|---|---|---|
| MV-48244 | 03 Aug 14:36 | LATERAL_ISSUE | ROOM_C_NORTH → FLOOR_6 | 40 | G-0071 | RK·SP ✦ |
| CF-0071 | 03 Aug 14:31 | GRANT | ↳ lateral_issue · scope ONCE · expires on use | — | X-0071 | VR·AK ✦ |
| CF-0070 | 03 Aug 14:19 | REQUEST | ↳ raised by A.KULKARNI · attached MV-48231 | — | — | AK ✦ |
| MV-48231 | 03 Aug 14:08 | BLOCKED | ROOM_C_NORTH → FLOOR_6 · policy lateral_issue | 40 | — | ⚑ not executed |
| MV-48226 | 03 Aug 11:50 | ISSUE | ROOM_C_NORTH → FLOOR_2 | 28 | — | RK·MB ✦ |
| CF-0066 | 02 Aug 09:14 | ROSTER | ↳ D.SHINDE suspended by A.KULKARNI · code revoked | — | — | AK ✦ |
| CF-0063 | 31 Jul 17:02 | POLICY | ↳ variance_tol FORBID → WARN · tolerance 2% | — | — | VR ✦ |
| CF-0059 | 28 Jul 10:40 | TOPOLOGY | ↳ SUBSTORE FLOOR_6 added · opening deployment 120 | 120 | — | VR·AK ✦ |
| CF-0055 | 26 Jul 15:22 | MODULE | ↳ rewash_tracking enabled · variance_tol tightened | — | — | VR ✦ |
MV-48231 never executed, but it exists. Most systems throw blocked actions away, which loses the single most useful signal about whether a policy is working.
A site with 40 blocked attempts a month has a policy problem, not a discipline problem. A site with zero either has perfect policy or staff who have found a way around the app — and both deserve a look.
Blocked rows carry no quantity into any balance. They are evidence, not stock.