Skip to content

Storage Contracts

Context

This page defines practical storage contracts so contributors know what can change, what must be retained, and what is considered operational evidence.

Details

Contract summary: - data/*.csv: input data, versioned, manually curated or imported. - data_fixtures/*.csv: test fixtures, deterministic, must remain stable for tests. - logs/marketstack_quota_usage.json: mutable state, updated by quota-aware workflows. - logs/fx_rate_state.json: mutable FX state containing the last successful USD-to-CAD rate, retrieval timestamp, and source. - logs/github_issue_bridge_state.json: mutable state for GitHub issue bridge continuity. - logs/github_issue_audit.jsonl: append-only audit log. - logs/archives/*.gz: compressed historical logs under retention policy.

Retention and lifecycle: - active log/state files keep recent operational history; - old log data is archived as .gz under logs/archives/; - old archives are purged by retention policy automation.

Change rules: - update contracts and runbooks in the same PR when behavior changes; - avoid renaming log/state files without updating workflows and wiki links. - keep an_haut/an_bas faithful to source values; do not introduce converted 52-week thresholds into the data contract. - treat valeur/devise as the only source for native prices and 52-week comparisons; cad_value is reserved for consolidated CAD reporting and historical charting.

Contrat de position courante et de transactions

Le chapitre métier décrit les règles fonctionnelles de ce contrat.

  • titres.current_shares : NUMERIC(18,4) nullable, cache recalculé après mutation réussie depuis toutes les transactions. NULL signifie non initialisé. Les lectures des totaux utilisent le registre, pas ce cache ni la quantité du dernier relevé ; zéro ne déclenche aucun repli.
  • transactions.shares et transactions.price : précision historique jusqu'à quatre décimales conservée ; nouvelles saisies à deux décimales. transactions.total_amount reste à deux décimales et est la source des sommes signées, sans reconstruction depuis le prix affiché.
  • transaction_totals : objet Decimal calculé : net_shares, average_unit_cost, sell_average_price, net_cash_flow. net_cash_flow additionne les ventes encaissées et soustrait les achats payés pour suivre la récupération du capital, non le profit comptable. Zéro sans opération; un achat réduit le solde, une vente l'augmente. Le total positif n'a pas de signe « + ». Dans l'interface, seules la valeur et son aide occupent la cellule existante. Il n'y a pas de champ ou de colonne supplémentaire.
  • historique.quantite, valeur, devise, cad_value, fx_rate_used et les dates restent inchangés par les POST de transactions. Les graphiques et performances continuent à lire les snapshots.

Écritures et lectures

Les POST sécurisés conservent les verrous titre puis registre et valident strictement le registre candidat complet. Le rejeu suit la date, BUY avant SELL le même jour, puis l'identifiant stable. Aucune quantité snapshot ou en cache et aucun solde d'ouverture inféré ne couvrent une vente. Une quantité chronologiquement négative bloque la mutation, même si le solde final serait positif. Le cache est recalculé depuis le net du registre après succès, à quatre décimales, avec contrôle de capacité numérique.

Le coût moyen décrit les actions encore détenues : les achats sont pondérés par leur quantité et les ventes retirent le coût des actions vendues. Une vente partielle laisse inchangé le coût moyen des actions conservées; après liquidation, un nouvel achat établit un nouveau coût. Le prix moyen SELL est pondéré par le nombre d'actions vendues. Le flux de trésorerie est calculé séparément, sans déduire le coût des actions vendues.

Les nouvelles opérations restent soumises à la validation chronologique et à la protection contre une vente supérieure aux actions détenues.

Transaction et quantité sont publiées par un commit unique. Refus, défaut de confirmation et erreur ne doivent ni initialiser la colonne ni laisser un état partiel. Les contrôles de chronologie, droits et CSRF restent applicables. La démonstration demeure en session uniquement : affichage unique après POST, réinitialisation au GET suivant et filet de sécurité de 30 minutes.

Les GET ne persistent aucune initialisation. _current_portfolio_totals agrège les quantités BUY moins SELL en SQL et récupère les derniers relevés de chaque titre. Le test pour 1 et 10 titres exige une SELECT de marché et au plus une agrégation du registre ; il ne garantit pas une seule SELECT totale. Le natif utilise valeur dans la devise du titre ; le consolidé utilise les cad_value persistés, même si les taux diffèrent entre relevés. Un cours nul n'est pas absent ; un cours absent n'est pas remplacé par un prix de transaction. Aucun nouveau taux externe ni réécriture de snapshot.

Le DDL initial du pipeline inclut la colonne ; ses UPSERT de cours ne l'écrasent pas. Le chargement initial reste idempotent. Une opération extérieure doit être enregistrée dans le registre, pas compensée par un snapshot. Le retrait des titres absents du CSV reste inchangé ; aucune nouvelle suppression n'est déclenchée par une vente ramenant la quantité à zéro.

Lors d'un import, insert_data calcule historique.quantite par agrégation BUY moins SELL des transactions dont transaction_date <= execution_date. La quantité vaut zéro si le registre est vide; aucun repli sur no_of_shares n'est appliqué. Le CSV conserve la liste des titres et les métriques importées. Les anciens relevés ne sont pas recalculés en masse. Dans le dashboard sécurisé, portfolio_dust reçoit les quantités courantes du registre via share_quantities, utilise la normalisation existante des colonnes et exclut les positions nulles ou absentes; les pourcentages restent ceux du CSV. Les autres métriques, dont les prix de référence et expositions importées, restent issues du CSV et ne sont pas recalculées depuis les transactions.

Ces règles de persistance s'appuient sur une migration additive et définissent également les précautions à respecter lors d'un retour arrière :

Migration et retour arrière

  1. La migration additive 0004 ajoute la colonne sans backfill. scripts/apply_migrations.py conserve le suivi par schema_migrations. Un échec DDL MySQL peut avoir déjà créé la colonne : vérifier colonne et version avant toute reprise, sans supposer que le rollback Python a annulé le DDL.
  2. scripts/start_web.py vérifie obligatoirement la présence de la colonne avant de servir cette version. Les autres échecs d'amorçage qui étaient facultatifs ne deviennent pas tous bloquants.
  3. En retour arrière, conserver la colonne et ses valeurs. L'ancien code revient aux quantités snapshot ; il ne maintient pas le nouveau cache. Geler les mutations pendant ce retour, ou rapprocher les écritures anciennes avant réactivation de 023. Ne supprimer ni remettre à NULL les valeurs pour simuler un retour transparent. Le registre reste la source de vérité de la version #369.

Les contrôles SQLite et la présence de FOR UPDATE dans une requête compilée ne démontrent pas la sérialisation réelle MariaDB. Les mesures et limites des vérifications de #369 sont conservées dans le rapport d'activité.

Additive authentication contract #377

The authentication data model defines field invariants; the functional reference defines owner-facing behavior.

  • user retains the password hash and adds encrypted active/pending TOTP factors, preparation timestamps, the last consumed period, session generation, recent account failures and a blocking deadline. No plaintext TOTP factor belongs in persistent storage.
  • auth_origin_attempts stores canonical-origin pseudonyms, bounded recent failures, blocking deadlines and expiry. Unknown usernames do not create account records.
  • A dedicated transaction locks origin then account on MariaDB/InnoDB; SQLite takes a write lock before reading. Code consumption and limits commit before a successful login result. Failed commits cannot grant access.
  • Confirmation atomically installs the pending factor, consumes its first period, increments session generation and clears preparation. Replacement leaves the previous factor active until confirmation.
  • The additive migration 0005 and initial pipeline DDL must agree, preserving existing accounts and portfolio data. The web startup guard checks the schema and factor state before serving traffic.
  • Migration comments must not contain semicolons: the current runner splits SQL at these separators before removing comment lines. A regression in tests/test_apply_migrations.py checks that migration 0005 sends only the intended SQL statements to the connection; it does not replace MariaDB execution.
  • The isolated local server and administrative CLI share an explicitly configured file-based SQLite database. Restarts retain factors, periods, limits and transactions. Production credentials and .env are excluded.
  • Back up the database and persistent encryption key separately. Retain authentication columns and factors during rollback; never run password-only code against an activated private account. Database or key loss requires fail-closed recovery through authorized administration.

SQLite validation is not evidence of MariaDB migration or concurrency behavior. See the isolated verification procedure.