Changelog

Technical changes across Sodimo's infrastructure

Repo
Category

Manual scope audit + accounts-playbook promoted + new /leger/ section + release schema extended

Four hygiene passes on the internal manual and the release feed. No chapter is removed from the Sodimo-facing site — the scope classification is a publishing-side decision, not a content edit. A new /leger/ section explains the externalization on the site itself. The extended release schema prepares for a clean two-layer render: product-release rows (signed harness image, new quadlet, new LP chapter) vs tenant-release rows (what actually landed on the Framework Desktop, when, against which harness digest).

Added
  • scope: sodimo | leger | both field on the manual schema, backfilled across all 40 English chapters. Counts: 17 scope:sodimo (engagement-specific — company, data, network, CRM, launchpad, repo registry, open decisions, computer-use-agent), 9 scope:leger (reusable patterns — black-box principle, three design principles, vault, skills library, the-repos registry, four employee-guide chapters on CF Pages / account vault / key rotation / SSH), 14 scope:both (shared patterns with a Sodimo-specific chapter — infrastructure part, hybrid architecture, Cloudflare, mail, harness, accounts playbook, OpenWebUI, quadlet reference, AI-layer part, LLM stack, MCP tools, Paperclip, annex decisions, employee-guide section)
  • src/content/manual/en/36-accounts-playbook.md — the Google → GitHub → Cloudflare → Tailscale → leger.run account spine, promoted from DRAFT-baseline-accounts-playbook.md to an ordered chapter at position 360
  • New /leger/ top-level section at src/pages/leger/index.astro with companion src/content/leger/en/overview.md. One page, explains the externalization cut in Sodimo-internal terms: scope-field counts, the three artefact families (reference templates / runbooks / checklist-policies), the flagged re-classification candidates (38-vault, 34-mail), and the pipeline status table. Nav entry added between /mcp-tools/ and the site actions
  • Release schema extended with scope: product | tenant | both, harness_digest (sha256), harness_digest_prev, image_digest_set (quarter-keyed like images-2026-Q2-a), surface, product_release_id, tenant_slug fields. A single release entry can now self-describe which layer it belongs on
Changed
  • Chapter renumbering in the 36–39 block so accounts-playbook sits at 36 and the OpenWebUI / vault / quadlet-reference chapters shift one slot: 36-openwebui.md37-openwebui.md, 37-vault.md38-vault.md, 38-quadlet-reference.md39-quadlet-reference.md. Order values updated (370, 380, 390); TOC and navigation reflect the new sequence
  • src/lib/navigation.ts — threads the new scope field through for future RENDER_TARGET-filtered renders
  • src/components/SiteHeader.astro, src/layouts/SiteLayout.astro, src/site.config.ts — Leger section registered alongside Manual / Changelog / Skills / MCP tools
  • src/i18n/en.ts + src/i18n/fr.ts — new site.navLeger and leger.* string blocks for the section header and landing copy
Other
  • Nothing is deleted from the manual. Chapters tagged scope: leger stay in the Sodimo-facing site; when a second render target is wired, the same source tree produces both views
  • French chapter frontmatter keeps its current shape — scope rolls out to FR on the next translation pass (EN-first policy). The /fr/leger/ overview and French release-feed rows are translated in this same cut
  • build:sodimo / build:leger npm scripts remain unwired. The RENDER_TARGET env var is honoured by every component that branches on scope; the sibling builds are the last mile and stay on the follow-up list

MCP-tools catalog — 13 tools, copy-ready JSON schemas

The /mcp-tools/ section ships with 13 tool detail pages covering every verb the sodimo-core MCP Worker currently exposes, each with input schema, output schema, and a copy-ready example invocation. Matches the governance section in chapter 42 (mcp-tools) with the operational copy-paste reference that a Claude Code or OpenWebUI session actually needs. Closes issue #4 per D-173.

