Skip to content

Commit 46891dc

Browse files
author
DavidQ
committed
Make the promotion-gate authoritative/passive handoff boundary explicit across the exact gate/state/snapshot files.
1 parent 3d5651f commit 46891dc

9 files changed

Lines changed: 108 additions & 10 deletions

docs/dev/CODEX_COMMANDS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ MODEL: GPT-5.4-codex
22
REASONING: high
33

44
COMMAND:
5-
Execute exactly docs/pr/BUILD_PR_PROMOTION_GATE_55_CONTRACT_AND_PUBLIC_READERS.md.
5+
Execute exactly docs/pr/BUILD_PR_PROMOTION_GATE_56_AUTHORITATIVE_PASSIVE_HANDOFF_BOUNDARY.md.
66
Modify only the exact target files listed in the PR doc.
77
Do not expand scope.
88
Package the delta zip to:
9-
<project folder>/tmp/BUILD_PR_PROMOTION_GATE_55_CONTRACT_AND_PUBLIC_READERS_delta.zip
9+
<project folder>/tmp/BUILD_PR_PROMOTION_GATE_56_AUTHORITATIVE_PASSIVE_HANDOFF_BOUNDARY_delta.zip

docs/dev/COMMIT_COMMENT.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Establish an explicit shared public-reader contract for the promotion gate and reduce local gate-specific state read assumptions.
1+
Make the promotion-gate authoritative/passive handoff boundary explicit across the exact gate/state/snapshot files.

docs/dev/NEXT_COMMAND.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
After commit, inspect the promotion-gate delta and build the next exact slice only if authoritative/passive handoff files are explicit.
1+
After commit, inspect the delta and build the next exact promotion-gate slice only if observability/abort files are explicit.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
- narrow promotion-gate BUILD focused on contract and public-reader boundary only
2-
- exact target files limited to createPromotionGate + shared state reader + promotion snapshot
1+
- narrow promotion-gate BUILD focused on authoritative/passive handoff boundary only
2+
- exact target files limited to promotion gate + world game state system + promotion snapshot
33
- explicitly excludes replay/timeline/sample/game/debug widening

docs/dev/reports/file_tree.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
docs/pr/BUILD_PR_PROMOTION_GATE_55_CONTRACT_AND_PUBLIC_READERS.md
1+
docs/pr/BUILD_PR_PROMOTION_GATE_56_AUTHORITATIVE_PASSIVE_HANDOFF_BOUNDARY.md
22
docs/dev/codex_commands.md
33
docs/dev/commit_comment.txt
44
docs/dev/next_command.txt
55
docs/dev/reports/file_tree.txt
66
docs/dev/reports/change_summary.txt
77
docs/dev/reports/validation_checklist.txt
88
src/advanced/promotion/createPromotionGate.js
9-
src/shared/state/getState.js
9+
src/advanced/state/createWorldGameStateSystem.js
1010
src/shared/state/createPromotionStateSnapshot.js
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# BUILD PR — Promotion Gate Authoritative/Passive Handoff Boundary
2+
3+
## Purpose
4+
Make the authoritative/passive handoff boundary explicit for the promotion-gate lane without widening into replay, timeline, sample, or repo-structure work.
5+
6+
## Exact Target Files
7+
- `src/advanced/promotion/createPromotionGate.js`
8+
- `src/advanced/state/createWorldGameStateSystem.js`
9+
- `src/shared/state/createPromotionStateSnapshot.js`
10+
11+
## Why These Files
12+
This PR stays inside the existing promotion-gate and authoritative-state lane:
13+
- `createPromotionGate.js` is the promotion-gate owner
14+
- `createWorldGameStateSystem.js` is the authoritative world-state system entry
15+
- `createPromotionStateSnapshot.js` is the shared promotion snapshot contract already used in the lane
16+
17+
This PR must not widen beyond these exact files.
18+
19+
## Required Code Changes
20+
1. In `src/advanced/promotion/createPromotionGate.js`
21+
- make the handoff boundary between passive and authoritative modes explicit
22+
- ensure the gate names or uses a single handoff decision path
23+
- preserve current runtime semantics except where needed to remove ambiguous handoff behavior
24+
25+
2. In `src/advanced/state/createWorldGameStateSystem.js`
26+
- align the world game state system with the explicit handoff path
27+
- make the minimum authoritative/passive transition boundary readable from the gate-facing integration
28+
- do not broaden this into general state cleanup
29+
30+
3. In `src/shared/state/createPromotionStateSnapshot.js`
31+
- update the shared promotion snapshot shape only if required to support the explicit handoff boundary
32+
- keep this limited to handoff compatibility, not general snapshot redesign
33+
34+
## Hard Constraints
35+
- exact files only
36+
- do not modify replay files
37+
- do not modify timeline files
38+
- do not modify selectors beyond what the exact files already require
39+
- do not modify sample files
40+
- do not modify game files
41+
- do not widen into debug UI work
42+
- do not perform repo-wide state cleanup
43+
- do not change unrelated state semantics
44+
45+
## Validation Steps
46+
- confirm only the exact target files changed
47+
- confirm the promotion gate has a single explicit handoff path
48+
- confirm authoritative vs passive behavior boundary is explicit in the touched code
49+
- confirm imports/exports resolve
50+
- confirm no unrelated refactor or formatting-only churn was introduced
51+
52+
## Acceptance Criteria
53+
- authoritative/passive handoff boundary is explicit
54+
- gate-to-state-system handoff path is singular and readable
55+
- promotion snapshot contract remains coherent with the handoff boundary
56+
- no replay/timeline/sample/game/debug scope expansion occurred

