chore(connect): provider-host ratchet in check:guards, drop the dead Arcim gateway client (#2178)

* chore(connect): provider-host ratchet in check:guards, drop the dead Arcim gateway client

Two boundary chores from the Connect plan. (1) A per-file ratchet in
scripts/checks/no-new-antipatterns.mjs over files under lib/, app/ and
extensions/ that name a provider API host (Enable Banking, Skatteverket,
Qvalia, Fortnox, Visma, Briox, Bjorn Lunden, Bokio, Bolagsverket, TIC, Meta,
Gmail). The 22 files that do so today are grandfathered in the baseline; a
new one fails the guard with the connector routing as the remedy, and the set
may only shrink as upstreams move behind the connector. (2) The client for
the retired Arcim Sync gateway (extensions/general/arcim-migration/lib/
arcim-client.ts) is deleted with its test: provider-client.ts replaced it and
nothing else imported it. The --update rewrite also locks in the lower
naive-ore-round count (620 to 617) that main already reached.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMUUom4fUk6zi4xYZSSfat
Signed-off-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>

* chore(guards): provider-host ratchet is case-insensitive and skips colocated .test.tsx; document the own-credentials exception

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>

---------

Signed-off-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-09-02 20:57:37 +02:00
committed by GitHub
co-authored by Claude Fable 5.1 Jakob Wennberg
parent c0ecf2fa3b
commit 8c8996773f
5 changed files with 93 additions and 513 deletions
+28 -1
View File
@@ -7,7 +7,7 @@
]
},
"naiveOreRound": {
"count": 620
"count": 617
},
"handRolledInvariants": {
"count": 113
@@ -36,5 +36,32 @@
"rawReferenceFetch": {
"count": 0,
"files": []
},
"providerHosts": {
"count": 22,
"files": [
"app/api/connect/bank/[...path]/route.ts",
"extensions/general/bolagsverket/lib/client.ts",
"extensions/general/enable-banking/index.ts",
"extensions/general/enable-banking/lib/api-client.ts",
"extensions/general/mail/lib/gmail-client.ts",
"extensions/general/skatteverket/index.ts",
"extensions/general/skatteverket/lib/connector-mode.ts",
"extensions/general/skatteverket/lib/ombud-client.ts",
"extensions/general/skatteverket/lib/skattekonto-client.ts",
"extensions/general/tic/index.ts",
"extensions/general/tic/lib/bankid-client.ts",
"extensions/general/tic/lib/bankid-types.ts",
"extensions/general/whatsapp-inbox/lib/graph-api.ts",
"lib/connect/upstreams/enable-banking-jwt.ts",
"lib/invoices/transports/qvalia.ts",
"lib/providers/bjornlunden/config.ts",
"lib/providers/bjornlunden/oauth.ts",
"lib/providers/bokio/client.ts",
"lib/providers/bokio/config.ts",
"lib/providers/briox/config.ts",
"lib/providers/fortnox/config.ts",
"lib/providers/visma/config.ts"
]
}
}
+57 -1
View File
@@ -390,6 +390,34 @@ function countNaiveRound() {
return count
}
/**
* 9. provider-host: files that talk to an external provider API directly.
* Provider integration logic is moving behind the connector (hosted
* `app/api/connect/*` today, the Accounted Connect service later): the open
* repo keeps the ledger, the contract and the manual file paths, and a
* self-hosted instance reaches every provider through its connector key.
* Per-file ratchet: the grandfathered set may only shrink. A NEW file naming a
* provider API host is a boundary violation unless it is the connector's own
* hosted adapter side.
*/
const PROVIDER_HOST_RE =
/api\.enablebanking\.com|api\.tilisy\.com|api\.skatteverket\.se|peroauth2\.skatteverket\.se|sso\.skatteverket\.se|api\.qvalia\.com|api-test\.qvalia\.com|api\.fortnox\.se|apps\.fortnox\.se|vismaonline\.com|briox\.services|apigateway\.blinfo\.se|api\.bokio\.se|api\.bolagsverket\.se|api-accept2\.bolagsverket\.se|id\.tic\.io|graph\.facebook\.com|gmail\.googleapis\.com/i
function findProviderHostFiles() {
const files = [
...walk(path.join(ROOT, 'lib'), ['.ts', '.tsx']),
...walk(path.join(ROOT, 'app'), ['.ts', '.tsx']),
...walk(path.join(ROOT, 'extensions'), ['.ts', '.tsx']),
]
const found = []
for (const f of files) {
const r = rel(f)
if (r.includes('__tests__/') || r.endsWith('.test.ts') || r.endsWith('.test.tsx')) continue
if (PROVIDER_HOST_RE.test(fs.readFileSync(f, 'utf8'))) found.push(r)
}
return found.sort()
}
/**
* Occurrences of a shared format rule written out by hand instead of imported
* from lib/invariants/. Counted, not file-setted: the campaign lowers the
@@ -1025,6 +1053,7 @@ const current = {
rawRouteAuth: findRawRouteAuth(),
naiveOreRound: countNaiveRound(),
handRolledInvariants: countHandRolledInvariants(),
providerHosts: findProviderHostFiles(),
ledgerScanningReports: findLedgerScanningReports(),
directJelInsert: findDirectJelInserts(),
leakySupabaseClients: findLeakySupabaseClients(),
@@ -1064,6 +1093,10 @@ if (isUpdate) {
count: current.rawReferenceFetch.length,
files: current.rawReferenceFetch,
},
providerHosts: {
count: current.providerHosts.length,
files: current.providerHosts,
},
}
fs.writeFileSync(BASELINE_PATH, JSON.stringify(baseline, null, 2) + '\n')
console.log(
@@ -1344,6 +1377,26 @@ if (newLedgerScans.length) {
)
}
// 1c2. provider-host: a file naming a provider API host outside the
// grandfathered set is a NEW direct integration in the open repo.
const providerHostBaseline = new Set(baseline.providerHosts?.files ?? [])
const newProviderHosts = current.providerHosts.filter((f) => !providerHostBaseline.has(f))
const fixedProviderHosts = (baseline.providerHosts?.files ?? []).filter((f) => !current.providerHosts.includes(f))
if (baseline.providerHosts && newProviderHosts.length) {
failed = true
console.error(
`\n✗ provider-host: ${newProviderHosts.length} new file(s) call a provider API host directly:`,
)
newProviderHosts.forEach((f) => console.error(` ${f}`))
console.error(
' → provider integration logic lives behind the connector, not in the open ledger:\n' +
' route the call through the hosted connector (app/api/connect/*) and the\n' +
' instance-side connector-mode seam (lib/connect/instance/upstreams.ts), or\n' +
' keep the manual file path. If this file IS the connector\'s own hosted adapter\n' +
' side, re-baseline with --update and say so in the PR.',
)
}
// 1d. raw-reference-fetch: per-file ratchet. A file outside the baseline set
// that fetches reference data raw (see raw-reference-fetch.mjs) is a NEW
// violation; grandfathered files stay until they move to the hooks. Once the
@@ -1410,6 +1463,7 @@ if (
fixedLedgerScans.length ||
fixedDialogOverflow.length ||
fixedRawRefs.length ||
fixedProviderHosts.length ||
current.naiveOreRound < baseline.naiveOreRound.count
) {
console.log('\n✓ Progress since baseline:')
@@ -1422,6 +1476,8 @@ if (
console.log(` raw-reference-fetch: -${fixedRawRefs.length} file(s)`)
if (current.naiveOreRound < baseline.naiveOreRound.count)
console.log(` naive-ore-round: -${baseline.naiveOreRound.count - current.naiveOreRound} occurrence(s)`)
if (fixedProviderHosts.length)
console.log(` provider-host: -${fixedProviderHosts.length} file(s) no longer call a provider directly`)
console.log(' Run with --update to ratchet the baseline down and lock in the gains.')
}
if (migratedDirectAi.length) {
@@ -1444,5 +1500,5 @@ if (failed) {
process.exit(1)
}
console.log(
`\n✓ Antipattern guard passed (raw-route-auth: ${current.rawRouteAuth.length}, naive-ore-round: ${current.naiveOreRound}, hand-rolled-invariant: ${current.handRolledInvariants}, ledger-scanning-report: ${current.ledgerScanningReports.length}, direct-jel-insert: 0, leaky-supabase-client: 0, pinned-dep: 0, raw-user-error: 0, sek-labelled-amount: 0, off-ladder-radius: 0, folded-public-flag: 0, cross-extension-import: 0, ungated-extension-route: ${current.extensionRoutes.ungated.length}/${UNGATED_EXTENSION_ROUTES.size} allowlisted, dialog-overflow-risk: ${dialogOverflowFiles.length} file(s), raw-reference-fetch: ${current.rawReferenceFetch.length} file(s), client-node-builtin: ${current.clientNodeBuiltins.length}, ambiguous-embed: ${current.ambiguousEmbeds.length}, direct-ai-client: ${current.directAiClients.length}/${DIRECT_AI_CLIENT_ALLOWED.size} allowlisted).`,
`\n✓ Antipattern guard passed (raw-route-auth: ${current.rawRouteAuth.length}, naive-ore-round: ${current.naiveOreRound}, hand-rolled-invariant: ${current.handRolledInvariants}, ledger-scanning-report: ${current.ledgerScanningReports.length}, direct-jel-insert: 0, leaky-supabase-client: 0, pinned-dep: 0, raw-user-error: 0, sek-labelled-amount: 0, off-ladder-radius: 0, folded-public-flag: 0, cross-extension-import: 0, ungated-extension-route: ${current.extensionRoutes.ungated.length}/${UNGATED_EXTENSION_ROUTES.size} allowlisted, dialog-overflow-risk: ${dialogOverflowFiles.length} file(s), raw-reference-fetch: ${current.rawReferenceFetch.length} file(s), client-node-builtin: ${current.clientNodeBuiltins.length}, ambiguous-embed: ${current.ambiguousEmbeds.length}, provider-host: ${current.providerHosts.length} file(s), direct-ai-client: ${current.directAiClients.length}/${DIRECT_AI_CLIENT_ALLOWED.size} allowlisted).`,
)