Skip to content

Commit 11b1e2a

Browse files
author
DavidQ
committed
Level 11.1: Event whitelist guard
- Enforced allowed event types - No API changes
1 parent 172ae0b commit 11b1e2a

6 files changed

Lines changed: 87 additions & 23 deletions

File tree

docs/dev/CODEX_COMMANDS.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@ MODEL: GPT-5.3-codex
22
REASONING: high
33

44
COMMAND:
5-
Implement BUILD_PR_LEVEL_11_1_PASSIVE_MODE_GUARD.
5+
Implement BUILD_PR_LEVEL_11_1_EVENT_WHITELIST_GUARD.
66

77
Modify ONLY:
88
src/advanced/state/transitions.js
99

1010
Change:
11-
- Add guard to prevent authoritativeApply execution when in passive mode
11+
- Before emitting eventType, validate it exists in WORLD_GAME_STATE_EVENT_TYPES
12+
- Reject if not present
1213

1314
Do NOT:
1415
- change APIs
1516
- refactor unrelated logic
1617

1718
Validation:
18-
- Passive mode does not mutate state
19+
- Unknown event types rejected
1920
- Existing tests pass
2021

2122
Output:
22-
<project folder>/tmp/BUILD_PR_LEVEL_11_1_PASSIVE_MODE_GUARD.zip
23+
<project folder>/tmp/BUILD_PR_LEVEL_11_1_EVENT_WHITELIST_GUARD.zip

docs/dev/COMMIT_COMMENT.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Level 11.1: Passive mode guard
1+
Level 11.1: Event whitelist guard
22

3-
- Prevented authoritative mutations in passive mode
3+
- Enforced allowed event types
44
- No API changes
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Added passive mode guard to prevent state mutation during non-authoritative execution.
1+
Added guard to ensure only whitelisted event types are emitted from transitions.
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
[ ] transitions.js modified only
2-
[ ] Passive mode does not mutate
3-
[ ] Authoritative mode unchanged
2+
[ ] Unknown event types rejected
43
[ ] No API changes
54
[ ] Tests pass
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# BUILD PR — Level 11.1 Event Whitelist Guard
2+
3+
## Purpose
4+
Ensure only approved event types are emitted from transitions.
5+
6+
## Scope
7+
- Validation guard only
8+
- No API changes
9+
10+
## File
11+
- src/advanced/state/transitions.js
12+
13+
## Change
14+
- Enforce eventType must exist in WORLD_GAME_STATE_EVENT_TYPES
15+
16+
## Validation
17+
- Unknown event types rejected
18+
- Existing tests pass

src/advanced/state/transitions.js

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,52 @@ import { WORLD_GAME_STATE_EVENT_TYPES } from './constants.js';
99
import { isPlainObject } from '../../shared/utils/objectUtils.js';
1010
import { toFiniteNumber } from '../../shared/math/numberNormalization.js';
1111

12+
const ALLOWED_TRANSITION_EVENT_TYPES = new Set(Object.values(WORLD_GAME_STATE_EVENT_TYPES));
13+
1214
function isPassiveModeContext(context) {
1315
if (!isPlainObject(context)) return false;
1416
if (context.passiveMode === true) return true;
1517
if (typeof context.mode === 'string' && context.mode.trim().toLowerCase() === 'passive') return true;
1618
return false;
1719
}
1820

21+
function validateTransitionEventType(eventType, transitionName) {
22+
const normalizedEventType = String(eventType || '').trim();
23+
if (!ALLOWED_TRANSITION_EVENT_TYPES.has(normalizedEventType)) {
24+
return {
25+
ok: false,
26+
reason: `${String(transitionName || 'transition')} has unapproved eventType.`
27+
};
28+
}
29+
return { ok: true };
30+
}
31+
32+
function createWhitelistedTransition({
33+
transitionName,
34+
validate,
35+
eventType,
36+
authoritativeApply
37+
}) {
38+
const baseValidate = typeof validate === 'function'
39+
? validate
40+
: () => ({ ok: false, reason: `${String(transitionName || 'transition')} missing validate handler.` });
41+
42+
const definition = {
43+
eventType: String(eventType || ''),
44+
validate(payload, context) {
45+
const eventTypeValidation = validateTransitionEventType(definition.eventType, transitionName);
46+
if (!eventTypeValidation.ok) return eventTypeValidation;
47+
return baseValidate(payload, context);
48+
}
49+
};
50+
51+
if (typeof authoritativeApply === 'function') {
52+
definition.authoritativeApply = authoritativeApply;
53+
}
54+
55+
return definition;
56+
}
57+
1958
function recalcObjectiveSummary(objectivesById) {
2059
const objectiveIds = Object.keys(objectivesById);
2160
let completed = 0;
@@ -191,36 +230,43 @@ function validateResolveRunOutcome(payload) {
191230

192231
function createTransitionRegistry() {
193232
return {
194-
transitionGameMode: {
233+
transitionGameMode: createWhitelistedTransition({
234+
transitionName: 'transitionGameMode',
195235
validate: validateTransitionGameMode,
196236
eventType: WORLD_GAME_STATE_EVENT_TYPES.GAME_MODE_CHANGED
197-
},
198-
transitionPhase: {
237+
}),
238+
transitionPhase: createWhitelistedTransition({
239+
transitionName: 'transitionPhase',
199240
validate: validateTransitionPhase,
200241
eventType: WORLD_GAME_STATE_EVENT_TYPES.GAME_PHASE_CHANGED
201-
},
202-
advanceWave: {
242+
}),
243+
advanceWave: createWhitelistedTransition({
244+
transitionName: 'advanceWave',
203245
validate: validateAdvanceWave,
204246
eventType: WORLD_GAME_STATE_EVENT_TYPES.TRANSITION_APPLIED
205-
},
206-
applyScoreDelta: {
247+
}),
248+
applyScoreDelta: createWhitelistedTransition({
249+
transitionName: 'applyScoreDelta',
207250
validate: validateApplyScoreDelta,
208251
eventType: WORLD_GAME_STATE_EVENT_TYPES.TRANSITION_APPLIED,
209252
authoritativeApply: applyAuthoritativeScoreDelta
210-
},
211-
updateObjectiveProgress: {
253+
}),
254+
updateObjectiveProgress: createWhitelistedTransition({
255+
transitionName: 'updateObjectiveProgress',
212256
validate: validateUpdateObjectiveProgress,
213257
eventType: WORLD_GAME_STATE_EVENT_TYPES.OBJECTIVE_SNAPSHOT_UPDATED,
214258
authoritativeApply: applyAuthoritativeObjectiveProgress
215-
},
216-
setWorldFlag: {
259+
}),
260+
setWorldFlag: createWhitelistedTransition({
261+
transitionName: 'setWorldFlag',
217262
validate: validateSetWorldFlag,
218263
eventType: WORLD_GAME_STATE_EVENT_TYPES.TRANSITION_APPLIED
219-
},
220-
resolveRunOutcome: {
264+
}),
265+
resolveRunOutcome: createWhitelistedTransition({
266+
transitionName: 'resolveRunOutcome',
221267
validate: validateResolveRunOutcome,
222268
eventType: WORLD_GAME_STATE_EVENT_TYPES.TRANSITION_APPLIED
223-
}
269+
})
224270
};
225271
}
226272

0 commit comments

Comments
 (0)