Commit Graph
3 Commits
Author SHA1 Message Date
78a581bca1 fix(import): guard against CP437-as-CP1252 mojibake entering via pre-decoded SIE text (#1569)
* refactor(arcim-migration): remove the dead gateway SIE export path

fetchSIEExport and SIEExportFile have had zero callers since the direct
provider clients replaced the Arcim Sync gateway (#181, #718). The path
returned SIE as a pre-decoded string, and the gateway's decode of CP437
bytes as windows-1252 is what wrote the 2026-03-17 mojibake into posted
entries. Deleting it makes the string-typed SIE fetch impossible to
re-wire; a comment marks the grave. The consent lifecycle and entity
accessors stay untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(import): warn when SIE text carries CP437-as-CP1252 mojibake

The 2026-03-17 migration wrote mojibake ("L"neutbetalning"-style C1
specials) into posted entries because the retired gateway handed the
/import-sie handler an already-decoded string: byte-level encoding
detection never saw it, and nothing downstream checked. The live bug is
gone; this is the tripwire so the signature can never land silently
again.

- lib/import/sie-artifact-scan.ts: pure scanner over parsed SIE account
  names and voucher/line descriptions, reusing hasCp1252Artifact from
  charset-repair; flags at >= 2 hits so a lone legitimate curly quote or
  apostrophe cannot false-positive a whole file.
- arcim-migration /import-sie: warn-never-block; the Swedish warning
  rides on result.warnings, which the workspace UI already renders, plus
  a server-side log.warn.
- wizard parse route: same scan, surfaced through the existing
  parse-issue warnings card in the preview, pointing at the first
  affected line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(bookkeeping): pin the reported gateway mojibake strings

Adds the four strings reported from the affected company's journal as
reverse_cp437 cases (all reverse losslessly) plus a false-positive
guard: space-padded typography must never route into the CP437
reversal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 15:14:23 +02:00
Jakob WennbergandClaude Sonnet 5 ec27228a8e style: remove em/en dashes repo-wide, add CLAUDE.md rule against them (#890)
Em dashes (—) and en dashes (–) had spread across comments, docs, tests,
and a few UI strings, reading as AI-generated boilerplate rather than
house style. Replaced each with punctuation matching its context: colon
for explanatory clauses, comma for asides, plain hyphen for numeric/legal
ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for
paired-dash asides. messages/en.json and messages/sv.json were fixed by
hand together to keep sv/en in sync.

Left untouched where the dash is the functional subject rather than
decorative punctuation: date-range-parser.ts's separator regex,
charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE
encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the
agent system-prompt files that already instruct against em dashes, and
a golden iXBRL test fixture compared byte-for-byte.

Also fixes two bugs surfaced along the way: an off-by-one in
ApiKeysPanel's scope-label split (a leftover from an earlier partial
pass), and a charset-repair test that had lost the literal en-dash it
exists to verify.

Regenerated the agent atom seed migration (skills:generate) since 27
SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes,
with an explicit carve-out for the functional-dash cases above.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 15:58:06 +02:00
Jakob WennbergandClaude Opus 4.8 d95a0b6105 fix(bookkeeping): comprehensive chart_of_accounts charset repair (#736)
* fix(bookkeeping): comprehensive chart_of_accounts charset repair

The 20260625120000 backfill (PR #734) only covered the 26 short-name seed
accounts. Investigation found the corruption was far broader — ~4,500 rows
across 858 companies — in four signatures, and verified the root cause is
already closed (prod's seed_chart_of_accounts() carries correct diacritics;
the corruption was prod-migration-drift, the seed fix reached prod ~2026-06-12,
no companies corrupted since).

Adds a tested, reusable repair core + a guarded script:

- lib/bookkeeping/charset-repair.ts — pure, unit-tested resolvers:
  * stripped diacritics ("Utgaende moms forsaljning...") → restore from a
    de-accent-equal clean sibling. DIRECTIONAL guard (only acts on a fully
    de-accented input) so a correct name is never stripped down; unique-match
    only, so user-renamed accounts are never clobbered.
  * double-encoded UTF-8-as-CP1252 ("Företagskonto") → lossless CP1252-aware
    byte reversal (recovers custom names too).
  * CP437-as-CP1252 ("F”rmedlad", "™vriga", "V„rdef”r„ndring") → lossless
    CP437 letter reversal.
  * lost-byte U+FFFD ("p� bilar") → fill via single-char-wildcard match to a
    unique clean sibling (the byte is gone, so only a confident sibling wins).
  isClean() rejects mojibake AND mid-word CP1252 artifacts, but treats a
  space-padded en-dash ("Kundfordringar – delad faktura") as legitimate.

- scripts/repair-chart-of-accounts-charset.ts — dry-run by default, --execute to
  apply; idempotent; refuses any non-prod project. Sources canonical names from
  the table's own clean sibling rows + BAS_REFERENCE.

Applied to production (UPDATE-only, account_name is display-only): 4,499 rows
across 858 companies repaired, 0 double-encoded remaining, 0 errors. 247 rows
left untouched and reported — custom account names with lost bytes and no
canonical (unrecoverable from the data; need the source SIE file or manual fix).

21 unit tests cover every transform with real prod fixtures, plus the two
dry-run bugs caught before any write (correct→stripped direction; matching a
CP437-mojibake sibling).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(scripts): avoid supabase-js generic mismatch in charset repair fetch

next build's tsc rejected fetchAll(supabase: ReturnType<typeof createClient>)
— the default-generic SupabaseClient type doesn't unify with the inferred
createClient() return. Make fetchAll a closure over the inferred client.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(scripts): add TOCTOU guard to charset repair updates

Per PR review: only write when the row still holds the exact corrupted value
read (.eq account_name), so a concurrent rename is skipped, not clobbered, and
the script is strictly idempotent. Track skipped count.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(charset-repair): build combining-marks regex from ASCII string

Per PR review: the deaccent regex literal embedded raw U+0300–U+036F combining
marks (invisible, encoding-fragile). Build it via RegExp('[\\u0300-\\u036f]')
so the source is plain ASCII. Behavior-identical; 21 tests still green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:01:45 +02:00