instar 1.3.569 → 1.3.571

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.
Files changed (36) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +127 -53
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/CoherenceJournal.d.ts +46 -0
  5. package/dist/core/CoherenceJournal.d.ts.map +1 -1
  6. package/dist/core/CoherenceJournal.js +94 -2
  7. package/dist/core/CoherenceJournal.js.map +1 -1
  8. package/dist/core/JournalSyncApplier.d.ts +22 -0
  9. package/dist/core/JournalSyncApplier.d.ts.map +1 -1
  10. package/dist/core/JournalSyncApplier.js +39 -2
  11. package/dist/core/JournalSyncApplier.js.map +1 -1
  12. package/dist/core/MachinePoolRegistry.d.ts.map +1 -1
  13. package/dist/core/MachinePoolRegistry.js +26 -4
  14. package/dist/core/MachinePoolRegistry.js.map +1 -1
  15. package/dist/core/PeerPresencePuller.d.ts +39 -0
  16. package/dist/core/PeerPresencePuller.d.ts.map +1 -1
  17. package/dist/core/PeerPresencePuller.js +46 -1
  18. package/dist/core/PeerPresencePuller.js.map +1 -1
  19. package/dist/core/ReplicatedPeerStreamReader.d.ts +97 -0
  20. package/dist/core/ReplicatedPeerStreamReader.d.ts.map +1 -0
  21. package/dist/core/ReplicatedPeerStreamReader.js +274 -0
  22. package/dist/core/ReplicatedPeerStreamReader.js.map +1 -0
  23. package/dist/core/ReplicatedRecordEmitter.d.ts +102 -0
  24. package/dist/core/ReplicatedRecordEmitter.d.ts.map +1 -0
  25. package/dist/core/ReplicatedRecordEmitter.js +122 -0
  26. package/dist/core/ReplicatedRecordEmitter.js.map +1 -0
  27. package/dist/core/ws2SendWiring.d.ts +52 -0
  28. package/dist/core/ws2SendWiring.d.ts.map +1 -0
  29. package/dist/core/ws2SendWiring.js +71 -0
  30. package/dist/core/ws2SendWiring.js.map +1 -0
  31. package/package.json +1 -1
  32. package/src/data/builtin-manifest.json +2 -2
  33. package/upgrades/1.3.570.md +69 -0
  34. package/upgrades/1.3.571.md +64 -0
  35. package/upgrades/side-effects/statesync-peer-advert-propagation-fix.md +125 -0
  36. package/upgrades/side-effects/ws2-send-side-emission.md +40 -0
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAkH7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAg4CD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CA8eN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAihctE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAkH7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAg4CD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CA8eN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA4mctE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
@@ -3423,6 +3423,69 @@ export async function startServer(options) {
3423
3423
  // answer to "who is my verified operator?" (only the local authenticated setOperator binds it).
3424
3424
  const { TOPIC_OPERATOR_KIND_REGISTRATION } = await import('../core/TopicOperatorReplicatedStore.js');
3425
3425
  replicatedKindRegistry.register(TOPIC_OPERATOR_KIND_REGISTRATION);
3426
+ // ── WS2 SEND-SIDE wiring (docs/specs/WS2-SEND-SIDE-EMISSION-SPEC.md). The
3427
+ // substrate above ships the registry + receive/serve machinery + advert; THIS
3428
+ // wires the SEND half that was deferred ("the journal-backed emitter is attached
3429
+ // in a later rollout stage"). Three pieces, all KIND-AGNOSTIC: (1) inject the
3430
+ // now-populated registry into the journal writer + the applier so a registered
3431
+ // `*-record` kind validates/accepts on BOTH ends (without it a peer's record
3432
+ // suspect-flags the stream — the receive-only gap); (2) the peer-stream reader
3433
+ // that materializes own + peer journal streams into the union's per-origin
3434
+ // records (makes a received record READABLE) + the snapshot loadOwnEntries
3435
+ // source + the emitter's `observed`-witness source; (3) the author-side HLC
3436
+ // clock. All require the coherence journal (the emit sink + the streams to read);
3437
+ // when it is dark these stay undefined and every consumer degrades to its
3438
+ // existing single-machine no-op. The generic record emitter is constructed below
3439
+ // (it also needs the resolved stateSync flags).
3440
+ let replicatedPeerStreamReader;
3441
+ let replicatedRecordEmitter;
3442
+ let replicatedHlcClock;
3443
+ if (coherenceJournal && cjOwnMachineId) {
3444
+ try {
3445
+ coherenceJournal.setReplicatedKindRegistry(replicatedKindRegistry);
3446
+ journalSyncApplier?.setReplicatedKindRegistry(replicatedKindRegistry);
3447
+ const { ReplicatedPeerStreamReader } = await import('../core/ReplicatedPeerStreamReader.js');
3448
+ replicatedPeerStreamReader = new ReplicatedPeerStreamReader({
3449
+ stateDir: config.stateDir,
3450
+ registry: replicatedKindRegistry,
3451
+ selfMachineId: cjOwnMachineId,
3452
+ });
3453
+ // Author-side HLC clock — persisted under the journal dir (atomic temp+rename)
3454
+ // so the merge total order survives restarts (§3.5). node = this machine's id.
3455
+ const { HybridLogicalClock } = await import('../core/HybridLogicalClock.js');
3456
+ const hlcSafeId = cjOwnMachineId.replace(/[^A-Za-z0-9_.-]/g, '_');
3457
+ const hlcPath = path.join(config.stateDir, 'state', 'coherence-journal', `hlc-${hlcSafeId}.json`);
3458
+ replicatedHlcClock = new HybridLogicalClock({
3459
+ node: cjOwnMachineId,
3460
+ now: () => Date.now(),
3461
+ persist: {
3462
+ load: () => {
3463
+ try {
3464
+ const o = JSON.parse(fs.readFileSync(hlcPath, 'utf-8'));
3465
+ return o && typeof o.physical === 'number' ? o : null;
3466
+ }
3467
+ catch { /* @silent-fallback-ok: absent/corrupt hlc file = fresh clock (first boot) — the clock starts at 0, never throws. */
3468
+ return null;
3469
+ }
3470
+ },
3471
+ save: (t) => {
3472
+ try {
3473
+ fs.mkdirSync(path.dirname(hlcPath), { recursive: true });
3474
+ const tmp = `${hlcPath}.tmp-${process.pid}`;
3475
+ fs.writeFileSync(tmp, JSON.stringify(t));
3476
+ fs.renameSync(tmp, hlcPath);
3477
+ }
3478
+ catch { /* @silent-fallback-ok: a failed hlc persist degrades to in-memory-only (the clock still advances this run); replication merge order is best-effort durable, never a boot blocker. */ }
3479
+ },
3480
+ },
3481
+ });
3482
+ }
3483
+ catch (e) { /* @silent-fallback-ok: the SEND-side wiring must never endanger boot — a failure leaves replication dark (undefined seams), exactly the pre-WS2-send behavior. */
3484
+ replicatedPeerStreamReader = undefined;
3485
+ replicatedHlcClock = undefined;
3486
+ console.log(pc.dim(` [ws2-send] emitter wiring skipped: ${e instanceof Error ? e.message : String(e)}`));
3487
+ }
3488
+ }
3426
3489
  // Snapshot-then-tail engine (Component 4 / build-order step 3,
3427
3490
  // multi-machine-replicated-store-foundation §6). The cache (FIXED ceiling,
3428
3491
  // §8.2 — NOT pool-scaled), the per-peer rebuild breaker (§6.3), and the engine
@@ -3443,10 +3506,12 @@ export async function startServer(options) {
3443
3506
  cache: snapshotCache,
3444
3507
  breaker: snapshotRebuildBreaker,
3445
3508
  seams: {
3446
- // Step-3 substrate: no concrete store kind is registered yet, so there are
3447
- // no contributing own-streams to load. A consumer PR (WS2.1) replaces this
3448
- // with a loader that reads the CoherenceJournal own streams for its kind(s).
3449
- loadOwnEntries: () => ({}),
3509
+ // WS2 send-side: read the OWN journal streams for the store's registered
3510
+ // kind(s) so a snapshot serve returns real entries (replaces the no-op stub).
3511
+ // Single-origin is enforced inside the reader (it serves only this machine's
3512
+ // own stream). When the journal is dark the reader is undefined ⇒ `{}` (the
3513
+ // correct no-store no-op; serveSnapshot then answers 'no-entries').
3514
+ loadOwnEntries: (store, origin) => replicatedPeerStreamReader ? replicatedPeerStreamReader.loadOwnEntries(store, origin) : {},
3450
3515
  now: () => Date.now(),
3451
3516
  },
3452
3517
  maxSnapshotBytes: stateSync.maxCacheBytes,
@@ -3461,6 +3526,26 @@ export async function startServer(options) {
3461
3526
  // `enabled === true` semantics but now see a live flag on a dev agent. An explicit
3462
3527
  // operator `enabled` in config still wins (force-dark false / fleet-flip true).
3463
3528
  const _stateSyncStoresResolved = resolveStateSyncStores(config);
3529
+ // WS2 send-side: the generic journal-backed record emitter (the concrete emitter
3530
+ // the per-store managers' emit hooks call). Needs the journal sink, the HLC clock,
3531
+ // the registry (store → kind), this machine's origin id, the resolved stateSync
3532
+ // flags (the dark gate — read via a getter so a live flip is honored), and the
3533
+ // peer-stream reader as the `observed`-witness source. Constructed only when the
3534
+ // journal + clock + reader exist (all gated on the coherence journal being live);
3535
+ // otherwise it stays undefined and every per-store emit adapter is simply never
3536
+ // attached (the managers' hooks stay no-ops, the pre-WS2-send behavior).
3537
+ if (coherenceJournal && replicatedHlcClock && replicatedPeerStreamReader) {
3538
+ const { ReplicatedRecordEmitter } = await import('../core/ReplicatedRecordEmitter.js');
3539
+ replicatedRecordEmitter = new ReplicatedRecordEmitter({
3540
+ journal: coherenceJournal,
3541
+ clock: replicatedHlcClock,
3542
+ registry: replicatedKindRegistry,
3543
+ origin: cjOwnMachineId,
3544
+ stores: () => _stateSyncStoresResolved,
3545
+ loadWitness: (store, recordKey) => replicatedPeerStreamReader.loadWitness(store, recordKey),
3546
+ log: (event, detail) => console.log(pc.dim(` [ws2-send] ${event} ${JSON.stringify(detail)}`)),
3547
+ });
3548
+ }
3464
3549
  const selfStateSyncReceive = () => {
3465
3550
  const out = {};
3466
3551
  const stores = _stateSyncStoresResolved;
@@ -7843,37 +7928,40 @@ export async function startServer(options) {
7843
7928
  // and-flag never silently clobbers two divergent lessons; the READ layer is advisory,
7844
7929
  // injecting both variants as hints rather than blocking — fork #2). Consulted by a
7845
7930
  // learnings peer-read surface ONLY when stateSync.learnings.enabled is true.
7846
- const { learningTierOf, learningToOriginRecord, deriveLearningRecordKey, LEARNING_STORE_KEY } = await import('../core/LearningsReplicatedStore.js');
7931
+ const { learningTierOf, deriveLearningRecordKey, buildLearningRecordData, buildLearningTombstoneData, LEARNING_STORE_KEY, } = await import('../core/LearningsReplicatedStore.js');
7847
7932
  const learningsUnionReader = new ReplicatedStoreReader({
7848
7933
  registry: replicatedKindRegistry,
7849
7934
  stores: _stateSyncStoresResolved, // gate-resolved (dev-live / fleet-dark) per operator directive 2026-06-13
7850
7935
  tierOf: learningTierOf,
7851
- loadOriginRecords: (store, recordKey) => {
7852
- if (store !== LEARNING_STORE_KEY || _meshSelfId === null)
7853
- return [];
7854
- for (const l of evolution.listLearnings()) {
7855
- if (deriveLearningRecordKey(l.title, l.category, l.source) === recordKey) {
7856
- const o = learningToOriginRecord(l, _meshSelfId);
7857
- return o ? [o] : [];
7858
- }
7859
- }
7860
- return [];
7861
- },
7862
- listRecordKeys: (store) => {
7863
- if (store !== LEARNING_STORE_KEY)
7864
- return [];
7865
- const keys = [];
7866
- for (const l of evolution.listLearnings()) {
7867
- const k = deriveLearningRecordKey(l.title, l.category, l.source);
7868
- if (k !== null)
7869
- keys.push(k);
7870
- }
7871
- return keys;
7872
- },
7936
+ // WS2 send-side: the union now reads OWN + PEER journal streams (each record's
7937
+ // authoritative emit-time HLC) via the peer-stream reader — so a learning
7938
+ // replicated FROM a peer is READABLE here, not just the local one. Dark / no
7939
+ // journal reader undefined ⇒ [] (isLive already gates a disabled store to a
7940
+ // strict no-op, so this never changes single-machine behavior).
7941
+ loadOriginRecords: (store, recordKey) => store === LEARNING_STORE_KEY && replicatedPeerStreamReader
7942
+ ? replicatedPeerStreamReader.loadOriginRecords(store, recordKey)
7943
+ : [],
7944
+ listRecordKeys: (store) => store === LEARNING_STORE_KEY && replicatedPeerStreamReader
7945
+ ? replicatedPeerStreamReader.listRecordKeys(store)
7946
+ : [],
7873
7947
  droppedOrigins: droppedOriginRegistry,
7874
7948
  conflictStore,
7875
7949
  });
7876
- void learningsUnionReader; // consumed by the learnings peer-read surface + the journal-apply rollout stage (WS2.2+)
7950
+ void learningsUnionReader; // consumed by the learnings peer-read surface (the E2E reads through it) + future session-context injection
7951
+ // WS2 SEND-SIDE: attach the journal-backed emitter to the EvolutionManager's
7952
+ // learning hooks. The call sites already fire emitPut/emitDelete on every
7953
+ // saveLearnings (prune→emitDelete, survivor→emitPut); the adapter maps the
7954
+ // manager's emit signature to the store's build*RecordData. The emitter owns the
7955
+ // dark gate, HLC tick, `observed` witness, and journal append. Attached only when
7956
+ // the emitter exists (journal live); when dark the hooks stay no-ops (byte-
7957
+ // identical single-machine behavior — the local LRN-NNN id never crosses the wire).
7958
+ if (replicatedRecordEmitter) {
7959
+ const emitter = replicatedRecordEmitter;
7960
+ evolution.setLearningReplicationEmitter({
7961
+ emitPut: (rec) => emitter.emit(LEARNING_STORE_KEY, deriveLearningRecordKey(rec.title, rec.category, rec.source), (hlc, origin, observed) => buildLearningRecordData({ record: rec, hlc, origin, observed })),
7962
+ emitDelete: (title, category, source, deletedAt) => emitter.emit(LEARNING_STORE_KEY, deriveLearningRecordKey(title, category, source), (hlc, origin, observed) => buildLearningTombstoneData({ title, category, source, hlc, origin, deletedAt, observed })),
7963
+ });
7964
+ }
7877
7965
  // WS2.4 — the bypass-proof union reader for the `knowledge` store + the emit seam on
7878
7966
  // the KnowledgeManager. The KnowledgeManager reads the local catalog.json (cheap, no
7879
7967
  // background work); we construct one here scoped to the replication wiring. The union
@@ -15405,31 +15493,17 @@ export async function startServer(options) {
15405
15493
  fetchPeerCapacity: async (machineId, url) => {
15406
15494
  const res = await meshClient.send({ machineId, url }, { type: 'session-status' }, 0);
15407
15495
  if (res.ok && res.result && typeof res.result === 'object') {
15408
- const cap = res.result;
15409
- const journalAdvert = _unwrapPeerJournalAdvert(machineId, cap.journalAdvert);
15410
- // #930 sibling (live, v1.3.369): the commitments advert was
15411
- // parsed AWAY here — served by the peer, dropped by this
15412
- // narrowing return so driveCommitmentsSync never fired and
15413
- // zero replicas ever landed. Pass it through.
15414
- // A2 (live, v1.3.384): the SAME narrowing also dropped the
15415
- // peer's quotaState (its getCapacity(self) includes it), so the
15416
- // router only ever saw its OWN quota and quota-aware placement
15417
- // (#804) could never avoid a rate-limited PEER — the original
15418
- // EXO failure. Pass it through too. Absent = not blocked.
15419
- return {
15420
- selfReportedLastSeen: cap.selfReportedLastSeen,
15421
- loadAvg: cap.loadAvg,
15422
- journalAdvert,
15423
- ...(cap.commitmentsAdvert ? { commitmentsAdvert: cap.commitmentsAdvert } : {}),
15424
- // §WS2.1 sibling pass-through (the #930/A2 narrowing lesson): the
15425
- // peer's preferences advert is served but would be dropped here
15426
- // without this, so drivePreferencesSync would never fire.
15427
- ...(cap.preferencesAdvert ? { preferencesAdvert: cap.preferencesAdvert } : {}),
15428
- ...(cap.quotaState ? { quotaState: cap.quotaState } : {}),
15429
- // Guard posture rides the same pass-through (the A2 narrowing
15430
- // lesson): dropping it here would blind the pool to peers' posture.
15431
- ...(cap.guardPosture ? { guardPosture: cap.guardPosture } : {}),
15432
- };
15496
+ // The journal advert is the one slice that needs closure context
15497
+ // (the machine-id-keyed unwrap), so it is computed HERE; the rest
15498
+ // of the narrowing commitmentsAdvert (#930), quotaState (A2/#804),
15499
+ // preferencesAdvert (WS2.1), guardPosture, and seamlessnessFlags
15500
+ // (THIS fix, the 4th instance of the narrowing-return-forgets-a-field
15501
+ // class) is the SINGLE shared `narrowSessionStatusToPeerCapacity`
15502
+ // pass-through that the peer-presence round-trip test also runs, so
15503
+ // the test proves the REAL mapping and a forgotten field can't recur
15504
+ // silently (the wiring-integrity ratchet asserts over this helper).
15505
+ const journalAdvert = _unwrapPeerJournalAdvert(machineId, res.result.journalAdvert);
15506
+ return presenceMod.narrowSessionStatusToPeerCapacity(res.result, journalAdvert);
15433
15507
  }
15434
15508
  return null;
15435
15509
  },