instar 1.3.587 → 1.3.588
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 +144 -7
- package/dist/commands/server.js.map +1 -1
- package/dist/core/WorkEvidence.d.ts +17 -0
- package/dist/core/WorkEvidence.d.ts.map +1 -1
- package/dist/core/WorkEvidence.js +17 -0
- package/dist/core/WorkEvidence.js.map +1 -1
- package/dist/core/gapBCommitmentEvidence.d.ts +143 -0
- package/dist/core/gapBCommitmentEvidence.d.ts.map +1 -0
- package/dist/core/gapBCommitmentEvidence.js +138 -0
- package/dist/core/gapBCommitmentEvidence.js.map +1 -0
- package/dist/core/types.d.ts +21 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/CommitmentTracker.d.ts +12 -0
- package/dist/monitoring/CommitmentTracker.d.ts.map +1 -1
- package/dist/monitoring/CommitmentTracker.js +14 -0
- package/dist/monitoring/CommitmentTracker.js.map +1 -1
- package/dist/monitoring/ReapLog.d.ts +11 -0
- package/dist/monitoring/ReapLog.d.ts.map +1 -1
- package/dist/monitoring/ReapLog.js +1 -0
- package/dist/monitoring/ReapLog.js.map +1 -1
- package/dist/monitoring/ResumeQueueDrainer.d.ts +15 -0
- package/dist/monitoring/ResumeQueueDrainer.d.ts.map +1 -1
- package/dist/monitoring/ResumeQueueDrainer.js +12 -1
- package/dist/monitoring/ResumeQueueDrainer.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.588.md +27 -0
- package/upgrades/side-effects/autonomous-registration-guarantee.md +51 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAmCH,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,CAshdtE;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
|
@@ -24,7 +24,8 @@ import { CodexResumeMap } from '../core/CodexResumeMap.js';
|
|
|
24
24
|
import { paneIdleWithEmptyInput } from '../core/ModelSwapService.js';
|
|
25
25
|
import { escalatedModelIds, normalizeTierEscalationConfig } from '../core/ModelTierEscalation.js';
|
|
26
26
|
import { activeAutonomousJobs, autonomousRunRemainingForTopic } from '../core/AutonomousSessions.js';
|
|
27
|
-
import { AGE_LIMIT_ACTIVE_RUN_REASON } from '../core/WorkEvidence.js';
|
|
27
|
+
import { AGE_LIMIT_ACTIVE_RUN_REASON, COMMITMENT_ACTIVE_RUN_REASON } from '../core/WorkEvidence.js';
|
|
28
|
+
import { gapBEligibleForTopic, recentUserMessageFromHistory, resolveGapBInjectionGate, decideGapBInjection } from '../core/gapBCommitmentEvidence.js';
|
|
28
29
|
import { TopicProfileTransferCarrier, createTopicProfilePullHandler } from '../core/TopicProfileTransferCarrier.js';
|
|
29
30
|
import { closeAllSqlite } from '../core/SqliteRegistry.js';
|
|
30
31
|
import { SessionManager } from '../core/SessionManager.js';
|
|
@@ -6377,6 +6378,56 @@ export async function startServer(options) {
|
|
|
6377
6378
|
const rqCfg = config.monitoring?.resumeQueue ?? {};
|
|
6378
6379
|
let resumeQueue = null;
|
|
6379
6380
|
let resumeDrainer = null;
|
|
6381
|
+
// GAP-B Part A surface (spec: autonomous-registration-guarantee.md) — the
|
|
6382
|
+
// aggregated-attention chokepoint reference, hoisted to the handler's scope.
|
|
6383
|
+
// Assigned inside the `if (rqCfg.enabled)` block (where raiseResumeAggregated
|
|
6384
|
+
// is built); null otherwise ⇒ Part A is a silent no-op (never blocks).
|
|
6385
|
+
let gapBRaiseAggregated = null;
|
|
6386
|
+
// GAP-B Part B (the commitment-evidence backstop, ships DARK). The gate is
|
|
6387
|
+
// armed ONLY by an explicit `monitoring.resumeQueue.commitmentEvidence.enabled
|
|
6388
|
+
// === true` (omitted ⇒ OFF on fleet AND dev — the containment: no injection ⇒
|
|
6389
|
+
// no revival ⇒ the 2026-06-13 loop is structurally impossible while dark).
|
|
6390
|
+
const gapBCfg = rqCfg.commitmentEvidence ?? {};
|
|
6391
|
+
const gapBGate = resolveGapBInjectionGate(gapBCfg);
|
|
6392
|
+
const gapBFreshWindowMs = gapBCfg.freshCommitmentWindowMs ?? 6 * 60 * 60_000; // D1: 6h
|
|
6393
|
+
// D8 window — reuse SessionReaper's staleCommitmentWindowMinutes (480/8h,
|
|
6394
|
+
// ConfigDefaults) so the GAP-B recent-user-message corroboration uses the SAME
|
|
6395
|
+
// horizon as ReapGuard's commitment-coupled KEEP (no new ConfigDefaults entry).
|
|
6396
|
+
const gapBStaleWindowMs = (config.monitoring?.sessionReaper?.staleCommitmentWindowMinutes ?? 480) * 60_000;
|
|
6397
|
+
// Part D (spec: autonomous-registration-guarantee.md) — the SHARED inbound-
|
|
6398
|
+
// user-message recency predicate. ONE definition, used by BOTH the GAP-B
|
|
6399
|
+
// eligibility helper (D8) below AND the ReapGuard/SessionReaper deps further
|
|
6400
|
+
// down, so a KEEP decision and the revival eligibility are computed from the
|
|
6401
|
+
// identical truth and CANNOT disagree (the loop the agreement prevents).
|
|
6402
|
+
// Store: TelegramAdapter.getTopicHistory (sync in-memory LogEntry tail cache —
|
|
6403
|
+
// NOT the Threadline A2A MessageStore). Inbound user = LogEntry.fromUser ===
|
|
6404
|
+
// true; freshness = its `timestamp` within `withinMs`. Fail-open (D7): a throw
|
|
6405
|
+
// ⇒ false (no KEEP, no inject) — never endangers the kill path.
|
|
6406
|
+
const recentUserMessageShared = (topicId, withinMs) => {
|
|
6407
|
+
try {
|
|
6408
|
+
if (!telegram)
|
|
6409
|
+
return false;
|
|
6410
|
+
// 50-entry tail is within getTopicHistory's TAIL_CACHE_LIMIT fast path
|
|
6411
|
+
// (sync cache read, no file scan for live topics).
|
|
6412
|
+
return recentUserMessageFromHistory(telegram.getTopicHistory(topicId, 50), withinMs);
|
|
6413
|
+
}
|
|
6414
|
+
catch {
|
|
6415
|
+
// @silent-fallback-ok — Part D D7: fail toward today's behavior.
|
|
6416
|
+
return false;
|
|
6417
|
+
}
|
|
6418
|
+
};
|
|
6419
|
+
// GAP-B Part B + D9 — the SHARED qualifying-commitment + D8 agreement verdict,
|
|
6420
|
+
// ONE closure used by BOTH the reaped-session injection (enqueue time) AND the
|
|
6421
|
+
// drainer's drain-time re-validation (`commitmentStillActiveForTopic`), so the
|
|
6422
|
+
// two cannot disagree on "what a qualifying open commitment means". Delegates
|
|
6423
|
+
// to the pure, unit-tested predicate; reads THIS machine's local tracker.
|
|
6424
|
+
// Fail-open (D7): handled inside gapBEligibleForTopic (a throw ⇒ false).
|
|
6425
|
+
const gapBCommitmentQualifies = (topicId) => gapBEligibleForTopic(topicId, commitmentTracker, {
|
|
6426
|
+
ownMachineId: cjOwnMachineId,
|
|
6427
|
+
freshCommitmentWindowMs: gapBFreshWindowMs,
|
|
6428
|
+
staleCommitmentWindowMs: gapBStaleWindowMs,
|
|
6429
|
+
recentUserMessage: recentUserMessageShared,
|
|
6430
|
+
});
|
|
6380
6431
|
// Operator-stop record for the drainer's R2.6 validation (in-memory map +
|
|
6381
6432
|
// the durable autonomous-emergency-stop flag file's mtime as global stop).
|
|
6382
6433
|
const operatorStopsByTopic = new Map();
|
|
@@ -6430,6 +6481,10 @@ export async function startServer(options) {
|
|
|
6430
6481
|
}
|
|
6431
6482
|
catch { /* never endanger the caller */ }
|
|
6432
6483
|
};
|
|
6484
|
+
// GAP-B Part A: route the unregistered-autonomous surface through the SAME
|
|
6485
|
+
// aggregated chokepoint (P17 — the bound lives at the creation chokepoint so
|
|
6486
|
+
// a unique key can't dodge it). Hoist the reference to the handler's scope.
|
|
6487
|
+
gapBRaiseAggregated = raiseResumeAggregated;
|
|
6433
6488
|
resumeQueue = new ResumeQueue({
|
|
6434
6489
|
stateDir: path.join(_projectDir, '.instar'),
|
|
6435
6490
|
audit: auditResumeQueue,
|
|
@@ -6535,6 +6590,13 @@ export async function startServer(options) {
|
|
|
6535
6590
|
// admitted the entry). A throw resolves to NOT-finished inside the drainer's
|
|
6536
6591
|
// safeBool (SAFE side — the revival still passes the other reality gates).
|
|
6537
6592
|
autonomousRunFinished: (topicId) => autonomousRunRemainingForTopic(config.stateDir, topicId) == null,
|
|
6593
|
+
// GAP-B D9: the parallel drain-time re-check for a COMMITMENT-admitted
|
|
6594
|
+
// entry. Re-runs the SAME qualifying + D8 agreement check used at
|
|
6595
|
+
// enqueue; `true` ⇒ still active (spawn proceeds), `false` ⇒ the
|
|
6596
|
+
// commitment closed or user-activity lapsed since enqueue (the drainer
|
|
6597
|
+
// invalidates `commitment-no-longer-active`, never a spawn). A throw
|
|
6598
|
+
// resolves SAFE (still-active) inside the drainer's safeBool.
|
|
6599
|
+
commitmentStillActiveForTopic: (topicId) => gapBCommitmentQualifies(topicId),
|
|
6538
6600
|
jobCheck: (slug, queuedAtIso) => {
|
|
6539
6601
|
if (!scheduler)
|
|
6540
6602
|
return { ok: false, why: 'scheduler-unavailable' };
|
|
@@ -6700,6 +6762,46 @@ export async function startServer(options) {
|
|
|
6700
6762
|
}
|
|
6701
6763
|
}
|
|
6702
6764
|
sessionManager.on('sessionReaped', (e) => {
|
|
6765
|
+
// ── GAP-B Part B (spec: autonomous-registration-guarantee.md) — the
|
|
6766
|
+
// commitment-evidence BACKSTOP for an UNregistered-but-working autonomous
|
|
6767
|
+
// run. Computed ONCE here (cheap, fail-open) so its verdict feeds BOTH the
|
|
6768
|
+
// reap-log `evidenceSource` tag and the enqueue-time injection below.
|
|
6769
|
+
// Pinned to the SAME branch the state-file source uses: an `age-limit`
|
|
6770
|
+
// reap whose per-topic state file is ABSENT (a registered run is handled
|
|
6771
|
+
// by the AGE_LIMIT_ACTIVE_RUN_REASON path; this is the gap it can't see).
|
|
6772
|
+
// Ships DARK (`gapBGate.armed`) — when off it is a strict no-op. ──
|
|
6773
|
+
let gapBDecision = { fired: false, inject: false };
|
|
6774
|
+
try {
|
|
6775
|
+
if (gapBGate.armed && e.reason === 'age-limit') {
|
|
6776
|
+
// Only resolve topic + the cost-bearing eligibility on the armed
|
|
6777
|
+
// age-limit branch (every other reap pays zero added cost).
|
|
6778
|
+
const rawTopicG = telegram?.getTopicForSession(e.session.tmuxSession);
|
|
6779
|
+
const topicG = rawTopicG == null ? null : Number.isFinite(Number(rawTopicG)) ? Number(rawTopicG) : null;
|
|
6780
|
+
if (topicG != null) {
|
|
6781
|
+
gapBDecision = decideGapBInjection({
|
|
6782
|
+
gate: gapBGate,
|
|
6783
|
+
reason: e.reason,
|
|
6784
|
+
// State-file PRESENT ⇒ the registered-run path covers it upstream;
|
|
6785
|
+
// GAP-B fires only when ABSENT.
|
|
6786
|
+
stateFilePresent: autonomousRunRemainingForTopic(config.stateDir, topicG) != null,
|
|
6787
|
+
// D1/D2 qualifying set + D8 recent-user-message agreement (shared with
|
|
6788
|
+
// the drainer's drain-time re-check so the two cannot disagree).
|
|
6789
|
+
eligible: gapBCommitmentQualifies(topicG),
|
|
6790
|
+
});
|
|
6791
|
+
}
|
|
6792
|
+
}
|
|
6793
|
+
}
|
|
6794
|
+
catch (err) {
|
|
6795
|
+
// @silent-fallback-ok — D7 fail-open: any read throwing ⇒ no injection,
|
|
6796
|
+
// the reap proceeds exactly as today. Never endangers the kill path.
|
|
6797
|
+
gapBDecision = { fired: false, inject: false };
|
|
6798
|
+
console.warn('[gap-b] commitment-evidence probe raised (non-fatal):', err);
|
|
6799
|
+
}
|
|
6800
|
+
const gapBCommitmentFired = gapBDecision.fired;
|
|
6801
|
+
// dryRun (default when armed): the verdict is logged but the candidate is
|
|
6802
|
+
// NOT tagged — the dark-soak proving KEEP/eligibility agree before evidence
|
|
6803
|
+
// actually flows. `gapBInject` is the LIVE-injection switch.
|
|
6804
|
+
const gapBInject = gapBDecision.inject;
|
|
6703
6805
|
reapLog.recordReaped({
|
|
6704
6806
|
session: e.session.name,
|
|
6705
6807
|
tmuxSession: e.session.tmuxSession,
|
|
@@ -6717,7 +6819,24 @@ export async function startServer(options) {
|
|
|
6717
6819
|
// chokepoint; the reap-log row is the boot-reconciliation source of truth.
|
|
6718
6820
|
...(e.midWork !== undefined ? { midWork: e.midWork } : {}),
|
|
6719
6821
|
...(e.workEvidence && e.workEvidence.length > 0 ? { workEvidence: e.workEvidence } : {}),
|
|
6822
|
+
// GAP-B D3: tag the source only when the LIVE injection fired (dryRun
|
|
6823
|
+
// logs but does not tag — the row must reflect what actually drove
|
|
6824
|
+
// eligibility). No PII: the field carries only the source token.
|
|
6825
|
+
...(gapBInject ? { evidenceSource: 'commitment' } : {}),
|
|
6720
6826
|
});
|
|
6827
|
+
if (gapBCommitmentFired) {
|
|
6828
|
+
// Part A (observe-only): surface the unregistered-but-committed run through
|
|
6829
|
+
// the SAME aggregated chokepoint (P17), deduped per topic. Never blocks.
|
|
6830
|
+
// Fires on the VERDICT (even in dryRun) — the surface exists to make the
|
|
6831
|
+
// invisible visible so the run gets registered, independent of injection.
|
|
6832
|
+
try {
|
|
6833
|
+
const rawTopicA = telegram?.getTopicForSession(e.session.tmuxSession);
|
|
6834
|
+
const topicA = rawTopicA == null ? null : Number.isFinite(Number(rawTopicA)) ? Number(rawTopicA) : null;
|
|
6835
|
+
gapBRaiseAggregated?.(`unregistered-autonomous:${topicA ?? '?'}`, `An autonomous-flavored run on topic ${topicA ?? '?'} is proceeding UNREGISTERED (no per-topic state file) — surfaced from a fresh open commitment + recent user activity. Register it so its survival doesn't depend on a commitment existing.`);
|
|
6836
|
+
}
|
|
6837
|
+
catch { /* @silent-fallback-ok — Part A never endangers the reap path */ }
|
|
6838
|
+
console.log(`[gap-b] commitment-evidence ${gapBInject ? 'INJECTED' : 'would inject (dryRun)'} for ${e.session.name} (unregistered autonomous run)`);
|
|
6839
|
+
}
|
|
6721
6840
|
// Enqueue hook (reap-notify R2.2): every terminal autonomous reap is
|
|
6722
6841
|
// OFFERED to the resume queue; eligibility (evidence classes, job
|
|
6723
6842
|
// opt-in, operator exclusion, resurrection cap) is decided inside.
|
|
@@ -6750,6 +6869,19 @@ export async function startServer(options) {
|
|
|
6750
6869
|
candidateReason = AGE_LIMIT_ACTIVE_RUN_REASON;
|
|
6751
6870
|
candidateWorkEvidence = [...candidateWorkEvidence, 'build-or-autonomous-active'];
|
|
6752
6871
|
}
|
|
6872
|
+
else if (gapBInject) {
|
|
6873
|
+
// GAP-B Part B (the committed-unregistered-run backstop, LIVE only when
|
|
6874
|
+
// armed + NOT dryRun — see the verdict computed at the top of the
|
|
6875
|
+
// handler). Mutually exclusive with the branch above by construction:
|
|
6876
|
+
// that requires the per-topic state file PRESENT, gapBCommitmentFired
|
|
6877
|
+
// requires it ABSENT. Tag the DISTINCT reason so the drainer routes
|
|
6878
|
+
// this candidate to its own drain-time re-check (commitment liveness,
|
|
6879
|
+
// not the absent state file). The strong signal then flows through the
|
|
6880
|
+
// already-shipped evidenceEligible gate → ResumeQueue → drainer,
|
|
6881
|
+
// inheriting the resurrection cap (≤2/24h/topic — P19 bound).
|
|
6882
|
+
candidateReason = COMMITMENT_ACTIVE_RUN_REASON;
|
|
6883
|
+
candidateWorkEvidence = [...candidateWorkEvidence, 'build-or-autonomous-active'];
|
|
6884
|
+
}
|
|
6753
6885
|
resumeQueue.considerEnqueue({
|
|
6754
6886
|
sessionName: e.session.name,
|
|
6755
6887
|
tmuxSession: e.session.tmuxSession,
|
|
@@ -12711,12 +12843,17 @@ export async function startServer(options) {
|
|
|
12711
12843
|
hasPendingInjection: (s) => sessionManager.getPendingInjection(s) != null,
|
|
12712
12844
|
isRelayLeaseActive: (id) => sessionManager.isRelayLeaseActive(id),
|
|
12713
12845
|
topicBinding: _resolveTopic,
|
|
12714
|
-
//
|
|
12715
|
-
//
|
|
12716
|
-
//
|
|
12717
|
-
//
|
|
12718
|
-
//
|
|
12719
|
-
|
|
12846
|
+
// Part D (spec: autonomous-registration-guarantee.md) — promoted from the
|
|
12847
|
+
// v1 stub (`() => false`) to the SHARED real inbound-user-message recency
|
|
12848
|
+
// predicate (`recentUserMessageShared`, defined once near the resume-queue
|
|
12849
|
+
// wiring). This dep feeds FIVE live sites at once: ReapGuard's standalone
|
|
12850
|
+
// ~30min KEEP @137, its commitment-coupled 8h KEEP @149, the two terminate-
|
|
12851
|
+
// path mirrors @221/@239, and SessionReaper's staleIdle inversion @489. By
|
|
12852
|
+
// sharing the SAME predicate the GAP-B eligibility uses (D8), the KEEP
|
|
12853
|
+
// decisions and the revival eligibility are computed from identical truth
|
|
12854
|
+
// and cannot disagree (the 2026-06-13 loop the agreement prevents). All five
|
|
12855
|
+
// un-stub in the SAFE (keep-more) direction; fail-open (D7) on a throw.
|
|
12856
|
+
recentUserMessage: recentUserMessageShared,
|
|
12720
12857
|
activeCommitmentForTopic: (topicId) => {
|
|
12721
12858
|
try {
|
|
12722
12859
|
return commitmentTracker.getActive().some(c => c.topicId === topicId);
|