@fml-inc/fml 0.8.6 → 0.8.7

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.
@@ -1,5 +1,8 @@
1
1
  import {
2
- FML_HOOK_INBOX_DIR
2
+ FML_HOOK_INBOX_DIR,
3
+ FML_OBSERVER_LOCK_FILE,
4
+ FML_OBSERVER_PORT,
5
+ FML_OBSERVER_STATE_FILE
3
6
  } from "./chunk-B3H2YWPJ.js";
4
7
 
5
8
  // ../../packages/panopticon/src/observer/auth.ts
@@ -2922,6 +2925,7 @@ function openObserverDatabase(databaseFile, options = {}) {
2922
2925
  // ../../packages/panopticon/src/observer/protocol.ts
2923
2926
  var OBSERVER_PROTOCOL_VERSION = 1;
2924
2927
  var OBSERVER_SERVICE_NAME = "fml-observer";
2928
+ var OBSERVER_IDENTITY_HEADER = "x-fml-observer";
2925
2929
  function isObserverHealth(value) {
2926
2930
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
2927
2931
  const record = value;
@@ -2959,7 +2963,12 @@ function isAllowedObserverHost(host) {
2959
2963
  }
2960
2964
  function writeJson(res, status, body) {
2961
2965
  const payload = JSON.stringify(body);
2962
- res.writeHead(status, { "Content-Type": "application/json" });
2966
+ res.writeHead(status, {
2967
+ "Content-Type": "application/json",
2968
+ // Identify every reply, including rejections, so a hook never mistakes
2969
+ // another localhost service on a recorded port for the observer.
2970
+ [OBSERVER_IDENTITY_HEADER]: OBSERVER_SERVICE_NAME
2971
+ });
2963
2972
  res.end(payload);
2964
2973
  }
2965
2974
  function readHookRequestBody(req) {
@@ -3296,10 +3305,941 @@ function createObserverHttpServer(opts) {
3296
3305
  });
3297
3306
  }
3298
3307
 
3299
- // src/hooks/inbox.ts
3308
+ // src/observer/state.ts
3300
3309
  import { randomUUID as randomUUID2 } from "crypto";
3310
+ import { execFileSync } from "child_process";
3301
3311
  import fs3 from "fs";
3302
3312
  import path4 from "path";
3313
+ var OBSERVER_STATE_SCHEMA_VERSION = 1;
3314
+ var OBSERVER_HEARTBEAT_STALE_MS = 2 * 60 * 1e3;
3315
+ var OBSERVER_RECLAIM_GUARD_STALE_MS = 3e4;
3316
+ var OBSERVER_RECLAIM_GUARD_UNVERIFIED_TTL_MS = 5 * 6e4;
3317
+ var MAX_RECLAIM_GUARD_RECOVERIES = 2;
3318
+ var MAX_RECLAIM_GUARD_RACE_RETRIES = 16;
3319
+ var MAX_INITIAL_STATE_PUBLICATION_RETRIES = 2;
3320
+ var EXCLUSIVE_LINK_UNSUPPORTED_ERROR_CODES = /* @__PURE__ */ new Set([
3321
+ "ENOSYS",
3322
+ "ENOTSUP",
3323
+ "EOPNOTSUPP",
3324
+ "EPERM",
3325
+ "EXDEV"
3326
+ ]);
3327
+ var TRANSIENT_OBSERVER_FILE_ERROR_CODES = /* @__PURE__ */ new Set([
3328
+ "EACCES",
3329
+ "EAGAIN",
3330
+ "EBUSY",
3331
+ "EPERM"
3332
+ ]);
3333
+ var reclaimProcessNonce = randomUUID2();
3334
+ var activeReclaimGuardIds = /* @__PURE__ */ new Set();
3335
+ function isRecord2(value) {
3336
+ return !!value && typeof value === "object" && !Array.isArray(value);
3337
+ }
3338
+ function isObserverPhase(value) {
3339
+ return value === "starting" || value === "syncing" || value === "idle" || value === "waiting_for_auth" || value === "backing_off" || value === "stopping";
3340
+ }
3341
+ function optionalNumber(record, key) {
3342
+ const value = record[key];
3343
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3344
+ }
3345
+ function optionalString2(record, key) {
3346
+ const value = record[key];
3347
+ return typeof value === "string" ? value : void 0;
3348
+ }
3349
+ function parseObserverState(value) {
3350
+ if (!isRecord2(value)) return null;
3351
+ if (value.schemaVersion !== OBSERVER_STATE_SCHEMA_VERSION || value.service !== OBSERVER_SERVICE_NAME || typeof value.pid !== "number" || !Number.isInteger(value.pid) || value.pid <= 0 || typeof value.port !== "number" || !Number.isInteger(value.port) || value.port <= 0 || value.port > 65535 || typeof value.instanceId !== "string" || value.instanceId.length === 0 || typeof value.startedAtMs !== "number" || typeof value.heartbeatAtMs !== "number" || !isObserverPhase(value.phase)) {
3352
+ return null;
3353
+ }
3354
+ return {
3355
+ schemaVersion: OBSERVER_STATE_SCHEMA_VERSION,
3356
+ service: OBSERVER_SERVICE_NAME,
3357
+ pid: value.pid,
3358
+ port: value.port,
3359
+ instanceId: value.instanceId,
3360
+ startedAtMs: value.startedAtMs,
3361
+ heartbeatAtMs: value.heartbeatAtMs,
3362
+ phase: value.phase,
3363
+ processIdentity: optionalString2(value, "processIdentity"),
3364
+ environment: optionalString2(value, "environment"),
3365
+ lastAttemptAtMs: optionalNumber(value, "lastAttemptAtMs"),
3366
+ lastSuccessAtMs: optionalNumber(value, "lastSuccessAtMs"),
3367
+ nextAttemptAtMs: optionalNumber(value, "nextAttemptAtMs"),
3368
+ lastOutcome: optionalString2(value, "lastOutcome"),
3369
+ lastError: optionalString2(value, "lastError")
3370
+ };
3371
+ }
3372
+ function isPidAlive(pid) {
3373
+ try {
3374
+ process.kill(pid, 0);
3375
+ return true;
3376
+ } catch (error) {
3377
+ return error.code === "EPERM";
3378
+ }
3379
+ }
3380
+ function readObserverState(stateFile = FML_OBSERVER_STATE_FILE) {
3381
+ try {
3382
+ return parseObserverState(
3383
+ JSON.parse(fs3.readFileSync(stateFile, "utf8"))
3384
+ );
3385
+ } catch {
3386
+ return null;
3387
+ }
3388
+ }
3389
+ function readObserverStateStatus(opts = {}) {
3390
+ const stateFile = opts.stateFile ?? FML_OBSERVER_STATE_FILE;
3391
+ const exists = fs3.existsSync(stateFile);
3392
+ const state = readObserverState(stateFile);
3393
+ if (!state) {
3394
+ return { exists, valid: false, running: false, responsive: false, state };
3395
+ }
3396
+ const running = (opts.pidAlive ?? isPidAlive)(state.pid);
3397
+ return {
3398
+ exists,
3399
+ valid: true,
3400
+ running,
3401
+ responsive: running && (opts.nowMs ?? Date.now()) - state.heartbeatAtMs <= OBSERVER_HEARTBEAT_STALE_MS,
3402
+ state
3403
+ };
3404
+ }
3405
+ function createInitialObserverState(opts = {}) {
3406
+ const nowMs = opts.nowMs ?? Date.now();
3407
+ return {
3408
+ schemaVersion: OBSERVER_STATE_SCHEMA_VERSION,
3409
+ service: OBSERVER_SERVICE_NAME,
3410
+ pid: process.pid,
3411
+ port: opts.port ?? FML_OBSERVER_PORT,
3412
+ instanceId: randomUUID2(),
3413
+ startedAtMs: nowMs,
3414
+ heartbeatAtMs: nowMs,
3415
+ phase: "starting",
3416
+ processIdentity: getCurrentProcessIdentity() ?? void 0
3417
+ };
3418
+ }
3419
+ function serializeState(state) {
3420
+ return `${JSON.stringify(state, null, 2)}
3421
+ `;
3422
+ }
3423
+ var ObserverStateMutationBusyError = class extends Error {
3424
+ constructor() {
3425
+ super("observer lease mutation is already in progress");
3426
+ this.name = "ObserverStateMutationBusyError";
3427
+ }
3428
+ };
3429
+ function parseObserverLeaseOwner(value) {
3430
+ try {
3431
+ const parsed = JSON.parse(value);
3432
+ return typeof parsed.pid === "number" && Number.isInteger(parsed.pid) && parsed.pid > 0 && typeof parsed.instanceId === "string" && parsed.instanceId.length > 0 ? { pid: parsed.pid, instanceId: parsed.instanceId } : null;
3433
+ } catch {
3434
+ return null;
3435
+ }
3436
+ }
3437
+ function readObserverLeaseOwner(lockFile = FML_OBSERVER_LOCK_FILE) {
3438
+ try {
3439
+ return parseObserverLeaseOwner(fs3.readFileSync(lockFile, "utf8"));
3440
+ } catch {
3441
+ return null;
3442
+ }
3443
+ }
3444
+ function inspectObserverFile(file) {
3445
+ try {
3446
+ return {
3447
+ raw: fs3.readFileSync(file, "utf8"),
3448
+ exists: true,
3449
+ busy: false
3450
+ };
3451
+ } catch (error) {
3452
+ if (error.code === "ENOENT") {
3453
+ return { raw: null, exists: false, busy: false };
3454
+ }
3455
+ if (isTransientObserverFileError(error)) {
3456
+ return { raw: null, exists: true, busy: true };
3457
+ }
3458
+ throw error;
3459
+ }
3460
+ }
3461
+ function inspectObserverLease(opts = {}) {
3462
+ const stateResult = inspectObserverFile(
3463
+ opts.stateFile ?? FML_OBSERVER_STATE_FILE
3464
+ );
3465
+ const lockResult = inspectObserverFile(
3466
+ opts.lockFile ?? FML_OBSERVER_LOCK_FILE
3467
+ );
3468
+ let state = null;
3469
+ if (stateResult.raw !== null) {
3470
+ try {
3471
+ state = parseObserverState(JSON.parse(stateResult.raw));
3472
+ } catch {
3473
+ state = null;
3474
+ }
3475
+ }
3476
+ return {
3477
+ state,
3478
+ owner: lockResult.raw === null ? null : parseObserverLeaseOwner(lockResult.raw),
3479
+ stateExists: stateResult.exists,
3480
+ lockExists: lockResult.exists,
3481
+ stateBusy: stateResult.busy,
3482
+ lockBusy: lockResult.busy,
3483
+ busy: stateResult.busy || lockResult.busy
3484
+ };
3485
+ }
3486
+ function observerLeaseIsOwned(instanceId, lockFile = FML_OBSERVER_LOCK_FILE) {
3487
+ return readObserverLeaseOwner(lockFile)?.instanceId === instanceId;
3488
+ }
3489
+ function getProcessIdentity(pid) {
3490
+ try {
3491
+ if (process.platform === "linux") {
3492
+ const stat = fs3.readFileSync(`/proc/${pid}/stat`, "utf8");
3493
+ const fieldsAfterCommand = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
3494
+ const startTimeTicks = fieldsAfterCommand[19];
3495
+ return startTimeTicks ? `linux:${startTimeTicks}` : null;
3496
+ }
3497
+ if (process.platform === "darwin") {
3498
+ const startedAt = execFileSync(
3499
+ "ps",
3500
+ ["-p", String(pid), "-o", "lstart="],
3501
+ {
3502
+ encoding: "utf8",
3503
+ stdio: ["ignore", "pipe", "ignore"],
3504
+ timeout: 1e3
3505
+ }
3506
+ ).trim();
3507
+ return startedAt ? `darwin:${startedAt}` : null;
3508
+ }
3509
+ if (process.platform === "win32") {
3510
+ const startedAt = execFileSync(
3511
+ "powershell.exe",
3512
+ [
3513
+ "-NoProfile",
3514
+ "-NonInteractive",
3515
+ "-Command",
3516
+ `(Get-Process -Id ${pid}).StartTime.ToUniversalTime().Ticks`
3517
+ ],
3518
+ {
3519
+ encoding: "utf8",
3520
+ stdio: ["ignore", "pipe", "ignore"],
3521
+ timeout: 1e3
3522
+ }
3523
+ ).trim();
3524
+ return startedAt ? `win32:${startedAt}` : null;
3525
+ }
3526
+ } catch {
3527
+ }
3528
+ return null;
3529
+ }
3530
+ var currentProcessIdentityCache;
3531
+ function getCurrentProcessIdentity() {
3532
+ if (currentProcessIdentityCache === void 0) {
3533
+ currentProcessIdentityCache = getProcessIdentity(process.pid);
3534
+ }
3535
+ return currentProcessIdentityCache;
3536
+ }
3537
+ function parseObserverReclaimGuardOwner(value) {
3538
+ try {
3539
+ const parsed = JSON.parse(value);
3540
+ if (typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0 || typeof parsed.instanceId !== "string" || parsed.instanceId.length === 0 || typeof parsed.guardId !== "string" || parsed.guardId.length === 0 || typeof parsed.createdAtMs !== "number" || parsed.processIdentity !== void 0 && typeof parsed.processIdentity !== "string" || parsed.processNonce !== void 0 && typeof parsed.processNonce !== "string") {
3541
+ return null;
3542
+ }
3543
+ return parsed;
3544
+ } catch {
3545
+ return null;
3546
+ }
3547
+ }
3548
+ function isTransientObserverFileError(error) {
3549
+ const code = error?.code;
3550
+ return code !== void 0 && TRANSIENT_OBSERVER_FILE_ERROR_CODES.has(code);
3551
+ }
3552
+ function readObserverMutationFile(file) {
3553
+ try {
3554
+ return fs3.readFileSync(file, "utf8");
3555
+ } catch (error) {
3556
+ if (error.code === "ENOENT") return null;
3557
+ if (isTransientObserverFileError(error)) {
3558
+ throw new ObserverStateMutationBusyError();
3559
+ }
3560
+ throw error;
3561
+ }
3562
+ }
3563
+ function readObserverStateForMutation(stateFile) {
3564
+ const raw = readObserverMutationFile(stateFile);
3565
+ if (raw === null) return null;
3566
+ try {
3567
+ return parseObserverState(JSON.parse(raw));
3568
+ } catch {
3569
+ return null;
3570
+ }
3571
+ }
3572
+ function assertObserverStateMutationOwnership(instanceId, stateFile, lockFile, errorMessage) {
3573
+ const leaseOwner = readObserverMutationFile(lockFile);
3574
+ if (leaseOwner === null || parseObserverLeaseOwner(leaseOwner)?.instanceId !== instanceId) {
3575
+ throw new Error(errorMessage);
3576
+ }
3577
+ if (readObserverStateForMutation(stateFile)?.instanceId !== instanceId) {
3578
+ throw new Error(errorMessage);
3579
+ }
3580
+ }
3581
+ function isRetryableReclaimGuardRace(error) {
3582
+ return error.code === "ENOENT" || isTransientObserverFileError(error);
3583
+ }
3584
+ function removeReclaimTombstone(tombstone) {
3585
+ try {
3586
+ fs3.rmSync(tombstone, { force: true });
3587
+ } catch (error) {
3588
+ if (!isRetryableReclaimGuardRace(error)) throw error;
3589
+ }
3590
+ }
3591
+ function restoreDisplacedReclaimGuard(tombstone, reclaimFile, fallbackRaw) {
3592
+ let canonicalReserved = false;
3593
+ let tombstoneRestored = false;
3594
+ let fallbackPublished = false;
3595
+ try {
3596
+ fs3.linkSync(tombstone, reclaimFile);
3597
+ canonicalReserved = true;
3598
+ tombstoneRestored = true;
3599
+ } catch (error) {
3600
+ if (error.code === "EEXIST") {
3601
+ canonicalReserved = true;
3602
+ } else if (!isRetryableReclaimGuardRace(error)) {
3603
+ throw error;
3604
+ }
3605
+ }
3606
+ if (!canonicalReserved) {
3607
+ try {
3608
+ fs3.copyFileSync(tombstone, reclaimFile, fs3.constants.COPYFILE_EXCL);
3609
+ canonicalReserved = true;
3610
+ tombstoneRestored = true;
3611
+ } catch (error) {
3612
+ if (error.code === "EEXIST") {
3613
+ canonicalReserved = true;
3614
+ } else if (!isRetryableReclaimGuardRace(error)) {
3615
+ throw error;
3616
+ }
3617
+ }
3618
+ }
3619
+ if (!canonicalReserved) {
3620
+ let fd;
3621
+ try {
3622
+ fd = fs3.openSync(reclaimFile, "wx", 384);
3623
+ canonicalReserved = true;
3624
+ } catch (error) {
3625
+ if (error.code === "EEXIST") {
3626
+ canonicalReserved = true;
3627
+ } else if (isTransientObserverFileError(error)) {
3628
+ return false;
3629
+ } else {
3630
+ throw error;
3631
+ }
3632
+ fd = -1;
3633
+ }
3634
+ if (fd >= 0) {
3635
+ let publicationError;
3636
+ try {
3637
+ fs3.writeFileSync(fd, fallbackRaw ?? "");
3638
+ } catch (error) {
3639
+ publicationError = error;
3640
+ }
3641
+ try {
3642
+ fs3.closeSync(fd);
3643
+ } catch (error) {
3644
+ if (publicationError === void 0 || !isTransientObserverFileError(error)) {
3645
+ publicationError = error;
3646
+ }
3647
+ }
3648
+ if (publicationError !== void 0 && !isTransientObserverFileError(publicationError)) {
3649
+ throw publicationError;
3650
+ }
3651
+ fallbackPublished = publicationError === void 0 && fallbackRaw !== void 0;
3652
+ }
3653
+ }
3654
+ if (canonicalReserved && (fallbackPublished || tombstoneRestored)) {
3655
+ removeReclaimTombstone(tombstone);
3656
+ }
3657
+ return canonicalReserved;
3658
+ }
3659
+ function sameFileIdentity(left, right) {
3660
+ return left.dev === right.dev && left.ino === right.ino;
3661
+ }
3662
+ function removeCreatedFileByIdentity(file, created) {
3663
+ let observed;
3664
+ try {
3665
+ observed = fs3.statSync(file);
3666
+ } catch (error) {
3667
+ if (!isRetryableReclaimGuardRace(error)) throw error;
3668
+ return error.code === "ENOENT";
3669
+ }
3670
+ if (!sameFileIdentity(observed, created)) return true;
3671
+ const tombstone = `${file}.failed-${process.pid}-${randomUUID2()}`;
3672
+ try {
3673
+ fs3.renameSync(file, tombstone);
3674
+ } catch (error) {
3675
+ if (!isRetryableReclaimGuardRace(error)) throw error;
3676
+ return error.code === "ENOENT";
3677
+ }
3678
+ let displaced;
3679
+ try {
3680
+ displaced = fs3.statSync(tombstone);
3681
+ } catch (error) {
3682
+ if (!isRetryableReclaimGuardRace(error)) throw error;
3683
+ return restoreDisplacedReclaimGuard(tombstone, file);
3684
+ }
3685
+ let displacedRaw;
3686
+ try {
3687
+ displacedRaw = fs3.readFileSync(tombstone, "utf8");
3688
+ } catch (error) {
3689
+ if (!isRetryableReclaimGuardRace(error)) throw error;
3690
+ }
3691
+ if (sameFileIdentity(displaced, created)) {
3692
+ removeReclaimTombstone(tombstone);
3693
+ return true;
3694
+ }
3695
+ return restoreDisplacedReclaimGuard(tombstone, file, displacedRaw);
3696
+ }
3697
+ function rewriteExclusiveFileBody(fd, contents) {
3698
+ const body = Buffer.from(contents, "utf8");
3699
+ fs3.ftruncateSync(fd, 0);
3700
+ let offset = 0;
3701
+ while (offset < body.length) {
3702
+ const written = fs3.writeSync(
3703
+ fd,
3704
+ body,
3705
+ offset,
3706
+ body.length - offset,
3707
+ offset
3708
+ );
3709
+ if (written === 0) {
3710
+ throw Object.assign(
3711
+ new Error("observer guard rewrite made no progress"),
3712
+ {
3713
+ code: "EBUSY"
3714
+ }
3715
+ );
3716
+ }
3717
+ offset += written;
3718
+ }
3719
+ }
3720
+ function publishExclusiveLease(lockFile, serializedOwner) {
3721
+ const stagingFile = `${lockFile}.${process.pid}.${randomUUID2()}.tmp`;
3722
+ let fd = null;
3723
+ let staged = null;
3724
+ let publicationError;
3725
+ try {
3726
+ fd = fs3.openSync(stagingFile, "wx", 384);
3727
+ fs3.writeFileSync(fd, serializedOwner);
3728
+ fs3.fsyncSync(fd);
3729
+ staged = fs3.fstatSync(fd);
3730
+ const stagedFd = fd;
3731
+ fd = null;
3732
+ fs3.closeSync(stagedFd);
3733
+ try {
3734
+ fs3.linkSync(stagingFile, lockFile);
3735
+ } catch (error) {
3736
+ if (error.code === "EEXIST") return null;
3737
+ let linkedDespiteError = false;
3738
+ try {
3739
+ const published2 = fs3.statSync(lockFile);
3740
+ linkedDespiteError = sameFileIdentity(published2, staged) && fs3.readFileSync(lockFile, "utf8") === serializedOwner;
3741
+ } catch {
3742
+ }
3743
+ if (!linkedDespiteError) {
3744
+ const code = error.code;
3745
+ if (code !== void 0 && EXCLUSIVE_LINK_UNSUPPORTED_ERROR_CODES.has(code)) {
3746
+ throw new Error(
3747
+ `The filesystem containing ${lockFile} does not support atomic FML observer leases; choose an FML state or observer directory on a filesystem with hard-link support`,
3748
+ { cause: error }
3749
+ );
3750
+ }
3751
+ throw error;
3752
+ }
3753
+ }
3754
+ const published = fs3.statSync(lockFile);
3755
+ if (fs3.readFileSync(lockFile, "utf8") !== serializedOwner) {
3756
+ try {
3757
+ removeCreatedFileByIdentity(lockFile, published);
3758
+ } catch {
3759
+ }
3760
+ throw new Error(`FML observer lease publication was incomplete`);
3761
+ }
3762
+ return published;
3763
+ } catch (error) {
3764
+ publicationError = error;
3765
+ throw error;
3766
+ } finally {
3767
+ if (fd !== null) {
3768
+ try {
3769
+ fs3.closeSync(fd);
3770
+ } catch (closeError) {
3771
+ if (publicationError === void 0) throw closeError;
3772
+ }
3773
+ }
3774
+ try {
3775
+ fs3.rmSync(stagingFile, { force: true });
3776
+ } catch {
3777
+ }
3778
+ }
3779
+ }
3780
+ function ownsReclaimGuard(reclaimFile, guardId) {
3781
+ try {
3782
+ return parseObserverReclaimGuardOwner(fs3.readFileSync(reclaimFile, "utf8"))?.guardId === guardId;
3783
+ } catch {
3784
+ return false;
3785
+ }
3786
+ }
3787
+ function ownsReclaimGuardForMutation(reclaimFile, guardId) {
3788
+ const raw = readObserverMutationFile(reclaimFile);
3789
+ return raw !== null && parseObserverReclaimGuardOwner(raw)?.guardId === guardId;
3790
+ }
3791
+ function writeLeaseAndState(state, stateFile, lockFile, reclaimFile, reclaimGuardId) {
3792
+ const serializedOwner = `${JSON.stringify({
3793
+ pid: state.pid,
3794
+ instanceId: state.instanceId
3795
+ })}
3796
+ `;
3797
+ const created = publishExclusiveLease(lockFile, serializedOwner);
3798
+ if (!created) return false;
3799
+ if (!ownsReclaimGuard(reclaimFile, reclaimGuardId) || !observerLeaseIsOwned(state.instanceId, lockFile)) {
3800
+ removeCreatedFileByIdentity(lockFile, created);
3801
+ return false;
3802
+ }
3803
+ try {
3804
+ writeObserverState(state, stateFile);
3805
+ } catch (error) {
3806
+ try {
3807
+ removeCreatedFileByIdentity(lockFile, created);
3808
+ } catch {
3809
+ }
3810
+ throw error;
3811
+ }
3812
+ const coherent = ownsReclaimGuard(reclaimFile, reclaimGuardId) && observerLeaseIsOwned(state.instanceId, lockFile) && readObserverState(stateFile)?.instanceId === state.instanceId;
3813
+ if (!coherent) {
3814
+ try {
3815
+ removeCreatedFileByIdentity(lockFile, created);
3816
+ } catch {
3817
+ }
3818
+ }
3819
+ return coherent;
3820
+ }
3821
+ function leaseCanBeReclaimed(opts) {
3822
+ const owner = parseObserverLeaseOwner(opts.rawOwner);
3823
+ if (!owner) {
3824
+ if (opts.liveOwnerPolicy === "process_identity") {
3825
+ return false;
3826
+ }
3827
+ return opts.nowMs - opts.lockMtimeMs >= OBSERVER_RECLAIM_GUARD_STALE_MS;
3828
+ }
3829
+ if (!opts.pidAlive(owner.pid)) return true;
3830
+ const ownerState = readObserverState(opts.stateFile);
3831
+ if (opts.liveOwnerPolicy === "process_identity") {
3832
+ if (ownerState?.pid !== owner.pid || ownerState.instanceId !== owner.instanceId) {
3833
+ return false;
3834
+ }
3835
+ if (ownerState.processIdentity === void 0) {
3836
+ return false;
3837
+ }
3838
+ const currentIdentity = opts.processIdentity(owner.pid);
3839
+ return currentIdentity !== null && currentIdentity !== ownerState.processIdentity;
3840
+ }
3841
+ if (ownerState?.pid === owner.pid && ownerState.instanceId === owner.instanceId) {
3842
+ return opts.nowMs - ownerState.heartbeatAtMs > OBSERVER_HEARTBEAT_STALE_MS;
3843
+ }
3844
+ return opts.nowMs - opts.lockMtimeMs > OBSERVER_HEARTBEAT_STALE_MS;
3845
+ }
3846
+ function acquireReclaimGuard(owner, reclaimFile, nowMs, opts = {}) {
3847
+ const guardId = randomUUID2();
3848
+ const processIdentity = opts.processIdentity ?? ((pid) => pid === process.pid ? getCurrentProcessIdentity() : getProcessIdentity(pid));
3849
+ let recoveryAttempts = 0;
3850
+ let raceRetries = 0;
3851
+ while (recoveryAttempts < MAX_RECLAIM_GUARD_RECOVERIES && raceRetries <= MAX_RECLAIM_GUARD_RACE_RETRIES) {
3852
+ try {
3853
+ const fd = fs3.openSync(reclaimFile, "wx", 384);
3854
+ let created = null;
3855
+ const serializedGuard = `${JSON.stringify({
3856
+ pid: process.pid,
3857
+ instanceId: owner.instanceId,
3858
+ guardId,
3859
+ createdAtMs: nowMs,
3860
+ processIdentity: processIdentity(process.pid) ?? void 0,
3861
+ processNonce: reclaimProcessNonce
3862
+ })}
3863
+ `;
3864
+ let publicationError;
3865
+ let publicationFailed = false;
3866
+ let cleanupBlocked = false;
3867
+ try {
3868
+ created = fs3.fstatSync(fd);
3869
+ fs3.writeFileSync(fd, serializedGuard);
3870
+ } catch (error) {
3871
+ if (isTransientObserverFileError(error)) {
3872
+ try {
3873
+ rewriteExclusiveFileBody(fd, serializedGuard);
3874
+ } catch (retryError) {
3875
+ publicationFailed = true;
3876
+ publicationError = retryError;
3877
+ }
3878
+ } else {
3879
+ publicationFailed = true;
3880
+ publicationError = error;
3881
+ }
3882
+ if (publicationFailed && created) {
3883
+ try {
3884
+ cleanupBlocked = !removeCreatedFileByIdentity(reclaimFile, created);
3885
+ } catch (cleanupError) {
3886
+ publicationError = cleanupError;
3887
+ }
3888
+ }
3889
+ }
3890
+ let displacedAfterPublication = false;
3891
+ if (!publicationFailed) {
3892
+ try {
3893
+ displacedAfterPublication = parseObserverReclaimGuardOwner(fs3.readFileSync(reclaimFile, "utf8"))?.guardId !== guardId;
3894
+ } catch (error) {
3895
+ if (error.code === "ENOENT") {
3896
+ displacedAfterPublication = true;
3897
+ } else {
3898
+ publicationFailed = true;
3899
+ publicationError = error;
3900
+ if (isTransientObserverFileError(error) && created) {
3901
+ try {
3902
+ cleanupBlocked = !removeCreatedFileByIdentity(
3903
+ reclaimFile,
3904
+ created
3905
+ );
3906
+ } catch (cleanupError) {
3907
+ publicationError = cleanupError;
3908
+ }
3909
+ }
3910
+ }
3911
+ }
3912
+ }
3913
+ try {
3914
+ fs3.closeSync(fd);
3915
+ } catch (error) {
3916
+ if (!publicationFailed) publicationError = error;
3917
+ publicationFailed = true;
3918
+ }
3919
+ if (publicationFailed) {
3920
+ if (cleanupBlocked) return null;
3921
+ if (isTransientObserverFileError(publicationError)) {
3922
+ if (ownsReclaimGuard(reclaimFile, guardId)) {
3923
+ activeReclaimGuardIds.add(guardId);
3924
+ return guardId;
3925
+ }
3926
+ raceRetries += 1;
3927
+ continue;
3928
+ }
3929
+ throw publicationError;
3930
+ }
3931
+ if (displacedAfterPublication) return null;
3932
+ activeReclaimGuardIds.add(guardId);
3933
+ return guardId;
3934
+ } catch (error) {
3935
+ if (error.code !== "EEXIST") {
3936
+ if (isTransientObserverFileError(error)) return null;
3937
+ throw error;
3938
+ }
3939
+ let observedRaw;
3940
+ let observed;
3941
+ try {
3942
+ observedRaw = fs3.readFileSync(reclaimFile, "utf8");
3943
+ observed = fs3.statSync(reclaimFile);
3944
+ } catch (readError) {
3945
+ if (readError.code === "ENOENT") {
3946
+ raceRetries += 1;
3947
+ continue;
3948
+ }
3949
+ if (isTransientObserverFileError(readError)) return null;
3950
+ throw readError;
3951
+ }
3952
+ const observedOwner = parseObserverReclaimGuardOwner(observedRaw);
3953
+ if (observedOwner) {
3954
+ const alive = (opts.pidAlive ?? isPidAlive)(observedOwner.pid);
3955
+ const abandonedByThisProcess = observedOwner.processNonce === reclaimProcessNonce && !activeReclaimGuardIds.has(observedOwner.guardId);
3956
+ if (alive && !abandonedByThisProcess) {
3957
+ const currentIdentity = observedOwner.processIdentity === void 0 ? null : processIdentity(observedOwner.pid);
3958
+ if (currentIdentity !== null && currentIdentity === observedOwner.processIdentity) {
3959
+ return null;
3960
+ }
3961
+ if (currentIdentity === null && nowMs - observedOwner.createdAtMs < OBSERVER_RECLAIM_GUARD_UNVERIFIED_TTL_MS) {
3962
+ return null;
3963
+ }
3964
+ }
3965
+ } else if (nowMs - observed.mtimeMs < OBSERVER_RECLAIM_GUARD_STALE_MS) {
3966
+ return null;
3967
+ }
3968
+ const tombstone = `${reclaimFile}.stale-${process.pid}-${randomUUID2()}`;
3969
+ try {
3970
+ fs3.renameSync(reclaimFile, tombstone);
3971
+ } catch (renameError) {
3972
+ if (renameError.code === "ENOENT") {
3973
+ raceRetries += 1;
3974
+ continue;
3975
+ }
3976
+ if (isTransientObserverFileError(renameError)) return null;
3977
+ throw renameError;
3978
+ }
3979
+ recoveryAttempts += 1;
3980
+ let displacedRaw;
3981
+ try {
3982
+ displacedRaw = fs3.readFileSync(tombstone, "utf8");
3983
+ } catch (error2) {
3984
+ if (!isRetryableReclaimGuardRace(error2)) throw error2;
3985
+ restoreDisplacedReclaimGuard(tombstone, reclaimFile, observedRaw);
3986
+ return null;
3987
+ }
3988
+ let displaced;
3989
+ try {
3990
+ displaced = fs3.statSync(tombstone);
3991
+ } catch (error2) {
3992
+ if (!isRetryableReclaimGuardRace(error2)) throw error2;
3993
+ restoreDisplacedReclaimGuard(tombstone, reclaimFile, displacedRaw);
3994
+ return null;
3995
+ }
3996
+ if (displacedRaw !== observedRaw || displaced.dev !== observed.dev || displaced.ino !== observed.ino || displaced.mtimeMs !== observed.mtimeMs || !observedOwner && nowMs - displaced.mtimeMs < OBSERVER_RECLAIM_GUARD_STALE_MS) {
3997
+ restoreDisplacedReclaimGuard(tombstone, reclaimFile, displacedRaw);
3998
+ return null;
3999
+ }
4000
+ removeReclaimTombstone(tombstone);
4001
+ }
4002
+ }
4003
+ return null;
4004
+ }
4005
+ function releaseReclaimGuard(reclaimFile, guardId) {
4006
+ try {
4007
+ let observedRaw;
4008
+ let observed;
4009
+ try {
4010
+ observedRaw = fs3.readFileSync(reclaimFile, "utf8");
4011
+ observed = fs3.statSync(reclaimFile);
4012
+ } catch (error) {
4013
+ if (error.code === "ENOENT" || isTransientObserverFileError(error)) {
4014
+ return;
4015
+ }
4016
+ throw error;
4017
+ }
4018
+ if (parseObserverReclaimGuardOwner(observedRaw)?.guardId !== guardId) {
4019
+ return;
4020
+ }
4021
+ const tombstone = `${reclaimFile}.release-${process.pid}-${randomUUID2()}`;
4022
+ fs3.renameSync(reclaimFile, tombstone);
4023
+ let displacedRaw;
4024
+ try {
4025
+ displacedRaw = fs3.readFileSync(tombstone, "utf8");
4026
+ } catch (error) {
4027
+ if (!isRetryableReclaimGuardRace(error)) throw error;
4028
+ restoreDisplacedReclaimGuard(tombstone, reclaimFile, observedRaw);
4029
+ return;
4030
+ }
4031
+ let displaced;
4032
+ try {
4033
+ displaced = fs3.statSync(tombstone);
4034
+ } catch (error) {
4035
+ if (!isRetryableReclaimGuardRace(error)) throw error;
4036
+ restoreDisplacedReclaimGuard(tombstone, reclaimFile, displacedRaw);
4037
+ return;
4038
+ }
4039
+ if (displacedRaw !== observedRaw || displaced.dev !== observed.dev || displaced.ino !== observed.ino || displaced.mtimeMs !== observed.mtimeMs) {
4040
+ restoreDisplacedReclaimGuard(tombstone, reclaimFile, displacedRaw);
4041
+ return;
4042
+ }
4043
+ removeReclaimTombstone(tombstone);
4044
+ } catch (error) {
4045
+ if (error.code !== "ENOENT" && !isTransientObserverFileError(error)) {
4046
+ throw error;
4047
+ }
4048
+ } finally {
4049
+ activeReclaimGuardIds.delete(guardId);
4050
+ }
4051
+ }
4052
+ function claimObserverState(state, opts = {}) {
4053
+ const stateFile = opts.stateFile ?? FML_OBSERVER_STATE_FILE;
4054
+ const lockFile = opts.lockFile ?? FML_OBSERVER_LOCK_FILE;
4055
+ fs3.mkdirSync(path4.dirname(lockFile), { recursive: true, mode: 448 });
4056
+ const nowMs = opts.nowMs ?? Date.now();
4057
+ const reclaimFile = `${lockFile}.reclaim`;
4058
+ let initialPublicationRetries = 0;
4059
+ while (!fs3.existsSync(lockFile)) {
4060
+ const initialGuardId = acquireReclaimGuard(state, reclaimFile, nowMs, {
4061
+ pidAlive: opts.pidAlive,
4062
+ processIdentity: opts.processIdentity
4063
+ });
4064
+ if (!initialGuardId) return false;
4065
+ let retryPublication = false;
4066
+ try {
4067
+ try {
4068
+ if (writeLeaseAndState(
4069
+ state,
4070
+ stateFile,
4071
+ lockFile,
4072
+ reclaimFile,
4073
+ initialGuardId
4074
+ )) {
4075
+ return true;
4076
+ }
4077
+ } catch (error) {
4078
+ if (!isTransientObserverFileError(error) || initialPublicationRetries >= MAX_INITIAL_STATE_PUBLICATION_RETRIES) {
4079
+ throw error;
4080
+ }
4081
+ initialPublicationRetries += 1;
4082
+ retryPublication = true;
4083
+ }
4084
+ } finally {
4085
+ releaseReclaimGuard(reclaimFile, initialGuardId);
4086
+ }
4087
+ if (!retryPublication) break;
4088
+ }
4089
+ let observedRaw;
4090
+ let observedStat;
4091
+ try {
4092
+ observedRaw = fs3.readFileSync(lockFile, "utf8");
4093
+ observedStat = fs3.statSync(lockFile);
4094
+ } catch (error) {
4095
+ if (error.code === "ENOENT") return false;
4096
+ throw error;
4097
+ }
4098
+ const reclaimable = leaseCanBeReclaimed({
4099
+ rawOwner: observedRaw,
4100
+ lockMtimeMs: observedStat.mtimeMs,
4101
+ stateFile,
4102
+ nowMs,
4103
+ pidAlive: opts.pidAlive ?? isPidAlive,
4104
+ processIdentity: opts.processIdentity ?? getProcessIdentity,
4105
+ liveOwnerPolicy: opts.liveOwnerPolicy ?? "heartbeat"
4106
+ });
4107
+ if (!reclaimable) return false;
4108
+ const reclaimGuardId = acquireReclaimGuard(state, reclaimFile, nowMs, {
4109
+ pidAlive: opts.pidAlive,
4110
+ processIdentity: opts.processIdentity
4111
+ });
4112
+ if (!reclaimGuardId) return false;
4113
+ try {
4114
+ const currentRaw = fs3.readFileSync(lockFile, "utf8");
4115
+ const currentStat = fs3.statSync(lockFile);
4116
+ if (!ownsReclaimGuard(reclaimFile, reclaimGuardId) || currentRaw !== observedRaw || currentStat.dev !== observedStat.dev || currentStat.ino !== observedStat.ino || currentStat.mtimeMs !== observedStat.mtimeMs || !leaseCanBeReclaimed({
4117
+ rawOwner: currentRaw,
4118
+ lockMtimeMs: currentStat.mtimeMs,
4119
+ stateFile,
4120
+ nowMs,
4121
+ pidAlive: opts.pidAlive ?? isPidAlive,
4122
+ processIdentity: opts.processIdentity ?? getProcessIdentity,
4123
+ liveOwnerPolicy: opts.liveOwnerPolicy ?? "heartbeat"
4124
+ })) {
4125
+ return false;
4126
+ }
4127
+ fs3.unlinkSync(lockFile);
4128
+ return writeLeaseAndState(
4129
+ state,
4130
+ stateFile,
4131
+ lockFile,
4132
+ reclaimFile,
4133
+ reclaimGuardId
4134
+ );
4135
+ } catch (error) {
4136
+ if (error.code === "ENOENT") return false;
4137
+ throw error;
4138
+ } finally {
4139
+ releaseReclaimGuard(reclaimFile, reclaimGuardId);
4140
+ }
4141
+ }
4142
+ function writeObserverState(state, stateFile = FML_OBSERVER_STATE_FILE) {
4143
+ fs3.mkdirSync(path4.dirname(stateFile), { recursive: true, mode: 448 });
4144
+ const tempFile = `${stateFile}.${process.pid}.${randomUUID2()}.tmp`;
4145
+ try {
4146
+ fs3.writeFileSync(tempFile, serializeState(state), { mode: 384 });
4147
+ fs3.renameSync(tempFile, stateFile);
4148
+ } catch (error) {
4149
+ try {
4150
+ fs3.rmSync(tempFile, { force: true });
4151
+ } catch {
4152
+ }
4153
+ throw error;
4154
+ }
4155
+ try {
4156
+ fs3.chmodSync(stateFile, 384);
4157
+ } catch {
4158
+ }
4159
+ }
4160
+ function updateObserverState(state, update, stateFile = FML_OBSERVER_STATE_FILE, lockFile = FML_OBSERVER_LOCK_FILE) {
4161
+ const reclaimFile = `${lockFile}.reclaim`;
4162
+ const reclaimGuardId = acquireReclaimGuard(state, reclaimFile, Date.now());
4163
+ if (!reclaimGuardId) {
4164
+ assertObserverStateMutationOwnership(
4165
+ state.instanceId,
4166
+ stateFile,
4167
+ lockFile,
4168
+ "observer lease ownership was lost"
4169
+ );
4170
+ throw new ObserverStateMutationBusyError();
4171
+ }
4172
+ try {
4173
+ assertObserverStateMutationOwnership(
4174
+ state.instanceId,
4175
+ stateFile,
4176
+ lockFile,
4177
+ "observer lease ownership was lost"
4178
+ );
4179
+ if (!ownsReclaimGuardForMutation(reclaimFile, reclaimGuardId)) {
4180
+ throw new ObserverStateMutationBusyError();
4181
+ }
4182
+ const next = { ...state, ...update };
4183
+ try {
4184
+ writeObserverState(next, stateFile);
4185
+ } catch (error) {
4186
+ if (isTransientObserverFileError(error)) {
4187
+ assertObserverStateMutationOwnership(
4188
+ state.instanceId,
4189
+ stateFile,
4190
+ lockFile,
4191
+ "observer lease ownership was lost during state update"
4192
+ );
4193
+ if (!ownsReclaimGuardForMutation(reclaimFile, reclaimGuardId)) {
4194
+ throw new ObserverStateMutationBusyError();
4195
+ }
4196
+ throw new ObserverStateMutationBusyError();
4197
+ }
4198
+ throw error;
4199
+ }
4200
+ assertObserverStateMutationOwnership(
4201
+ state.instanceId,
4202
+ stateFile,
4203
+ lockFile,
4204
+ "observer lease ownership was lost during state update"
4205
+ );
4206
+ if (!ownsReclaimGuardForMutation(reclaimFile, reclaimGuardId)) {
4207
+ throw new ObserverStateMutationBusyError();
4208
+ }
4209
+ return next;
4210
+ } finally {
4211
+ releaseReclaimGuard(reclaimFile, reclaimGuardId);
4212
+ }
4213
+ }
4214
+ function removeObserverStateIfOwned(instanceId, opts = {}) {
4215
+ const stateFile = opts.stateFile ?? FML_OBSERVER_STATE_FILE;
4216
+ const lockFile = opts.lockFile ?? FML_OBSERVER_LOCK_FILE;
4217
+ const reclaimFile = `${lockFile}.reclaim`;
4218
+ const reclaimGuardId = acquireReclaimGuard(
4219
+ { pid: process.pid, instanceId },
4220
+ reclaimFile,
4221
+ Date.now()
4222
+ );
4223
+ if (!reclaimGuardId) {
4224
+ return;
4225
+ }
4226
+ try {
4227
+ if (!ownsReclaimGuard(reclaimFile, reclaimGuardId) || !observerLeaseIsOwned(instanceId, lockFile) || readObserverState(stateFile)?.instanceId !== instanceId) {
4228
+ return;
4229
+ }
4230
+ fs3.rmSync(stateFile, { force: true });
4231
+ if (ownsReclaimGuard(reclaimFile, reclaimGuardId) && observerLeaseIsOwned(instanceId, lockFile)) {
4232
+ fs3.rmSync(lockFile, { force: true });
4233
+ }
4234
+ } finally {
4235
+ releaseReclaimGuard(reclaimFile, reclaimGuardId);
4236
+ }
4237
+ }
4238
+
4239
+ // src/hooks/inbox.ts
4240
+ import { randomUUID as randomUUID3 } from "crypto";
4241
+ import fs4 from "fs";
4242
+ import path5 from "path";
3303
4243
 
3304
4244
  // src/hooks/inbox-config.ts
3305
4245
  var BYTE_SUFFIXES = {
@@ -3311,7 +4251,8 @@ var BYTE_SUFFIXES = {
3311
4251
  var MAX_BYTES_PATTERN = /^([1-9]\d*)(b|kib|mib|gib)?$/i;
3312
4252
  var POSITIVE_INTEGER_PATTERN = /^[1-9]\d*$/;
3313
4253
  var DEFAULT_HOOK_INBOX_MAX_BYTES = 512 * 1024 * 1024;
3314
- var DEFAULT_HOOK_INBOX_MAX_ENTRIES = 1e4;
4254
+ var SMALLEST_REALISTIC_HOOK_EVENT_BYTES = 2 * 1024;
4255
+ var DEFAULT_HOOK_INBOX_MAX_ENTRIES = DEFAULT_HOOK_INBOX_MAX_BYTES / SMALLEST_REALISTIC_HOOK_EVENT_BYTES;
3315
4256
  function parseHookInboxMaxBytes(configured) {
3316
4257
  if (configured === void 0) return DEFAULT_HOOK_INBOX_MAX_BYTES;
3317
4258
  const value = configured.trim();
@@ -3347,9 +4288,12 @@ function parseHookInboxMaxEntries(configured) {
3347
4288
  }
3348
4289
  return entries;
3349
4290
  }
4291
+ function hookInboxEntriesForBytes(maxBytes) {
4292
+ return Math.ceil(maxBytes / SMALLEST_REALISTIC_HOOK_EVENT_BYTES);
4293
+ }
3350
4294
  function hookInboxLimitsFromEnv(env = process.env) {
3351
4295
  const maxBytes = parseHookInboxMaxBytes(env.FML_HOOK_INBOX_MAX_BYTES);
3352
- const maxEntries = parseHookInboxMaxEntries(env.FML_HOOK_INBOX_MAX_ENTRIES);
4296
+ const maxEntries = env.FML_HOOK_INBOX_MAX_ENTRIES === void 0 ? maxBytes === void 0 ? void 0 : hookInboxEntriesForBytes(maxBytes) : parseHookInboxMaxEntries(env.FML_HOOK_INBOX_MAX_ENTRIES);
3353
4297
  return {
3354
4298
  ...maxBytes === void 0 ? {} : { maxBytes },
3355
4299
  ...maxEntries === void 0 ? {} : { maxEntries }
@@ -3360,6 +4304,7 @@ function hookInboxLimitsFromEnv(env = process.env) {
3360
4304
  var HOOK_INBOX_SCHEMA_VERSION = 1;
3361
4305
  var MAX_HOOK_INBOX_ENTRY_BYTES = 16 * 1024 * 1024;
3362
4306
  var MAX_HOOK_INBOX_DRAIN_ENTRIES = 100;
4307
+ var DRAIN_YIELD_EVERY_ENTRIES = 25;
3363
4308
  var MAX_HOOK_INBOX_DRAIN_CURSORS = 8;
3364
4309
  var INBOX_ID_PATTERN = /^\d{1,16}-[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
3365
4310
  var INBOX_TEMP_PATTERN = /^\.(\d{1,16}-[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\.\d+\.[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.tmp$/i;
@@ -3397,7 +4342,7 @@ var HookInboxCapacityError = class extends Error {
3397
4342
  this.reason = reason;
3398
4343
  }
3399
4344
  };
3400
- function isRecord2(value) {
4345
+ function isRecord3(value) {
3401
4346
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3402
4347
  }
3403
4348
  function validTimestamp(value) {
@@ -3408,7 +4353,7 @@ function validSyncId(value) {
3408
4353
  }
3409
4354
  function parseDestination(value) {
3410
4355
  if (value === void 0) return void 0;
3411
- if (!isRecord2(value)) return null;
4356
+ if (!isRecord3(value)) return null;
3412
4357
  const { targetUrl, destinationIdentity } = value;
3413
4358
  if (typeof targetUrl !== "string" || targetUrl.length === 0 || Buffer.byteLength(targetUrl, "utf8") > 4096 || typeof destinationIdentity !== "string" || destinationIdentity.length === 0 || Buffer.byteLength(destinationIdentity, "utf8") > 4096) {
3414
4359
  return null;
@@ -3416,7 +4361,7 @@ function parseDestination(value) {
3416
4361
  return { targetUrl, destinationIdentity };
3417
4362
  }
3418
4363
  function parseMetadata(value) {
3419
- if (!isRecord2(value)) return null;
4364
+ if (!isRecord3(value)) return null;
3420
4365
  if (typeof value.id !== "string" || !INBOX_ID_PATTERN.test(value.id) || !validSyncId(value.syncId) || !validTimestamp(value.capturedAtMs)) {
3421
4366
  return null;
3422
4367
  }
@@ -3441,12 +4386,12 @@ function entryFileName(id) {
3441
4386
  return `${id}.json`;
3442
4387
  }
3443
4388
  function entryPath(inboxDir, id) {
3444
- return path4.join(inboxDir, entryFileName(id));
4389
+ return path5.join(inboxDir, entryFileName(id));
3445
4390
  }
3446
4391
  function enforceOwnerOnlyDirectory(inboxDir) {
3447
- fs3.mkdirSync(inboxDir, { recursive: true, mode: 448 });
4392
+ fs4.mkdirSync(inboxDir, { recursive: true, mode: 448 });
3448
4393
  try {
3449
- fs3.chmodSync(inboxDir, 448);
4394
+ fs4.chmodSync(inboxDir, 448);
3450
4395
  } catch {
3451
4396
  }
3452
4397
  }
@@ -3454,22 +4399,22 @@ function fsyncDirectory(inboxDir) {
3454
4399
  if (process.platform === "win32") return;
3455
4400
  let descriptor;
3456
4401
  try {
3457
- descriptor = fs3.openSync(inboxDir, "r");
3458
- fs3.fsyncSync(descriptor);
4402
+ descriptor = fs4.openSync(inboxDir, "r");
4403
+ fs4.fsyncSync(descriptor);
3459
4404
  } catch (error) {
3460
4405
  const code = error.code;
3461
4406
  if (code === void 0 || !UNSUPPORTED_DIRECTORY_FSYNC_CODES.has(code)) {
3462
4407
  throw error;
3463
4408
  }
3464
4409
  } finally {
3465
- if (descriptor !== void 0) fs3.closeSync(descriptor);
4410
+ if (descriptor !== void 0) fs4.closeSync(descriptor);
3466
4411
  }
3467
4412
  }
3468
4413
  async function fsyncDirectoryAsync(inboxDir) {
3469
4414
  if (process.platform === "win32") return;
3470
4415
  let descriptor;
3471
4416
  try {
3472
- descriptor = await fs3.promises.open(inboxDir, "r");
4417
+ descriptor = await fs4.promises.open(inboxDir, "r");
3473
4418
  await descriptor.sync();
3474
4419
  } catch (error) {
3475
4420
  const code = error.code;
@@ -3502,7 +4447,7 @@ function parseNullablePositiveInteger(value) {
3502
4447
  }
3503
4448
  function parsePendingCapacityMutation(value) {
3504
4449
  if (value === void 0) return void 0;
3505
- if (!isRecord2(value)) return null;
4450
+ if (!isRecord3(value)) return null;
3506
4451
  if (typeof value.fileName !== "string" || !value.fileName.endsWith(".json") || !INBOX_ID_PATTERN.test(value.fileName.slice(0, -5)) || !nonNegativeInteger(value.bytes) || !validTimestamp(value.startedAtMs)) {
3507
4452
  return null;
3508
4453
  }
@@ -3523,7 +4468,7 @@ function parsePendingCapacityMutation(value) {
3523
4468
  };
3524
4469
  }
3525
4470
  function parseCapacityState(value) {
3526
- if (!isRecord2(value) || value.schemaVersion !== CAPACITY_STATE_SCHEMA_VERSION || !nonNegativeInteger(value.pendingEntries) || !nonNegativeInteger(value.pendingBytes) || !nonNegativeInteger(value.rejectedEntries) || !nonNegativeInteger(value.rejectedBytes)) {
4471
+ if (!isRecord3(value) || value.schemaVersion !== CAPACITY_STATE_SCHEMA_VERSION || !nonNegativeInteger(value.pendingEntries) || !nonNegativeInteger(value.pendingBytes) || !nonNegativeInteger(value.rejectedEntries) || !nonNegativeInteger(value.rejectedBytes)) {
3527
4472
  return null;
3528
4473
  }
3529
4474
  const maxEntries = parseNullablePositiveInteger(value.maxEntries);
@@ -3536,28 +4481,33 @@ function parseCapacityState(value) {
3536
4481
  const lastRejectionReason = parseCapacityRejectionReason(
3537
4482
  value.lastRejectionReason
3538
4483
  );
4484
+ const lostEntries = nonNegativeInteger(value.lostEntries) ? value.lostEntries : 0;
4485
+ const lostBytes = nonNegativeInteger(value.lostBytes) ? value.lostBytes : 0;
3539
4486
  return {
3540
4487
  schemaVersion: CAPACITY_STATE_SCHEMA_VERSION,
3541
4488
  pendingEntries: value.pendingEntries,
3542
4489
  pendingBytes: value.pendingBytes,
3543
4490
  rejectedEntries: value.rejectedEntries,
3544
4491
  rejectedBytes: value.rejectedBytes,
4492
+ lostEntries,
4493
+ lostBytes,
3545
4494
  maxEntries,
3546
4495
  maxBytes,
3547
4496
  ...validTimestamp(value.lastRejectedAtMs) ? { lastRejectedAtMs: value.lastRejectedAtMs } : {},
3548
4497
  ...lastRejectionReason === void 0 ? {} : { lastRejectionReason },
4498
+ ...validTimestamp(value.lastLostAtMs) ? { lastLostAtMs: value.lastLostAtMs } : {},
3549
4499
  ...pendingPersist === void 0 ? {} : { pendingPersist },
3550
4500
  ...pendingRemoval === void 0 ? {} : { pendingRemoval }
3551
4501
  };
3552
4502
  }
3553
4503
  function capacityStatePath(inboxDir) {
3554
- return path4.join(inboxDir, CAPACITY_STATE_FILE);
4504
+ return path5.join(inboxDir, CAPACITY_STATE_FILE);
3555
4505
  }
3556
4506
  function readCapacityStateFile(inboxDir) {
3557
4507
  try {
3558
4508
  return parseCapacityState(
3559
4509
  JSON.parse(
3560
- fs3.readFileSync(capacityStatePath(inboxDir), "utf8")
4510
+ fs4.readFileSync(capacityStatePath(inboxDir), "utf8")
3561
4511
  )
3562
4512
  );
3563
4513
  } catch {
@@ -3571,6 +4521,8 @@ function defaultCapacityState(limits, pendingEntries, pendingBytes) {
3571
4521
  pendingBytes,
3572
4522
  rejectedEntries: 0,
3573
4523
  rejectedBytes: 0,
4524
+ lostEntries: 0,
4525
+ lostBytes: 0,
3574
4526
  maxEntries: limits.maxEntries ?? null,
3575
4527
  maxBytes: limits.maxBytes ?? null
3576
4528
  };
@@ -3579,26 +4531,26 @@ function writeCapacityState(inboxDir, state, capacityLock) {
3579
4531
  enforceOwnerOnlyDirectory(inboxDir);
3580
4532
  capacityLock.assertOwned();
3581
4533
  const destination = capacityStatePath(inboxDir);
3582
- const temporary = path4.join(
4534
+ const temporary = path5.join(
3583
4535
  inboxDir,
3584
- `${CAPACITY_STATE_FILE}.${process.pid}.${randomUUID2()}.tmp`
4536
+ `${CAPACITY_STATE_FILE}.${process.pid}.${randomUUID3()}.tmp`
3585
4537
  );
3586
4538
  let descriptor;
3587
4539
  try {
3588
- descriptor = fs3.openSync(temporary, "wx", 384);
3589
- fs3.writeFileSync(descriptor, `${JSON.stringify(state)}
4540
+ descriptor = fs4.openSync(temporary, "wx", 384);
4541
+ fs4.writeFileSync(descriptor, `${JSON.stringify(state)}
3590
4542
  `, "utf8");
3591
- fs3.fsyncSync(descriptor);
4543
+ fs4.fsyncSync(descriptor);
3592
4544
  capacityLock.assertOwned();
3593
- fs3.closeSync(descriptor);
4545
+ fs4.closeSync(descriptor);
3594
4546
  descriptor = void 0;
3595
- fs3.renameSync(temporary, destination);
4547
+ fs4.renameSync(temporary, destination);
3596
4548
  fsyncDirectory(inboxDir);
3597
4549
  capacityLock.assertOwned();
3598
4550
  } catch (error) {
3599
- if (descriptor !== void 0) fs3.closeSync(descriptor);
4551
+ if (descriptor !== void 0) fs4.closeSync(descriptor);
3600
4552
  try {
3601
- fs3.rmSync(temporary, { force: true });
4553
+ fs4.rmSync(temporary, { force: true });
3602
4554
  } catch {
3603
4555
  }
3604
4556
  throw error;
@@ -3606,19 +4558,19 @@ function writeCapacityState(inboxDir, state, capacityLock) {
3606
4558
  }
3607
4559
  function capacityLockIsOwned(lockPath, descriptor, ownerToken) {
3608
4560
  try {
3609
- const openStat = fs3.fstatSync(descriptor);
3610
- const pathStat = fs3.statSync(lockPath);
4561
+ const openStat = fs4.fstatSync(descriptor);
4562
+ const pathStat = fs4.statSync(lockPath);
3611
4563
  if (openStat.dev !== pathStat.dev || openStat.ino !== pathStat.ino) {
3612
4564
  return false;
3613
4565
  }
3614
- const value = JSON.parse(fs3.readFileSync(lockPath, "utf8"));
3615
- return isRecord2(value) && value.ownerToken === ownerToken;
4566
+ const value = JSON.parse(fs4.readFileSync(lockPath, "utf8"));
4567
+ return isRecord3(value) && value.ownerToken === ownerToken;
3616
4568
  } catch {
3617
4569
  return false;
3618
4570
  }
3619
4571
  }
3620
4572
  function parseCapacityLockOwner(value) {
3621
- if (!isRecord2(value) || typeof value.ownerToken !== "string" || value.ownerToken.length === 0 || typeof value.pid !== "number" || !Number.isSafeInteger(value.pid) || value.pid <= 0 || !validTimestamp(value.createdAtMs)) {
4573
+ if (!isRecord3(value) || typeof value.ownerToken !== "string" || value.ownerToken.length === 0 || typeof value.pid !== "number" || !Number.isSafeInteger(value.pid) || value.pid <= 0 || !validTimestamp(value.createdAtMs)) {
3622
4574
  return null;
3623
4575
  }
3624
4576
  return {
@@ -3630,7 +4582,7 @@ function parseCapacityLockOwner(value) {
3630
4582
  function readCapacityLockOwner(lockPath) {
3631
4583
  try {
3632
4584
  return parseCapacityLockOwner(
3633
- JSON.parse(fs3.readFileSync(lockPath, "utf8"))
4585
+ JSON.parse(fs4.readFileSync(lockPath, "utf8"))
3634
4586
  );
3635
4587
  } catch {
3636
4588
  return null;
@@ -3648,7 +4600,7 @@ function capacityLockOwnerIsAlive(pid) {
3648
4600
  }
3649
4601
  function restoreMovedCapacityLock(abandonedPath, lockPath) {
3650
4602
  try {
3651
- fs3.renameSync(abandonedPath, lockPath);
4603
+ fs4.renameSync(abandonedPath, lockPath);
3652
4604
  } catch (error) {
3653
4605
  if (!capacityLockRaceError(error)) throw error;
3654
4606
  }
@@ -3660,7 +4612,7 @@ function capacityLockRaceError(error) {
3660
4612
  function recoverStaleCapacityLock(lockPath, nowMs) {
3661
4613
  let observed;
3662
4614
  try {
3663
- observed = fs3.statSync(lockPath);
4615
+ observed = fs4.statSync(lockPath);
3664
4616
  } catch (error) {
3665
4617
  if (capacityLockRaceError(error)) return;
3666
4618
  throw error;
@@ -3672,10 +4624,10 @@ function recoverStaleCapacityLock(lockPath, nowMs) {
3672
4624
  if (!beyondStaleHorizon && (!observedOwner || capacityLockOwnerIsAlive(observedOwner.pid))) {
3673
4625
  return;
3674
4626
  }
3675
- const abandoned = `${lockPath}.stale.${nowMs}.${randomUUID2()}`;
4627
+ const abandoned = `${lockPath}.stale.${nowMs}.${randomUUID3()}`;
3676
4628
  try {
3677
- fs3.renameSync(lockPath, abandoned);
3678
- const moved = fs3.statSync(abandoned);
4629
+ fs4.renameSync(lockPath, abandoned);
4630
+ const moved = fs4.statSync(abandoned);
3679
4631
  if (moved.dev !== observed.dev || moved.ino !== observed.ino) {
3680
4632
  restoreMovedCapacityLock(abandoned, lockPath);
3681
4633
  return;
@@ -3687,8 +4639,8 @@ function recoverStaleCapacityLock(lockPath, nowMs) {
3687
4639
  restoreMovedCapacityLock(abandoned, lockPath);
3688
4640
  return;
3689
4641
  }
3690
- fs3.rmSync(abandoned, { force: true });
3691
- fsyncDirectory(path4.dirname(lockPath));
4642
+ fs4.rmSync(abandoned, { force: true });
4643
+ fsyncDirectory(path5.dirname(lockPath));
3692
4644
  } catch (error) {
3693
4645
  if (!capacityLockRaceError(error)) throw error;
3694
4646
  }
@@ -3699,21 +4651,21 @@ function sleepSync(milliseconds) {
3699
4651
  }
3700
4652
  function acquireCapacityLock(inboxDir, waitMs = CAPACITY_LOCK_WAIT_MS) {
3701
4653
  enforceOwnerOnlyDirectory(inboxDir);
3702
- const lockPath = path4.join(inboxDir, CAPACITY_LOCK_FILE);
4654
+ const lockPath = path5.join(inboxDir, CAPACITY_LOCK_FILE);
3703
4655
  const deadlineMs = Date.now() + Math.max(0, waitMs);
3704
4656
  while (true) {
3705
4657
  let descriptor;
3706
4658
  let ownerToken;
3707
4659
  try {
3708
- descriptor = fs3.openSync(lockPath, "wx+", 384);
3709
- ownerToken = randomUUID2();
3710
- fs3.writeFileSync(
4660
+ descriptor = fs4.openSync(lockPath, "wx+", 384);
4661
+ ownerToken = randomUUID3();
4662
+ fs4.writeFileSync(
3711
4663
  descriptor,
3712
4664
  `${JSON.stringify({ ownerToken, pid: process.pid, createdAtMs: Date.now() })}
3713
4665
  `,
3714
4666
  "utf8"
3715
4667
  );
3716
- fs3.fsyncSync(descriptor);
4668
+ fs4.fsyncSync(descriptor);
3717
4669
  let released = false;
3718
4670
  const capacityLock = {
3719
4671
  assertOwned() {
@@ -3727,14 +4679,14 @@ function acquireCapacityLock(inboxDir, waitMs = CAPACITY_LOCK_WAIT_MS) {
3727
4679
  let releaseError;
3728
4680
  try {
3729
4681
  if (capacityLockIsOwned(lockPath, descriptor, ownerToken)) {
3730
- fs3.unlinkSync(lockPath);
4682
+ fs4.unlinkSync(lockPath);
3731
4683
  fsyncDirectory(inboxDir);
3732
4684
  }
3733
4685
  } catch (error) {
3734
4686
  releaseError = error;
3735
4687
  }
3736
4688
  try {
3737
- fs3.closeSync(descriptor);
4689
+ fs4.closeSync(descriptor);
3738
4690
  } catch (error) {
3739
4691
  releaseError ??= error;
3740
4692
  }
@@ -3747,12 +4699,12 @@ function acquireCapacityLock(inboxDir, waitMs = CAPACITY_LOCK_WAIT_MS) {
3747
4699
  if (descriptor !== void 0) {
3748
4700
  const removeOwnedLock = ownerToken !== void 0 && capacityLockIsOwned(lockPath, descriptor, ownerToken);
3749
4701
  try {
3750
- fs3.closeSync(descriptor);
4702
+ fs4.closeSync(descriptor);
3751
4703
  } catch {
3752
4704
  }
3753
4705
  if (removeOwnedLock) {
3754
4706
  try {
3755
- fs3.rmSync(lockPath, { force: true });
4707
+ fs4.rmSync(lockPath, { force: true });
3756
4708
  fsyncDirectory(inboxDir);
3757
4709
  } catch {
3758
4710
  }
@@ -3772,23 +4724,23 @@ function capacityMutationEvidence(inboxDir, pending, includeTemporary) {
3772
4724
  const prefix = `.${id}.`;
3773
4725
  const candidates = [];
3774
4726
  if (includeTemporary && pending.temporaryFileName) {
3775
- candidates.push(path4.join(inboxDir, pending.temporaryFileName));
4727
+ candidates.push(path5.join(inboxDir, pending.temporaryFileName));
3776
4728
  } else if (includeTemporary) {
3777
4729
  try {
3778
- for (const name of fs3.readdirSync(inboxDir)) {
4730
+ for (const name of fs4.readdirSync(inboxDir)) {
3779
4731
  if (name.startsWith(prefix) && name.endsWith(".tmp")) {
3780
- candidates.push(path4.join(inboxDir, name));
4732
+ candidates.push(path5.join(inboxDir, name));
3781
4733
  }
3782
4734
  }
3783
4735
  } catch (error) {
3784
4736
  return error.code === "ENOENT" ? { kind: "missing" } : { kind: "unavailable" };
3785
4737
  }
3786
4738
  }
3787
- candidates.push(path4.join(inboxDir, fileName));
4739
+ candidates.push(path5.join(inboxDir, fileName));
3788
4740
  let presentBytes;
3789
4741
  for (const candidate of candidates) {
3790
4742
  try {
3791
- const stat = fs3.lstatSync(candidate);
4743
+ const stat = fs4.lstatSync(candidate);
3792
4744
  if (!stat.isFile() || presentBytes !== void 0) {
3793
4745
  return { kind: "unavailable" };
3794
4746
  }
@@ -3887,7 +4839,7 @@ function removeCapacityTrackedEntry(filePath, inboxDir, expectedBytes, missingIs
3887
4839
  );
3888
4840
  let bytes = expectedBytes;
3889
4841
  try {
3890
- const stat = fs3.lstatSync(filePath);
4842
+ const stat = fs4.lstatSync(filePath);
3891
4843
  if (!stat.isFile()) return false;
3892
4844
  bytes = stat.size;
3893
4845
  } catch (error) {
@@ -3896,7 +4848,7 @@ function removeCapacityTrackedEntry(filePath, inboxDir, expectedBytes, missingIs
3896
4848
  }
3897
4849
  if (bytes === void 0) return false;
3898
4850
  const pendingRemoval = {
3899
- fileName: path4.basename(filePath),
4851
+ fileName: path5.basename(filePath),
3900
4852
  bytes,
3901
4853
  startedAtMs: Date.now()
3902
4854
  };
@@ -3909,7 +4861,7 @@ function removeCapacityTrackedEntry(filePath, inboxDir, expectedBytes, missingIs
3909
4861
  writeCapacityState(inboxDir, state, capacityLock);
3910
4862
  capacityLock.assertOwned();
3911
4863
  try {
3912
- fs3.unlinkSync(filePath);
4864
+ fs4.unlinkSync(filePath);
3913
4865
  } catch (error) {
3914
4866
  if (error.code !== "ENOENT") {
3915
4867
  try {
@@ -3944,12 +4896,12 @@ function parseRetirementReason(value) {
3944
4896
  return value === "malformed" || value === "terminal_rejected" ? value : void 0;
3945
4897
  }
3946
4898
  function parseRetirementState(value) {
3947
- if (!isRecord2(value) || value.schemaVersion !== RETIREMENT_STATE_SCHEMA_VERSION || !nonNegativeInteger(value.retiredEntries) || !nonNegativeInteger(value.retiredBytes)) {
4899
+ if (!isRecord3(value) || value.schemaVersion !== RETIREMENT_STATE_SCHEMA_VERSION || !nonNegativeInteger(value.retiredEntries) || !nonNegativeInteger(value.retiredBytes)) {
3948
4900
  return null;
3949
4901
  }
3950
4902
  let pending;
3951
4903
  if (value.pending !== void 0) {
3952
- if (!isRecord2(value.pending)) return null;
4904
+ if (!isRecord3(value.pending)) return null;
3953
4905
  const reason = parseRetirementReason(value.pending.reason);
3954
4906
  if (typeof value.pending.fileName !== "string" || !value.pending.fileName.endsWith(".json") || !INBOX_ID_PATTERN.test(value.pending.fileName.slice(0, -5)) || !nonNegativeInteger(value.pending.bytes) || !validTimestamp(value.pending.retiredAtMs) || reason === void 0) {
3955
4907
  return null;
@@ -3977,7 +4929,7 @@ function readRetirementState(inboxDir) {
3977
4929
  try {
3978
4930
  return parseRetirementState(
3979
4931
  JSON.parse(
3980
- fs3.readFileSync(path4.join(inboxDir, RETIREMENT_STATE_FILE), "utf8")
4932
+ fs4.readFileSync(path5.join(inboxDir, RETIREMENT_STATE_FILE), "utf8")
3981
4933
  )
3982
4934
  ) ?? defaultRetirementState();
3983
4935
  } catch {
@@ -3986,25 +4938,25 @@ function readRetirementState(inboxDir) {
3986
4938
  }
3987
4939
  function writeRetirementState(inboxDir, state) {
3988
4940
  enforceOwnerOnlyDirectory(inboxDir);
3989
- const destination = path4.join(inboxDir, RETIREMENT_STATE_FILE);
3990
- const temporary = path4.join(
4941
+ const destination = path5.join(inboxDir, RETIREMENT_STATE_FILE);
4942
+ const temporary = path5.join(
3991
4943
  inboxDir,
3992
- `${RETIREMENT_STATE_FILE}.${process.pid}.${randomUUID2()}.tmp`
4944
+ `${RETIREMENT_STATE_FILE}.${process.pid}.${randomUUID3()}.tmp`
3993
4945
  );
3994
4946
  let descriptor;
3995
4947
  try {
3996
- descriptor = fs3.openSync(temporary, "wx", 384);
3997
- fs3.writeFileSync(descriptor, `${JSON.stringify(state)}
4948
+ descriptor = fs4.openSync(temporary, "wx", 384);
4949
+ fs4.writeFileSync(descriptor, `${JSON.stringify(state)}
3998
4950
  `, "utf8");
3999
- fs3.fsyncSync(descriptor);
4000
- fs3.closeSync(descriptor);
4951
+ fs4.fsyncSync(descriptor);
4952
+ fs4.closeSync(descriptor);
4001
4953
  descriptor = void 0;
4002
- fs3.renameSync(temporary, destination);
4954
+ fs4.renameSync(temporary, destination);
4003
4955
  fsyncDirectory(inboxDir);
4004
4956
  } catch (error) {
4005
- if (descriptor !== void 0) fs3.closeSync(descriptor);
4957
+ if (descriptor !== void 0) fs4.closeSync(descriptor);
4006
4958
  try {
4007
- fs3.rmSync(temporary, { force: true });
4959
+ fs4.rmSync(temporary, { force: true });
4008
4960
  } catch {
4009
4961
  }
4010
4962
  throw error;
@@ -4014,7 +4966,7 @@ function completePendingRetirement(inboxDir, state) {
4014
4966
  const pending = state.pending;
4015
4967
  if (!pending) return state;
4016
4968
  if (!removeCapacityTrackedEntry(
4017
- path4.join(inboxDir, pending.fileName),
4969
+ path5.join(inboxDir, pending.fileName),
4018
4970
  inboxDir,
4019
4971
  pending.bytes,
4020
4972
  true
@@ -4043,14 +4995,14 @@ function retireEntry(filePath, reason, inboxDir) {
4043
4995
  if (!current) return false;
4044
4996
  let bytes;
4045
4997
  try {
4046
- bytes = fs3.lstatSync(filePath).size;
4998
+ bytes = fs4.lstatSync(filePath).size;
4047
4999
  } catch {
4048
5000
  return false;
4049
5001
  }
4050
5002
  const pending = {
4051
5003
  ...current,
4052
5004
  pending: {
4053
- fileName: path4.basename(filePath),
5005
+ fileName: path5.basename(filePath),
4054
5006
  bytes,
4055
5007
  reason,
4056
5008
  retiredAtMs: Date.now()
@@ -4069,7 +5021,7 @@ function createHookInboxEntry(request, event, destination, persistedAtMs = Date.
4069
5021
  schemaVersion: HOOK_INBOX_SCHEMA_VERSION,
4070
5022
  // File ordering follows the local durable write, not an agent-supplied
4071
5023
  // event timestamp that may be delayed or backdated behind a drain cursor.
4072
- id: `${persistedAtMs}-${randomUUID2()}`,
5024
+ id: `${persistedAtMs}-${randomUUID3()}`,
4073
5025
  syncId: event.syncId,
4074
5026
  capturedAtMs,
4075
5027
  ...event.repository ? { repository: event.repository } : {},
@@ -4101,9 +5053,9 @@ function persistHookInboxEntry(entry, inboxDir = FML_HOOK_INBOX_DIR, limits = ho
4101
5053
  throw new Error("FML hook inbox admission is busy");
4102
5054
  }
4103
5055
  const destination = entryPath(inboxDir, entry.id);
4104
- const temporary = path4.join(
5056
+ const temporary = path5.join(
4105
5057
  inboxDir,
4106
- `.${entry.id}.${process.pid}.${randomUUID2()}.tmp`
5058
+ `.${entry.id}.${process.pid}.${randomUUID3()}.tmp`
4107
5059
  );
4108
5060
  let descriptor;
4109
5061
  let state;
@@ -4131,8 +5083,8 @@ function persistHookInboxEntry(entry, inboxDir = FML_HOOK_INBOX_DIR, limits = ho
4131
5083
  }
4132
5084
  try {
4133
5085
  const pendingPersist = {
4134
- fileName: path4.basename(destination),
4135
- temporaryFileName: path4.basename(temporary),
5086
+ fileName: path5.basename(destination),
5087
+ temporaryFileName: path5.basename(temporary),
4136
5088
  bytes: serializedBytes,
4137
5089
  startedAtMs: Date.now()
4138
5090
  };
@@ -4144,23 +5096,23 @@ function persistHookInboxEntry(entry, inboxDir = FML_HOOK_INBOX_DIR, limits = ho
4144
5096
  };
4145
5097
  writeCapacityState(inboxDir, state, capacityLock);
4146
5098
  capacityLock.assertOwned();
4147
- descriptor = fs3.openSync(temporary, "wx", 384);
4148
- fs3.writeFileSync(descriptor, serialized, "utf8");
4149
- fs3.fsyncSync(descriptor);
5099
+ descriptor = fs4.openSync(temporary, "wx", 384);
5100
+ fs4.writeFileSync(descriptor, serialized, "utf8");
5101
+ fs4.fsyncSync(descriptor);
4150
5102
  capacityLock.assertOwned();
4151
- fs3.closeSync(descriptor);
5103
+ fs4.closeSync(descriptor);
4152
5104
  descriptor = void 0;
4153
- fs3.renameSync(temporary, destination);
5105
+ fs4.renameSync(temporary, destination);
4154
5106
  try {
4155
- fs3.chmodSync(destination, 384);
5107
+ fs4.chmodSync(destination, 384);
4156
5108
  } catch {
4157
5109
  }
4158
5110
  fsyncDirectory(inboxDir);
4159
5111
  capacityLock.assertOwned();
4160
5112
  } catch (error) {
4161
- if (descriptor !== void 0) fs3.closeSync(descriptor);
5113
+ if (descriptor !== void 0) fs4.closeSync(descriptor);
4162
5114
  try {
4163
- fs3.rmSync(temporary, { force: true });
5115
+ fs4.rmSync(temporary, { force: true });
4164
5116
  } catch {
4165
5117
  }
4166
5118
  if (state.pendingPersist) {
@@ -4188,10 +5140,10 @@ function hookInboxDeliveryPayload(entry) {
4188
5140
  };
4189
5141
  }
4190
5142
  function parseHookInboxMetadata(value) {
4191
- return isRecord2(value) ? parseMetadata(value[INBOX_METADATA_KEY]) : null;
5143
+ return isRecord3(value) ? parseMetadata(value[INBOX_METADATA_KEY]) : null;
4192
5144
  }
4193
5145
  function parseHookInboxEntry(value) {
4194
- if (!isRecord2(value) || value.schemaVersion !== HOOK_INBOX_SCHEMA_VERSION) {
5146
+ if (!isRecord3(value) || value.schemaVersion !== HOOK_INBOX_SCHEMA_VERSION) {
4195
5147
  return null;
4196
5148
  }
4197
5149
  const metadata = parseMetadata(value);
@@ -4211,21 +5163,21 @@ function sourceDeclaresForwardSchema(source) {
4211
5163
  }
4212
5164
  function readEntryResult(filePath) {
4213
5165
  try {
4214
- const stat = fs3.lstatSync(filePath);
5166
+ const stat = fs4.lstatSync(filePath);
4215
5167
  if (!stat.isFile()) {
4216
5168
  return { kind: "malformed" };
4217
5169
  }
4218
5170
  if (stat.size > MAX_HOOK_INBOX_ENTRY_BYTES) {
4219
5171
  return { kind: "unsupported_schema" };
4220
5172
  }
4221
- const source = fs3.readFileSync(filePath, "utf8");
5173
+ const source = fs4.readFileSync(filePath, "utf8");
4222
5174
  let value;
4223
5175
  try {
4224
5176
  value = JSON.parse(source);
4225
5177
  } catch {
4226
5178
  return sourceDeclaresForwardSchema(source) ? { kind: "unsupported_schema" } : { kind: "malformed" };
4227
5179
  }
4228
- if (isRecord2(value) && typeof value.schemaVersion === "number" && Number.isSafeInteger(value.schemaVersion) && value.schemaVersion > HOOK_INBOX_SCHEMA_VERSION) {
5180
+ if (isRecord3(value) && typeof value.schemaVersion === "number" && Number.isSafeInteger(value.schemaVersion) && value.schemaVersion > HOOK_INBOX_SCHEMA_VERSION) {
4229
5181
  return { kind: "unsupported_schema" };
4230
5182
  }
4231
5183
  const entry = parseHookInboxEntry(value);
@@ -4283,19 +5235,19 @@ async function scanDrainEntryNames(inboxDir, maximumEntries, skipThrough, exclud
4283
5235
  let blockCursorAdvance = false;
4284
5236
  let removedOrphan = false;
4285
5237
  const cutoffMs = nowMs - STALE_INBOX_TEMP_AGE_MS;
4286
- const directory = await fs3.promises.opendir(inboxDir);
5238
+ const directory = await fs4.promises.opendir(inboxDir);
4287
5239
  try {
4288
5240
  for (let dirent = await directory.read(); dirent !== null; dirent = await directory.read()) {
4289
5241
  const name = dirent.name;
4290
5242
  const abandonedLockMatch = ABANDONED_CAPACITY_LOCK_PATTERN.exec(name);
4291
5243
  if (INBOX_STATE_TEMP_PATTERN.test(name) || abandonedLockMatch !== null) {
4292
5244
  try {
4293
- const orphanPath = path4.join(inboxDir, name);
4294
- const stat = await fs3.promises.lstat(orphanPath);
5245
+ const orphanPath = path5.join(inboxDir, name);
5246
+ const stat = await fs4.promises.lstat(orphanPath);
4295
5247
  const abandonedAtMs = abandonedLockMatch ? Number(abandonedLockMatch[1]) : void 0;
4296
5248
  const isStale = abandonedLockMatch ? validTimestamp(abandonedAtMs) && abandonedAtMs <= cutoffMs : stat.mtimeMs <= cutoffMs;
4297
5249
  if (stat.isFile() && isStale) {
4298
- await fs3.promises.unlink(orphanPath);
5250
+ await fs4.promises.unlink(orphanPath);
4299
5251
  removedOrphan = true;
4300
5252
  }
4301
5253
  } catch (error) {
@@ -4313,27 +5265,27 @@ async function scanDrainEntryNames(inboxDir, maximumEntries, skipThrough, exclud
4313
5265
  const id = match?.[1];
4314
5266
  if (!id) continue;
4315
5267
  const destination = entryPath(inboxDir, id);
4316
- const destinationName = path4.basename(destination);
4317
- const temporary = path4.join(inboxDir, name);
5268
+ const destinationName = path5.basename(destination);
5269
+ const temporary = path5.join(inboxDir, name);
4318
5270
  try {
4319
- const stat = await fs3.promises.lstat(temporary);
5271
+ const stat = await fs4.promises.lstat(temporary);
4320
5272
  if (!stat.isFile()) continue;
4321
5273
  blockCursorAdvance = true;
4322
5274
  if (skipThrough && destinationName <= skipThrough) resetCursor = true;
4323
5275
  if (stat.mtimeMs > cutoffMs) continue;
4324
5276
  let destinationExists = false;
4325
5277
  try {
4326
- await fs3.promises.lstat(destination);
5278
+ await fs4.promises.lstat(destination);
4327
5279
  destinationExists = true;
4328
5280
  } catch (error) {
4329
5281
  if (error.code !== "ENOENT") throw error;
4330
5282
  }
4331
5283
  if (destinationExists) {
4332
- await fs3.promises.unlink(temporary);
5284
+ await fs4.promises.unlink(temporary);
4333
5285
  } else {
4334
- await fs3.promises.rename(temporary, destination);
5286
+ await fs4.promises.rename(temporary, destination);
4335
5287
  try {
4336
- await fs3.promises.chmod(destination, 384);
5288
+ await fs4.promises.chmod(destination, 384);
4337
5289
  } catch {
4338
5290
  }
4339
5291
  }
@@ -4367,7 +5319,7 @@ async function scanDrainEntryNames(inboxDir, maximumEntries, skipThrough, exclud
4367
5319
  return { names, failed, resetCursor, blockCursorAdvance };
4368
5320
  }
4369
5321
  function drainCursor(inboxDir) {
4370
- const key = path4.resolve(inboxDir);
5322
+ const key = path5.resolve(inboxDir);
4371
5323
  const existing = HOOK_INBOX_DRAIN_CURSORS.get(key);
4372
5324
  if (existing) {
4373
5325
  HOOK_INBOX_DRAIN_CURSORS.delete(key);
@@ -4399,12 +5351,12 @@ function safeDrainCursor(cursor, nowMs) {
4399
5351
  async function pendingInboxSizeAsync(inboxDir) {
4400
5352
  let pendingEntries = 0;
4401
5353
  let pendingBytes = 0;
4402
- const directory = await fs3.promises.opendir(inboxDir);
5354
+ const directory = await fs4.promises.opendir(inboxDir);
4403
5355
  try {
4404
5356
  for (let dirent = await directory.read(); dirent !== null; dirent = await directory.read()) {
4405
5357
  if (!isPendingEntryName(dirent.name)) continue;
4406
5358
  try {
4407
- const stat = await fs3.promises.lstat(path4.join(inboxDir, dirent.name));
5359
+ const stat = await fs4.promises.lstat(path5.join(inboxDir, dirent.name));
4408
5360
  if (stat.isFile()) {
4409
5361
  pendingEntries += 1;
4410
5362
  pendingBytes += stat.size;
@@ -4421,14 +5373,14 @@ async function pendingInboxSizeAsync(inboxDir) {
4421
5373
  function pendingInboxSize(inboxDir, includeTemporary = false) {
4422
5374
  let pendingEntries = 0;
4423
5375
  let pendingBytes = 0;
4424
- const directory = fs3.opendirSync(inboxDir);
5376
+ const directory = fs4.opendirSync(inboxDir);
4425
5377
  try {
4426
5378
  for (let dirent = directory.readSync(); dirent !== null; dirent = directory.readSync()) {
4427
5379
  if (!isPendingEntryName(dirent.name) && !(includeTemporary && INBOX_TEMP_PATTERN.test(dirent.name))) {
4428
5380
  continue;
4429
5381
  }
4430
5382
  try {
4431
- const stat = fs3.lstatSync(path4.join(inboxDir, dirent.name));
5383
+ const stat = fs4.lstatSync(path5.join(inboxDir, dirent.name));
4432
5384
  if (stat.isFile()) {
4433
5385
  pendingEntries += 1;
4434
5386
  pendingBytes += stat.size;
@@ -4463,6 +5415,8 @@ function emptyHookInboxStatus(limits, unreadable) {
4463
5415
  maxBytes: limits.maxBytes ?? null,
4464
5416
  capacityRejectedEntries: 0,
4465
5417
  capacityRejectedBytes: 0,
5418
+ lostEntries: 0,
5419
+ lostBytes: 0,
4466
5420
  retiredEntries: 0,
4467
5421
  retiredBytes: 0,
4468
5422
  unreadable
@@ -4488,6 +5442,9 @@ function readHookInboxStatus(inboxDir = FML_HOOK_INBOX_DIR) {
4488
5442
  capacityRejectedBytes: capacity.rejectedBytes,
4489
5443
  ...capacity.lastRejectedAtMs === void 0 ? {} : { lastCapacityRejectedAtMs: capacity.lastRejectedAtMs },
4490
5444
  ...capacity.lastRejectionReason === void 0 ? {} : { lastCapacityRejectionReason: capacity.lastRejectionReason },
5445
+ lostEntries: capacity.lostEntries,
5446
+ lostBytes: capacity.lostBytes,
5447
+ ...capacity.lastLostAtMs === void 0 ? {} : { lastLostAtMs: capacity.lastLostAtMs },
4491
5448
  retiredEntries: retirement.retiredEntries,
4492
5449
  retiredBytes: retirement.retiredBytes,
4493
5450
  unreadable: false
@@ -4499,6 +5456,50 @@ function readHookInboxStatus(inboxDir = FML_HOOK_INBOX_DIR) {
4499
5456
  return emptyHookInboxStatus(limits, true);
4500
5457
  }
4501
5458
  }
5459
+ function readHookInboxCapacityCounters(inboxDir = FML_HOOK_INBOX_DIR) {
5460
+ const capacity = readCapacityStateFile(inboxDir);
5461
+ if (!capacity) return null;
5462
+ return {
5463
+ rejectedEntries: capacity.rejectedEntries,
5464
+ rejectedBytes: capacity.rejectedBytes,
5465
+ ...capacity.lastRejectedAtMs === void 0 ? {} : { lastRejectedAtMs: capacity.lastRejectedAtMs },
5466
+ ...capacity.lastRejectionReason === void 0 ? {} : { lastRejectionReason: capacity.lastRejectionReason },
5467
+ lostEntries: capacity.lostEntries,
5468
+ lostBytes: capacity.lostBytes,
5469
+ ...capacity.lastLostAtMs === void 0 ? {} : { lastLostAtMs: capacity.lastLostAtMs }
5470
+ };
5471
+ }
5472
+ function recordHookInboxLoss(inboxDir = FML_HOOK_INBOX_DIR, bytes, capacityLockWaitMs = CAPACITY_LOCK_WAIT_MS) {
5473
+ let capacityLock;
5474
+ try {
5475
+ enforceOwnerOnlyDirectory(inboxDir);
5476
+ capacityLock = acquireCapacityLock(inboxDir, capacityLockWaitMs);
5477
+ } catch {
5478
+ return false;
5479
+ }
5480
+ if (!capacityLock) return false;
5481
+ try {
5482
+ const state = loadCapacityState(
5483
+ inboxDir,
5484
+ maintenanceCapacityLimits(inboxDir)
5485
+ );
5486
+ writeCapacityState(
5487
+ inboxDir,
5488
+ {
5489
+ ...state,
5490
+ lostEntries: saturatingAdd(state.lostEntries, 1),
5491
+ lostBytes: saturatingAdd(state.lostBytes, Math.max(0, bytes)),
5492
+ lastLostAtMs: Date.now()
5493
+ },
5494
+ capacityLock
5495
+ );
5496
+ return true;
5497
+ } catch {
5498
+ return false;
5499
+ } finally {
5500
+ capacityLock.release();
5501
+ }
5502
+ }
4502
5503
  async function readHookInboxStatusAsync(inboxDir = FML_HOOK_INBOX_DIR) {
4503
5504
  let limits;
4504
5505
  try {
@@ -4519,6 +5520,9 @@ async function readHookInboxStatusAsync(inboxDir = FML_HOOK_INBOX_DIR) {
4519
5520
  capacityRejectedBytes: capacity.rejectedBytes,
4520
5521
  ...capacity.lastRejectedAtMs === void 0 ? {} : { lastCapacityRejectedAtMs: capacity.lastRejectedAtMs },
4521
5522
  ...capacity.lastRejectionReason === void 0 ? {} : { lastCapacityRejectionReason: capacity.lastRejectionReason },
5523
+ lostEntries: capacity.lostEntries,
5524
+ lostBytes: capacity.lostBytes,
5525
+ ...capacity.lastLostAtMs === void 0 ? {} : { lastLostAtMs: capacity.lastLostAtMs },
4522
5526
  retiredEntries: retirement.retiredEntries,
4523
5527
  retiredBytes: retirement.retiredBytes,
4524
5528
  unreadable: false
@@ -4530,7 +5534,10 @@ async function readHookInboxStatusAsync(inboxDir = FML_HOOK_INBOX_DIR) {
4530
5534
  return emptyHookInboxStatus(limits, true);
4531
5535
  }
4532
5536
  }
4533
- async function drainHookInbox(capture, inboxDir = FML_HOOK_INBOX_DIR) {
5537
+ var defaultYieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
5538
+ async function drainHookInbox(capture, inboxDir = FML_HOOK_INBOX_DIR, options = {}) {
5539
+ const maximumEntries = options.maximumEntries ?? MAX_HOOK_INBOX_DRAIN_ENTRIES;
5540
+ const yieldToEventLoop = options.yieldToEventLoop ?? defaultYieldToEventLoop;
4534
5541
  const retirement = readRetirementState(inboxDir);
4535
5542
  const completedRetirement = completePendingRetirement(inboxDir, retirement);
4536
5543
  let failed = completedRetirement === null ? 1 : 0;
@@ -4548,7 +5555,7 @@ async function drainHookInbox(capture, inboxDir = FML_HOOK_INBOX_DIR) {
4548
5555
  try {
4549
5556
  const scan = await scanDrainEntryNames(
4550
5557
  inboxDir,
4551
- MAX_HOOK_INBOX_DRAIN_ENTRIES,
5558
+ Math.max(1, Math.floor(maximumEntries)),
4552
5559
  skipThrough,
4553
5560
  pendingRetirementName
4554
5561
  );
@@ -4582,8 +5589,14 @@ async function drainHookInbox(capture, inboxDir = FML_HOOK_INBOX_DIR) {
4582
5589
  }
4583
5590
  safeThrough = name;
4584
5591
  };
5592
+ let processedSinceYield = 0;
4585
5593
  for (const name of names) {
4586
- const filePath = path4.join(inboxDir, name);
5594
+ if (processedSinceYield >= DRAIN_YIELD_EVERY_ENTRIES) {
5595
+ processedSinceYield = 0;
5596
+ await yieldToEventLoop();
5597
+ }
5598
+ processedSinceYield += 1;
5599
+ const filePath = path5.join(inboxDir, name);
4587
5600
  const readResult = readEntryResult(filePath);
4588
5601
  if (readResult.kind === "missing") {
4589
5602
  markResolved(name, false);
@@ -4659,8 +5672,22 @@ export {
4659
5672
  openObserverDatabase,
4660
5673
  OBSERVER_PROTOCOL_VERSION,
4661
5674
  OBSERVER_SERVICE_NAME,
5675
+ OBSERVER_IDENTITY_HEADER,
4662
5676
  isObserverHealth,
4663
5677
  createObserverHttpServer,
5678
+ OBSERVER_HEARTBEAT_STALE_MS,
5679
+ isPidAlive,
5680
+ readObserverState,
5681
+ readObserverStateStatus,
5682
+ createInitialObserverState,
5683
+ ObserverStateMutationBusyError,
5684
+ inspectObserverLease,
5685
+ observerLeaseIsOwned,
5686
+ getProcessIdentity,
5687
+ claimObserverState,
5688
+ updateObserverState,
5689
+ removeObserverStateIfOwned,
5690
+ HookInboxCapacityError,
4664
5691
  createHookInboxEntry,
4665
5692
  persistHookInboxEntry,
4666
5693
  hookInboxDeliveryPayload,
@@ -4668,6 +5695,8 @@ export {
4668
5695
  acknowledgeHookInboxEntry,
4669
5696
  retireHookInboxEntry,
4670
5697
  readHookInboxStatus,
5698
+ readHookInboxCapacityCounters,
5699
+ recordHookInboxLoss,
4671
5700
  readHookInboxStatusAsync,
4672
5701
  drainHookInbox
4673
5702
  };