Skip to content

Commit f980082

Browse files
committed
chore(gates): collapse the four ADR 0014 R7 rows into the owned refFrame value
R7's owner table listed `refFrameState`, `refFrameScope`, `refFrameTree` and `refFrameGeneration` as four fields that had to be written together by one module; the code now carries them as one nominal value, so the table carries one row. R10 follows: 19 writer-owned fields to 16, 22 owner claims to 19. The row itself stays. The type stops construction, editing and spread-derivation of a frame outside ref-frame.ts, but it cannot judge a whole frame moved unchanged — clearing the field, or assigning another session's frame — and the table can. The comments say that rather than claiming full enforcement. Seen red: a planted `session.refFrame = undefined` in snapshot-session.ts fails R7 with "owned by src/daemon/ref-frame.ts"; green once reverted.
1 parent 2f7d6db commit f980082

3 files changed

Lines changed: 41 additions & 37 deletions

File tree

scripts/layering/daemon-modularity.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ export const DAEMON_MODULARITY_BASELINE = {
2121
sessionState: {
2222
// R60 moved `audioProbe` to store-owned. R64 does the same for the neutral `perfCapture`
2323
// and `lastPerfProfile` records after retiring the two platform-specific perf fields.
24-
writerOwnedFields: 19,
25-
ownerFileClaims: 22,
24+
// The ADR 0014 ref frame then collapsed four rows into one owned value (-3 fields, -3 claims).
25+
writerOwnedFields: 16,
26+
ownerFileClaims: 19,
2627
},
2728
largestTypeCycle: {
2829
zoneMembers: LARGEST_TYPE_CYCLE_ZONE_CEILINGS,

scripts/layering/model.test.ts

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -293,79 +293,79 @@ test('SessionState field names come from the declaration, not a hand-kept list',
293293
" kind: 'cwd';",
294294
' id: string;',
295295
' };',
296-
' refFrameState?: RefFrameState;',
296+
' refFrame?: RefFrame;',
297297
'};',
298298
'',
299299
'export type Other = { notAField: string };',
300300
].join('\n'),
301301
);
302302
// Nested object members are not session fields, and neighbouring types are not scanned.
303-
assert.deepEqual(fields, ['name', 'sessionScope', 'refFrameState']);
303+
assert.deepEqual(fields, ['name', 'sessionScope', 'refFrame']);
304304
});
305305

306306
test('session-state writes are found by field, and non-daemon or undeclared names are not', () => {
307307
const writes = findSessionStateWrites(
308308
new Map([
309-
['src/daemon/ref-frame.ts', "session.refFrameState = 'active';"],
309+
['src/daemon/ref-frame.ts', "session.refFrame = 'active';"],
310310
['src/daemon/session-snapshot.ts', 'session.snapshotGeneration += 1;'],
311311
// the store owns the record and may write anything on it
312-
['src/daemon/session-store.ts', "session.refFrameState = 'expired';"],
312+
['src/daemon/session-store.ts', "session.refFrame = 'expired';"],
313313
// a runner session outside the daemon is a different type that happens to share a name
314-
['src/platforms/apple/runner-session.ts', 'session.refFrameState = 1;'],
314+
['src/platforms/apple/runner-session.ts', 'session.refFrame = 1;'],
315315
// a local that is not a declared SessionState field
316316
['src/daemon/session-observability/internal/session-audio.ts', 'session.somethingElse = 1;'],
317317
// reads and comparisons are not writes
318318
[
319319
'src/daemon/interaction/internal/find.ts',
320-
"if (session.refFrameState === 'active') return;",
320+
"if (session.refFrame === 'active') return;",
321321
],
322322
// a write into a sub-object is not a write to the field itself
323-
['src/daemon/handlers/session-probe.ts', 'session.refFrameState.inner = 1;'],
323+
['src/daemon/handlers/session-probe.ts', 'session.refFrame.inner = 1;'],
324324
// a different binding that happens to have a matching property
325325
[
326326
'src/daemon/session-lifecycle/internal/session-close.ts',
327-
"other.refFrameState = 'expired';",
327+
"other.refFrame = 'expired';",
328328
],
329329
]),
330-
['refFrameState', 'snapshotGeneration'],
330+
['refFrame', 'snapshotGeneration'],
331331
);
332332

333333
assert.deepEqual(
334334
writes.map(({ file, field }) => `${file}:${field}`),
335-
['src/daemon/ref-frame.ts:refFrameState', 'src/daemon/session-snapshot.ts:snapshotGeneration'],
335+
['src/daemon/ref-frame.ts:refFrame', 'src/daemon/session-snapshot.ts:snapshotGeneration'],
336336
);
337337
});
338338

