Replan reviewed field conflicts with durable audit and conditional history

This commit is contained in:
2026-11-08 01:56:21 +08:00
parent a0417b4811
commit 89b6f4a253
11 changed files with 453 additions and 29 deletions
+11 -2
View File
@@ -269,7 +269,7 @@ export function createEditor<Runtime = undefined>(options: EditorOptions<Runtime
finally { entered = false; }
},
/** Engine-only canonical proposal: validation and runtime state publish as one root. */
reconcile(document: Document, runtime: Runtime, expectedRootRevision: number): Result {
reconcile(document: Document, runtime: Runtime, expectedRootRevision: number, replacement?: { readonly history: HistoryState; readonly operations: ReadonlyMap<Id,OperationIntent> }): Result {
try { assertOpen(); } catch (error) { return reject(error); }
entered = true;
try {
@@ -277,8 +277,17 @@ export function createEditor<Runtime = undefined>(options: EditorOptions<Runtime
if (document.projectId !== state.document.projectId) throw fault('project_mismatch');
const next = createDocument(document.projectId, [...document.canvases.values()], [...document.elements.values()], document.tombstones);
validateDocument(next, options.registry);
let nextSequence=sequence;
if(replacement){
const {history,operations}=replacement,ids=[...history.undo,...history.redo];
if(new Set(ids).size!==ids.length||ids.length!==history.entries.size||ids.some(id=>!history.entries.has(id)))throw fault('invalid_history');
for(const [id,intent]of state.operations)if(!equal(intent,operations.get(id)))throw fault('operation_identity_conflict',[id]);
for(const [id,intent]of operations){if(id!==intent.id||!Number.isSafeInteger(intent.sequence)||intent.sequence<1)throw fault('invalid_operation_sequence');nextSequence=Math.max(nextSequence,intent.sequence);}
for(const [id,entry]of history.entries)if(entry.id!==id||entry.operationIds.some(operationId=>!operations.has(operationId)))throw fault('invalid_history');
}
const changed = !documentsEqual(next, state.document);
state = Object.freeze({ ...state, document: changed ? next : state.document, runtime, revision: state.revision + Number(changed), rootRevision: state.rootRevision + 1 });
state = Object.freeze({ ...state, ...(replacement?{history:replacement.history,operations:replacement.operations}:{}), document: changed ? next : state.document, runtime, revision: state.revision + Number(changed), rootRevision: state.rootRevision + 1 });
sequence=nextSequence;
emit(freeze({ revision: state.revision, origin: 'ack', changes: [], operationIds: [] }));
return Object.freeze({ status: 'applied', revision: state.revision, operationIds: Object.freeze([]) });
} catch (error) { return reject(error); }