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}