instar 1.3.761 → 1.3.763

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 +54 -0
  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 +2 -1
  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 +64 -0
  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/scripts/model-registry-freshness.manifest.json +2 -2
  128. package/src/data/builtin-manifest.json +64 -64
  129. package/src/data/llmBenchCoverage.ts +18 -0
  130. package/src/scaffold/templates.ts +2 -1
  131. package/upgrades/1.3.762.md +29 -0
  132. package/upgrades/1.3.763.md +23 -0
  133. package/upgrades/side-effects/doorway-model-registry-inc5.md +118 -0
  134. package/upgrades/side-effects/external-hog-sentinel.md +832 -0
@@ -0,0 +1,71 @@
1
+ /**
2
+ * ExternalHogFactBuilder — the STAGE-2 deterministic fact + identity derivation (CMT-1901,
3
+ * docs/specs/external-hog-zombie-autokill-sentinel.md §2, §4). PURE over its inputs.
4
+ *
5
+ * Given a candidate's proc row, its FULL argv (fetched off-loop by the impure adapter), the
6
+ * process tree, the instar-owned set, and a couple of I/O-gathered inputs (own euid, the set of
7
+ * launchctl-labeled pids), this builds the `ExternalHogFacts` the floor evaluates and the
8
+ * identity (classId / commandHash / ledgerKey) the ledger + funnel key on. It NEVER spawns a
9
+ * process or reads a clock — the impure edges (ps -o args=, launchctl, geteuid) live in the
10
+ * adapter and are passed IN, so every derivation here is unit-testable.
11
+ *
12
+ * The load-bearing derivation is `ownerAppRunning` (§ round-6/round-8): for the editor-exthost
13
+ * class it means "is the SPECIFIC `--parentPid` process that spawned this exthost still alive?"
14
+ * — NOT "any window of the app," which would veto a real zombie whenever any editor window is
15
+ * open (the exact 2026-07-03 anchor incident, where multiple windows is the common case). Every
16
+ * un-establishable branch fails toward `ownerAppRunning:true` → the floor VETOES (alert-never-kill).
17
+ */
18
+ import type { ProcTableRow } from './ExternalHogProcTable.js';
19
+ import { type ProcTree, type OwnedRefs } from './ExternalHogOwnership.js';
20
+ import { type ExternalHogFacts } from './ExternalHogFloor.js';
21
+ export interface FactBuilderInput {
22
+ /** The candidate's proc row (from the parsed ps table). */
23
+ readonly row: ProcTableRow;
24
+ /** The candidate's FULL argv (ps -o args=), fetched off-loop by the adapter. */
25
+ readonly argv: string;
26
+ readonly tree: ProcTree;
27
+ readonly ownedRefs: OwnedRefs;
28
+ readonly maxAncestorHops: number;
29
+ /** The sentinel's own effective uid (process.geteuid()), passed in by the adapter. */
30
+ readonly ownEuid: number;
31
+ /** The set of pids that are labeled launchd jobs (from `launchctl`), gathered by the adapter. */
32
+ readonly launchctlLabeledPids: ReadonlySet<number>;
33
+ /** Was the candidate over the core threshold THIS window? (The sampler guarantees true for a
34
+ * candidate; the orchestrator ANDs this with the N-window streak — see ExternalHogSustained.) */
35
+ readonly sustainedThisWindow: boolean;
36
+ }
37
+ /** Extract the class-parameterized spawning parent pid (`--parentPid=N`) from argv; null if absent. */
38
+ export declare function parseParentPid(argv: string): number | null;
39
+ /**
40
+ * Parse an `lstart` token (`ps lstart=` — "Wed Jul 2 10:00:00 2026") to epoch millis, for
41
+ * ORDERING ONLY (the reused-parent-pid check). Returns null on ANY ambiguity — a null forces the
42
+ * caller to the fail-safe (conservative veto) branch. The proc-table parser keeps `startTime` an
43
+ * opaque equality token by design; this targeted parse is confined to the one ordering comparison.
44
+ */
45
+ export declare function lstartToEpochMs(startTime: string): number | null;
46
+ /**
47
+ * Is the SPECIFIC spawning parent still alive? (§ round-8 provenance path b.)
48
+ * - no `--parentPid` in argv → owner cannot be established → TRUE (veto, safe)
49
+ * - `--parentPid=N` absent from the table → parent dead → FALSE (kill-eligible)
50
+ * - `N` present, its start-time LATER than this child's → `N` was reused, real parent dead → FALSE
51
+ * - `N` present, started before-or-equal the child (or start-times un-orderable) → alive/uncertain
52
+ * → TRUE (veto, safe)
53
+ */
54
+ export declare function deriveOwnerAppRunning(row: ProcTableRow, argv: string, tree: ProcTree): boolean;
55
+ /**
56
+ * Build the deterministic facts the floor evaluates. Returns null only if the row is structurally
57
+ * unusable (a non-finite pid) — every OTHER uncertainty is encoded as a veto-leaning fact value,
58
+ * never a silent drop (the §4 broad-observability floor still surfaces it).
59
+ */
60
+ export declare function buildFacts(input: FactBuilderInput): ExternalHogFacts | null;
61
+ /**
62
+ * Derive the kill identity for a candidate: its allowlist class, a stable command-hash, and the
63
+ * ledger key the P19 breaker uses. Returns null when the candidate is OUTSIDE the code-defined
64
+ * allowlist (not kill-eligible) — the caller then surfaces it (observability) but never kills it.
65
+ */
66
+ export declare function buildIdentity(facts: ExternalHogFacts): {
67
+ classId: string;
68
+ commandHash: string;
69
+ ledgerKey: string;
70
+ } | null;
71
+ //# sourceMappingURL=ExternalHogFactBuilder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExternalHogFactBuilder.d.ts","sourceRoot":"","sources":["../../src/monitoring/ExternalHogFactBuilder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAiB,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACzF,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEnF,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,sFAAsF;IACtF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iGAAiG;IACjG,QAAQ,CAAC,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACnD;sGACkG;IAClG,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;CACvC;AAID,uGAAuG;AACvG,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM1D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIhE;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAW9F;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,IAAI,CAmB3E;AAQD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,gBAAgB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAKzH"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * ExternalHogFactBuilder — the STAGE-2 deterministic fact + identity derivation (CMT-1901,
3
+ * docs/specs/external-hog-zombie-autokill-sentinel.md §2, §4). PURE over its inputs.
4
+ *
5
+ * Given a candidate's proc row, its FULL argv (fetched off-loop by the impure adapter), the
6
+ * process tree, the instar-owned set, and a couple of I/O-gathered inputs (own euid, the set of
7
+ * launchctl-labeled pids), this builds the `ExternalHogFacts` the floor evaluates and the
8
+ * identity (classId / commandHash / ledgerKey) the ledger + funnel key on. It NEVER spawns a
9
+ * process or reads a clock — the impure edges (ps -o args=, launchctl, geteuid) live in the
10
+ * adapter and are passed IN, so every derivation here is unit-testable.
11
+ *
12
+ * The load-bearing derivation is `ownerAppRunning` (§ round-6/round-8): for the editor-exthost
13
+ * class it means "is the SPECIFIC `--parentPid` process that spawned this exthost still alive?"
14
+ * — NOT "any window of the app," which would veto a real zombie whenever any editor window is
15
+ * open (the exact 2026-07-03 anchor incident, where multiple windows is the common case). Every
16
+ * un-establishable branch fails toward `ownerAppRunning:true` → the floor VETOES (alert-never-kill).
17
+ */
18
+ import { createHash } from 'node:crypto';
19
+ import { isInstarOwned } from './ExternalHogOwnership.js';
20
+ import { matchAllowlistClass } from './ExternalHogFloor.js';
21
+ const PARENT_PID_RE = /--parentPid[= ](\d+)/;
22
+ /** Extract the class-parameterized spawning parent pid (`--parentPid=N`) from argv; null if absent. */
23
+ export function parseParentPid(argv) {
24
+ if (typeof argv !== 'string')
25
+ return null;
26
+ const m = PARENT_PID_RE.exec(argv);
27
+ if (!m)
28
+ return null;
29
+ const n = Number(m[1]);
30
+ return Number.isInteger(n) && n > 0 ? n : null;
31
+ }
32
+ /**
33
+ * Parse an `lstart` token (`ps lstart=` — "Wed Jul 2 10:00:00 2026") to epoch millis, for
34
+ * ORDERING ONLY (the reused-parent-pid check). Returns null on ANY ambiguity — a null forces the
35
+ * caller to the fail-safe (conservative veto) branch. The proc-table parser keeps `startTime` an
36
+ * opaque equality token by design; this targeted parse is confined to the one ordering comparison.
37
+ */
38
+ export function lstartToEpochMs(startTime) {
39
+ if (typeof startTime !== 'string' || startTime.trim().length === 0)
40
+ return null;
41
+ const ms = Date.parse(startTime);
42
+ return Number.isFinite(ms) ? ms : null;
43
+ }
44
+ /**
45
+ * Is the SPECIFIC spawning parent still alive? (§ round-8 provenance path b.)
46
+ * - no `--parentPid` in argv → owner cannot be established → TRUE (veto, safe)
47
+ * - `--parentPid=N` absent from the table → parent dead → FALSE (kill-eligible)
48
+ * - `N` present, its start-time LATER than this child's → `N` was reused, real parent dead → FALSE
49
+ * - `N` present, started before-or-equal the child (or start-times un-orderable) → alive/uncertain
50
+ * → TRUE (veto, safe)
51
+ */
52
+ export function deriveOwnerAppRunning(row, argv, tree) {
53
+ const n = parseParentPid(argv);
54
+ if (n === null)
55
+ return true; // no provenance token → cannot establish owner-dead → veto
56
+ const parent = tree.get(n);
57
+ if (!parent)
58
+ return false; // the specific parent pid is gone → dead → kill-eligible
59
+ // Parent pid is present — is it the REAL parent, or a reused pid now hosting a newer process?
60
+ const childMs = lstartToEpochMs(row.startTime);
61
+ const parentMs = lstartToEpochMs(parent.startTime);
62
+ if (childMs === null || parentMs === null)
63
+ return true; // can't order → assume live parent → veto
64
+ if (parentMs > childMs)
65
+ return false; // parent started AFTER the child ⇒ pid reused ⇒ real parent dead
66
+ return true; // parent older-or-equal ⇒ plausibly the live real parent ⇒ veto
67
+ }
68
+ /**
69
+ * Build the deterministic facts the floor evaluates. Returns null only if the row is structurally
70
+ * unusable (a non-finite pid) — every OTHER uncertainty is encoded as a veto-leaning fact value,
71
+ * never a silent drop (the §4 broad-observability floor still surfaces it).
72
+ */
73
+ export function buildFacts(input) {
74
+ const { row, argv, tree, ownedRefs, maxAncestorHops, ownEuid, launchctlLabeledPids } = input;
75
+ if (!row || !Number.isInteger(row.pid) || row.pid <= 0)
76
+ return null;
77
+ return {
78
+ name: row.comm,
79
+ argv,
80
+ pid: row.pid,
81
+ // Defense-in-depth: a candidate is already non-instar-owned (the sampler filtered), recompute.
82
+ isInstarProcess: isInstarOwned(row.pid, tree, ownedRefs, maxAncestorHops),
83
+ // A root/system-owned process is never in our same-uid envelope → the floor vetoes it anyway,
84
+ // but flag it explicitly so the veto reason is honest (`system-root-daemon`).
85
+ ownerRootDaemon: row.uid === 0,
86
+ hasLaunchctlLabel: launchctlLabeledPids.has(row.pid),
87
+ ownerAppRunning: deriveOwnerAppRunning(row, argv, tree),
88
+ // Single-window confirmation; the orchestrator ANDs this with the N-window streak (§1).
89
+ sustainedHighCpu: input.sustainedThisWindow === true,
90
+ targetUid: row.uid,
91
+ ownEuid,
92
+ };
93
+ }
94
+ /** Strip the volatile provenance token so the command signature is STABLE across re-launches (a
95
+ * fresh exthost gets a new `--parentPid`; the breaker must count respawns of the SAME command). */
96
+ function stableCommandSignature(argv) {
97
+ return argv.replace(PARENT_PID_RE, '--parentPid=<>');
98
+ }
99
+ /**
100
+ * Derive the kill identity for a candidate: its allowlist class, a stable command-hash, and the
101
+ * ledger key the P19 breaker uses. Returns null when the candidate is OUTSIDE the code-defined
102
+ * allowlist (not kill-eligible) — the caller then surfaces it (observability) but never kills it.
103
+ */
104
+ export function buildIdentity(facts) {
105
+ const classId = matchAllowlistClass(facts.name, facts.argv);
106
+ if (!classId)
107
+ return null; // outside the narrow killable envelope
108
+ const commandHash = createHash('sha256').update(stableCommandSignature(facts.argv), 'utf8').digest('hex');
109
+ return { classId, commandHash, ledgerKey: `${classId}:${commandHash}` };
110
+ }
111
+ //# sourceMappingURL=ExternalHogFactBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExternalHogFactBuilder.js","sourceRoot":"","sources":["../../src/monitoring/ExternalHogFactBuilder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * ExternalHogFloor — the deterministic, VETO-ONLY safety floor of the External-Hog
3
+ * Zombie Auto-Kill Sentinel (CMT-1901, docs/specs/external-hog-zombie-autokill-sentinel.md §3-§4).
4
+ *
5
+ * SAFETY MODEL (signal-vs-authority): the intelligence (zombie-classify) holds the kill
6
+ * DECISION authority (kill/leave/alert); this floor holds only VETO authority. A kill
7
+ * executes iff `evaluateKillFloor(...).permitted === true && classifierVerdict === 'kill'`
8
+ * — a two-key AND. The floor can ONLY ever BLOCK a kill (downgrade it to an alert); it can
9
+ * NEVER trigger one. The model's authority is therefore purely SUBTRACTIVE: it may SPARE a
10
+ * process inside the envelope, but can never widen the envelope the floor has proven safe.
11
+ *
12
+ * The floor is a PURE function over a normalized fact set (no I/O). It fails CLOSED: any
13
+ * missing/unknowable invariant → NOT permitted (→ alert), never a kill. The process name
14
+ * and argv are attacker-controllable text, so the floor NEVER trusts them beyond the
15
+ * code-defined allowlist match — a cleverly-named process can at most be SPARED or land a
16
+ * kill INSIDE the allowlist envelope (the intended action), never expand the target set.
17
+ */
18
+ /** The code-defined allowlist: v1 seed = orphaned Electron editor extension-host WRAPPERS.
19
+ * NOT a config key — growing it is a reviewed source change (§3, blast-radius invariant).
20
+ * Each class requires BOTH a name-regex match AND an extension-host/language-server argv
21
+ * token (tolerant of extension-path-dominated argv — the token may appear anywhere). */
22
+ export interface AllowlistClass {
23
+ readonly id: string;
24
+ readonly nameRegex: RegExp;
25
+ }
26
+ export declare const EXTERNAL_HOG_ALLOWLIST: readonly AllowlistClass[];
27
+ /**
28
+ * Match a candidate against the code-defined allowlist. Returns the matched class id, or
29
+ * null. The name-regex is matched on the process `comm`/name (which the discovery layer
30
+ * reads); the argv token is matched on the FULL argv (never the truncatable `comm`).
31
+ */
32
+ export declare function matchAllowlistClass(name: string, fullArgv: string): string | null;
33
+ /**
34
+ * The ORDERED rule-source list for a class — the SINGLE source of truth the content-hash
35
+ * arm-scope is computed from (docs/specs §7-§8). BOTH the PIN arm route (building the marker's
36
+ * `allowlistSnapshot`) and the funnel's `currentClassContentHash` MUST derive the hash from THIS,
37
+ * so a class's armed hash and its re-checked hash always agree — and ANY change to the matcher
38
+ * (a new name-regex or argv token) yields a different hash, forcing a fresh PIN re-arm before that
39
+ * class can kill again. Returns null for an unknown class id. Ordered = [nameRegex source, then
40
+ * the shared argv-token sources] so a reordering (which changes anchored matching) is a new hash.
41
+ */
42
+ export declare function classRuleSources(classId: string): readonly string[] | null;
43
+ /**
44
+ * The deterministically-computed facts the floor evaluates. Every field is derived by the
45
+ * discovery/sampler layer from the live OS (never from model output). Optional fields that
46
+ * are UNKNOWN (undefined) fail the floor CLOSED — an unestablished invariant is a veto.
47
+ */
48
+ export interface ExternalHogFacts {
49
+ /** Process name / `comm` — attacker-controllable; only ever used for the allowlist regex. */
50
+ readonly name: string;
51
+ /** Full argv — attacker-controllable; only ever used for the allowlist argv-token match. */
52
+ readonly argv: string;
53
+ readonly pid: number;
54
+ /** Provenance: is the SPECIFIC spawning parent (argv `--parentPid`) still alive? `true` = owner
55
+ * app running (or owner cannot be positively established) → floor VETO; `false` = the specific
56
+ * `--parentPid` is dead (start-time-verified) → kill-eligible. NOT bare ppid===1. (The invariant
57
+ * at evaluateKillFloor step 5 vetoes when this is `true`.) */
58
+ readonly ownerAppRunning: boolean;
59
+ /** Confirmed sustained CPU hog (the §1 N-window CPU-delta ≥ cpuCoreThreshold). Hard veto. */
60
+ readonly sustainedHighCpu: boolean;
61
+ /** The process is instar-owned (server / session / sampler / a live-session descendant). */
62
+ readonly isInstarProcess: boolean;
63
+ /** The owner is a system/root daemon (fseventsd, WindowServer, …) — never killable. */
64
+ readonly ownerRootDaemon: boolean;
65
+ /** A labeled launchd job — managed, killing invites a respawn loop → veto. */
66
+ readonly hasLaunchctlLabel: boolean;
67
+ /** Target real/effective uid. Must equal the sentinel's own non-root euid (same-uid floor). */
68
+ readonly targetUid?: number;
69
+ /** The sentinel's own effective uid (non-root). */
70
+ readonly ownEuid?: number;
71
+ }
72
+ export type FloorVerdict = {
73
+ readonly permitted: true;
74
+ readonly matchedClass: string;
75
+ } | {
76
+ readonly permitted: false;
77
+ readonly vetoReason: string;
78
+ };
79
+ /**
80
+ * Evaluate the VETO-ONLY safety floor over the deterministic facts. Returns
81
+ * `{ permitted: true }` only when EVERY hard invariant holds; otherwise
82
+ * `{ permitted: false, vetoReason }` with the FIRST failing invariant. A `permitted:true`
83
+ * result is a NECESSARY (never sufficient) condition for a kill — the caller must ALSO
84
+ * have a `classifier === 'kill'` verdict. Fails CLOSED on any unknown invariant.
85
+ */
86
+ export declare function evaluateKillFloor(facts: ExternalHogFacts): FloorVerdict;
87
+ //# sourceMappingURL=ExternalHogFloor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExternalHogFloor.d.ts","sourceRoot":"","sources":["../../src/monitoring/ExternalHogFloor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;yFAGyF;AACzF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,sBAAsB,EAAE,SAAS,cAAc,EAKlD,CAAC;AAUX;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMjF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,IAAI,CAI1E;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4FAA4F;IAC5F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;mEAG+D;IAC/D,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,6FAA6F;IAC7F,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,4FAA4F;IAC5F,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,uFAAuF;IACvF,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,8EAA8E;IAC9E,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,+FAA+F;IAC/F,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,mDAAmD;IACnD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/D;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,YAAY,CAsDvE"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * ExternalHogFloor — the deterministic, VETO-ONLY safety floor of the External-Hog
3
+ * Zombie Auto-Kill Sentinel (CMT-1901, docs/specs/external-hog-zombie-autokill-sentinel.md §3-§4).
4
+ *
5
+ * SAFETY MODEL (signal-vs-authority): the intelligence (zombie-classify) holds the kill
6
+ * DECISION authority (kill/leave/alert); this floor holds only VETO authority. A kill
7
+ * executes iff `evaluateKillFloor(...).permitted === true && classifierVerdict === 'kill'`
8
+ * — a two-key AND. The floor can ONLY ever BLOCK a kill (downgrade it to an alert); it can
9
+ * NEVER trigger one. The model's authority is therefore purely SUBTRACTIVE: it may SPARE a
10
+ * process inside the envelope, but can never widen the envelope the floor has proven safe.
11
+ *
12
+ * The floor is a PURE function over a normalized fact set (no I/O). It fails CLOSED: any
13
+ * missing/unknowable invariant → NOT permitted (→ alert), never a kill. The process name
14
+ * and argv are attacker-controllable text, so the floor NEVER trusts them beyond the
15
+ * code-defined allowlist match — a cleverly-named process can at most be SPARED or land a
16
+ * kill INSIDE the allowlist envelope (the intended action), never expand the target set.
17
+ */
18
+ export const EXTERNAL_HOG_ALLOWLIST = [
19
+ { id: 'vscode-exthost', nameRegex: /^Code Helper \(Plugin\)$/ },
20
+ { id: 'cursor-exthost', nameRegex: /^Cursor Helper \(Plugin\)$/ },
21
+ { id: 'windsurf-exthost', nameRegex: /^Windsurf Helper \(Plugin\)$/ },
22
+ { id: 'vscodium-exthost', nameRegex: /^Code - OSS Helper \(Plugin\)$/ },
23
+ ];
24
+ /** The extension-host / language-server argv tokens a candidate must ALSO carry. Matched
25
+ * case-insensitively ANYWHERE in the full argv (incl. inside a path segment). */
26
+ const ALLOWLIST_ARGV_TOKENS = [
27
+ /extension[\s._-]*host/i,
28
+ /language[\s._-]*server/i,
29
+ /--type=extensionHost/i,
30
+ ];
31
+ /**
32
+ * Match a candidate against the code-defined allowlist. Returns the matched class id, or
33
+ * null. The name-regex is matched on the process `comm`/name (which the discovery layer
34
+ * reads); the argv token is matched on the FULL argv (never the truncatable `comm`).
35
+ */
36
+ export function matchAllowlistClass(name, fullArgv) {
37
+ if (typeof name !== 'string' || typeof fullArgv !== 'string')
38
+ return null;
39
+ const cls = EXTERNAL_HOG_ALLOWLIST.find((c) => c.nameRegex.test(name));
40
+ if (!cls)
41
+ return null;
42
+ const hasToken = ALLOWLIST_ARGV_TOKENS.some((t) => t.test(fullArgv));
43
+ return hasToken ? cls.id : null;
44
+ }
45
+ /**
46
+ * The ORDERED rule-source list for a class — the SINGLE source of truth the content-hash
47
+ * arm-scope is computed from (docs/specs §7-§8). BOTH the PIN arm route (building the marker's
48
+ * `allowlistSnapshot`) and the funnel's `currentClassContentHash` MUST derive the hash from THIS,
49
+ * so a class's armed hash and its re-checked hash always agree — and ANY change to the matcher
50
+ * (a new name-regex or argv token) yields a different hash, forcing a fresh PIN re-arm before that
51
+ * class can kill again. Returns null for an unknown class id. Ordered = [nameRegex source, then
52
+ * the shared argv-token sources] so a reordering (which changes anchored matching) is a new hash.
53
+ */
54
+ export function classRuleSources(classId) {
55
+ const cls = EXTERNAL_HOG_ALLOWLIST.find((c) => c.id === classId);
56
+ if (!cls)
57
+ return null;
58
+ return [cls.nameRegex.source, ...ALLOWLIST_ARGV_TOKENS.map((t) => t.source)];
59
+ }
60
+ /**
61
+ * Evaluate the VETO-ONLY safety floor over the deterministic facts. Returns
62
+ * `{ permitted: true }` only when EVERY hard invariant holds; otherwise
63
+ * `{ permitted: false, vetoReason }` with the FIRST failing invariant. A `permitted:true`
64
+ * result is a NECESSARY (never sufficient) condition for a kill — the caller must ALSO
65
+ * have a `classifier === 'kill'` verdict. Fails CLOSED on any unknown invariant.
66
+ */
67
+ export function evaluateKillFloor(facts) {
68
+ // (0) STRICT fail-closed guard — every required boolean invariant must be a genuine
69
+ // boolean. A missing/`undefined`/non-boolean value VETOES rather than skipping the
70
+ // check. This is load-bearing: `if (facts.X)` truthiness would let an `undefined`
71
+ // field fail OPEN, and the fail-closed property must NOT be delegated to the type
72
+ // system or sampler correctness — exactly the layer that degrades under the CPU/memory
73
+ // starvation this sentinel hunts (a sampler that times out computing `ownerAppRunning`
74
+ // and drops the field must never yield a permitted kill of a process whose orphanhood
75
+ // was never established). (round-11 — second-pass reviewer.)
76
+ const requiredBooleans = {
77
+ isInstarProcess: facts.isInstarProcess,
78
+ ownerRootDaemon: facts.ownerRootDaemon,
79
+ hasLaunchctlLabel: facts.hasLaunchctlLabel,
80
+ ownerAppRunning: facts.ownerAppRunning,
81
+ sustainedHighCpu: facts.sustainedHighCpu,
82
+ };
83
+ for (const [field, value] of Object.entries(requiredBooleans)) {
84
+ if (typeof value !== 'boolean')
85
+ return { permitted: false, vetoReason: `field-unknown:${field}` };
86
+ }
87
+ // (1) instar-own exclusion — never act on our own process tree (defense-in-depth; also
88
+ // excluded at discovery). Checked first so an own-process can never even be reasoned about.
89
+ if (facts.isInstarProcess)
90
+ return { permitted: false, vetoReason: 'instar-owned' };
91
+ // (2) same-uid floor invariant — a hard property, not incidental EPERM. The grant's scope
92
+ // is the granting operator's OWN processes. Unknown uid → fail closed.
93
+ if (facts.ownEuid === undefined || facts.targetUid === undefined) {
94
+ return { permitted: false, vetoReason: 'uid-unknown' };
95
+ }
96
+ if (facts.ownEuid === 0)
97
+ return { permitted: false, vetoReason: 'refuse-to-arm-as-root' };
98
+ if (facts.targetUid !== facts.ownEuid)
99
+ return { permitted: false, vetoReason: 'other-uid' };
100
+ // (3) system/root daemon — the OS itself is never killable (defense-in-depth denylist).
101
+ if (facts.ownerRootDaemon)
102
+ return { permitted: false, vetoReason: 'system-root-daemon' };
103
+ // (4) launchctl label — a managed launchd job; killing invites a respawn loop.
104
+ if (facts.hasLaunchctlLabel)
105
+ return { permitted: false, vetoReason: 'launchctl-labeled' };
106
+ // (5) provenance-of-orphanhood — the specific spawning parent must be DEAD. A live owner
107
+ // app (an exthost whose window is still open) is never a zombie.
108
+ if (facts.ownerAppRunning)
109
+ return { permitted: false, vetoReason: 'owner-app-running' };
110
+ // (6) sustainedHighCpu — HARD VETO. An idle orphan or a momentary spike is NEVER killed,
111
+ // regardless of the model's verdict — the feature stays scoped to CPU reclamation by
112
+ // construction, not by the model's judgment.
113
+ if (!facts.sustainedHighCpu)
114
+ return { permitted: false, vetoReason: 'not-sustained-hog' };
115
+ // (7) code-defined allowlist class — outside the narrow envelope is a veto regardless of
116
+ // the model's call. Attacker-controllable name/argv can at most land INSIDE this
117
+ // envelope (the intended action), never expand it.
118
+ const matchedClass = matchAllowlistClass(facts.name, facts.argv);
119
+ if (!matchedClass)
120
+ return { permitted: false, vetoReason: 'outside-allowlist' };
121
+ return { permitted: true, matchedClass };
122
+ }
123
+ //# sourceMappingURL=ExternalHogFloor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExternalHogFloor.js","sourceRoot":"","sources":["../../src/monitoring/ExternalHogFloor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAWH,MAAM,CAAC,MAAM,sBAAsB,GAA8B;IAC/D,EAAE,EAAE,EAAE,gBAAgB,EAAE,SAAS,EAAE,0BAA0B,EAAE;IAC/D,EAAE,EAAE,EAAE,gBAAgB,EAAE,SAAS,EAAE,4BAA4B,EAAE;IACjE,EAAE,EAAE,EAAE,kBAAkB,EAAE,SAAS,EAAE,8BAA8B,EAAE;IACrE,EAAE,EAAE,EAAE,kBAAkB,EAAE,SAAS,EAAE,gCAAgC,EAAE;CAC/D,CAAC;AAEX;kFACkF;AAClF,MAAM,qBAAqB,GAAsB;IAC/C,wBAAwB;IACxB,yBAAyB;IACzB,uBAAuB;CACxB,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,QAAgB;IAChE,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC1E,MAAM,GAAG,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACvE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrE,OAAO,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAClC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,MAAM,GAAG,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;IACjE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAC/E,CAAC;AAoCD;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAuB;IACvD,oFAAoF;IACpF,uFAAuF;IACvF,sFAAsF;IACtF,sFAAsF;IACtF,2FAA2F;IAC3F,2FAA2F;IAC3F,0FAA0F;IAC1F,iEAAiE;IACjE,MAAM,gBAAgB,GAAG;QACvB,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;KACzC,CAAC;IACF,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC9D,IAAI,OAAO,KAAK,KAAK,SAAS;YAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,iBAAiB,KAAK,EAAE,EAAE,CAAC;IACpG,CAAC;IAED,uFAAuF;IACvF,gGAAgG;IAChG,IAAI,KAAK,CAAC,eAAe;QAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC;IAEnF,0FAA0F;IAC1F,2EAA2E;IAC3E,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACjE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC;IACzD,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC;QAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,uBAAuB,EAAE,CAAC;IAC1F,IAAI,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,OAAO;QAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;IAE5F,wFAAwF;IACxF,IAAI,KAAK,CAAC,eAAe;QAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,CAAC;IAEzF,+EAA+E;IAC/E,IAAI,KAAK,CAAC,iBAAiB;QAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAE1F,yFAAyF;IACzF,qEAAqE;IACrE,IAAI,KAAK,CAAC,eAAe;QAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAExF,yFAAyF;IACzF,yFAAyF;IACzF,iDAAiD;IACjD,IAAI,CAAC,KAAK,CAAC,gBAAgB;QAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAE1F,yFAAyF;IACzF,qFAAqF;IACrF,uDAAuD;IACvD,MAAM,YAAY,GAAG,mBAAmB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACjE,IAAI,CAAC,YAAY;QAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAEhF,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * ExternalHogGuardStatus — the guard-posture state of the External-Hog sentinel
3
+ * (CMT-1901, docs/specs/external-hog-zombie-autokill-sentinel.md §1/§8).
4
+ *
5
+ * The posture must reflect VERIFIED kill-capability, NOT a config wish (the `/guards` honesty
6
+ * rule): `on-confirmed` ONLY when the feature is actually kill-capable (enabled && !dryRun && a
7
+ * VALID PIN armed marker). The reachable `config.dryRun:false` + marker-absent state reads
8
+ * `on-dry-run` (v1 maps the deferred `armed-pending` → `on-dry-run`), never `on-confirmed`. A
9
+ * dead sampler degrades to `on-stale` (§1) — NOT config-only. Pure function.
10
+ */
11
+ import type { GuardEffectiveState } from './guardPostureView.js';
12
+ export interface ExternalHogGuardInput {
13
+ /** The resolved `enabled` (dev-gate resolved). */
14
+ readonly enabled: boolean;
15
+ readonly dryRun: boolean;
16
+ /** Is the PIN armed marker currently VALID (armEpoch > lastDisarmEpoch)? */
17
+ readonly markerValid: boolean;
18
+ /** Is the sampler heartbeat stale past the sampler-dead threshold (§1)? */
19
+ readonly samplerDead: boolean;
20
+ }
21
+ /**
22
+ * Map the live external-hog state to a guard-posture effective state.
23
+ * - not enabled → `off`
24
+ * - sampler dead → `on-stale` (the §1 liveness degradation; feature is blind)
25
+ * - enabled && !dryRun && marker-valid → `on-confirmed` (actually kill-capable)
26
+ * - otherwise (dryRun OR no valid marker) → `on-dry-run` (watch-only / armed-pending)
27
+ */
28
+ export declare function externalHogEffectiveState(input: ExternalHogGuardInput): GuardEffectiveState;
29
+ //# sourceMappingURL=ExternalHogGuardStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExternalHogGuardStatus.d.ts","sourceRoot":"","sources":["../../src/monitoring/ExternalHogGuardStatus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,MAAM,WAAW,qBAAqB;IACpC,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,qBAAqB,GAAG,mBAAmB,CAK3F"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * ExternalHogGuardStatus — the guard-posture state of the External-Hog sentinel
3
+ * (CMT-1901, docs/specs/external-hog-zombie-autokill-sentinel.md §1/§8).
4
+ *
5
+ * The posture must reflect VERIFIED kill-capability, NOT a config wish (the `/guards` honesty
6
+ * rule): `on-confirmed` ONLY when the feature is actually kill-capable (enabled && !dryRun && a
7
+ * VALID PIN armed marker). The reachable `config.dryRun:false` + marker-absent state reads
8
+ * `on-dry-run` (v1 maps the deferred `armed-pending` → `on-dry-run`), never `on-confirmed`. A
9
+ * dead sampler degrades to `on-stale` (§1) — NOT config-only. Pure function.
10
+ */
11
+ /**
12
+ * Map the live external-hog state to a guard-posture effective state.
13
+ * - not enabled → `off`
14
+ * - sampler dead → `on-stale` (the §1 liveness degradation; feature is blind)
15
+ * - enabled && !dryRun && marker-valid → `on-confirmed` (actually kill-capable)
16
+ * - otherwise (dryRun OR no valid marker) → `on-dry-run` (watch-only / armed-pending)
17
+ */
18
+ export function externalHogEffectiveState(input) {
19
+ if (input.enabled !== true)
20
+ return 'off';
21
+ if (input.samplerDead === true)
22
+ return 'on-stale';
23
+ if (input.dryRun === false && input.markerValid === true)
24
+ return 'on-confirmed';
25
+ return 'on-dry-run';
26
+ }
27
+ //# sourceMappingURL=ExternalHogGuardStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExternalHogGuardStatus.js","sourceRoot":"","sources":["../../src/monitoring/ExternalHogGuardStatus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAcH;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAA4B;IACpE,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACzC,IAAI,KAAK,CAAC,WAAW,KAAK,IAAI;QAAE,OAAO,UAAU,CAAC;IAClD,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,IAAI,KAAK,CAAC,WAAW,KAAK,IAAI;QAAE,OAAO,cAAc,CAAC;IAChF,OAAO,YAAY,CAAC;AACtB,CAAC"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * ExternalHogKillFunnel — the hardened kill sequence of the External-Hog sentinel
3
+ * (CMT-1901, docs/specs/external-hog-zombie-autokill-sentinel.md §4).
4
+ *
5
+ * The ONLY place a real signal is sent. Structured so the watch-only guarantee is by
6
+ * construction: unless a LIVE kill is authorized (`canKillLive` — enabled && !dryRun && a valid
7
+ * PIN armed marker for this class), the funnel sends NO signal and returns a `would-kill`
8
+ * record. All I/O (fact re-read, arm re-read, fd-probe, signal send, aliveness, clock, wait) is
9
+ * INJECTED so the sequence is fully testable without ever killing a process.
10
+ *
11
+ * Sequence (a candidate that has already passed §4 Stage-A CPU admission + §5 classifier=kill):
12
+ * 1. Gate: re-read the LIVE arm state → not authorized ⇒ `would-kill` (dry-run / not-armed).
13
+ * 2. Stage-B (pre-SIGTERM): re-read the LIVE facts → the §4 floor must still PERMIT; abort→alert.
14
+ * 3. SIGTERM → wait `sigtermGraceMs`.
15
+ * 4. If the process exited during grace → `sigterm-exited` (no SIGKILL needed).
16
+ * 5. Stage-B (pre-SIGKILL): re-read facts + arm → any change/disarm ⇒ abort (never SIGKILL).
17
+ * 6. fd-skip: an open writable WORKSPACE file ⇒ DEFER the SIGKILL (bounded by maxKillDeferrals).
18
+ * 7. SIGKILL → `killed`.
19
+ */
20
+ import { type ExternalHogFacts } from './ExternalHogFloor.js';
21
+ import { type ArmConfig, type ArmMarker } from './ExternalHogArmMarker.js';
22
+ export type KillOutcome = {
23
+ action: 'would-kill';
24
+ reason: 'dry-run' | 'not-armed';
25
+ } | {
26
+ action: 'killed';
27
+ } | {
28
+ action: 'sigterm-exited';
29
+ } | {
30
+ action: 'deferred';
31
+ reason: 'writable-workspace-file';
32
+ } | {
33
+ action: 'aborted';
34
+ reason: string;
35
+ };
36
+ export interface KillTarget {
37
+ readonly pid: number;
38
+ readonly startTime: string;
39
+ readonly commandHash: string;
40
+ readonly classId: string;
41
+ }
42
+ export interface KillArmState {
43
+ readonly config: ArmConfig;
44
+ readonly marker: ArmMarker | null;
45
+ readonly lastDisarmEpoch: number;
46
+ }
47
+ export interface KillFunnelDeps {
48
+ /** Re-read the LIVE deterministic facts for (pid, startTime); null if gone/identity-changed.
49
+ * May be async (a fresh ps + argv read) — the funnel awaits it. */
50
+ reReadFacts(pid: number, startTime: string): ExternalHogFacts | null | Promise<ExternalHogFacts | null>;
51
+ /** Re-read the LIVE arm state (config + marker + lastDisarmEpoch). */
52
+ reReadArmState(): KillArmState;
53
+ /** The current content-hash of a class's compiled match rules (for the arm-scope check). */
54
+ currentClassContentHash(classId: string): string;
55
+ /** True if the pid holds an open writable REGULAR file under a workspace/document path.
56
+ * May be async (an lsof-shaped probe) — the funnel awaits it. */
57
+ hasOpenWritableWorkspaceFile(pid: number): boolean | Promise<boolean>;
58
+ /** Send a signal ('SIGTERM' | 'SIGKILL') to the pid. */
59
+ sendSignal(pid: number, signal: 'SIGTERM' | 'SIGKILL'): void;
60
+ /** Is (pid, startTime) still the live, same process? */
61
+ stillAlive(pid: number, startTime: string): boolean;
62
+ /** Await ms (injected so tests control the grace wait). */
63
+ wait(ms: number): Promise<void>;
64
+ }
65
+ export interface KillFunnelOpts {
66
+ readonly sigtermGraceMs: number;
67
+ readonly maxKillDeferrals: number;
68
+ /** How many times THIS target has already been deferred (caller-tracked). */
69
+ readonly currentDeferrals: number;
70
+ }
71
+ /**
72
+ * Run the kill funnel for one target. Sends real signals ONLY when a live kill is authorized at
73
+ * BOTH re-check points; otherwise returns a non-destructive outcome. Pure control flow over the
74
+ * injected I/O.
75
+ */
76
+ export declare function runKillFunnel(target: KillTarget, opts: KillFunnelOpts, deps: KillFunnelDeps): Promise<KillOutcome>;
77
+ //# sourceMappingURL=ExternalHogKillFunnel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExternalHogKillFunnel.d.ts","sourceRoot":"","sources":["../../src/monitoring/ExternalHogKillFunnel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAA0C,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACtG,OAAO,EAAe,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAExF,MAAM,MAAM,WAAW,GACnB;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,WAAW,CAAA;CAAE,GACzD;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAC5B;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,yBAAyB,CAAA;CAAE,GACzD;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,cAAc;IAC7B;wEACoE;IACpE,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACxG,sEAAsE;IACtE,cAAc,IAAI,YAAY,CAAC;IAC/B,4FAA4F;IAC5F,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACjD;sEACkE;IAClE,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,wDAAwD;IACxD,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,CAAC;IAC7D,wDAAwD;IACxD,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IACpD,2DAA2D;IAC3D,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,6EAA6E;IAC7E,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAoBD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CA4BxH"}