instar 1.3.589 → 1.3.591
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +35 -14
- package/dist/commands/server.js.map +1 -1
- package/dist/core/OwnershipApplier.d.ts +10 -1
- package/dist/core/OwnershipApplier.d.ts.map +1 -1
- package/dist/core/OwnershipApplier.js +11 -1
- package/dist/core/OwnershipApplier.js.map +1 -1
- package/dist/core/durableOwnershipActivation.d.ts +29 -0
- package/dist/core/durableOwnershipActivation.d.ts.map +1 -0
- package/dist/core/durableOwnershipActivation.js +41 -0
- package/dist/core/durableOwnershipActivation.js.map +1 -0
- package/dist/core/ownershipApplierWiring.d.ts +44 -0
- package/dist/core/ownershipApplierWiring.d.ts.map +1 -0
- package/dist/core/ownershipApplierWiring.js +20 -0
- package/dist/core/ownershipApplierWiring.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.590.md +21 -0
- package/upgrades/1.3.591.md +34 -0
- package/upgrades/side-effects/ownership-applier-meshself-ordering-fix.md +52 -0
- package/upgrades/side-effects/pool-consistent-multimachine-activation.md +39 -0
|
@@ -51,7 +51,14 @@ export interface OwnershipApplierDeps {
|
|
|
51
51
|
/** The SAME durable store the SessionOwnershipRegistry reads — so a materialized
|
|
52
52
|
* record is immediately visible to owner-resolution / routing. */
|
|
53
53
|
store: SessionOwnershipStore;
|
|
54
|
-
|
|
54
|
+
/**
|
|
55
|
+
* This machine's mesh id — used ONLY for the SELF-vs-peer log label, never for
|
|
56
|
+
* materialization (every placement is adopted regardless of owner). Accepts a
|
|
57
|
+
* **late-bound getter** so a caller can wire the applier before `_meshSelfId` is
|
|
58
|
+
* resolved without capturing a stale `null` (the boot-ordering hazard this fix closes);
|
|
59
|
+
* a plain string still works for callers/tests that already have the id.
|
|
60
|
+
*/
|
|
61
|
+
selfMachineId: string | (() => string | null | undefined);
|
|
55
62
|
/** Max placement entries to scan per tick (bounded cost). Default 1000. */
|
|
56
63
|
scanLimit?: number;
|
|
57
64
|
logger?: (msg: string) => void;
|
|
@@ -68,6 +75,8 @@ export declare class OwnershipApplier {
|
|
|
68
75
|
constructor(deps: OwnershipApplierDeps);
|
|
69
76
|
private now;
|
|
70
77
|
private log;
|
|
78
|
+
/** Resolve the (possibly late-bound) self machine id at tick time, for the log label only. */
|
|
79
|
+
private resolveSelf;
|
|
71
80
|
/**
|
|
72
81
|
* One pass: adopt the highest-epoch placement per topic into the local store.
|
|
73
82
|
* Returns counts for observability. Never throws — a read/parse failure degrades
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OwnershipApplier.d.ts","sourceRoot":"","sources":["../../src/core/OwnershipApplier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAE3E,uFAAuF;AACvF,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG;QAC9D,OAAO,EAAE,KAAK,CAAC;YAAE,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACrG,CAAC;CACH;AAED,MAAM,WAAW,oBAAoB;IACnC,yEAAyE;IACzE,MAAM,EAAE,eAAe,CAAC;IACxB;uEACmE;IACnE,KAAK,EAAE,qBAAqB,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"OwnershipApplier.d.ts","sourceRoot":"","sources":["../../src/core/OwnershipApplier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAE3E,uFAAuF;AACvF,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG;QAC9D,OAAO,EAAE,KAAK,CAAC;YAAE,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACrG,CAAC;CACH;AAED,MAAM,WAAW,oBAAoB;IACnC,yEAAyE;IACzE,MAAM,EAAE,eAAe,CAAC;IACxB;uEACmE;IACnE,KAAK,EAAE,qBAAqB,CAAC;IAC7B;;;;;;OAMG;IACH,aAAa,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAC1D,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAuB;gBAE7B,IAAI,EAAE,oBAAoB;IAItC,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,GAAG;IAIX,8FAA8F;IAC9F,OAAO,CAAC,WAAW;IAKnB;;;;OAIG;IACH,IAAI,IAAI,oBAAoB;CAuD7B"}
|
|
@@ -40,6 +40,11 @@ export class OwnershipApplier {
|
|
|
40
40
|
log(m) {
|
|
41
41
|
this.d.logger?.(`[ownership-applier] ${m}`);
|
|
42
42
|
}
|
|
43
|
+
/** Resolve the (possibly late-bound) self machine id at tick time, for the log label only. */
|
|
44
|
+
resolveSelf() {
|
|
45
|
+
const s = this.d.selfMachineId;
|
|
46
|
+
return typeof s === 'function' ? s() : s;
|
|
47
|
+
}
|
|
43
48
|
/**
|
|
44
49
|
* One pass: adopt the highest-epoch placement per topic into the local store.
|
|
45
50
|
* Returns counts for observability. Never throws — a read/parse failure degrades
|
|
@@ -85,8 +90,13 @@ export class OwnershipApplier {
|
|
|
85
90
|
const r = this.d.store.casWrite(rec);
|
|
86
91
|
if (r.ok) {
|
|
87
92
|
materialized++;
|
|
93
|
+
const self = this.resolveSelf();
|
|
88
94
|
this.log(`materialized topic ${sessionKey} → owner ${best.owner} @epoch ${best.epoch}` +
|
|
89
|
-
(best.owner ===
|
|
95
|
+
(self && best.owner === self
|
|
96
|
+
? ' (SELF — this machine now serves it)'
|
|
97
|
+
: self
|
|
98
|
+
? ' (peer — route forwards there)'
|
|
99
|
+
: ''));
|
|
90
100
|
}
|
|
91
101
|
}
|
|
92
102
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OwnershipApplier.js","sourceRoot":"","sources":["../../src/core/OwnershipApplier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;
|
|
1
|
+
{"version":3,"file":"OwnershipApplier.js","sourceRoot":"","sources":["../../src/core/OwnershipApplier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* durableOwnershipActivation — the pool-consistent activation predicate for the
|
|
3
|
+
* cross-machine durable ownership store (spec:
|
|
4
|
+
* docs/specs/pool-consistent-multimachine-activation.md).
|
|
5
|
+
*
|
|
6
|
+
* The transfer fix's durable store + OwnershipApplier MUST run on every machine that
|
|
7
|
+
* CONSUMES replicated placement journals — otherwise a seat transferred TO a machine
|
|
8
|
+
* whose store is dark never materializes ownership there and the move dies on arrival
|
|
9
|
+
* (the live Laptop↔Mini finding, 2026-06-16: the Mini's echo wasn't a dev agent, so a
|
|
10
|
+
* purely per-machine dev-gate left the store dark there while the Laptop's was live).
|
|
11
|
+
*
|
|
12
|
+
* So activation is the OR of:
|
|
13
|
+
* - the per-machine dev-agent gate (a dev agent opts in to dogfood), AND
|
|
14
|
+
* - the explicit, pool-consistent `multiMachine.coherenceJournal.replication.enabled
|
|
15
|
+
* === true` signal — the SAME one gating the placement-replication applier
|
|
16
|
+
* (`journalSyncApplier`). Invariant: a machine running placement replication runs the
|
|
17
|
+
* ownership applier + durable store too (they consume the same replicated placements).
|
|
18
|
+
*
|
|
19
|
+
* A single-machine agent (no replication, not dev) stays on the in-memory store — a
|
|
20
|
+
* strict no-op.
|
|
21
|
+
*/
|
|
22
|
+
/** The replication signal: explicit opt-in (`=== true`); ConfigDefaults leaves it absent. */
|
|
23
|
+
export declare function isPlacementReplicationEnabled(config: unknown): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether to activate the durable ownership store + applier on THIS machine.
|
|
26
|
+
* `isDevAgentGate` is the injected `resolveDevAgentGate` (so this stays pure/testable).
|
|
27
|
+
*/
|
|
28
|
+
export declare function shouldActivateDurableOwnership(config: unknown, isDevAgentGate: (flag: any, config: any) => boolean): boolean;
|
|
29
|
+
//# sourceMappingURL=durableOwnershipActivation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durableOwnershipActivation.d.ts","sourceRoot":"","sources":["../../src/core/durableOwnershipActivation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,6FAA6F;AAC7F,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAGtE;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,OAAO,EAIf,cAAc,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK,OAAO,GAClD,OAAO,CAIT"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* durableOwnershipActivation — the pool-consistent activation predicate for the
|
|
3
|
+
* cross-machine durable ownership store (spec:
|
|
4
|
+
* docs/specs/pool-consistent-multimachine-activation.md).
|
|
5
|
+
*
|
|
6
|
+
* The transfer fix's durable store + OwnershipApplier MUST run on every machine that
|
|
7
|
+
* CONSUMES replicated placement journals — otherwise a seat transferred TO a machine
|
|
8
|
+
* whose store is dark never materializes ownership there and the move dies on arrival
|
|
9
|
+
* (the live Laptop↔Mini finding, 2026-06-16: the Mini's echo wasn't a dev agent, so a
|
|
10
|
+
* purely per-machine dev-gate left the store dark there while the Laptop's was live).
|
|
11
|
+
*
|
|
12
|
+
* So activation is the OR of:
|
|
13
|
+
* - the per-machine dev-agent gate (a dev agent opts in to dogfood), AND
|
|
14
|
+
* - the explicit, pool-consistent `multiMachine.coherenceJournal.replication.enabled
|
|
15
|
+
* === true` signal — the SAME one gating the placement-replication applier
|
|
16
|
+
* (`journalSyncApplier`). Invariant: a machine running placement replication runs the
|
|
17
|
+
* ownership applier + durable store too (they consume the same replicated placements).
|
|
18
|
+
*
|
|
19
|
+
* A single-machine agent (no replication, not dev) stays on the in-memory store — a
|
|
20
|
+
* strict no-op.
|
|
21
|
+
*/
|
|
22
|
+
/** The replication signal: explicit opt-in (`=== true`); ConfigDefaults leaves it absent. */
|
|
23
|
+
export function isPlacementReplicationEnabled(config) {
|
|
24
|
+
const c = config;
|
|
25
|
+
return c?.multiMachine?.coherenceJournal?.replication?.enabled === true;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Whether to activate the durable ownership store + applier on THIS machine.
|
|
29
|
+
* `isDevAgentGate` is the injected `resolveDevAgentGate` (so this stays pure/testable).
|
|
30
|
+
*/
|
|
31
|
+
export function shouldActivateDurableOwnership(config,
|
|
32
|
+
// The injected `resolveDevAgentGate` — typed loosely so the real gate
|
|
33
|
+
// (`(boolean|undefined, DevAgentGateConfig|undefined) => boolean`) and test fakes
|
|
34
|
+
// both satisfy it without importing the gate's config type here.
|
|
35
|
+
isDevAgentGate) {
|
|
36
|
+
const c = config;
|
|
37
|
+
const devOn = isDevAgentGate(c?.multiMachine?.durableOwnership?.enabled, config);
|
|
38
|
+
return devOn || isPlacementReplicationEnabled(config);
|
|
39
|
+
}
|
|
40
|
+
// (pool-consistent activation — see docs/specs/pool-consistent-multimachine-activation.md)
|
|
41
|
+
//# sourceMappingURL=durableOwnershipActivation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durableOwnershipActivation.js","sourceRoot":"","sources":["../../src/core/durableOwnershipActivation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,6FAA6F;AAC7F,MAAM,UAAU,6BAA6B,CAAC,MAAe;IAC3D,MAAM,CAAC,GAAG,MAAkG,CAAC;IAC7G,OAAO,CAAC,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;AAC1E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,8BAA8B,CAC5C,MAAe;AACf,sEAAsE;AACtE,kFAAkF;AAClF,iEAAiE;AACjE,cAAmD;IAEnD,MAAM,CAAC,GAAG,MAAgF,CAAC;IAC3F,MAAM,KAAK,GAAG,cAAc,CAAC,CAAC,EAAE,YAAY,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACjF,OAAO,KAAK,IAAI,6BAA6B,CAAC,MAAM,CAAC,CAAC;AACxD,CAAC;AAED,2FAA2F"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ownershipApplierWiring — the testable construction factory for the transfer-fix §7.2
|
|
3
|
+
* OwnershipApplier (spec: docs/specs/ownership-applier-meshself-ordering-fix.md).
|
|
4
|
+
*
|
|
5
|
+
* Why this exists: the applier's construction CONDITION used to live inline in
|
|
6
|
+
* `server.ts` as `if (durableOwnershipStore && _meshSelfId)`. That guard read
|
|
7
|
+
* `_meshSelfId` ~650 lines BEFORE the boot sequence assigns it, so it was always
|
|
8
|
+
* `null` at the check → the applier was never constructed or ticked, on either machine,
|
|
9
|
+
* and a transferred seat never materialized on the destination. The inline condition was
|
|
10
|
+
* also untestable, which is precisely how the ordering bug shipped.
|
|
11
|
+
*
|
|
12
|
+
* The fix (Structure > Willpower): gate construction on the durable store ALONE — the
|
|
13
|
+
* genuinely relevant condition (applying replicated placements is a durable-store concern;
|
|
14
|
+
* the in-memory store has no cross-machine replication to apply) — and pass `selfMachineId`
|
|
15
|
+
* as a LATE-BOUND getter so the (label-only) self id resolves at tick time instead of being
|
|
16
|
+
* captured as a stale `null` at construction. This makes the wiring order-independent and
|
|
17
|
+
* unit-testable without booting the whole server.
|
|
18
|
+
*/
|
|
19
|
+
import type { PlacementReader } from './OwnershipApplier.js';
|
|
20
|
+
import { OwnershipApplier } from './OwnershipApplier.js';
|
|
21
|
+
import type { SessionOwnershipStore } from './SessionOwnershipRegistry.js';
|
|
22
|
+
export interface OwnershipApplierWiringDeps {
|
|
23
|
+
/** The durable per-session store, or null when the in-memory store is in use. */
|
|
24
|
+
durableOwnershipStore: SessionOwnershipStore | null;
|
|
25
|
+
/** Reads replicated + own placement entries (CoherenceJournalReader). */
|
|
26
|
+
reader: PlacementReader;
|
|
27
|
+
/**
|
|
28
|
+
* Late-bound resolver for this machine's mesh id. May legitimately return null/undefined
|
|
29
|
+
* at construction time (before the boot sequence assigns it); it is used ONLY for the
|
|
30
|
+
* SELF-vs-peer log label, never for materialization.
|
|
31
|
+
*/
|
|
32
|
+
getSelfMachineId: () => string | null | undefined;
|
|
33
|
+
scanLimit?: number;
|
|
34
|
+
logger?: (msg: string) => void;
|
|
35
|
+
now?: () => number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build the OwnershipApplier when (and only when) the durable ownership store is active.
|
|
39
|
+
* Returns `null` for the in-memory store (nothing to apply). NEVER gates on the self id —
|
|
40
|
+
* that was the boot-ordering bug. The caller schedules the boot tick + interval on a
|
|
41
|
+
* non-null result.
|
|
42
|
+
*/
|
|
43
|
+
export declare function wireOwnershipApplier(deps: OwnershipApplierWiringDeps): OwnershipApplier | null;
|
|
44
|
+
//# sourceMappingURL=ownershipApplierWiring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ownershipApplierWiring.d.ts","sourceRoot":"","sources":["../../src/core/ownershipApplierWiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAE3E,MAAM,WAAW,0BAA0B;IACzC,iFAAiF;IACjF,qBAAqB,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACpD,yEAAyE;IACzE,MAAM,EAAE,eAAe,CAAC;IACxB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,0BAA0B,GAAG,gBAAgB,GAAG,IAAI,CAU9F"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { OwnershipApplier } from './OwnershipApplier.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build the OwnershipApplier when (and only when) the durable ownership store is active.
|
|
4
|
+
* Returns `null` for the in-memory store (nothing to apply). NEVER gates on the self id —
|
|
5
|
+
* that was the boot-ordering bug. The caller schedules the boot tick + interval on a
|
|
6
|
+
* non-null result.
|
|
7
|
+
*/
|
|
8
|
+
export function wireOwnershipApplier(deps) {
|
|
9
|
+
if (!deps.durableOwnershipStore)
|
|
10
|
+
return null;
|
|
11
|
+
return new OwnershipApplier({
|
|
12
|
+
reader: deps.reader,
|
|
13
|
+
store: deps.durableOwnershipStore,
|
|
14
|
+
selfMachineId: deps.getSelfMachineId, // getter — resolved per-tick, never captured stale
|
|
15
|
+
scanLimit: deps.scanLimit,
|
|
16
|
+
logger: deps.logger,
|
|
17
|
+
now: deps.now,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=ownershipApplierWiring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ownershipApplierWiring.js","sourceRoot":"","sources":["../../src/core/ownershipApplierWiring.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAmBzD;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAgC;IACnE,IAAI,CAAC,IAAI,CAAC,qBAAqB;QAAE,OAAO,IAAI,CAAC;IAC7C,OAAO,IAAI,gBAAgB,CAAC;QAC1B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,qBAAqB;QACjC,aAAa,EAAE,IAAI,CAAC,gBAAgB,EAAE,mDAAmD;QACzF,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,GAAG,EAAE,IAAI,CAAC,GAAG;KACd,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-06-16T06:55:01.483Z",
|
|
5
|
+
"instarVersion": "1.3.591",
|
|
6
6
|
"entryCount": 201,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The cross-machine transfer fix (shipped dark in v1.3.589) was caught half-active by its own live test: the durable ownership store only turned on for a dev-flagged machine, so on a non-dev pool machine it stayed off and a transferred conversation died on arrival. This activates the durable store wherever placement replication is explicitly on (`multiMachine.coherenceJournal.replication.enabled === true`) — the pool-consistent signal — so it comes up on every machine that needs to materialize transferred ownership.
|
|
9
|
+
|
|
10
|
+
## What to Tell Your User
|
|
11
|
+
|
|
12
|
+
Nothing changes in normal single-machine use. For a multi-machine setup with replication enabled, the cross-machine "move a conversation between machines" feature now actually completes the move on the receiving machine — the gap the live test caught.
|
|
13
|
+
|
|
14
|
+
## Summary of New Capabilities
|
|
15
|
+
|
|
16
|
+
- `durableOwnership` activates wherever placement replication is on (not only dev machines); single-machine agents unchanged (strict no-op).
|
|
17
|
+
- New testable predicate `src/core/durableOwnershipActivation.ts`.
|
|
18
|
+
|
|
19
|
+
## Evidence
|
|
20
|
+
|
|
21
|
+
The live Laptop↔Mini proof of v1.3.589 found the Mini's durable store dark (its echo wasn't a dev agent) → ownership never materialized → seat died on arrival. The Mini HAS `coherenceJournal.replication.enabled:true`, so the new predicate activates the durable store there. 7 unit tests (both sides of every boundary) green; the live re-proof (a reply served from the Mini after a transfer) is the acceptance criterion.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Fixed a boot-ordering bug that left the cross-machine `OwnershipApplier` permanently
|
|
9
|
+
un-wired: a topic moved between machines was reported moved but the destination never took
|
|
10
|
+
ownership, so the conversation went silent on arrival. The applier's on-switch now depends on
|
|
11
|
+
the durable ownership store alone (extracted into a testable `wireOwnershipApplier` factory),
|
|
12
|
+
and the machine id it uses for logging is late-bound, so the wiring can't be broken by code
|
|
13
|
+
reordering. Caught by applying the Live-User-Channel Proof gold standard to a real
|
|
14
|
+
Laptop→Mini transfer (the second, deeper bug after the dev-gate-darkness fix in v1.3.590).
|
|
15
|
+
|
|
16
|
+
## What to Tell Your User
|
|
17
|
+
|
|
18
|
+
If you move a conversation between your machines, the destination now genuinely takes over and
|
|
19
|
+
answers there. Before this, a moved conversation could report success but then go quiet on the
|
|
20
|
+
new machine, because the new machine never realized it owned the conversation. Single-machine
|
|
21
|
+
setups are unaffected.
|
|
22
|
+
|
|
23
|
+
## Summary of New Capabilities
|
|
24
|
+
|
|
25
|
+
| Capability | How to Use |
|
|
26
|
+
|-----------|-----------|
|
|
27
|
+
| Moving a conversation between machines actually lands it on the destination | Move a topic to another machine as usual; the destination now takes ownership and serves it |
|
|
28
|
+
|
|
29
|
+
## Evidence
|
|
30
|
+
|
|
31
|
+
- 6 new unit/wiring tests (`tests/unit/ownershipApplierWiring.test.ts`) + 7 existing applier
|
|
32
|
+
tests stay green; tsc clean.
|
|
33
|
+
- Release gate: a live two-machine re-run asserts the destination materializes the seat and a
|
|
34
|
+
reply serves from it (Telegram + Slack).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Side-Effects Review — OwnershipApplier mesh-self ordering fix
|
|
2
|
+
|
|
3
|
+
**Slug:** ownership-applier-meshself-ordering-fix
|
|
4
|
+
**Spec:** docs/specs/ownership-applier-meshself-ordering-fix.md
|
|
5
|
+
**Parent principle:** Wiring Integrity — a dependency-injected component must actually run, not be a silently-skipped no-op.
|
|
6
|
+
|
|
7
|
+
## What changed
|
|
8
|
+
|
|
9
|
+
A boot-ordering bug in `src/commands/server.ts` meant the transfer-fix §7.2 `OwnershipApplier`
|
|
10
|
+
was never constructed or ticked at runtime (its guard read `_meshSelfId` ~650 lines before
|
|
11
|
+
the boot sequence assigns it, so the guard was always false). Result: a topic transferred
|
|
12
|
+
between machines was recorded on the SOURCE and replicated to the DESTINATION's journal, but
|
|
13
|
+
the destination never materialized ownership → it never knew it owned the session → the seat
|
|
14
|
+
died on arrival. Caught by applying the Live-User-Channel Proof gold standard to a real
|
|
15
|
+
Laptop→Mini transfer.
|
|
16
|
+
|
|
17
|
+
Fix (order-independent — Structure > Willpower):
|
|
18
|
+
- `src/core/OwnershipApplier.ts` — `selfMachineId` accepts a late-bound getter (label-only;
|
|
19
|
+
materialization never needs it); resolved per-tick instead of captured stale at construction.
|
|
20
|
+
- `src/core/ownershipApplierWiring.ts` (new) — `wireOwnershipApplier()` factory gates
|
|
21
|
+
construction on the durable store ALONE (the correct condition), extracted so the invariant
|
|
22
|
+
is unit-testable (the inline condition was untestable, which is how the bug shipped).
|
|
23
|
+
- `src/commands/server.ts` — calls the factory in place of the inline guard.
|
|
24
|
+
|
|
25
|
+
## Blast radius
|
|
26
|
+
|
|
27
|
+
- **Multi-machine destinations (the target):** a transferred seat now materializes durable
|
|
28
|
+
ownership on the destination and is served there. This was BROKEN before — net improvement.
|
|
29
|
+
- **Single machine:** the durable store may be active but there are no peer placements; the
|
|
30
|
+
applier ticks and finds nothing — a harmless no-op. No behavior change.
|
|
31
|
+
- **Transfer SOURCE:** unchanged (it writes ownership directly via the tclaim path).
|
|
32
|
+
- **No new route, config flag, state schema, URL, or migration.** Pure internal wiring +
|
|
33
|
+
one new internal module (no agent-facing surface → no CLAUDE.md template change required).
|
|
34
|
+
- **Backward compatibility:** `OwnershipApplier`'s `selfMachineId` still accepts a plain
|
|
35
|
+
string; all existing callers/tests are unaffected (7 existing applier tests stay green).
|
|
36
|
+
|
|
37
|
+
## Reversibility
|
|
38
|
+
|
|
39
|
+
No flag (it only activates the durable-ownership path that already ships pool-consistent for
|
|
40
|
+
replication-on pools / dark for single-machine). Revertable by reverting the three-file diff;
|
|
41
|
+
no durable state is written that would outlive a revert (ownership records are re-derived from
|
|
42
|
+
the replicated journal on the next tick).
|
|
43
|
+
|
|
44
|
+
## Risk / monitoring
|
|
45
|
+
|
|
46
|
+
Low. The applier is off the hot path (boot tick + 15s interval), never throws (degrades to
|
|
47
|
+
"materialized fewer this tick"), and fast-forward-CAS guards against clobbering a fresher
|
|
48
|
+
local decision. Observable via `[ownership-applier] materialized …` / `[OwnershipApplier]
|
|
49
|
+
materialized N/M …` in `logs/server.log` — their appearance is itself the proof the wiring is
|
|
50
|
+
now alive (the bug's signature was their total absence). Release gate: the live two-machine
|
|
51
|
+
re-run (deploy both → transfer → assert destination materializes + a reply serves from the
|
|
52
|
+
destination).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Side-Effects Review — Pool-Consistent durableOwnership Activation (PR1: the predicate)
|
|
2
|
+
|
|
3
|
+
**Spec:** docs/specs/pool-consistent-multimachine-activation.md (CONVERGED iter 4, self-approved). **Tracking:** CMT-1568 follow-on. **Parent:** No Silent Degradation to Brittle Fallback.
|
|
4
|
+
**Earned from:** the live Laptop↔Mini transfer-fix proof (v1.3.589) — the fix was half-active because `durableOwnership` is per-machine dev-gated and the Mini's echo isn't dev-flagged.
|
|
5
|
+
**Files:** src/core/durableOwnershipActivation.ts (new), src/commands/server.ts, tests/unit/durableOwnershipActivation.test.ts (new), site/src/content/docs/architecture/live-user-channel-proof.md.
|
|
6
|
+
|
|
7
|
+
## What changed
|
|
8
|
+
|
|
9
|
+
1. **durableOwnershipActivation.ts (new):** a pure, testable activation predicate.
|
|
10
|
+
`shouldActivateDurableOwnership(config, resolveDevAgentGate)` = `devGate || isPlacementReplicationEnabled(config)`, where `isPlacementReplicationEnabled` reads the explicit `multiMachine.coherenceJournal.replication.enabled === true` signal (the SAME one gating `journalSyncApplier` at server.ts:16588). Invariant: a machine consuming replicated placements runs the ownership applier + durable store.
|
|
11
|
+
2. **server.ts:14861:** replaced the inline `resolveDevAgentGate(durableOwnership.enabled)` gate with `shouldActivateDurableOwnership(config, resolveDevAgentGate)`; the boot log names the activation reason (`pool: replication-on` vs `dev-gate`).
|
|
12
|
+
3. **Tests:** 7 unit tests covering both sides of every boundary (non-dev+replication-on activates [the Mini case]; non-dev+replication-off stays dark; dev activates; single-machine no-op; explicit `=== true` required).
|
|
13
|
+
|
|
14
|
+
## Blast radius
|
|
15
|
+
|
|
16
|
+
- **Strictly WIDENS activation** from "dev agents only" to "dev agents OR any machine with placement replication explicitly on." A single-machine agent (no replication) is UNCHANGED (stays InMemory — strict no-op). A multi-machine agent that explicitly enabled `coherenceJournal.replication.enabled` now also runs the durable store (which it needs to materialize transferred ownership).
|
|
17
|
+
- The durable store is well-tested (18 tests + the live Laptop-side proof in v1.3.589). Activating it where replication is on is the SET that needs it; it never DEACTIVATES anywhere it was active.
|
|
18
|
+
- No new route, no config schema change, no migration. Reversible: revert the predicate to the dev-only gate.
|
|
19
|
+
|
|
20
|
+
## Risk + mitigation
|
|
21
|
+
|
|
22
|
+
- **Risk:** a machine activates the durable store but its peer (mid rolling-deploy, old code) doesn't → half-applied transfer. **Mitigation:** the applier already backfills (queries existing placements per tick); the capability-refuse guard (PR2 follow-on) fail-closes the deploy window; the split-active detector (PR3) surfaces residual inconsistency. For PR1 the predicate alone makes a same-version pool consistent.
|
|
23
|
+
- **Risk:** over-promotion past the dev ladder. **Mitigation:** it activates only where the operator EXPLICITLY enabled replication (`=== true`, absent by default) — operator opt-in, not automatic.
|
|
24
|
+
|
|
25
|
+
## Migration parity
|
|
26
|
+
|
|
27
|
+
- No config default added (the predicate reads existing flags). No CLAUDE.md template change (the durableOwnership awareness shipped with v1.3.589). The new class is documented in the architecture site doc.
|
|
28
|
+
|
|
29
|
+
## Dark-gate line-map
|
|
30
|
+
|
|
31
|
+
- UNCHANGED. No new `enabled: false` line in ConfigDefaults.ts. `durableOwnership` stays in DEV_GATED_FEATURES; this only BROADENS its activation predicate, it does not change its gate registration.
|
|
32
|
+
|
|
33
|
+
## Rollback
|
|
34
|
+
|
|
35
|
+
- Revert the predicate to `resolveDevAgentGate(durableOwnership.enabled, config)`. The durable files remain valid; the journal stays the source of truth.
|
|
36
|
+
|
|
37
|
+
## Evidence
|
|
38
|
+
|
|
39
|
+
- 7 new unit tests (both sides of every boundary), all green; tsc clean; the predicate directly closes the live finding (the Mini has `replication.enabled:true` → the predicate activates the durable store there). The live re-proof (a reply served from the Mini) is the acceptance criterion, run after deploy.
|