Commit Graph
5 Commits
Author SHA1 Message Date
Jakob Wennberg dc07ca8872 feat(transactions): steer private marking in locked periods to ignore, with v1 and MCP ignore verbs (#1661) (#2031)
Decision (option a): a private marking stays a real booking (eget uttag/insattning), so it remains blocked in a locked or closed period; the legal escape for rows that are not affarshandelser is ignore. Private + locked now returns TX_CATEGORIZE_PRIVATE_PERIOD_LOCKED with remediation naming the ignore paths instead of a bare PERIOD_LOCKED, on all four categorize surfaces and the bulk driver. New v1 POST/DELETE /transactions/{id}/ignore (isTransactionBooked-based 409, idempotent) and a staged MCP gnubok_ignore_transaction (+ accounted_ alias, search visibility to respect the tools/list payload ceiling) with operation_type ignore_transaction; the CHECK pair 20260831070000/070001 rebuilds the constraint from main's newest list plus the new value. Dashboard toast gains an Ignorera i stallet action. Closes #1661
2026-08-31 08:39:04 +01:00
188816652d docs(api,mcp): tool counts, changelog backfill, version-header honesty, lazy auth, endpoint map (#1929)
Brings the developer-facing API and MCP docs back in line with origin/main
(audit 2026-08-26). Docs only; no runtime behaviour changes.

- Tool counts: the server registers 153 tools; docs said 90+/100+/120.
  All now say "150+" (connect-claude, gnubok-mcp README, plugin README,
  mcp-server rules, CLAUDE.md, registry entry with refreshed updatedAt).
  Not derived from the tools array: lib/ must not import @/extensions/.
- REST changelog: backfilled the additive 2026-08 changes (#1909 report
  date ranges + PDFs, #1864 POST /companies, #1773 vat-declarations,
  #1405 PATCH settings, #1724/#1788 customer personal_number, #1809
  cash_account_id filter). API version date unchanged.
- Version headers: Gnubok-Deprecation is planned, not emitted; the
  Gnubok-Version request header is not read today (version.ts comment,
  versioning page, conventions overlay, regenerated skills/accounted-api).
- connect-claude Path A documents lazy auth (connector works before an
  account exists; sign-in on the first company-scoped call).
- MCP server README: real Anthropic SDK call sites, real resource URIs,
  pending-operations widget, public-tools/tasks/origin-guard/pii-guard.
  Rules file gains Lazy auth + feedback/tasks paragraphs.
- api-routes endpoint map regenerated from the filesystem (560 routes,
  55 families incl. v1, agent, reconciliation account-keyed, dimensions,
  peppol, rot-rut, webshop-orders, mileage, billing, skatteverket,
  receipt-hunt).
- gnubok-mcp/accounted-mcp: /settings?tab=api is the pre-redesign URL;
  now /settings/api (README + help hints, no version bump).

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 13:35:54 +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 0b86901a2b Enforce MFA on critical mutation routes + post-audit foundation (A1) (#646)
* feat(lib): add canonical money + format + fetch primitives (audit Tier 0)

Foundation for post-audit cleanup: shared primitives so subsequent refactors import one helper instead of reinventing (the duplication the audit found).

- lib/money.ts: canonical roundOre/ORE_TOLERANCE (+ equalOre/isZeroOre/sumOre); lib/bokslut/rounding.ts re-exports for back-compat
- lib/utils.ts: formatAmount, formatWholeKr, formatDateTime
- lib/hooks/use-fetch.ts: generic client fetch hook (abort, bilingual errors, refetch)
- components/common/DataState.tsx: loading/error/empty wrapper over Skeleton/EmptyState
- messages: common.retry / common.load_error (sv+en)
- tests: 16 tests incl. the 1.005 half-ore case and locale-robust format assertions

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

* ci(guards): ratchet against new MFA-bypassing routes and naive ore-rounding

Adds scripts/checks/no-new-antipatterns.mjs + committed baseline. Fails CI only when a PR ADDS a route hand-rolling supabase.auth.getUser() (which skips MFA AAL2 enforcement) or a new Math.round(x*100)/100. Baseline: 178 raw-auth routes, 668 naive rounds — ratchets down as the A1 (route-auth) and D1 (rounding) migrations land. Wired into core-build.yml; green at baseline.

Note: scripts/ is gitignored (.gitignore:70 '/scripts') yet tracks 39 files via force-add; these two were force-added to match that existing pattern.

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

* feat(api,errors): enforce MFA on journal-entry mutation routes via withRouteContext (A1)

Migrates the 4 journal-entry mutation routes (commit, correct, reverse, recordate) off hand-rolled supabase.auth.getUser() onto withRouteContext, which enforces MFA AAL2 (requireAuth) + non-viewer role (requireWrite) and routes thrown errors through the canonical errorResponse envelope. Fixes audit finding A1 for the most compliance-critical mutations and folds in C8 for these routes (drops bookkeepingErrorResponse; they now emit message_en).

Also fixes a latent bug: errorResponse()/extractBookkeepingDetails only handled 11 of 15 typed bookkeeping errors, so MeaninglessCorrection / NoOpenPeriodForDate / TargetPeriodClosed / TargetPeriodLocked silently degraded to a generic 500 (affecting existing v1 callers too). Adds the 4 missing registry codes + extract cases -> correct 400/409.

Behavior change: untyped engine throws now return the canonical 500 envelope instead of 400+raw-string; typed errors keep their status (verified against the registry). Tests updated to the realistic typed-error contract + a 403 write-gate test on commit. Updates .claude/rules/api-routes.md to prescribe withRouteContext. Ratchets the antipattern guard 178 -> 174. Full unit suite green (5023); tsc: no new errors.

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

* feat(api): enforce MFA on salary run authorization routes via withRouteContext (A1)

Migrates the salary-run lifecycle write routes (approve, paid, revert) — the highest-PII A1 surface — off hand-rolled supabase.auth.getUser() onto withRouteContext (enforces MFA AAL2 + non-viewer role). Explicit { error } returns are preserved unchanged (passed through the wrapper); only auth changes, so no error-shape regression. Salary unit suite green (8). Ratchets the antipattern guard 174 -> 171.

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

* review: address PR #646 bot findings

- guard: match withRouteContext/requireAuth at the CALL site (withRouteContext[<(]), not a bare import — closes the false-negative greptile flagged. It surfaced app/api/sandbox/seed (hand-rolled getUser; the loose regex had matched a code comment). Switched that route to requireAuth() — the documented stopgap for routes that can't use withRouteContext (it runs before a company exists; anonymous users, so MFA is a no-op but the auth path is now consistent). Guard stays at 171.
- money.test: add the negative half-ore case roundOre(-1.005) === -1 to lock the rounding direction against regressions.
- use-fetch: document keep-previous-data + deferred-loading (effect-tick) semantics.
- structured-errors: drop the BFL 5 kap. 5 § citation from MEANINGLESS_CORRECTION per the swedish-compliance bot (5 § governs correction procedure, not the no-op precondition).

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

* review: enrich wrapper error logging + document sandbox GDPR controls (PR #646)

- with-route-context: log unhandled errors and route errorResponse through the resolved { userId, companyId } logger, not just { requestId, operation } — closes the OWASP V16 audit-trail finding for all 82+ routes using the wrapper. Documented in the JSDoc.
- sandbox/seed: document the GDPR Art.32 compensating controls for the anonymous write path (anonymous-only, /24 rate limit, synthetic demo data, own-company RLS scope). No functional change — the flagged behaviour is pre-existing by design; this records the reasoning inline.

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-03 15:34:58 +02:00
Jakob WennbergandClaude Opus 4.8 c74b19df1b Accounted rebrand + swarm-skill cleanup + bank-reconciliation fixes (#643)
* feat(reconciliation): close the bank-feed loop on voucher links and re-tag mis-typed opening balances

Two related fixes to bank reconciliation correctness:

1. Auto-reconcile on voucher link. Linking an invoice or supplier invoice to
   an existing voucher previously advanced only the invoice — the bank
   transaction that paid it kept sitting in the Transactions inbox with a null
   journal_entry_id. linkInvoiceToVoucher / linkSupplierInvoiceToVoucher now
   call autoReconcileTransactionForLinkedVoucher (lib/reconciliation), which
   links the bank transaction to the same verifikat when exactly one unbooked
   line matches it. Best-effort and post-commit: a failure here never fails the
   link. The result surfaces reconciledTransactionId; the inbox row leaves the
   list and the UI shows link_success_tx_reconciled.

2. Re-tag mis-typed opening balances. getReconciliationStatus and the GL-line
   matching RPCs identify a cash account's ingående balans solely by
   journal_entries.source_type='opening_balance'. Companies migrated from other
   systems often booked the bank IB as an ordinary voucher (source_type
   'import' or 'manual'), so it was never excluded and surfaced as a phantom
   reconciliation difference equal to the opening balance. Adds:
   - migration mark_entry_as_opening_balance: a GUC-gated carve-out in the
     immutability trigger plus a SECURITY DEFINER RPC that validates the entry
     (balance-sheet lines only, dated on a fiscal-period boundary), flips the
     source_type, and writes an audit row — no blanket data sweep.
   - POST /api/reconciliation/bank/mark-opening-balance + MarkOpeningBalanceSchema.
   - BankReconciliationView action to trigger it from the IB diff.

The gnubok_create_voucher executor now accepts a typed is_opening_balance flag
and derives source_type='opening_balance' only after validating class 1/2 lines
on the period start, so new IBs land correctly typed.

Covered by lib/reconciliation auto-reconcile tests, voucher-executors tests,
and a mark-entry-as-opening-balance pg-real test.

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

* chore: rebrand gnubok → Accounted and prune swarm agent skills

Product rebrand and skills housekeeping. No runtime behaviour change.

Rebrand: replace user-visible "gnubok" with "Accounted" across docs, READMEs,
in-code comments, doc-site content, MCP skill/resource prose, and the
gnubok-mcp package description. The MCP resource URI scheme is moved gnubok://
→ Accounted:// consistently across resource registrations, the event-type
comment, and the resource/skill tests. Deliberately preserved as stable
identifiers (NOT rebranded): the gnubok-company-id cookie, gnubok_sk_ / gnubok_inv_
token prefixes, the gnubok-mcp npm bridge name, and the AGI <gem:Programnamn>
value (kept 'gnubok' per its source comment — it is the software identifier sent
to Skatteverket and must not churn across visual rebrands).

Skills: remove the 27 swarm-* agent SKILL.md atoms (no longer used; already
absent from the agent_atom_registry in prod), refresh the remaining skill docs,
add the .claude/rules/ path-scoped rule set, and regenerate the
seed_agent_atom_bodies migration + .skill-body-manifest.json via
`npm run skills:generate` so the DB-backed skill bodies match the trimmed set.

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-03 10:52:01 +02:00