Skip to content

Commit 32ce0e7

Browse files
author
DavidQ
committed
Add promotion gate observability and abort visibility
1 parent 46891dc commit 32ce0e7

11 files changed

Lines changed: 146 additions & 29 deletions

docs/dev/CODEX_COMMANDS.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
MODEL: GPT-5.4-codex
22
REASONING: high
3-
43
COMMAND:
5-
Execute exactly docs/pr/BUILD_PR_PROMOTION_GATE_56_AUTHORITATIVE_PASSIVE_HANDOFF_BOUNDARY.md.
6-
Modify only the exact target files listed in the PR doc.
7-
Do not expand scope.
8-
Package the delta zip to:
9-
<project folder>/tmp/BUILD_PR_PROMOTION_GATE_56_AUTHORITATIVE_PASSIVE_HANDOFF_BOUNDARY_delta.zip
4+
Execute docs/pr/BUILD_PR_PROMOTION_GATE_57_OBSERVABILITY_ABORT.md exactly.
5+
Package to <project folder>/tmp/BUILD_PR_PROMOTION_GATE_57_OBSERVABILITY_ABORT_delta.zip

docs/dev/COMMIT_COMMENT.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Make the promotion-gate authoritative/passive handoff boundary explicit across the exact gate/state/snapshot files.
1+
Add promotion gate observability and abort visibility

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 delta and build the next exact promotion-gate slice only if observability/abort files are explicit.
1+
Next: validation closeout
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
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
3-
- explicitly excludes replay/timeline/sample/game/debug widening
1+
narrow slice

docs/dev/reports/file_tree.txt

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
docs/pr/BUILD_PR_PROMOTION_GATE_56_AUTHORITATIVE_PASSIVE_HANDOFF_BOUNDARY.md
2-
docs/dev/codex_commands.md
3-
docs/dev/commit_comment.txt
4-
docs/dev/next_command.txt
5-
docs/dev/reports/file_tree.txt
6-
docs/dev/reports/change_summary.txt
7-
docs/dev/reports/validation_checklist.txt
81
src/advanced/promotion/createPromotionGate.js
9-
src/advanced/state/createWorldGameStateSystem.js
10-
src/shared/state/createPromotionStateSnapshot.js
2+
src/engine/debug/panels/PromotionGatePanel.js
3+
src/shared/state/createPromotionStateSnapshot.js
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
1-
- single PR purpose only
2-
- exact target files only
3-
- executable BUILD with Codex command included
4-
- no roadmap edits
5-
- no docs-only commit intent
6-
- no replay/timeline/sample/game/debug scope expansion
1+
exact files only
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# BUILD PR — Promotion Gate Observability + Abort Visibility
2+
3+
## Files
4+
- src/advanced/promotion/createPromotionGate.js
5+
- src/engine/debug/panels/PromotionGatePanel.js
6+
- src/shared/state/createPromotionStateSnapshot.js
7+
8+
## Actions
9+
- expose gate status (mode, handoff, abort flag)
10+
- surface minimal data to debug panel (no new subsystem)
11+
- add explicit abort/rollback visibility path
12+
13+
## Constraints
14+
- exact files only
15+
- no replay/timeline/sample changes
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# BUILD PR — Promotion Gate Validation + Closeout
2+
3+
## Files
4+
- src/advanced/promotion/createPromotionGate.js
5+
- src/shared/state/createPromotionStateSnapshot.js
6+
7+
## Actions
8+
- add validation hooks/checklist alignment
9+
- ensure gate can run full cycle (passive→authoritative→abort)
10+
- no feature expansion
11+
12+
## Constraints
13+
- exact files only

src/advanced/promotion/createPromotionGate.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,20 @@ function createHandoffDecision({ promoted, promotedNow }) {
4848
};
4949
}
5050

