Skip to content

Commit 497bcc1

Browse files
author
DavidQ
committed
BUILD: Level 11.7 Final Promotion Gate
- Implements promotion gating logic - Adds stability + rollback checks
1 parent 1ab1c5a commit 497bcc1

File tree

10 files changed

+475
-25
lines changed

10 files changed

+475
-25
lines changed

docs/dev/CODEX_COMMANDS.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
1-
# Next Step (not executed here)
2-
# BUILD_PR_LEVEL_11_7_FINAL_PROMOTION_GATE
1+
MODEL: GPT-5.3-codex
2+
REASONING: high
3+
4+
COMMAND:
5+
Implement BUILD_PR_LEVEL_11_7_FINAL_PROMOTION_GATE as specified.
6+
Do not modify engine core APIs.
7+
Limit changes to listed target files.

docs/dev/COMMIT_COMMENT.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
PLAN: Level 11.7 Final Promotion Gate
1+
BUILD: Level 11.7 Final Promotion Gate
22

3-
- Defines promotion criteria
4-
- Prepares BUILD phase
3+
- Implements promotion gating logic
4+
- Adds stability + rollback checks
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
CHANGE SUMMARY
22

3-
- Introduced final promotion gate definition
4-
- Defined criteria and constraints for authoritative switch
3+
- Added promotion gate runtime logic
4+
- Enforced stability checks before promotion
5+
- Added rollback safety conditions
6+
- Added observability logging
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
VALIDATION CHECKLIST
22

