¶ ▶️ START HERE — NEXT SESSION (updated 27 Jul 2026, evening — state-store is the primary day-to-day tracker, 89 items)
📌 STABLE CONTENT MOVED: Standing rules + documentation/single-writer rule + session hygiene + operational procedure + glossary + stable REFERENCE facts live in session-protocol (wiki id=80 / "SP") — read it FIRST, then this page for volatile state. Single-writer unchanged: id=80 = CC (via repo); id=15 ("SH") = chat only.
Boot line (next session, refreshed 27 Jul evening — supersedes all prior boot lines): "Xzopia MSP, continuing — read SP (wiki id=80) FIRST, then SH (id=15) in full, then check XS (xzopia-state MCP) TO_COMMIT/AGENDA/SIMON_ACTIONS. XS is the live working task list (89 items) and day-to-day status lives THERE, not in fresh SH prose. Glossary: SH=Session Handoff(id=15), SP=Session Protocol(id=80), XS=Xzopia-State(the store). ✅ ni4kids cutover is COMPLETE — that thread is closed bar follow-ups. 🔑 FIRST: Simon must vault the new ni4kids DB backup-user password (on-box only, CC cannot do it). 🟠 THEN: the Linux container test (XS 83), test (a) second half. 🔴 TWO STANDING RULES STILL STAGED FOR CC TO PROMOTE INTO SP — item 75 (CC↔chat↔XS channel responsibilities — XS is CC's DEFAULT WRITE TARGET for new findings/blockers/status, written as it goes without being asked; cc-reports remain the permanent audit trail; id=15 is volatile session-level state only; the UPDATES-block mechanism narrows to id=15-worthy changes only); item 76 (session hygiene — mid-session RECALL CHECK: ask the agent to restate a specific fact AND its source before anything consequential; Sourced-Facts binds chat as hard as CC; no numeric confidence scores). ✅ item 71 (Secrets & Credential Handling) is DONE — commit 6dfc73a pushed. Do NOT re-stage it.
✅ ni4kids.com CUTOVER COMPLETE 27 Jul (XS 89, cc-report 2026-07-27-ni4kids-cutover-complete; recon in XS 85, pre-cutover fixes in XS 86).
DNS flipped ~14:55 UTC; site LIVE on 194.164.125.9 with current files, current DB and a valid Let's Encrypt cert whose SAN covers both apex and
www(valid 27 Jul → 25 Oct 2026). DB parity verified — posts 10,964 both, users 10,049 both, comments 0 both, newest post timestamp identical on both boxes;siteurl/homebothhttps://ni4kids.com, so no search-replace needed. End-to-end green: apex /www/wp-admin/ deep permalink all 200, single-hopwww→apex with no redirect loop, cert validates cleanly with no TLS bypass, scotland4kids.com unaffected. Load on new 0.51/0.78/0.95 on 4 vCPU vs old's sustained 6.9 on 8 vCPU — the hardware move is a substantial win.
Backup now LIVE on new — restic 0.17.3 → B2, dedicated least-privilege DB user (read verified, write correctly denied), credentials0600 root:root, repository path reused so new inherits the full snapshot history, first snapshota5e99d7c9.718 GiB,restic checkclean, nightly 02:00 verified by crontab read-back — and explicitly tested under a cron-like minimal PATH, which is the exact failure mode that silently killed the rsync for six days.
Old wound down but NOT decommissioned — root cron entries commented (reversible, backed up first, 0 active confirmed by read-back); system cron deliberately left alone (logrotate/antivirus — disabling them would be harmful); migration rsync cron on new disabled. Old remains up, serving familymediagroup.co.uk, and is the rollback.
⚠️ THREE RUNBOOK PREMISES WERE FALSE AND WERE FIXED EN ROUTE: the nightly rsync had been broken since 21 Jul (missingknown_hostsin the domain homedir — Virtualmin housekeeping, exactly as BookStack id=69 warns — plusvirtualminabsent from cron's PATH); the rsync silently clobberedwp-config.php(now fixed structurally); and restic on old was never scheduled at all — re-confirmed, the script and credentials exist but the cron entry never did.
✅ The SAN/wwwtiming risk chat flagged as the headline danger never materialised —wwwis a CNAME to the apex, so both names moved in the same instant. CC also ran a STAGING issuance first (Virtualmin's ACME supports it; staging failures don't count against production rate limits), proving the whole flow at zero risk before firing the real request — which defused the "5 failed validations per hostname per hour" hazard entirely. Worth keeping as standard practice for every future issuance.
⚠️ Gotcha for the fleet: Virtualmin's cert command automatically restarts Webmin, Usermin and the mail server. Expected, but material on a box carrying three client sites. All re-verified active.
⏳ BaaS M365 — the architecture track. Read XS 74 (the decision), 78 (Pattern-B proof), 79 (credential residency), 80 (licensing), 82 (Simon's hands-on validation), 83 (container build spec), 81 (commercial unknowns + billing experiment) before touching anything BaaS. Pattern B is the direction, is unblocked, and the remaining work is engineering, not analysis.
🟢 25 JUL — SIMON'S HANDS-ON COMET TEST (XS 82). Direct human observation on live infrastructure. Best evidence on this track so far; confirms three previously-inferred findings and part-closes test (a).
1. STORAGE TEMPLATES / DIRECT-TO-CLOUD B2 — the important one, previously under-weighted. Simon configured B2 as a Storage Template (requestable vault) rather than hand-building a Storage Role per client. One bucket configured once with the master key; assigning a vault to a customer creates a per-vault sub-directory with access keys scoped to that sub-directory only. VENDOR-CONFIRMED (docs.cometbackup.com): Comet "generate[s] unique sub-directory level access keys per Storage Vault to minimize the impact this could have if the credentials are acquired", and "the access keys granted will only have permissions for its own subdirectory and won't be able to access other accounts data." One template can create thousands of vaults; direct-to-cloud with templates is Comet's recommended method, and it removes all manual per-client bucket work.
🔴 SECURITY CONSEQUENCE + HARD RUNBOOK RULE. Under direct-to-cloud the DEVICE talks straight to B2, so it must hold a storage credential. The open worry was that it holds the MASTER key. Via a Storage Template it does not — only its own scoped key. BUT that is only true via the template. Hand-configure a B2 vault with master credentials and the device gets the whole bucket — every client's data. ⇒ STANDING RULE: BaaS client vaults are ALWAYS provisioned via Storage Template. NEVER hand-configure a per-client B2 Storage Vault with master credentials. No exceptions.
🛡️ Second isolation layer, already in place: Comet can use B2 keys carryinghideFilebut NOTdeleteFile, "preventing a compromised key from being used to delete backed up data."xzopia-baasis already on hide-files. With per-vault scoped keys this is a materially better delete-isolation story than was on record when Pattern A was rejected. ⚠️ It does NOT rehabilitate Pattern A — scoped storage keys defend against a leaked STORAGE credential and touch neither ground Simon rejected A on. Pattern A stays rejected. 📌 Adjacent: this proves Comet mints scoped B2 application keys programmatically, which sits next to the "Object Lock via B2 non-achievable — B2 lacks the IAM API" conclusion (item 74 step 2, still OPEN) and may deserve re-testing rather than re-assertion.
2. THE WINDOWS AGENT FLOW, END TO END. On the TRMM Windows VM: agent installed, logged in asxzopiatestbackup(accountXzopiaTest), device registered on login alone, agent closed. Storage vault auto-provisioned from the template with zero manual work — confirmsAutoStorageTemplateGUID=server_controlled. Custom GFS policy (14d/8w/12m) applied and behaved as expected.
3. ONE DEVICE ⇒ THE WHOLE TENANT. Protected Item added from the server side; Azure AD registration completed interactively using Simon's own Global Admin account; M365 selected, explicitly no files or folders. The account picker listed EVERY M365 account in the tenant, not just the consenting user's. One selected for the test; schedule 2300. ⇒ A single registered device can back up every account in a tenant. Device count is driven ENTIRELY by client-isolation policy — never by tenant size, never by cost. ⇒ Independently corroborates the Pattern C kill (XS 79): that picker enumerating every mailbox is the tenant-wide blast radius made visible.
🟡 EFFECT ON TEST (a): does NOT close it — Simon's observation is Windows, the open question is Linux. But it eliminates a branch: "Microsoft Office 365" demonstrably appears in the type list for a registered device and the server-side add-flow works as designed. Narrowed to the single question of whether the type list is PLATFORM-GATED.
⛔ TEST (a), SECOND HALF — STILL OPEN, BUT NARROWED AND CHEAP. Register a Linux client to a throwaway account (path fully proven and scripted, ~2 min), open Add Protected Item, look at the dropdown, close without saving. That is the entire remaining question. No Azure work needed, and creating no Protected Item means no billing. (The earlier blocker — CC's own auto-approval classifier blocking dispatcher/request-office365-accounts — is bypassed entirely by this route.)
🐧 PATTERN B CONTAINER BUILD SPEC (XS 83) — consolidated so the image can be built without re-deriving it.
✅ THE GUI IS NOT REQUIRED. Simon saw the Windows desktop agent's interactive login; the Linux client does not need it. Headless registration is PROVEN against the live server:
OVERRIDE_INSTALL_SERVER=https://backup.xzopiasecure.com+backup-tool login prompt, username and password on stdin →Registering this device... Logged in., server-sidelinuxgeneric/amd64. Background service:backup-tool cmd -ValidateConfig=false -Action=delegate-server. ⛔ Do NOT design onboarding around API lobby-assign —installation/dispatch/register-devicereturnedConnection ID invalidagainst a live connection. UNPROVEN.
YES — one username/password per container, and that is inherent. Pattern B is one Comet account per client, so N containers = N account credentials. That is a secrets-management problem, not a UI problem: Vaultwarden → injected at container start, never baked into the image, never in the compose file, never printed. Needs designing before any fleet build.
HARD IMAGE REQUIREMENTS (all proven): (1)/var/lib/dbus/machine-idmust exist and must be GIVEN a distinct value per container — sole input to device-id;/etc/machine-idhas no effect when the dbus file exists; device-id is derived and deterministic across rebuilds, so one will not appear on its own. (2) Both identity files absent ⇒ the service refuses to start — ascratchimage WILL fail. (3)/usr/bin/unamemust be present plus its loader/libc — the client fork/execs it, and its absence gives a misleadingHTTP 400 … fork/exec /usr/bin/unamethat does not name the real cause. (4) The client is a statically linked Go binary (backup-tool.x86_64, fromopt/cometd/client/nix.datin the .deb) and ran fine in unprivileged user+mount namespaces — so a genuinely cut-down image is right, just not empty: minimal base with shell, libc and coreutils. (5) Config path follows$HOME($HOME/.config/backup-tool/config.dat), not a fixed/etc/backup-tool— set$HOMEdeliberately per container. ⚠️ Device identity is a plain file the container controls — trivially settable and re-bindable, so it carries only limited trust as a separation boundary.
💷 On "no licensing cost for Linux": correct on the OS — no Windows licence per client instance, a real saving versus VM-per-client Windows. But it does not change the Comet cost, which is per protected M365 account plus a platform-independent endpoint charge. Linux saves OS licensing and RAM, not Comet licensing.
🔴 STILL OPEN before a fleet build: (1) concurrent multi-ACCOUNT operation on one host — 78 proved N distinct identities, NOT N clients on N different Comet accounts backing up concurrently. This is the next decisive test. (2) Per-instance footprint — unknown; S7 cannot size it (WSL2 distros share the Windows host allocation, so any figure will be wildly optimistic); needs a capped.wslconfigor a real VM. (3) Must containers run CONTINUOUSLY, or can they be started on a stagger around their backup window? The client executes the schedule, so it must be up in its window. UNTESTED, and it is the difference between a modest host and a large one. Also untested: whether Comet re-runs missed backups when a device reappears. (4) Credential injection design. (5) Vendor support posture — containerising remains undocumented and unsupported.
💷 COMET COMMERCIAL POSITION (XS 81) — RESOLUTION DATE SET: 14 AUGUST 2026. DECISION: DO NOT SEND THE VENDOR ENQUIRY; WAIT FOR THE FIRST INVOICE.
Xzopia is on the Comet GROWTH plan: £49/month + £2 per endpoint (Simon, from his own account). Trial first, then Growth signed. No invoice issued yet; first billing date 14 Aug 2026. ⛔ "read your last invoice" is VOID — recorded so it is not re-suggested.
Billing unit = "device agent = endpoint" (Simon's formulation, and correct). Matches Comet's documented model: the charge attaches to the device, gated on it having Protected Items — "devices will not attract fees or charges unless the device has Protected Items or performs a backup." The unit is not in doubt. What is unproven is whether the M365 product type carries the base charge at all — Comet demonstrably waives base charges per product type (the $2 base was dropped for Hyper-V, Feb 2024, booster-only). ⇒ If it applies: ~70 containers ≈ £140/month ≈ £2 per client per month. Noise. Does not touch the architecture; Pattern B stands either way.
🧪 THE NATURAL EXPERIMENT — ALREADY BUILT, PROTECT IT.XzopiaTestis exactly the configuration the open questions ask about: one registered device, Protected Item = M365 ONLY, no files or folders, one protected account, 2300 schedule. The 14 Aug invoice answers, with no vendor correspondence: whether an M365-only device attracts the £2 endpoint charge; what the per-protected-M365-account rate actually is on Growth (still the dominant cost of the whole service, by an order of magnitude); whether £49 includes an endpoint allowance; whether billing is £ or $ (published list is USD at the same numerals — ~25%/line); direct or via distributor. 🔴 KEEPXzopiaTestRUNNING THROUGH TO 14 AUG. Add nothing to that device or account.
🔴 BILLING-WINDOW HYGIENE, 25 JUL → 14 AUG — applies to ALL BaaS work. Endpoint charges accrue across the whole installation, so every device registered in this window lands on the invoice. (1) Never register test devices againstXzopiaTest— use azz-throwaway. (2) A throwaway with a device but no Protected Items should not bill — ⚠️ BUT Comet warns that if the user-profile Policy auto-adds default Protected Items, a new device DOES attract charges. Xzopia's custom GFS policy is exactly such a policy ⇒ do NOT assign it to throwaway test accounts. Bare account, no policy, no protected item. (3) Delete test accounts and devices in-session. (4) If containers are spun up for the concurrency test, keep a dated log of what existed and when so the invoice stays reconcilable.
Residual the invoice will NOT answer, if either becomes pressing: per-client minimums that make a 3-5 mailbox client uneconomic (price-card question, not a build question); and whether a containerised client is a SUPPORTED deployment (support-posture, Simon's call). Contact if sending:partners@cometbackup.com, or a ticket at support.cometbackup.com for a numbered trail. Procurement route (direct vs distributor) still unestablished — the invoice answers that too.
📊 STILL THE BIGGEST GAP, INDEPENDENT OF COMET: the total protected-M365-account count across all ~70 client tenants. ⚠️ NOT the licensed-seat count — it includes unlicensed Azure AD accounts, shared mailboxes and SharePoint sites, so it will be materially higher than the seats Xzopia bills M365 licensing on. Sourceable from CIPP / M365 Admin. Until that number exists any BaaS price card is guesswork. Pairs with the 14 Aug per-account rate to give the first real cost model.
🟠 XS ITEM 80 — COMET LICENSING RESEARCH (chat, 25 Jul, from Comet's published pages; superseded on the plan/rate layer by XS 81 above). Model = base per-endpoint charge + per-feature boosters, base list "Starting at $2 / endpoint / month". The O365 workload bills PER PROTECTED M365 ACCOUNT — list $1.50 — where "account" = any Azure AD account (licensed or not) plus shared mailboxes and SharePoint sites; mailbox + SharePoint + OneDrive + Teams files all in that one charge. One device holding N Protected Items for N tenants does not multiply the device charge. Devices with no Protected Items attract no charge — ⚠️ unless a Policy auto-adds default Protected Items. Pricing-model consequence: the per-protected-account rate is the floor under Xzopia's BaaS client pricing, and client billable counts will exceed licensed-user counts. Item 70's quota reads (QuotaOffice365ProtectedAccounts=0, MaximumDevices=0) remain ENFORCEMENT settings, not billing answers — but the linkage is now clear: that quota exists to cap surprise billing when a tenant grows, so it should be SET per client, not left at 0, once BaaS is live.
🟢 WEBOTTLE WENT LIVE ON COMET 28 Jul — FIRST REAL BaaS CLIENT (XS 94). The "dormant" state below is SUPERSEDED. Simon built a Windows VM, installed the agent, registered a device to webottle, completed the Azure AD consent in WeBottle's tenant, and configured an M365 Protected Item covering 18 selected accounts plus SharePoint sites and Teams folders. First job RUNNING as of 28 Jul. ⚠️ ALL SIMON-REPORTED IN CHAT, NOT READ FROM S3 — unverified. 🔴 Open: Status 5000 not yet observed ("running" is not "succeeded" — see the XS 84 zero-byte 7002 failure mode); the ~20 unselected accounts may include shared mailboxes that are therefore NOT backed up; protected-account count and storage-vault provenance must come from the server, not recollection. 🟠 The VM must stay powered on (the client executes the schedule) and now holds a tenant-wide O365 credential on a desktop — acceptable for a first seed, wrong as a permanent home.
📄 VPD EVIDENCE ANNEX DRAFTED 29 Jul (XS 95). Webottle is applying for HMRC Vaping Products Duty + Vaping Duty Stamps approval — that is what the documentation pressure is. v0.1 Word draft produced in chat (5pp): scope in/out, backup control, GFS retention, 5 screenshot slots, restore-verification section, sign-off. Chris Bell confirmed the backup is a new system brought in specifically for the HMRC requirement — stated plainly in the Annex, which turns the recent commencement date from a weakness into evidence of preparation. 🔴 KEY FINDING, generalises to every client pack: HMRC VPD requires records kept 6 YEARS; Xzopia GFS retention is ~12 MONTHS. A backup CANNOT evidence multi-year statutory retention — it is a recovery control, not a retention control. Resolved for Webottle (VPD records live outside M365; 6-year obligation met by the live systems), but the distinction must be explicit in all future client documents. 🔴 Four items outstanding before issue: Status 5000 observed · protected-account count read from the console · shared-mailbox coverage check · restore test performed. ⚠️ The .docx is sandbox-only and must be downloaded — BookStack cannot store binaries and Nextcloud is not built, so there is still no system of record for signed client deliverables.
🔴 WEBOTTLE COMMERCIAL RECONCILIATION — FOUR ITEMS, ALL BLOCKING THE INVOICE AND THE ANNEX (XS 97, found 29 Jul from BookStack). Quoted figure is £190.00/mo ex VAT = 38 users × £5.00 (page 41, reissued 24 Jul). (1) Quoted 38, provisioned 18 — £100/mo gap, and a scope reduction Chris Bell has not been told about. (2) Proposal is UNSIGNED and its own provenance note says "do not provision/bill until signed" — provisioning happened 28 Jul. (3) SharePoint/Teams delivered but NOT PRICED — the internal Storage Tiering policy (page 28) states shared document stores are "not covered by the mailbox rate at all" and gives them a separate £25/mo base incl. 350GB tier; the proposal has no such line. Generalises — sweep every BaaS proposal and template 60. (4) 🔴 XS 81's installation log is now STALE — it says nothing has been added since 25 Jul, but WeBottle added +1 endpoint and +18 protected accounts on 28 Jul, so the 14 Aug denominator is 20 accounts / 2 endpoints, not 2 / 1. 📌 The invoice is itself part of the HMRC evidence (page 40 excluded backup evidence precisely because no backup line appeared on INV-11712) — so billing accuracy is a precondition of the Annex being safe to issue, not a separate task.
WeBottle state (as built 24-25 Jul, pre-go-live): pre-change backup taken, Comet user webottle created, GFS policy assigned (PolicyID=2516ced7…, 14d/8w/12m, retention enforced), account dormant (no billing), password in Vaultwarden with the on-box file deleted. Storage vault auto-provisions at first backup (AutoStorageTemplateGUID=server_controlled). Tenant ID 46732e32-ad13-4e63-bd48-eb11066a24e2 (webottle.co.uk, ITFlow client #3). AUTH PATH: CIPP-SAM is delegated/GDAP and is the WRONG model — must NOT be reused. Comet O365 needs APP-ONLY auth, application permissions admin-consented in the client tenant, via Comet's built-in Azure AD auto-registration wizard (launches from INSIDE the Protected Item dialog: Protected Items → Add → "Microsoft Office 365" → "Azure Active Directory" button — not in the account or admin area). Simon's 25 Jul test walked this exact path successfully on Xzopia's own tenant — the wizard works as documented. Runbook corrected, commit ba4be9b. ✅ API AUTH: the Comet admin API needs an explicit AuthType=Password form field alongside username+password — to be recorded in scripts/server3/api-admin.env.template.
🔴 ORIGINAL BLOCKER, DIAGNOSED AND NOW CONFIRMED BY HAND: there is NO click-path that creates an O365 Protected Item on an account with no device — a registered device is a HARD PREREQUISITE. Comet's O365 is NOT agentless; a Protected Item binds to a device. TERMINOLOGY TRAP: "cloud-to-cloud" describes the DATA PATH (M365 → vault, never touching on-prem storage), NOT agentlessness — the agent is the compute proxy making the Graph calls. Chat and CC both misread this on 24 Jul; Simon's 25 Jul test confirmed the corrected reading directly. Candidate LESSON: verify the COMPUTE model, not just the data path. Ground truth (S3): cometd 26.7.0.
⛔ THE a/b/c "where does the proxy device live" FRAMING IS SUPERSEDED — DO NOT USE IT.
🔴 THE REAL DECISION IS CLIENT ISOLATION — SIMON DECIDED 25 Jul: PATTERN A IS REJECTED, on security not cost. Pattern A (all clients under one Comet account) means account-level encryption keys — one password unlocks every client's mail — AND no delete-isolation, since devices in an account can restore and delete each other's data. One compromised credential could destroy every client's backups simultaneously, the exact event backup exists to survive. Also indefensible under Art.32 with ~70 separate data controllers on executed DPAs. Do NOT reoffer Pattern A as a fallback without flagging it as an explicitly accepted risk needing fresh sign-off. (Two reinforcements 25 Jul: the licensing answer removes any cost advantage; and the storage-template finding, while improving storage-layer isolation, touches neither ground for rejection.)
✅ PATTERN B (one Comet account per client) — MAKE-OR-BREAK BLOCKER CLEARED (XS 78, report id=165, commit de7488f). The client derives device-id from /var/lib/dbus/machine-id and does NOT read /sys/class/dmi/id/product_uuid at all — proven over 8 controlled runs including a negative control. ⇒ N containers on one host, each given a distinct dbus machine-id, register as N distinct devices. Full build detail in the container-spec block above. Amendments: the -config flag is a confirmed FALSE POSITIVE; the real config path follows $HOME.
✅ TEST (a), FIRST HALF — HEADLESS LINUX REGISTRATION PROVEN LIVE. Client registered to throwaway zz-cctest on S3 as linuxgeneric/amd64 v26.7.0; account and device created and DELETED in-session; deletion verified by re-read (404 User not found, installation/list-active empty); webottle verified untouched. All six of Simon's conditions met with evidence (pre-change backup /var/backups/cometd/pre-zzcctest-20260725-114332Z.tgz, 939,786 bytes, 42 entries). Password generated on-box via openssl rand, never printed, destroyed. ⚠️ An early probe printed PolicyIDs: None for webottle — CC reading the wrong field name (PolicyIDs vs PolicyID), NOT a change. Do not re-investigate.
⚠️ TEST-VALIDITY LESSON (candidate, promote to LESSONS): WSL2 exposes no /sys/class/dmi at all. Run the obvious way — two containers, compare device-ids — distinct ids would have "passed" while proving nothing, because the fallback source the test existed to rule out was ABSENT rather than ignored. Supplying an identical synthetic product_uuid is what made it decisive. Generalises: when a test's purpose is to rule out fallback source X, first verify X is actually present in the test environment. Related: engine IDENTIFIER strings are platform-independent (the Linux binary also carries engine1/windisk and engine1/vsswriter, both Windows-only) — but IMPLEMENTATION types are build-tag gated and the asymmetry is clean: VSS/disk-image contribute none, O365 contributes ~27.
🔑 CREDENTIAL RESIDENCY ANSWERED (XS 79) — the O365 app-only credential is held BY THE DEVICE, not just server-side. The Linux client deserializes Office365Credential{AppID,TenantID,Secret,AppCert} and acquires its own OAuth token, so it must hold the secret at job time. ⇒ THIS RULES OUT PATTERN C. A per-client on-site device would put a live tenant-wide credential (full_access_as_app across every mailbox, Sites.FullControl.All, Files.ReadWrite.All, Mail.ReadWrite, Directory.Read.All) on the least physically-controlled hardware in the estate, outside Xzopia's perimeter. Do not adopt Pattern C on the assumption the credential stays server-side; it does not. Pattern B keeps it on Xzopia-controlled hardened infrastructure, so Pattern B wins on BOTH isolation and credential custody.
⚠️ PATTERN B RESIDUAL RISKS — accepted, not eliminated: (1) container separation is NOT VM separation — one host holds every client's Comet account credential, every client's tenant-wide M365 credential, and every client's scoped storage key; root compromise or container escape reaches all of them. Still meaningfully better than Pattern A (which falls to a single leaked credential) but not VM-per-client isolation. Middle ground worth costing: a handful of VMs each hosting a group of client containers — caps blast radius without ~70 VMs. Not yet evaluated. (2) containerising the Comet client is undocumented and unsupported by the vendor — today's device-id behaviour is an implementation detail, not a contract. (3) the immutability gap. These — not cost — are the live risks on Pattern B.
✅ CLAUDE.md S7 SPEC DRIFT — FIXED, commit de7488f. S7 reports 32 cores / 47 GB / 1 TB; WSL2 distros get no per-distro cap. S7 CANNOT produce a realistic per-instance footprint. ✅ S7 REACHABLE AGAIN 28 Jul — the recurring WSLInterop binfmt fault is permanently fixed via /etc/binfmt.d/WSLInterop.conf (XS 92). This unblocks the container/concurrency track only; it does not change the sizing limitation above.
📄 RUNBOOK CORRECTION OWED: docs/runbooks/baas-client-onboarding.md still says M365 is pulled "agentlessly" — wrong. Fix alongside the device-prerequisite correction; add that the device holds the credential; add the licensing facts (per-protected-account floor, shared-mailbox/shared-site multiplier, SET the Protected-Accounts quota per client rather than leaving it at 0); and add the Storage Template rule — always via template, never a hand-configured B2 vault with master credentials.
🆕 XS item 77 — M365 BACKUP VENDOR EVALUATION. Stays a BENCHMARK, not the live path. The reason to keep it is the unsupported-containerisation risk (XS 78), not cost. Shortlist led by MSP360 Managed Backup for M365 (BYOC, native Backblaze B2, multi-tenant, white-label); also AvePoint, N-able Cove, Dropsuite, Keepit, Barracuda, Redstor. Exclude NinjaOne Backup. ⚠️ NO per-seat pricing sourced for ANY of these — channel-quoted only; do not assume a £/seat figure. Check Giacom's catalogue FIRST — UNKNOWN, not checked.
Still open for Simon: whether WeBottle goes live on Comet as a one-off ahead of the wider decision (not blocked — webottle is already built per-client-account).
🆕 CIPP on v10.7.0 'The Frisky Whiskey' (24 Jul, item 13) — repo moved orgs to github.com/CyberDrain/CIPP. CIPP is migrating self-hosted from Azure Functions+Static Web Apps to Azure Web Apps; new self-hosted deployments use the new architecture from 27 Jul, no migration script yet for existing ones (destructive when it lands). Xzopia's CIPP deployment is still PARKED pre-build (blocked on the browser-CORS bug) — CC should re-check the self-hosting docs for the new Web Apps path BEFORE resuming the old Functions+SWA runbook (docs/cipp-azure-deployment). Also verify directly whether v10.7.0 fixes the ListTenants/enrichment-step schema defect (BUG #2) — changelog doesn't mention it, don't trust silence either way. ✅ MeshCentral (items 31/72) — FULLY RESOLVED. Residual: TRMM's 'MeshCmd' shortcut falsely reports 2FA not enabled — genuine TRMM bug, low priority, may be fixed by the pending version bump (item 66). Other open: MFA rollout 6/7 — only FusionPBX left, needs CC's read-only check first (item 73); ITFlow MFA separately blocked (item 68); familymediagroup.co.uk — now a DECOMMISSION BLOCKER, see item 67; Nextcloud-on-S6 blocked on Simon (item 54); code-signing cert (items 63/65); stack-wide version audit (item 66); Twenty CRM transient load issue (item 69); S1-EDR fleet upgrade (item 57) + drift root cause (item 56); branding/website tracking + Claude Design access undecided (items 59/60, not CC-actionable). Stephen doing BSA offboarding. CONFIRMED-only commits."
¶ 🔜 SIMON — PENDING (BaaS thread, overtaken by the cutover; still owed)
- Check the
XzopiaTest2300 job COMPLETED — job status, data landed in the vault, no errors. Registration working and a backup completing are different claims. Several nights of runs to look at now.- Linux container, test (a) second half. Register a Linux client to a
zz-throwaway (NOTXzopiaTest, and no GFS policy assigned — see billing-window hygiene). Open Add Protected Item, look at the type list, close without saving. Five minutes. Delete the throwaway in-session.- Only then the real gate: two containers on two different Comet accounts, running concurrently (XS 83 open item 1).
¶ 📤 STAGED CC PROMPT #2 — 25 Jul 2026, promotion / reconcile session (NOT YET RUN)
Staged by chat per the standing "prompts staged before pasting to CC" discipline. Documentation and repo work only — no container work, no live-box changes, no S3 mutations. CC: promote to a chat-prompt page at
xzopia/chat-prompts/2026-07-25-promotion-reconcile, then collapse this block to a one-line pointer. Verbatim text:Xzopia MSP, fresh CC session — read SP (wiki id=80) FIRST, then SH (id=15) IN FULL, then check XS (xzopia-state MCP) TO_COMMIT / AGENDA / SIMON_ACTIONS. THIS SESSION IS PROMOTION / RECONCILE WORK ONLY. No container work, no live-box changes, no S3 mutations. XS items 80-89 were written by chat, Simon and CC on 25-27 Jul — read them, do not redo that work. DO ALL OF THE FOLLOWING, commit and push: 1. XS item 75 — CC<->chat<->XS channel responsibilities. Promote into SP (wiki id=80) + the repo as a standing rule. This one first: until it lands, every fresh CC session has to be told by hand to read XS, because the rule saying so only exists inside XS. 2. XS item 76 — session hygiene, mid-session recall check. Promote into SP (id=80) + repo alongside 75. 3. Six candidate LESSONS -> LESSONS_LEARNED (detail in SH and XS 70/78/79/82/85/89): a. "Cloud-to-cloud" is not "agentless" — verify the COMPUTE model, not just the data path. b. When a test exists to rule out fallback source X, first verify X is actually present in the test environment. (WSL2 exposes no /sys/class/dmi; the naive device-id test would have passed while proving nothing.) c. Identifier strings prove nothing; implementations do. The Linux Comet client carries engine1/windisk and engine1/vsswriter identifiers despite both being Windows-only. d. A half-hour of hands-on operation of the actual product settled in one sitting what days of static analysis had been circling. Where a UI exists and the system is safe to touch, drive it early. e. A RUNBOOK IS NOT A SOURCE. ni4kids runbook id=70 asserted a working nightly rsync, a live restic schedule and fail2ban ignoreip on both boxes. All three were false within six days of being written, and chat built a cutover plan on top of them. Anything a runbook claims about live state must be re-read before it is acted on. Scheduled jobs especially: "it was set up" is not "it is running". f. TEST SCHEDULED JOBS UNDER CRON'S ENVIRONMENT, NOT YOUR SHELL. The rsync failed silently for six days on two cron-context defects — a missing known_hosts and virtualmin absent from cron's PATH — both of which work fine interactively. The same /usr/local/bin PATH gotcha then recurred on Rocky 9 during the restic build. A hand-run proves the script works, not that the schedule does. 4. Promote the staged chat prompt in SH to a chat-prompt page at xzopia/chat-prompts/2026-07-25-comet-pattern-b-proof (date-first naming convention per SP). Then emit an id=15 UPDATES block telling chat to collapse that SH block to a one-line pointer — do NOT edit id=15 yourself. 5. docs/runbooks/baas-client-onboarding.md — FOUR corrections, sourced to XS 70, 79, 80 and 82: - The Architecture section still says M365 is pulled "agentlessly". Wrong. A registered device is a hard prerequisite for an O365 Protected Item. - Add that the O365 app-only credential is held BY THE DEVICE, not only server-side (Office365Credential{AppID,TenantID,Secret,AppCert}). It constrains where the worker device may live, and it is why Pattern C was ruled out. - Add the licensing facts: O365 bills per protected M365 account, shared mailboxes bill as full accounts, a shared SharePoint/Teams site bills every user with access, and the Protected-Accounts quota should be SET per client rather than left at 0 so tenant growth cannot produce surprise billing. - ADD THE STORAGE TEMPLATE RULE, as a hard rule: BaaS client vaults are ALWAYS provisioned via Storage Template, which issues per-vault sub-directory-scoped B2 keys. NEVER hand-configure a per-client B2 Storage Vault with master credentials — under direct-to-cloud the device holds the storage credential, so that would put the whole bucket, and therefore every client's data, on every device. 6. scripts/server3/api-admin.env.template — record the Comet admin API auth requirement: an explicit AuthType=Password form field alongside username+password. Basic alone returns "Invalid authentication"; user+pass without AuthType returns "Invalid request". Note also that installation/dispatch/register-device returned "Connection ID invalid" against a live connection, so automated lobby-assign is UNPROVEN and onboarding must not be designed around it. 7. XS item 74 step 2 — re-confirm whether "Comet Object Lock via B2 is non-achievable" still holds for the xzopia-baas bucket. Read-only. Note the new adjacent evidence in XS 82: Comet demonstrably mints scoped B2 application keys programmatically via Storage Templates, so re-test this rather than re-asserting the old conclusion. 8. XS item 80/81 — Xzopia's Comet plan is now KNOWN from Simon (Growth, £49/month + £2 per endpoint, first invoice 14 Aug, no invoice yet). What is still missing is the per-protected-M365-account rate. If the licence or entitlement as it reads on S3 carries rate information, capture it READ-ONLY and write it to XS 81. Do not create, modify or delete anything on S3, and do not register any device — a billing experiment is running on the XzopiaTest account until 14 Aug and must not be contaminated. 9. BookStack id=70 (ni4kids cutover runbook) — SIX CORRECTIONS, all proven during the 27 Jul cutover (XS 85 / 86 / 89): - "Restic -> B2 backup live on old, nightly 02:00" — FALSE. No backup job was ever scheduled on old. Script and credentials exist; the cron entry never did. - "fail2ban ignoreip set for admin IP" on BOTH boxes — FALSE for old; its ignore list is empty and still is. - The migration rsync silently CLOBBERED wp-config.php. Now fixed structurally — record the fix and the failure mode. - Virtualmin vhosts on ni4kids-new live in /etc/httpd/conf/httpd.conf, NOT conf.d/. - The "subdomains to update" list is over-stated: only the apex A record needed changing. www and admin. are CNAMEs to the apex, mail. points to a third party (80.76.205.229), webmail. does not exist. - The /usr/local/bin PATH gotcha applies to Rocky 9 as well as CentOS 7 — scripts must call restic by absolute path. Also add, as recommended practice: run a STAGING ACME issuance before any production certificate request. Staging failures do not count against production rate limits, so it proves the flow at zero risk. And note that Virtualmin's cert command restarts Webmin, Usermin and the mail server. Sourced-Facts Rule applies. CONFIRMED-only commits. Write progress to XS as you go per the item-75 rule you are promoting. Report as a cc-report page and emit an id=15 UPDATES block for anything SH-worthy.Note on item 4: the single-writer rule means CC must hand id=15 changes back to chat rather than editing the page.
Note on item 9 (rewritten 27 Jul evening): grew from two corrections to six as the cutover proved more of the runbook wrong. No longer gated — the cutover is complete.
¶ 📤 STAGED CC PROMPT #3 — 27 Jul 2026, SPLIT 28 Jul INTO #3A (Task 1, READ-ONLY, PASTE NOW) and #3B (Task 2, HELD)
Staged by chat per the standing "prompts staged before pasting to CC" discipline. Simon authorised 27 Jul: "Xzopia test job and run test a". Task 1 read-only; Task 2 is a scoped S3 mutation with a HUMAN GATE mid-way. Recorded in XS. Verbatim text:
Xzopia MSP, fresh CC session — read SP (wiki id=80) FIRST, then SH (id=15) IN FULL, then check XS (xzopia-state MCP) TO_COMMIT / AGENDA / SIMON_ACTIONS. SCOPE: TWO tasks on S3 (LIVE Comet BaaS). Task 1 is READ-ONLY. Task 2 is a Simon-authorised scoped mutation with a HUMAN GATE in the middle. Nothing else on S3. WEBOTTLE WENT LIVE 28 Jul (XS 94) — this instruction is SUPERSEDED: READ `webottle` READ-ONLY (job status, protected-account count, storage vault provenance, registration timestamp) but mutate NOTHING on it. Do not touch `XzopiaTest` — a billing experiment runs on that account until 14 Aug (XS 81) and must not be contaminated. ━━ TASK 1 — XzopiaTest job final status (READ-ONLY) ━━ XS 84 last read job b39a908f-f662-4206-a264-a9dc1b0f2f77 at 10:25 UTC on 27 Jul: Status 6001 (running), ~14.5 GB uploaded, Simon Killen mailbox + OneDrive still in flight. Read its final state and report: 1. Final Status code (5000 = success) — completed or errored. 2. Total duration, final TotalSize and TotalFiles. 3. Whether a subsequent scheduled 2300 run has fired, and its outcome. XS 84 flagged that incremental cost is an ASSUMPTION until a second run is observed — this is the first chance to observe it. 4. Confirm the protected-account scope from the job log. XS 84 found TWO users (stephen.quate@ and simon@), mailboxes AND OneDrive personal sites. Confirm or correct. Then PATCH XS 81 and XS 82 to record 2 protected accounts, not 1 — the 14 Aug invoice must be read against the right denominator or the derived per-protected-account rate, the single most important number in the BaaS cost model, comes out exactly double. Patch XS 84 with the final numbers. Read-only calls only: list-users, get-jobs-for-user, get-job-properties, get-job-log, get-job-log-entries, get-user-profile. Create, modify or delete NOTHING in this task. STOP HERE. TASK 1 IS THE WHOLE SCOPE OF THIS SESSION. Report, write your findings to XS, emit the id=15 UPDATES block, and HALT. Do NOT proceed to Task 2 below. Task 2 is HELD by Simon as of 28 Jul and is NOT authorised to run in this session. It is retained below only so it can be released later without re-drafting. If you find yourself creating an account, registering a device, or writing anything at all to S3, you have exceeded scope: stop. ━━ TASK 2 — TEST (a) SECOND HALF: is the O365 Protected-Item type PLATFORM-GATED on Linux? ━━ 🛑 HELD 28 Jul BY SIMON. DO NOT RUN IN THIS SESSION. The 27 Jul authorisation still stands and every condition below is unchanged, but execution is deferred: this test answers nothing about the WeBottle documentation deliverable, and it puts another device on the installation inside the 25 Jul to 14 Aug billing window. Retained verbatim so it can be released later without re-drafting. Release requires a fresh instruction from Simon. This is the entire remaining question on Pattern B's engine availability. ⚠️ READ FIRST — XS 84 SIDE-FINDING 1 CHANGES THIS TEST. The live, human-created O365 Protected Item uses engine `engine1/winmsofficemail`, NOT `engine1/ms365`. XS 79's static evidence (~27 O365 implementation types in the Linux binary) was built around `ms365` and may therefore be pointing at the wrong engine entirely. ⇒ the empirical look is now MORE decisive, not less. The question is whether a registered LINUX device is offered the Microsoft 365 type that is backed by `winmsofficemail`. 2.1 Pre-change backup of /etc/cometd + /var/lib/cometd, as on 25 Jul. Confirm cometd active afterwards. 2.2 Create a BARE throwaway account named `zz-cctest2`. 🔴 DO NOT ASSIGN THE CUSTOM GFS POLICY. Bare account, no policy, no protected item. Comet's docs warn that a policy which auto-adds default Protected Items makes a new device billable, and Xzopia's GFS policy is exactly such a policy (XS 81 billing-window hygiene, condition 2). Password generated on-box via openssl rand, never printed, destroyed after. 2.3 Register a Linux client to it headlessly. Proven path, XS 78 Result 6: OVERRIDE_INSTALL_SERVER=https://backup.xzopiasecure.com plus `backup-tool login prompt`, username and password on stdin. The image needs /usr/bin/uname and a distinct /var/lib/dbus/machine-id (XS 83 hard image requirements). Do NOT use API lobby-assign — installation/dispatch/ register-device returned "Connection ID invalid" and is UNPROVEN. 2.4 🛑 HALT AND REPORT. Write to XS: account name, device ID, reported platform string, and the exact registration timestamp. Then STOP and wait. Simon opens the Comet console, selects that Linux device, opens Add Protected Item, LOOKS AT THE TYPE LIST, and closes WITHOUT SAVING. No Protected Item is created. No Azure work. No consent. No backup run. 2.5 ON SIMON'S WORD ONLY: delete the device and the account. Verify deletion BY RE-READ, not by the delete call's return — get-user-profile must return 404, installation/list-active must not show the device, list-users must show only the expected accounts. Then re-verify `webottle` untouched (Devices 0 / Sources 0 / Destinations 0, PolicyID 2516ced7-…) and confirm `XzopiaTest` unchanged. ⛔ DO NOT attempt dispatcher/request-office365-accounts. Your own auto-approval classifier blocked that call on 25 Jul (XS 78 Result 7) and it is not needed — a human eyeball on the dropdown answers the question directly, at zero risk, and with no credential-shaped call to an external service. BILLING-WINDOW LOG (XS 81 condition 4): record a dated line in XS stating exactly what existed on the installation and between which timestamps, so the 14 Aug invoice stays reconcilable. Sourced-Facts Rule applies. CONFIRMED-only commits. Write to XS as you go per the item-75 rule. Report as a dated cc-report page and emit an id=15 UPDATES block for anything SH-worthy.Note: Task 2 is a production mutation on a live box, so it runs under SP rule-5 override (b) on Simon's explicit 27 Jul sign-off. That sign-off is scoped to this test only and does not generalise, exactly as the 25 Jul
zz-cctestauthorisation did not.
¶ 📤 STAGED CC PROMPT #1 — 25 Jul 2026, Comet Pattern-B proof
✅ EXECUTED — results in cc-report id=165 and XS items 78/79. CC: still to be promoted to a chat-prompt page at
xzopia/chat-prompts/2026-07-25-comet-pattern-b-proof(see prompt #2 item 4), after which this block collapses to a one-line pointer. Verbatim text as pasted:Xzopia MSP, fresh CC session — boot: read SP (wiki id=80) FIRST, then SH (wiki id=15) IN FULL, then check XS (xzopia-state MCP) TO_COMMIT / AGENDA / SIMON_ACTIONS. PRIORITY: XS item 78 — read it in full, including Simon's authorisation block at the top (added by chat 25 Jul). Then item 74 (the decision), then item 77 (vendor evaluation, parallel track, not yours today). Simon has run `wsl --shutdown` — verify S7 (xzopia-s7-drill) is reachable before starting. DO IN THIS ORDER: 1. Device-id collision test on S7: >=2 containers with differing /etc/machine-id, compare `backup-tool info device-id`. Needs NO Comet account. THIS IS DECISIVE — a negative result kills Pattern B, so do it first and report before moving on. 2. Test (a) — Linux client registers + confirm "Microsoft Office 365" appears in the Add-Protected-Item type list. Simon has AUTHORISED a throwaway `zz-cctest` user on S3 for this, under SIX MANDATORY CONDITIONS listed in item 78. Read them and follow all six. The authorisation is scoped to this action only — it is not a general licence to change S3. `webottle` is not to be touched. 3. Answer the credential-residency question: do the O365 app-only credentials live server-side on S3, or on the device? Sourced answer only — this decides whether a client-site device is safe. 4. Correct the CLAUDE.md S7 spec line (recorded 1 vCPU/2 GB/20 GB, actually reports 32 cores/48 GB). WRITE FINDINGS TO XS AS YOU GO — per the item-75 rule Simon staged. Do not wait to be asked and do not batch it to the end. FOLLOW-ON (not today unless the above finishes early): XS items 75 and 76 are still staged for promotion into SP (id=80) + the repo. Item 71 is already DONE (commit 6dfc73a) — do NOT re-stage it.
¶ ✅ CLOSED — O'BOYLE ACCOUNTING (8 Jul)
Fully remediated + closed, zero recurrence, all 127 S1-EDR incidents resolved. Full record: BookStack id=4. Only remaining item: VirusTotal DLL submission (low priority).
¶ TO COMMIT (Claude Code) — full detail (NOT yet in repo; id=15 is the only copy)
¶ 🔴 TWO STANDING RULES TO PROMOTE INTO SP (id=80) — XS items 75, 76
- 75 — CC↔chat↔XS channel responsibilities. XS = CC's default write target for new findings/tasks/blockers/status, written as it goes without being asked. cc-reports = permanent audit trail (unchanged). id=15 = volatile session-level state only. UPDATES block = narrowed to id=15-worthy changes. Single-writer boundary unchanged; XS is the shared surface. Chat's reciprocal duty: write Simon's decisions/sign-offs into XS as they happen.
- 76 — Session hygiene: mid-session recall check. Ask the agent to restate a specific fact AND its source; fail = cut to a fresh session. Sourced-Facts binds chat as hard as CC. No numeric confidence scores. Name-based "canary" evaluated and rejected as unreliable.
- ✅ 71 — Secrets & Credential Handling: COMMITTED 25 Jul, commit
6dfc73a.¶ 🆕 CANDIDATE LESSONS (CC to promote into LESSONS_LEARNED)
- "Cloud-to-cloud" is not the same as "agentless." Verify the COMPUTE model, not just the data path. Cost: a wrong runbook step and a day's misdiagnosis on XS 70.
- When a test exists to rule out fallback source X, first verify X is present in the test environment. WSL2 exposes no
/sys/class/dmi; the naive device-id test would have passed while proving nothing.- Identifier strings prove nothing; implementations do. The Linux Comet client carries
engine1/windiskandengine1/vsswriteridentifiers despite both being Windows-only. Implementation types are build-tag gated — that is the real signal.- Drive the actual product early. Simon's half-hour hands-on session settled in one sitting what days of static analysis had been circling, and surfaced the Storage Template model that nobody had looked at. Where a UI exists and the system is safe to touch, operating it is the cheapest source of truth available.
- A RUNBOOK IS NOT A SOURCE (27 Jul, XS 85/89). ni4kids runbook id=70 asserted a working nightly rsync, a live restic schedule, and fail2ban
ignoreipon both boxes. All three were false within six days of being written, and chat built an entire cutover plan on top of them — including ranking a broken job as the top danger. Anything a runbook claims about live state must be re-read before being acted on. Scheduled jobs especially: "it was set up" is not "it is running."- 🆕 TEST SCHEDULED JOBS UNDER CRON'S ENVIRONMENT, NOT YOUR SHELL (27 Jul, XS 89). The rsync failed silently for six days on two cron-context defects — a missing
known_hostsandvirtualminabsent from cron's PATH — both of which work perfectly interactively. The same/usr/local/binPATH gotcha then recurred on Rocky 9 during the restic build. A hand-run proves the script works, not that the schedule does. CC's restic job was explicitly tested under a minimal PATH; make that the standard.¶ 🆕 STORAGE TEMPLATE RULE — hard rule, to
docs/runbooks/baas-client-onboarding.md(XS 82)BaaS client vaults are always provisioned via Storage Template (per-vault sub-directory-scoped B2 keys, vendor-documented). Never hand-configure a per-client B2 Storage Vault with master credentials — under direct-to-cloud the device holds the storage credential, so that hands every device the whole bucket.
¶ 🆕 ni4kids RUNBOOK CORRECTIONS OWED — BookStack id=70, SIX of them (XS 89)
restic-on-old never scheduled · fail2ban ignore list empty on old · the rsync clobbered
wp-config.php(now fixed structurally) · vhosts live in/etc/httpd/conf/httpd.confnotconf.d/· only the apex A record needed changing · the/usr/local/binPATH gotcha applies to Rocky 9 too. Plus two additions: run a STAGING ACME issuance before any production cert request (staging failures don't count against production rate limits — proves the flow at zero risk), and note that Virtualmin's cert command restarts Webmin, Usermin and the mail server. Staged as CC prompt #2 item 9.¶ 🆕 CONTAINER BUILD SPEC — XS 83, to
docs/design/or a runbook once the Linux test closes.¶ ✅ STATE-STORE — LIVE + closed-out session items (22 Jul)
xzopia-stateMCP build, first audit, nginx symlink fix (9b5a53e), auth confirmation, glossary add (1770dfe), wiki naming convention (8177cc9) — all DONE and tracked in the store itself (items 4/38–41). Nightly human-readable mirror at wiki id=157.¶ 🆕 S1-EDR BULK-RESOLVE GOTCHA — ✅ DONE, tracked in XS item 5.
¶ 🆕 CC LAUNCH — npm hardening — ✅ DONE (
~/start-cc.shself-healing wrapper), tracked in XS item 6.¶ 🆕 THREAT MODEL — ✅ DONE, verified + committed, tracked in XS item 7.
¶ 🐛 BUG — S1-EDR MCP
get_agent_passphrase404 — ✅ DONE, tracked in XS item 8.¶ 🟠 ACTION — orphaned EDR agent offboarding — ✅ DONE, tracked in XS item 9 (303/303 clean, no orphans).
¶ 🟡 DESIGN INTENT (TO COMMIT — CC → docs/design/, DESIGN/PROPOSED)
- comms-app.md — ✅ DONE.
send_teams_messagebuilt + deployed + live-tested (02f87fb, report id=95); reconciled to the real Power Automate mechanism (9d43df4, report id=99).- alert-routing.md: severity-tiered — routine "all clear" → dashboard only; actionable → dashboard + email (
alerts-team@) + ITFlow ticket (deferred until ITFlow live). Backup alerting stays on its own Mailgun/msmtp + healthchecks.io path.- maintenance-schedule.md: resolved near-term with chat-generated
.icsfiles + Cowork/scheduletasks that read the real runbook at run time. Phase 4 SecureAgent portal should build a first-class recurring-maintenance view informed by both.- state-store.md: ✅ DONE.
¶ 🆕 CIPP MCP (TO COMMIT — durable fix pending upstream)
Native CIPP MCP Server (OAuth,
ExecMcp) connected 6 Jul, all 6 Xzopia-side steps done. BUG #1 cleared. BUG #2 — upstream 10.6.0 enrichment-step generator defect (reports id=107/109), NOT confirmed fixed in v10.7.0. CIPP Custom Role blockingListTenantsis NOT the fix (report id=108). Standing guidance: CIPP MCP connector stays out of live claude.ai chat sessions until confirmed fixed.¶ 🆕 CIPP GDAP GOVERNANCE (TO COMMIT — migration well underway)
All 61 client tenants onto the 15-Recommended-role, no-GA, Auto-Extend model, then terminate legacy Global-Admin relationships. 57 legacy Global-Admin relationships across 53 tenants are termination-eligible — full list in report id=117. Simon's UI work, not CC's.
¶ 🆕 XZOPIA BaaS — client-facing Backup-as-a-Service (production build, S3=Comet live)
S3 build complete, Comet installed+licensed+live at
backup.xzopiasecure.com, branding applied + verified (XS item 61). Open finishing items in XS item 15: locate/redraft remaining client-facing policy docs, provision Momentum. Architecture (XS 74): Pattern A REJECTED on security grounds; Pattern B (one Comet account per client) is the direction and is UNBLOCKED — device-id proven (XS 78), credential residency answered and Pattern C ruled out (XS 79), licensing answered (XS 80/81), and Simon's hands-on session (XS 82) confirmed the device-prerequisite, the tenant-wide account picker and the Storage Template isolation model. Build spec consolidated in XS 83. Remaining engineering: Linux Protected-Item type list, concurrent multi-account operation, per-instance footprint, continuous-vs-staggered container running, credential injection. Commercial unknowns resolve on the 14 Aug invoice (XS 81) — billing experiment running onXzopiaTestuntil then, do not contaminate.
¶ AGENDA (open only — most now tracked in XS, see boot line)
- CIPP MCP — durable fix pending upstream, re-verify against v10.7.0.
- CIPP GDAP governance — continue batch + terminate the 57 legacy relationships (Simon's UI work).
- BSA offboarding — Stephen removing 2 orphaned S1-EDR agents.
- Teams-as-user delegated · ITFlow import · NinjaOne ticket export · MCP capability audit · KB5034441
-Apply· S1-EDR Engineer Queue (BookStack id=6) · ticket-triage agent v1 · thin status dashboard.- VirusTotal submission —
Stack360Hui634094.dll.forensic, low priority (last O'Boyle item).- Boot-time backup race (S1+S2) — XS item 19.
- S2 timezone — XS item 20.
- 🔴 familymediagroup.co.uk — now a DECOMMISSION BLOCKER. Live on OLD. Simon's decision 27 Jul: clean and contain, replace with a static holding page (FMG is the holding-group site for both ni4kids and scotland4kids, so it stays). Approach is REPLACE not clean — no
wp-config.php, so nothing to preserve; wipe the docroot, static page, then disable PHP execution for the vhost entirely. Preserve evidence first; grep the access logs for requests to89d12d8e.php— that decides hygiene vs incident. Check persistence outside the docroot and rotatefamilymgcredentials. Findings go in BookStack, not SP/SH. XS item 67.- TRMM "MeshCmd" falsely reports 2FA not enabled. Low priority; check whether the version bump (item 66) fixes it. XS item 72.
- FusionPBX MFA — needs CC read-only check of the actual admin panel/app_config on S5. XS item 73.
- M365 backup vendor evaluation — benchmark quotes only. XS item 77.
- Comet commercial unknowns — RESOLVE 14 AUG on the first invoice. Enquiry HELD, not sent. Billing-window hygiene applies to all BaaS work until then. XS item 81.
- Storage Template rule + hands-on validation — to promote into the runbook. XS item 82.
- Pattern B container build + the untested items: Linux Protected-Item type list; concurrent multi-ACCOUNT operation on one host; per-instance footprint (needs a capped
.wslconfigor a real VM — S7 cannot size it); continuous-vs-staggered running; credential injection design. XS item 83.- ni4kids/ni4kidsV2 hosting — open findings from the 21 Jul audit that never reached any plan: the 521 MB world-readable WordPress dump (10,049 users' password hashes, mode 644 on a multi-tenant box) · 25.8 GB orphaned tarball, same permissions · BIND possible open resolver on both boxes, never tested · plugin-level PHP 8.2 QA never done · pending-updates position not re-checked. XS item 87.
- ni4kids-old decommission — Simon's decision: shut down after cutover, retained 7 days before removal. ⚠️ Gated on item 67 — FMG is live on old. Pre-shutdown checklist in XS 88; keep the final snapshots well beyond the 7 days, since plugin-level QA was never done and a seasonal failure can surface weeks later. XS item 88.
- ✅ ni4kids cutover COMPLETE. Remaining: six runbook corrections to BookStack id=70 (staged as CC prompt #2 item 9) · watch the PHP error log · broader QA. Old's habitual restarts are FLAGGED ONLY, not being pursued (Simon, 27 Jul) — the host is being decommissioned anyway. XS item 89.
¶ 🔑 ni4kids — POST-CUTOVER
- 🔑 VAULT THE NEW ni4kids DB BACKUP-USER PASSWORD. It exists only in the on-box credentials file. Read it on-box (no-echo) and store in Vaultwarden per the secrets procedure. CC cannot write to Vaultwarden. This is the one genuinely outstanding item from the cutover.
- ⬜ Watch the ni4kids PHP error log for a few days — plugin-level 8.0→8.2 QA was never done (XS 87), only extension-level clearance plus a homepage load. On a magazine site with event listings, a monthly cron or seasonal template can fail weeks later.
- ⬜ Broader QA — form submission, a few more page types.
- ⬜ 🔴 familymediagroup.co.uk is now a DECOMMISSION BLOCKER — live on OLD, so old cannot be retired until FMG is migrated or repointed (XS 67 / 88).
¶ Everything else
- 🔴 KEEP
XzopiaTestRUNNING UNTIL 14 AUG — it is the billing experiment (XS 81). Add nothing to that device or account.- ⬜ Vault the Azure app registration created during the 25 Jul test: a real app registration now exists in Xzopia's own tenant carrying tenant-wide app-only mail permissions. Record it in Vaultwarden, or remove it if the test is ever torn down.
- ⬜ Linux container test (see "SIMON — PENDING" above): check the
XzopiaTest2300 job completed, then the Protected-Item dropdown on azz-throwaway with no policy assigned, then concurrent multi-account.- ✅ DONE 28 Jul — Workstation WSLInterop binfmt fault ROOT-CAUSED AND PERMANENTLY FIXED (XS 92). Recurring fault (≥3 occurrences) closed. Cause:
systemd-binfmtflushes all binfmt registrations on start and re-registers only what is declared inbinfmt.d; WSL registers WSLInterop via/initoutside that mechanism, so every run wiped it. Fix:/etc/binfmt.d/WSLInterop.conf=:WSLInterop:M:0:\x4d\x5a::/init:P, built from the box's own live registration. Verified by restarting the very service that used to wipe it — registration survived. S7 reachable again (wsl.exe -d xzopia-s7-drill -- uname -a→ kernel6.18.33.2-microsoft-standard-WSL2). ⚠️ Residual ASSUMPTION: survival across a fullwsl --shutdowncold boot not yet observed.- Give Stephen Fasthosts console access + document the manual-boot procedure (standing resilience item, not urgent).
- MFA rollout — 6/7 done. Only ⬜ FusionPBX left — waiting on CC's read-only check (item 73). ⬜ ITFlow separately BLOCKED (item 68). Detail in XS item 32.
- Code-signing cert procurement for branded Comet installers — XS item 65, not urgent.
- S6 Nextcloud build — needs Simon: B2 bucket/key, DNS subdomain choice, build sign-off. XS item 54.
- Stack-wide version audit results (XS item 66) — review when convenient.
- ⬜ OPEN — WeBottle (item 70): does WeBottle go live on Comet as a one-off ahead of the wider decision? Not blocked by XS 74. If yes, Simon runs the interactive Azure AD consent (WeBottle Global Admin, or Xzopia via GDAP for tenant 46732e32-…) and selects the 38 users, after a device is registered. ⚠️ Doing this before 14 Aug adds a device and 38 protected accounts to the invoice — still readable, but log it.
- ⬜ OPEN — vendor quotes (XS 77): 1-2 M365 backup quotes as a benchmark, and check whether Giacom resells an M365 backup SKU.
- ⬜ OPTIONAL — ask Comet whether a containerised client is a SUPPORTED deployment (XS 81 Q4). Support-posture question, unaffected by the invoice.
- ✅ DECIDED 25 Jul — Pattern A REJECTED on security grounds (XS 74). Per-client-account is the direction.
- ✅ RESOLVED 25 Jul — Pattern C ruled out by credential residency (XS 79). Do not revisit without new evidence.
- ✅ ANSWERED 25 Jul — Comet plan known (Growth, £49/mo + £2/endpoint) and the licensing model understood (XS 80/81). The residual resolves on the invoice.
- ✅ AUTHORISED 25 Jul — throwaway
zz-ccteston S3 (XS 78): scoped to test (a) only, six conditions, all met with evidence, deleted in-session. Does not generalise.- ✅ DONE — WeBottle agreement signed (Adobe Sign); MeshCentral login + 2FA resolved; Comet creds vaulted.
¶ REFERENCE (volatile — stable facts now in session-protocol id=80)
REFERENCE facts → session-protocol wiki id=80.
¶ 🆕 ITFlow / Twenty CRM population (kicked off 17 Jul, now substantially complete)
CC owns this task end-to-end. 📄 Detail → wiki id=155,
xzopia/migration/itflow-population. Residual OPEN: write 15 confirmed Companies House matches + Royal Belfast address; DP Commercials cleanup 2-4; TRMM "Direct Vape" client (UI); register Companies House connector.
📄 BRANDING / rebrand + NEW WEBSITE — approved 18 Jul, website pricing removed 22 Jul (XS item 49 done). Design system lives in Claude Design — role/access/handoff process NOT yet decided, see XS item 60. Branding/website tracking has no proper home yet — see XS item 59. Both need a real conversation, not CC action.
¶ 📋 PLANNING (not yet built) — ITFlow → Xero invoicing architecture, 17 Jul (Simon + chat)
Not yet designed in detail — flagged as a future planning item once ITFlow population is further along. No blocking dependency today.