diff --git a/DECISIONS.md b/DECISIONS.md index 10b89281..89d358c5 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -621,3 +621,5 @@ One line per decision: `[YYYY-MM-DD] : `. Appended by agents and [2026-07-27] Privacy page corrected after verifying prod: PostHog's conversations module writes ph_conv_ = {"widgetSessionId": uuid} to localStorage at PAGE LOAD, not when support is contacted, bypassing persistence: 'memory' exactly like surveys' seenSurvey_ flags. The page previously claimed enkäter were the only local storage, which was wrong the moment Support was enabled. Both values are functional UI state without personuppgifter (do-not-ask-again, and find-my-own-ticket), so the no-consent-banner position holds, but "inga kakor eller annan lagring" did not and is now stated accurately. Worth remembering that enabling any new PostHog product can silently add device storage: check localStorage in prod after each one rather than trusting the persistence setting. [2026-07-27] Documented the two client-side storage keys as having DIFFERENT lifecycles, after the compliance swarm flagged ISO 27001 A.8.10 (no deletion mechanism) on the storage inventory. ph_conv_ IS deleted on logout: posthog.reset() calls the conversations manager's own reset, which removes that single known key, and resetAnalyticsIdentity() already runs in both logout handlers. That is what stops a shared device carrying one user's support-ticket session into the next user's session. seenSurvey_ genuinely has no deletion trigger, and cannot have one from the SDK: no PostHog bundle enumerates localStorage (zero occurrences of localStorage.key( or Object.keys(localStorage) across module.js, surveys.js, conversations.js), so nothing can discover the keys to remove them. Accepted as the retention position rather than an oversight: the value is "true" under an opaque survey id with no personal data, and clearing it on logout would re-prompt every survey to the next person on the device and produce false survey responses. [2026-07-27] Fixed the "Underlag saknas on a verifikat that shows the invoice" contradiction by ANCHORING the floating document, not by loosening the missing-underlag predicate. The predicate's anchoring requirement (migration 20260724090000) is legally right: block_document_deletion keys on journal_entry_id, so an unanchored doc is deletable and must not silence the warning. The bug was that nothing ever anchored it, and delete_last_voucher actively un-anchors (it must: the FK is ON DELETE RESTRICT). So the fix re-anchors after a voucher delete, at every supplier-invoice payment path (dashboard + v1 mark-paid, dashboard + v1 match-supplier-invoice, which between them had zero, partial and cash-only coverage), and backfills the 5 prod rows. getJournalEntryUnderlagReferences now also withholds an unanchored document, so the verifikat view and the list can no longer state opposite things about the same row. Rejected the alternative (extend the WORM deletion guard to protect docs referenced by a supplier invoice, then drop the anchoring requirement everywhere): it touches an enforcement trigger for a strictly larger blast radius and leaves the document outside the guard until the trigger ships. +[2026-07-27] Claude Code plugin `homepage` points at the GitHub README, not /docs/api/connect-claude: next.config.ts redirects /docs/api/* to the separate docs.gnubok.se repo, where the connect-claude page was never ported, so the documented URL 404s and the in-repo page is dead code. Submission to the Claude plugin directory cannot wait on a cross-repo docs port. +[2026-07-27] Applied to anthropics/claude-plugins-community, not claude-plugins-official: the official marketplace is curated by Anthropic at its own discretion with no application process, and the submission form explicitly does not feed it. Community listing is the only route we control. diff --git a/claude-plugin/.claude-plugin/plugin.json b/claude-plugin/.claude-plugin/plugin.json index 4b3ae5ca..64200e1a 100644 --- a/claude-plugin/.claude-plugin/plugin.json +++ b/claude-plugin/.claude-plugin/plugin.json @@ -2,11 +2,11 @@ "name": "accounted", "displayName": "Accounted", "description": "Official Accounted plugin: Swedish double-entry bookkeeping flows for Claude. Connects your ledger over MCP and ships short workflow skills (daily bookkeeping, health check, month close, VAT, payroll, year-end) that work from the company's live data and load Swedish accounting knowledge from the product when needed. Every write is staged for your approval; nothing is booked on its own.", - "version": "0.1.0", + "version": "1.0.0", "author": { "name": "Accounted (erp-mafia)" }, - "homepage": "https://app.gnubok.se/docs/api/connect-claude", + "homepage": "https://github.com/erp-mafia/accounted/blob/main/claude-plugin/README.md", "repository": "https://github.com/erp-mafia/accounted", "license": "MIT", "keywords": [ diff --git a/claude-plugin/LICENSE b/claude-plugin/LICENSE new file mode 100644 index 00000000..b1dee5a4 --- /dev/null +++ b/claude-plugin/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025-2026 Jakob Wennberg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/claude-plugin/README.md b/claude-plugin/README.md index 118d8570..96a6e383 100644 --- a/claude-plugin/README.md +++ b/claude-plugin/README.md @@ -42,4 +42,4 @@ This plugin is not legal, tax, or audit advice. Output is underlag for you and y ## License -MIT (the plugin). The Accounted platform itself is AGPL-3.0. +The plugin in this directory is MIT licensed; see [LICENSE](./LICENSE). The Accounted platform it connects to is a separate work, licensed AGPL-3.0 under the [LICENSE](../LICENSE) at the repository root. diff --git a/claude-plugin/skills/start/SKILL.md b/claude-plugin/skills/start/SKILL.md index f29fe43e..a3bad734 100644 --- a/claude-plugin/skills/start/SKILL.md +++ b/claude-plugin/skills/start/SKILL.md @@ -11,6 +11,7 @@ Verify the connection, learn who this company is, and surface what needs attenti 1. Call `accounted_get_agent_briefing`. This is the single source for company facts: entity type (aktiebolag or enskild firma), accounting method (faktureringsmetoden or kontantmetoden), VAT period, employees, and ledger context. Never assume these; the flows below behave differently depending on them. - If the call fails with an auth error, the MCP server is not connected yet: tell the user to run `/mcp` and authenticate with Accounted (OAuth consent screen; read-only scopes by default, write scopes are ticked explicitly). Self-hosted users: see the plugin README. + - If the user has no Accounted account at all, say so plainly and point them at https://app.accounted.se to create one. The plugin drives an existing ledger; it cannot bookkeep without one. Do not attempt any other flow until a company is connected. 2. Read `Accounted://attention` and `Accounted://period/active`. 3. Present a short orientation in the user's language: company name and form, active fiscal period and its lock status, and the top 3 items needing attention. 4. Point at the flows, matched to what attention showed: