2670934d | Clio pivot: research + decision record | sloth-ninja | 2026-08-03 | ↗ GitHub |
commit body Owner decision 03/08: Quill/Unity cancelled; Clio (Manage + Grow, EU) is
the first practice-management connector. Full two-API research in
docs/research/2026-08-03-clio.md; CLAUDE.md status updated; BUILD_LOG
entry. Docs only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
46cf57e9 | Merge pull request #59 from Sloth-ninja/clio-pivot-research | Ezana.H | 2026-08-03 | ↗ GitHub |
Clio pivot: research + decision record |
af85d350 | Clio connector PR 1: user_clio_connections migration | sloth-ninja | 2026-08-03 | ↗ GitHub |
commit body Owner-authorised 03/08/2026 (allowlist entry added by the owner).
Additive table: per-user encrypted OAuth tokens for Clio Manage + Grow
(one row per user per product, AES-256-GCM triplets per the
user_mcp_oauth_tokens precedent, rotation-safe design note), RLS
enabled, browser-role grants revoked. schema.sql mirrored.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
f13adcaf | Merge pull request #62 from Sloth-ninja/clio-connections-migration | Ezana.H | 2026-08-03 | ↗ GitHub |
Clio connector PR 1: user_clio_connections migration |
4ccb7835 | Clio connector PR 2: backend (lib/clio seam, routes, chat tools) | sloth-ninja | 2026-08-03 | ↗ GitHub |
commit body Self-contained lib/clio/ seam plus /clio + /clio-grow routes, chat-tool
wiring, and profile serialization for the Clio Manage + Grow connectors.
No migration/.env/schema.sql/LICENSE touched.
- config: region host maps (EU live), creds, redirect-URI derivation,
pinned Manage X-API-VERSION (4.0.11, env-overridable).
- connections: user_clio_connections CRUD over shared apiKeyCrypto;
42P01/42703-tolerant; atomic refresh-rotation persistence.
- oauth: start/callback both products; one-time in-process state; PKCE
S256 for Grow; who_am_i on connect; best-effort deauthorize.
- client: per-user authed fetch; proactive + 401 single refresh/retry;
Grow rotating refresh persisted before use; 429 Retry-After + rate
buckets (Manage 50/min, Grow 3/s app-wide); fixed error mapping.
- manage/grow chat tools registered through the companiesHouse seam,
gated on the product being connected; writes confirmed via prompt;
save-document enforces ensureDocAccess + 25 MB cap via presigned upload.
- routes: oauth start/callback (callback = exact registered path),
/status (both products + cheap Manage count), MFA-gated disconnect.
- chat + projectChat gate Clio tools on listConnectedProducts; profile
exposes clioConnections {manage, grow}.
Verified: tsc clean; vitest 584/584 (513 baseline + 71 new); prettier
clean on new files. Built on the 03/08 spike/write-probe verified facts
(EU hosts, seconds semantics, 3-step upload, rotation, rate limits).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2ebab293 | Clio PR2 correction: Grow who_am_i is live-verified - wire it on connect | sloth-ninja | 2026-08-03 | ↗ GitHub |
commit body The 03/08 spike exercised the Grow API with a real token: GET /users/who_am_i,
GET /matters, GET /inbox_leads, GET /users all returned 200. Earlier flag
wrongly called Grow endpoints "unverified".
- oauth: fetchClioIdentity now hits /users/who_am_i for BOTH products on
connect (Grow parsed defensively for shape drift - data/user/top-level,
name or first+last); best-effort, connect never fails on a who_am_i hiccup.
- growTools header + BUILD_LOG flags: /matters, /inbox_leads, /users,
/users/who_am_i are LIVE-VERIFIED; only the matter-note read/write endpoints
and /contacts remain research-based.
- tests: +2 (Grow identity stored on connect; connect survives who_am_i
failure). vitest 586/586.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2d6b71cb | Clio PR2 review fixes: wire proactive rate-limit backoff + deterministic tests | sloth-ninja | 2026-08-03 | ↗ GitHub |
commit body Independent review (#63): security paths passed; two should-fixes + three nits.
SHOULD-FIX 1 (chosen: WIRE, not delete) - proactive backoff was exported and
tested but never called. Now clioRequest records a per-bucket backoff when a
response reports X-RateLimit-Remaining 0 + a Reset, and the next call for that
bucket waits until reset (capped 5s) or fails fast with the fixed rate-limit
message. Added parseRateLimitResetMs (epoch/delta tolerant) + 3 tests
(reset parsing, over-cap fail-fast without a second request, within-cap
wait-then-proceed under fake timers).
SHOULD-FIX 2 - deterministic CI: the scrypt-heavy Clio suites (connections,
client, oauth, manageTools, growTools) now raise testTimeout to 20s with the
#61-style explanatory comment, so real scryptSync under 43 parallel files can't
flake the 5s default.
NITS: BUILD_LOG test counts → 589/589 (76 new); "presigned PUT/download" →
"presigned PUT never carries the bearer (no document download is implemented)";
added a line that migration 20260803_01 was already run in production 03/08 so
the connector activates on deploy (code stays 42P01-tolerant regardless).
Verified: tsc clean; prettier clean; full suite run TWICE back-to-back, both
589/589 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3f7a50e2 | Merge pull request #63 from Sloth-ninja/clio-connector-backend | Ezana.H | 2026-08-03 | ↗ GitHub |
Clio connector PR 2: backend (lib/clio seam, routes, chat tools) |
8886caec | Clio connector PR 3: frontend practice-management card | sloth-ninja | 2026-08-03 | ↗ GitHub |
commit body Adds the "Practice management - Clio" card at the top of Account ›
Connectors (above the MCP gallery), per the approved mock-up screen 1.
- ClioConnectorCard.tsx: self-contained card - Manage/Grow rows, state
pills (green Connected-as / neutral Not-connected), Connect (OAuth
popup) + MFA-gated Disconnect (confirm dialogue, optimistic + rollback),
loading skeleton, LoadErrorRow-style retry, not-configured honest line.
- mikeApi.ts: getClioStatus / startClioConnect / disconnectClio + types;
clioConnections added to UserProfile.
- connectors/page.tsx: render the card above the gallery.
Connect uses popup-location return-polling keyed off the backend's
redirect callback (routes/clio.ts redirects, it does not postMessage), so
no change to the connectors page. Frontend only; backend untouched
(tsc clean, vitest 589/589). UK English; aria-labels on actions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
cb5c7a77 | Clio PR 3 review fixes: unconfigured pill, drop unused field, BUILD_LOG | sloth-ninja | 2026-08-03 | ↗ GitHub |
commit body - ClioConnectorCard: an unconfigured product row (other product
configured) now shows a "Not available" pill with title/aria hint
"Not configured on this deployment." instead of "Not connected".
- mikeApi: remove the unread/unpopulated clioConnections field from
UserProfile (nit).
- BUILD_LOG: reword deviation 2 - Clio card reachability depends on the
firm's live memberMcpConnectors value (hidden for all org users incl.
admins when OFF); exemption is an open owner decision for the deploy
gate. Drop the stale clioConnections mention.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
b9289056 | Merge pull request #64 from Sloth-ninja/clio-connector-frontend | Ezana.H | 2026-08-03 | ↗ GitHub |
Clio connector PR 3: frontend practice-management card |
e2573b2e | Exempt Clio card from the memberMcpConnectors policy | sloth-ninja | 2026-08-03 | ↗ GitHub |
commit body Owner decision (03/08): the "Members may add custom connectors" policy
governs custom MCP servers only; the Practice management - Clio card must
be reachable for all org users regardless.
- connectors/page.tsx: the personalConnectorsBlocked early-return now
renders <ClioConnectorCard /> above the neutral FirmManagedCard (which
stays for the MCP gallery); neutral copy narrowed to "Custom connectors".
- account/layout.tsx: Connectors tab always visible (drop the
personalConnectorsBlocked filter branch + its now-unused import).
- BUILD_LOG: new entry (layout decision (a) recorded); the #64 open owner
decision marked TAKEN (exempt).
Frontend only; backend untouched. tsc clean; eslint clean on changed files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
3684498b | BUILD_LOG: restore the orphaned PR 3 heading, normalise entry separator | sloth-ninja | 2026-08-03 | ↗ GitHub |
commit body The exemption entry's heading had displaced #64's "Clio connector PR 3:
frontend" heading, orphaning its Scope block. Reinstate the PR 3 heading
above that block and normalise to a single `---` + blank line between the
two entries.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
86986c91 | Merge pull request #65 from Sloth-ninja/clio-policy-exemption | Ezana.H | 2026-08-03 | ↗ GitHub |
Exempt Clio card from the memberMcpConnectors policy |
dd00a829 | Clio train: composed-range fix wave | sloth-ninja | 2026-08-03 | ↗ GitHub |
commit body Address the six findings from the composed-range multi-lens review of the
Clio connector train (PRs 2/3 + policy-exemption), plus one stale-comment nit.
1. SAR completeness (#52 pattern): buildUserAccountExport gains a token-free
clio_connections section via getClioConnectionMetadata (allowlist re-projection,
42P01/42703-tolerant) - never any encrypted/token material.
2. Account deletion best-effort revokes each connected Clio grant
(revokeAllClioGrants) before auth.admin.deleteUser; failures never block delete.
3. Connect-flow timeout re-checks /clio/status once before declaring failure,
so a slow-but-successful connect self-heals; honest fixed message otherwise.
4. A refresh rejected as invalid_grant prunes the dead connection row so the
stale connected pill self-heals; transient/network failures never prune.
5. Policy-OFF connectors page: page-level "Connectors" heading now sits above
the Clio card (FirmManagedCard heading made optional).
6. BUILD_LOG: add the missing --- between the PR 3 and PR 2 entries.
Also correct client.test.ts's stale "5s cap" comment to 3s (MAX_RETRY_AFTER_MS).
Backend tsc clean, vitest 600/600 (589 baseline + 11 new). Frontend tsc clean,
ESLint clean on changed files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
b619847a | Clio train close-out: status refresh | sloth-ninja | 2026-08-03 | ↗ GitHub |
commit body CLAUDE.md Current status: Clio connector v1 complete (#62-#66), both
sides deployed 03/08, migration run in production, pilot onboarding =
owner + two solicitors connect via the Practice management card.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
aca24961 | Merge pull request #66 from Sloth-ninja/clio-train-fixes | Ezana.H | 2026-08-03 | ↗ GitHub |
Clio train: composed-range fix wave |
df1ec415 | Merge pull request #67 from Sloth-ninja/clio-closeout | Ezana.H | 2026-08-03 | ↗ GitHub |
Clio train close-out: status refresh |
14d45833 | Record pilot Clio per-user permissions verification (04/08) | sloth-ninja | 2026-08-04 | ↗ GitHub |
commit body Lindsay's reconnect succeeded after the API_PUBLIC_URL fix and her
matters-visible count differs from the owner's as expected - the
owner-designated per-user confidentiality check for Clio v1 passes.
Docs-only: BUILD_LOG entry, HANDOVER §0.1 resolved, CLAUDE.md status.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
6aae01fa | Address review: separator, scoping-claim precision, count/chronology fixes | sloth-ninja | 2026-08-04 | ↗ GitHub |
commit body Review findings on #70: BUILD_LOG entry separator; soften the per-user
scoping claim to what was observed (count-level check only); 604 tests;
status-date bump; verification sentence moved after the incident it
depends on and scoped to the two provisioned accounts; HANDOVER #69;
UK 'solicitors'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
faaf5a6b | Clio matter search: status filter, 100-result pages, honest pagination | sloth-ninja | 2026-08-04 | ↗ GitHub |
commit body Pilot bug (04/08): "open Kyckr matters" made the model loop targeted
searches for minutes because clio_find_matter hardcoded limit 10 with no
filters or pagination. Now: page size 100 (Clio caps index actions at
200/request), validated open/pending/closed status filter passed through
as Clio's comma-separated status param, page_token continuation minted
from meta.paging.next and replayed against the API base, and a
{ matters, count, total_entries, has_more, next_page_token? } payload so
the model reports totals honestly instead of guessing. meta.records is
undocumented in reachable Clio docs, so it is parsed tolerantly (null
when absent). Schema + system prompt teach: status arg not query
keywords, report totals, fetch further pages only on request, never
brute-force loop.
Verified: tsc clean, 616/616 vitest (baseline 604 + 12 new), prettier
clean on changed files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
381d6fff | Merge pull request #70 from Sloth-ninja/pilot-verification-aug4 | Ezana.H | 2026-08-04 | ↗ GitHub |
Record pilot Clio per-user permissions verification (04/08) |
11373e51 | Review fixes: opaque page_token cursor, raw-next has_more, FAQ citation | sloth-ninja | 2026-08-04 | ↗ GitHub |
commit body Round-1 review blocker: the path-shaped page_token with a
startsWith("/matters.json") guard was traversal-bypassable because
buildUrl's new URL() normalises ".." segments (DURABLE_LESSONS
2026-07-28 class). Redesigned to an opaque cursor:
matterPageTokenFromNext now parses meta.paging.next, requires the exact
Manage API origin and exact <apiBase>/matters.json pathname, and
extracts only the page_token query-param value; the continuation
request is rebuilt from named parts (hardcoded path, our fields+limit,
repeated query/status per the schema) so a model-supplied string never
becomes a request path. has_more now derives from the raw presence of
meta.paging.next, never from cursor extraction success. Status-filter
citation pinned to docs.developers.clio.com/faq/ (case sensitivity
undocumented; lowercase literals enforced); at-least-one-argument rule
stated in the function description. Tests: traversal/suffix-path/
off-host/protocol-relative next URLs rejected, traversal-shaped
page_token asserted to never reach the path, continuation rebuild
asserted to carry fields+limit+cursor. 620/620 tests, tsc + prettier
clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
ea89d17e | Final review wave: filter-bound page token, host-spoof tests, length bound | sloth-ninja | 2026-08-04 | ↗ GitHub |
commit body Bind the continuation token: next_page_token is now base64url JSON
{ c: cursor, q?, s? } carrying the query/status the page was actually
fetched with. On continuation the token is length-bounded (<=1024),
decoded (failures -> the fixed friendly error before any fetch), its
status re-validated, and the decoded filters used - model-supplied
query/status are ignored under page_token, so a model dropping optional
args on a follow-up can never silently fetch the unfiltered matter list.
Schema/prompt now say "pass page_token alone; filters are remembered".
Removed the uncited "next URL = original params + page_token" comment
claim. Added the two named 2026-07-28 host-spoof regression shapes
(suffix-domain, userinfo) to the matterPageTokenFromNext tests.
627/627 tests (23 new vs baseline), tsc + prettier clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
74646d1e | Merge pull request #71 from Sloth-ninja/clio-matter-search-fix | Ezana.H | 2026-08-04 | ↗ GitHub |
Clio matter search: status filter, 100-result pages, honest pagination |