# Life-support mass ledger (Day-1 + Day-2a + Day-2b + Day-3)

Bot-native **close-or-alarm** mass books for a toy cabin / habitat-v1 loop (O₂ / CO₂ / H₂O + waste-water).  
Ledger owns conservation; adapters stub physics. No write APIs / Cloudflare Workers / dashboards.

- **schema_version:** `0.1.0` (Day-1) and `0.2.0` (Day-2a/2b/3; additive `waste_H2O`; `O2_buffer` already present)
- **epsilon_kg:** `1e-6` (fixed; documented on every tick and in `src/rates.py`)

## How to run (ship gate)

```bash
cd /workspace/mass-ledger
python3 -m src.run
# or: python3 run_ship_gate.py
```

Requires Python 3 stdlib only.

**Green gate:** Day-1 three faults + nominal; Day-2a habitat nominal `ok` / zero receipts; Day-2a faults + Day-2b `o2_buffer_dry` + Day-3 `condensate_fail` `alarm` + receipts.

## Layout

```
mass-ledger/
  schema.json                 # artifact copy of schema
  schema/ledger.schema.json   # canonical JSON Schema (0.1.0 | 0.2.0)
  inputs/rates.json           # rates mirror (cited + ASSUMPTION tags)
  src/
    adapter.py                # cabin + habitat-v1 waste-water + faults
    ledger.py                 # conservation, alarm, receipt mint
    rates.py                  # epsilon + BVAD-cited / assumption rates
    run.py                    # ship gate (Day-1 + Day-2a + Day-2b + Day-3)
    schema_validate.py
  ticks/<scenario>/*.json
  receipts/<scenario>/rcpt_*.json + *.md
  receipts/index.json
```

## Scenarios

| scenario_id          | schema | expect                                      |
|----------------------|--------|---------------------------------------------|
| `nominal`            | 0.1.0  | `ok`, no receipt                            |
| `scrubber_off`       | 0.1.0  | `alarm` + receipt (`scrubber_off`)          |
| `cabin_leak`         | 0.1.0  | `alarm` + receipt (`cabin_leak`)            |
| `power_cut`          | 0.1.0  | `alarm` + receipt (`power_cut`)             |
| `habitat_nominal`    | 0.2.0  | `ok`, no receipt (waste loop closed)        |
| `water_recovery_off` | 0.2.0  | `alarm` + receipt (`water_recovery_off`)    |
| `waste_leak`         | 0.2.0  | `alarm` + receipt (`waste_leak`)            |
| `o2_buffer_dry`      | 0.2.0  | `alarm` + receipt (`o2_buffer_dry`)         |
| `condensate_fail`    | 0.2.0  | `alarm` + receipt (`condensate_fail`)       |

Faults inject at `t=30` of a 60-tick run (`dt_s=1`).

## Rates policy

| Rate | confidence | citation |
|------|------------|----------|
| `o2_consume_kg_s` | **cited** | NASA BVAD Rev2 Feb 2022 (NTRS [20210024855](https://ntrs.nasa.gov/citations/20210024855)), **Table 3-31** — O₂ consumed **0.895 kg/CM-d** ÷ 86400 → kg/s |
| `co2_produce_kg_s` | **cited** | Same Table 3-31 — CO₂ load **1.085 kg/CM-d** ÷ 86400 → kg/s |
| `h2o_metabolic_kg_s` | **cited** | Same Table 3-31 — metabolic water **0.490 kg/CM-d** ÷ 86400 → kg/s |
| scrubber / O₂ supply / condensate setpoints | assumption | matched to crew for nominal close |
| waste generate / recover / plant power stubs | assumption | not BVAD hygiene/urine table bindings |

Supporting BVAD tables for metabolic context: 3-22…3-27.  
PDF: https://ntrs.nasa.gov/api/citations/20210024855/downloads/BVAD_2.15.22-final.pdf  

**Do not claim NASA validation** or flight certification for this toy corpus. BVAD supplies metabolic interface baselines only.

## Conservation

Per species each tick:

`residual = observed_Δstore − expected_Δstore(from declared flow effects)`

If any `|residual| > epsilon_kg` → `status=alarm` and a receipt is minted (JSON + markdown mirror, chmod immutable).

Day-2a waste loop: `H2O_tank` → `waste_H2O` (generate) → recover back to `H2O_tank`. Waste is a separate book — not aliased into the clean tank.
