fix(council-v2): serialize topicTicker to prevent fire-and-forget concurrency
executorLoop fire-and-forgets each effect execution. Multiple concurrent execute() calls each invoke topicTicker(), causing concurrent tick() calls that bypass Moore diff (both read same prevSnapshotJson before either updates). Fix: chain tick() calls via promise serialization. Critical section (read → diff → update baseline) is now guaranteed sequential.
This commit is contained in:
@@ -22,9 +22,17 @@ import type { TopicRule } from './topic-rule-adapter.js';
|
||||
* suitable for calling at the end of a runPulse tick cycle.
|
||||
*/
|
||||
export function createTopicTicker(rules: TopicRule[]): () => Promise<void> {
|
||||
return async () => {
|
||||
for (const rule of rules) {
|
||||
await rule.tick();
|
||||
}
|
||||
let pending: Promise<void> | null = null;
|
||||
return () => {
|
||||
// Serialize tick calls: if a tick is in progress, chain after it.
|
||||
// This ensures the Moore machine's critical section (read → diff → update baseline)
|
||||
// is never concurrent, even when called from fire-and-forget executors.
|
||||
const run = async () => {
|
||||
for (const rule of rules) {
|
||||
await rule.tick();
|
||||
}
|
||||
};
|
||||
pending = (pending ?? Promise.resolve()).then(run, run);
|
||||
return pending;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user