51+
function resolveGateMode({ promoted }) {
52+
return promoted ? 'authoritative' : 'passive';
53+
}
54+
55+
function createAbortVisibility({ rollbackTriggered, promoted, reason }) {
56+
const aborted = Boolean(rollbackTriggered) && !promoted;
57+
return {
58+
decisionPath: 'PROMOTION_GATE_ABORT_VISIBILITY',
59+
rollbackTriggered: Boolean(rollbackTriggered),
60+
aborted,
61+
reason: aborted ? 'ROLLBACK_ABORTED_PROMOTION' : String(reason || '')
62+
};
63+
}
64+
5165
function createPromotionGate(options = {}) {
5266
const now = typeof options.now === 'function' ? options.now : () => Date.now();
5367
const requiredCriteria = sanitizeRequiredCriteria(options.requiredCriteria);
@@ -136,12 +150,19 @@ function createPromotionGate(options = {}) {
136150
const readiness = promoted
137151
? 'authoritative'
138152
: (allCriteriaMet ? 'stabilizing' : 'passive');
153+
const mode = resolveGateMode({ promoted });
139154
const handoff = createHandoffDecision({ promoted, promotedNow });
155+
const abort = createAbortVisibility({
156+
rollbackTriggered,
157+
promoted,
158+
reason: lastReason
159+
});
140160
const evaluation = {
141161
transitionName: String(transitionName || ''),
142162
frame: frame !== undefined && frame !== null ? Number(frame) : null,
143163
timestampMs,
144164
readiness,
165+
mode,
145166
promoted,
146167
promotedNow,
147168
rollbackTriggered: Boolean(rollbackTriggered),
@@ -155,6 +176,7 @@ function createPromotionGate(options = {}) {
155176
allMet: allCriteriaMet
156177
},
157178
handoff,
179+
abort,
158180
reason: lastReason,
159181
metrics: getMetrics()
160182
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
Toolbox Aid
3+
David Quesenberry
4+
04/09/2026
5+
PromotionGatePanel.js
6+
*/
7+
8+
import { drawPanel } from '../DebugPanel.js';
9+
10+
function asObject(value) {
11+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
12+
}
13+
14+
function resolvePromotionStatus(source) {
15+
const snapshot = asObject(source);
16+
const evaluation = asObject(snapshot.lastEvaluation);
17+
const status = asObject(snapshot.status);
18+
const handoff = asObject(status.handoff).toMode ? status.handoff : asObject(evaluation.handoff);
19+
const abort = asObject(status.abort).decisionPath ? status.abort : asObject(evaluation.abort);
20+
const mode = typeof status.mode === 'string' && status.mode
21+
? status.mode
22+
: (typeof evaluation.mode === 'string' && evaluation.mode ? evaluation.mode : 'passive');
23+
return {
24+
mode,
25+
handoff,
26+
abort,
27+
reason: String(snapshot.lastReason || evaluation.reason || ''),
28+
stability: asObject(evaluation.stability)
29+
};
30+
}
31+
32+
export function getPromotionGatePanelLines(promotionState) {
33+
const status = resolvePromotionStatus(promotionState);
34+
const handoffLabel = status.handoff.toMode
35+
? `${String(status.handoff.fromMode || 'passive')} -> ${String(status.handoff.toMode)}`
36+
: 'n/a';
37+
const abortVisible = status.abort.rollbackTriggered === true || status.abort.aborted === true;
38+
const abortLabel = abortVisible
39+
? `${status.abort.aborted === true ? 'ABORTED' : 'VISIBLE'} (${String(status.abort.reason || 'rollback')})`
40+
: 'no';
41+
42+
return [
43+
`Mode: ${status.mode}`,
44+
`Handoff: ${handoffLabel}`,
45+
`Abort: ${abortLabel}`,
46+
`Stable: ${Number(status.stability.currentFrames || 0)}/${Number(status.stability.requiredFrames || 0)}`,
47+
`Reason: ${status.reason || 'n/a'}`
48+
];
49+
}
50+
51+
export function drawPromotionGatePanel(renderer, promotionState, {
52+
x = 620,
53+
y = 184,
54+
width = 300,
55+
height = 146,
56+
title = 'Promotion Gate'
57+
} = {}) {
58+
drawPanel(renderer, x, y, width, height, title, getPromotionGatePanelLines(promotionState));
59+
}

0 commit comments

Comments
 (0)