Added
  • /mcp-tools/ index with filter bar (category + stability) — pure DOM, no framework runtime
  • 13 detail pages under src/content/mcp-tools/en/: erp_read_accounts, erp_read_orders, crm_list_deals, crm_get_contact, crm_upsert_contact, crm_advance_stage, crm_add_activity, crm_search, email_send, email_status, ledger_write, doc_search_piler, whatsapp_send
  • Matching FR mirrors under src/content/mcp-tools/fr/ (26 pages across both locales)
  • src/components/McpToolCard.astro, src/pages/mcp-tools/[name].astro, src/scripts/mcp-filters.ts
  • i18n mcpTools.* block in both src/i18n/en.ts and src/i18n/fr.ts
  • Copy-block behaviour shared with the skills section — navigator.clipboard primary, execCommand fallback
Changed
  • src/site.config.tsMCP tools added to the main nav
  • src/content.config.tsmcp_tools_en + mcp_tools_fr collections registered with a typed schema (category enum, stability enum, auth enum, required invocation + response examples)
Other
  • Playwright smoke: /mcp-tools/erp_read_accounts/ and /fr/mcp-tools/erp_read_accounts/ — copy button copies 257 chars to navigator.clipboard; both PASS
  • npm run build exits 0; pagefind indexes 11 030 words across both locales (147 pages total)

Shared-skills section — 5 copy-ready prompt templates

The /skills/ section lands with five canonical team prompt templates, each one written to run on the local stack first and escalate to cloud only when the team explicitly asks for it. Each skill carries audience (employee / admin / agent-runtime), tier (local-task / local-heavy / cloud-heavy), tool requirements, cost estimate, and a copy-ready prompt block. Closes issue #3.

Added
  • /skills/ index with filter bar (audience + tier)
  • Five seed skills under src/content/skills/en/: summarize-email, rewrite-english, translate-fr, look-up-customer, draft-order-ack
  • Matching FR mirrors under src/content/skills/fr/ (10 detail pages across both locales)
  • src/components/SkillCard.astro, src/components/CopyBlock.astro, src/pages/skills/[slug].astro, src/scripts/copy.ts
  • i18n skills.* block in both src/i18n/en.ts and src/i18n/fr.ts
  • scripts/test-copy.mjs — Playwright smoke test that serves dist/ and asserts navigator.clipboard content on the copy button
Changed
  • src/site.config.tsSkills added to the main nav
  • src/content.config.tsskills_en + skills_fr collections registered with a typed schema (audience enum, tier enum, required prompt + summary)

gpt-oss-120b on Strix Halo via kyuz0 flags; smoke-validated apps

End-to-end kyuz0-aligned local-AI stack on AMD Strix Halo, validated in a full 8-container smoke test (OpenWebUI → LiteLLM → llama-swap → llama-server), plus a companion 11-target apps-smoke pass that surfaced and fixed real quadlet bugs in-tree.

Added
  • docs/README.md, docs/kyuz0-toolbox.md, docs/resync-runbook.md — new docs tree documenting kyuz0 interpolation map, pinned upstream SHA (1421e8706020e8d7e797f71b9f28cd3072e7f868), RADV vs AMDVLK vs ROCm trade-offs on gfx1151, and a step-by-step resync runbook for future kyuz0 upstream bumps
  • Mandatory kyuz0 Strix-Halo flag set applied to every model in llama-swap.yaml: --no-mmap -fa on -ngl 999 --batch-size 4096 --ubatch-size 512 --cache-type-k q8_0 --cache-type-v q8_0 --jinja --direct-io --cache-prompt --cache-reuse 256 --threads 12
  • gpt-oss-120b (unsloth UD-Q8_K_XL, 2-shard GGUF, 65k ctx, reasoning_effort=high) wired as the local-heavy alias
