feat(adoption): one owner for config/layout identity and the adoption sequence

`src/config-adoption.ts` owns config/layout with revision and fingerprint;
the host keeps `_serverCfg`/`_cfgRev`/`_layout`/`_layoutRev` as delegates.
All seven authoritative adoptions go through `adoptAuthoritativeGated`
(backdrop readiness → continuity → adopt → profile tail); the post-write
paths (space/delete ×2, optimize_undo, import/apply) gain the gate and take
revisions from the re-read bodies. `rollbackOptimistic` moves to the owner;
plan-optimize, space copy and the vacuum writers stop assigning identity.

Issue: #500
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-10 11:41:16 +00:00
parent a44fbd373d
commit 7194fb2df0
57 changed files with 2082 additions and 1063 deletions
+9 -57
View File
@@ -4,7 +4,6 @@ import test from 'node:test';
import {
enqueueSerializedWrite,
optimisticAttempt,
rollbackOptimistic,
} from '../test-build/serialized-write-queue.js';
function deferred() {
@@ -56,64 +55,17 @@ test('a failed write does not poison the next queued edit (#224)', async () => {
assert.deepEqual(seen, ['failed', 'recovered']);
});
test('a rejected optimistic write restores only its own candidate (#439)', () => {
// The rollback of an optimistic attempt moved to the identity owner (#500):
// see test/config-adoption.test.mjs (#439/#442 cases live there now).
test('optimisticAttempt snapshots the previous body by value (#439)', () => {
const fingerprint = (value) => JSON.stringify(value);
const previous = { settings: { enabled: true } };
const attempted = { settings: { enabled: false } };
const attempt = optimisticAttempt(previous, attempted, 'server-fingerprint', 7, fingerprint);
let updates = 0;
const host = {
_serverCfg: attempted,
_cfgRev: 7,
_cfgContentFingerprint: fingerprint(attempted),
requestUpdate: () => { updates += 1; },
};
assert.equal(rollbackOptimistic(host, attempt, fingerprint), true);
assert.deepEqual(host._serverCfg, previous);
assert.notEqual(host._serverCfg, previous, 'the rollback snapshot is isolated from later mutations');
assert.equal(host._cfgContentFingerprint, 'server-fingerprint');
assert.equal(updates, 1);
});
test('a conflict reload or newer mutation wins over a rejected candidate (#439)', () => {
const fingerprint = (value) => JSON.stringify(value);
const previous = { settings: { value: 'server-before' } };
const attempted = { settings: { value: 'draft' } };
const attempt = optimisticAttempt(previous, attempted, 'before', 3, fingerprint);
const authoritative = { settings: { value: 'server-after' } };
let updates = 0;
const host = {
_serverCfg: authoritative,
_cfgRev: 4,
_cfgContentFingerprint: fingerprint(authoritative),
requestUpdate: () => { updates += 1; },
};
assert.equal(rollbackOptimistic(host, attempt, fingerprint), false);
assert.equal(host._serverCfg, authoritative);
assert.equal(updates, 0);
host._cfgRev = 3;
host._serverCfg = { settings: { value: 'newer-local-edit' } };
assert.equal(rollbackOptimistic(host, attempt, fingerprint), false);
assert.equal(host._serverCfg.settings.value, 'newer-local-edit');
assert.equal(updates, 0);
});
test('newer in-place content on the attempted root also wins (#442)', () => {
const fingerprint = (value) => JSON.stringify(value);
const previous = { settings: { value: 'server-before' } };
const attempted = { settings: { value: 'draft' } };
const attempt = optimisticAttempt(previous, attempted, 'before', 3, fingerprint);
const host = {
_serverCfg: attempted,
_cfgRev: 3,
_cfgContentFingerprint: fingerprint(attempted),
requestUpdate: () => assert.fail('newer content must not be rolled back'),
};
attempted.settings.newer = true;
assert.equal(rollbackOptimistic(host, attempt, fingerprint), false);
assert.equal(host._serverCfg, attempted);
assert.deepEqual(attempt.previous, previous);
assert.notEqual(attempt.previous, previous, 'the rollback snapshot is isolated from later mutations');
assert.equal(attempt.previousFingerprint, 'server-fingerprint');
assert.equal(attempt.revision, 7);
assert.equal(attempt.attempted, attempted);
assert.equal(attempt.attemptedFingerprint, fingerprint(attempted));
});