instar 1.3.762 → 1.3.764
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 +92 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +28 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/FileClassifier.d.ts.map +1 -1
- package/dist/core/FileClassifier.js +5 -0
- package/dist/core/FileClassifier.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +12 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +61 -2
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
- package/dist/core/WriteDomainRegistry.js +16 -0
- package/dist/core/WriteDomainRegistry.js.map +1 -1
- package/dist/core/componentCategories.d.ts.map +1 -1
- package/dist/core/componentCategories.js +1 -0
- package/dist/core/componentCategories.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +6 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/types.d.ts +50 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/data/llmBenchCoverage.d.ts.map +1 -1
- package/dist/data/llmBenchCoverage.js +18 -0
- package/dist/data/llmBenchCoverage.js.map +1 -1
- package/dist/monitoring/ExternalHogArmMarker.d.ts +65 -0
- package/dist/monitoring/ExternalHogArmMarker.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogArmMarker.js +0 -0
- package/dist/monitoring/ExternalHogArmMarker.js.map +1 -0
- package/dist/monitoring/ExternalHogArmStore.d.ts +46 -0
- package/dist/monitoring/ExternalHogArmStore.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogArmStore.js +124 -0
- package/dist/monitoring/ExternalHogArmStore.js.map +1 -0
- package/dist/monitoring/ExternalHogClassifier.d.ts +62 -0
- package/dist/monitoring/ExternalHogClassifier.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogClassifier.js +0 -0
- package/dist/monitoring/ExternalHogClassifier.js.map +1 -0
- package/dist/monitoring/ExternalHogClassifierPrompt.d.ts +29 -0
- package/dist/monitoring/ExternalHogClassifierPrompt.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogClassifierPrompt.js +70 -0
- package/dist/monitoring/ExternalHogClassifierPrompt.js.map +1 -0
- package/dist/monitoring/ExternalHogCpuDelta.d.ts +50 -0
- package/dist/monitoring/ExternalHogCpuDelta.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogCpuDelta.js +79 -0
- package/dist/monitoring/ExternalHogCpuDelta.js.map +1 -0
- package/dist/monitoring/ExternalHogFactBuilder.d.ts +71 -0
- package/dist/monitoring/ExternalHogFactBuilder.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogFactBuilder.js +111 -0
- package/dist/monitoring/ExternalHogFactBuilder.js.map +1 -0
- package/dist/monitoring/ExternalHogFloor.d.ts +87 -0
- package/dist/monitoring/ExternalHogFloor.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogFloor.js +123 -0
- package/dist/monitoring/ExternalHogFloor.js.map +1 -0
- package/dist/monitoring/ExternalHogGuardStatus.d.ts +29 -0
- package/dist/monitoring/ExternalHogGuardStatus.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogGuardStatus.js +27 -0
- package/dist/monitoring/ExternalHogGuardStatus.js.map +1 -0
- package/dist/monitoring/ExternalHogKillFunnel.d.ts +77 -0
- package/dist/monitoring/ExternalHogKillFunnel.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogKillFunnel.js +72 -0
- package/dist/monitoring/ExternalHogKillFunnel.js.map +1 -0
- package/dist/monitoring/ExternalHogKillLedger.d.ts +76 -0
- package/dist/monitoring/ExternalHogKillLedger.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogKillLedger.js +84 -0
- package/dist/monitoring/ExternalHogKillLedger.js.map +1 -0
- package/dist/monitoring/ExternalHogNoticeCoalescer.d.ts +46 -0
- package/dist/monitoring/ExternalHogNoticeCoalescer.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogNoticeCoalescer.js +67 -0
- package/dist/monitoring/ExternalHogNoticeCoalescer.js.map +1 -0
- package/dist/monitoring/ExternalHogOwnership.d.ts +48 -0
- package/dist/monitoring/ExternalHogOwnership.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogOwnership.js +60 -0
- package/dist/monitoring/ExternalHogOwnership.js.map +1 -0
- package/dist/monitoring/ExternalHogProcTable.d.ts +37 -0
- package/dist/monitoring/ExternalHogProcTable.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogProcTable.js +65 -0
- package/dist/monitoring/ExternalHogProcTable.js.map +1 -0
- package/dist/monitoring/ExternalHogRealAdapters.d.ts +91 -0
- package/dist/monitoring/ExternalHogRealAdapters.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogRealAdapters.js +247 -0
- package/dist/monitoring/ExternalHogRealAdapters.js.map +1 -0
- package/dist/monitoring/ExternalHogSampler.d.ts +62 -0
- package/dist/monitoring/ExternalHogSampler.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogSampler.js +0 -0
- package/dist/monitoring/ExternalHogSampler.js.map +1 -0
- package/dist/monitoring/ExternalHogScanTick.d.ts +89 -0
- package/dist/monitoring/ExternalHogScanTick.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogScanTick.js +126 -0
- package/dist/monitoring/ExternalHogScanTick.js.map +1 -0
- package/dist/monitoring/ExternalHogSentinel.d.ts +127 -0
- package/dist/monitoring/ExternalHogSentinel.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogSentinel.js +168 -0
- package/dist/monitoring/ExternalHogSentinel.js.map +1 -0
- package/dist/monitoring/ExternalHogServerPrimitives.d.ts +50 -0
- package/dist/monitoring/ExternalHogServerPrimitives.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogServerPrimitives.js +93 -0
- package/dist/monitoring/ExternalHogServerPrimitives.js.map +1 -0
- package/dist/monitoring/ExternalHogSustained.d.ts +49 -0
- package/dist/monitoring/ExternalHogSustained.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogSustained.js +62 -0
- package/dist/monitoring/ExternalHogSustained.js.map +1 -0
- package/dist/monitoring/guardManifest.d.ts.map +1 -1
- package/dist/monitoring/guardManifest.js +24 -0
- package/dist/monitoring/guardManifest.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +3 -2
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +3 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +3 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +79 -5
- package/dist/server/routes.js.map +1 -1
- package/dist/testing/selfActionRegistry.d.ts.map +1 -1
- package/dist/testing/selfActionRegistry.js +51 -0
- package/dist/testing/selfActionRegistry.js.map +1 -1
- package/package.json +1 -1
- package/scripts/lint-scrape-fixture-realness.js +14 -0
- package/src/data/builtin-manifest.json +64 -64
- package/src/data/llmBenchCoverage.ts +18 -0
- package/src/scaffold/templates.ts +3 -2
- package/upgrades/1.3.763.md +23 -0
- package/upgrades/1.3.764.md +45 -0
- package/upgrades/side-effects/actionclaim-config-shape-fix.eli16.md +50 -0
- package/upgrades/side-effects/actionclaim-config-shape-fix.md +149 -0
- package/upgrades/side-effects/external-hog-sentinel.md +832 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExternalHogScanTick — the orchestrator that composes the External-Hog sentinel's modules into
|
|
3
|
+
* ONE scan tick (CMT-1901, docs/specs/external-hog-zombie-autokill-sentinel.md §1-§6).
|
|
4
|
+
*
|
|
5
|
+
* This is the "make it alive" integration: discovery (sampler) → classify (worst-CPU-first under
|
|
6
|
+
* the cap) → floor → kill funnel → P19 ledger → P17 coalesced notices. All I/O is INJECTED
|
|
7
|
+
* (read ps, build ownership, classify, the kill-funnel deps, clock, deliver) so the whole tick
|
|
8
|
+
* is end-to-end testable without a real ps read, a real model call, or a real signal. The thin
|
|
9
|
+
* server-side wiring (the actual ps spawn, LlmQueue call, notice delivery, interval) is a
|
|
10
|
+
* separate slice; this is the pure control flow that ties the reviewed modules together.
|
|
11
|
+
*
|
|
12
|
+
* Watch-only rides through: the kill funnel returns `would-kill` (no signal) unless a live kill
|
|
13
|
+
* is authorized, so in the shipped dryRun state a scan tick produces would-kill records + the
|
|
14
|
+
* §4 observability notices, and kills NOTHING.
|
|
15
|
+
*/
|
|
16
|
+
import { advanceSampler } from './ExternalHogSampler.js';
|
|
17
|
+
import { selectForClassification, parseClassifierVerdict } from './ExternalHogClassifier.js';
|
|
18
|
+
import { evaluateKillFloor } from './ExternalHogFloor.js';
|
|
19
|
+
import { runKillFunnel } from './ExternalHogKillFunnel.js';
|
|
20
|
+
import { isBreakerTripped, recordKill, } from './ExternalHogKillLedger.js';
|
|
21
|
+
import { coalesceNotices } from './ExternalHogNoticeCoalescer.js';
|
|
22
|
+
import { advanceSustained, isSustained, candidateSignature } from './ExternalHogSustained.js';
|
|
23
|
+
/**
|
|
24
|
+
* Run one scan tick. Pure control flow over the injected I/O; mutates nothing. In the shipped
|
|
25
|
+
* dryRun state every `outcome` is `would-kill`/`alert-only` and no signal is sent.
|
|
26
|
+
*/
|
|
27
|
+
export async function runScanTick(state, deps, opts) {
|
|
28
|
+
const now = deps.nowMs();
|
|
29
|
+
const table = deps.readProcTable();
|
|
30
|
+
const { tree, owned } = deps.buildOwnership(table);
|
|
31
|
+
// Stage-1 candidacy.
|
|
32
|
+
const tick = advanceSampler(state.sampler, table, tree, owned, now, opts.sampler);
|
|
33
|
+
const nextSampler = tick.nextState;
|
|
34
|
+
// Stage-2 sustained confirmation (§1 anti-spike): advance the per-signature streak with THIS
|
|
35
|
+
// tick's over-threshold candidates. A failed/empty parse yields no candidates → every streak
|
|
36
|
+
// resets (fail toward not-sustained). This is the AUTHORITATIVE N-window signal that overrides
|
|
37
|
+
// whatever single-window read the fact builder produced.
|
|
38
|
+
const sustainedTick = advanceSustained(state.sustained, tick.candidates.map((c) => candidateSignature(c.pid, c.startTime)));
|
|
39
|
+
const nextSustained = sustainedTick.nextState;
|
|
40
|
+
// Worst-CPU-first under the per-scan classifier cap.
|
|
41
|
+
const outcomes = [];
|
|
42
|
+
const notices = [];
|
|
43
|
+
let ledger = state.ledger;
|
|
44
|
+
// Enrich candidates. A candidate whose facts VANISHED (factsFor null) is skipped silently —
|
|
45
|
+
// the process is gone, there is nothing to surface. But a PRESENT candidate (non-null facts)
|
|
46
|
+
// that we can't fully IDENTIFY (identityFor null — e.g. a sustained hog outside the killable
|
|
47
|
+
// allowlist class, or an identity race on the command-hash) is NEVER a killable target, yet
|
|
48
|
+
// it IS a sustained external hog: it MUST be SURFACED, never silently dropped (round-13 —
|
|
49
|
+
// second-pass reviewer: the §4 broad-observability guarantee — no present hog is invisible).
|
|
50
|
+
const enriched = [];
|
|
51
|
+
for (const c of tick.candidates) {
|
|
52
|
+
const rawFacts = await deps.factsFor(c);
|
|
53
|
+
if (!rawFacts)
|
|
54
|
+
continue; // vanished → nothing to surface (safe)
|
|
55
|
+
// Override sustainedHighCpu with the AUTHORITATIVE N-window signal: it stays true ONLY if the
|
|
56
|
+
// fact builder's single-window read was GENUINELY boolean-true AND the streak has reached N
|
|
57
|
+
// consecutive windows (§1). A one-window spike (streak < N) is forced to false → the floor's
|
|
58
|
+
// hard veto downgrades it to alert — never a kill on a transient burst.
|
|
59
|
+
//
|
|
60
|
+
// We apply the AND-gate ONLY to a strict `=== true`; a MALFORMED value (a degraded fact
|
|
61
|
+
// builder emitting `1`/undefined/null under the CPU starvation this sentinel hunts) is
|
|
62
|
+
// PRESERVED verbatim, NOT coerced. `x && sustained` would launder a truthy non-boolean into
|
|
63
|
+
// boolean `true`, defeating the floor's round-11 `typeof !== 'boolean' → field-unknown` veto
|
|
64
|
+
// in the kill-PERMITTING direction (Phase-5 reviewer). Preserving it keeps that veto intact.
|
|
65
|
+
const sustained = isSustained(sustainedTick, candidateSignature(c.pid, c.startTime), opts.sustainedSampleCount);
|
|
66
|
+
const facts = { ...rawFacts, sustainedHighCpu: rawFacts.sustainedHighCpu === true ? sustained : rawFacts.sustainedHighCpu };
|
|
67
|
+
const id = deps.identityFor(c, facts);
|
|
68
|
+
if (!id) {
|
|
69
|
+
// Present sustained hog, not kill-eligible → surface (observability), never classify/kill.
|
|
70
|
+
notices.push({ cls: 'hog-left-alive', signature: `${c.pid} ${c.startTime}`, text: `sustained external hog (not kill-eligible): pid ${c.pid}` });
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
enriched.push({ c, facts, id, tuple: { pid: c.pid, startTime: c.startTime, commandHash: id.commandHash }, coreEquivalents: c.coreEquivalents });
|
|
74
|
+
}
|
|
75
|
+
const { toClassify, degradedToAlert } = selectForClassification(enriched, opts.maxClassificationsPerScan);
|
|
76
|
+
for (const cand of toClassify) {
|
|
77
|
+
const floor = evaluateKillFloor(cand.facts);
|
|
78
|
+
const raw = await deps.classify(cand.facts);
|
|
79
|
+
const verdict = parseClassifierVerdict(raw); // null → decider-unavailable → alert
|
|
80
|
+
// The §4 observability floor: a deterministic sustained hog that is NOT killed is ALWAYS
|
|
81
|
+
// surfaced (the model can never silence it).
|
|
82
|
+
const surfaceLeftAlive = () => notices.push({ cls: 'hog-left-alive', signature: cand.id.ledgerKey, text: `sustained hog left alive: pid ${cand.c.pid}` });
|
|
83
|
+
if (verdict === null) {
|
|
84
|
+
// Decider unavailable → no kill → alert.
|
|
85
|
+
outcomes.push({ pid: cand.c.pid, ledgerKey: cand.id.ledgerKey, classId: cand.id.classId, verdict: 'decider-unavailable', outcome: 'alert-only' });
|
|
86
|
+
notices.push({ cls: 'decider-unavailable', signature: cand.id.ledgerKey, text: `decider unavailable: pid ${cand.c.pid}` });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (verdict !== 'kill' || !floor.permitted) {
|
|
90
|
+
// Model spared it, or the floor vetoes → alert-only (observability floor still surfaces).
|
|
91
|
+
outcomes.push({ pid: cand.c.pid, ledgerKey: cand.id.ledgerKey, classId: cand.id.classId, verdict, outcome: 'alert-only' });
|
|
92
|
+
surfaceLeftAlive();
|
|
93
|
+
if (!floor.permitted)
|
|
94
|
+
notices.push({ cls: 'floor-veto-downgrade', signature: cand.id.ledgerKey, text: `floor vetoed a kill: pid ${cand.c.pid}` });
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
// verdict === 'kill' && floor permits. Check the P19 breaker before acting.
|
|
98
|
+
const tripped = isBreakerTripped(ledger, cand.id.ledgerKey, cand.id.classId, { ...opts.breaker, nowMs: now });
|
|
99
|
+
if (tripped) {
|
|
100
|
+
outcomes.push({ pid: cand.c.pid, ledgerKey: cand.id.ledgerKey, classId: cand.id.classId, verdict, outcome: 'alert-only' });
|
|
101
|
+
surfaceLeftAlive(); // shielded from KILL only, never from surfacing
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
// Run the kill funnel (watch-only unless armed → would-kill, no signal).
|
|
105
|
+
const target = { pid: cand.c.pid, startTime: cand.c.startTime, commandHash: cand.id.commandHash, classId: cand.id.classId };
|
|
106
|
+
const funnelOpts = { ...opts.killFunnel, currentDeferrals: deps.deferralsFor(cand.id.ledgerKey) };
|
|
107
|
+
const outcome = await runKillFunnel(target, funnelOpts, deps.killFunnelDeps);
|
|
108
|
+
outcomes.push({ pid: cand.c.pid, ledgerKey: cand.id.ledgerKey, classId: cand.id.classId, verdict, outcome });
|
|
109
|
+
if (outcome.action === 'killed') {
|
|
110
|
+
ledger = recordKill(ledger, { key: cand.id.ledgerKey, classId: cand.id.classId, atMs: now }, opts.killLedgerRetentionMs, now);
|
|
111
|
+
notices.push({ cls: 'kill', signature: cand.id.ledgerKey, text: `auto-killed zombie: pid ${cand.c.pid}` });
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// would-kill (watch-only) / deferred / aborted / sigterm-exited → surface as left-alive.
|
|
115
|
+
surfaceLeftAlive();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// The over-cap remainder degrades to alert-only (observability floor).
|
|
119
|
+
for (const cand of degradedToAlert) {
|
|
120
|
+
outcomes.push({ pid: cand.c.pid, ledgerKey: cand.id.ledgerKey, classId: cand.id.classId, verdict: 'decider-unavailable', outcome: 'alert-only' });
|
|
121
|
+
notices.push({ cls: 'hog-left-alive', signature: cand.id.ledgerKey, text: `sustained hog left alive (over cap): pid ${cand.c.pid}` });
|
|
122
|
+
}
|
|
123
|
+
const coalesced = coalesceNotices(notices, { budgetPerWindow: opts.noticeBudgetPerWindow });
|
|
124
|
+
return { nextState: { sampler: nextSampler, ledger, sustained: nextSustained }, outcomes, notices: coalesced };
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=ExternalHogScanTick.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExternalHogScanTick.js","sourceRoot":"","sources":["../../src/monitoring/ExternalHogScanTick.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExternalHogSentinel — the composition shell that turns the reviewed pure modules into a live,
|
|
3
|
+
* tickable monitor (CMT-1901, docs/specs/external-hog-zombie-autokill-sentinel.md §1-§8).
|
|
4
|
+
*
|
|
5
|
+
* This is the ADAPTER LAYER, not new decision logic: it holds the cross-tick scan state (sampler
|
|
6
|
+
* baseline + kill ledger + per-signature deferral counts), reads the real I/O through an INJECTED
|
|
7
|
+
* adapter seam (so the whole shell is unit-testable with fakes — no real `ps`, model call, or
|
|
8
|
+
* signal), and on each tick delegates the ENTIRE decision to the reviewed `runScanTick`
|
|
9
|
+
* orchestrator. It adds nothing to the kill decision: the watch-only guarantee, the floor veto,
|
|
10
|
+
* the classifier authority, the P19 breaker and P17 coalescing all live below it and are unchanged.
|
|
11
|
+
*
|
|
12
|
+
* The shell's only responsibilities:
|
|
13
|
+
* - carry state across ticks (sampler map, kill ledger, deferral counts) — none of which the
|
|
14
|
+
* pure orchestrator can hold;
|
|
15
|
+
* - bridge the async real reads (spawn `ps` off-loop, resolve owned pids) into the sync closures
|
|
16
|
+
* the orchestrator expects, by reading BEFORE the tick and closing over the snapshot;
|
|
17
|
+
* - persist the deferral count per target signature so `maxKillDeferrals` actually bounds across
|
|
18
|
+
* ticks (a target deferred for an open workspace file eventually proceeds to SIGKILL);
|
|
19
|
+
* - deliver the coalesced notices and expose an honest guard-posture `status()`.
|
|
20
|
+
*
|
|
21
|
+
* Watch-only rides straight through: in the shipped dryRun state the funnel returns `would-kill`
|
|
22
|
+
* and no signal is sent, so a tick produces would-kill records + the §4 observability notices and
|
|
23
|
+
* kills NOTHING.
|
|
24
|
+
*
|
|
25
|
+
* Self-action convergence (Capacity Safety — No Unbounded Self-Action): the kill loop's respawn
|
|
26
|
+
* brake is registered in the convergence ratchet, driving the REAL pure ledger functions.
|
|
27
|
+
*/
|
|
28
|
+
import { type ScanOpts, type ScanOutcome, type ScanResult } from './ExternalHogScanTick.js';
|
|
29
|
+
import type { ProcTableRow } from './ExternalHogProcTable.js';
|
|
30
|
+
import type { ProcTree, OwnedRefs } from './ExternalHogOwnership.js';
|
|
31
|
+
import type { ExternalHogFacts } from './ExternalHogFloor.js';
|
|
32
|
+
import type { Candidate } from './ExternalHogSampler.js';
|
|
33
|
+
import type { KillFunnelDeps } from './ExternalHogKillFunnel.js';
|
|
34
|
+
import type { CoalesceResult } from './ExternalHogNoticeCoalescer.js';
|
|
35
|
+
import type { GuardEffectiveState } from './guardPostureView.js';
|
|
36
|
+
/** The real-I/O seam. Every method is injected so the shell is fully testable with fakes. */
|
|
37
|
+
export interface ExternalHogAdapters {
|
|
38
|
+
/** Spawn `ps` off the event loop and parse it. Empty/failed read → []. */
|
|
39
|
+
readProcTable(): Promise<readonly ProcTableRow[]>;
|
|
40
|
+
/** instar-owned pids → their EXPECTED start-time (server pid, sampler pid, tmux panes). */
|
|
41
|
+
ownedRefs(): Promise<OwnedRefs>;
|
|
42
|
+
/** Deterministic facts for a candidate (the floor input); null if it vanished/unreadable. May be
|
|
43
|
+
* async (a per-candidate ps -o args= read + launchctl) — the orchestrator awaits it. */
|
|
44
|
+
factsFor(candidate: Candidate, table: readonly ProcTableRow[]): ExternalHogFacts | null | Promise<ExternalHogFacts | null>;
|
|
45
|
+
/** command-hash + ledger key + class for a candidate; null if not a killable allowlist class. */
|
|
46
|
+
identityFor(candidate: Candidate, facts: ExternalHogFacts): {
|
|
47
|
+
commandHash: string;
|
|
48
|
+
ledgerKey: string;
|
|
49
|
+
classId: string;
|
|
50
|
+
} | null;
|
|
51
|
+
/** The classifier (LlmQueue). Raw model output, or null when the decider is unavailable. */
|
|
52
|
+
classify(facts: ExternalHogFacts): Promise<unknown>;
|
|
53
|
+
/** The hardened kill-funnel I/O (re-read facts/arm, fd-probe, signal, aliveness, wait). */
|
|
54
|
+
killFunnelDeps: KillFunnelDeps;
|
|
55
|
+
/** Deliver the coalesced notices (attention queue / telegram). */
|
|
56
|
+
deliverNotices(result: CoalesceResult): void;
|
|
57
|
+
/** The live arm status for the guard posture (enabled/dryRun/valid PIN marker). */
|
|
58
|
+
armStatus(): {
|
|
59
|
+
enabled: boolean;
|
|
60
|
+
dryRun: boolean;
|
|
61
|
+
markerValid: boolean;
|
|
62
|
+
};
|
|
63
|
+
/** A monotonic clock reading (ms). Same clock the sampler + kill-funnel use. */
|
|
64
|
+
nowMs(): number;
|
|
65
|
+
/** Append one audit row per tick (scrubbed, metadata-only). Never throws into the tick. */
|
|
66
|
+
auditTick?(row: ExternalHogAuditRow): void;
|
|
67
|
+
}
|
|
68
|
+
export interface ExternalHogRuntimeOpts extends ScanOpts {
|
|
69
|
+
/** Heartbeat age past which the sampler is DEAD (blind) → guard posture on-stale (§1). */
|
|
70
|
+
readonly samplerDeadThresholdMs: number;
|
|
71
|
+
/** Hard cap on the deferral map (memory bound; oldest pruned past this). */
|
|
72
|
+
readonly deferralMapMax?: number;
|
|
73
|
+
}
|
|
74
|
+
export interface ExternalHogAuditRow {
|
|
75
|
+
readonly at: number;
|
|
76
|
+
readonly candidates: number;
|
|
77
|
+
readonly killed: number;
|
|
78
|
+
readonly wouldKill: number;
|
|
79
|
+
readonly alertOnly: number;
|
|
80
|
+
readonly deferred: number;
|
|
81
|
+
}
|
|
82
|
+
export interface ExternalHogStatus {
|
|
83
|
+
readonly effectiveState: GuardEffectiveState;
|
|
84
|
+
readonly enabled: boolean;
|
|
85
|
+
readonly dryRun: boolean;
|
|
86
|
+
readonly markerValid: boolean;
|
|
87
|
+
readonly samplerDead: boolean;
|
|
88
|
+
readonly lastTickAt: number | null;
|
|
89
|
+
readonly recentOutcomes: readonly ScanOutcome[];
|
|
90
|
+
readonly trackedDeferrals: number;
|
|
91
|
+
}
|
|
92
|
+
/** Build a ProcTree (pid → node) from a parsed ps table — the ownership walk's input. */
|
|
93
|
+
export declare function buildProcTree(table: readonly ProcTableRow[]): ProcTree;
|
|
94
|
+
export declare class ExternalHogSentinel {
|
|
95
|
+
private readonly adapters;
|
|
96
|
+
private readonly opts;
|
|
97
|
+
private state;
|
|
98
|
+
/** ledgerKey → how many times this signature has been deferred (persisted across ticks). */
|
|
99
|
+
private readonly deferrals;
|
|
100
|
+
private lastTickAt;
|
|
101
|
+
private lastOutcomes;
|
|
102
|
+
constructor(adapters: ExternalHogAdapters, opts: ExternalHogRuntimeOpts);
|
|
103
|
+
/**
|
|
104
|
+
* Run one scan tick. Reads the real table + owned pids (async, off-loop), then delegates the
|
|
105
|
+
* whole decision to the reviewed orchestrator over a snapshot-closing ScanDeps. Watch-only in
|
|
106
|
+
* the shipped state (no signal). Never throws — a read failure degrades to an empty tick
|
|
107
|
+
* (the sampler heartbeat simply does not advance → eventually on-stale).
|
|
108
|
+
*/
|
|
109
|
+
tick(): Promise<ScanResult>;
|
|
110
|
+
/**
|
|
111
|
+
* The `/guards` runtime getter (GuardRegistry). `lastTickAt` is the last SUCCESSFUL PARSE (the
|
|
112
|
+
* sampler heartbeat), NOT the tick time — so a sentinel that is ticking but BLIND (ps failing,
|
|
113
|
+
* never parsing) reports a stale/zero lastTickAt and `/guards` honestly derives `on-stale`
|
|
114
|
+
* instead of a falsely-reassuring `on-confirmed`. A never-parsed sentinel → 0 → on-stale.
|
|
115
|
+
*/
|
|
116
|
+
guardRuntimeStatus(): {
|
|
117
|
+
enabled: boolean;
|
|
118
|
+
dryRun: boolean;
|
|
119
|
+
lastTickAt: number;
|
|
120
|
+
};
|
|
121
|
+
/** Honest guard posture (§8): reflects VERIFIED kill-capability, never a config wish. */
|
|
122
|
+
status(): ExternalHogStatus;
|
|
123
|
+
/** Bump deferral counts for live deferrals; clear resolved targets; bound the map. */
|
|
124
|
+
private updateDeferrals;
|
|
125
|
+
private emitAudit;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=ExternalHogSentinel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExternalHogSentinel.d.ts","sourceRoot":"","sources":["../../src/monitoring/ExternalHogSentinel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExternalHogSentinel — the composition shell that turns the reviewed pure modules into a live,
|
|
3
|
+
* tickable monitor (CMT-1901, docs/specs/external-hog-zombie-autokill-sentinel.md §1-§8).
|
|
4
|
+
*
|
|
5
|
+
* This is the ADAPTER LAYER, not new decision logic: it holds the cross-tick scan state (sampler
|
|
6
|
+
* baseline + kill ledger + per-signature deferral counts), reads the real I/O through an INJECTED
|
|
7
|
+
* adapter seam (so the whole shell is unit-testable with fakes — no real `ps`, model call, or
|
|
8
|
+
* signal), and on each tick delegates the ENTIRE decision to the reviewed `runScanTick`
|
|
9
|
+
* orchestrator. It adds nothing to the kill decision: the watch-only guarantee, the floor veto,
|
|
10
|
+
* the classifier authority, the P19 breaker and P17 coalescing all live below it and are unchanged.
|
|
11
|
+
*
|
|
12
|
+
* The shell's only responsibilities:
|
|
13
|
+
* - carry state across ticks (sampler map, kill ledger, deferral counts) — none of which the
|
|
14
|
+
* pure orchestrator can hold;
|
|
15
|
+
* - bridge the async real reads (spawn `ps` off-loop, resolve owned pids) into the sync closures
|
|
16
|
+
* the orchestrator expects, by reading BEFORE the tick and closing over the snapshot;
|
|
17
|
+
* - persist the deferral count per target signature so `maxKillDeferrals` actually bounds across
|
|
18
|
+
* ticks (a target deferred for an open workspace file eventually proceeds to SIGKILL);
|
|
19
|
+
* - deliver the coalesced notices and expose an honest guard-posture `status()`.
|
|
20
|
+
*
|
|
21
|
+
* Watch-only rides straight through: in the shipped dryRun state the funnel returns `would-kill`
|
|
22
|
+
* and no signal is sent, so a tick produces would-kill records + the §4 observability notices and
|
|
23
|
+
* kills NOTHING.
|
|
24
|
+
*
|
|
25
|
+
* Self-action convergence (Capacity Safety — No Unbounded Self-Action): the kill loop's respawn
|
|
26
|
+
* brake is registered in the convergence ratchet, driving the REAL pure ledger functions.
|
|
27
|
+
*/
|
|
28
|
+
/* @self-action-controller: external-hog-kill-breaker */
|
|
29
|
+
import { runScanTick } from './ExternalHogScanTick.js';
|
|
30
|
+
import { EMPTY_SAMPLER_STATE, isSamplerDead } from './ExternalHogSampler.js';
|
|
31
|
+
import { EMPTY_KILL_LEDGER } from './ExternalHogKillLedger.js';
|
|
32
|
+
import { EMPTY_SUSTAINED_STATE } from './ExternalHogSustained.js';
|
|
33
|
+
import { externalHogEffectiveState } from './ExternalHogGuardStatus.js';
|
|
34
|
+
const DEFAULT_DEFERRAL_MAP_MAX = 128;
|
|
35
|
+
/** Build a ProcTree (pid → node) from a parsed ps table — the ownership walk's input. */
|
|
36
|
+
export function buildProcTree(table) {
|
|
37
|
+
const tree = new Map();
|
|
38
|
+
for (const row of table) {
|
|
39
|
+
if (!Number.isInteger(row.pid) || row.pid <= 0)
|
|
40
|
+
continue;
|
|
41
|
+
tree.set(row.pid, { pid: row.pid, ppid: row.ppid, startTime: row.startTime });
|
|
42
|
+
}
|
|
43
|
+
return tree;
|
|
44
|
+
}
|
|
45
|
+
/** Is this outcome a live deferral that should bump the per-signature count? */
|
|
46
|
+
function isDeferred(o) {
|
|
47
|
+
return typeof o === 'object' && o !== null && o.action === 'deferred';
|
|
48
|
+
}
|
|
49
|
+
/** Is this a TERMINAL outcome that resolves a target (so its deferral count is cleared)? */
|
|
50
|
+
function isTerminal(o) {
|
|
51
|
+
return typeof o === 'object' && o !== null && (o.action === 'killed' || o.action === 'sigterm-exited' || o.action === 'aborted');
|
|
52
|
+
}
|
|
53
|
+
export class ExternalHogSentinel {
|
|
54
|
+
adapters;
|
|
55
|
+
opts;
|
|
56
|
+
state = { sampler: EMPTY_SAMPLER_STATE, ledger: EMPTY_KILL_LEDGER, sustained: EMPTY_SUSTAINED_STATE };
|
|
57
|
+
/** ledgerKey → how many times this signature has been deferred (persisted across ticks). */
|
|
58
|
+
deferrals = new Map();
|
|
59
|
+
lastTickAt = null;
|
|
60
|
+
lastOutcomes = [];
|
|
61
|
+
constructor(adapters, opts) {
|
|
62
|
+
this.adapters = adapters;
|
|
63
|
+
this.opts = opts;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Run one scan tick. Reads the real table + owned pids (async, off-loop), then delegates the
|
|
67
|
+
* whole decision to the reviewed orchestrator over a snapshot-closing ScanDeps. Watch-only in
|
|
68
|
+
* the shipped state (no signal). Never throws — a read failure degrades to an empty tick
|
|
69
|
+
* (the sampler heartbeat simply does not advance → eventually on-stale).
|
|
70
|
+
*/
|
|
71
|
+
async tick() {
|
|
72
|
+
const table = await this.adapters.readProcTable();
|
|
73
|
+
const owned = await this.adapters.ownedRefs();
|
|
74
|
+
const tree = buildProcTree(table);
|
|
75
|
+
const deps = {
|
|
76
|
+
readProcTable: () => table,
|
|
77
|
+
buildOwnership: () => ({ tree, owned }),
|
|
78
|
+
factsFor: (c) => this.adapters.factsFor(c, table),
|
|
79
|
+
identityFor: (c, f) => this.adapters.identityFor(c, f),
|
|
80
|
+
classify: (f) => this.adapters.classify(f),
|
|
81
|
+
killFunnelDeps: this.adapters.killFunnelDeps,
|
|
82
|
+
nowMs: () => this.adapters.nowMs(),
|
|
83
|
+
deferralsFor: (key) => this.deferrals.get(key) ?? 0,
|
|
84
|
+
};
|
|
85
|
+
const result = await runScanTick(this.state, deps, this.opts);
|
|
86
|
+
this.state = result.nextState;
|
|
87
|
+
this.updateDeferrals(result.outcomes);
|
|
88
|
+
this.lastOutcomes = result.outcomes.slice(-16);
|
|
89
|
+
this.lastTickAt = this.adapters.nowMs();
|
|
90
|
+
this.adapters.deliverNotices(result.notices);
|
|
91
|
+
this.emitAudit(result.outcomes);
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The `/guards` runtime getter (GuardRegistry). `lastTickAt` is the last SUCCESSFUL PARSE (the
|
|
96
|
+
* sampler heartbeat), NOT the tick time — so a sentinel that is ticking but BLIND (ps failing,
|
|
97
|
+
* never parsing) reports a stale/zero lastTickAt and `/guards` honestly derives `on-stale`
|
|
98
|
+
* instead of a falsely-reassuring `on-confirmed`. A never-parsed sentinel → 0 → on-stale.
|
|
99
|
+
*/
|
|
100
|
+
guardRuntimeStatus() {
|
|
101
|
+
const arm = this.adapters.armStatus();
|
|
102
|
+
return { enabled: arm.enabled, dryRun: arm.dryRun, lastTickAt: this.state.sampler.lastSnapshotAt ?? 0 };
|
|
103
|
+
}
|
|
104
|
+
/** Honest guard posture (§8): reflects VERIFIED kill-capability, never a config wish. */
|
|
105
|
+
status() {
|
|
106
|
+
const arm = this.adapters.armStatus();
|
|
107
|
+
// BLIND ⇒ on-stale, never on-confirmed. Two ways to be blind: (1) NO successful parse has
|
|
108
|
+
// EVER happened (a boot where `ps` fails from the start — Phase-5 reviewer note D: without
|
|
109
|
+
// this, an armed-but-never-parsed sentinel would falsely read on-confirmed), or (2) the
|
|
110
|
+
// heartbeat has gone stale after previously parsing. on-confirmed REQUIRES a fresh successful
|
|
111
|
+
// parse — a feature that has never read the process table is not verified kill-capable.
|
|
112
|
+
const everParsed = this.state.sampler.lastSnapshotAt !== null;
|
|
113
|
+
const samplerDead = !everParsed
|
|
114
|
+
|| isSamplerDead(this.state.sampler, this.adapters.nowMs(), this.opts.samplerDeadThresholdMs);
|
|
115
|
+
return {
|
|
116
|
+
effectiveState: externalHogEffectiveState({
|
|
117
|
+
enabled: arm.enabled, dryRun: arm.dryRun, markerValid: arm.markerValid, samplerDead,
|
|
118
|
+
}),
|
|
119
|
+
enabled: arm.enabled,
|
|
120
|
+
dryRun: arm.dryRun,
|
|
121
|
+
markerValid: arm.markerValid,
|
|
122
|
+
samplerDead,
|
|
123
|
+
lastTickAt: this.lastTickAt,
|
|
124
|
+
recentOutcomes: this.lastOutcomes,
|
|
125
|
+
trackedDeferrals: this.deferrals.size,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** Bump deferral counts for live deferrals; clear resolved targets; bound the map. */
|
|
129
|
+
updateDeferrals(outcomes) {
|
|
130
|
+
for (const o of outcomes) {
|
|
131
|
+
if (isDeferred(o.outcome)) {
|
|
132
|
+
this.deferrals.set(o.ledgerKey, (this.deferrals.get(o.ledgerKey) ?? 0) + 1);
|
|
133
|
+
}
|
|
134
|
+
else if (isTerminal(o.outcome)) {
|
|
135
|
+
this.deferrals.delete(o.ledgerKey); // resolved or gone → forget it
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const max = this.opts.deferralMapMax ?? DEFAULT_DEFERRAL_MAP_MAX;
|
|
139
|
+
while (this.deferrals.size > max) {
|
|
140
|
+
const oldest = this.deferrals.keys().next().value; // Map preserves insertion order
|
|
141
|
+
if (oldest === undefined)
|
|
142
|
+
break;
|
|
143
|
+
this.deferrals.delete(oldest);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
emitAudit(outcomes) {
|
|
147
|
+
if (!this.adapters.auditTick)
|
|
148
|
+
return;
|
|
149
|
+
let killed = 0, wouldKill = 0, alertOnly = 0, deferred = 0;
|
|
150
|
+
for (const o of outcomes) {
|
|
151
|
+
if (o.outcome === 'alert-only')
|
|
152
|
+
alertOnly++;
|
|
153
|
+
else if (o.outcome.action === 'killed')
|
|
154
|
+
killed++;
|
|
155
|
+
else if (o.outcome.action === 'would-kill')
|
|
156
|
+
wouldKill++;
|
|
157
|
+
else if (o.outcome.action === 'deferred')
|
|
158
|
+
deferred++;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
this.adapters.auditTick({ at: this.lastTickAt ?? this.adapters.nowMs(), candidates: outcomes.length, killed, wouldKill, alertOnly, deferred });
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
/* audit is best-effort — never break a tick on an audit-write failure */
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=ExternalHogSentinel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExternalHogSentinel.js","sourceRoot":"","sources":["../../src/monitoring/ExternalHogSentinel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExternalHogServerPrimitives — builds the real-OS ExternalHogPrimitives the server injects into
|
|
3
|
+
* createExternalHogAdapters (CMT-1901). Kept OUT of the giant server command so the one non-trivial
|
|
4
|
+
* composed primitive — the §4.5 kill-time CPU micro-probe — is unit-testable in isolation.
|
|
5
|
+
*
|
|
6
|
+
* Everything here is thin over injected low-level deps (an async `exec`, `process.kill`, a monotonic
|
|
7
|
+
* clock, a `sleep`, the intelligence `evaluate`, the `raiseAttention` sink, the resolved config
|
|
8
|
+
* getter, the arm-file loader). The server passes the real implementations; a test passes fakes.
|
|
9
|
+
*/
|
|
10
|
+
import type { ExternalHogPrimitives } from './ExternalHogRealAdapters.js';
|
|
11
|
+
/** The low-level deps the server wires in. */
|
|
12
|
+
export interface ServerPrimitiveDeps {
|
|
13
|
+
/** Run a command off the event loop → stdout (execFile-based; rejects on non-zero exit). */
|
|
14
|
+
exec(cmd: string, args: readonly string[]): Promise<string>;
|
|
15
|
+
/** Send a signal (0 = aliveness probe). */
|
|
16
|
+
signal(pid: number, sig: 'SIGTERM' | 'SIGKILL' | 0): boolean;
|
|
17
|
+
/** The classifier model call (intelligence.evaluate, routed off-Claude by default). */
|
|
18
|
+
evaluate(prompt: string): Promise<string>;
|
|
19
|
+
/** Raise ONE attention item (already mapped to the platform's createAttentionItem in the server). */
|
|
20
|
+
raiseAttention(item: {
|
|
21
|
+
title: string;
|
|
22
|
+
body: string;
|
|
23
|
+
priority?: 'low' | 'medium' | 'high';
|
|
24
|
+
source?: string;
|
|
25
|
+
}): Promise<unknown> | unknown;
|
|
26
|
+
/** The LIVE, dev-gate-RESOLVED kill-capability config (enabled resolved, dryRun from config). */
|
|
27
|
+
config(): {
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
dryRun: boolean;
|
|
30
|
+
};
|
|
31
|
+
readonly stateDir: string;
|
|
32
|
+
readonly ownEuid: number;
|
|
33
|
+
readonly serverPid: number;
|
|
34
|
+
/** Await ms (injected so tests don't really sleep). */
|
|
35
|
+
sleep(ms: number): Promise<void>;
|
|
36
|
+
auditRow?(row: unknown): void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the §4.5 kill-time CPU re-confirm probe: sample a pid's cumulative cputime TWICE over
|
|
40
|
+
* `windowMs` (monotonic Δwall) and resolve core-equivalents RIGHT NOW. Returns null (→ the caller
|
|
41
|
+
* aborts the kill, fail-safe) if the pid vanished, is unreadable, was pid-REUSED mid-window
|
|
42
|
+
* (startTime changed), or the delta is UNKNOWN (non-positive/implausible window per the reviewed
|
|
43
|
+
* CPU-delta guards). Pure over the injected exec/now/sleep — testable with fakes.
|
|
44
|
+
*/
|
|
45
|
+
export declare function makeCpuCoresOver(exec: (cmd: string, args: readonly string[]) => Promise<string>, nowMs: () => number, sleep: (ms: number) => Promise<void>): (pid: number, windowMs: number) => Promise<number | null>;
|
|
46
|
+
/** Parse `tmux list-panes -a -F '#{pane_pid}'` output into pids. Pure. */
|
|
47
|
+
export declare function parseTmuxPanePids(out: string): number[];
|
|
48
|
+
/** Assemble the full ExternalHogPrimitives from the low-level server deps. */
|
|
49
|
+
export declare function createExternalHogServerPrimitives(deps: ServerPrimitiveDeps): ExternalHogPrimitives;
|
|
50
|
+
//# sourceMappingURL=ExternalHogServerPrimitives.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExternalHogServerPrimitives.d.ts","sourceRoot":"","sources":["../../src/monitoring/ExternalHogServerPrimitives.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAG1E,8CAA8C;AAC9C,MAAM,WAAW,mBAAmB;IAClC,4FAA4F;IAC5F,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5D,2CAA2C;IAC3C,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,GAAG,SAAS,GAAG,CAAC,GAAG,OAAO,CAAC;IAC7D,uFAAuF;IACvF,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,qGAAqG;IACrG,cAAc,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACzI,iGAAiG;IACjG,MAAM,IAAI;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uDAAuD;IACvD,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,QAAQ,CAAC,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/B;AAID;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,EAC/D,KAAK,EAAE,MAAM,MAAM,EACnB,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GACnC,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyB3D;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAOvD;AAED,8EAA8E;AAC9E,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,mBAAmB,GAAG,qBAAqB,CA4BlG"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExternalHogServerPrimitives — builds the real-OS ExternalHogPrimitives the server injects into
|
|
3
|
+
* createExternalHogAdapters (CMT-1901). Kept OUT of the giant server command so the one non-trivial
|
|
4
|
+
* composed primitive — the §4.5 kill-time CPU micro-probe — is unit-testable in isolation.
|
|
5
|
+
*
|
|
6
|
+
* Everything here is thin over injected low-level deps (an async `exec`, `process.kill`, a monotonic
|
|
7
|
+
* clock, a `sleep`, the intelligence `evaluate`, the `raiseAttention` sink, the resolved config
|
|
8
|
+
* getter, the arm-file loader). The server passes the real implementations; a test passes fakes.
|
|
9
|
+
*/
|
|
10
|
+
import { monotonicNowMs, computeCoreEquivalents, isUnknown } from './ExternalHogCpuDelta.js';
|
|
11
|
+
import { parseProcTable } from './ExternalHogProcTable.js';
|
|
12
|
+
import { loadArmState } from './ExternalHogArmStore.js';
|
|
13
|
+
const CPU_PROBE_PS_ARGS = ['-o', 'pid=,ppid=,uid=,lstart=,time=,comm='];
|
|
14
|
+
/**
|
|
15
|
+
* Build the §4.5 kill-time CPU re-confirm probe: sample a pid's cumulative cputime TWICE over
|
|
16
|
+
* `windowMs` (monotonic Δwall) and resolve core-equivalents RIGHT NOW. Returns null (→ the caller
|
|
17
|
+
* aborts the kill, fail-safe) if the pid vanished, is unreadable, was pid-REUSED mid-window
|
|
18
|
+
* (startTime changed), or the delta is UNKNOWN (non-positive/implausible window per the reviewed
|
|
19
|
+
* CPU-delta guards). Pure over the injected exec/now/sleep — testable with fakes.
|
|
20
|
+
*/
|
|
21
|
+
export function makeCpuCoresOver(exec, nowMs, sleep) {
|
|
22
|
+
return async (pid, windowMs) => {
|
|
23
|
+
const readOne = async () => {
|
|
24
|
+
try {
|
|
25
|
+
const at = nowMs();
|
|
26
|
+
const row = parseProcTable(await exec('ps', [...CPU_PROBE_PS_ARGS, '-p', String(pid)]))[0];
|
|
27
|
+
if (!row || row.cputimeSeconds === undefined || !Number.isFinite(row.cputimeSeconds))
|
|
28
|
+
return null;
|
|
29
|
+
return { cpu: row.cputimeSeconds, start: row.startTime, at };
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// @silent-fallback-ok: fail-SAFE direction — a probe that can't read the process
|
|
33
|
+
// yields null = an uncertain reading, which the measurement REFUSES to trust (no kill).
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const a = await readOne();
|
|
38
|
+
if (!a)
|
|
39
|
+
return null;
|
|
40
|
+
await sleep(Math.max(0, windowMs));
|
|
41
|
+
const b = await readOne();
|
|
42
|
+
if (!b)
|
|
43
|
+
return null;
|
|
44
|
+
if (b.start !== a.start)
|
|
45
|
+
return null; // pid reused mid-window → identity changed → abort
|
|
46
|
+
const prev = { cumulativeCpuSeconds: a.cpu, monotonicWallMs: a.at };
|
|
47
|
+
const curr = { cumulativeCpuSeconds: b.cpu, monotonicWallMs: b.at };
|
|
48
|
+
const cores = computeCoreEquivalents(prev, curr, { intendedWindowMs: windowMs });
|
|
49
|
+
return isUnknown(cores) ? null : cores;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Parse `tmux list-panes -a -F '#{pane_pid}'` output into pids. Pure. */
|
|
53
|
+
export function parseTmuxPanePids(out) {
|
|
54
|
+
const pids = [];
|
|
55
|
+
for (const line of out.split('\n')) {
|
|
56
|
+
const n = Number(line.trim());
|
|
57
|
+
if (Number.isInteger(n) && n > 0)
|
|
58
|
+
pids.push(n);
|
|
59
|
+
}
|
|
60
|
+
return pids;
|
|
61
|
+
}
|
|
62
|
+
/** Assemble the full ExternalHogPrimitives from the low-level server deps. */
|
|
63
|
+
export function createExternalHogServerPrimitives(deps) {
|
|
64
|
+
const now = monotonicNowMs;
|
|
65
|
+
return {
|
|
66
|
+
exec: deps.exec,
|
|
67
|
+
signal: deps.signal,
|
|
68
|
+
cpuCoresOver: makeCpuCoresOver(deps.exec, now, deps.sleep),
|
|
69
|
+
callModel: deps.evaluate,
|
|
70
|
+
raiseAttention: deps.raiseAttention,
|
|
71
|
+
now,
|
|
72
|
+
ownEuid: () => deps.ownEuid,
|
|
73
|
+
serverPid: () => deps.serverPid,
|
|
74
|
+
listTmuxPanePids: async () => {
|
|
75
|
+
try {
|
|
76
|
+
return parseTmuxPanePids(await deps.exec('tmux', ['list-panes', '-a', '-F', '#{pane_pid}']));
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// @silent-fallback-ok: no tmux server / tmux unavailable → empty pane set. The server
|
|
80
|
+
// pid remains an owned root, and any process a lost pane hosted still has a LIVING
|
|
81
|
+
// parent (the tmux server), so the orphan floor invariant blocks a kill regardless.
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
loadArm: () => {
|
|
86
|
+
const s = loadArmState(deps.stateDir);
|
|
87
|
+
return { marker: s.marker, lastDisarmEpoch: s.lastDisarmEpoch };
|
|
88
|
+
},
|
|
89
|
+
config: deps.config,
|
|
90
|
+
...(deps.auditRow ? { auditRow: deps.auditRow } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=ExternalHogServerPrimitives.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExternalHogServerPrimitives.js","sourceRoot":"","sources":["../../src/monitoring/ExternalHogServerPrimitives.ts"],"names":[],"mappings":"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"}
|