instar 1.3.590 → 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 +21 -12
- 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/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.591.md +34 -0
- package/upgrades/side-effects/ownership-applier-meshself-ordering-fix.md +52 -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,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-16T06:
|
|
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,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).
|