Skip to content
Back to skills

Live Connectors

ASecurity

Wire a live GL/accounting-system OAuth2 extractor (QuickBooks Online / NetSuite / Sage Intacct / Xero) that feeds the canonical trial-balance staging seam, using the reference-implementation token client in scripts/connectors/ — atomic persist-then-use rotating refresh, per-entity lock, error-cause routing (401 refresh / 429 backoff / invalid_grant re-auth), Xero 30-min grace — plus drill-through GL lineage that feeds statement_engine --gl-detail unchanged. Reference impl + record/replay harn...

  • 7 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 23, 2026
ai-agentspythonrustgoshellsecurity

Works with

  • cli

Security analysis

A100/100

Scanned September 23, 2026

npx -y skills add mcorbett51090/RavenClaude --skill live-connectors --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Live Connectors?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Live Connectors
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mcorbett51090-live-connectors/badge)](https://www.skillsdirectory.com/skills/mcorbett51090-live-connectors)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: live-connectors
description: "Wire a live GL/accounting-system OAuth2 extractor (QuickBooks Online / NetSuite / Sage Intacct / Xero) that feeds the canonical trial-balance staging seam, using the reference-implementation token client in scripts/connectors/ — atomic persist-then-use rotating refresh, per-entity lock, error-cause routing (401 refresh / 429 backoff / invalid_grant re-auth), Xero 30-min grace — plus drill-through GL lineage that feeds statement_engine --gl-detail unchanged. Reference impl + record/replay harness; NOT live-verified. Used by `controller`."
---

# Skill: live-connectors

**Purpose:** turn "a controller has OAuth access to their GL" into "a raw export lands in the
canonical staging seam, with drill-through provenance" — safely. The single highest-consequence
failure in this tier is a **dropped rotating refresh token** (QBO + Xero): lose it on a crash and
you are locked out mid-close until a human re-auths. This skill's reference implementation encodes
the four disciplines that prevent that, and a **record/replay harness** so you can develop and test
the whole path with **zero live credentials and zero network**.

> **HONEST BOUNDARY — read first.** Everything here is a **reference implementation + offline
> harness**, NOT a live-verified, certified connector. No live credentials are used; no live socket
> is ever opened by this code. **Every provider "fact" in this skill and in
> [`../../knowledge/finance-elt-connector-facts.md`](../../knowledge/finance-elt-connector-facts.md) —
> token lifetimes, rate limits, the QBO/Xero rotating-refresh behavior, the Xero 30-minute grace
> window, error-code semantics — is `doc-sourced, not live-observed; re-confirm before go-live`.**
> It is **training knowledge**, **settling-gated**: browser-verify each against its primary provider
> doc before it gates a live build; do not treat any of it as observed from a running integration.
> Live wiring — the real IdP, the real client credentials, the real token/report endpoints,
> and the real warehouse to land into — is **the consumer's step**. Outputs are decision-support
> scaffolding, not an accounting/audit/tax opinion and not a competitive claim of a working
> integration (see [`../../CLAUDE.md`](../../CLAUDE.md) §3).

## What's in the box

| File | Role |
|---|---|
| [`../../scripts/connectors/oauth_client.py`](../../scripts/connectors/oauth_client.py) | Token-lifecycle client. Providers-as-data; **atomic persist-then-use** rotating refresh; **per-entity `fcntl` lock**; **error-cause routing**; **Xero 30-min grace**. **security_review target — token handling.** |
| [`../../scripts/connectors/replay_transport.py`](../../scripts/connectors/replay_transport.py) | Stdlib **record/replay** transport. Serves recorded synthetic fixtures; **fails loudly on a missing fixture**; **never opens a socket**. |
| [`../../scripts/connectors/adapters/`](../../scripts/connectors/adapters/) | Per-provider adapters (QBO / NetSuite / Sage Intacct / Xero). Each emits a **raw export file** that `tb_stage.py` already normalizes — staging is **not** re-implemented. |
| [`../../scripts/connectors/gl_lineage.py`](../../scripts/connectors/gl_lineage.py) | Drill-through lineage: first 6 columns **byte-identical** to `statement_engine --gl-detail`, plus `source_system,source_type,source_id,source_doc_url`. Feeds the engine **unchanged**. |
| [`../../scripts/connectors/fixtures/`](../../scripts/connectors/fixtures/) | Synthetic, obviously-fake recorded fixtures (fake realm/tenant ids; fake token values) per provider. |
| [`../../scripts/test_connectors.py`](../../scripts/test_connectors.py) | 31-test acceptance suite (all the invariants below). |

## The rotating-refresh failure mode and the four disciplines

For **QBO and Xero** the refresh token **rotates**: a successful refresh returns a NEW refresh
token and kills the old one. The client encodes all four required mitigations:

1. **Persist-then-use, atomically.** The new token pair is written to a temp file and
   `os.replace()`d into the store **before** the new access token is ever used. Consequence:
   a crash **after** the rename leaves the NEW token durable; a crash **during** the write leaves
   the OLD token fully intact. There is never a half-written store and never a silent lockout.
2. **Per-entity exclusive lock.** An `fcntl.flock` on a per-entity lockfile wraps the
   read-refresh-write critical section; a recheck-under-lock collapses two racing refreshes to
   **one** rotation (two processes can't each rotate and invalidate the other).
3. **Error-cause routing — the cause selects the fix, and they are not interchangeable.**
   `401` (expired access) → refresh + retry the same route; `429` → backoff honoring `Retry-After`;
   `400 invalid_grant` (dead refresh) → **REAUTH_REQUIRED**, non-retryable, **fire the alert hook**,
   never backoff. Guessing the cause picks the wrong fix.
4. **Xero 30-min grace.** A refresh whose response is lost leaves you unsure whether the server
   rotated; within the provider grace window the client retries with the **existing** refresh token
   rather than assuming a rotation it never observed.

## Consumer OAuth wiring runbook

1. **Copy the config template per entity** — [`../../templates/connector-config.template.json`](../../templates/connector-config.template.json).
   Set `source_system`, the one matching `source_ids.*`, the `token_store_path`, and — critically —
   set `rotating_refresh_token: true` for **QBO and Xero**.
2. **Provision credentials as ENV-VAR NAMES only.** The template stores `client_id_env`,
   `client_secret_env`, `refresh_token_env` — the **names** of environment variables, never the
   values. Populate the actual secrets in your runtime's secret manager. **Never commit a token
   value or a `.token.json` store.**
3. **First-time auth (interactive).** Run the provider's authorization-code flow (Xero: **+ PKCE**)
   to obtain the first access+refresh pair, and write it to `token_store_path` (0600). This is the
   one step that needs a human/browser; automate the rest.
4. **Implement the live transport.** `oauth_client.OAuthClient` takes an injected `transport` with a
   `token_request(url, data) -> (status, body, headers)` seam. For development/tests use
   `ReplayTransport`; for production supply a real HTTPS transport (e.g. stdlib `urllib`/`http.client`
   or your HTTP library) that calls the provider `token_url`. **The token-lifecycle disciplines live
   in `OAuthClient`, so the live transport stays thin.**
5. **Wire the alert hook + re-auth runbook.** Pass `alert_hook=` so an `invalid_grant` pages an
   on-call controller. Document the interactive re-consent path per source (authorize URL → consent →
   capture code → exchange → persist atomically) so access is restorable without an engineer.
6. **Extract → adapter → stage.** Pull the TB/report, hand it to the provider adapter to emit a raw
   export file, then normalize with `tb_stage.py stage` using that provider's column-map. The staged
   CSV flows straight into `statement_engine.py --tb`.
7. **(Optional) drill-through lineage.** Build a lineage file with `gl_lineage.py build` (a
   `--gl-detail` file + a source-doc sidecar) and pass it to `statement_engine.py --gl-detail`; the
   badge lifts to `GL-detail-traced` and the source-doc keys ride into the reasoning trail.

```shell
# Offline (replay) end-to-end, zero credentials:
python3 scripts/connectors/gl_lineage.py build \
  --gl-detail skills/produce-gaap-statements/examples/gl-detail-2026-06.csv \
  --source-system qbo \
  --docs scripts/connectors/fixtures/lineage/source-docs.csv \
  --out /tmp/lineage.csv
python3 scripts/statement_engine.py --entity <entity.json> --coa <coa.csv> \
  --tb <staging.csv> --gl-detail /tmp/lineage.csv   # badge -> GL-detail-traced
```

## Verify before you trust the numbers

Run the acceptance suite: `python3 scripts/test_connectors.py` (31 tests, stdlib-only). It proves
the persist-then-use ordering, crash-safety both ways, the one-rotation lock, the invalid_grant
re-auth path, the three-way error routing, the Xero grace retry, the replay transport's loud
missing-fixture failure + no-socket guarantee, and the lineage byte-identity + badge lift. Before a
**live** build, also browser-verify the settling-gated provider facts in
[`../../knowledge/finance-elt-connector-facts.md`](../../knowledge/finance-elt-connector-facts.md).

## What this is not

Not a certified or live-verified connector; not an accounting/audit/tax opinion. The harness proves
the **token-handling and lineage disciplines** against synthetic fixtures — it does **not** prove the
source GL is correct, reconciled, or complete. A clean extract is a necessary, not sufficient,
condition for a trustworthy close: reconciliation ([`../reconciliation-summary/SKILL.md`](../reconciliation-summary/SKILL.md) /
[`../reconciliation-automatch/SKILL.md`](../reconciliation-automatch/SKILL.md)) and the governed
review→approve→lock spine ([`../close-approval-workflow/SKILL.md`](../close-approval-workflow/SKILL.md))
still apply.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…