code wiki / (root) / nx_ferment_recipes.nx

nx_ferment_recipes.nx source

↩ module page · 127 lines · 4341 B

1// nx_ferment_recipes.nx -- R4 of the FERMENTATION ladder: the RECIPE 2// CATALOG. A data-driven library of named fermentations -- yogurt, soft 3// and aged cheese, sauerkraut, kimchi, kombucha, tempeh -- so "build a 4// cheesemaker and yogurt maker and other fermentation" is a LOOKUP, not 5// bespoke code. Per Rule 6/11 the parameters are DATA in a catalog, not 6// magic numbers in logic. 7// 8// THE EXCEED: every catalog entry is checked against the R0 never-poison 9// law (nx_ferment_validate). A cookbook lists times; it cannot prove a 10// recipe is structurally safe. Here a deliberately-unsafe spec (raw-milk 11// cheese under the 60-day floor; under-salted brine) is REFUSED by the 12// same gate the live process uses -- so the catalog cannot ship a 13// poison-by-construction recipe. 14// 15// genealogy_id: nishi_ferment_safety_r0 + data_driven_catalog_rule_11 16 17import "nx_syscalls.nx" 18import "nx_ferment_safety.nx" 19 20// ===== Sealed enum: catalog recipe ids =========================== 21 22const NX_RCP_YOGURT: i64 = 0 23const NX_RCP_CHEVRE: i64 = 1 // soft pasteurised cheese 24const NX_RCP_CHEDDAR: i64 = 2 // aged raw-milk cheese (>=60 d) 25const NX_RCP_SAUERKRAUT: i64 = 3 26const NX_RCP_KIMCHI: i64 = 4 27const NX_RCP_KOMBUCHA: i64 = 5 28const NX_RCP_TEMPEH: i64 = 6 29const NX_RCP_N: i64 = 7 30 31struct NxFermentRecipeSpec { 32 name_id: i64, 33 ferment_kind: i64, 34 oxygen: i64, 35 hold_temp_c: i64, 36 target_ph_milli: i64, 37 salt_pct_milli: i64, 38 set_hours: i64, 39 age_days: i64, 40} 41 42// Catalog lookup: fill a spec from the named recipe's data row. 43func nx_ferment_recipe(name_id: i64) -> *NxFermentRecipeSpec { 44 let s: *NxFermentRecipeSpec = (sys_mmap(64)) as *NxFermentRecipeSpec 45 s.name_id = name_id 46 s.ferment_kind = NX_FK_DAIRY_CULTURED 47 s.oxygen = NX_OX_AEROBIC 48 s.hold_temp_c = 25 49 s.target_ph_milli = 4500 50 s.salt_pct_milli = 0 51 s.set_hours = 24 52 s.age_days = 0 53 if name_id == NX_RCP_YOGURT { 54 s.ferment_kind = NX_FK_DAIRY_CULTURED 55 s.oxygen = NX_OX_ANAEROBIC 56 s.hold_temp_c = 43 57 s.target_ph_milli = 4400 58 s.salt_pct_milli = 0 59 s.set_hours = 8 60 s.age_days = 0 61 } 62 if name_id == NX_RCP_CHEVRE { 63 s.ferment_kind = NX_FK_CHEESE_PASTEUR 64 s.oxygen = NX_OX_AEROBIC 65 s.hold_temp_c = 22 66 s.target_ph_milli = 4600 67 s.salt_pct_milli = 0 68 s.set_hours = 18 69 s.age_days = 0 70 } 71 if name_id == NX_RCP_CHEDDAR { 72 s.ferment_kind = NX_FK_CHEESE_RAWMILK 73 s.oxygen = NX_OX_AEROBIC 74 s.hold_temp_c = 31 75 s.target_ph_milli = 5200 76 s.salt_pct_milli = 0 77 s.set_hours = 6 78 s.age_days = 90 79 } 80 if name_id == NX_RCP_SAUERKRAUT { 81 s.ferment_kind = NX_FK_VEG_ANAEROBIC 82 s.oxygen = NX_OX_ANAEROBIC 83 s.hold_temp_c = 20 84 s.target_ph_milli = 3500 85 s.salt_pct_milli = 2500 86 s.set_hours = 168 87 s.age_days = 0 88 } 89 if name_id == NX_RCP_KIMCHI { 90 s.ferment_kind = NX_FK_VEG_ANAEROBIC 91 s.oxygen = NX_OX_ANAEROBIC 92 s.hold_temp_c = 15 93 s.target_ph_milli = 4200 94 s.salt_pct_milli = 2500 95 s.set_hours = 120 96 s.age_days = 0 97 } 98 if name_id == NX_RCP_KOMBUCHA { 99 s.ferment_kind = NX_FK_ALCOHOL_ACID 100 s.oxygen = NX_OX_AEROBIC 101 s.hold_temp_c = 24 102 s.target_ph_milli = 3000 103 s.salt_pct_milli = 0 104 s.set_hours = 240 105 s.age_days = 0 106 } 107 if name_id == NX_RCP_TEMPEH { 108 s.ferment_kind = NX_FK_MOLD_RIPENED 109 s.oxygen = NX_OX_AEROBIC 110 s.hold_temp_c = 31 111 s.target_ph_milli = 6000 112 s.salt_pct_milli = 0 113 s.set_hours = 36 114 s.age_days = 0 115 } 116 return s 117} 118 119// Validate a catalog spec against the R0 never-poison law at its target 120// (acidified) state. Returns the sealed verdict. 121func nx_ferment_recipe_safe(env: *NxFermentSafetyEnvelope, s: *NxFermentRecipeSpec) -> nx_int { 122 let win: nx_size = env.max_hours_to_acidify 123 let nolog: *NxFermentReadingLog = (0) as *NxFermentReadingLog 124 return nx_ferment_validate(env, s.ferment_kind, s.oxygen, 125 (s.hold_temp_c * 1000) as nx_size, s.target_ph_milli as nx_size, 126 s.salt_pct_milli as nx_size, win, s.age_days as nx_size, nolog, win) 127}