Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
# Enterprise migration: `@suaveplan/imessage-adapter`
- **Change ID**: `enterprise-imessage-adapter`
- **Status**: PROPOSED
- **Capability**: `imessage-adapter`
- **Target package**: `@suaveplan/imessage-adapter` at `0.1.0`
- **Target path**: `packages/communication/messaging/imessage-adapter/`
- **Design**: ./design.md
- **Spec delta**: ./specs/imessage-adapter/spec.md
- **Source on main**: `/mnt/s/web/suaveplan/packages/universal/imessage-adapter` (`~718 LOC`)
- **Enterprise plan**: `/mnt/s/web/suaveplan/enterprise/imessage-adapter.md`
## Why
`@suaveplan/imessage-adapter` is the macOS-side ingress for any genesis
messaging surface that wants to read from Apple's Messages app. It
polls the `~/Library/Messages/chat.db` SQLite database, decodes the
Apple-epoch / nanosecond timestamps, resolves handle + chat + attachment
joins, and emits `ParsedIMessage` payloads — and a downstream
`InboundMessage`-shaped projection — that any inbound-message router
can consume without coupling to Apple's schema.
The upstream package (`v1.0.0`) is functional but not enterprise-grade:
- `require("bun:sqlite")` is reached through an `any`-cast cast at every
call site, defeating type-checking and blocking testability.
- No structured error codes — every failure is a swallowed `console`-less
`error` event with a bare `Error`.
- No Zod boundary on config; no schema-registry hook.
- No OpenTelemetry instrumentation — no counters for messages emitted,
no histogram for poll duration, no error counter.
- No `AbortSignal` propagation on `start()` — `stop()` only.
- The internal listener registry duplicates `@suaveplan/events`'s
`EventBus`; the package should ride the shared bus.
- Tests don't exercise the watcher loop end-to-end against a real
SQLite chat.db fixture — they only construct the adapter and assert
on the un-initialised error path.
This change rewrites the package on genesis at enterprise quality
without removing a single export from the upstream surface, preserving
the contract that any future consumer (e.g. an inbound-message router,
the existing `@suaveplan/api-plugin-messaging` plugin family, or a
standalone agent) can rely on.
## What
A net-new package at
`packages/communication/messaging/imessage-adapter/` at `0.1.0`,
preserving the upstream public surface:
- Classes: `IMessageAdapter`, `IMessageWatcher`, `IMessageParser`
- Types: `IMessageRow`, `HandleRow`, `ChatRow`, `AttachmentRow`,
`ParsedIMessage`, `ParsedAttachment`, `IMessageAdapterConfig`,
`IMessageWatcherConfig`, `InboundMessage`, `MessageSourceAdapter`,
`IMessageWatcherEvents`
Enterprise additions on top:
1. **Structured errors via `defineCodes("IMESSAGE", [...])`** — 10
stable codes (`DB_OPEN_FAILED`, `DB_QUERY_FAILED`,
`WATCHER_NOT_RUNNING`, `WATCHER_ALREADY_RUNNING`,
`ADAPTER_NOT_INITIALIZED`, `INVALID_CONFIG`, `POLL_ABORTED`,
`POLL_FAILED`, `ATTACHMENT_PARSE_FAILED`,
`APPLE_DATE_PARSE_FAILED`), each reachable from a real call site
and asserted in tests.
2. **Zod-validated config boundary** at `IMessageWatcherConfigSchema`
and `IMessageAdapterConfigSchema`, used by the constructor; invalid
config throws `IMessageInvalidConfigError`.
3. **Injectable `SqliteReader` seam** (structural type) replaces the
`require("bun:sqlite") as any` cast. Production wiring uses
`bun:sqlite` lazily; tests use a real in-process `bun:sqlite`
database populated from a chat.db-shaped fixture (no mocks; rule 2).
4. **Injectable `Clock` + `Timers` + `IdGenerator`** for deterministic
polling, last-rowid pointer management, and envelope IDs.
5. **OTel instrumentation** — counters
`imessage.messages_emitted_total`, `imessage.poll_errors_total`,
`imessage.attachments_emitted_total`; histogram
`imessage.poll_duration_ms`.
6. **AbortSignal propagation** — `start({ signal })`, `poll({ signal })`,
and the internal scheduler all honour cancellation; aborted polls
throw `IMessagePollAbortedError`.
7. **Bus bridge** — `IMessageWatcher` exposes an `EventBus`-shaped
surface in parallel with its legacy `on/off` API so downstream
subscribers can compose with `@suaveplan/events` directly. The
legacy `on/off` API is preserved for back-compat.
8. **Idempotent rowid pointer** — `lastRowId` is settable + readable,
and the watcher refuses to re-emit messages with `ROWID <= lastRowId`
so persisted-pointer consumers can resume safely.
9. **100/100/100/100 coverage** with real SQLite fixtures (no mocks),
sibling `.md` ≥200 words per implementation file, root `README.md`.
## Impact
### Affected dependents
| Package | Impact | Action |
|---|---|---|
| `@suaveplan/events` | New consumer | None — additive |
| `@suaveplan/error` | New consumer | None — additive |
| `@suaveplan/opentelemetry` (via `@opentelemetry/api`) | New consumer | None — additive |
| `@suaveplan/types` | New consumer of `Clock`, `IdGenerator`, `LoggerLike` | None — additive |
| (future) `@suaveplan/api-plugin-messaging` / inbound-message router | Will consume `IMessageAdapter` as a `MessageSourceAdapter` | No live consumer today; future package can depend on `^0.1.0` |
No genesis package currently imports `@suaveplan/imessage-adapter`, so
this change has zero blast radius beyond adding the package to the
manifest + flipping the STATUS row.
## Out of scope
Nothing. Per `feedback_no_deferrals_on_migration.md` and `AGENTS.md
§2b`, no "phase 2" is permitted. The whole upstream surface plus the
enterprise additions land in this change, feature-complete, with
100/100/100/100 coverage.