Skip to content

Commit bb9d39a

Browse files
committed
docs(trigger-record-change): re-anchor the dead tracker citations to the commits that decided them
29 comment and docblock lines in 5 files under packages/triggers/trigger-record-change/src cited tracker numbers that answer 404. Each now cites the commit that decided what the line describes and says so in its own words: - the flow-facing record decoupled from the batch payload -> 4f85e4d - the census of same-key / per-row-value beforeUpdate rewrites -> 03c1b0f - the post-hook half of the declared-field door -> b003cf2 - the shared expected read-refusal noise capture -> c28e4cf Comments only: every file keeps its line count and no code token moves. Test titles that carry a dead number are strings and are left. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
1 parent 91e8fa1 commit bb9d39a

5 files changed

Lines changed: 29 additions & 29 deletions

File tree

‎packages/triggers/trigger-record-change/src/before-update-flow-payload-reach.test.ts‎

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
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
@@ -19,14 +19,14 @@
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
@@ -44,7 +44,7 @@
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"
@@ -84,15 +84,15 @@
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']);

‎packages/triggers/trigger-record-change/src/decouple-flow-record.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
22

33
/**
4-
* [#14744] The flow-facing `record` / `previous` roots share no mutable object
4+
* [commit 4f85e4d11] The flow-facing `record` / `previous` roots share no mutable object
55
* with the engine's own state.
66
*
77
* `before-update-flow-payload-reach.test.ts` is the END-TO-END pin: it boots a

‎packages/triggers/trigger-record-change/src/decouple-flow-record.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
/**
44
* Decouple the flow-facing `record` / `previous` roots from the ENGINE-OWNED
5-
* objects they were overlaid from (#14744, measured by #15356).
5+
* objects they were overlaid from (commit 4f85e4d11, measured by #15356).
66
*
77
* ## The leak this closes
88
*

‎packages/triggers/trigger-record-change/src/record-change-integration.test.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { SqlDriver } from '@objectstack/driver-sql';
3030
import { AutomationServicePlugin, type AutomationEngine } from '@objectstack/service-automation';
3131
import type { IDataEngine, IObjectQLEngine } from '@objectstack/spec/contracts';
3232
import { RecordChangeTriggerPlugin } from './plugin.js';
33-
// [#11081] `@objectstack/runtime`'s shared expected-noise capture. This import
33+
// [commit c28e4cfae] `@objectstack/runtime`'s shared expected-noise capture. This import
3434
// escapes the package on PURPOSE, so it is DECLARED rather than left for CI to
3535
// discover: `CROSS_PACKAGE_TEST_INPUTS` in
3636
// `scripts/check-cross-package-test-inputs.mjs` names the one file, and
@@ -65,7 +65,7 @@ type TestObjectQLEngine = IObjectQLEngine & {
6565
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
6666

6767
/**
68-
* [#11081] The tables this file deliberately never provisions — and therefore
68+
* [commit c28e4cfae] The tables this file deliberately never provisions — and therefore
6969
* the ONLY read refusals whose log frames may be withheld here.
7070
*
7171
* Every `it` below boots a kernel with no datasource, attaches sqlite late, and
@@ -196,11 +196,11 @@ const authzResolverObjects = [
196196
},
197197
] as const;
198198

199-
/** [#11081] Shared by every kernel this file boots; asserted once in `afterAll`. */
199+
/** [commit c28e4cfae] Shared by every kernel this file boots; asserted once in `afterAll`. */
200200
const noise = captureExpectedReadRefusals([...EXPECTED_ABSENT_PROBE_TABLES]);
201201

202202
/**
203-
* [#11081] The PIN half. ⛔ Repairing a failure here means re-deriving the list
203+
* [commit c28e4cfae] The PIN half. ⛔ Repairing a failure here means re-deriving the list
204204
* above or finding out why a probe stopped firing — NEVER deleting the channel:
205205
* a runtime read that silently stopped happening is exactly the finding this
206206
* assertion exists to make loud.
@@ -243,7 +243,7 @@ afterEach(async () => {
243243
*/
244244
async function attachSqlite(objectql: any): Promise<any> {
245245
const driver = makeSqliteDriver();
246-
// [#11081] Before `connect()` — i.e. before the driver runs any statement, the
246+
// [commit c28e4cfae] Before `connect()` — i.e. before the driver runs any statement, the
247247
// discipline `captureExpectedReadRefusals` documents. `logger` is a protected
248248
// field with a `console` default, so the sink also RESTORES a loud channel:
249249
// an unexpected driver fault reaches the real console from here even though

‎packages/triggers/trigger-record-change/src/record-change-trigger.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export interface TriggerLogger {
121121
* `ctx.previous`, observed by every OTHER binding sharing the same
122122
* HookContext — pass a copy in. ⚠️ A SHALLOW copy is enough for THIS function
123123
* (it only ever assigns top-level keys), and it is NOT enough for the object
124-
* that reaches a flow: see {@link decoupleFromEngineState} and #14744.
124+
* that reaches a flow: see {@link decoupleFromEngineState} and commit 4f85e4d11.
125125
*
126126
* Exported (module-scope only — NOT re-exported from `index.ts`, so this
127127
* stays off the package's published API) so
@@ -337,8 +337,8 @@ export class RecordChangeTrigger implements FlowTrigger {
337337
* declared fields (see the `materializeDeclaredFields` call below).
338338
*
339339
* ⭐ Both roots it returns are a SNAPSHOT and are DECOUPLED from the
340-
* engine's own state (#14744): a flow can mutate them however it likes and
341-
* reach nothing outside its own run. Until #14744 that was true only of the
340+
* engine's own state (commit 4f85e4d11): a flow can mutate them however it likes and
341+
* reach nothing outside its own run. Until commit 4f85e4d11 that was true only of the
342342
* TOP LEVEL — every overlay here is a shallow spread, so each nested value
343343
* was still the engine's own object, and `inputData` is the batch payload
344344
* ADR-0058 Addendum II D3 shares across every row of a `multi: true` write.
@@ -450,7 +450,7 @@ export class RecordChangeTrigger implements FlowTrigger {
450450
const materializedPrevious =
451451
priorBase && fields ? materializeDeclaredFields({ ...priorBase }, fields) : previous;
452452

453-
// #14744 — DECOUPLE the flow-facing roots from the engine's own objects.
453+
// Commit 4f85e4d11 — DECOUPLE the flow-facing roots from the engine's own objects.
454454
// Every overlay above is a SHALLOW spread, so until this point each
455455
// nested value in `record` is still the engine's: `inputData` is
456456
// `ctx.input.data`, which ADR-0058 Addendum II D3 shares across every
@@ -493,7 +493,7 @@ export class RecordChangeTrigger implements FlowTrigger {
493493
...(session.organizationId ? { tenantId: session.organizationId } : {}),
494494
// Expose the record as params too, so flows with named `isInput`
495495
// variables matching record fields get them seeded. Deliberately the
496-
// SAME object as `record` (unchanged by #14744 — `params` was never a
496+
// SAME object as `record` (unchanged by commit 4f85e4d11 — `params` was never a
497497
// second snapshot, and making it one here would be an observable
498498
// change on top of the aliasing fix).
499499
params: isolatedRecord,

0 commit comments

Comments
 (0)