As AI service use grew, requests that would publish or share company material outside had to be blocked and recorded at the moment they ran, on both macOS and Windows and across a mix of subscription plans.
A resident daemon blocks policy-breaking publish and share requests, notifies the user, and records each attempt. A guard server registers devices through company-account OAuth (PKCE), collects heartbeats and block events, and sends only where a rule lives and its version; the daemon applies only signature-checked rules and keeps blocking when registration or reporting fails. The same policy path turned the usage-reporting switch on globally after two canary devices.
→ All 92 people on the active roster got the reporting switch, and all 87 reporting online devices applied it. macOS and Windows protection reports were confirmed; install, operations, and recovery guides were handed over, and the company-wide rollout belongs to another team.
The collection server, newly moved from an office NAS to AWS, was completely unresponsive for 2 days 18 hours 31 minutes and collected 0 records. The instance was running, with no error logs and no OOM kill.
The last sar samples before the gap showed memory pressure (memory use 62.90%→81.66%, page scan 153.77→332.58/s); disk, hypervisor contention, and CPU credits (exhausted 38 hours in, a result) were ruled out with evidence. ClickHouse claimed 90% of RAM by default on a host shared with other containers and without swap, so it could thrash instead of OOM-killing. The budget is now explicit, and the two large containers got mem_limit.
→ That closed one path, a default budget taking over a shared host; analysis of the next outage with the same signature points to a reparse loop as this one's likely trigger too. Upsizing waited: before observing the fix, it would only have paid to cover a wrong setting.
Once memory limits were in place, the collection app hit 19 OOM restarts under a 512m limit; after the limit went up to 768m, the host stopped responding for 17 hours 40 minutes.
The first fix for the restarts, an explicit Node heap cap, was dropped before it shipped: measurement showed Node 22 already sized its heap at about 51% of the cgroup limit. The 241MiB baseline the limit was based on had also been measured at idle. The restarts and the 17h 40m stall shared one cause, an endless reparse where any file with a pending chunk was reread whole; at 512m the loop killed only the container and the host held. Once a 176MB file had 9 pending chunks, every cycle pulled 45 chunks, 254.3MB including that file, into a string array. The 512MB file cap never applied to a 176MB file, the 2,048MB worker heap exceeded host RAM, and the 768m limit, the only one that fired, pushed out page cache each time and thrashed the host; my own raise from 512m to 768m turned restarts into a host stall. A 64MB file cap, 256MB heap, and 640m container broke the loop; with chunk-by-chunk streaming in place, the file cap went back to 256MB.
→ Parsing the largest file now peaks at 427.8MiB, 67% of the 640MiB limit. The O(n²) pattern, where one pending chunk rereads the whole file, is split out as follow-up work.
Throughout the 17h 40m outage the alerts fired and reached their channel, but nobody saw them, and the same had happened in the outage of 2 days 18 hours before it.
Of 104 channel messages over 3 days, 98 came from the bot and 0 from people, and none of the 47 firings carried a mention, so no mobile push went out. Shared routing and contact points stayed untouched; mentions went into the summary text of the 5 team-managed alert rules.
→ Without changing shared settings, mentions were added to the summary text of the 5 alerts.