Clio read-only reactivation
Status: in-progress. Owner: kwiss. Approved scope: firm reads; no Clio data writes. This is not deployment or activation approval.
Observed starting state
Owner-provided baseline: the sole organization connector is revoked, with no scopes; the feature flag is unset. Last full/matter sync was June 25; last contacts sync was June 2. No worker re-queries live preprod to reconfirm these facts.
Shared policy and transport
@workspace/connectors-coreexports the pureisClioReadOnlyEnabled(): boolean: exactlyprocess.env.CLIO_READ_ONLY_ENABLED === "1". No legacy alias and no second switch. Turbo passes that single flag through.- Data HTTP uses one
CLIO_READ_METHOD = "GET"constant. Each attempt checks the shared switch before network access. Runtime method/body overrides and method-override headers/queries are rejected. Data destinations are HTTPS, same approved Clio origin, and matter/contact/user read paths; redirects are errors. - The only POST exception is the private, fixed
/oauth/tokenexchange/refresh implementation, with onlyauthorization_codeandrefresh_tokengrants. It also checks the switch and refuses redirects. No configurable POST destination. - Remote revoke, activity creation, webhook subscribe/unsubscribe/renew are always denied without fetch. Required public interface signatures remain throwing stubs to avoid cross-package churn.
- Requested scopes are exactly
[read_contacts, read_matters, offline_access], neverwrite_webhooks. Provider validity of these strings remains unproven; an authorize URL only proves what the application requested. - When a token response omits scope, the credential bundle records
[], on exchange and refresh. Requested or previous scopes are not substituted as evidence of a grant. Callers must treat the resulting scope-dependent capability as unavailable rather than claim proven consent. - The AST guard scans production source recursively, inventories all calls (including injected-fetch aliases), verifies the read constant and exact private OAuth site/path/grants, and rejects unknown methods/sites. Comments cannot satisfy it.
Runtime, MCP and connection posture
- Explicit read/write split on both catalogues and invocation paths. Writes stay hidden in every mode, including old broad grants. Recheck posture at callbacks/invocation, not only catalogue construction.
- Hide Clio reads on disconnection; re-admit authorized reads in runtime and MCP after the owner reconnects through the existing UI OAuth flow, while the shared switch is on. No new reconnect action/tool or tool-based authorization capability is approved.
- Use the same switch in startup/mode logs and orchestration. Turning it off is the single reversal; it does not turn any write capability on.
Ingestion and cold start
- After safe deployment and owner reconnect, perform a fresh full enumeration that upserts available records; stale last-sync dates are not evidence of current coverage. Historical deletions missed while revoked remain unproven: this is not complete provider reconciliation or an outage-coverage promise. Poll matters and contacts without managing provider webhooks.
- Do not create fake subscription rows to unlock backfill. Add the provider filter to renewal scans: the existing
user_id IS NULLSQL condition is not OneDrive-only because it lacksc.type. - Retain safe authenticated webhook-delivery and queued-job ingestion; disabling provider webhook management must not silently delete received work. Apply connection and shared-switch posture at execution boundaries.
- Legitimate empty delta results succeed. Do not confuse an empty poll with a skipped/disabled operation.
- There is no persistent backfill cursor. Request-level retries can recover a 429/5xx in place; only exhausted attempts followed by a job retry restart the walk. No resumable-cursor or outage-coverage promise.
- No schema, migrations or grants change.
Parallel ownership and review dispositions
The original three-way parallel split was transport, runtime/MCP/web, and ingestion/API. After the initial worker stopped without edits and surface fan-out was re-estimated, runtime/web and MCP received separate owners. Each owner stays in its scoped files; transport publishes the shared contract first. No worker or orchestrator deploys.
Independent plan review: Grok 4.6 deletions seat, medium, closed. This was the substitute cross-family reviewer; no Claude session was launched. Kept the explicit read/write split, catalogue and callback posture checks, no fake subscription rows, throwing interface stubs, and URL/redirect confinement. Rejected skipping AST (explicit owner requirement), a second switch (single reversal requirement), and the claim that empty poll results alone fake success. Rejected the OneDrive-only renewal-scan claim after SQL verification. Fresh Astra and Grok code reviews follow implementation; this plan review does not replace them.
Acceptance gates
- Transport: full connector-clio and connectors-core suites, own lint/typecheck, behavioral mutants and AST mutant fixtures. Mutation runs use scratch copies so other workers never observe a mutated shared switch. Collection errors and assertion deletion do not count as mutation proof.
- Runtime/web and MCP owners: catalogue and callback posture tests, reads hidden while disconnected, authorized reads re-admitted after owner reconnect through the existing UI OAuth flow, writes always hidden. No tool-based authorization capability.
- Ingestion/API owner: fresh backfill, polling without webhook management, safe retained deliveries/jobs, provider-filtered renewal, shared-switch mode logs and guards.
- Orchestrator: aggregate gates, fresh two-model code reviews, then doc status/index generation. Do not mark implemented while any acceptance gate remains open.
Release boundary
No preprod activation, live API access, credentials changes, schema work or deploy from these workers. Neither workers nor orchestrator deploy. kwiss reconnects only after the safe deploy through the authorized deployment process; a green local test does not prove provider acceptance or authorize activation.
Transport progress
Transport verification after class-test restoration: bun run test in connector-clio passed 87 tests (10 files); connectors-core previously passed 75 tests (11 files), with no subsequent core source change. Connector lint/typecheck and the mutation runner's standalone TypeScript check passed again. Core typecheck passed; core lint had no errors and nine existing unused-parameter warnings in test/fixture/echo-connector.ts; focused lint on the three changed core files was clean.
bun mutation-proof.ts in connector-clio killed 51/51 deliberate mutants in isolated scratch copies, accepting only Vitest assertion failures, then passed 60 connector policy/class tests and 7 core tests after scratch restoration. All eleven class mutants failed assertions: six identity/capability checks, refresh/matter/contact delegation, webhook success and webhook network-before-denial. The AST suite includes bare-fetch/whitelisted-name, injected-fetch, undici import, dynamic loader and shadowed-constant escape fixtures, with stale replacement detection. A local transport smoke previously passed: disabled read made zero requests, enabled GET and fixed token POST succeeded, and activity creation was denied. Its throwaway script was removed.
Deletion audit correction (kept orchestrator finding): restored connector.test.ts and its existing class identity, capability and refreshAuth/syncMatters/syncContacts delegation assertions. Only the obsolete webhook-success expectation is replaced with rejection and no-network assertions; the test explicitly opts into the read-only switch. Class contracts must not be removed to narrow mutation work or fit the cap. No assertion deletion counts as mutation proof. The initial working-tree mutation attempt restored source on a surviving password-only URL mutant; that case was added, both full suites passed, and subsequent mutation runs used scratch copies exclusively.
The shared switch, denied write methods, confined transport, conservative scope recording and local regression guards are implemented. Verification and mutation results are recorded in the transport worker report. Other owners and fresh code reviews remain required; status stays in-progress.