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.
Files changed (134) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +92 -1
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +28 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/FileClassifier.d.ts.map +1 -1
  8. package/dist/core/FileClassifier.js +5 -0
  9. package/dist/core/FileClassifier.js.map +1 -1
  10. package/dist/core/PostUpdateMigrator.d.ts +12 -0
  11. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  12. package/dist/core/PostUpdateMigrator.js +61 -2
  13. package/dist/core/PostUpdateMigrator.js.map +1 -1
  14. package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
  15. package/dist/core/WriteDomainRegistry.js +16 -0
  16. package/dist/core/WriteDomainRegistry.js.map +1 -1
  17. package/dist/core/componentCategories.d.ts.map +1 -1
  18. package/dist/core/componentCategories.js +1 -0
  19. package/dist/core/componentCategories.js.map +1 -1
  20. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  21. package/dist/core/devGatedFeatures.js +6 -0
  22. package/dist/core/devGatedFeatures.js.map +1 -1
  23. package/dist/core/types.d.ts +50 -0
  24. package/dist/core/types.d.ts.map +1 -1
  25. package/dist/core/types.js.map +1 -1
  26. package/dist/data/llmBenchCoverage.d.ts.map +1 -1
  27. package/dist/data/llmBenchCoverage.js +18 -0
  28. package/dist/data/llmBenchCoverage.js.map +1 -1
  29. package/dist/monitoring/ExternalHogArmMarker.d.ts +65 -0
  30. package/dist/monitoring/ExternalHogArmMarker.d.ts.map +1 -0
  31. package/dist/monitoring/ExternalHogArmMarker.js +0 -0
  32. package/dist/monitoring/ExternalHogArmMarker.js.map +1 -0
  33. package/dist/monitoring/ExternalHogArmStore.d.ts +46 -0
  34. package/dist/monitoring/ExternalHogArmStore.d.ts.map +1 -0
  35. package/dist/monitoring/ExternalHogArmStore.js +124 -0
  36. package/dist/monitoring/ExternalHogArmStore.js.map +1 -0
  37. package/dist/monitoring/ExternalHogClassifier.d.ts +62 -0
  38. package/dist/monitoring/ExternalHogClassifier.d.ts.map +1 -0
  39. package/dist/monitoring/ExternalHogClassifier.js +0 -0
  40. package/dist/monitoring/ExternalHogClassifier.js.map +1 -0
  41. package/dist/monitoring/ExternalHogClassifierPrompt.d.ts +29 -0
  42. package/dist/monitoring/ExternalHogClassifierPrompt.d.ts.map +1 -0
  43. package/dist/monitoring/ExternalHogClassifierPrompt.js +70 -0
  44. package/dist/monitoring/ExternalHogClassifierPrompt.js.map +1 -0
  45. package/dist/monitoring/ExternalHogCpuDelta.d.ts +50 -0
  46. package/dist/monitoring/ExternalHogCpuDelta.d.ts.map +1 -0
  47. package/dist/monitoring/ExternalHogCpuDelta.js +79 -0
  48. package/dist/monitoring/ExternalHogCpuDelta.js.map +1 -0
  49. package/dist/monitoring/ExternalHogFactBuilder.d.ts +71 -0
  50. package/dist/monitoring/ExternalHogFactBuilder.d.ts.map +1 -0
  51. package/dist/monitoring/ExternalHogFactBuilder.js +111 -0
  52. package/dist/monitoring/ExternalHogFactBuilder.js.map +1 -0
  53. package/dist/monitoring/ExternalHogFloor.d.ts +87 -0
  54. package/dist/monitoring/ExternalHogFloor.d.ts.map +1 -0
  55. package/dist/monitoring/ExternalHogFloor.js +123 -0
  56. package/dist/monitoring/ExternalHogFloor.js.map +1 -0
  57. package/dist/monitoring/ExternalHogGuardStatus.d.ts +29 -0
  58. package/dist/monitoring/ExternalHogGuardStatus.d.ts.map +1 -0
  59. package/dist/monitoring/ExternalHogGuardStatus.js +27 -0
  60. package/dist/monitoring/ExternalHogGuardStatus.js.map +1 -0
  61. package/dist/monitoring/ExternalHogKillFunnel.d.ts +77 -0
  62. package/dist/monitoring/ExternalHogKillFunnel.d.ts.map +1 -0
  63. package/dist/monitoring/ExternalHogKillFunnel.js +72 -0
  64. package/dist/monitoring/ExternalHogKillFunnel.js.map +1 -0
  65. package/dist/monitoring/ExternalHogKillLedger.d.ts +76 -0
  66. package/dist/monitoring/ExternalHogKillLedger.d.ts.map +1 -0
  67. package/dist/monitoring/ExternalHogKillLedger.js +84 -0
  68. package/dist/monitoring/ExternalHogKillLedger.js.map +1 -0
  69. package/dist/monitoring/ExternalHogNoticeCoalescer.d.ts +46 -0
  70. package/dist/monitoring/ExternalHogNoticeCoalescer.d.ts.map +1 -0
  71. package/dist/monitoring/ExternalHogNoticeCoalescer.js +67 -0
  72. package/dist/monitoring/ExternalHogNoticeCoalescer.js.map +1 -0
  73. package/dist/monitoring/ExternalHogOwnership.d.ts +48 -0
  74. package/dist/monitoring/ExternalHogOwnership.d.ts.map +1 -0
  75. package/dist/monitoring/ExternalHogOwnership.js +60 -0
  76. package/dist/monitoring/ExternalHogOwnership.js.map +1 -0
  77. package/dist/monitoring/ExternalHogProcTable.d.ts +37 -0
  78. package/dist/monitoring/ExternalHogProcTable.d.ts.map +1 -0
  79. package/dist/monitoring/ExternalHogProcTable.js +65 -0
  80. package/dist/monitoring/ExternalHogProcTable.js.map +1 -0
  81. package/dist/monitoring/ExternalHogRealAdapters.d.ts +91 -0
  82. package/dist/monitoring/ExternalHogRealAdapters.d.ts.map +1 -0
  83. package/dist/monitoring/ExternalHogRealAdapters.js +247 -0
  84. package/dist/monitoring/ExternalHogRealAdapters.js.map +1 -0
  85. package/dist/monitoring/ExternalHogSampler.d.ts +62 -0
  86. package/dist/monitoring/ExternalHogSampler.d.ts.map +1 -0
  87. package/dist/monitoring/ExternalHogSampler.js +0 -0
  88. package/dist/monitoring/ExternalHogSampler.js.map +1 -0
  89. package/dist/monitoring/ExternalHogScanTick.d.ts +89 -0
  90. package/dist/monitoring/ExternalHogScanTick.d.ts.map +1 -0
  91. package/dist/monitoring/ExternalHogScanTick.js +126 -0
  92. package/dist/monitoring/ExternalHogScanTick.js.map +1 -0
  93. package/dist/monitoring/ExternalHogSentinel.d.ts +127 -0
  94. package/dist/monitoring/ExternalHogSentinel.d.ts.map +1 -0
  95. package/dist/monitoring/ExternalHogSentinel.js +168 -0
  96. package/dist/monitoring/ExternalHogSentinel.js.map +1 -0
  97. package/dist/monitoring/ExternalHogServerPrimitives.d.ts +50 -0
  98. package/dist/monitoring/ExternalHogServerPrimitives.d.ts.map +1 -0
  99. package/dist/monitoring/ExternalHogServerPrimitives.js +93 -0
  100. package/dist/monitoring/ExternalHogServerPrimitives.js.map +1 -0
  101. package/dist/monitoring/ExternalHogSustained.d.ts +49 -0
  102. package/dist/monitoring/ExternalHogSustained.d.ts.map +1 -0
  103. package/dist/monitoring/ExternalHogSustained.js +62 -0
  104. package/dist/monitoring/ExternalHogSustained.js.map +1 -0
  105. package/dist/monitoring/guardManifest.d.ts.map +1 -1
  106. package/dist/monitoring/guardManifest.js +24 -0
  107. package/dist/monitoring/guardManifest.js.map +1 -1
  108. package/dist/scaffold/templates.d.ts.map +1 -1
  109. package/dist/scaffold/templates.js +3 -2
  110. package/dist/scaffold/templates.js.map +1 -1
  111. package/dist/server/AgentServer.d.ts +3 -0
  112. package/dist/server/AgentServer.d.ts.map +1 -1
  113. package/dist/server/AgentServer.js +1 -0
  114. package/dist/server/AgentServer.js.map +1 -1
  115. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  116. package/dist/server/CapabilityIndex.js +1 -0
  117. package/dist/server/CapabilityIndex.js.map +1 -1
  118. package/dist/server/routes.d.ts +3 -0
  119. package/dist/server/routes.d.ts.map +1 -1
  120. package/dist/server/routes.js +79 -5
  121. package/dist/server/routes.js.map +1 -1
  122. package/dist/testing/selfActionRegistry.d.ts.map +1 -1
  123. package/dist/testing/selfActionRegistry.js +51 -0
  124. package/dist/testing/selfActionRegistry.js.map +1 -1
  125. package/package.json +1 -1
  126. package/scripts/lint-scrape-fixture-realness.js +14 -0
  127. package/src/data/builtin-manifest.json +64 -64
  128. package/src/data/llmBenchCoverage.ts +18 -0
  129. package/src/scaffold/templates.ts +3 -2
  130. package/upgrades/1.3.763.md +23 -0
  131. package/upgrades/1.3.764.md +45 -0
  132. package/upgrades/side-effects/actionclaim-config-shape-fix.eli16.md +50 -0
  133. package/upgrades/side-effects/actionclaim-config-shape-fix.md +149 -0
  134. 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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wDAAwD;AAExD,OAAO,EAAE,WAAW,EAAmF,MAAM,0BAA0B,CAAC;AACxI,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAOlE,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAuDxE,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAErC,yFAAyF;AACzF,MAAM,UAAU,aAAa,CAAC,KAA8B;IAC1D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAoB,CAAC;IACzC,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC;YAAE,SAAS;QACzD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gFAAgF;AAChF,SAAS,UAAU,CAAC,CAAyB;IAC3C,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC;AACxE,CAAC;AACD,4FAA4F;AAC5F,SAAS,UAAU,CAAC,CAAyB;IAC3C,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,gBAAgB,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;AACnI,CAAC;AAED,MAAM,OAAO,mBAAmB;IAQX;IACA;IARX,KAAK,GAAc,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,EAAE,iBAAiB,EAAE,SAAS,EAAE,qBAAqB,EAAE,CAAC;IACzH,4FAA4F;IAC3E,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC/C,UAAU,GAAkB,IAAI,CAAC;IACjC,YAAY,GAA2B,EAAE,CAAC;IAElD,YACmB,QAA6B,EAC7B,IAA4B;QAD5B,aAAQ,GAAR,QAAQ,CAAqB;QAC7B,SAAI,GAAJ,IAAI,CAAwB;IAC5C,CAAC;IAEJ;;;;;OAKG;IACH,KAAK,CAAC,IAAI;QACR,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;QAClD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;QAElC,MAAM,IAAI,GAAa;YACrB,aAAa,EAAE,GAAG,EAAE,CAAC,KAAK;YAC1B,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;YACvC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC;YACjD,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;YACtD,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC1C,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc;YAC5C,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE;YAClC,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;SACpD,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9D,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC;QAC9B,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QAExC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7C,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAChC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;OAKG;IACH,kBAAkB;QAChB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;QACtC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,IAAI,CAAC,EAAE,CAAC;IAC1G,CAAC;IAED,yFAAyF;IACzF,MAAM;QACJ,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;QACtC,0FAA0F;QAC1F,2FAA2F;QAC3F,wFAAwF;QACxF,8FAA8F;QAC9F,wFAAwF;QACxF,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,KAAK,IAAI,CAAC;QAC9D,MAAM,WAAW,GAAG,CAAC,UAAU;eAC1B,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QAChG,OAAO;YACL,cAAc,EAAE,yBAAyB,CAAC;gBACxC,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,WAAW;aACpF,CAAC;YACF,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,WAAW;YACX,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,cAAc,EAAE,IAAI,CAAC,YAAY;YACjC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI;SACtC,CAAC;IACJ,CAAC;IAED,sFAAsF;IAC9E,eAAe,CAAC,QAAgC;QACtD,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,IAAI,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC1B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9E,CAAC;iBAAM,IAAI,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;gBACjC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,+BAA+B;YACrE,CAAC;QACH,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,wBAAwB,CAAC;QACjE,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,GAAG,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,gCAAgC;YACnF,IAAI,MAAM,KAAK,SAAS;gBAAE,MAAM;YAChC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAEO,SAAS,CAAC,QAAgC;QAChD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS;YAAE,OAAO;QACrC,IAAI,MAAM,GAAG,CAAC,EAAE,SAAS,GAAG,CAAC,EAAE,SAAS,GAAG,CAAC,EAAE,QAAQ,GAAG,CAAC,CAAC;QAC3D,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,IAAI,CAAC,CAAC,OAAO,KAAK,YAAY;gBAAE,SAAS,EAAE,CAAC;iBACvC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,QAAQ;gBAAE,MAAM,EAAE,CAAC;iBAC5C,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,YAAY;gBAAE,SAAS,EAAE,CAAC;iBACnD,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,UAAU;gBAAE,QAAQ,EAAE,CAAC;QACvD,CAAC;QACD,IAAI,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QACjJ,CAAC;QAAC,MAAM,CAAC;YACP,yEAAyE;QAC3E,CAAC;IACH,CAAC;CACF"}
@@ -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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"}