Changelog¶
All notable changes. The format follows Keep a Changelog; versions follow Semantic Versioning
(below 1.0, minor versions may change behaviour). The top section must carry the version in pyproject.toml and a date: coach dev release-check enforces it.
[Unreleased]¶
Changed: the category picker is searchable¶
- Choosing a category meant scrolling a native list of about a hundred entries. The picker is now a search box (ARIA 1.2 combobox): typing narrows the list on the category, its group, its id and its description, ignoring case and accents ("sante", "insur"); arrows, Enter and Escape work, and an empty box still lists every category by group. Used by the transaction panel, Review, Gold set, Budgets and the category filter.
- Docs: the user guides now say how to tell apart several payments to one merchant (memory annotations on the amount or the bank text,
docs/guide/transactions.md) and point to the opt-in web lookup of unknown shops (classify enrich,docs/guide/categories.md).
Changed: the two category queues (merchants to review, transactions to label) share one row layout¶
- Each page built its rows on its own: the picker and buttons sat on the right of a short row and dropped below a long one, so the cards of
one list did not line up. Both now use
DecisionRow: name and badge, one line of facts, then the picker (same width) and its buttons on their own line. The transaction queue's source badge moved next to the description, like the reason badge of the merchant queue. - The merchant queue shows one primary button, like the transaction queue: "Keep
" while the picker shows the current label (never for a label guessed from a similar merchant), "Set" once another category is picked.
Fixed: the Insights page renders the coach's Markdown, and its filter shows what it filters¶
- The coach's write-ups (the weekly summary) showed raw
#/##headings and*italic*markers: the web renderer only knew paragraphs,-lists and**bold**. It now reads the text line by line: headings, bullet and numbered lists (also right under a heading, without a blank line),*italic*,**bold**and`code`(never_italic_: category keys hold underscores), still as React nodes, never HTML. - Choosing a kind on Insights only filtered the cards below the coach's write-ups, which stayed on top, so the tabs seemed to do nothing. The coach's write-ups have no kind: they now show under "All" only.
Fixed: SSO works with an authentik proxy provider (E16)¶
- authentik signs the
X-authentik-jwtof a PROXY provider with HS256 and the provider's client secret, always: it clears a proxy provider's signing key on every save, so its JWKS is empty and every token was refused (algorithm), leaving the login-link page. New[ui] sso_signing = "client_secret": the token is verified with the secretsso_client_secret(COACH_SSO_CLIENT_SECRET, or a Docker secret file), HS256 only, no fetch; the app refuses to start without it.sso_signing = "jwks"(the default) keeps the RS / ES / PS keys ofsso_jwks_url, and its refusal of an HS token now says which setting to use. Docs: the authentik recipe indocs/configuration/remote.md.
Fixed: the login link no longer lands in the container log behind a proxy (#37)¶
- In a container without a terminal,
coach uiwrites the one-time login link to the privatelogin-link.txtinstead of printing it. That only happened in the loopback container mode: with[ui] allow_remote = true(the Tailscale and authentik recipes) every start printed a valid link intodocker compose logs. The file is now used whenever the process runs in a container with no TTY, whatever the bind mode; the "publish the port on 127.0.0.1 only" warning stays specific to the loopback container mode.
Added: sign in with a passkey, or through your SSO proxy (E16)¶
- The one-time login link was the only way into a session: fine on the machine, heavy on a phone away from home (a shell on the server for every new session). Two opt-in ways in, both off by default, the link still works next to them and remains the enrolment and recovery path.
- Passkeys (
[ui] passkeys = true, migration 0024ui_passkeys): enrolled from a session (Household > Passkeys; a child: My money), a passkey (Face ID, Touch ID, Windows Hello, a security key) opens exactly the login it was made for, on the host name it was made on. The server (py_webauthn) checks origin, relying-party id, signature and counter; challenges are single-use and short-lived; sign-in attempts are rate limited; a loopback address is refused as relying party (uselocalhost). Ten passkeys per login, removable one by one; a child manages its own. - SSO through an identity-aware proxy (
[ui] sso = "authentik"withsso_jwks_url, optionalsso_issuer/sso_audience, and a[ui.sso_users]table): an API call without a session but with authentik'sX-authentik-jwtis verified against the provider's JWKS fetched from the CONFIGURED URL (egress kindui.sso, cached), mapped bypreferred_username/email/subto"owner"or acoach userslogin, and the ordinary cookie is issued on that response. A plain identity header is never trusted. Refusals say why (sso_missing,sso_unmappedwith the name to map,sso_rejectedwith a one-word reason); "Sign out" also ends the proxy's session. Requiresallow_remote,remote_tls_ackandallowed_hosts. coach security audit:ui_ssoandui_passkeyschecks;coach config showlists the new keys (identities are counted, never printed).- Docs:
docs/configuration/remote.md(both recipes, Traefik labels), the security model indocs/ui.md, the threat model and the residual risks indocs/security.md, the egress inventory indocs/privacy.md.
Changed: the landing page has a real light theme, and the film a voice-over¶
- The hero, the redaction card, the stats and the privacy section follow the theme (they stayed dark, and "The model sees" turned the card
white with unreadable text: its state class clashed with the model cards'
.model). The theme button shows the theme it switches to. - The film is narrated in English and French (
film.en.mp4,film.fr.mp4, with WebVTT subtitles); the page picks the visitor's language and has a switch.scripts/demo/make_film.pyrenders it with Kokoro, an open-weight speech model run locally (or--tts edge).
Security and privacy: findings of the pre-publication review of 2026-10-09¶
- The classification and document-extraction
claude -p(classify run|compare|enrich,eval models,memory doc extract --send) now gets the same isolation as the coach runtime: no settings file, hooks off, no slash command, every built-in tool denied (WebSearchonly forenrich),--restrictedwhen configured, and an empty temporary working directory. Before, the project'sCLAUDE.md, the user's ownCLAUDE.md, the auto-memory and any hook of the current directory could be added to a classification prompt (regression test intest_llm_backends.py). coverage(the first call of every coach run) sent the real bank names, even in coarse mode. The redactor now maps abankfield to its kind (regional bank,online bank) in coarse mode and strips the region in standard mode, like the memory context already did.- The classification payload no longer carries the known towns (cut from the descriptor, the raw example and the kNN hints, as
enrichalready did) nor the exact average amount per merchant: it is rounded to an order of magnitude (1-2-5 series).docs/privacy.mdnow lists every field. - Web app: a stray
?dry_run=trueon an endpoint that has no dry run no longer skips the audit row nor opens the lax preview budget; the middleware checks that the matched endpoint really takesdry_run(DRY_RUN_TABLE). Remote mode sendsStrict-Transport-Security. A missing secret is reported by name only (never the path of the secrets folder). .dockerignoremirrors.gitignorefor every personal file pattern; the image carries OCI source / licence labels; the Claude Code permission template deniesWriteonmemory/andcoach-home/and reading the Docker secrets folder.
Documentation¶
docs/enable-banking.md: who Enable Banking is (a Finnish FSA-registered AISP), what it sees and keeps, its free personal-use terms, what was not found (no published certification), and the exact read-only calls this app makes, with dated sources.- README: a "personal open-source project, no warranty, not a regulated service" paragraph under the disclaimer.
docs/privacy.md: an "Other people's data" section for the other adults, the children and the counterparties.docs/security.md: the key location and the 180-day consent corrected, log rotation, the[coach] claude_envcaveat. LICENSE.choose.md(the decision aid, which still said "not licensed yet") is removed: the licence is MIT, inLICENSE.
Fixed: "Ask the coach" went blank after a question in recent browsers¶
- Recent Chromium returns a Promise from
scrollIntoView; the page's auto-scroll effect returned it to React, which called it as a cleanup function and unmounted the whole app on the next answer. The effect now has a block body (regression test inCoach.test.tsx).
Added: documentation site and demo household¶
- A public site (GitHub Pages,
.github/workflows/docs.yml): a landing page (website/) and a MkDocs Material documentation under/docs/with a getting-started path (demo, install, quickstart, banks, running on a server), a user guide of every page with light / dark / phone screenshots, the configuration (models, privacy modes, alerts, daily job, remote access), Claude Code and its skills; the existing technical docs are its reference. Build:bash scripts/build-site.sh(strict). scripts/demo/: a fully SYNTHETIC demo household (seed_demo.py: the Rossi family, 24 months on invented banks and merchants, loans, a lease, a rental flat, subscriptions, budgets, goals, questions, proposals, alerts, insights, a child login) in a scratch home that never touches the Keychain;run_demo.shstarts the web app on it with a scriptedclaudethat makes real tool calls and fills a pre-written answer with the figures they return (no model, no network);shoot.pyandmake_film.pyproduce the screenshots, screencasts and film of the site.
Fixed: a bank entry that is not booked no longer breaks the app¶
- The sync stores only
BOOKentries (or entries without a status) as transactions; any other status (PDNG,OTHR,HOLD...) goes to the pending list. A booked entry without a booking date takes its value or transaction date; one with no date at all is pending. Before, anOTHRentry without a date was stored with an empty date and every page failed to load. - Migration 0023 repairs a database that already holds such rows (moved to the pending list; a pre-migrate safety copy is taken first).
Added: the claude-code backend in Docker¶
- Opt-in:
--build-arg WITH_CLAUDE_CODE=1adds the self-contained Claude Code CLI at a pinned version, checked against a pinned integrity hash (no Node at runtime); on a Linux host the host's own nativeclaudebinary can be bind-mounted on/opt/claude/bin/claudeinstead. The default image is unchanged. - New optional secret
claude_code_oauth_token(claude setup-token), passed toclaudeasCLAUDE_CODE_OAUTH_TOKENby the coach and by classification. The finance MCP server started byclaudenow receives the secret-store LOCATIONS (COACH_SECRETS_BACKEND,COACH_SECRETS_DIR...), so it opens the database in a container.coach doctorchecks the CLI and the token in a container;coach security auditlists the token. Seedocs/docker.md.
Added: OpenAI-compatible model providers (OpenRouter, Eden AI, vLLM ...)¶
- A fourth LLM backend,
openai-compatible, for AI categorization ([llm] backend) and the coach ([coach] backend): any OpenAI-style/chat/completionsAPI. Settings[llm] openai_base_url(defaulthttps://openrouter.ai/api/v1; https, or http on this machine only),[llm] openai_model(the provider's model id) and the secretopenai_api_key(environmentCOACH_OPENAI_API_KEY, deliberately notOPENAI_API_KEY). The coach needs a model with tool calling and refuses clearly otherwise. - Its own egress flow
llm.openai-compatible(refused under[privacy] local_only),coach doctorandcoach security auditchecks for the key. On OpenRouter the requests ask for providers that do not store or train on prompts ([llm] openrouter_deny_data_collection, on by default). The cost is logged only when the provider reports it.
Security¶
- The classification backend
claude-code(claude -pfor labelling andclassify enrich) no longer inherits the whole process environment: it gets the same minimal environment as the coach runtime (PATH,HOME,USER,LANG,LC_*... plus the names listed in[coach] claude_env), soCOACH_DB_KEY,COACH_BACKUP_KEY,COACH_PROPOSAL_KEY,ANTHROPIC_API_KEYandANTHROPIC_AUTH_TOKENare never passed to it. The allowlist moved tocoach.claude_cli, shared by both.
Fixed: hygiene scan inside a git worktree¶
publishable_filesnever lists a path named.git(file or folder, any depth): in a worktree or a submodule.gitis a file holding an absolutegitdir:path, which madetests/test_hygiene.pyand the release check's personal data scan fail there.
Added: multi-language web app, part 4h (see docs/i18n.md, "Server text")¶
- Household, Kids' money and Who pays notes, the Set up checklist and first-run wizard, the Connections health problems and sync results, the calendar titles and the
generated memory questions are shown in the interface language. Generated questions store optional
topic_code/question_msg/context_msginopen-questions.yaml(older files load unchanged; questions proposed by the coach stay plain text). The.icsexport, the MCP tools and the CLI keep the English.
Added: multi-language web app, part 4i (see docs/i18n.md, "Server text")¶
- The memory check (Memory > Check), the warnings of a memory write preview and the invalid budget entries are shown in the interface language (
memoryCheck.*,memoryLoad.*);coach ...commands, file names and ids stay as they are, Pydantic's own text is passed through.coach memory checkand the MCP tools are unchanged.
Added: multi-language web app, part 4g (see docs/i18n.md, "Server text")¶
- The rental property page shows the missing facts, the scheme warnings, the tax-year candidates (items, bounds, sources, unknowns, documents), the reduction notes,
the loan-rate, market and equity readings, the signals and the scenarios in the interface language. Scheme names stay as proper nouns; the general-advice, tax and
loan disclaimers come from
disclaimers.pyin the same language. New param type*_num(a plain decimal in the reader's number format). MCP and CLI unchanged.
Added: multi-language web app, part 4f (see docs/i18n.md, "Server text")¶
- Loans and net worth: the unknown values and their reasons, the missing loan fields, the schedule hints and assumptions, the scenarios (early repayment, renegotiation,
insurance: notes, penalty explanations, options, verdicts), the fields inferred from the payments and the lease checks are shown in the interface language.
Legal citations stay as written; the loan disclaimer comes from
disclaimers.py. The MCP tools and the CLI keep the English.
Added: multi-language web app, part 4d (see docs/i18n.md, "Server text")¶
- The transaction panel shows why a transaction has its category (the steps of the decision chain and their details), why an annotation or an attribution rule does not match,
and the warnings of a category change in the interface language.
coach explainand the MCPexplain_transactionoutput are byte-identical to before.
Added: multi-language web app, part 4c (see docs/i18n.md, "Server text")¶
- Insight cards (anomalies, price changes, forecast, budgets, subscription reminders, loan and rental alerts) and alert events (bank consent, failing sync, kid budgets, AI usage)
carry
title_msg/body_msgand are shown in the interface language on Insights, the Dashboard, Alerts and the loan pages. New rows store the codes in the existinganomalies.payloadandalert_events.payload(no migration); older rows keep their English. Messages sent outside the machine (ntfy, e-mail, Telegram) and the CLI are unchanged. - New short disclaimer
tax_shortindisclaimers.py(the English is the text the rental scheme card already showed).
Added: multi-language web app, part 4e (see docs/i18n.md, "Server text")¶
- The subscription inventory shows the cancellation rules (name, summary, method, conditions, missing facts), decision checks, usage, offer notes and contract-draft warnings
in the interface language; the legal citations stay as written, and the contract disclaimers come from
disclaimers.pyin the same language (GET /meta/disclaimers). The MCP tools, the CLI, letters and the calendar keep the English (cancellability(..., messages=False)by default).
Added: multi-language web app, part 4b (see docs/i18n.md, "Server text")¶
- The coverage notes of the analytics results (incomplete months, non-EUR transactions left out, low confidence of a category, partial year, ...) carry
notes_msgnext tonotesand are shown in the interface language on the Dashboard, Categories, a category's page and Subscriptions. The MCP finance tools and the CLI--jsonoutput never contain a*_msgkey.
Added: multi-language web app, part 4a (see docs/i18n.md, "Server text")¶
- Server text the web translates: next to an English sentence the API can send
<field>_msg={code, params, text}(built bycoach.i18n_msg.server_msg, raw params typed by their name:*_date,*_month,*_amount,*_pct,*_category,*_group,count); the web renders it withtServer()(new namespaceserver) and falls back to the English text. The CLI, the MCP finance tools and the stored rows keep the English. First use: the balances note of the Dashboard. - Fixed vocabularies sent as codes are named in the interface language: alert kinds, subscription groups, balance types, the first-run wizard steps, the onboarding steps, account purposes, the household line of the forecast, and the kinds of assets, loans and contracts in Memory and Wealth.
- Category and group names are translated by id (new namespace
taxonomy, every group and leaf of the built-in taxonomy); a custom leaf keeps its title-cased id. A few English names changed too ("Electricity and gas", "Online marketplaces", ...). - API errors are shown in the interface language for the generic codes (session, CSRF, rate limit, not found, validation, migration pending...); a domain rule keeps the server's own message.
GET /meta/disclaimers?lang=returns the AI-generated label in one language (its wording still lives only insrc/coach/disclaimers.py); the web no longer copies it, and a coach insight carriesai_label_short.- The forecast flag "N account(s) without balance left out" is now the code
accounts_without_balance:N(the MCPforecast/what_ifoutput shows the code; the insight id of a household forecast card changes once).
Added: multi-language web app, part 3 of 5 (see docs/i18n.md)¶
- Translated into French and Italian: the pages Alerts, Loans & net worth, Rental property, Memory, Set up, Connections, Household, Who pays what, Kids' money, the child's home page, Gold set and AI usage
(namespaces
alerts,wealth,rental,memory,setup,connections,household,kids,quality). Text sent by the server, thecoach ...commands and the legal terms of a rental scheme stay as they are.
Added: multi-language web app, part 2 of 5 (see docs/i18n.md)¶
- The pages Dashboard, Transactions, Categories (with a category's page and Merchants to review), Budgets, Subscriptions & contracts (with the inventory), Calendar, Insights, Ask the coach and "page not found"
are translated into French and Italian: one namespace per page area (
dashboard,transactions,categories,budgets,subscriptions,calendar,insights,coach). Text sent by the server is still in English (step 4).
Added: multi-language web app, part 1 of 5 (see docs/i18n.md)¶
- The web app has one language setting (English, French, Italian) in the header and the "More" sheet; it replaces the "dates and numbers" selector and sets the interface text, the date / number / money format (
en-GB,fr-FR,it-IT) and<html lang>. The choice is saved in the browser (the formercoach.localevalue is migrated); by default the first supported language of the browser is used, else English. i18nextandreact-i18next, bundled JSON files underweb/src/locales/<language>/<namespace>.json, typed keys, a single language registry (web/src/i18n/languages.ts): adding a language is a folder of JSON files and one entry. A completeness test fails when a translation misses a key, a plural form or a{{variable}}.- Translated in this step: the navigation, the header, the sign-in page and the shared components (dialogs, charts, transaction panel, loan dialog, item forms). The pages follow in steps 2 and 3, the text sent by the server in step 4.
Changed: release hygiene and hardening (security scan of 2026-10-06)¶
- The project is licensed under MIT (
LICENSE); the security contact is GitHub's private vulnerability reporting; the repository URL is set inpyproject.toml. - CI actions are pinned to commit SHAs, Dependabot watches uv, pnpm, GitHub Actions and Docker; the Docker base images are pinned by digest.
- The CAMT.053 import parses with
defusedxml(DTD, entities and external references refused) in addition to the existing pre-check. - An unexpected API error no longer names the exception type in the response body.
Added: rental property under a tax-incentive scheme (E15, see docs/rental.md)¶
- A rental property is an asset of kind
real_estate_rentalwith itsaccount,loan,scheme(a name: data), acommitmentblock (start, length, end, rent cap, tenant income limit, reduction rate, extension decision), a typedmarket_rateand declaredvacancies: every figure is the owner's own, nothing is looked up and a missing fact is listed and asked (one open question per property,memory checkon the links). - Property flows: three new taxonomy leaves (
housing.property_tax,housing.property_management,housing.property_insurance) and generic French rules (taxe fonciere, gestion locative, PNO / GLI, copropriete, an incoming loyer); an older copy gets them withcoach taxonomy merge-package. coach rental list / show / cashflow / pnl / scheme / tax / indicators / flowsand the writesadd / edit / extension / vacancy / market-rate(previewed, typed yes); the Rental page (/rental) andGET /rental/...: the monthly cash flow with the effort d'epargne and the vacancy months, the yearly P&L (loan interest and principal from the E9 schedule), the commitment with its reminders, the tax-year candidates of the rental-income return (micro-foncier vs reel, the scheme reduction from the declared price and rate, a documents checklist), and the renegotiate-or-sell indicators (loan rate vs the market rate typed, end of the commitment, net equity).- Alert kinds
scheme_end,scheme_check,rent_missing(local content, minimal external messages); arentalcard kind in the Insights feed. - New MCP tool
rental_overview(read-only; a property isasset-N, never an address, manager, lender or tenant);tax_candidatesgainsfr-revenus-fonciersand computes the Pinel reduction from the declared price and rate. [analytics]settingsrental_reminder_months,rental_rent_grace_days,rental_rate_gap_pts. No migration.
Added: household and people (E14, see docs/household.md)¶
- Members and owners: the Household page (members, account owner and purpose editing, attribution rules, logins, audit);
coach accounts setstores the member id and refuses an owner that is neitherjointnor a declared member. - Attribution: every transaction belongs to a person (
member id | joint | nobody): a manual reassignment (coach household assign / unassign / why / log / undo, the transaction panel; recorded intx_person_log, reversible), thenattributionrules inhousehold.yaml(account, card last four digits, merchant / description pattern, direction, amount), then the owner of the account.coach explainand the panel say why. - Person views:
?member=on every analytics endpoint, the person switch in the header, amemberpseudonym argument on the analytics MCP tools (a member view of the dataset: their transactions on any account, the balances of the accounts they own). - The children's money (
coach household kids, the Kids' money page, thekids_moneytool): regular pocket money detected or declared, extra top-ups with their source, spending, month-end balance trend, pocket versus extra. Kid budgets (coach household budget) with a gentle, LOCAL-ONLYkid_budgetalert kind. - Transfers across the household's banks:
[transfers] cross_bank_window_days(default 5) and household members named in a leg as strong evidence; a parent's top-up stays an internal transfer for the household and is the child's income in the children's view. - Who pays what (
coach household allocation / allocate, the Who pays what page, thewho_paystool): shared costs split equally, by income or by custom percentages; the settlement adds up to zero. - Per-person logins (
coach users add / list / set-role / disable / enable / remove / prefs / audit,coach ui --login-link --user ID): adult = all data, child = own data through/api/v1/me/*only, enforced server-side on every endpoint, deny by default; per-login preferences; an audit log of web changes andSource: ui:<login>in the memory history. The one-time link, CSRF, Host checks, CSP and the terminal-only acceptance of memory proposals are unchanged. - Cross-bank transfer matching runs in two passes (the original window first, pairs locked; then a wider, strong-evidence-only pass on the leftovers): it can only add links. A model gets an age band, never a birth year.
- New MCP tools
household_overview,kids_money,who_pays; migration 0022 (additive:tx_person,tx_person_log,ui_users,audit_log).
Changed¶
LoginTokens.issue(user=...),Security.new_cookie(user=...)andparse_sessioncarry a login in the one-time token and the signed cookie;coach.api.state.UiMemoryStorederives the history source from the login of the current request.
[0.2.0] - 2026-10-06¶
First packaged release: everything of epics E0 to E12, installable in ten minutes, and ready to publish once the owner chooses a licence.
Added: packaging and first run (E13)¶
coach init: a fresh private home in one command (commentedconfig.toml, 0700 data folder, memory skeleton of templates only, secrets generated after a typed confirmation, empty encrypted database). Idempotent, never overwrites,--dry-run.coach doctor: Python, SQLCipher, secret store and secrets, database, Enable Banking, permissions, built web app,claudecommand, git; every problem comes with its next step;--json.coach setup: the resumable first-run wizard (init, Enable Banking, first bank, first sync, categorize with a printed dry run and an explicit privacy choice, onboarding interview, optional daily job). Nothing leaves the machine without a typed word at the step that sends it. The web Setup page shows the same steps read-only.coach setup enablebanking: guided creation of your own Enable Banking application in restricted mode, with key handling and an optionalcoach check.- Docker: multi-stage
Dockerfile,docker-compose.yml(non-root, read-only root filesystem, dropped capabilities, port on 127.0.0.1 only, docker secrets),filesecrets backend (COACH_SECRETS_BACKEND=file),coach schedule loopfor the daily job without launchd, container defaults. Statically checked in the tests; not built by them. - Package metadata, classifiers, the built web app, migrations, data files and the shipped templates in the wheel;
uvx/uv tool/pipxinstall paths.
Added: open-source readiness (E13-4)¶
- README for newcomers, CONTRIBUTING (parsers, skills, review checklist), SECURITY, CODE_OF_CONDUCT,
docs/architecture.md,docs/reference.md(the former README),docs/docker.md,docs/release.md,LICENSE.choose.md, a Claude Code permission-rules template (docs/claude-settings.example.json). coach dev hygieneandcoach dev release-check. The scan covers every publishable file (everything.gitignoredoes not exclude) and the sdist member list, with structural rules (keys, home-folder paths, valid IBANs, e-mail addresses) and a real-data rule driven by a LOCAL term file (hygiene-terms.txt, never in the tree:coach dev hygiene --build-termsderives it from your database and memory). No hash, salt or list of real terms ships with the package. A missing local file FAILS the release check;--ciskips only that rule and says so.- GitHub Actions workflow (pytest, vitest, type check, lint, release-check).
Changed¶
.gitignorereviewed: the built web app (src/coach/api/static/) is not committed and is built by CI, Docker and the release procedure;.claude/settings.json,config/taxonomy.yaml,config/rules.yaml,secrets/andcoach-home/are ignored.- The market research moved from
reports/andresearch_notes/todocs/research/(it contains no personal data). - Test data, docs and examples were re-invented where they echoed real-looking names, places, merchants, amounts and the household's situation; built-in name lists are generic.
coach config set-secret,wipeand the scheduler preflight work on the active secret store.- The web app may listen on all interfaces only inside a real container (marker file or cgroup) AND with
[ui] container_bind = true(written by the image'scoach initonly); it warns at start that the port must be published on127.0.0.1, and does not print the login link into the container logs. The compose file publishes on the host's loopback only; a lint rule rejects any example that does not. - Connecting a bank from the Docker image: the HTTPS redirect server listens on 0.0.0.0 inside a real container only (marker AND
[callback] container_bind, written by the image's init), published as-p 127.0.0.1:8443:8443; hints namedocker compose run --rm coach ...;coach finishtolerates a shell-escaped or quoted paste and never echoes the code. - The
filesecrets backend checks the folder (owner, 0700) and writes with a random temporary name,O_EXCL | O_NOFOLLOW, 0600, fsync, atomic rename. - The sdist excludes
docs/researchandevals.
Known limits¶
- No licence yet (owner's decision): the release check fails until
LICENSEexists. - The Docker image has not been built or run by the automated checks; the Enable Banking guide has not been run against the live control panel.
Earlier work (summarised by epic; this project had no public releases before 0.2.0)¶
- E0 Foundations: single
coachpackage and CLI,config.toml+ secrets in the Keychain or environment, numbered migrations, SQLCipher encryption, launchd scheduler, encrypted backups. - E1 Bank ingestion: Enable Banking connect / finish flow with a local HTTPS callback, longest history at first link, deduplication, pending transactions, balances, PSD2 call limits, consent tracking and renewal, connector health, CSV / OFX / QFX / CAMT.053 imports, internal transfer matching.
- E2 Normalization and categorization: per-bank description parsers, a two-level taxonomy, rules, household memory annotations, a local nearest-neighbour step, canonical merchants, split transactions, LLM labelling with redaction, batches and a review queue, corrections as permanent memory.
- E3 Household memory: plain-file memory (Markdown + YAML) with schemas, validated and recorded writes (local change history), proposals the user accepts, open questions, documents, explain and context for models.
- E4 Analytics: coverage-aware averages, recurring payments and price changes, anomalies, cash-flow forecast, budgets, goals, calendar, year in review.
- E5 Web app: local React app (dashboard, transactions, categories, budgets, subscriptions, loans, calendar, insights, memory, connections) behind a one-time login link, loopback only.
- E6 Coach runtime: finance MCP server (redacted read-only tools, injection guard), coach backends (Claude Code, Anthropic API, Ollama), "Ask the coach", digests, insights.
- E7 Skills: monthly review, explain a spike, subscription audit, contract check, find cheaper, mortgage check, what-if, tax helper, onboarding interview.
- E8 Subscriptions and contracts: inventory, usage questions, cancellability rules (FR / IT), alternatives with sources, letters, decisions and savings tracking.
- E9 Loans, mortgage and net worth: schedules, payment alerts, inference from bank data, net-worth history, scenarios, end-of-lease reminders.
- E10 Alerts: signals to events, noise control, the weekly summary, in-app centre, optional macOS / ntfy / e-mail / Telegram channels (all off by default, minimal messages).
- E11 Privacy, security and compliance: egress policy and journal,
local_only/offlinemodes, security audit, export and wipe, AI-generated labels and disclaimers, the agent threat model. - E12 Quality: parser fixtures synthesised without real data, gold set and evaluations, model comparison, usage and cost tracking, structured run logs.