From f2f01fd33e9e1d21bf5e43136fe43108092ad755 Mon Sep 17 00:00:00 2001 From: elhoim Date: Mon, 3 Aug 2026 07:01:01 +0000 Subject: [PATCH 1/4] fix(pulse/observability): close the fd when readJsonlByteTail throws Both sibling readers added in the same change already guard with try/finally; this one leaked a descriptor on any throw between open and close. --- .../LIFEOS/PULSE/Observability/observability.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts b/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts index a7cf16dba4..c94d707442 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts +++ b/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts @@ -143,10 +143,14 @@ function readJsonlByteTail(filePath: string, maxBytes: number): any[] { const size = statSync(filePath).size const start = Math.max(0, size - maxBytes) const fd = openSync(filePath, "r") - const buf = Buffer.alloc(size - start) - readSync(fd, buf, 0, buf.length, start) - closeSync(fd) - let text = buf.toString("utf-8") + let text: string + try { + const buf = Buffer.alloc(size - start) + readSync(fd, buf, 0, buf.length, start) + text = buf.toString("utf-8") + } finally { + closeSync(fd) + } if (start > 0) { const nl = text.indexOf("\n") text = nl === -1 ? "" : text.slice(nl + 1) From 6ef670c30ef4103a944e648500d54171ecb470ed Mon Sep 17 00:00:00 2001 From: elhoim Date: Mon, 3 Aug 2026 07:01:01 +0000 Subject: [PATCH 2/4] fix(pulse/observability): evict stale series keys from the activity and climb caches Each series was capped, but the number of KEYS was not: every slug and session_id ever folded stayed resident for the daemon's lifetime. Note for the porter: this is a behaviour change for climbCache, not only memory. buildIscDeltas computes all-time totals from a slug's points, so an ISA quiet for >24h then resuming re-seeds from prevDone = 0. --- .../LIFEOS/PULSE/Observability/observability.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts b/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts index c94d707442..d1549c6b75 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts +++ b/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts @@ -285,6 +285,21 @@ function foldClimbLine(map: Map, line: string): void { map.set(ev.slug, points) } +// Key-count eviction. The per-key series are ring-buffered (200 points / 600 +// ticks), but the number of KEYS was unbounded: every slug and every session_id +// the daemon ever folded stayed resident for its lifetime. Nothing downstream +// reads a series whose newest point is older than the stalest run window +// (RUN_ACTIVITY.nativeStaleMs, 4h), so a day of retention is generous. Called +// on the fold path only — an unchanged log returns early and grows nothing. +const CACHE_RETENTION_MS = 24 * 60 * 60 * 1000 + +function evictStaleSeries(map: Map, now: number): void { + for (const [key, series] of map) { + const last = series[series.length - 1] + if (!last || now - last.ts > CACHE_RETENTION_MS) map.delete(key) + } +} + export function getClimbData(): Map { try { if (!existsSync(WORK_EVENTS_PATH)) return new Map() @@ -314,6 +329,7 @@ export function getClimbData(): Map { climbCache.offset += Buffer.byteLength(complete, "utf-8") + 1 for (const line of complete.split("\n")) foldClimbLine(climbCache.data, line) + evictStaleSeries(climbCache.data, Date.now()) return climbCache.data } catch { return climbCache.data @@ -444,6 +460,7 @@ export function getActivityData(): Map { const remainder = chunk.slice(lastNewline + 1) activityCache.offset = st.size - Buffer.byteLength(remainder, "utf-8") for (const line of chunk.slice(0, lastNewline).split("\n")) foldActivityLine(line) + evictStaleSeries(activityCache.bySession, Date.now()) return activityCache.bySession } catch { return activityCache.bySession From 6eb8d9048b3cd32d71481872be3ab81613ecf3d9 Mon Sep 17 00:00:00 2001 From: elhoim Date: Mon, 3 Aug 2026 07:01:01 +0000 Subject: [PATCH 3/4] fix(pulse/observability): drop the gendered pronoun from a shipped API response buildTeamFromUserFiles emitted "serves his TELOS" in a function whose own comment says names never live in this file. --- LifeOS/install/LIFEOS/PULSE/Observability/observability.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts b/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts index d1549c6b75..c72271f2d1 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts +++ b/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts @@ -3959,7 +3959,7 @@ function buildTeamFromUserFiles(appIds: string[]): OverviewTeam[] | null { if (principal) { team.push({ id: "T0", name: principal, role: "Principal", kind: "human", owns: [], - avatar: principal.charAt(0), note: "Sets direction. Everything here serves his TELOS.", + avatar: principal.charAt(0), note: "Sets direction. Everything here serves the principal's TELOS.", }) } const da = nameFrom(readMd(join(USER_DIR, "DIGITAL_ASSISTANT", "DA_IDENTITY.md"))) From 5d71e649535a0c2577c02f73e32da2f165bb5bef Mon Sep 17 00:00:00 2001 From: elhoim Date: Mon, 3 Aug 2026 07:01:01 +0000 Subject: [PATCH 4/4] =?UTF-8?q?fix(pulse/observability):=20match=20the=20s?= =?UTF-8?q?hipped=20"(sample=20=E2=80=94=20=E2=80=A6)"=20entries=20in=20SA?= =?UTF-8?q?MPLE=5FENTRY=5FRE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The literal ")" missed the scaffold's own variants, so telosPersonalized() returned true on a fresh install, template mode never armed, and Pulse showed the sample mission and goals to a new user as their own. Matches the sibling filter in LIFEOS/TOOLS/GenerateTelosSummary.ts. --- LifeOS/install/LIFEOS/PULSE/Observability/observability.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts b/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts index c72271f2d1..b5362672bb 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts +++ b/LifeOS/install/LIFEOS/PULSE/Observability/observability.ts @@ -4234,7 +4234,10 @@ function handleLifeCardApi(): Response { // arm on a fresh install: the "run /interview" onboarding banner never rendered, // and Pulse presented the fabricated sample mission and goals to the new user as // if they were their own. Placeholder entries do not count as personalization. -const SAMPLE_ENTRY_RE = /^\**\s*\(sample\)/i +// \b not literal ")": the scaffold also writes "(sample — …)" variants (e.g. +// MISSION.md M2), which a literal match let through. Same fix as the sibling +// filter in LIFEOS/TOOLS/GenerateTelosSummary.ts. +const SAMPLE_ENTRY_RE = /^\**\s*\(sample\b/i function telosPersonalized(): boolean { try {