Changed
  • home/dot_config/containers/systemd/llama-swap.yaml — full rewrite. Drives /app/llama-server directly instead of ramalama --runtime llama.cpp run. Discovered during integration that ghcr.io/mostlygeek/llama-swap:vulkan does NOT ship a ramalama binary — the pre-existing config was latently broken at first model load
  • home/dot_config/containers/systemd/llama-swap.yamlproxy: URLs switched from http://llama-swap:NNNN (netavark DNS round-trip) to http://127.0.0.1:NNNN (co-located netns). Self-DNS bug under adguard DNAT hijack produced 502s after the first request; verified fixed (5.4 s cold, 0.08 s warm)
  • home/dot_config/containers/systemd/llama-swap.container — volume mount ~/.local/share/ramalama~/.local/share/llama-models (raw GGUF layout, no ramalama cache format). /dev/kfd + HSA_OVERRIDE_GFX_VERSION=11.0.0 retained as vestige candidates for future removal (tracked in #14)
  • home/dot_config/containers/systemd/litellm.yamllocal-heavy model field openai/gpt-oss-20bopenai/gpt-oss-120b
  • home/dot_config/containers/systemd/twenty.container + twenty-worker.container — image pinned to fully-qualified docker.io/twentycrm/twenty:v2.0.0 (short-name policy fails non-TTY pulls under podman auto-update / bootc)
Fixed
  • home/dot_config/containers/systemd/llama-swap.container — dropped GroupAdd=keep-groups and PodmanArgs=--group-add=render; kept --group-add=video only. The prior combination was incompatible with rootless podman 5.8.2 and blocked llama-swap from reaching Active
  • home/dot_config/containers/systemd/postfix.container — commented out Volume=postfix-main.cf and Volume=postfix-master.cf lines. The referenced files don't exist in the repo yet; podman was failing to create the mount. Postfix now boots on env-only POSTFIX_* config (real config authoring tracked in #18)
  • home/dot_config/sodimo/paperclip.env.tmpl — added BETTER_AUTH_SECRET=CHANGEME-BETTER-AUTH-32-BYTE-HEX. Paperclip refuses to boot without it
Other
  • Full 8-container AI stack on 172.30.0.0/24 test network: qwen3-4b end-to-end through OpenWebUI → LiteLLM → llama-swap → llama-server: PASS, 11.5 s cold / 0.08–0.20 s warm / 53 tok/s on RADV Vulkan. Every kyuz0 flag verified verbatim in podman top on the spawned llama-server process
  • Full 11-target apps stack on 172.31.0.0/24: 9/11 Tier 1 PASS, 3/3 Tier 2 HTTP PASS (vaultwarden /alive, paperclip /api/health, twenty /healthz). Twenty migrations completed in ~18 s (well under the 3–10 min budget)
  • gpt-oss-120b runtime deferred to prod harness — 100 GB UD-Q8_K_XL download infeasible in session window + dev-box kernel cmdline is not kyuz0-tuned (tracked as sodimo/harness#11)

Wednesday buildout — quadlet reference, vault, employee guide, progress meter

Large content-and-tooling pass: six new chapters, a progress meter page, a 5-chapter reference-grade trim, and an accounts-playbook draft. The shape of the manual stabilized in this pass — the infrastructure and AI-layer parts are now reference-complete for the Sodimo engagement.

Added
  • src/content/manual/en/37-vault.md — Vaultwarden deployment, paper envelope, recovery path (new chapter)
  • src/content/manual/en/38-quadlet-reference.md — per-quadlet lookup table for every service on the harness (new chapter)
  • Employee-guide chapters 71–74: 71-cf-pages-with-claude-code.md, 72-account-vault-basics.md, 73-key-rotation.md, 74-ssh-basics.md
  • src/content/manual/en/60-part-dashboard.md + 61-launchpad.md — dashboard section + launchpad chapter
  • src/content/manual/en/70-part-employee-guide.md — new section
  • /progress page + src/pages/fr/progress.astro — per-repo completion meter driven by src/data/progress.json and populated by scripts/compute-progress.mjs
  • .github/workflows/update-progress.yml — recomputes the progress JSON on a schedule
  • src/content/manual/en/DRAFT-baseline-accounts-playbook.md — Google → GitHub → Cloudflare → Tailscale → leger.run account spine, landed as a DRAFT chapter (promoted to 36-accounts-playbook.md in the 2026-04-23 cut)
  • gpt-oss-120b benchmark row appended to 41-llm-stack.md (65k-ctx UD-Q8_K_XL numbers on Strix Halo)
Changed
  • 5-chapter reference-grade trim (15-design-principles, 35-harness, 38-quadlet-reference, 42-mcp-tools, 53-crm) — decision rationale moved to 55-annex-decisions.md. Net −486 lines across the five chapters; annex grew by 23 rows
  • 51-the-repos.md, 31-network.md, 33-cloudflare.md, 35-harness.md, 42-mcp-tools.md — content refresh alongside the trim
Other
  • Per-repo progress computed: every tracked repo reports > 0 % completion; JSON-schema-validated against src/data/progress.json

Paperclip quadlet + cloudflared + Caddy routing

First cut of the Paperclip quadlet stack landed in sodimo/dotfiles, wired into the existing cloudflared tunnel and Caddy reverse-proxy, with the derived image built locally on the Strix-Halo dev box.

Added
  • docker/paperclip/Dockerfile — derived image layering @mariozechner/pi@latest on ghcr.io/paperclipai/paperclip:sha-b8725c5. First use of a top-level docker/ dir for a Sodimo-built image (establishes the pattern for future derived images)
  • home/dot_config/containers/systemd/paperclip.container + paperclip-db.container (Postgres sidecar)
  • home/dot_config/containers/systemd/paperclip-data.volume + paperclip-db-data.volume
  • home/dot_config/caddy/routes/paperclip.caddy — reverse-proxy route for paperclip.sodimo.eu
  • home/dot_config/sodimo/paperclip.env.tmpl — first .tmpl EnvironmentFile in the repo (divergence from the stack-wide convention of co-locating envs with .container units; authorized in the handoff)
  • home/dot_config/sodimo/paperclip/models.json — model allowlist for Paperclip
  • Local image build: ghcr.io/sodimo/paperclip:v2026.416.0-pi0.x.y (2.55 GB, image id 6ebbd8afe02f) — not yet pushed to ghcr.io, pending org-level write:packages (tracked in #12)
Changed
  • home/dot_config/cloudflared/config.yml — ingress list appended with paperclip.sodimo.eulocalhost:80 (single named tunnel, now routing 6 hostnames)
  • Stack-wide Restart=always relaxed to Restart=on-failure for Paperclip only (explicit handoff instruction; revisit after first-week production runtime)
Fixed
  • podman quadlet -dryrun passes with 0 parse errors across all 42 units after Paperclip addition

Changelog site scaffolded

The internal changelog site is live in draft form, temporarily hosted on GitHub Pages at sodimo-changelog.pages.dev. Cloudflare Pages and the changelog.sodimo.eu subdomain follow once the domain is transferred from Strato.

Added
  • Static site generated from the Omarchy-style template with Sodimo colours applied
  • Release feed aggregates GitHub releases across every repo registered in config/repos.toml
  • Pagefind full-text search across releases and the internal manual
  • Filters by repo, category, and date
  • Cloudflare Access gate planned for sodimo.eu emails plus thomas@leger.run once the domain flips
Changed
  • All release tags move to calver YYYY-MM-DD-<slug> — semver is not a legible format for Michel reading reports on his phone

Template repo for every Sodimo repository

Every new Sodimo repo is forked from this template. It bakes in the release cadence, the architectural descriptor, and the commit conventions so a new repo reaches production readiness in minutes rather than hours.

Added
  • REPO.md architectural descriptor with frontmatter fields: name, slug, chapter, audience, vendor_class, upstream, sanitization_posture, status, depends_on, consumed_by
  • CLAUDE.md with the release convention, the definition of "deployed", and the Sodimo stack split
  • COMMIT_MESSAGE.md with conventional-commit conventions
  • GitHub Actions: release-please.yml for calver releases, pr-feed.yml appending to .changelog/prs.jsonl on merge, deploy-docs.yml triggering the changelog rebuild on release, and ci.yml
  • Vendor taxonomy labels: vendored-upstream, hard-fork, soft-fork-inspired, contrib-upstream, home-built
  • Issue and PR templates, CODEOWNERS, .gitignore, and tasks/ scaffold
Changed
  • Release convention fixed to calver YYYY-MM-DD-<slug> — board-legible, avoids semver churn on client repos that do not export APIs

Sodiwin data contract locked with Florian

The nightly Sodiwin dump is fully specified. Florian accepted SFTP to the Synology NAS, atomic writes, the dated filename convention, and the failure-email contract. Column widening on clients, suppliers and articles is in a second round. No code has landed — the repo will be scaffolded once the first dump arrives.

Added
  • 20 canonical tables specified (7 référentiels, 2 stock/lots, 9 document twins, 2 finances)
  • SFTP destination: 192.168.0.17, user sodiwin-dump, chroot /volume1/sodiwin-dumps/, schedule 02:30 Paris
  • Filename convention: YYYY-MM-DD_<slug>.csv per slug directory, atomic rename from .csv.partial
  • File format: Sodiwin-native CSV — ; separator, ISO-8859-1/Windows-1252, DD/MM/YYYY dates, , decimal, CRLF
  • Temporal windows negotiated: référentiels, stock, lots, devis, BL, encours, balance, objectifs and recettes as nightly full snapshot; base-ventes, base-commandes-ventes, base-achats and base-reglements on a 1-year rolling window; base-echeances restricted to unsettled invoices only
  • One-time historical dump agreed — all data since ERP origin through Dec 31 2025
  • Failure email contract: 3 retries at 60s intervals, then admin@sodimo.eu with fallback to paul.ghafary@sodimo.eu
Changed
  • Historical window on transactional tables reduced from 18 months (Thomas's ask) to 1 year rolling — Florian offered 3 years, settled on 1 year plus one-time historical backfill
  • Previous day's file deletion delegated to the Sodimo side after nightly ingest
Removed
  • Dynamic rupture columns dropped from base-articles (rup1/rup2/rup3/sous_rupt/librup1..3) — generated at report time in Sodiwin, not stored

Hybrid Cloudflare plus on-prem architecture locked

The architecture split for the engagement is fixed. Neither pure-Cloudflare (Monday plan, rejected for sovereignty over email and inference) nor pure on-prem (rejected for public surface cost). Cloudflare owns DNS, R2, D1, MCP Worker, Pages and Access. A single Framework Desktop owns SMTP, IMAP, FTP, AI inference, voice transcription and the mail archive. Tailscale binds the box, the NAS, the laptops and Rani's phone.

Added
  • Framework Desktop AMD Strix Halo 395+ with 128GB unified memory, ordered by Paul, ETA approximately 1 week
  • Four R2 buckets planned: sodimo-archive, sodimo-backup, sodimo-dashboards, sodimo-public with a 90d Standard → IA lifecycle
  • Credentials are self-managed — the master admin uses a personal Gmail account and the credentials that matter are written down on paper
  • Second-node decision deferred to T+90 dashboard review — one Framework plus Synology backup plus 2-hour RTO bare-metal restore is sufficient
  • Four domains inventoried (sodimo.eu, yallafood.eu, cavisteduliban.fr, sodimonet.fr) and Strato mailbox weights captured across 33 mailboxes totalling ~45.5 GB
Changed
  • AI plan: cloud-only Anthropic → local inference on the Framework box, with cloud escalation reserved for high-stakes work
  • Email plan: Strato (33 mailboxes, ~45.5 GB) → Postfix, Dovecot, rspamd and Piler on the Framework box — sovereignty and deletion guarantees Strato cannot give

v2 brand system — Levantine Modernism

The v2 brand system replaces the Olive/Leaf v1 that shipped with the legacy WordPress site. Cedar, sumac, parchment, ink. Fraunces, Inter, JetBrains Mono. Four P0 accessibility fixes cleared against measured contrast ratios. Review score 7.80/10 against v1 at 5.27/10.

Added
  • Cedar primary #1F3B2D, sumac accent #C85A3A, parchment canvas #FAF6EF, warm-black ink #1B1913
  • Saffron reserved as signal colour — focus rings and warning badges only
  • Composite type tokens (type.h1 bundles family, weight, size, line-height, tracking) so templates reference one token per heading level
  • Dual spacing scales: static space.100..900 plus fluid space-fluid.100..800, 1:1 name-aligned
  • Warm-tinted elevation using cedar-900 alpha rather than neutral grey, 5 shadow tiers plus focus ring
  • prefers-reduced-motion collapses every duration token to 0ms at :root
  • VOICE.md covering forbidden vocabulary, three writing tests, tone dial, sample headlines in FR and EN
  • Six-file SVG logo set at assets/logo-v2/ (light, dark, icon, favicon, wordmark-dark, wordmark-light)
  • Two dark-mode variants shipped side by side: cedar-deepening default and warm-charcoal alternate
Changed
  • Primary colour #4b5759#1F3B2D — the old value read as hesitant B2B
  • Accent #a2c32a → #5d9934 gradient removed entirely, replaced by solid sumac for print compatibility
  • Canvas #f7f7f5#FAF6EF — warm parchment harmonises with the food and wine subject
  • Typography Montserrat → Fraunces + Inter + JetBrains Mono — Montserrat is universal, Fraunces has a voice
  • Button radius 999px pill → 8px rectangle, pill reserved for badges and tags only
  • Status colours reuse brand primitives — no foreign traffic-light red or green
  • Focus ring remapped from saffron on parchment (1.95:1, fails WCAG 1.4.11) to a surface-aware pair: sumac-500 on light (5.63:1), saffron-400 on dark (11.3:1)
  • --color-text-subtle remapped from ink-300 (2.79:1, fails AA-Large) to ink-400 (5.13:1 AA)
Fixed
  • Warning badge tokenised as --color-warning-bg: saffron-50 + --color-warning-ink: saffron-700 — the prior ad-hoc pairing was 2.95:1 (AA fail)
  • Contrast claim corrected at index.html:824 — white on sumac-400 is 4.22:1 (AA-Large only), not the self-reported 4.6:1

sodimo.eu rebuilt on Cloudflare Pages

The public site is off WordPress and onto Cloudflare Pages, served from a single build.mjs ESM script with no framework, no CMS, no runtime dependencies. Three sibling domains (yallafood.eu, cavisteduliban.fr, sodimonet.fr) will move to the same pattern.

Added
  • 20 HTML pages (10 FR at root, 10 EN under /en/, full hreflang coverage)
  • Pages Functions POST /api/contact (contact form, Turnstile support, MailChannels delivery, Slack webhook fan-out) and POST /api/newsletter (Workers KV persistence)
  • Strict security headers: CSP, HSTS, X-Frame-Options DENY, interest-cohort=(), immutable asset caching
  • 301 redirects for every legacy WordPress path, xmlrpc.php returns 410 to deter probes
  • GDPR-compliant cookie notice, no analytics, no advertising cookies, explicit consent checkbox on the contact form
  • 21 brand tiles on the homepage anchor-linked to nos-marques.html feature sections
Changed
  • Hosting: legacy WordPress (over €100/month) → Cloudflare Pages (free tier)
  • Build: WordPress/PHP → single build.mjs, only devDependency is wrangler@^3
Removed
  • WordPress install, wp-admin, wp-content, RSS feeds

PNG logos replaced with SVG set

Four legacy PNGs (sodimo-logo-dark.png, sodimo-logo-light.png, sodimo-logo-footer.png, sodimo-favicon.png) are replaced by a six-file SVG set. Web, email and print now share a single source of truth.

Added
  • logo-light.svg, logo-dark.svg, icon.svg, favicon.svg, wordmark-dark.svg, wordmark-light.svg
  • SVG generation pipeline documented in HANDOFF.md — potrace for the icon, vtracer for the wordmark, svgo multipass
Removed
  • All PNG logo files
Fixed
  • Logo-dark rule updated for photography backgrounds — a 35% or darker veil is required before logo-dark.svg is legible over imagery

Network topology mapped; working path established

Sodimo's two-VLAN network is now documented: 192.168.0.x for data, 192.168.1.x for VoIP. Thomas's desk wall ports are both wrong — one lands on VoIP, the other is dead. Until the panel repatch, the work path is the sodimo_wifi SSID, which binds to the data VLAN and gives access to the Synology SAUVE_DISK at 192.168.0.17.

Added
  • Device inventory: TP-Link ER706W router, TP-Link OC300 controller, Synology DS218j (DSM 6.2), Sodiwin server at 192.168.0.88 (Fujitsu, Windows Server, IIS 10.0, Pervasive SQL on ports 1583/3351), Canon iR-ADV printer, 12 Dahua IP cameras
  • Second Fujitsu Windows host at 192.168.0.108 noted, purpose TBD
  • VoIP VLAN inventory: Sagemcom router, Zyxel PoE switch, Yealink DECT — no NAS access
  • Guest SSID on 192.168.200.x flagged as client-isolated and unusable for work
Changed
  • Working path confirmed as sodimo_wifi until the data-port repatch to NETGEAR clears the wall-port problem