instar 1.3.339 → 1.3.340
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 +166 -10
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +17 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/AutonomousSessions.d.ts +17 -2
- package/dist/core/AutonomousSessions.d.ts.map +1 -1
- package/dist/core/AutonomousSessions.js +30 -4
- package/dist/core/AutonomousSessions.js.map +1 -1
- package/dist/core/CoherenceJournal.d.ts +326 -0
- package/dist/core/CoherenceJournal.d.ts.map +1 -0
- package/dist/core/CoherenceJournal.js +999 -0
- package/dist/core/CoherenceJournal.js.map +1 -0
- package/dist/core/CoherenceJournalReader.d.ts +158 -0
- package/dist/core/CoherenceJournalReader.d.ts.map +1 -0
- package/dist/core/CoherenceJournalReader.js +450 -0
- package/dist/core/CoherenceJournalReader.js.map +1 -0
- package/dist/core/JournalSyncApplier.d.ts +272 -0
- package/dist/core/JournalSyncApplier.d.ts.map +1 -0
- package/dist/core/JournalSyncApplier.js +835 -0
- package/dist/core/JournalSyncApplier.js.map +1 -0
- package/dist/core/StateManager.d.ts +41 -0
- package/dist/core/StateManager.d.ts.map +1 -1
- package/dist/core/StateManager.js +100 -0
- package/dist/core/StateManager.js.map +1 -1
- package/dist/core/types.d.ts +35 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/messaging/TelegramAdapter.d.ts +14 -0
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +16 -1
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +7 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +54 -3
- package/dist/server/routes.js.map +1 -1
- package/package.json +2 -2
- package/scripts/lint-cas-emit-placement.js +87 -0
- package/scripts/lint-journal-actuation-ban.js +63 -0
- package/scripts/lint-state-registry.js +313 -0
- package/src/data/builtin-manifest.json +46 -46
- package/src/data/state-coherence-registry.json +880 -0
- package/src/scaffold/templates.ts +7 -0
- package/upgrades/1.3.340.md +132 -0
- package/upgrades/coherence-journal-p1-1.eli16.md +11 -0
- package/upgrades/coherence-journal-p1-2.eli16.md +11 -0
- package/upgrades/coherence-journal-p1-3.eli16.md +13 -0
- package/upgrades/side-effects/coherence-journal-p1-1.md +68 -0
- package/upgrades/side-effects/coherence-journal-p1-2.md +54 -0
- package/upgrades/side-effects/coherence-journal-p1-3.md +49 -0
- package/upgrades/1.3.339.md +0 -48
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA6SH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAuxDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA6SH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAuxDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA65RtE;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"}
|
package/dist/commands/server.js
CHANGED
|
@@ -2304,6 +2304,94 @@ export async function startServer(options) {
|
|
|
2304
2304
|
console.log(pc.yellow(' Standby mode — processing gated, writes disabled'));
|
|
2305
2305
|
}
|
|
2306
2306
|
}
|
|
2307
|
+
// ── Coherence Journal (COHERENCE-JOURNAL-SPEC §3.1/§3.3, P1.1) ─────────
|
|
2308
|
+
// Per-machine append-only event streams (topic-placement / session-
|
|
2309
|
+
// lifecycle / autonomous-run). Dark-ship: `enabled ?? !!developmentAgent`.
|
|
2310
|
+
// Constructed HERE — after the coordinator (machineId available), before
|
|
2311
|
+
// the reaper/ownership wiring that emits into it. Writer runs even on a
|
|
2312
|
+
// single-machine agent (locally useful); replication is P1.3.
|
|
2313
|
+
let coherenceJournal;
|
|
2314
|
+
{
|
|
2315
|
+
const cjCfg = config.multiMachine?.coherenceJournal;
|
|
2316
|
+
const cjEnabled = cjCfg?.enabled ?? !!config.developmentAgent;
|
|
2317
|
+
if (cjEnabled) {
|
|
2318
|
+
try {
|
|
2319
|
+
const cjMod = await import('../core/CoherenceJournal.js');
|
|
2320
|
+
// Stable machine id: mesh identity when present; deterministic
|
|
2321
|
+
// hostname-derived fallback for single-machine agents (sanitized by
|
|
2322
|
+
// the journal's own percent-encode rule either way).
|
|
2323
|
+
const cjMachineId = coordinator.identity?.machineId ?? `m_host_${os.hostname()}`;
|
|
2324
|
+
coherenceJournal = new cjMod.CoherenceJournal({
|
|
2325
|
+
stateDir: config.stateDir,
|
|
2326
|
+
machineId: cjMachineId,
|
|
2327
|
+
flushIntervalMs: cjCfg?.flushIntervalMs,
|
|
2328
|
+
retention: cjCfg?.retention,
|
|
2329
|
+
// The §3.1 standby-safe seam: the flusher asks StateManager before
|
|
2330
|
+
// each append batch; the prefix allowlist lives there.
|
|
2331
|
+
guardWrite: (p) => state.guardJournalWrite(p),
|
|
2332
|
+
logger: (m) => console.log(pc.dim(` [coherence-journal] ${m}`)),
|
|
2333
|
+
});
|
|
2334
|
+
coherenceJournal.open();
|
|
2335
|
+
// Lifecycle funnel (§3.3): every session status transition flows
|
|
2336
|
+
// through StateManager.saveSession; the diff-derived emit lives there.
|
|
2337
|
+
state.setCoherenceJournal(coherenceJournal);
|
|
2338
|
+
// §3.3 autonomous-run journal scanner — observation-based start/stop
|
|
2339
|
+
// (no single .local.md write funnel exists; polling is the structural
|
|
2340
|
+
// choice). P19 brakes, declared: constant per-tick cost (bounded by
|
|
2341
|
+
// maxConcurrent active runs); a throwing read skips the tick and
|
|
2342
|
+
// never compounds; the seen-set evicts a run after its stopped emit
|
|
2343
|
+
// (bounded by active runs, not history); emits inherit the writer's
|
|
2344
|
+
// rate cap. Sub-scan-interval runs are not observed (stated spec
|
|
2345
|
+
// limitation). Op-key dedupe collapses scanner + stop-funnel emits.
|
|
2346
|
+
{
|
|
2347
|
+
const journal = coherenceJournal;
|
|
2348
|
+
const cjScannerMs = cjCfg?.scannerIntervalMs ?? 60000;
|
|
2349
|
+
const seenRuns = new Map();
|
|
2350
|
+
const cjScan = async () => {
|
|
2351
|
+
try {
|
|
2352
|
+
const { activeAutonomousJobs, autonomousRunId } = await import('../core/AutonomousSessions.js');
|
|
2353
|
+
const active = activeAutonomousJobs(config.stateDir);
|
|
2354
|
+
const liveTopics = new Set();
|
|
2355
|
+
for (const j of active) {
|
|
2356
|
+
if (j.topic == null)
|
|
2357
|
+
continue; // legacy single-file job: not topic-scoped
|
|
2358
|
+
liveTopics.add(j.topic);
|
|
2359
|
+
const topicNum = Number(j.topic);
|
|
2360
|
+
if (!Number.isFinite(topicNum))
|
|
2361
|
+
continue;
|
|
2362
|
+
if (!seenRuns.has(j.topic)) {
|
|
2363
|
+
const runId = autonomousRunId(j.startedAt, j.topic);
|
|
2364
|
+
seenRuns.set(j.topic, { runId, file: j.file });
|
|
2365
|
+
journal.emitAutonomousRun(topicNum, { action: 'started', runId, artifactPaths: [j.file] });
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
for (const [topicKey, run] of [...seenRuns]) {
|
|
2369
|
+
if (liveTopics.has(topicKey))
|
|
2370
|
+
continue;
|
|
2371
|
+
const topicNum = Number(topicKey);
|
|
2372
|
+
if (Number.isFinite(topicNum)) {
|
|
2373
|
+
// observed-stopped: covers deaths outside the stop funnels
|
|
2374
|
+
// (crash / reboot / reaper kill) — no phantom-live runs.
|
|
2375
|
+
journal.emitAutonomousRun(topicNum, { action: 'stopped', runId: run.runId, artifactPaths: [run.file] });
|
|
2376
|
+
}
|
|
2377
|
+
seenRuns.delete(topicKey);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
catch { /* @silent-fallback-ok: journal observability must never endanger the observed operation (COHERENCE-JOURNAL-SPEC §3.1) */ /* skip-tick; never compounds (P19) */ }
|
|
2381
|
+
};
|
|
2382
|
+
const cjScanTimer = setInterval(() => { void cjScan(); }, cjScannerMs);
|
|
2383
|
+
cjScanTimer.unref?.();
|
|
2384
|
+
void cjScan(); // prime at boot (re-emits dedupe via op keys)
|
|
2385
|
+
}
|
|
2386
|
+
console.log(pc.dim(` Coherence journal: writer active (${cjMachineId.slice(0, 16)}…)`));
|
|
2387
|
+
}
|
|
2388
|
+
catch (err) { /* @silent-fallback-ok: journal observability must never endanger the observed operation (COHERENCE-JOURNAL-SPEC §3.1) */
|
|
2389
|
+
// The journal must never endanger boot (§3.1 inverted at startup).
|
|
2390
|
+
console.warn(pc.yellow(` Coherence journal failed to start (continuing without): ${err instanceof Error ? err.message : String(err)}`));
|
|
2391
|
+
coherenceJournal = undefined;
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2307
2395
|
// Cross-Machine Seamlessness (spec §9) — resolve + validate the tunable
|
|
2308
2396
|
// knobs at startup. A violating config (e.g. a widened ingressHeartbeatMs
|
|
2309
2397
|
// that breaks the RPO bound) is REJECTED here with a clear message rather
|
|
@@ -3577,6 +3665,16 @@ export async function startServer(options) {
|
|
|
3577
3665
|
console.error(`[server] Failed to ensure Agent Updates topic: ${err}`);
|
|
3578
3666
|
});
|
|
3579
3667
|
}
|
|
3668
|
+
// ── Coherence Journal × Telegram emergency-stop seam (COHERENCE-JOURNAL §3.3)
|
|
3669
|
+
// The adapter's sentinel emergency-stop path clears a topic's autonomous job
|
|
3670
|
+
// via stopAutonomousTopic but holds no StateManager, so it cannot reach the
|
|
3671
|
+
// wired journal on its own. Inject the journal as the AutonomousJournalSeam so
|
|
3672
|
+
// a sentinel-driven stop emits the autonomous-run `stopped` event like every
|
|
3673
|
+
// other stop funnel. Placed AFTER both adapter-setup blocks (so `telegram` is
|
|
3674
|
+
// assigned in either mode) and only when the journal is wired.
|
|
3675
|
+
if (telegram && coherenceJournal) {
|
|
3676
|
+
telegram.setCoherenceJournalSeam(coherenceJournal);
|
|
3677
|
+
}
|
|
3580
3678
|
// Agent worktree convention (Layer 4) — lifeline detector.
|
|
3581
3679
|
//
|
|
3582
3680
|
// Runs once per server boot. Inspects the canonical instar repo's
|
|
@@ -4301,6 +4399,19 @@ export async function startServer(options) {
|
|
|
4301
4399
|
origin: e.origin,
|
|
4302
4400
|
});
|
|
4303
4401
|
reapNotifier.onReaped({ session: e.session, reason: e.reason, disposition: e.disposition, origin: e.origin });
|
|
4402
|
+
// Coherence journal 'reaped' (§3.3): emitted HERE, alongside the
|
|
4403
|
+
// reap-log append it references — never derived in the saveSession
|
|
4404
|
+
// funnel (which records the plain killed/completed transition).
|
|
4405
|
+
try {
|
|
4406
|
+
const m = /(?:^|[-_])(?:topic|telegram)[-_]?(\d+)(?:$|[-_])/.exec(e.session.name ?? '');
|
|
4407
|
+
coherenceJournal?.emitLifecycle({
|
|
4408
|
+
sessionId: e.session.id,
|
|
4409
|
+
status: 'reaped',
|
|
4410
|
+
reapReason: e.reason,
|
|
4411
|
+
reapLogRef: `logs/reap-log.jsonl:${e.session.name}`,
|
|
4412
|
+
}, m ? Number(m[1]) : undefined);
|
|
4413
|
+
}
|
|
4414
|
+
catch { /* observability never endangers the observed */ }
|
|
4304
4415
|
});
|
|
4305
4416
|
sessionManager.on('reapBlocked', (e) => {
|
|
4306
4417
|
reapLog.recordSkipped({
|
|
@@ -9307,16 +9418,55 @@ export async function startServer(options) {
|
|
|
9307
9418
|
logger: (m) => console.log(pc.dim(` ${m}`)),
|
|
9308
9419
|
});
|
|
9309
9420
|
const ownReg = sessionOwnershipRegistry;
|
|
9421
|
+
// Coherence journal §3.3: the emit is a thin wrapper at every CAS call
|
|
9422
|
+
// site — `reason` is caller knowledge (cas() is a storage primitive and
|
|
9423
|
+
// cannot know WHY). A mesh-applied action records the coarse reason;
|
|
9424
|
+
// the ORIGINATING machine's own emit carries the precise one. The
|
|
9425
|
+
// cas-pairing lint (scripts/lint-cas-emit-placement.js) fails CI on
|
|
9426
|
+
// any cas( call site missing this pairing.
|
|
9427
|
+
const emitPlacement = (sessionKey, r, reason, prevOwner) => {
|
|
9428
|
+
try {
|
|
9429
|
+
if (!coherenceJournal || !r?.ok)
|
|
9430
|
+
return;
|
|
9431
|
+
const rec = r.record;
|
|
9432
|
+
const topicNum = Number(sessionKey);
|
|
9433
|
+
if (!Number.isFinite(topicNum) || rec?.ownershipEpoch == null)
|
|
9434
|
+
return;
|
|
9435
|
+
coherenceJournal.emitPlacement(topicNum, {
|
|
9436
|
+
owner: rec.ownerMachineId ?? '',
|
|
9437
|
+
...(prevOwner ? { prevOwner } : {}),
|
|
9438
|
+
epoch: rec.ownershipEpoch,
|
|
9439
|
+
reason,
|
|
9440
|
+
});
|
|
9441
|
+
}
|
|
9442
|
+
catch { /* observability never endangers the observed */ }
|
|
9443
|
+
};
|
|
9310
9444
|
// The §L3 ownership commands, routed from MeshRpc to the registry CAS.
|
|
9311
9445
|
const ownAction = (cmd, sender, env) => {
|
|
9312
|
-
if (cmd.type === 'place')
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
if (cmd.type === '
|
|
9319
|
-
|
|
9446
|
+
if (cmd.type === 'place') {
|
|
9447
|
+
const prev = ownReg.read(cmd.session)?.ownerMachineId;
|
|
9448
|
+
const r = ownReg.cas({ type: 'place', machineId: cmd.machine }, { sessionKey: cmd.session, sender, nonce: env.nonce });
|
|
9449
|
+
emitPlacement(cmd.session, r, 'placed', prev);
|
|
9450
|
+
return r;
|
|
9451
|
+
}
|
|
9452
|
+
if (cmd.type === 'claim') {
|
|
9453
|
+
const prev = ownReg.read(cmd.session)?.ownerMachineId;
|
|
9454
|
+
const r = ownReg.cas({ type: 'claim', machineId: sender }, { sessionKey: cmd.session, sender, nonce: env.nonce });
|
|
9455
|
+
emitPlacement(cmd.session, r, cmd.failover ? 'failover' : 'placed', prev);
|
|
9456
|
+
return r;
|
|
9457
|
+
}
|
|
9458
|
+
if (cmd.type === 'transfer') {
|
|
9459
|
+
const prev = ownReg.read(cmd.session)?.ownerMachineId;
|
|
9460
|
+
const r = ownReg.cas({ type: 'transfer', to: cmd.target }, { sessionKey: cmd.session, sender, nonce: env.nonce });
|
|
9461
|
+
emitPlacement(cmd.session, r, 'user-move', prev);
|
|
9462
|
+
return r;
|
|
9463
|
+
}
|
|
9464
|
+
if (cmd.type === 'release') {
|
|
9465
|
+
const prev = ownReg.read(cmd.session)?.ownerMachineId;
|
|
9466
|
+
const r = ownReg.cas({ type: 'release', machineId: sender }, { sessionKey: cmd.session, sender, nonce: env.nonce });
|
|
9467
|
+
emitPlacement(cmd.session, r, 'released', prev);
|
|
9468
|
+
return r;
|
|
9469
|
+
}
|
|
9320
9470
|
return { ok: false, reason: 'unsupported' };
|
|
9321
9471
|
};
|
|
9322
9472
|
// The §L4 owner-side deliverMessage receive handler (shared factory — same
|
|
@@ -9702,7 +9852,9 @@ export async function startServer(options) {
|
|
|
9702
9852
|
markOwnerSuspect: (m) => ownerSuspectBreaker.markSuspect(m),
|
|
9703
9853
|
onOwnerResponsive: (m) => ownerSuspectBreaker.recordSuccess(m),
|
|
9704
9854
|
casClaimOwnership: (sk, machineId) => {
|
|
9855
|
+
const prevOwner = ownReg.read(sk)?.ownerMachineId;
|
|
9705
9856
|
const r = ownReg.cas({ type: 'place', machineId }, { sessionKey: sk, sender: meshSelfId, nonce: `${meshSelfId}:c:${++routerNonce}` });
|
|
9857
|
+
emitPlacement(sk, r, 'placed', prevOwner);
|
|
9706
9858
|
return { ok: r.ok, epoch: ownReg.read(sk)?.ownershipEpoch ?? 0 };
|
|
9707
9859
|
},
|
|
9708
9860
|
// bug #11: confirm the remote owner (placing → active) after the spawn is
|
|
@@ -9711,7 +9863,9 @@ export async function startServer(options) {
|
|
|
9711
9863
|
// owner, so the router confirms on the target's behalf. Without this the
|
|
9712
9864
|
// record stays 'placing' and every later message for the session queues.
|
|
9713
9865
|
confirmClaim: (sk, machineId) => {
|
|
9714
|
-
ownReg.
|
|
9866
|
+
const prevOwner = ownReg.read(sk)?.ownerMachineId;
|
|
9867
|
+
const r = ownReg.cas({ type: 'claim', machineId }, { sessionKey: sk, sender: meshSelfId, nonce: `${meshSelfId}:cl:${++routerNonce}` });
|
|
9868
|
+
emitPlacement(sk, r, 'placed', prevOwner); // placing→active confirmation: a real epoch bump in the registry
|
|
9715
9869
|
},
|
|
9716
9870
|
deliverMessage: async (target, env) => {
|
|
9717
9871
|
const url = peerUrl(target);
|
|
@@ -9879,7 +10033,9 @@ export async function startServer(options) {
|
|
|
9879
10033
|
// If THIS machine actively owns the topic, release so the next message re-places to the pin.
|
|
9880
10034
|
try {
|
|
9881
10035
|
if (ownReg.ownerOf(sessionKey) === meshSelfId) {
|
|
9882
|
-
|
|
10036
|
+
const prevOwner = ownReg.read(sessionKey)?.ownerMachineId;
|
|
10037
|
+
const r = ownReg.cas({ type: 'release', machineId: meshSelfId }, { sessionKey, sender: meshSelfId, nonce: `${meshSelfId}:rel:${sessionKey}:${Math.round(performance.now())}` });
|
|
10038
|
+
emitPlacement(sessionKey, r, 'user-move', prevOwner); // the explicit move's release half
|
|
9883
10039
|
}
|
|
9884
10040
|
}
|
|
9885
10041
|
catch { /* best-effort; route() re-places regardless once the owner is cleared */ }
|