src/advanced/promotion/createPromotionGate.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ function sanitizeRequiredCriteria(requiredCriteria) {
3939
return out;
4040
}
4141

42+
function createHandoffDecision({ promoted, promotedNow }) {
43+
return {
44+
decisionPath: 'PROMOTION_GATE_HANDOFF',
45+
fromMode: promotedNow ? 'passive' : (promoted ? 'authoritative' : 'passive'),
46+
toMode: promoted ? 'authoritative' : 'passive',
47+
shouldHandoff: Boolean(promotedNow)
48+
};
49+
}
50+
4251
function createPromotionGate(options = {}) {
4352
const now = typeof options.now === 'function' ? options.now : () => Date.now();
4453
const requiredCriteria = sanitizeRequiredCriteria(options.requiredCriteria);
@@ -127,6 +136,7 @@ function createPromotionGate(options = {}) {
127136
const readiness = promoted
128137
? 'authoritative'
129138
: (allCriteriaMet ? 'stabilizing' : 'passive');
139+
const handoff = createHandoffDecision({ promoted, promotedNow });
130140
const evaluation = {
131141
transitionName: String(transitionName || ''),
132142
frame: frame !== undefined && frame !== null ? Number(frame) : null,
@@ -144,6 +154,7 @@ function createPromotionGate(options = {}) {
144154
unmet: unmetCriteria,
145155
allMet: allCriteriaMet
146156
},
157+
handoff,
147158
reason: lastReason,
148159
metrics: getMetrics()
149160
};

src/advanced/state/createWorldGameStateSystem.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@ function normalizeCriteriaMap(criteria, requiredCriteria) {
106106
return normalized;
107107
}
108108

109+
function createPromotionHandoffDecision({ promoted, promotedNow }) {
110+
return {
111+
decisionPath: 'PROMOTION_GATE_HANDOFF',
112+
fromMode: promotedNow ? 'passive' : (promoted ? 'authoritative' : 'passive'),
113+
toMode: promoted ? 'authoritative' : 'passive',
114+
shouldHandoff: Boolean(promotedNow)
115+
};
116+
}
117+
109118
function createInlinePromotionGate({
110119
now,
111120
requiredCriteria,
@@ -185,6 +194,7 @@ function createInlinePromotionGate({
185194
}
186195

187196
const readiness = promoted ? 'authoritative' : (allCriteriaMet ? 'stabilizing' : 'passive');
197+
const handoff = createPromotionHandoffDecision({ promoted, promotedNow });
188198
const evaluation = {
189199
transitionName: String(transitionName || ''),
190200
frame: frame !== undefined && frame !== null ? Number(frame) : null,
@@ -202,6 +212,7 @@ function createInlinePromotionGate({
202212
unmet,
203213
allMet: allCriteriaMet
204214
},
215+
handoff,
205216
reason: lastReason,
206217
metrics: getMetrics()
207218
};
@@ -227,6 +238,10 @@ function createInlinePromotionGate({
227238
stabilityWindowFrames: windowFrames,
228239
lastReason,
229240
lastEvaluation,
241+
handoff: createPromotionHandoffDecision({
242+
promoted,
243+
promotedNow: false
244+
}),
230245
cloneLastEvaluation: (value) => (value ? cloneDeep(value) : null)
231246
});
232247
}
@@ -323,7 +338,13 @@ function createWorldGameStateSystem(options = {}) {
323338
frame: resolveFrameFromMeta(meta, payload)
324339
});
325340

326-
if (evaluation.promotedNow) {
341+
const handoff = isPlainObject(evaluation?.handoff)
342+
? evaluation.handoff
343+
: createPromotionHandoffDecision({
344+
promoted: Boolean(evaluation?.promoted),
345+
promotedNow: Boolean(evaluation?.promotedNow)
346+
});
347+
if (handoff.shouldHandoff) {
327348
activeMode = 'authoritative';
328349
}
329350

src/shared/state/createPromotionStateSnapshot.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@ function defaultCloneLastEvaluation(value) {
22
return value ? { ...value } : null;
33
}
44

5+
function cloneHandoff(value) {
6+
if (!value || typeof value !== 'object') return null;
7+
return { ...value };
8+
}
9+
510
export function createPromotionStateSnapshot({
611
promoted,
712
stableFrames,
813
stabilityWindowFrames,
914
lastReason,
1015
lastEvaluation,
16+
handoff,
1117
cloneLastEvaluation
1218
}) {
13-
return {
19+
const snapshot = {
1420
promoted,
1521
stableFrames,
1622
stabilityWindowFrames,
@@ -19,4 +25,8 @@ export function createPromotionStateSnapshot({
1925
? cloneLastEvaluation
2026
: defaultCloneLastEvaluation)(lastEvaluation)
2127
};
28+
if (handoff !== undefined) {
29+
snapshot.handoff = cloneHandoff(handoff);
30+
}
31+
return snapshot;
2232
}

0 commit comments

Comments
 (0)