3-
[ ] Promotion criteria defined
4-
[ ] Stability thresholds defined
5-
[ ] Rollback conditions defined
6-
[ ] Observability requirements defined
7-
[ ] No runtime scope introduced
3+
[ ] Promotion triggers only under valid conditions
4+
[ ] Stability window enforced
5+
[ ] Rollback conditions abort correctly
6+
[ ] Logging provides visibility
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# BUILD TEMPLATE (Protected)
2+
3+
## Constraints (Mandatory)
4+
- Do not scan the repo
5+
- Only modify explicitly listed files
6+
- Do not change engine core APIs
7+
8+
## Requirement
9+
This section must be included in every BUILD PR.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# BUILD PR — Level 11.7 Final Promotion Gate
2+
3+
## Purpose
4+
Implement the final promotion gate logic for transitioning from passive to authoritative mode.
5+
6+
## Target Files
7+
- src/advanced/state/*
8+
- src/advanced/promotion/*
9+
- samples/network_sample_c/*
10+
11+
## Scope
12+
- Implement promotion criteria evaluation
13+
- Implement stability window checks
14+
- Implement rollback triggers
15+
- Implement observability hooks (logging + metrics)
16+
17+
## Non-Goals
18+
- No refactor outside promotion gate
19+
- No changes to engine core APIs
20+
- No new systems beyond gating logic
21+
22+
## Acceptance Criteria
23+
- Promotion only occurs when all criteria met
24+
- Stability window enforced (N frames consistent)
25+
- Rollback triggers correctly abort promotion
26+
- Logs expose promotion readiness state
27+
28+
## Validation Steps
29+
1. Run network_sample_c
30+
2. Simulate stable state → confirm promotion triggers
31+
3. Introduce divergence → confirm promotion blocked
32+
4. Force rollback condition → confirm abort
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# PLAN PR — Protected BUILD Template
2+
3+
## Purpose
4+
Introduce a protected BUILD template to standardize constraints across all BUILD PRs.
5+
6+
## Scope
7+
- Add BUILD template under docs/dev/templates/
8+
- Define mandatory constraints section
9+
- Ensure reuse across all future BUILD bundles
10+
11+
## Non-Goals
12+
- No runtime changes
13+
- No modification to existing BUILD PRs
14+
- No Codex execution
15+
16+
## Acceptance Criteria
17+
- Template exists in protected location
18+
- Includes mandatory constraints section
19+
- Future BUILD PRs can reference template
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
Toolbox Aid
3+
David Quesenberry
4+
04/07/2026
5+
createPromotionGate.js
6+
*/
7+
8+
function asFiniteNumber(value, fallback = 0) {
9+
const numeric = Number(value);
10+
return Number.isFinite(numeric) ? numeric : fallback;
11+
}
12+
13+
function asPositiveInteger(value, fallback = 1) {
14+
const numeric = Math.floor(asFiniteNumber(value, fallback));
15+
return numeric >= 1 ? numeric : fallback;
16+
}
17+
18+
function isPlainObject(value) {
19+
return !!value && typeof value === 'object' && !Array.isArray(value);
20+
}
21+
22+
function normalizeCriteriaMap(input, requiredCriteria = []) {
23+
const normalized = {};
24+
if (isPlainObject(input)) {
25+
const keys = Object.keys(input);
26+
for (let i = 0; i < keys.length; i += 1) {
27+
normalized[String(keys[i])] = Boolean(input[keys[i]]);
28+
}
29+
}
30+
31+
for (let i = 0; i < requiredCriteria.length; i += 1) {
32+
const key = requiredCriteria[i];
33+
if (!(key in normalized)) normalized[key] = false;
34+
}
35+
36+
return normalized;
37+
}
38+
39+
function sanitizeRequiredCriteria(requiredCriteria) {
40+
if (!Array.isArray(requiredCriteria)) return [];
41+
const seen = new Set();
42+
const out = [];
43+
for (let i = 0; i < requiredCriteria.length; i += 1) {
44+
const key = String(requiredCriteria[i] || '').trim();
45+
if (!key || seen.has(key)) continue;
46+
seen.add(key);
47+
out.push(key);
48+
}
49+
return out;
50+
}
51+
52+
function createPromotionGate(options = {}) {
53+
const now = typeof options.now === 'function' ? options.now : () => Date.now();
54+
const requiredCriteria = sanitizeRequiredCriteria(options.requiredCriteria);
55+
const stabilityWindowFrames = asPositiveInteger(options.stabilityWindowFrames, 3);
56+
const logger = typeof options.logger === 'function' ? options.logger : null;
57+
const onEvaluation = typeof options.onEvaluation === 'function' ? options.onEvaluation : null;
58+
59+
let promoted = Boolean(options.initiallyPromoted);
60+
let stableFrames = promoted ? stabilityWindowFrames : 0;
61+
let lastReason = promoted ? 'ALREADY_PROMOTED' : 'AWAITING_CRITERIA';
62+
let lastEvaluation = null;
63+
64+
const metrics = {
65+
evaluations: 0,
66+
stableEvaluations: 0,
67+
blockedEvaluations: 0,
68+
rollbackAborts: 0,
69+
promotions: promoted ? 1 : 0,
70+
lastEvaluationAtMs: null,
71+
lastPromotionAtMs: promoted ? Number(now()) : null
72+
};
73+
74+
function getMetrics() {
75+
return {
76+
...metrics,
77+
promoted,
78+
stableFrames,
79+
stabilityWindowFrames
80+
};
81+
}
82+
83+
function getState() {
84+
return {
85+
promoted,
86+
stableFrames,
87+
stabilityWindowFrames,
88+
lastReason,
89+
lastEvaluation: lastEvaluation ? { ...lastEvaluation } : null
90+
};
91+
}
92+
93+
function evaluate({
94+
criteria = {},
95+
rollbackTriggered = false,
96+
transitionName = '',
97+
frame = null
98+
} = {}) {
99+
metrics.evaluations += 1;
100+
const timestampMs = Number(now());
101+
metrics.lastEvaluationAtMs = timestampMs;
102+
103+
const normalizedCriteria = normalizeCriteriaMap(criteria, requiredCriteria);
104+
const criteriaKeys = Object.keys(normalizedCriteria);
105+
const unmetCriteria = [];
106+
for (let i = 0; i < criteriaKeys.length; i += 1) {
107+
const key = criteriaKeys[i];
108+
if (!normalizedCriteria[key]) unmetCriteria.push(key);
109+
}
110+
111+
const hasCriteria = criteriaKeys.length > 0;
112+
const allCriteriaMet = hasCriteria && unmetCriteria.length === 0;
113+
114+
let promotedNow = false;
115+
if (rollbackTriggered && !promoted) {
116+
stableFrames = 0;
117+
lastReason = 'ROLLBACK_ABORTED_PROMOTION';
118+
metrics.rollbackAborts += 1;
119+
metrics.blockedEvaluations += 1;
120+
} else if (promoted) {
121+
lastReason = 'ALREADY_PROMOTED';
122+
stableFrames = stabilityWindowFrames;
123+
metrics.stableEvaluations += 1;
124+
} else if (!hasCriteria) {
125+
stableFrames = 0;
126+
lastReason = 'PROMOTION_CRITERIA_MISSING';
127+
metrics.blockedEvaluations += 1;
128+
} else if (!allCriteriaMet) {
129+
stableFrames = 0;
130+
lastReason = 'PROMOTION_CRITERIA_UNMET';
131+
metrics.blockedEvaluations += 1;
132+
} else {
133+
stableFrames += 1;
134+
metrics.stableEvaluations += 1;
135+
lastReason = stableFrames >= stabilityWindowFrames
136+
? 'PROMOTION_READY'
137+
: 'PROMOTION_STABILIZING';
138+
if (stableFrames >= stabilityWindowFrames) {
139+
promoted = true;
140+
promotedNow = true;
141+
metrics.promotions += 1;
142+
metrics.lastPromotionAtMs = timestampMs;
143+
lastReason = 'PROMOTED';
144+
}
145+
}
146+
147+
const readiness = promoted
148+
? 'authoritative'
149+
: (allCriteriaMet ? 'stabilizing' : 'passive');
150+
const evaluation = {
151+
transitionName: String(transitionName || ''),
152+
frame: frame !== undefined && frame !== null ? Number(frame) : null,
153+
timestampMs,
154+
readiness,
155+
promoted,
156+
promotedNow,
157+
rollbackTriggered: Boolean(rollbackTriggered),
158+
stability: {
159+
currentFrames: stableFrames,
160+
requiredFrames: stabilityWindowFrames
161+
},
162+
criteria: {
163+
values: normalizedCriteria,
164+
unmet: unmetCriteria,
165+
allMet: allCriteriaMet
166+
},
167+
reason: lastReason,
168+
metrics: getMetrics()
169+
};
170+
171+
lastEvaluation = evaluation;
172+
173+
if (logger) {
174+
logger(
175+
`[promotion-gate] readiness=${readiness} promoted=${String(promoted)} ` +
176+
`stable=${stableFrames}/${stabilityWindowFrames} reason=${lastReason}`
177+
);
178+
}
179+
if (onEvaluation) {
180+
onEvaluation(evaluation);
181+
}
182+
183+
return evaluation;
184+
}
185+
186+
function reset(reason = 'MANUAL_RESET') {
187+
promoted = false;
188+
stableFrames = 0;
189+
lastReason = String(reason || 'MANUAL_RESET');
190+
lastEvaluation = null;
191+
}
192+
193+
return {
194+
evaluate,
195+
getMetrics,
196+
getState,
197+
reset
198+
};
199+
}
200+
201+
export { createPromotionGate };

src/advanced/promotion/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
Toolbox Aid
3+
David Quesenberry
4+
04/07/2026
5+
index.js
6+
*/
7+
8+
export { createPromotionGate } from './createPromotionGate.js';

0 commit comments

Comments
 (0)