- logger: emit straight to fd 1/2 (unbuffered). The buffered process.std*
streams block-buffer to a pipe under systemd, so low-volume logs never
flushed and were invisible.
- /client/imessage/contacts: return 401 (like /sync/batch) when the caller
presents the operator/service token instead of a device token, instead of
500ing on a null deviceId downstream.
- systemd unit: reflect the working deploy (root + /root/.bun, Redis
dependency, file logging since the droplet journald is volatile).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The server source imports bullmq/ioredis/sharp/exifr but they were never
declared (the stale pnpm-lock pinned tarballs to the dead black.lan registry).
Declare them, add REDIS_URL to the config schema (default local Redis) since
the queue connection already reads it, and replace the unusable pnpm-lock with
a bun.lock resolved against npmjs. Import graph now evaluates cleanly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The @lilith/quinn-db-pg@1.0.1-dev.* snapshot was only ever published to the
retired black.lan Verdaccio and resolves nowhere now (not on the DO forge, no
local cache, no source). Replace the single `createPool` import with a faithful
in-repo pg.Pool factory (service-name -> QUINN_<SERVICE>_DB_URL) and add `pg`
as a direct dependency (was transitive via the dead package).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generalize the photos-originals rclone-mount pattern to a video-projects
prefix so the video studio (and imajin ETL, per storage-portability-plan
§2.3) can read/write multi-GB project sources/renders as local files while
only hot data stays resident on plum (bounded VFS LRU cache). Lets a
small-disk laptop work with large footage without filling APFS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
autoqueue: send_rate_config gains auto_queue (default true). When on, the
pending endpoint holds over-cap sends to drip out (burst-friendly); when
off, the cap is disabled and queued sends release immediately. Threaded
through getSendRateConfig/setSendRateConfig and GET/PUT /admin/send-rate-limit.
provenance: send_queue gains authored_by + dispatched_by (who composed the
text vs what triggered the send), a fixed vocabulary (user, claude-prospector,
claude-messenger, autoresponder, scheduled-worker, unknown) validated at the
enqueue boundary and recorded on insert. Nullable for legacy rows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The /client/imessage/send-queue/pending endpoint released up to 50 queued
sends per poll, so an enqueued burst all fired at once. Add a configurable
release cap: the endpoint now returns at most (maxSends − sent-in-window)
queued items, so a burst queues and drips out at the configured rate.
- macsync.send_rate_config single-row table, default max_sends=10,
window_seconds=300 (10 per 5 min).
- entities/send-queue repo: getSendRateConfig / setSendRateConfig /
countSentWithin.
- Admin control: GET/PUT /admin/send-rate-limit (service-token auth) so the
cap is adjustable at runtime (wired to MCP via quinn.api separately).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The contact-summary sweep generated a 3-field digest (mostRecently /
overallSummary / recap) per iMessage contact via the model-boss chat
endpoint. It's redundant with the prospector, which already classifies
1271 prospects with tier + archetype + score + status — strictly richer
per-person intel for the contacts that matter. It was also the path that
wedged the server against the decommissioned model-boss host (2026-06-23).
Remove the generation path entirely: the per-sync sweep in
ingestContacts, the contact-summary feature module + its test, and the
now-orphaned chatJson client in shared/model-boss.ts (contact-summary was
its only caller). The connection circuit breaker stays — the
embedding-worker still calls the same coordinator and needs the same
wedge protection.
Kept the read-side data layer (summary_data column, summaryData field,
updateContactSummary, the /my/contacts surface field) dormant as the
landing spot if summaries are ever repopulated offline via batch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The contact-summary sweep and embedding-worker call model-boss (GPU
coordinator) sequentially. When its host is offline every call paid the
full TCP-connect timeout (~3s) before failing; a sweep over ~1700
contacts serialised thousands of slow failures and stalled the whole
server — message ingest froze for hours while the listener stayed up
(observed 2026-06-23, coordinator host decommissioned).
Add a connectivity circuit breaker in shared/model-boss.ts: after 3
consecutive connection failures it opens for a 60s cooldown and fails
fast (no fetch), auto-probing once afterwards to recover. The
contact-summary sweep now bails the moment the breaker is open instead
of queueing doomed per-contact work. HTTP error responses still count as
reachable — the breaker tracks connectivity, not request success.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>