339339
test('every assignment form is a write, including the ones a regex forgets', () => {
340340
// A line-based matcher has to enumerate operators, and the ones it misses are the natural
341341
// ways to write these: `??=` for a default on an optional field, `||=`/`&&=` for a flag.
342342
const forms = [
343-
'session.refFrameState = 1;',
344-
'session.refFrameState ??= 1;',
345-
'session.refFrameState ||= 1;',
346-
'session.refFrameState &&= 1;',
347-
'session.refFrameState += 1;',
348-
'session.refFrameState -= 1;',
349-
'session.refFrameState++;',
350-
'--session.refFrameState;',
351-
'session\n .refFrameState = 1;',
343+
'session.refFrame = 1;',
344+
'session.refFrame ??= 1;',
345+
'session.refFrame ||= 1;',
346+
'session.refFrame &&= 1;',
347+
'session.refFrame += 1;',
348+
'session.refFrame -= 1;',
349+
'session.refFrame++;',
350+
'--session.refFrame;',
351+
'session\n .refFrame = 1;',
352352
];
353353
for (const form of forms) {
354354
const writes = findSessionStateWrites(new Map([['src/daemon/probe.ts', form]]), [
355-
'refFrameState',
355+
'refFrame',
356356
]);
357357
assert.deepEqual(
358358
writes.map(({ field }) => field),
359-
['refFrameState'],
359+
['refFrame'],
360360
`expected ${JSON.stringify(form)} to count as a write`,
361361
);
362362
}
363363
});
364364

365365
test('a computed session write is reported rather than silently unattributed', () => {
366366
const writes = findSessionStateWrites(
367-
new Map([['src/daemon/probe.ts', 'session[key] = 1;\nsession[`refFrameState`] = 2;']]),
368-
['refFrameState'],
367+
new Map([['src/daemon/probe.ts', 'session[key] = 1;\nsession[`refFrame`] = 2;']]),
368+
['refFrame'],
369369
);
370370
// `[computed]` has no entry in SESSION_STATE_FIELD_OWNERS, so R7 fails on it by
371371
// construction — a computed write can never pass as an owned one.
@@ -401,19 +401,19 @@ test('a session write counts through an aliased binding, not only one named `ses
401401
'src/daemon/probe.ts',
402402
[
403403
'nextSession.snapshotGeneration = 3;',
404-
'preEntrySession.refFrameState = "active";',
404+
'preEntrySession.refFrame = "active";',
405405
'completedSession.saveScriptComplete = true;',
406406
// Not a session binding, and not a session write.
407407
'result.snapshotGeneration = 9;',
408-
'flags.refFrameState = "x";',
408+
'flags.refFrame = "x";',
409409
].join('\n'),
410410
],
411411
]),
412-
['snapshotGeneration', 'refFrameState', 'saveScriptComplete'],
412+
['snapshotGeneration', 'refFrame', 'saveScriptComplete'],
413413
);
414414
assert.deepEqual(
415415
writes.map(({ field, line }) => `${line}:${field}`),
416-
['1:snapshotGeneration', '2:refFrameState', '3:saveScriptComplete'],
416+
['1:snapshotGeneration', '2:refFrame', '3:saveScriptComplete'],
417417
);
418418
});
419419

scripts/layering/session-state.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
// the set of writers that exist, so the gate's job is to stop the set from growing quietly.
2626
// Adding a field to `SessionState` forces a deliberate owner; writing an existing field from
2727
// a new module fails until that module is either declared an owner or, better, calls the
28-
// owner instead. ADR 0014's ref frame is the worked example — its four fields moved together
29-
// across two modules until `activateRefFrame` took the transition.
28+
// owner instead. ADR 0014's ref frame is the worked example, and the one that has since been
29+
// taken further than this table can go: its four fields moved together across two modules
30+
// until `activateRefFrame` took the transition, and they are now a single value whose nominal
31+
// type no other module can construct, edit, or derive from an existing frame.
3032
//
3133
// Detection is AST-based (`oxc-parser`, already a devDependency) rather than a line regex. A
3234
// regex has to enumerate assignment operators, and the ones it forgets are exactly the ones
@@ -49,12 +51,13 @@ export type SessionStateWrite = {
4951
* owner list has one entry is a field only that module can get wrong.
5052
*/
5153
export const SESSION_STATE_FIELD_OWNERS: Readonly<Record<string, readonly string[]>> = {
52-
// ADR 0014 ref frame: the four frame fields move together or the frame is incoherent, so
53-
// both issuance forms go through ref-frame.ts.
54-
refFrameState: ['src/daemon/ref-frame.ts'],
55-
refFrameScope: ['src/daemon/ref-frame.ts'],
56-
refFrameTree: ['src/daemon/ref-frame.ts'],
57-
refFrameGeneration: ['src/daemon/ref-frame.ts'],
54+
// ADR 0014 ref frame. The four frame fields this row replaced moved together or the frame was
55+
// incoherent, and only this table said so; `RefFrame` is now a nominal type (`#`-private
56+
// fields) that no other module can construct, edit, or spread into a new frame, and the
57+
// transitions replace it whole. The row stays because the type cannot judge a whole frame
58+
// moved unchanged: assigning `undefined` (a reset to the pristine frame) and assigning a
59+
// frame read off another session.
60+
refFrame: ['src/daemon/ref-frame.ts'],
5861
// Scoped-snapshot lineage is cleared at two distinct events: crossing a device side-effect
5962
// seam (ref-frame.ts) and replacing the stored observation (session-snapshot.ts).
6063
snapshotScopeSource: ['src/daemon/ref-frame.ts', 'src/daemon/session-snapshot.ts'],

0 commit comments

Comments
 (0)