11// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
22
33/**
4- * [#15356 measured, #14744 closed] PIN — a `record-before-update` flow reaches
4+ * [#15356 measured, commit 4f85e4d11 closed] PIN — a `record-before-update` flow reaches
55 * NO write shape into the BATCH PAYLOAD of a `multi: true` update.
66 *
77 * ## What changed about this file, and what did not
88 *
99 * It was written for #15356 as a MEASUREMENT and it answered NOT BOUNDED: one
10- * shape (`S5`) reached the payload. #14744 then ruled the door closed —
10+ * shape (`S5`) reached the payload. The option-A ruling (commit 4f85e4d11) then closed the door —
1111 * `buildContext` decouples the flow-facing roots from the engine's own objects
1212 * (`decoupleFromEngineState`) — and this file was adopted whole as the pin.
1313 * `S5`, `S5b` and the SQL replica were FLIPPED to their opposites in that same
1919 *
2020 * The two controls are why the negatives are readable, and BOTH must keep
2121 * firing: the positive control (a script hook that ASSIGNS the payload — the
22- * #14744 residue shape — still lands the LAST dispatch's value on every row,
23- * because #14744's fix is about aliasing and deliberately does not touch that
22+ * residue shape commit 4f85e4d11 pins — still lands the LAST dispatch's value on every row,
23+ * because commit 4f85e4d11 fixes aliasing and deliberately does not touch that
2424 * residue) and the #14099 armed control (divergent key sets are still refused
2525 * whole). If either stops firing, this file has stopped measuring.
2626 *
2727 * ## Why this file exists
2828 *
29- * #14744's census found the in-repo population of same-key / per-row-VALUE
29+ * The census in commit 03c1b0f6f found the in-repo population of same-key / per-row-VALUE
3030 * `beforeUpdate` payload rewrites is ZERO across 23 production registration
3131 * sites. One door that zero does not bound was named there but never driven:
3232 * `record-change-trigger.ts`'s `start()` binds `beforeUpdate` for the
4444 * write shape that mutated a nested value IN PLACE therefore wrote the batch
4545 * payload without ever assigning a top-level key. See `S5` below.
4646 *
47- * ⭐ #14744 made the reading's sentence TRUE AS STATED rather than deleting it:
47+ * ⭐ Commit 4f85e4d11 made the reading's sentence TRUE AS STATED rather than deleting it:
4848 * the overlay still materialises a new object, and `decoupleFromEngineState`
4949 * now makes that true of the object's CONTENTS too. The distinction is worth
5050 * keeping in front of the next reader — "a new object" and "reaches nothing"
8484 * ## Two controls, because a negative needs them
8585 *
8686 * - `positive control` — a script `beforeUpdate` hook that DOES assign the
87- * payload (the #14744 pinned residue shape), in this same harness, showing
87+ * payload (the residue shape commit 4f85e4d11 pins ), in this same harness, showing
8888 * the last dispatch's value on every row. Without it firing, every "does not
8989 * reach" below would be a claim about this harness, not about flows.
9090 * - `#14099 armed control` — a hook writing DIVERGENT KEY SETS per row must
9191 * be refused whole, so "the refusal did not fire for the nested shape" is a
9292 * measurement rather than an unarmed check.
9393 *
9494 * ⚠️ #15356 was a MEASUREMENT card: no guard, no write-shape change, no ADR.
95- * #14744 carries the fix, and it is still not a write-shape change: ADR-0058
95+ * Commit 4f85e4d11 carries the fix, and it is still not a write-shape change: ADR-0058
9696 * Addendum II D3 stands untouched — the engine does not split its own write,
9797 * one payload still serves N rows, and every per-row context is still handed
9898 * that one object (asserted in `S5`). What changed is only that the object a
@@ -203,7 +203,7 @@ function makeDriver(): any {
203203
204204/**
205205 * `residue` and `tags` are DECLARED fields on purpose: the engine's
206- * declared-field door (#8738 pre-hook / #13657 post-hook) refuses a payload
206+ * declared-field door (#8738 pre-hook / commit b003cf2e8 post-hook) refuses a payload
207207 * carrying an undeclared key, so a probe writing an undeclared name would be
208208 * measuring that refusal instead of the reach question.
209209 *
@@ -452,7 +452,7 @@ describe('[#15356] can a record-before-update flow reach a multi:true batch payl
452452
453453 // The residue shape: a per-row-VALUE write of the SAME key on every row.
454454 // The key SET is identical across rows, so #14099's divergence refusal does
455- // not fire — that is precisely the blind spot #14744 is weighing .
455+ // not fire — that is precisely the blind spot commit 4f85e4d11 left unguarded .
456456 const dispatched : string [ ] = [ ] ;
457457 stack . objectql . registerHook (
458458 'beforeUpdate' ,
@@ -639,7 +639,7 @@ describe('[#15356] can a record-before-update flow reach a multi:true batch payl
639639 /**
640640 * ⭐ S5 — THE PIN. This case was written on 2026-09-04 as a CHARACTERISATION
641641 * of the defect (the nested in-place mutation REACHED the payload, and both
642- * rows carried both dispatches' contributions). #14744 closed the door on the
642+ * rows carried both dispatches' contributions). Commit 4f85e4d11 closed the door on the
643643 * same day by decoupling the flow-facing roots from the engine's own objects
644644 * (`decoupleFromEngineState`, called at the end of `buildContext`), and the
645645 * case was flipped in the same PR — the assertions below are the OPPOSITE of
@@ -683,7 +683,7 @@ describe('[#15356] can a record-before-update flow reach a multi:true batch payl
683683 expect ( observed , 'the script function must have RUN, once per row' ) . toHaveLength ( 2 ) ;
684684 // Row 2 does NOT see row 1's mutation: each dispatch is handed its own copy
685685 // of the nested value, so neither run can observe the other's write. Before
686- // #14744 the second reading was `["seed","REACHED-alpha"]`.
686+ // commit 4f85e4d11 the second reading was `["seed","REACHED-alpha"]`.
687687 expect ( observed [ 0 ] ?. tagsAsSeen ) . toBe ( '["seed"]' ) ;
688688 expect ( observed [ 1 ] ?. tagsAsSeen ) . toBe ( '["seed"]' ) ;
689689 // Reference identity, measured across the same boundary the defect was
@@ -702,7 +702,7 @@ describe('[#15356] can a record-before-update flow reach a multi:true batch payl
702702
703703 // ⭐ The persisted rows: the SET clause carries what the CALLER wrote, and
704704 // no row carries a value derived from the other row's pre-image. Before
705- // #14744 both rows read `['seed','REACHED-alpha','REACHED-beta']`.
705+ // commit 4f85e4d11 both rows read `['seed','REACHED-alpha','REACHED-beta']`.
706706 expect ( wrote , 'and the write still succeeds — this is not a refusal' ) . toMatchObject ( { ok : true } ) ;
707707 const rows = await rowsByTitle ( stack . data , object ) ;
708708 expect ( rows . get ( 'alpha' ) ?. tags ) . toEqual ( [ 'seed' ] ) ;
@@ -735,7 +735,7 @@ describe('[#15356] can a record-before-update flow reach a multi:true batch payl
735735 // ⚠️ THE BREAKING HALF, pinned deliberately. The aliasing was never
736736 // multi-specific: on a by-id write the same in-place mutation reached this
737737 // write's own payload and PERSISTED correctly (`['seed','REACHED']` before
738- // #14744 ), so it read as a working per-row write path rather than as
738+ // commit 4f85e4d11 ), so it read as a working per-row write path rather than as
739739 // corruption. It is the same alias, so closing the door closes it here too,
740740 // and a stack author using it loses a write that used to land. That is why
741741 // the changeset carries a BREAKING banner: the alternative is `update_record`
@@ -802,7 +802,7 @@ describe('[#15356] can a record-before-update flow reach a multi:true batch payl
802802 } , 20000 ) ;
803803
804804 /**
805- * ⭐ [#14744 ] THE CONTROL ON THE FIX'S SHAPE — why a COPY and not a FREEZE.
805+ * ⭐ [commit 4f85e4d11 ] THE CONTROL ON THE FIX'S SHAPE — why a COPY and not a FREEZE.
806806 *
807807 * The ruling named deep-copy and freeze as alternatives. They are not
808808 * equivalent, and this case is the measurement that chose between them:
@@ -856,7 +856,7 @@ describe('[#15356] can a record-before-update flow reach a multi:true batch payl
856856} ) ;
857857
858858/**
859- * [#15356 measured it, #14744 closed it] S5 again, on the REAL SQL backend —
859+ * [#15356 measured it, commit 4f85e4d11 closed it] S5 again, on the REAL SQL backend —
860860 * `@objectstack/driver-sql` over better-sqlite3 `:memory:`, built the canonical
861861 * way this package's `record-change-integration.test.ts` boots it.
862862 *
@@ -921,7 +921,7 @@ describe('[#15356/#14744] S5 on the real SQL driver — the mutation reaches no
921921 console . log ( '[#15356] SQL rows:' , JSON . stringify ( [ ...rows . values ( ) ] . map ( ( r ) => ( { title : r . title , tags : r . tags } ) ) ) ) ;
922922 const alpha = rows . get ( 'alpha' ) ?. tags ;
923923 const beta = rows . get ( 'beta' ) ?. tags ;
924- // Before #14744 both read `['seed','REACHED-alpha','REACHED-beta']` — one
924+ // Before commit 4f85e4d11 both read `['seed','REACHED-alpha','REACHED-beta']` — one
925925 // SET clause carrying both dispatches, including the value derived from the
926926 // other row's pre-image.
927927 expect ( alpha ) . toEqual ( [ 'seed' ] ) ;
0 commit comments