// Die Prozess-Leine (Windows-Arbeitsgruppe / Job Object) — KONZEPT § 3.3, // gemessen in beweise/leine.js am 30.08.2026. Der Befund dahinter // (SAVEPOINT v4.0-rc10): Windows räumt Kindprozesse NICHT auf. Ein hart // beendeter Rippy hinterließe ein makemkvcon, das das Laufwerk festhält. // // Mechanik: Dieser Prozess legt die Arbeitsgruppe an, setzt // KILL_ON_JOB_CLOSE und hängt SICH SELBST hinein. Kinder (der Kern, später // makemkvcon/HandBrakeCLI im Kern) erben die Mitgliedschaft. Stirbt der // Haupt-Prozess — Absturz, taskkill /F, Drüber-Installieren —, schließt // Windows sein einziges Handle auf die Gruppe, und ALLE Mitglieder sterben // mit (§ 4.1: „hält die Prozess-Leine — ALLES stirbt mit ihm"). // // WARUM die Leine HIER liegt und nicht in kern/werkzeuge/leine.ts (wie die // Ordnerliste § 4.2 sie einsortiert): Das Handle muss bei dem Prozess liegen, // dessen Tod alles mitreißen soll — und das ist laut § 4.1 der Haupt-Prozess. // Hielte der Kern das Handle, wäre ein hart beendetes Haupt wieder die // rc10-Waisen-Falle. Damit ist diese Datei neben kern/laufwerk/win32.ts der // ZWEITE erlaubte koffi-Ort; der Wächter-Test R1 kennt genau diese zwei. import koffi from 'koffi' // Konstanten aus winnt.h — dieselbe Herleitung wie beweise/leine.js: const JOB_OBJECT_EXTENDED_LIMIT_INFORMATION_KLASSE = 9 // JobObjectExtendedLimitInformation const JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000 // JOBOBJECT_EXTENDED_LIMIT_INFORMATION ist 144 Byte auf x64 (Rippy v5 ist // x64-only, § 8); LimitFlags liegt im eingebetteten BASIC_LIMIT_INFORMATION // bei Offset 16. const STRUKTUR_GROESSE = 144 const LIMIT_FLAGS_OFFSET = 16 export type LeineStatus = { ok: true } | { ok: false; fehler: string } let gesetzt: LeineStatus | null = null /** * Setzt die Prozess-Leine für DIESEN Prozess. Einmal pro Programmlauf, * VOR dem Start des Kerns — Kinder erben die Mitgliedschaft nur, wenn sie * nach dem Anlegen gestartet werden. * * Ein Fehlschlag wird als Wert zurückgegeben, nie verschluckt (R4): Der * Aufrufer protokolliert ihn und zeigt ihn im Fenster an. */ export function leineSetzen(): LeineStatus { if (gesetzt !== null) return gesetzt try { const kernel32 = koffi.load('kernel32.dll') const CreateJobObjectW = kernel32.func( 'void* __stdcall CreateJobObjectW(void *lpJobAttributes, str16 lpName)', ) const SetInformationJobObject = kernel32.func( 'bool __stdcall SetInformationJobObject(void *hJob, int JobObjectInformationClass, void *lpJobObjectInformation, uint32_t cbJobObjectInformationLength)', ) const AssignProcessToJobObject = kernel32.func( 'bool __stdcall AssignProcessToJobObject(void *hJob, void *hProcess)', ) const GetCurrentProcess = kernel32.func('void* __stdcall GetCurrentProcess()') const GetLastError = kernel32.func('uint32_t __stdcall GetLastError()') const job = CreateJobObjectW(null, null) if (koffi.address(job) === 0n) { gesetzt = { ok: false, fehler: `CreateJobObjectW: Win32-Fehler ${GetLastError()}` } return gesetzt } const info = Buffer.alloc(STRUKTUR_GROESSE) info.writeUInt32LE(JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, LIMIT_FLAGS_OFFSET) if ( !SetInformationJobObject( job, JOB_OBJECT_EXTENDED_LIMIT_INFORMATION_KLASSE, info, STRUKTUR_GROESSE, ) ) { gesetzt = { ok: false, fehler: `SetInformationJobObject: Win32-Fehler ${GetLastError()}` } return gesetzt } if (!AssignProcessToJobObject(job, GetCurrentProcess())) { gesetzt = { ok: false, fehler: `AssignProcessToJobObject: Win32-Fehler ${GetLastError()}` } return gesetzt } // Das Job-Handle wird ABSICHTLICH nie geschlossen — sein Zuschnappen beim // Prozess-Tod IST der Mechanismus. gesetzt = { ok: true } return gesetzt } catch (fehler) { gesetzt = { ok: false, fehler: `koffi/kernel32 nicht verfügbar: ${String(fehler)}` } return gesetzt } }