Chapter 33 / 40
Annex: decisions log
Status: Live
This annex is the full decision log for the engagement. Over the course of five weeks, 152 named decisions (D-001 through D-152) were surfaced, deliberated, and either locked, left open, or marked out of scope. Every one was annotated by Tom in the source files; the table below is that log in compact form.
The annex exists for two reasons. First, the earlier ANNOTATED files that held these annotations lived outside the handoff artifact and were not visible to anyone reading this manual — anyone wanting to know why a given chapter says what it says had no way to trace it back. The annex closes that gap. Second, the decisions are the project’s archaeological record: when a future engineer looks at a quadlet, a table, or a workflow and wonders why it was shaped that way, the D-number in this log is the primary-source answer.
How to read the table. Each row is one decision. The ID column is the D-number assigned when the decision surfaced. The Decision column is a short restatement of the question. The Annotation column is Tom’s actual call, transcribed (lightly trimmed to fit; sensitive or out-of-scope commentary is marked [redacted]). The Status column reduces the outcome to one of four buckets: locked (a call was made and it is reflected elsewhere in the manual), open (still to be resolved, usually at a specific later moment such as “during the email migration”), or out-of-scope (explicitly dropped or deferred past the engagement). The Related column points at the chapter where the decision lands, when one exists.
D-001 through D-082 come from the Monday ANNOTATED pass at end of Week 1. D-083 through D-152 come from the Tuesday ANNOTATED pass that closed Week 2 architecture. Cross-domain dependency notes are omitted from this table for readability; they are in the source files.
Decisions table
| ID | Decision | Tom’s annotation | Status | Related |
|---|---|---|---|---|
| D-001 | Purge plaintext password from git history before public push | leave as is | locked | — |
| D-002 | D1 as canonical source vs Postgres mirror for Power BI | drop PowerBI completely; D1 on a CF Worker is the canonical db | locked | ch23, ch32 |
| D-003 | MCP transport protocol (Streamable HTTP only vs dual) | 100% Cloudflare MCP host; D1 retrieval + long-run CRM; self-hosted email accessible | locked | ch42 |
| D-004 | MCP auth layering (Worker-only vs CF Access + bearer bypass) | most Cloudflare-native path; maximally CF-side | locked | ch42 |
| D-005 | Tailscale tag/ACL topology | Tailscale is a dev tool for now; already installed on harness, sufficient | open | ch31 |
| D-006 | Sodiwin ingest: SFTP only vs SFTP + email fallback | FTP only at Florian’s rolling window; one-time historical dump; written to D1 via ETL | locked | ch14, ch21 |
| D-007 | Upsert path split (Pattern A seeds vs Pattern B hot tables) | depends on shape of Sodiwin data; decision postponed | open | ch23 |
| D-008 | Router exposure surface (internal vs router.sodimo.eu) | MCP done = the principal connects the Claude.ai sub and controls everything | locked | ch42 |
| D-009 | Rupture/stockout source (Florian declined rup* fields) | compute ourselves; ETL repo concern; option C | locked | ch23 |
| D-010 | Sodiwin-native CRM vs custom TS-on-CF-Workers | custom TS on CF; inspired by Twenty + Pipedrive; centerpiece; unlocks CF-gated dashboards | locked | ch53 |
| D-011 | Repo naming convention inside sodimo org | option B — drop prefix inside org | locked | ch51 |
| D-012 | Repo visibility matrix | all private until final deployment; exception for sodimo/harness | locked | ch51 |
| D-013 | REPO.md frontmatter schema (10 fields vs 5) | use whatever the handbook schema already has | locked | ch51 |
| D-014 | What folds into sodimo/harness vs lives separate | no Terraform; harness is anything in the Linux server that’s not a dotfile — it programs | locked | ch35 |
| D-015 | Skills library canonical home | chezmoi dotfiles-backed; users consume via releases; may add a changelog section | open | ch43 |
| D-016 | Release tag convention (calver vs semver) | calver only across all sodimo/* repos; semver dropped | locked | ch51 |
| D-017 | Harness fork strategy | sodimo/harness already done | locked | ch35 |
| D-018 | sodimo-repo-template end-to-end readiness gate | test E2E with harness release as first test, tied to changelog | locked | ch51 |
| D-019 | Side-quest repo sequencing while pillars blocked | one bite at a time | locked | ch51 |
| D-020 | Ship v2 Cedar/Sumac brand as production | real nice-to-have; not before Week 3-4; not shown to team until more is shipped | out-of-scope | — |
| D-021 | Icon library (custom commission vs Lucide) | Lucide | locked | — |
| D-022 | Self-host fonts vs Google Fonts CDN | option A fine; hold off on further brand/design; will use new Claude design later | locked | — |
| D-023 | Fedora bootc version (41 vs 44) | F44, pinned, resolved | locked | ch35 |
| D-024 | NAS mount protocol (NFSv4.1 vs SMB vs both) | big blocker; needs in-person resolution mid-Week 2 | open | ch35 |
| D-025 | Network topology to Halo (repatch vs wifi interim) | ideal handoff: harness in the server rack next to NAS, on company intranet | locked | ch31 |
| D-026 | UPS spec | same UPS as current server; not my concern, resolved for a decade | locked | ch35 |
| D-027 | Mail retention default (10y vs 7y) | indefinite, redundant backups; still need to resolve “email sent during reboot” | locked | ch34 |
| D-028 | ”Deployed = 5 days” instrumentation | purpose of the manual; nothing fancy needed; the principal sees completed work not progress | locked | ch28 |
| D-029 | Admin identity bootstrap sequence | CF/Gmail/GitHub/Tailscale set up; domain + email transfer in one day when server arrives | locked | ch35 |
| D-030 | ISP ticket escalation ladder | no external SMTP provider; this was relevant to email, revisit during migration | open | ch34 |
| D-031 | Canonical table count (20 vs 21) | built around the Sodiwin data dumps we receive | open | ch24 |
| D-032 | Temporal window on document tables | Friday sent spec (full history forever) | locked | ch23 |
| D-033 | One-time historical dump still owed | one-time done by Florian; we work around that | locked | ch14 |
| D-034 | ETL failure notification recipient | the ERP admin; forward to Florian once admin is set up | locked | ch23 |
| D-035 | base-clients column widening (35 to ~130 cols) | ETL concern when building | open | ch23 |
| D-036 | SCD-2 scope (3 hubs vs 4 vs 5) | ETL concern | open | ch23 |
| D-037 | Idempotency key for document_lines | ETL concern | open | ch23 |
| D-038 | ETL run cadence (03:00 timer vs inotify) | ETL concern | open | ch23 |
| D-039 | D1 snapshot cadence + R2 retention | D1 daily with time-travel and clear rollback procedure; belongs in the manual | locked | ch23 |
| D-040 | Parity gate: which 2 tables must match | ETL concern, to be decided later | open | ch23 |
| D-041 | Team password manager selection | neither hosted option; self-manage; important keys written on paper; Google Password Manager OK | locked | — |
| D-042 | MX cutover order | make email addresses available to others; easy since we own the email server | open | ch34 |
| D-043 | External SMTP smarthost: always-on vs fallback-only | no external smarthost; defeats the purpose | locked | ch34 |
| D-044 | DMARC ramp on sodimo.eu | option C (skip quarantine; none to reject once DKIM clean) | locked | ch34 |
| D-045 | Piler access model (Tailscale UI + CF Tunnel REST) | service token; Claude.ai programmatic access; middle layer on harness possible | locked | ch34 |
| D-046 | Shared inbox mechanism (Dovecot ACL vs Sieve vs alias-fanout) | tbd; want the most feature-rich; aiming for podman quadlet for email | open | ch34 |
| D-047 | BCC archive pattern (always_bcc inbound + journal outbound) | tbd when we build email | open | ch34 |
| D-048 | Strato retention post-cutover | 4 weeks extra safety | locked | ch34 |
| D-049 | CNIL employee transparency notice | defer to Chapter 6 handoff; ensure employees read it once | locked | ch34 |
| D-050 | orders@ / commandes@ inbox topology | MCP always uses English; preserve existing email aliases | locked | ch34, ch42 |
| D-051 | BCC pattern for Sodiwin order emails (rep outbound) | existing mechanism; keep for later | open | ch34 |
| D-052 | Model routing for recouvrement drafts | current local models today, new model tomorrow; manage in harness; testing required | locked | ch41 |
| D-053 | Model tier for the principal’s monthly commentary skill | tbd | open | ch41 |
| D-054 | Cloud fallback per-user monthly ceiling | cloud fallback is the Claude Code subscription; issue for later | open | — |
| D-055 | Cloud model selection (Anthropic-only vs +OpenAI) | Claude only by design until further notice | locked | ch41 |
| D-056 | API key ownership (Sodimo billing vs Thomas-transfer) | [redacted — commercial] | out-of-scope | — |
| D-057 | Cloud inference org-wide cap | [redacted — commercial] | out-of-scope | — |
| D-058 | Validator design for cloud escalation | cloud escalation is described on the Sodimo website | open | ch41 |
| D-059 | MCP tool count discrepancy (27 vs 18) | tbd final count, feature by feature | open | ch42 |
| D-060 | Kanban in CRM | yes Kanban; feature parity and beyond; absorb Pipedrive workflows; CRM is the crown jewel | locked | ch53 |
| D-061 | Pipedrive decommission timeline | progressive rollout; real-world testing | locked | ch53 |
| D-062 | MCP bearer-token rotation policy | to be discussed on MCP repo; most Cloudflare-native way | open | ch42 |
| D-063 | Sales-lead shell alias set (5 vs 7 vs 3) | CRM concern | open | ch43 |
| D-064 | Photo thumbnail in MCP pre-call brief | tbd | open | ch42 |
| D-065 | OpenWebUI deployment scope | another podman quadlet on harness, documented to the team | locked | ch36 |
| D-066 | Admin cadence content (structured vs open) | the ERP admin decides; we build the infra | locked | — |
| D-067 | Principal Friday-report delivery channel | teach team to edit dashboards with Claude Code and deploy to CF Pages themselves | locked | ch43 |
| D-068 | [commercial posture] | [redacted — commercial] | out-of-scope | — |
| D-069 | Recouvrement auto-send threshold for Tier 1 | set up with good CRM; add to CRM features (think like an engineer) | locked | ch53 |
| D-070 | [commercial] | [redacted — commercial] | out-of-scope | — |
| D-071 | [commercial] | [redacted — commercial] | out-of-scope | — |
| D-072 | [Claude.ai subscription transfer] | drop fully; the sales lead has his own subscription | out-of-scope | — |
| D-073 | Draco engagement scoping cadence | [redacted — commercial] | out-of-scope | — |
| D-074 | Manual chapter count (7 vs 6 vs 5) | flexible; revise in a focused session on handbook | open | — |
| D-075 | ”Deployed” checklist execution mechanism | dashboard specifics come way later | open | ch28 |
| D-076 | Regression-discipline enforcement | convention only is fine; no need to overcomplicate | locked | — |
| D-077 | Daily engagement logbook format | daily engagement goes into the daily releases on the changelog | locked | — |
| D-078 | Friday team demo format | drop; the changelog does that | locked | — |
| D-079 | PR ticker visibility cadence | all PRs render | locked | — |
| D-080 | ETL failure runbook + escalation path | ERP-admin email; escalates to Florian; debugs with Claude; texts me if issues | locked | ch23 |
| D-081 | Florian relationship tone | option A (arm’s-length) | locked | ch14 |
| D-082 | ”10 Unknowns” scope trimming | some point during the engagement; not priority at all | open | — |
| D-083 | Paperclip AI scope and identity | Paperclip = tool to visualise agent runs dashboard-style; observability on agent runs | locked | ch44 |
| D-084 | Paperclip scope — agents only vs agents + humans | option C (agent now, human T+30); see paperclip doc | locked | ch44 |
| D-085 | Paperclip read-only vs approvals-queue | preferred read-only for observability | locked | ch44 |
| D-086 | 20 vs 21 canonical D1 tables | agent queue/other mechanisms discussed later; prefer D1 airgapped for ERP mirror | locked | ch24 |
| D-087 | Sodiwin export window (full vs 18-month rolling) | already resolved with Florian; done when we build the ETL pipeline | locked | ch14 |
| D-088 | 8-to-20 file schema collapse confirmation | leave for later; finalise D1 tables when Florian gives us data and we write ETL | open | ch23 |
| D-089 | sodiwin-agent queue table naming | sodiwin agents are not the same as the MCP server; MCP delivers data and wraps ops | open | ch45 |
| D-090 | Paperclip repo visibility | will be explained better in the dedicated paperclip file | locked | ch44 |
| D-091 | sodiwin-agent remote protocol | computer-use agent for the Sodiwin Windows VM; nice to have only | open | ch45 |
| D-092 | sodiwin-agent pilot ramp criterion | build and deploy early; let people within the company test it | open | ch45 |
| D-093 | sodimo/paperclip vs sodimo/observability naming | see paperclip file | open | ch44 |
| D-094 | Collapse email repos to single sodimo-runbooks | overwritten into sodimo/mail; DNS thing tbd during mail repo work | locked | ch51 |
| D-095 | SEO agent for sodimo.eu | SEO is not my concern; team changes their website themselves once CF is set | out-of-scope | — |
| D-096 | OpenWebUI deployment scope | yes, another quadlet on harness; harness documentation must be strongest point of manual | locked | ch36 |
| D-097 | OpenWebUI auth posture | keep OpenWebUI dead simple; no auth; admin-level for everyone; CF Pages and/or Tailscale | locked | ch36 |
| D-098 | OpenWebUI users & per-user bearer tokens | OpenWebUI only for local AI models hosted on the device, optionally connecting to MCP | locked | ch36 |
| D-099 | OpenWebUI sees Paperclip | OpenWebUI is chat for local models; Paperclip covers background agent runs | locked | ch36, ch44 |
| D-100 | Moonlight/Sunshine for human VNC on harness | yes, Moonlight installed on harness; more a backup if things go wrong than daily tool | locked | ch35 |
| D-101 | External smarthost as email fallback | drop completely; all Sodimo email hosted locally until further notice | locked | ch34 |
| D-102 | Secrets manager replacement | to be discussed later; favor on-premise free OSS tools | open | — |
| D-103 | MX cutover order + T-schedule confirmation | to be discussed during the email migration | open | ch34 |
| D-104 | Fallback MX destination | Strato while we phase out; self-hosted primary; CF Routing 99% not needed | locked | ch34 |
| D-105 | PTR delegation from upstream IP provider | to be resolved during email migration | open | ch34 |
| D-106 | Retention 7y vs 10y (vs tripartite) | hold all emails, ever; full archive; ~100 GB cap for decades is not a problem | locked | ch34 |
| D-107 | ETL failure alert recipient | ETL issues are near-fatal; I remain available to deploy emergency fixes | locked | ch23 |
| D-108 | v_cultural_calendar view in final D1 schema | vestigial from initial research; drop completely | out-of-scope | — |
| D-109 | DDMRP Red/Yellow/Green buffer system | vestigial; drop | out-of-scope | — |
| D-110 | Dual-calendar demand forecasting | vestigial; drop | out-of-scope | — |
| D-111 | MailerLite + Shopify/Oxatis SPF reconciliation | MailerLite fully unused; kill entirely; make self-hosted; Shopify is the company’s scope | locked | ch34 |
| D-112 | Halo-IP reputation reuse | part of email transition; agreed re: warmup | open | ch34 |
| D-113 | Cold-outreach separate domain pattern | decided NOT to do cold outreach; no cold outbound from the harness that carries the inbox | locked | ch34 |
| D-114 | Shared-inbox implementation (Dovecot ACL vs forwarder) | Postfix virtual aliases good enough; Dovecot ACL also fine; explain, don’t finalize | open | ch34 |
| D-115 | imapsync batching order confirmation | gets done once, over the weekend; not a big problem | locked | ch34 |
| D-116 | Deliverability monitor stack | thinnest defensible stack | locked | ch34 |
| D-117 | Primary local-inference runtime | llama.cpp in a toolbox; final decision; no alternative runtime whatsoever | locked | ch41 |
| D-118 | MoA ensemble build scope | dense vs MoA doesn’t matter; only constraint is best available model on our hardware | locked | ch41 |
| D-119 | Voice ecosystem staging | drop voice ecosystem completely; WhatsApp gets a scoped bot with strong guardrails | locked | ch54 |
| D-120 | 24 canonical skill IDs — 15 production vs 9 stubs split | leave that granularity for later; my job is infrastructure for skill sharing | out-of-scope | ch43 |
| D-121 | Monthly commentary skill template + samples sequencing | just another skill; not important | out-of-scope | — |
| D-122 | Skill-level validator coverage | drop; skills are not my responsibility until perhaps the final week | out-of-scope | — |
| D-123 | Eval set cadence | evals are not something Sodimo must do itself; teach them to upgrade LLMs | out-of-scope | — |
| D-124 | ”Claude default” Tuesday leak cleanup | heuristic to teach the team, not enforced at UI/UX level | open | ch41 |
| D-125 | CRM Kanban scope | yes Kanban; spending a full week refining the final CRM version | locked | ch53 |
| D-126 | Sodimo delta-alert tool | yes, this is indeed needed | locked | ch53 |
| D-127 | Recouvrement auto-send Tier 1 threshold | auto-send + escalation to management are CRM-side concerns | locked | ch53 |
| D-128 | Sodiwin Kanban-in-CRM — specific sales-lead views | no sodiwin-in-CRM; clear layer of separation; sodiwin-as-blackbox matters here | locked | ch53 |
| D-129 | Twenty CRM + Pipedrive features to absorb | dedicated one-on-one with the sales lead; he is scoping his CRM of dreams; not a major blocker | open | ch53 |
| D-130 | Research-corpus ideas triage | ideas + admin feedback become a Sodimo SOUL.md; three-year plan for the principal; purge research | open | — |
| D-131 | Pre-opening restaurant detection | sales strategy to incorporate into the CRM | open | ch53 |
| D-132 | Halal certification decision | not my problem | out-of-scope | — |
| D-133 | Admin institutional-memory topic sessions | needs doing before I leave; not crucial to my success | open | — |
| D-134 | Per-repo Tuesday pass on all 28 repos | list of repos still being finalized; issues added to repos we created; list finalized today | locked | ch51 |
| D-135 | ”Deployed” formal definition adoption | semantics; not sending energy on that | open | ch28 |
| D-136 | Hypothesis-space dashboard owner | I teach the team to build dashboards; I don’t build them | locked | ch43 |
| D-137 | [political posture] | [redacted — commercial] | out-of-scope | — |
| D-138 | Weekly digest — existence + delivery | this is what the changelog and manual are for | locked | — |
| D-139 | Paperclip identity scope (Claude.ai users vs all authenticated users) | will explain clearly what I mean with paperclip | open | ch44 |
| D-140 | Sodiwin read-path vs write-path coexistence | writing to Sodiwin may be explored later; for now keep the black-box concept | locked | ch14, ch45 |
| D-141 | sodiwin-agent cloud vs local computer-use model | deferred; nice to have, not the cornerstone | out-of-scope | ch45 |
| D-142 | sodiwin-agent post-engagement maintainer | I build self-healing parts; tech choice (bootc + quadlets + backups + rollbacks) minimises risk | locked | ch35, ch45 |
| D-143 | sodiwin-agent nightly smoke-test harness | same as above — self-healing + robust tech minimises risk | locked | ch45 |
| D-144 | Bot I1-I4 meta-bot layer | resolve with paperclip | open | ch44 |
| D-145 | Per-secret storage remap | to be determined later; leanest possible setup | open | — |
| D-146 | DMARC ramp cadence | to be determined as we do the mail migration; add a note | open | ch34 |
| D-147 | Strato retention post-flip | Strato retention in place until I leave; detail; not thinking about 2 vs 4 weeks | locked | ch34 |
| D-148 | CNIL notice priority (Chapter 3 block vs Chapter 6 defer) | part of the email transition; easy | locked | ch34 |
| D-149 | Voicemail-to-order (J1) ship vs defer | extremely secondary; drop | out-of-scope | — |
| D-150 | Live voice-agent (J2) T+30 evaluation criterion | drop never-ship; add as option for later | out-of-scope | — |
| D-151 | DKIM signing — rspamd vs OpenDKIM | taken as part of the investigation for mail setup | open | ch34 |
| D-152 | MX topology active/active vs active/passive | problem for when I build email; CF Routing as fallback when harness is down is good | open | ch34 |
| D-153 | Twenty CRM adoption (supersedes D-010’s “custom TS-on-CF-Workers CRM”) | adopt Twenty v2.0.0 as pinned quadlet; Sodimo builds the MCP interaction surface, not the CRM itself; commodity tooling is adopt-as-pinned (Pivot 1) | locked | ch53 |
| D-154 | Twenty interaction: direct-Postgres (turbular) vs REST/GraphQL API | use Twenty’s REST + GraphQL API directly; turbular / direct-Postgres dropped — schema is a private implementation detail that would couple Sodimo to every Twenty migration; API is versioned, documented, webhook-capable | locked | ch53, ch38, ch42 |
| D-155 | Recouvrement split: Twenty-Workflow vs Worker-Claude | tier-1 (scheduled search + templated email) stays in Twenty Workflows; tier-2 (Claude-drafted letters, cross-system augmentation) runs on the Worker and pushes back via crm_add_activity / crm_upsert_contact | locked | ch53 |
| D-156 | Sodiwin push on Export Clients stage change: REST API vs FTP+CSV fallback | API-first; fall back to the Supli 2020 FTP+CSV pattern if Christian Semat confirms Sodiwin exposes no REST/GraphQL API; both routes write the run_ledger row before the Sodiwin handoff | open | ch53 |
| D-157 | Delta-alert definition and thresholds | open — Rani to confirm which of the five delta kinds (new SKU drop, volume change, cadence break, price anomaly, range change) matter and at what thresholds; blocks DeltaAlert custom-object rollout | open | ch53 |
| D-158 | Pipedrive decommission window | two-week parallel-running period with Pipedrive read-only after Twenty cutover; then decommission the €1,058/yr subscription; cutover gated on Rani confirming template re-authoring complete | locked | ch53 |
| D-160 | bootc over traditional Fedora provisioning | atomic swap + rollback is non-negotiable; one OS image rebuilt in CI beats Ansible drift across reprovisioning events | locked | ch35 |
| D-161 | chezmoi over Ansible for app-layer quadlets | Ansible is push-model and stateful; chezmoi is user-scope, declarative, already the leger-labs upstream pattern; one-line chezmoi apply after every quadlet edit | locked | ch35 |
| D-162 | App-layer in a separate sodimo/dotfiles repo vs single sodimo/harness monorepo | app versions churn weekly, OS versions monthly; splitting lets a Twenty bump land without rebuilding the bootc image; precedent: leger-labs already runs 12 quadlets this way | locked | ch35 |
| D-163 | Rename llm.network to sodimo.network (leger-labs lift adaptation) | leger-labs is a workstation pattern (“llm”); Sodimo is a production harness — the network name should reflect the product, not the upstream’s opinion | locked | ch35 |
| D-164 | WantedBy=scroll-session.target → WantedBy=multi-user.target (leger-labs lift adaptation) | leger-labs units target a workstation login session; Sodimo is headless-server — units must start at boot without a login, hence multi-user.target (or a sodimo.target that depends on it) | locked | ch35 |
| D-165 | Twenty MCP wrapper: turbular DB-introspection vs Twenty API direct (infra framing; see D-154 for product framing) | drop turbular; call the Twenty REST + GraphQL API directly — Twenty already exposes a typed API surface, turbular adds a second translation layer (schema-from-Postgres) that can drift from the UI contract | locked | ch38 |
| D-166 | llama-swap GPU backend on Strix Halo (ROCm vs Vulkan) | Vulkan — the :vulkan tag, not :rocm. gfx1151 on ROCm is upstream-bleeding-edge; RADV token-gen + AMDVLK long-context per-model is the production-stable posture, validated on the mecattaf asr-toolbox reference | locked | ch38 |
| D-167 | Image pinning discipline across every quadlet (no :latest in production) | pin every production image by digest; cockpit:latest is the one deliberate exception (slow cadence, strong compat story) and is called out as such. :main-stable, v2.0.0, 3.9.1 etc. are tag-pinned today; digest-pinning is the next hardening pass | locked | ch38 |
| D-168 | System-scope vs user-scope quadlet split rule | system-scope when the service must come up before login, owns a privileged daemon role, or the OS image would be incomplete without it (tailscaled, cloudflared, cockpit); everything else is user-scope in dotfiles — smaller blast radius, rootless by default | locked | ch35 |
| D-170 | MCP backend for CRM tools: hybrid (Postgres-direct via turbular + REST) vs Twenty API direct (MCP-surface framing; cross-ref D-154, D-165) | Twenty API direct is the whole surface — REST for reads/writes, GraphQL for crm_search, webhooks for inbound. Supersedes D-003’s implicit “Postgres-direct” framing. Twenty’s REST API is versioned independently of schema, which makes it the stable surface across minor-version bumps; Postgres-direct re-entangles the Worker with Twenty’s schema and defeats static-at-handoff | locked | ch42, ch53 |
| D-171 | Claude.ai bearer-token-after-OAuth bug class: workaround strategy | require a per-user bearer token on top of CF Access for Claude.ai clients; token lives in Vaultwarden, rotates per D-062. Paperclip and server-side callers use CF Access alone. Upstream bugs tracked at claude-code#46140 and claude-ai-mcp#49 — revisit when either closes. Not a CF-native workaround, a Sodimo-side one | locked | ch42 |
| D-172 | Cloudflare Portals / MCP gateway product | defer. None of the Cloudflare MCP product landscape (Managed MCP, SDK, Code Mode, Portals) is load-bearing for Sodimo day-one. Principle 1 says do not adopt a young upstream in the hot path. sodimo-core is a hand-written Worker with hand-written tool files under src/tools/; no Portal in front, no gateway, no aggregator. Revisit if the CF MCP category stabilizes AND a second MCP surface ever becomes justifiable | locked (defer) | ch42 |
| D-173 | MCP tool count for v1 | 13 tools: 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. Other tools (AR aging, depot stock, margin query, revenue query) are erp_read_* specializations — views on D1 on top of erp_read_orders/erp_read_accounts, not distinct tools. Resolves D-059 | locked | ch42 |
| D-174 | Ledger emission: tool-call vs sidecar | the Worker emits run_ledger rows as the first action of every tool-dispatch, not via a sidecar process or a Twenty-audit-log scrape. Pre-write before side-effect, post-write after. Fails closed if the ledger insert fails — the tool is not dispatched. A sidecar is a second moving part that can drift; Principle 2 invariant is surface-wide, not opportunistic. Twenty’s native audit log covers row-level changes; the ledger covers which run_id drove each write | locked | ch42, ch53 |
| D-178 | Principle 1 formalized: conceptual-fork-over-upstream with static-at-handoff classification test | Week 2 pivot: default is adopt-as-pinned-quadlet for commodity tooling (Twenty, OpenWebUI, Paperclip, Vaultwarden, Postfix, Dovecot, rspamd, Piler, future Prometheus/Grafana); default is conceptual fork for differentiating surface (sodimo-core Worker + MCP tool surface, run_ledger schema, Sodiwin ETL adapter, Sodiwin black-box FTP+CSV adapter, email-drain systemd service). Reverses week-1 framing which treated conceptual fork as universal default. Both defaults preserve pinned versions + aggressive scope-down + no continuous sync | locked | ch15, ch38, ch51 |
| D-179 | Principle 2 formalized: run_ledger append-only in D1, emitted by every AI invocation across every surface | Full schema locked: run_id, ts, surface, user_id, agent_id, model, provider, tokens_in, tokens_out, tokens_cached, latency_ms, outcome, cost_eur, cost_eur_if_cloud. The cost_eur_if_cloud counterfactual column carries the savings narrative — summed across surfaces, counterfactual minus real is the dashboard number. Emission discipline applies uniformly to interactive chat, scheduled agents, email auto-reply, MCP tool calls. Every new quadlet, MCP tool, or skill must answer the emission question before shipping | locked | ch15, ch36, ch42, ch60 |
| D-180 | Principle 3 supersedes earlier hybrid-MCP architecture: Cloudflare is the single MCP surface, on-prem reached via native UIs or pull-based wiring | three compounding reasons: (1) on-prem MCP inventory walk found nothing that must run on-prem for data-gravity, must be agent-callable (not human), and cannot be satisfied by a pull-queue; (2) Claude.ai OAuth handshake to self-hosted MCP servers behind tunnels drops the bearer token — recommended workaround (CF Access fronting) is equivalent to not self-hosting; (3) MCP gateway tooling on the 2026 market is too young to pin statically. Symmetric-application clause preserved: agents on the harness call the Worker email_send tool rather than shortcut to local sendmail. “Single MCP surface” is not “single HTTP surface” — Cloudflare Tunnel + Access for human browser traffic to on-prem services is legal and does not add an MCP endpoint | locked | ch15, ch31, ch33, ch42, ch44 |
| D-181 | kyuz0 amd-strix-halo-toolboxes adopted as the canonical Strix-Halo llama.cpp runtime reference | pin kyuz0 upstream SHA 1421e8706020e8d7e797f71b9f28cd3072e7f868; llama-swap model cmd: flag sets are derived verbatim from kyuz0’s Dockerfiles and README; resync runbook lives at docs/resync-runbook.md in sodimo/dotfiles. Replaces the generic leger-labs llama-swap config with a Strix-Halo-specific, battle-tested reference path — removes a class of “does it work on this silicon?” questions | locked | ch38, ch41 |
| D-182 | llama-swap gateway runs raw /app/llama-server (not ramalama) | discovered during kyuz0 integration that ghcr.io/mostlygeek/llama-swap:vulkan does NOT ship a ramalama binary — the prior ramalama --runtime llama.cpp run cmd: blocks would have failed at first model load. Invoking /app/llama-server directly matches kyuz0’s toolbox invocation pattern and removes a latent-bug class | locked | ch38, ch41 |
| D-183 | proxy: target in llama-swap.yaml uses http://127.0.0.1:NNNN, never container-self DNS | llama-swap and the spawned llama-server share a netns; hitting the container-self DNS name (http://llama-swap:NNNN) forces a netavark round-trip that produces 502s under the adguard DNAT hijack after the first request. Loopback form was verified fixed in the Wednesday-evening smoke test (5.4 s cold, 0.08 s warm). Applies to every proxy: URL in the file | locked | ch38, ch41 |
| D-184 | local-heavy alias = gpt-oss-120b (unsloth UD-Q8_K_XL, 2-shard GGUF, 65k ctx, reasoning_effort=high) | supersedes the earlier gpt-oss-20b wiring in litellm.yaml. gpt-oss-120b at Q8 fits the unified-memory budget of the Framework Desktop 128 GB and is the weight class that makes the “local inference is first-class” pitch credible to Michel. First runtime validation deferred to prod harness (100 GB download + kyuz0 kernel cmdline gate) | locked | ch41 |
| D-185 | Rootless podman 5.8.2 group-posture: GroupAdd=video only | dropped GroupAdd=keep-groups and PodmanArgs=--group-add=render from llama-swap.container. Under rootless podman 5.8.2 the combination is incompatible and blocks llama-swap from reaching Active. video alone is sufficient for RADV on gfx1151. /dev/kfd + HSA_OVERRIDE_GFX_VERSION=11.0.0 kept for ROCm forward-compat as vestige candidates (see sodimo/dotfiles#14) | locked | ch38 |
| D-186 | Container image references must be fully-qualified | twenty.container + twenty-worker.container pinned to docker.io/twentycrm/twenty:v2.0.0. Short-name policy fails under non-TTY auto-update paths (podman auto-update, bootc first-boot) because there is no prompt to disambiguate the registry. Applies to every quadlet; digest-pinning is the follow-up hardening pass (D-167) | locked | ch38 |
| D-187 | Sodimo-built derived images live under docker/<name>/Dockerfile, tagged ghcr.io/sodimo/<name>:v<upstream>-pi<resolved> | first instance is docker/paperclip/Dockerfile layering @mariozechner/pi@latest on ghcr.io/paperclipai/paperclip:sha-b8725c5; local build landed as ghcr.io/sodimo/paperclip:v2026.416.0-pi0.x.y (2.55 GB). Establishes the pattern for any future Sodimo-built image: top-level docker/ dir, upstream-SHA-pinned base, calver + resolved-dep tag. GHA publish path + org write:packages tracked in sodimo/dotfiles#12 | locked | ch38, ch44 |
| D-188 | Default LLM routing is local-first; cloud (Claude Opus via cloud-heavy alias) is opt-in escalation per-invocation, not the default | Token-savings discipline under Principle 2: cost_eur is 0 for every local run; cost_eur_if_cloud records the counterfactual. Starting at Opus and falling back to local inverts the economics — the savings number becomes a claim about an exception case rather than the baseline. Four escalation triggers are enumerated in ch41; nothing escalates silently without an explicit escalate: "cloud-heavy" flag in the tool invocation. gpt-oss-120b at UD-Q8_K_XL on 128 GB Strix Halo has closed the capability gap for a large share of work as of 2026-04-22 kyuz0 integration | locked | ch15, ch41 |
| D-189 | Local-AI usage counter is ledger-backed via Worker ledger_write → run_ledger; Prometheus/Grafana not activated at v1 | every llama-swap run emits a run_ledger row with cost_eur=0 and cost_eur_if_cloud set to the counterfactual cloud price. SUM(cost_eur_if_cloud) - SUM(cost_eur) across all local rows is the headline savings number — one SQL query, no separate metric pipeline. A Grafana-backed dashboard would require a D1-to-Prometheus bridge with no additional analytical value at v1. Surface: cumulative counter on the launchpad tile (ch61) + rolling 7-day chart on a progress-adjacent page (exact placement open). Grafana/Prometheus revisit deferred as Pivot 5a | locked | ch41, ch42, ch61 |
How to use this log
The table above is the authoritative decision index; the manual chapters are where those decisions land. If a chapter seems to contradict the table, the table wins until the chapter is updated to match. If a decision is marked open, it has not yet been resolved — the related chapter (where one exists) describes the current best understanding, and the resolution lives with the work identified in the annotation.
Three reading patterns are worth knowing:
- “Why is it like this?” Start with the related chapter, find the D-number it references, read the row here, and the source ANNOTATED file at
~/sodimo/secondweek/if you need the longer context. - “What is still open?” Filter the Status column to
open. Those are the live questions. - “What got dropped and why?” Filter to
out-of-scope. These are the scope trims that kept the engagement shippable; the annotation explains the reason.