@@ -3084,6 +3084,59 @@ fn store_into(
30843084 }
30853085}
30863086
3087+ // setcell_into is engine.py _reconcile_absorbed_heads' setcell (:1165): the
3088+ // reassembly's write REPLACES IN PLACE when the cell exists and APPENDS AT
3089+ // THE END when it doesn't — Store's remove+prepend would re-top the written
3090+ // cells and reorder the dump against python (order forensics, 2026-07-11).
3091+ fn setcell_into (
3092+ d : & mut V ,
3093+ cells : & mut Vec < ( Leaf , V ) > ,
3094+ nd : & mut N ,
3095+ ncells : & mut Vec < ( Leaf , N ) > ,
3096+ name : & Leaf ,
3097+ contents : V ,
3098+ ) {
3099+ let ncontents = v_to_n ( & contents) ;
3100+ let cellv = seq ( from_vec ( vec ! [
3101+ atom( Leaf :: S ( "CELL" . into( ) ) ) ,
3102+ atom( name. clone( ) ) ,
3103+ contents. clone( ) ,
3104+ ] ) ) ;
3105+ let mut entries = items ( & list_of ( d) ) ;
3106+ match entries. iter ( ) . position ( |c| {
3107+ let it = items ( & list_of ( c) ) ;
3108+ it. len ( ) >= 2 && matches ! ( aval( & it[ 1 ] ) , Some ( k) if k. nateq( name) )
3109+ } ) {
3110+ Some ( p) => entries[ p] = cellv,
3111+ None => entries. push ( cellv) ,
3112+ }
3113+ * d = seq ( from_vec ( entries) ) ;
3114+ match cells. iter_mut ( ) . find ( |( k, _) | k. nateq ( name) ) {
3115+ Some ( slot) => slot. 1 = contents,
3116+ None => cells. push ( ( name. clone ( ) , contents) ) ,
3117+ }
3118+ let ncellv = N :: S ( Rc :: new ( vec ! [
3119+ N :: A ( Rc :: new( Leaf :: S ( "CELL" . into( ) ) ) ) ,
3120+ N :: A ( Rc :: new( name. clone( ) ) ) ,
3121+ ncontents. clone( ) ,
3122+ ] ) ) ;
3123+ let mut nentries: Vec < N > = match nd {
3124+ N :: S ( v) => v. to_vec ( ) ,
3125+ _ => Vec :: new ( ) ,
3126+ } ;
3127+ match nentries. iter ( ) . position ( |c| {
3128+ matches ! ( c, N :: S ( it) if it. len( ) >= 2 && matches!( & it[ 1 ] , N :: A ( k) if k. nateq( name) ) )
3129+ } ) {
3130+ Some ( p) => nentries[ p] = ncellv,
3131+ None => nentries. push ( ncellv) ,
3132+ }
3133+ * nd = N :: S ( Rc :: new ( nentries) ) ;
3134+ match ncells. iter_mut ( ) . find ( |( k, _) | k. nateq ( name) ) {
3135+ Some ( slot) => slot. 1 = ncontents,
3136+ None => ncells. push ( ( name. clone ( ) , ncontents) ) ,
3137+ }
3138+ }
3139+
30873140// eval_rules is engine.py's _eval_rules (line 1188): the UNION of the given
30883141// rules' outputs over the store, deduplicated by row key in first-seen order
30893142// and keeping only sequence rows (Python keeps only tuples). It is the shared
@@ -4044,7 +4097,21 @@ fn op_run_rules(j: &J, srv: &mut Srv) -> Result<String, String> {
40444097 _ => None ,
40454098 } ;
40464099 let hole = || atom ( Leaf :: S ( "#" . to_string ( ) ) ) ;
4100+ // python's reconcile filter (engine.py:1523): touched ∩
4101+ // DERIVED_HEADS ∩ absorbed — a base-populated absorbed ft that
4102+ // changed in the round must NOT have its column reassembled
4103+ // from its pop here (its storage is the routed write's, not the
4104+ // derive cache's; visible on pre-layout stores where the extra
4105+ // write fills columns python leaves holed)
4106+ let derived: HashSet < String > = rules
4107+ . iter ( )
4108+ . chain ( agg_rules. iter ( ) )
4109+ . map ( |rr| leaf_text ( & rr. head ) )
4110+ . collect ( ) ;
40474111 for ftname in changed. iter ( ) {
4112+ if !derived. contains ( ftname) {
4113+ continue ;
4114+ }
40484115 let ( table, col) = match layout. get ( ftname) {
40494116 Some ( ( t, c) ) => ( t. clone ( ) , * c) ,
40504117 None => continue ,
@@ -4100,7 +4167,7 @@ fn op_run_rules(j: &J, srv: &mut Srv) -> Result<String, String> {
41004167 }
41014168 if !eqobj ( & row[ col - 1 ] , & v) {
41024169 row[ col - 1 ] = v;
4103- store_into ( & mut d, & mut cells, & mut nd, & mut ncells, & rc,
4170+ setcell_into ( & mut d, & mut cells, & mut nd, & mut ncells, & rc,
41044171 seq ( from_vec ( row) ) ) ;
41054172 }
41064173 }
@@ -4130,12 +4197,12 @@ fn op_run_rules(j: &J, srv: &mut Srv) -> Result<String, String> {
41304197 row. push ( hole ( ) ) ;
41314198 }
41324199 row[ col - 1 ] = v;
4133- store_into ( & mut d, & mut cells, & mut nd, & mut ncells, & rc,
4200+ setcell_into ( & mut d, & mut cells, & mut nd, & mut ncells, & rc,
41344201 seq ( from_vec ( row) ) ) ;
41354202 tbl. push ( seq ( from_vec ( vec ! [ ka] ) ) ) ;
41364203 }
41374204 if grew {
4138- store_into ( & mut d, & mut cells, & mut nd, & mut ncells, & tleaf,
4205+ setcell_into ( & mut d, & mut cells, & mut nd, & mut ncells, & tleaf,
41394206 seq ( from_vec ( tbl) ) ) ;
41404207 }
41414208 }
@@ -5690,6 +5757,131 @@ fn fold_fire(fire: &cooks::Fire, cells: &mut Vec<(Leaf, V)>) -> Result<(), Strin
56905757 Ok ( ( ) )
56915758}
56925759
5760+ // ======================= rekey_transitions (#20, native pipeline tail slice 1) ===
5761+ // engine.py:1608 rekey_transitions, ported whole (NOT canon-backed: its body is
5762+ // pure host list/dict manipulation over from_lam(D), no _apply/reduce call
5763+ // anywhere in it — confirmed against every DEF in shared/*.canon; none is named
5764+ // or shaped for this). Machine-scope each Transition's IDENTITY: Core.png/
5765+ // GraphDL model Transition(.id) as a SURROGATE, not the readings' name, so a
5766+ // base-vs-app reuse of a transition NAME must not merge one entity carrying
5767+ // two machines' froms/tos. Runs PER COMPILE PASS (the base is rekeyed first,
5768+ // frozen; an app compiling atop it via context_from:"resident" sees the
5769+ // base's transitions ALREADY surrogate-keyed and skips them, so the name->SMD
5770+ // map stays unambiguous per pass): a bare-named transition gets the surrogate
5771+ // "txn:{SMD}\x1f{name}" keyed by its defined-in SMD; rows already
5772+ // surrogate-keyed are skipped. Rewrites EVERY Transition-typed position — the
5773+ // role metamodel's declared referencing fact types PLUS the hardcoded
5774+ // machinery cells (smFrom/smTo/smTrigger/smGuard/smEmit/smMoore) and
5775+ // Guard_prevents_Transition — so no reference dangles. A bare name mapping to
5776+ // more than one SMD in one pass (genuinely ambiguous) is left as-is, never a
5777+ // partial rekey.
5778+ const TXN_SUR : & str = "txn:" ;
5779+
5780+ fn rekey_transitions_native ( cells : & mut Vec < ( Leaf , V ) > ) {
5781+ use std:: collections:: HashSet ;
5782+ let leaf = |s : & str | Leaf :: S ( s. to_string ( ) ) ;
5783+ // name_smd: bare transition-name key -> (name V, smd V), built from
5784+ // Transition_is_defined_in_State_Machine_Definition rows; a name seen
5785+ // with two DIFFERENT smd values anywhere in the population is ambiguous
5786+ // and excluded whole (python's unconditional overwrite-then-pop)
5787+ let mut name_smd: HashMap < String , ( V , V ) > = HashMap :: new ( ) ;
5788+ let mut ambiguous: HashSet < String > = HashSet :: new ( ) ;
5789+ for r in pop_rows ( cells, & leaf ( "Transition_is_defined_in_State_Machine_Definition" ) ) {
5790+ let it = items ( & list_of ( & r) ) ;
5791+ if it. len ( ) >= 2 {
5792+ let already_sur = match aval ( & it[ 0 ] ) {
5793+ Some ( l) => leaf_text ( & l) . starts_with ( TXN_SUR ) ,
5794+ None => false ,
5795+ } ;
5796+ if !already_sur {
5797+ let k = key_of ( & it[ 0 ] ) ;
5798+ if let Some ( ( _, existing_smd) ) = name_smd. get ( & k) {
5799+ if !eqobj ( existing_smd, & it[ 1 ] ) {
5800+ ambiguous. insert ( k. clone ( ) ) ;
5801+ }
5802+ }
5803+ name_smd. insert ( k, ( it[ 0 ] . clone ( ) , it[ 1 ] . clone ( ) ) ) ;
5804+ }
5805+ }
5806+ }
5807+ for k in & ambiguous {
5808+ name_smd. remove ( k) ;
5809+ }
5810+ if name_smd. is_empty ( ) {
5811+ return ;
5812+ }
5813+ // surro: bare-name key -> the surrogate atom, ready to substitute in place
5814+ let mut surro: HashMap < String , V > = HashMap :: new ( ) ;
5815+ for ( k, ( nm, smd) ) in & name_smd {
5816+ if let ( Some ( nl) , Some ( sl) ) = ( aval ( nm) , aval ( smd) ) {
5817+ let sur = format ! ( "{}{}\x1f {}" , TXN_SUR , leaf_text( & sl) , leaf_text( & nl) ) ;
5818+ surro. insert ( k. clone ( ) , atom ( Leaf :: S ( sur) ) ) ;
5819+ }
5820+ }
5821+ // pos_of: fact-type-name key -> 0-based Transition column position, from
5822+ // the role metamodel's Transition-typed declarations plus the hardcoded
5823+ // machinery cells (python's pos_of.update literal, unconditional so it
5824+ // overrides any role-derived entry for the same name)
5825+ let mut pos_of: HashMap < String , i64 > = HashMap :: new ( ) ;
5826+ for r in pop_rows ( cells, & leaf ( "role" ) ) {
5827+ let it = items ( & list_of ( & r) ) ;
5828+ if it. len ( ) >= 4 {
5829+ let is_transition =
5830+ matches ! ( aval( & it[ 3 ] ) . as_deref( ) , Some ( Leaf :: S ( s) ) if s == "Transition" ) ;
5831+ if is_transition {
5832+ if let Some ( Leaf :: I ( p) ) = aval ( & it[ 2 ] ) . as_deref ( ) {
5833+ pos_of. insert ( key_of ( & it[ 1 ] ) , * p - 1 ) ;
5834+ }
5835+ }
5836+ }
5837+ }
5838+ for ( name, pos) in [
5839+ ( "smFrom" , 0i64 ) ,
5840+ ( "smTo" , 0 ) ,
5841+ ( "smTrigger" , 0 ) ,
5842+ ( "smGuard" , 0 ) ,
5843+ ( "smEmit" , 0 ) ,
5844+ ( "smMoore" , 0 ) ,
5845+ ( "Guard_prevents_Transition" , 1 ) ,
5846+ ] {
5847+ pos_of. insert ( key_of ( & atom ( leaf ( name) ) ) , pos) ;
5848+ }
5849+ // the walk: every cell in D, in place; only a cell named in pos_of has
5850+ // its rows visited, and only the row's value AT that column, when it is
5851+ // a bare name in surro, is replaced — everything else copies through
5852+ for i in 0 ..cells. len ( ) {
5853+ let nk = key_of ( & atom ( cells[ i] . 0 . clone ( ) ) ) ;
5854+ let p = match pos_of. get ( & nk) {
5855+ Some ( & pp) if pp >= 0 => pp as usize ,
5856+ _ => continue ,
5857+ } ;
5858+ let rows = items ( & list_of ( & cells[ i] . 1 ) ) ;
5859+ if rows. is_empty ( ) {
5860+ continue ;
5861+ }
5862+ let mut changed = false ;
5863+ let mut new_rows: Vec < V > = Vec :: with_capacity ( rows. len ( ) ) ;
5864+ for row in rows {
5865+ let mut out_row = row. clone ( ) ;
5866+ if let Shape :: Seq ( rl) = shape ( & row) {
5867+ let mut cols = items ( & rl) ;
5868+ if cols. len ( ) > p {
5869+ let ck = key_of ( & cols[ p] ) ;
5870+ if let Some ( sur) = surro. get ( & ck) {
5871+ cols[ p] = sur. clone ( ) ;
5872+ out_row = seq ( from_vec ( cols) ) ;
5873+ changed = true ;
5874+ }
5875+ }
5876+ }
5877+ new_rows. push ( out_row) ;
5878+ }
5879+ if changed {
5880+ cells[ i] . 1 = seq ( from_vec ( new_rows) ) ;
5881+ }
5882+ }
5883+ }
5884+
56935885fn op_compile_model ( j : & J , srv : & mut Srv ) -> Result < String , String > {
56945886 use std:: collections:: { BTreeMap , HashMap , HashSet } ;
56955887 // args parse before anything runs (op_run_rules' discipline: a malformed
@@ -5903,6 +6095,10 @@ fn op_compile_model(j: &J, srv: &mut Srv) -> Result<String, String> {
59036095 let mut prose: Vec < String > = Vec :: new ( ) ;
59046096 let mut blocked: Vec < String > = Vec :: new ( ) ;
59056097 let mut classified = 0usize ;
6098+ // slice 1's identity-skip witness: true the instant ANY fold_fire or
6099+ // canon-DEF adoption actually mutates model_cells — "the fold produced
6100+ // no cells beyond the seed" (the probe-app case) is exactly !folded_any
6101+ let mut folded_any = false ;
59066102 // the native cook context (#20): the SAME names/subs/fts/plain/vals the
59076103 // ctx operand carries, in cooks form, built once per compile
59086104 let kn = cooks:: Known :: new ( & names, & subs, & fts, & plain, & vals) ;
@@ -6019,6 +6215,7 @@ fn op_compile_model(j: &J, srv: &mut Srv) -> Result<String, String> {
60196215 // future seam, kept correct even though dormant today)
60206216 model_cells = raw_cells_of ( & res) ;
60216217 accepted = true ;
6218+ folded_any = true ;
60226219 continue ;
60236220 }
60246221 // the panic fence (#32): a cook tripping a guarded-by-construction
@@ -6051,6 +6248,7 @@ fn op_compile_model(j: &J, srv: &mut Srv) -> Result<String, String> {
60516248 e, stmt
60526249 ) ) ;
60536250 }
6251+ folded_any = true ;
60546252 if trace_on {
60556253 let mut f = String :: new ( ) ;
60566254 cooks:: fire_json ( t, & fire, & mut f) ;
@@ -6095,6 +6293,62 @@ fn op_compile_model(j: &J, srv: &mut Srv) -> Result<String, String> {
60956293 translated. push ( e) ;
60966294 }
60976295 }
6296+ // slice 1 tail (native pipeline tail, #20): rekey_transitions
6297+ // (machine-scope transition identity — compiler.py's compile_model
6298+ // wrapper applies it right after the fold: D2 = system.rekey_transitions(D2))
6299+ rekey_transitions_native ( & mut model_cells) ;
6300+ // then the post-model rules fixpoint (protocol.py:1815's separate
6301+ // system.run_rules(D, ...) call, made after compile_model returns) through
6302+ // the EXISTING native op_run_rules machinery — the SAME save/swap/restore
6303+ // discipline the batch-classification derive above already uses, so this
6304+ // op stays pure: the resident's own store is read for dispatch throughout
6305+ // and left exactly as found, never overwritten with the compiled model.
6306+ // Identity-skip: a fold that never fired (no cell mutation, no canon-DEF
6307+ // adoption — the probe-app case) leaves model_cells identical to the seed,
6308+ // and running the fixpoint over an unchanged, already-derived seed is a
6309+ // proven no-op (round one finds nothing new and breaks immediately) — so
6310+ // it is skipped here to save the setup cost, never to change the answer.
6311+ if folded_any {
6312+ let saved_d2 = srv. d . clone ( ) ;
6313+ let saved_cells2 = srv. cells . clone ( ) ;
6314+ let saved_nd2 = srv. nd . clone ( ) ;
6315+ let saved_ncells2 = srv. ncells . clone ( ) ;
6316+ srv. d = cells_to_d ( & model_cells) ;
6317+ srv. cells = model_cells. clone ( ) ;
6318+ srv. nd = v_to_n ( & srv. d ) ;
6319+ srv. ncells = n_cells_of ( & srv. nd ) ;
6320+ let rules_req = J :: O ( Vec :: new ( ) ) ;
6321+ let derived2 = op_run_rules ( & rules_req, srv) ;
6322+ if derived2. is_ok ( ) {
6323+ // harvest from the TRUE store (srv.d), not the index Vec: python's
6324+ // Store re-tops every write, so a head cell NEW in the rules phase
6325+ // sits at the FRONT of D — the index Vec appends it at the end,
6326+ // which is a different (wrong) dump order (core.md's two
6327+ // new-in-rules heads, found by the order forensics 2026-07-11)
6328+ model_cells = raw_cells_of ( & srv. d ) ;
6329+ }
6330+ srv. d = saved_d2;
6331+ srv. cells = saved_cells2;
6332+ srv. nd = saved_nd2;
6333+ srv. ncells = saved_ncells2;
6334+ derived2?;
6335+ }
6336+ // rule_diagnostics: the ruleDiag population AFTER rules (python's own
6337+ // compiler.py:2489 reads it right after rekey, BEFORE the pipeline's
6338+ // separate run_rules call — but ruleDiag is a STAGE-1 COMPILE diagnostic,
6339+ // written only by the rule_if/rule_iff cook when a rule body fails to
6340+ // compile, never a derivation rule's HEAD in any known corpus, so
6341+ // run_rules never adds to it; reading it here, after everything, equals
6342+ // python's snapshot by construction)
6343+ let rulediag_rows: Vec < V > = pop_rows ( & model_cells, & leaf ( "ruleDiag" ) ) ;
6344+ let mut rulediag_json = String :: from ( "[" ) ;
6345+ for ( i, row) in rulediag_rows. iter ( ) . enumerate ( ) {
6346+ if i > 0 {
6347+ rulediag_json. push ( ',' ) ;
6348+ }
6349+ write_v ( row, & mut rulediag_json) ;
6350+ }
6351+ rulediag_json. push ( ']' ) ;
60986352 // the report: the seed contract's surviving keys (total/unclassified/prose)
60996353 // plus the honest diagnostics (classified/grammar/missing/blocked)
61006354 let mut r = String :: from ( "{\" total\" :" ) ;
@@ -6121,15 +6375,31 @@ fn op_compile_model(j: &J, srv: &mut Srv) -> Result<String, String> {
61216375 arr ( & missing, & mut r) ;
61226376 r. push_str ( ",\" blocked\" :" ) ;
61236377 arr ( & blocked, & mut r) ;
6378+ // slice 2 (native pipeline tail, #20): the Python rep contract's exact
6379+ // field names (compiler.py:2490), alongside the skeleton's own honest
6380+ // diagnostics above — total/prose already match; kinds is always the
6381+ // selfhost path's empty dict (a seed leftover, emitted verbatim, never
6382+ // innovated on); unparsed mirrors unclassified under python's external
6383+ // name; rule_diagnostics is the ruleDiag rows built above
6384+ r. push_str ( ",\" kinds\" :{}" ) ;
6385+ r. push_str ( ",\" unparsed\" :" ) ;
6386+ arr ( & unclassified, & mut r) ;
6387+ r. push_str ( ",\" rule_diagnostics\" :" ) ;
6388+ r. push_str ( & rulediag_json) ;
61246389 if trace_on {
61256390 // the differential dump: per statement, the fires' ⟨asserts, objs⟩
61266391 r. push_str ( ",\" translated\" :[" ) ;
61276392 r. push_str ( & translated. join ( "," ) ) ;
61286393 r. push ( ']' ) ;
61296394 }
61306395 if dump_store_on {
6131- // the folded store itself: python compile_model_selfhost's returned
6132- // D, full from_lam — the fold's own differential surface
6396+ // the store as of THIS slice's own boundary: fold -> rekey_transitions
6397+ // -> the post-model run_rules fixpoint (when it ran) — python's
6398+ // compile_model (WITH rekey) + the pipeline's separate run_rules call,
6399+ // full from_lam. (The fold slice's own narrower boundary,
6400+ // compile_model_selfhost's bare return, is no longer what dump_store
6401+ // answers — that comparison now lives in fold_pydump.py calling
6402+ // compile_model_selfhost directly, pre-rekey, pre-rules.)
61336403 r. push_str ( ",\" store\" :" ) ;
61346404 write_v ( & cells_to_d ( & model_cells) , & mut r) ;
61356405 }
0 commit comments