<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>devs group blog</title><description>Articles on Go, Kubernetes, AI architecture and Swiss regulation, written by the devs group team.</description><link>https://devs-group.ch/</link><language>en</language><atom:link rel="alternate" type="application/rss+xml" href="https://devs-group.ch/blog/rss.xml"/><lastBuildDate>Wed, 26 Aug 2026 00:00:00 GMT</lastBuildDate><item><title>Kubernetes v1.37: upgrading without downtime</title><link>https://devs-group.ch/en/blog/kubernetes-1-37-upgrade-without-downtime/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/kubernetes-1-37-upgrade-without-downtime/</guid><description>Static pods break, old cAdvisor flags stop the kubelet, SELinuxMount is now on. What actually goes wrong upgrading to Kubernetes v1.37.</description><pubDate>Wed, 26 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Kubernetes v1.37 was released on 26 August 2026. Three changes break running clusters on upgrade: static pods may no longer reference secrets or config maps, the kubelet refuses to start when deprecated cAdvisor flags are set, and &lt;code&gt;SELinuxMount&lt;/code&gt; is now on by default. The frequently quoted containerd 1.x deadline, by contrast, has been postponed again.&lt;/p&gt;
&lt;p&gt;The full release notes are in &lt;a href=&quot;https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.37.md&quot;&gt;&lt;code&gt;CHANGELOG-1.37.md&lt;/code&gt;&lt;/a&gt; in the kubernetes/kubernetes repository, and the feature overview in the &lt;a href=&quot;https://kubernetes.io/blog/2026/07/31/kubernetes-v1-37-sneak-peek/&quot;&gt;sneak peek of 31 July 2026&lt;/a&gt;. Anyone running between two and twenty production clusters needs only the part that hurts: an order of operations and a handful of check commands. That is what follows.&lt;/p&gt;
&lt;h2 id=&quot;static-pods-may-no-longer-reference-secrets-and-config-maps&quot;&gt;Static pods may no longer reference secrets and config maps&lt;/h2&gt;
&lt;p&gt;Kubernetes v1.37 removes the &lt;code&gt;PreventStaticPodAPIReferences&lt;/code&gt; feature gate. Static pods using &lt;code&gt;configMapRef&lt;/code&gt; or &lt;code&gt;secretRef&lt;/code&gt; will not start after the upgrade. The ones affected are mostly bare-metal installations and self-managed control planes, not the standard components kubeadm manages.&lt;/p&gt;
&lt;p&gt;This is a bug fix, not an imposition. Static pods are read by the kubelet directly out of &lt;code&gt;/etc/kubernetes/manifests/&lt;/code&gt; and do not go through the API server. That they could nonetheless reference API objects was a defect. According to the release entry the gate has been removed and can no longer be disabled.&lt;/p&gt;
&lt;p&gt;The check takes a minute per node:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# On every control plane and worker node&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;grep&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -rlE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;configMapRef|secretRef&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /etc/kubernetes/manifests/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every output line is a manifest you have to rework before the upgrade. The replacement is unspectacular: write the values as literals into the pod spec, mount files through &lt;code&gt;hostPath&lt;/code&gt;, or move the component out of the static pod directory into a real DaemonSet.&lt;/p&gt;
&lt;h2 id=&quot;the-kubelet-will-not-start-when-old-cadvisor-flags-are-set&quot;&gt;The kubelet will not start when old cAdvisor flags are set&lt;/h2&gt;
&lt;p&gt;The kubelet in v1.37 refuses to start as soon as a deprecated cAdvisor option appears in its configuration. The embedded cAdvisor now uses the leaner module &lt;code&gt;github.com/google/cadvisor/lib&lt;/code&gt;, and of the old flags only &lt;code&gt;--housekeeping-interval&lt;/code&gt; survives.&lt;/p&gt;
&lt;p&gt;The release notes carry this as a deprecation, but in practice it behaves like a hard breaking change: a node with &lt;code&gt;--containerd&lt;/code&gt;, &lt;code&gt;--boot-id-file&lt;/code&gt;, &lt;code&gt;--machine-id-file&lt;/code&gt;, &lt;code&gt;--container-hints&lt;/code&gt;, &lt;code&gt;--global-housekeeping-interval&lt;/code&gt;, &lt;code&gt;--event-storage-age-limit&lt;/code&gt;, or one of the &lt;code&gt;--storage-driver-*&lt;/code&gt; flags simply does not come back after a restart.&lt;/p&gt;
&lt;p&gt;Where you look matters. The systemd unit is only one of the places: kubeadm puts its flags in &lt;code&gt;/var/lib/kubelet/kubeadm-flags.env&lt;/code&gt;, and many distributions additionally in &lt;code&gt;/etc/default/kubelet&lt;/code&gt;. Checking only &lt;code&gt;systemctl cat&lt;/code&gt; gives you a clean result on an affected node.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# On every node: find forbidden cAdvisor flags in all three sources&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;FLAGS&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;--(application-metrics-count-limit|boot-id-file|container-hints|containerd|enable-load-reader|event-storage-|global-housekeeping-interval|log-cadvisor-usage|machine-id-file|storage-driver-)&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;systemctl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; cat&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; kubelet&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; 2&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/dev/null&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -E&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$FLAGS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;grep&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -hE&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$FLAGS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /var/lib/kubelet/kubeadm-flags.env&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /etc/default/kubelet&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; 2&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/dev/null&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No output means clean. On top of that, the metrics &lt;code&gt;container_cpu_load_average_10s&lt;/code&gt;, &lt;code&gt;container_cpu_load_d_average_10s&lt;/code&gt; and &lt;code&gt;container_tasks_state&lt;/code&gt; disappear from &lt;code&gt;/metrics/cadvisor&lt;/code&gt;. Anyone with a Grafana panel or an alerting rule hanging off those sees a silent gap after the upgrade rather than an error. That is the more unpleasant variant. More on this in our article on &lt;a href=&quot;https://devs-group.ch/en/blog/observability-you-actually-read/&quot;&gt;observability you actually read&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;containerd-20-under-kubernetes-is-the-move-mandatory-for-v137&quot;&gt;containerd 2.0 under Kubernetes: is the move mandatory for v1.37?&lt;/h2&gt;
&lt;p&gt;No, containerd 2.0 is not mandatory with Kubernetes v1.37 either. The claim has been circulating in upgrade guides since the v1.36 cycle and is wrong. Removal of the deprecated kubelet configuration flags along with the CRI fallback has been postponed twice: in &lt;code&gt;CHANGELOG-1.36.md&lt;/code&gt; from v1.36 to v1.37, and in &lt;code&gt;CHANGELOG-1.37.md&lt;/code&gt; again from v1.37 to &lt;strong&gt;v1.38&lt;/strong&gt; (PR #139121, merged 22 May 2026), explicitly “to align with containerd v1.7 support”.&lt;/p&gt;
&lt;p&gt;Do not relax on that account. According to &lt;a href=&quot;https://github.com/containerd/containerd/blob/main/RELEASES.md&quot;&gt;containerd RELEASES.md&lt;/a&gt;, extended support for containerd 1.7 ends in &lt;strong&gt;September 2026&lt;/strong&gt;, and it is explicitly limited to Kubernetes 1.30 through 1.32 on GKE. For anything else, 1.7 is already unmaintained.&lt;/p&gt;
&lt;p&gt;The kubelet tells you itself when the music stops:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Node inventory: kubelet version, runtime, OS image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;kubectl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; get&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; nodes&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; custom-columns=&amp;#39;NODE:.metadata.name,\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;KUBELET:.status.nodeInfo.kubeletVersion,\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;RUNTIME:.status.nodeInfo.containerRuntimeVersion,\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;OS:.status.nodeInfo.osImage&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# kubelet metrics: cgroup version and the runtime&amp;#39;s end of support&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;kubectl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; get&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --raw&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;/api/v1/nodes/&amp;lt;NODE&amp;gt;/proxy/metrics&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -E&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;^kubelet_(cgroup_version|cri_losing_support)&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;kubelet_cri_losing_support&lt;/code&gt; is a gauge with a &lt;code&gt;version&lt;/code&gt; label reporting the Kubernetes version in which the currently running CRI implementation loses support. Alpha stability, but exactly the number you need when planning.&lt;/p&gt;
&lt;p&gt;What the jump from 1.7 to 2.x actually means:&lt;/p&gt;



































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Point&lt;/th&gt;&lt;th&gt;containerd 1.7&lt;/th&gt;&lt;th&gt;containerd 2.x&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Support&lt;/td&gt;&lt;td&gt;extended to September 2026, only for K8s 1.30–1.32 on GKE&lt;/td&gt;&lt;td&gt;2.3 as LTS until 30 April 2028&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CRI API&lt;/td&gt;&lt;td&gt;&lt;code&gt;v1&lt;/code&gt; and &lt;code&gt;v1alpha2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;v1&lt;/code&gt; only, &lt;code&gt;v1alpha2&lt;/code&gt; removed&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;config.toml&lt;/code&gt;&lt;/td&gt;&lt;td&gt;version 2&lt;/td&gt;&lt;td&gt;version 3 recommended, version 2 still works&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Docker schema 1 images&lt;/td&gt;&lt;td&gt;pull possible, deprecated&lt;/td&gt;&lt;td&gt;disabled in 2.0, removed in 2.1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Runtime plugins as &lt;code&gt;*.so&lt;/code&gt;&lt;/td&gt;&lt;td&gt;supported&lt;/td&gt;&lt;td&gt;removed in 2.1, use external plugins&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;If you migrate, go to &lt;strong&gt;containerd 2.3&lt;/strong&gt;: LTS until 30 April 2028. containerd 2.2 already goes out of support on 6 November 2026, and 2.1 has been EOL since 3 July 2026. The config does not have to move at the same time; containerd 2.x still reads version 2.&lt;/p&gt;
&lt;p&gt;If you are not sure whether your nodes come back after the kubelet restart, you can find out in about an hour: node inventory, kubelet unit diff, static pod scan. If you would rather not work through it yourself, &lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;we will look at it together&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;cgroup-v1-was-settled-in-v135-not-now&quot;&gt;cgroup v1 was settled in v1.35, not now&lt;/h2&gt;
&lt;p&gt;The kubelet has refused to start on cgroup v1 nodes since Kubernetes v1.35, because &lt;code&gt;failCgroupV1&lt;/code&gt; defaults to &lt;code&gt;true&lt;/code&gt; there. The &lt;a href=&quot;https://kubernetes.io/blog/2026/07/31/kubernetes-v1-37-sneak-peek/&quot;&gt;v1.37 sneak peek&lt;/a&gt; lists it as an ongoing change, not a new one.&lt;/p&gt;
&lt;p&gt;In practice: anyone still on v1.34 does not meet cgroup v1 at the jump to v1.37 but at the intermediate step to v1.35. Affected are older node images and on-premise estates, typically CentOS 7, RHEL 7 and Ubuntu 18.04. The &lt;code&gt;kubelet_cgroup_version&lt;/code&gt; metric from the block above answers this per node without anyone having to log in.&lt;/p&gt;
&lt;h2 id=&quot;kube-proxy-ipvs-the-clock-is-running-but-slowly&quot;&gt;kube-proxy IPVS: the clock is running, but slowly&lt;/h2&gt;
&lt;p&gt;kube-proxy’s IPVS mode disappears over four releases, not in this one. &lt;a href=&quot;https://kubernetes.dev/resources/keps/5495&quot;&gt;KEP-5495&lt;/a&gt; lays out the path: v1.35 warnings, v1.37 a new &lt;code&gt;KubeProxyIPVS&lt;/code&gt; feature gate (on by default), v1.40 gate off by default, v1.43 code removed, v1.46 gate removed.&lt;/p&gt;
&lt;p&gt;The reasoning is honest and readable in the KEP: “sig-network currently lacks maintainers who are familiar with the &lt;code&gt;ipvs&lt;/code&gt; backend code.” On top of that, IPVS still uses iptables underneath. The migration target is &lt;code&gt;nftables&lt;/code&gt;, GA since v1.33.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;kubectl&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; kube-system&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; get&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; configmap&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; kube-proxy&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; jsonpath=&amp;#39;{.data.config\.conf}&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;mode:&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An empty &lt;code&gt;mode:&lt;/code&gt; is worth a warning from v1.37 as well: kube-proxy now reports when no mode is set explicitly, because the Linux default will move from &lt;code&gt;iptables&lt;/code&gt; to &lt;code&gt;nftables&lt;/code&gt;. Set the value explicitly, even if you want to keep current behaviour.&lt;/p&gt;
&lt;h2 id=&quot;which-kubernetes-version-still-has-support&quot;&gt;Which Kubernetes version still has support&lt;/h2&gt;
&lt;p&gt;Kubernetes maintains the three most recent minor releases. The documentation calls that “roughly a year”; the last three releases actually ran 14.0, 14.4 and 14.2 months. Plan for twelve months and be pleased about the rest. As of &lt;a href=&quot;https://kubernetes.io/releases/&quot;&gt;kubernetes.io/releases&lt;/a&gt; on 18 August 2026:&lt;/p&gt;



































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Version&lt;/th&gt;&lt;th&gt;Release&lt;/th&gt;&lt;th&gt;End of life&lt;/th&gt;&lt;th&gt;What to do&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;v1.34&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;27.08.2025&lt;/td&gt;&lt;td&gt;&lt;strong&gt;27.10.2026&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Urgent. Two months left, three minor jumps to v1.37&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;v1.35&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;17.12.2025&lt;/td&gt;&lt;td&gt;28.02.2027&lt;/td&gt;&lt;td&gt;cgroup v1 already enforced, IPVS warnings active&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;v1.36&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;22.04.2026&lt;/td&gt;&lt;td&gt;28.06.2027&lt;/td&gt;&lt;td&gt;The most comfortable position, one jump to v1.37&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;v1.37&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;26.08.2026&lt;/td&gt;&lt;td&gt;approx. October 2027 *&lt;/td&gt;&lt;td&gt;New, initially at .0 patch level&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;* EOL for v1.37 has not been published. The value is extrapolated from the 14-month pattern of the three predecessors and should be treated as a planning figure rather than a commitment.&lt;/p&gt;
&lt;p&gt;For Swiss teams on &lt;a href=&quot;https://community.exoscale.com/product/compute/containers/how-to/lifecycle-management/&quot;&gt;Exoscale SKS&lt;/a&gt; there is a second cadence: minor versions are supported for around 14 months, upgrades must run sequentially through every minor version, and the control plane is raised first, after which the node pools are cycled. From v1.34 to v1.37 that is three rounds, not one.&lt;/p&gt;
&lt;h2 id=&quot;the-upgrade-order-we-recommend&quot;&gt;The upgrade order we recommend&lt;/h2&gt;
&lt;p&gt;An upgrade without downtime follows the official &lt;a href=&quot;https://kubernetes.io/releases/version-skew-policy/&quot;&gt;version skew policy&lt;/a&gt;: control plane before nodes, no skipped minor versions on the kube-apiserver.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Check before touching anything.&lt;/strong&gt; Static pod scan, cAdvisor flag scan, &lt;code&gt;kubelet_cgroup_version&lt;/code&gt;, &lt;code&gt;kubelet_cri_losing_support&lt;/code&gt;, kube-proxy mode. All five commands are above.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clear &lt;code&gt;scheduling.k8s.io/v1alpha2&lt;/code&gt; out of etcd.&lt;/strong&gt; v1.37 removes the version entirely. The core types &lt;code&gt;Workload&lt;/code&gt; and &lt;code&gt;PodGroup&lt;/code&gt; have been promoted to &lt;code&gt;scheduling.k8s.io/v1beta1&lt;/code&gt;, the rest to &lt;code&gt;v1alpha3&lt;/code&gt;. Existing objects must go before the update.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Raise the kube-apiserver&lt;/strong&gt;, then kube-controller-manager, kube-scheduler and cloud-controller-manager. Those three may be at most one minor version older than the API server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lift one worker node as a test.&lt;/strong&gt; kubelet and kube-proxy may be up to three minor versions older than the kube-apiserver. So you have time, and you need it: the first node is the experiment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Roll the remaining node pools&lt;/strong&gt;, with PodDisruptionBudgets that deserve the name. A PDB with &lt;code&gt;minAvailable: 1&lt;/code&gt; on two replicas does not block a drain, it only delays it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One point that often gets lost: &lt;code&gt;SELinuxMount&lt;/code&gt; is GA in v1.37 and on by default. On clusters with SELinux enabled — practically every RHEL and OpenShift estate — that can break running workloads. Kubernetes published a &lt;a href=&quot;https://kubernetes.io/blog/2026/04/22/breaking-changes-in-selinux-volume-labeling/&quot;&gt;dedicated blog post&lt;/a&gt; on 22 April 2026 that makes the affected patterns identifiable in a v1.36 cluster. Without SELinux, nothing changes.&lt;/p&gt;
&lt;h2 id=&quot;when-you-should-not-upgrade&quot;&gt;When you should not upgrade&lt;/h2&gt;
&lt;p&gt;Do not upgrade to v1.37.0. Wait for v1.37.1 or v1.37.2, unless you have a concrete reason. On release day a .0 is the least production-exercised state of a version, and the history of the urgent upgrade notes gives little cause for optimism.&lt;/p&gt;
&lt;p&gt;The trade-off is real and cuts both ways: waiting accumulates distance from v1.34, whose support ends on 27 October 2026. Because kube-apiserver may not skip minor versions, that is three sequential upgrades. Starting only in October means three jumps under time pressure rather than one at leisure. Every jump is operational effort, and operational effort is the largest item when you &lt;a href=&quot;https://devs-group.ch/en/blog/what-kubernetes-costs-in-switzerland/&quot;&gt;work through what Kubernetes really costs in Switzerland&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Our line: anyone on v1.36 has peace until June 2027 and can wait for .2. Anyone on v1.34 starts this week, and starts with v1.35, not v1.37. And anyone in the middle of a quarterly close or running a release freeze postpones the whole thing deliberately and in writing, rather than forgetting it.&lt;/p&gt;
&lt;p&gt;Also, do not put everything into the same maintenance window. &lt;a href=&quot;https://kubernetes.io/blog/2026/01/29/ingress-nginx-statement/&quot;&gt;Ingress NGINX was retired in March 2026&lt;/a&gt; with no further security patches, backed according to internal Datadog research by around half of all cloud-native environments. Moving to the Gateway API is its own project with its own risk, not a sideshow of a minor upgrade.&lt;/p&gt;
&lt;p&gt;A cluster is not an end in itself anyway. Before planning three upgrade rounds, it is worth asking &lt;a href=&quot;https://devs-group.ch/en/blog/kubernetes-is-not-a-goal/&quot;&gt;whether a cluster is the right answer at all&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;frequently-asked&quot;&gt;Frequently asked&lt;/h2&gt;
&lt;h3 id=&quot;how-does-a-kubernetes-cluster-upgrade-without-downtime-work&quot;&gt;How does a Kubernetes cluster upgrade without downtime work?&lt;/h3&gt;
&lt;p&gt;A downtime-free upgrade raises the control plane first, then the nodes. kube-apiserver goes first, then controller-manager and scheduler, and finally kubelet and kube-proxy. Workloads stay available when every application has at least two replicas, a sensible PodDisruptionBudget and working readiness probes. Nodes are drained one at a time, not in parallel.&lt;/p&gt;
&lt;h3 id=&quot;which-kubernetes-version-is-still-supported&quot;&gt;Which Kubernetes version is still supported?&lt;/h3&gt;
&lt;p&gt;Supported are v1.35, v1.36 and v1.37 once v1.37 is out. v1.34 reaches end of life on 27 October 2026. Kubernetes maintains the three most recent minor releases as a rule, which works out to roughly a year of patch support per version. Managed providers differ: Exoscale SKS gives around 14 months.&lt;/p&gt;
&lt;h3 id=&quot;how-do-i-check-which-kube-proxy-mode-my-cluster-uses&quot;&gt;How do I check which kube-proxy mode my cluster uses?&lt;/h3&gt;
&lt;p&gt;The command &lt;code&gt;kubectl -n kube-system get configmap kube-proxy -o jsonpath=&amp;#39;{.data.config\.conf}&amp;#39; | grep &amp;#39;mode:&amp;#39;&lt;/code&gt; reads the mode out of the ConfigMap. An empty value means the default, currently &lt;code&gt;iptables&lt;/code&gt; on Linux. If it says &lt;code&gt;ipvs&lt;/code&gt;, plan the migration to &lt;code&gt;nftables&lt;/code&gt;: from v1.40 IPVS is off by default, and from v1.43 it is removed.&lt;/p&gt;
&lt;h3 id=&quot;do-i-have-to-move-to-containerd-2x-for-kubernetes-v137&quot;&gt;Do I have to move to containerd 2.x for Kubernetes v1.37?&lt;/h3&gt;
&lt;p&gt;No, not necessarily. Removal of the kubelet CRI fallback has been postponed to v1.38. Extended support for containerd 1.7 does however end in September 2026 and covers only Kubernetes 1.30 through 1.32 on GKE. In practice: plan the move to containerd 2.3 now, but not in the same maintenance window as the v1.37 upgrade.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;strong&gt;A little over two months until v1.34 reaches end of life.&lt;/strong&gt; To v1.37 that is three sequential jumps, and each needs its own maintenance window. We will walk the path with you once: which version first, what breaks at each jump, how long it realistically takes. At the end you have an order of operations, not a presentation.&lt;/p&gt;
&lt;p&gt;Write to us through &lt;a href=&quot;https://devs-group.ch/en/contact/&quot;&gt;contact&lt;/a&gt; with the version you are running and how many clusters hang off it, and we will prepare the path. Or book &lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;a slot&lt;/a&gt; directly. What else we build and operate in clusters is under &lt;a href=&quot;https://devs-group.ch/en/services/kubernetes-and-infrastructure/&quot;&gt;Kubernetes and infrastructure&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>Cloud &amp; Infrastructure</category><author>Ralph Segi</author></item><item><title>What Kubernetes really costs in Switzerland</title><link>https://devs-group.ch/en/blog/what-kubernetes-costs-in-switzerland/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/what-kubernetes-costs-in-switzerland/</guid><description>A provider comparison in CHF, a fully worked cost model, and the item everyone underestimates: operations cost more than the infrastructure.</description><pubDate>Tue, 25 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A production Kubernetes cluster with three worker nodes of 4 vCPU and 16 GB RAM each, 500 GB of storage and a control plane with an SLA costs between CHF 158 and CHF 779 a month at Swiss providers. That is the smaller half of the bill. Operations start at CHF 1,730 a month, calculated at four hours a week at the lowest Swiss hourly rate.&lt;/p&gt;
&lt;p&gt;Both figures are a model calculation. Every input comes from a public price list, retrieved on 19 August 2026. For context we worked the same scenario through on Azure in Switzerland North and on AWS in Europe (Zurich). The derivation is below, item by item.&lt;/p&gt;
&lt;h2 id=&quot;why-searching-for-kubernetes-prices-in-switzerland-comes-up-empty&quot;&gt;Why searching for Kubernetes prices in Switzerland comes up empty&lt;/h2&gt;
&lt;p&gt;Search for Kubernetes costs and you find calculators from AWS, Azure and Google in USD and EUR. For Swiss providers there is not a single worked comparison in CHF. That is the reason for this article.&lt;/p&gt;
&lt;p&gt;Instead, two figures circulate that both come from search summaries rather than from checked price lists: “managed Kubernetes from CHF 100 a month” and “CHF 150 to 400 a month”. We looked both up. Both are wrong, in different directions.&lt;/p&gt;
&lt;p&gt;The cheapest published entry point is considerably lower. At &lt;a href=&quot;https://www.infomaniak.com/de/hosting/public-cloud/kubernetes&quot;&gt;Infomaniak&lt;/a&gt; the shared control plane is free, and three worker nodes with 2 vCPU and 4 GB RAM cost CHF 0.01043 per hour per node, so around CHF 23 a month together. With a load balancer, a minimal cluster lands at roughly CHF 33.&lt;/p&gt;
&lt;p&gt;And a cluster at production size is considerably higher than CHF 400. At &lt;a href=&quot;https://doc.flow.swiss/platform/pricing/kubernetes&quot;&gt;Flow Swiss&lt;/a&gt; a single worker node with 4 vCPU and 16 GB RAM alone costs CHF 197 a month. Three of them are mandatory.&lt;/p&gt;
&lt;h2 id=&quot;the-four-cost-blocks-the-bill-is-made-of&quot;&gt;The four cost blocks the bill is made of&lt;/h2&gt;
&lt;p&gt;A Kubernetes bill consists of control plane, worker nodes, storage plus traffic, and operations. The first three are on the price list. The fourth is nowhere and is the largest.&lt;/p&gt;
&lt;p&gt;First, because it is the second most common search in this area: Kubernetes is open source under the &lt;a href=&quot;https://github.com/kubernetes/kubernetes/blob/master/LICENSE&quot;&gt;Apache 2.0 licence&lt;/a&gt; and therefore free of licence cost. Operating it is not. You do not pay a licence, you pay for machines and people.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Control plane.&lt;/strong&gt; The price for the managed control plane. Exoscale and Infomaniak give it away in the basic variant, though then without an SLA. At Flow Swiss it costs CHF 73 a month per cluster, independent of cluster size.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Worker nodes.&lt;/strong&gt; The largest item on the bill. Between Swiss providers the differences here are factors, not percentages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Storage and traffic.&lt;/strong&gt; Block storage is billed per GiB-hour. Egress models differ sharply: Infomaniak gives bandwidth away free as a matter of course, Flow Swiss includes 20 TB per organisation per month, and Exoscale grants 1.42 GiB per hour per instance and charges CHF 0.02 per GiB after that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Operations.&lt;/strong&gt; Upgrades, certificates, network policies, alerts at three in the morning. This block appears on no price list, because no provider sells it.&lt;/p&gt;
&lt;p&gt;This is exactly where budgets slip. A managed Kubernetes offering from Infomaniak or Exoscale takes on the control plane, the etcd backups and node provisioning. Everything above the Kubernetes API stays with you: ingress, certificate rotation, RBAC, backups of application data, alerting, and the question of who plans the upgrade path.&lt;/p&gt;
&lt;h2 id=&quot;what-managed-kubernetes-costs-the-swiss-provider-comparison&quot;&gt;What managed Kubernetes costs: the Swiss provider comparison&lt;/h2&gt;
&lt;p&gt;Of seven Swiss providers, four publish a price list and three do not. All values in the table below come from the linked price lists, retrieved 19 August 2026, excluding VAT.&lt;/p&gt;































































































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Provider&lt;/th&gt;&lt;th&gt;Control plane&lt;/th&gt;&lt;th&gt;Worker 4 vCPU / 16 GB&lt;/th&gt;&lt;th&gt;Block storage&lt;/th&gt;&lt;th&gt;Egress&lt;/th&gt;&lt;th&gt;Data location&lt;/th&gt;&lt;th&gt;SLA&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://www.exoscale.com/pricing/&quot;&gt;Exoscale SKS&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Starter free, Pro CHF 0.054995/h&lt;/td&gt;&lt;td&gt;CHF 0.18667/h&lt;/td&gt;&lt;td&gt;CHF 0.00014 per GiB/h&lt;/td&gt;&lt;td&gt;1.42 GiB/h per instance free, then CHF 0.02/GiB&lt;/td&gt;&lt;td&gt;zones ch-gva-2, ch-dk-2&lt;/td&gt;&lt;td&gt;99.95 % (Pro)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://www.infomaniak.com/de/hosting/public-cloud/kubernetes&quot;&gt;Infomaniak&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Shared free, dedicated 4 from CHF 0.04/h&lt;/td&gt;&lt;td&gt;CHF 0.03429/h&lt;/td&gt;&lt;td&gt;CHF 0.00012 per GiB/h&lt;/td&gt;&lt;td&gt;included&lt;/td&gt;&lt;td&gt;own data centres in Switzerland&lt;/td&gt;&lt;td&gt;99.9 % (dedicated)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://doc.flow.swiss/platform/pricing/kubernetes&quot;&gt;Flow Swiss&lt;/a&gt;&lt;/td&gt;&lt;td&gt;CHF 73/month per cluster&lt;/td&gt;&lt;td&gt;CHF 197/month (k1.4x16)&lt;/td&gt;&lt;td&gt;CHF 0.22 per GB/month&lt;/td&gt;&lt;td&gt;20 TB per organisation included&lt;/td&gt;&lt;td&gt;ZRH1 Zurich, ALP1 Lucerne&lt;/td&gt;&lt;td&gt;99.9 %&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://www.cloudscale.ch/de/preise&quot;&gt;cloudscale.ch&lt;/a&gt;&lt;/td&gt;&lt;td&gt;no managed Kubernetes of its own&lt;/td&gt;&lt;td&gt;CHF 4.40 per 24 h (Flex-16-4)&lt;/td&gt;&lt;td&gt;published&lt;/td&gt;&lt;td&gt;100 GB per 24 h included&lt;/td&gt;&lt;td&gt;Switzerland&lt;/td&gt;&lt;td&gt;99.99 %&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://products.vshn.ch/openshift/pricing.html&quot;&gt;VSHN / APPUiO&lt;/a&gt;&lt;/td&gt;&lt;td&gt;included in the service price&lt;/td&gt;&lt;td&gt;from CHF 4,900/month for the whole cluster&lt;/td&gt;&lt;td&gt;client’s infrastructure&lt;/td&gt;&lt;td&gt;client’s infrastructure&lt;/td&gt;&lt;td&gt;free choice&lt;/td&gt;&lt;td&gt;up to 99.99 % 24/7&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://www.metanet.ch/de/solutions/managed-kubernetes&quot;&gt;Metanet&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;no public price list&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;no public price list&lt;/td&gt;&lt;td&gt;no public price list&lt;/td&gt;&lt;td&gt;no public price list&lt;/td&gt;&lt;td&gt;Switzerland, ISO 27001&lt;/td&gt;&lt;td&gt;enterprise SLA on request&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://www.swisscom.ch/de/business/enterprise/angebot/cloud/cloudnative/container-services.html&quot;&gt;Swisscom&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;no public price list&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;no public price list&lt;/td&gt;&lt;td&gt;no public price list&lt;/td&gt;&lt;td&gt;no public price list&lt;/td&gt;&lt;td&gt;Switzerland&lt;/td&gt;&lt;td&gt;not published&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://azure.microsoft.com/de-de/pricing/details/kubernetes-service/&quot;&gt;Azure AKS&lt;/a&gt;, Switzerland North&lt;/td&gt;&lt;td&gt;free tier free, Standard CHF 0.082/h&lt;/td&gt;&lt;td&gt;CHF 0.2075/h (D4s v5)&lt;/td&gt;&lt;td&gt;CHF 72.68/month (P20, 512 GiB)&lt;/td&gt;&lt;td&gt;100 GB/month free, then CHF 0.0656/GB&lt;/td&gt;&lt;td&gt;Switzerland North&lt;/td&gt;&lt;td&gt;free without SLA, Standard with a stated uptime SLA&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://aws.amazon.com/eks/pricing/&quot;&gt;AWS EKS&lt;/a&gt;, Europe (Zurich)&lt;/td&gt;&lt;td&gt;USD 0.10/h per cluster&lt;/td&gt;&lt;td&gt;USD 0.253/h (m6i.xlarge)&lt;/td&gt;&lt;td&gt;USD 0.1142 per GB/month (gp3)&lt;/td&gt;&lt;td&gt;USD 0.09/GB up to 10 TB&lt;/td&gt;&lt;td&gt;eu-central-2&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://aws.amazon.com/eks/sla/&quot;&gt;99.95 %&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Three notes. First, Flow Swiss computes monthly prices with 730 hours and Exoscale with 720; we standardise on 730 below. Second, AWS publishes only USD for the Zurich region, while Azure delivers genuine CHF prices for Switzerland North through the retail prices API. We convert nothing, because we have no defensible reference rate for a given day.&lt;/p&gt;
&lt;p&gt;Third, Exoscale has belonged to &lt;a href=&quot;https://newsroom.a1.com/news-a1-digital-erwirbt-schweizer-cloud-anbieter-exoscale?id=65425&amp;amp;menueid=13799&amp;amp;l=deutsch&quot;&gt;A1 Digital&lt;/a&gt;, a subsidiary of the Telekom Austria Group, since 2017. The data sits in Switzerland; the ownership does not. For revDSG questions both count.&lt;/p&gt;
&lt;h2 id=&quot;the-model-calculation-the-same-cluster-at-five-providers&quot;&gt;The model calculation: the same cluster at five providers&lt;/h2&gt;
&lt;p&gt;The same reference scenario costs between CHF 158.10 at Infomaniak and CHF 779.00 at Flow Swiss. The scenario: three worker nodes of 4 vCPU and 16 GB RAM each, 500 GB of persistent storage, a control plane with an SLA, one load balancer, egress under 1 TB, calculated at 730 hours.&lt;/p&gt;





















































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Provider&lt;/th&gt;&lt;th&gt;3 worker nodes&lt;/th&gt;&lt;th&gt;Control plane&lt;/th&gt;&lt;th&gt;Storage&lt;/th&gt;&lt;th&gt;Load balancer&lt;/th&gt;&lt;th&gt;Total per month&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Infomaniak&lt;/td&gt;&lt;td&gt;CHF 75.10&lt;/td&gt;&lt;td&gt;CHF 29.20&lt;/td&gt;&lt;td&gt;CHF 43.80&lt;/td&gt;&lt;td&gt;CHF 10.00&lt;/td&gt;&lt;td&gt;&lt;strong&gt;CHF 158.10&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Exoscale&lt;/td&gt;&lt;td&gt;CHF 408.81&lt;/td&gt;&lt;td&gt;CHF 40.15&lt;/td&gt;&lt;td&gt;CHF 66.43&lt;/td&gt;&lt;td&gt;CHF 25.35&lt;/td&gt;&lt;td&gt;&lt;strong&gt;CHF 540.74&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Azure AKS, Switzerland North&lt;/td&gt;&lt;td&gt;CHF 454.42&lt;/td&gt;&lt;td&gt;CHF 59.86&lt;/td&gt;&lt;td&gt;CHF 72.68&lt;/td&gt;&lt;td&gt;CHF 14.78&lt;/td&gt;&lt;td&gt;&lt;strong&gt;CHF 601.74&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Flow Swiss&lt;/td&gt;&lt;td&gt;CHF 591.00&lt;/td&gt;&lt;td&gt;CHF 73.00&lt;/td&gt;&lt;td&gt;CHF 110.00&lt;/td&gt;&lt;td&gt;CHF 5.00&lt;/td&gt;&lt;td&gt;&lt;strong&gt;CHF 779.00&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AWS EKS, Europe (Zurich)&lt;/td&gt;&lt;td&gt;USD 554.07&lt;/td&gt;&lt;td&gt;USD 73.00&lt;/td&gt;&lt;td&gt;USD 57.10&lt;/td&gt;&lt;td&gt;USD 21.68&lt;/td&gt;&lt;td&gt;&lt;strong&gt;USD 705.85&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;A model calculation, not a quotation. Prices excluding VAT, retrieved 19 August 2026 from the price lists linked above. The storage row additionally contains the node disks at Exoscale (3 × 50 GiB), which are billed separately there; at Infomaniak 50 GB per node is included in the flavour; at Azure it corresponds to a premium SSD P20 with 512 GiB. Egress falls within the free allowance or is free at all five.&lt;/p&gt;
&lt;p&gt;Price is not performance. Infomaniak runs shared vCPUs in an OpenStack cluster and positions itself &lt;a href=&quot;https://www.infomaniak.com/de/hosting/public-cloud/tarife&quot;&gt;explicitly on price&lt;/a&gt;. Flow Swiss delivers dedicated resources, a 99.9 per cent SLA and two Swiss regions. What the table does not capture: support quality, response time during incidents, API maturity, how quickly a new Kubernetes minor version becomes available.&lt;/p&gt;
&lt;h2 id=&quot;what-kubernetes-costs-on-azure-and-aws-in-switzerland&quot;&gt;What Kubernetes costs on Azure and AWS in Switzerland&lt;/h2&gt;
&lt;p&gt;Azure AKS in Switzerland North costs CHF 601.74 a month in the same scenario, AWS EKS in Europe (Zurich) USD 705.85. Both therefore sit within the field of Swiss providers rather than above it. That refutes the widespread assumption that a hyperscaler is automatically the expensive option.&lt;/p&gt;
&lt;p&gt;Azure is cheaper than Flow Swiss and dearer than Exoscale. Infomaniak undercuts Azure by nearly a factor of four. The price spread within Switzerland is therefore larger than the gap between Switzerland and the hyperscalers.&lt;/p&gt;
&lt;p&gt;Two differences that appear on no price list. First, egress: Azure gives 100 GB a month free and charges CHF 0.0656 per GB after that, AWS USD 0.09 per GB up to 10 TB. At Infomaniak bandwidth is free; at Flow Swiss 20 TB is included. Shipping 2 TB (2,000 GB) a month costs an additional CHF 124.64 at Azure and nothing at Infomaniak.&lt;/p&gt;
&lt;p&gt;Second, the legal position. Microsoft and AWS operate Swiss regions but as US groups are subject to the &lt;a href=&quot;https://www.justice.gov/criminal/cloud-act-resources&quot;&gt;CLOUD Act&lt;/a&gt;. For revDSG reviews with elevated protection needs, that is the point where the pricing question ends and the architecture question begins.&lt;/p&gt;
&lt;h2 id=&quot;what-operations-cost-swiss-hourly-rates-as-the-unit&quot;&gt;What operations cost: Swiss hourly rates as the unit&lt;/h2&gt;
&lt;p&gt;Operations cost a multiple of the infrastructure. &lt;a href=&quot;https://www.gryps.ch/produkte/softwareentwicklung-13/individualsoftware-kosten/&quot;&gt;gryps.ch&lt;/a&gt; gives, for software engineering in German-speaking Switzerland, CHF 100 to 150 an hour for junior profiles and CHF 140 to 180 for senior profiles, with CHF 160 as the midpoint in the published example (retrieved 19 August 2026). Running a cluster is senior work.&lt;/p&gt;





























&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Operational effort&lt;/th&gt;&lt;th&gt;CHF 100/h&lt;/th&gt;&lt;th&gt;CHF 140/h&lt;/th&gt;&lt;th&gt;CHF 180/h&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;4 h per week (17.3 h/month)&lt;/td&gt;&lt;td&gt;CHF 1,730&lt;/td&gt;&lt;td&gt;CHF 2,430&lt;/td&gt;&lt;td&gt;CHF 3,120&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;10 h per week (43.3 h/month)&lt;/td&gt;&lt;td&gt;CHF 4,330&lt;/td&gt;&lt;td&gt;CHF 6,070&lt;/td&gt;&lt;td&gt;CHF 7,800&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;20 h per week (86.7 h/month)&lt;/td&gt;&lt;td&gt;CHF 8,670&lt;/td&gt;&lt;td&gt;CHF 12,130&lt;/td&gt;&lt;td&gt;CHF 15,600&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The row that matters: even the most cautious assumption, four hours a week at the lowest published rate, comes to CHF 1,730 a month. That is more than the most expensive infrastructure bill in the comparison above.&lt;/p&gt;
&lt;p&gt;Four hours a week is tight for a production cluster. Kubernetes ships three minor versions a year, each with deprecations. How much preparation a single upgrade costs we worked through on the example of &lt;a href=&quot;https://devs-group.ch/en/blog/kubernetes-1-37-upgrade-without-downtime/&quot;&gt;1.37&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Work your own case through completely once, infrastructure plus person-days. We do that with you in one session, with your node sizes and your price list, and you keep the table. &lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;Thirty minutes with an engineer who runs Swiss clusters&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;managed-self-operated-or-operated-for-you&quot;&gt;Managed, self-operated, or operated for you&lt;/h2&gt;
&lt;p&gt;There are three operating models, and they differ less in price than in who gets up at night. The matrix below sorts them along the criteria that actually come up in budget discussions.&lt;/p&gt;





















































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Criterion&lt;/th&gt;&lt;th&gt;Managed at the provider&lt;/th&gt;&lt;th&gt;Self-operated on IaaS&lt;/th&gt;&lt;th&gt;Operated for you&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Infrastructure per month&lt;/td&gt;&lt;td&gt;CHF 158 to 779&lt;/td&gt;&lt;td&gt;CHF 132 per node per 30 days at cloudscale.ch&lt;/td&gt;&lt;td&gt;as managed or IaaS&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Internal operational effort&lt;/td&gt;&lt;td&gt;4 to 10 h a week&lt;/td&gt;&lt;td&gt;10 to 20 h a week&lt;/td&gt;&lt;td&gt;close to zero&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Additional operating cost&lt;/td&gt;&lt;td&gt;CHF 1,730 to 6,070&lt;/td&gt;&lt;td&gt;CHF 4,330 to 12,130&lt;/td&gt;&lt;td&gt;from CHF 4,900 at VSHN&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Control plane&lt;/td&gt;&lt;td&gt;provider&lt;/td&gt;&lt;td&gt;you&lt;/td&gt;&lt;td&gt;partner&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Vendor lock-in&lt;/td&gt;&lt;td&gt;high&lt;/td&gt;&lt;td&gt;low&lt;/td&gt;&lt;td&gt;low&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Data location controllable&lt;/td&gt;&lt;td&gt;provider zones only&lt;/td&gt;&lt;td&gt;fully&lt;/td&gt;&lt;td&gt;fully&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Fits when&lt;/td&gt;&lt;td&gt;one team, clear workloads&lt;/td&gt;&lt;td&gt;compliance demands your own control&lt;/td&gt;&lt;td&gt;the platform is critical and the team is small&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The price for “operated for you” is public: &lt;a href=&quot;https://products.vshn.ch/openshift/pricing.html&quot;&gt;VSHN publishes&lt;/a&gt; CHF 4,900 a month as the baseline for managed OpenShift on cloud provider infrastructure, CHF 7,500 with 99.9 per cent around the clock, and CHF 10,100 at 99.99 per cent (retrieved 19 August 2026). The hourly rate for additional work is CHF 250.&lt;/p&gt;
&lt;p&gt;This is where the gap in the Swiss market sits. Exoscale, Infomaniak, Flow Swiss, cloudscale.ch, Metanet and Swisscom sell infrastructure. VSHN sells its own platform. Nobody sells “we build and operate your platform on your infrastructure, with your tools”. That is precisely our position, which is why it appears here in the matrix rather than in an advertising box.&lt;/p&gt;
&lt;h2 id=&quot;where-the-over-provisioning-nobody-sees-is-hiding&quot;&gt;Where the over-provisioning nobody sees is hiding&lt;/h2&gt;
&lt;p&gt;The infrastructure bill is created at a point that lives in the code, not in the portal. Kubernetes schedules pods by &lt;code&gt;requests&lt;/code&gt;, not by actual load. Requesting generously means paying for nodes worth of capacity that is never used.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;resources&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  requests&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    cpu&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    memory&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;4Gi&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  limits&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    cpu&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    memory&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;4Gi&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four such pods occupy 8 vCPU and thereby tie up, on the CPU side, two of the worker nodes from our reference scenario. At Flow Swiss that is CHF 394 a month, at Infomaniak CHF 50.06. Whether the pods use 5 per cent or 95 per cent of it is not visible on the bill.&lt;/p&gt;
&lt;p&gt;Comparing requested against consumed takes two commands:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;kubectl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; get&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pods&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; custom-columns=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;NS:.metadata.namespace,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;POD:.metadata.name,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;CPU_REQ:.spec.containers[*].resources.requests.cpu,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MEM_REQ:.spec.containers[*].resources.requests.memory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;kubectl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; top&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pods&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -A&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --containers&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wherever the second output sits durably far below the first, you are paying for air. That is the only lever in this article that works without changing provider.&lt;/p&gt;
&lt;h2 id=&quot;when-the-cluster-does-not-pay-for-itself&quot;&gt;When the cluster does not pay for itself&lt;/h2&gt;
&lt;p&gt;Below roughly five services and one team, Kubernetes does not pay for itself in Switzerland. At CHF 158 of infrastructure and CHF 2,430 of operations a month, around 94 per cent of the bill goes on operational knowledge that a single well-sized VM with Docker Compose, &lt;code&gt;systemd&lt;/code&gt; and a clean deploy script does not need.&lt;/p&gt;
&lt;p&gt;We advise against it in those cases, even though we build and run Kubernetes. The honest question is in &lt;a href=&quot;https://devs-group.ch/en/blog/kubernetes-is-not-a-goal/&quot;&gt;Kubernetes is not a goal&lt;/a&gt;: what happens when this service falls over at three in the morning, and who looks at it? If the answer is “an alert, a runbook, a restart”, you do not need an orchestrator.&lt;/p&gt;
&lt;p&gt;The cluster pays for itself as soon as several teams deploy independently, load spikes are unpredictable, or compliance demands reproducible, declarative infrastructure with an audit trail. For &lt;a href=&quot;https://devs-group.ch/en/projects/onocoy/&quot;&gt;onocoy&lt;/a&gt; and &lt;a href=&quot;https://devs-group.ch/en/projects/relai/&quot;&gt;Relai&lt;/a&gt; exactly that applies. For an application with one database and known traffic it does not.&lt;/p&gt;
&lt;h2 id=&quot;what-changes-on-the-bill-over-twelve-months&quot;&gt;What changes on the bill over twelve months&lt;/h2&gt;
&lt;p&gt;Over twelve months it is mostly the worker nodes that move, and in both directions. Right-sizing the requests typically extracts the most, because it directly reduces the number of nodes.&lt;/p&gt;
&lt;p&gt;Cluster autoscaling only helps with variable load. With constant baseline load it does not move the bill, it complicates it. Infomaniak and Exoscale support node pools with a minimum and a maximum; setting both to the same value gives you autoscaling in name only.&lt;/p&gt;
&lt;p&gt;What is missing in Switzerland: reservations. Neither Exoscale nor Infomaniak nor Flow Swiss publishes discounts for term commitments of the kind AWS offers with savings plans. Planning to save 30 per cent through prepayment means planning with an instrument that does not exist here.&lt;/p&gt;
&lt;p&gt;The operations side moves the other way. Effort is highest in the first year, because ingress, secrets handling, backup strategy and CI integration are only being built. After that it settles to a background hum of upgrades and incidents, and that background hum is the number to plan with, not the build-out effort.&lt;/p&gt;
&lt;p&gt;The item that blows this calculation apart fastest is not in it: GPUs. A single inference card costs more than the entire cluster above. What sovereign AI inference costs on Swiss infrastructure, and from what request volume self-operation beats an API, is in &lt;a href=&quot;https://devs-group.ch/en/blog/sovereign-ai-inference-switzerland/&quot;&gt;sovereign AI inference in Switzerland&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;frequently-asked&quot;&gt;Frequently asked&lt;/h2&gt;
&lt;h3 id=&quot;what-does-a-managed-kubernetes-cluster-cost-per-month-in-switzerland&quot;&gt;What does a managed Kubernetes cluster cost per month in Switzerland?&lt;/h3&gt;
&lt;p&gt;A cluster with three worker nodes of 4 vCPU and 16 GB RAM each, 500 GB of storage and a control plane with an SLA costs between CHF 158 and CHF 779 a month, depending on the provider. The minimum for a very small cluster at Infomaniak is around CHF 33. All values from published price lists, retrieved 19 August 2026, excluding VAT.&lt;/p&gt;
&lt;h3 id=&quot;isnt-kubernetes-free&quot;&gt;Isn’t Kubernetes free?&lt;/h3&gt;
&lt;p&gt;Kubernetes is open source under the Apache 2.0 licence, and there are no licence costs. Costs arise from machines and people: control plane, worker nodes, storage and traffic come to CHF 158 to 779 a month for a cluster at production size, plus operational effort from CHF 1,730 a month. The licence-free part is the smallest part of the bill.&lt;/p&gt;
&lt;h3 id=&quot;what-does-kubernetes-cost-on-azure-and-aws-in-switzerland&quot;&gt;What does Kubernetes cost on Azure and AWS in Switzerland?&lt;/h3&gt;
&lt;p&gt;Azure AKS in the Switzerland North region costs CHF 601.74 a month in the reference scenario, AWS EKS in Europe (Zurich) USD 705.85. Both sit within the field of Swiss providers: dearer than Exoscale at CHF 540.74, cheaper than Flow Swiss at CHF 779.00. AWS publishes only USD prices for Zurich; Azure delivers genuine CHF prices (as of 19 August 2026).&lt;/p&gt;
&lt;h3 id=&quot;as-an-sme-do-i-need-kubernetes-at-all-or-is-docker-compose-enough&quot;&gt;As an SME do I need Kubernetes at all, or is Docker Compose enough?&lt;/h3&gt;
&lt;p&gt;Docker Compose on a VM is enough as long as there is one application, one database and one team, and traffic is known. Kubernetes earns its keep from several independently deploying teams, unpredictable load spikes, or compliance requirements for declarative infrastructure. Operating costs from CHF 1,730 a month upward decide this question more often than the infrastructure costs do.&lt;/p&gt;
&lt;h3 id=&quot;does-my-data-stay-in-switzerland-with-a-swiss-kubernetes-provider&quot;&gt;Does my data stay in Switzerland with a Swiss Kubernetes provider?&lt;/h3&gt;
&lt;p&gt;That depends on the provider and the zone chosen. Infomaniak operates its own data centres in Switzerland, Flow Swiss the regions ZRH1 and ALP1. Exoscale offers the Swiss zones ch-gva-2 and ch-dk-2, but has belonged to the Telekom Austria Group since 2017. For revDSG reviews, ownership matters alongside location.&lt;/p&gt;
&lt;h2 id=&quot;lets-work-through-your-cluster&quot;&gt;Let’s work through your cluster&lt;/h2&gt;
&lt;p&gt;Bring your node sizes, your storage class, and an honest estimate of the internal hours. We put the current price lists next to them and work both sides of the bill, infrastructure and operations, through in one session.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;Book a slot&lt;/a&gt; or &lt;a href=&quot;https://devs-group.ch/en/contact/&quot;&gt;write to us&lt;/a&gt; if you would rather ask by email. What we build and operate in this area is under &lt;a href=&quot;https://devs-group.ch/en/services/kubernetes-and-infrastructure/&quot;&gt;Kubernetes and infrastructure&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>Cloud &amp; Infrastructure</category><author>Ralph Segi</author></item><item><title>Sovereign AI inference in Switzerland: the architecture</title><link>https://devs-group.ch/en/blog/sovereign-ai-inference-switzerland/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/sovereign-ai-inference-switzerland/</guid><description>A provider comparison in CHF, GPU scheduling with DRA, and the points where sovereignty actually breaks. Usually it is not the model.</description><pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sovereign AI inference is a property of the whole data flow, not of the model. An Apertus model hosted in Zurich is not sovereign if the traces land at a US monitoring service, the vector database sits in Ireland, or support from a third country reads the logs. An architecture is sovereign only when prompt, context, weights, logs and metadata never leave the same jurisdiction.&lt;/p&gt;
&lt;p&gt;A great deal has been written about digital sovereignty. ipt, for instance, runs a page titled “Sovereign AI platforms on Kubernetes” and has worked the subject with the ETH AI Center. What is missing is the layer beneath: which providers exist, what they cost, what operating them looks like.&lt;/p&gt;
&lt;p&gt;We work it through on the Swiss market, because prices and legal position there are publicly checkable. The architecture underneath is the same whether the sovereign cloud sits in Zurich, Frankfurt or Paris.&lt;/p&gt;
&lt;h2 id=&quot;what-sovereign-ai-inference-means-technically&quot;&gt;What sovereign AI inference means technically&lt;/h2&gt;
&lt;p&gt;Sovereignty can be checked against five artefacts produced by every inference request: model weights, prompt, retrieved context, generated response, and the telemetry about it. Each has a physical location and a legally entitled accessor, and those two are not the same thing.&lt;/p&gt;
&lt;p&gt;Swiss law turns that into a concrete test. Art. 16 revDSG permits disclosure of personal data abroad only where the Federal Council has assessed the destination state as adequate.&lt;/p&gt;
&lt;p&gt;The list is in Annex 1 of the Data Protection Ordinance (SR 235.11). The USA has been on it &lt;a href=&quot;https://www.edoeb.admin.ch/de/bekanntgabe-von-personendaten-ins-ausland&quot;&gt;since 15 September 2024&lt;/a&gt;, but only for organisations certified under the Swiss-US Data Privacy Framework.&lt;/p&gt;
&lt;p&gt;The second test runs through the provider itself. &lt;a href=&quot;https://www.law.cornell.edu/uscode/text/18/2713&quot;&gt;18 U.S.C. § 2713&lt;/a&gt;, introduced with the CLOUD Act, obliges US providers to hand over stored data “regardless of whether such communication, record, or other information is located within or outside of the United States”.&lt;/p&gt;
&lt;p&gt;A data centre in Zurich changes nothing about that if the operating company is subject to US corporate law. Which architecture decisions follow is in &lt;a href=&quot;https://devs-group.ch/en/blog/revdsg-compliant-ai-architecture/&quot;&gt;A revDSG-compliant AI architecture&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;which-swiss-cloud-providers-offer-sovereign-ai-inference&quot;&gt;Which Swiss cloud providers offer sovereign AI inference&lt;/h2&gt;
&lt;p&gt;Four providers realistically come into question for production GPU inference in Switzerland, plus self-operation. Only one of them publishes complete prices.&lt;/p&gt;





















































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Provider&lt;/th&gt;&lt;th&gt;GPU types&lt;/th&gt;&lt;th&gt;Billing model&lt;/th&gt;&lt;th&gt;Data location&lt;/th&gt;&lt;th&gt;Kubernetes access&lt;/th&gt;&lt;th&gt;Public prices&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Exoscale&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;RTX Pro 6000 (96 GB), A40, A5000, A30, V100, 3080ti; B300 on request&lt;/td&gt;&lt;td&gt;per instance-hour, dedicated inference per second&lt;/td&gt;&lt;td&gt;depending on GPU type CH-DK-2 (Switzerland), DE-FRA-1, AT-VIE-2, HR-ZAG-1&lt;/td&gt;&lt;td&gt;yes, SKS&lt;/td&gt;&lt;td&gt;&lt;strong&gt;yes&lt;/strong&gt;, complete in CHF&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Infomaniak&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;B300, H200, H100, A100, L40S, L4, A2, T4&lt;/td&gt;&lt;td&gt;GPU instance per hour, AI services per token&lt;/td&gt;&lt;td&gt;Geneva and Zurich&lt;/td&gt;&lt;td&gt;yes, Kubernetes service&lt;/td&gt;&lt;td&gt;&lt;strong&gt;partly&lt;/strong&gt;: token prices published, instance prices only in the configurator&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Swisscom Swiss AI Platform&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;NVIDIA SuperPOD&lt;/td&gt;&lt;td&gt;GPU as a service, plus inference endpoints per token&lt;/td&gt;&lt;td&gt;Switzerland&lt;/td&gt;&lt;td&gt;not documented&lt;/td&gt;&lt;td&gt;&lt;strong&gt;no&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Phoenix Technologies&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;H100, H200&lt;/td&gt;&lt;td&gt;not published&lt;/td&gt;&lt;td&gt;Switzerland&lt;/td&gt;&lt;td&gt;not documented&lt;/td&gt;&lt;td&gt;&lt;strong&gt;no&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Self-operation, colocation&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;free choice&lt;/td&gt;&lt;td&gt;CapEx plus housing&lt;/td&gt;&lt;td&gt;free choice&lt;/td&gt;&lt;td&gt;yes, self-built&lt;/td&gt;&lt;td&gt;n/a&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;One detail that counts in a sovereignty review: Exoscale’s RTX Pro 6000 instances run in DE-FRA-1, HR-ZAG-1 and CH-DK-2, and the 3080ti instances exclusively in AT-VIE-2. A Swiss provider does not automatically guarantee a Swiss zone (retrieved 19 August 2026).&lt;/p&gt;
&lt;h2 id=&quot;apertus-15-what-the-swiss-model-can-do-today&quot;&gt;Apertus 1.5: what the Swiss model can do today&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.apertus-ai.org/articles/2026-07-apertus-1-5/&quot;&gt;Apertus 1.5&lt;/a&gt; appeared on 24 July 2026 and is a different model from the September 2025 version. The context window is 262,144 tokens, a fourfold increase. The models now accept images; audio is marked experimental. Licence: Apache 2.0.&lt;/p&gt;
&lt;p&gt;For operations, tool integration matters more than multimodality. The &lt;a href=&quot;https://huggingface.co/swiss-ai/Apertus-v1.5-70B&quot;&gt;model card for Apertus-v1.5-70B&lt;/a&gt; documents a dedicated tool-call parser and an optional thinking mode, both drivable directly in vLLM. That makes Apertus usable for agent architectures, not only for chat.&lt;/p&gt;
&lt;p&gt;It was developed by EPFL, ETH Zurich and CSCS on the Alps supercomputer in Lugano. For those who do not want to run it themselves, Infomaniak lists &lt;code&gt;swiss-ai/Apertus-v1.5-70B&lt;/code&gt; as an API model, currently in beta.&lt;/p&gt;
&lt;h2 id=&quot;running-apertus-yourself-hugging-face-ollama-or-apertus-mini&quot;&gt;Running Apertus yourself: Hugging Face, Ollama or Apertus Mini&lt;/h2&gt;
&lt;p&gt;There are three routes to self-operation, with very different degrees of production readiness.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hugging Face plus vLLM&lt;/strong&gt; is the production path. &lt;code&gt;swiss-ai/Apertus-v1.5-70B&lt;/code&gt; and the 8B variant sit there under Apache 2.0 and load without a conversion step.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ollama is the fastest route to a prompt and the slowest to production.&lt;/strong&gt; There is no official Apertus build from swiss-ai; the widespread GGUF packages come from community packagers and carry the &lt;code&gt;2509&lt;/code&gt; release, i.e. Apertus 1.0. Anyone wanting 1.5 locally quantises it themselves. For a laptop test that is fine; for a service under concurrent load it is not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Apertus Mini often solves the actual problem.&lt;/strong&gt; The &lt;a href=&quot;https://huggingface.co/collections/swiss-ai/apertus-mini&quot;&gt;official collection&lt;/a&gt; contains v1.1 distillates at 0.5B, 1.5B and 4B, plus quantisations for MLX and vLLM. For classification, extraction and routing a 4B model is usually enough, and it moves the GPU cost by an order of magnitude.&lt;/p&gt;
&lt;h2 id=&quot;what-inference-on-your-own-infrastructure-costs&quot;&gt;What inference on your own infrastructure costs&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;A model calculation&lt;/strong&gt;, derived from published price lists retrieved on 19 August 2026. It applies to Apertus-v1.5-70B and compares two purchasing routes at the same level of sovereignty.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://huggingface.co/swiss-ai/Apertus-v1.5-70B&quot;&gt;model card&lt;/a&gt; gives &lt;code&gt;--tensor-parallel-size 4&lt;/code&gt; as the reference configuration at the full 262,144 tokens of context. With 72 billion parameters in BF16 that is 144 GB of weights alone, plus the KV cache. Four cards of 96 GB each are therefore the minimum, not generosity.&lt;/p&gt;
&lt;p&gt;Self-operation, Exoscale dedicated inference on RTX Pro 6000, &lt;a href=&quot;https://www.exoscale.com/pricing/&quot;&gt;published price list&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CHF 2.15277778 per GPU-hour, billed by the second&lt;/li&gt;
&lt;li&gt;4 GPUs running continuously: 4 × 2.15277778 × 720 h = &lt;strong&gt;CHF 6,200 per month&lt;/strong&gt; (720 hours, as Exoscale itself converts to monthly prices)&lt;/li&gt;
&lt;li&gt;Model storage 134 GiB (144 GB of weights) at CHF 0.0000275 per GiB-hour: CHF 2.65 per month&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;API purchase, Infomaniak AI Services, &lt;a href=&quot;https://www.infomaniak.com/en/hosting/ai-services/prices&quot;&gt;published price list&lt;/a&gt;: CHF 0.70 per million input tokens, CHF 2.50 per million output tokens for the same model.&lt;/p&gt;
&lt;p&gt;Assumed request profile: 3,000 tokens of input (system prompt plus RAG context), 500 tokens of output. That comes to CHF 0.00335 per request, or CHF 0.96 per million mixed tokens.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The break-even sits at around 1.85 million requests per month&lt;/strong&gt;, i.e. roughly 62,000 a day. Below that the API is cheaper. That corresponds to 6.5 billion tokens a month, or around 2,500 tokens per second of sustained throughput.&lt;/p&gt;
&lt;p&gt;At 2,500 tokens per second, though, the four cards run flat out around the clock, and no operation runs like that. Planning headroom for load spikes and a second zone for failures doubles the GPU cost and with it the break-even: realistically three to four million requests a month.&lt;/p&gt;
&lt;p&gt;Conversely, per-second billing helps: someone with load only during office hours who scales to zero pays CHF 1,894 for 220 hours a month rather than CHF 6,200, and the break-even falls to around 566,000 requests. The required peak capacity stays the same.&lt;/p&gt;
&lt;p&gt;Not included: the cluster underneath (Exoscale SKS Pro at CHF 0.054995 per hour, around CHF 40 a month, in detail in &lt;a href=&quot;https://devs-group.ch/en/blog/what-kubernetes-costs-in-switzerland/&quot;&gt;what Kubernetes really costs in Switzerland&lt;/a&gt;) and your own time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Recompute the break-even for your profile.&lt;/strong&gt; It moves a lot with prompt caching, a smaller model, or a different input-output ratio. If you know your request profile, &lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;we will work it through together&lt;/a&gt;, with your numbers rather than our assumptions.&lt;/p&gt;
&lt;h2 id=&quot;gpu-scheduling-on-kubernetes-dra-rather-than-device-plugins&quot;&gt;GPU scheduling on Kubernetes: DRA rather than device plugins&lt;/h2&gt;
&lt;p&gt;Dynamic Resource Allocation replaces the counting approach of “give me a GPU” with a requirement expressed over properties. The core of DRA has been &lt;a href=&quot;https://kubernetes.io/blog/2025/09/01/kubernetes-v1-34-dra-updates/&quot;&gt;GA since Kubernetes v1.34&lt;/a&gt;, with the API group &lt;code&gt;resource.k8s.io/v1&lt;/code&gt;; the &lt;a href=&quot;https://kubernetes.io/docs/concepts/scheduling-eviction/dynamic-resource-allocation/&quot;&gt;concept page in the docs&lt;/a&gt; now lists DRA as &lt;code&gt;Kubernetes v1.35 [stable]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The old device plugin only knew &lt;code&gt;nvidia.com/gpu: 4&lt;/code&gt;. Which four cards the scheduler picked was of no concern to it. With a 70B model using tensor parallelism that is not a detail: four 24 GB cards are not the same as four 96 GB cards, and the pod only dies when the weights load.&lt;/p&gt;
&lt;p&gt;With DRA you express the requirement as a CEL expression against the attributes the driver publishes:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Four GPUs of at least 80 GiB each, exclusively for one vLLM pod.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Requires the DRA driver for NVIDIA GPUs (DeviceClass gpu.nvidia.com).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;apiVersion&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;resource.k8s.io/v1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;kind&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ResourceClaimTemplate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;metadata&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;apertus-70b-gpus&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  namespace&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;inference&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;spec&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  spec&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    devices&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      requests&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;gpu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          exactly&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;            deviceClassName&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;gpu.nvidia.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;            allocationMode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ExactCount&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;            count&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;            selectors&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;cel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;                  expression&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                    device.capacity[&amp;#39;gpu.nvidia.com&amp;#39;].memory.isGreaterThan(quantity(&amp;#39;80Gi&amp;#39;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pod references the template instead of asking for a number:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;spec&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  resourceClaims&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;gpu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      resourceClaimTemplateName&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;apertus-70b-gpus&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  containers&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;vllm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;vllm/vllm-openai:v0.27.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      resources&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        claims&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;gpu&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The practical difference: a wrongly sized node fails at scheduling, not after an eight-minute model download. The &lt;a href=&quot;https://dra-driver-nvidia-gpu.sigs.k8s.io/docs/reference/resourceslice-attributes/&quot;&gt;DRA driver for NVIDIA GPUs&lt;/a&gt; publishes &lt;code&gt;productName&lt;/code&gt;, &lt;code&gt;architecture&lt;/code&gt; and &lt;code&gt;cudaComputeCapability&lt;/code&gt;, among others, for this purpose.&lt;/p&gt;
&lt;h2 id=&quot;inference-serving-what-belongs-between-model-and-application&quot;&gt;Inference serving: what belongs between model and application&lt;/h2&gt;
&lt;p&gt;vLLM is the model server, not the platform. It does continuous batching, PagedAttention and an OpenAI-compatible endpoint. What it does not do: decide which of eight replicas gets a request.&lt;/p&gt;
&lt;p&gt;A deployment fragment for Apertus. The tool parser and chat template come from the model card; the context window is halved, because otherwise the KV cache fills the GPU memory:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;containers&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;vllm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    image&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;vllm/vllm-openai:v0.27.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    args&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;--model=swiss-ai/Apertus-v1.5-70B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;--served-model-name=apertus-70b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;--tensor-parallel-size=4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;--max-model-len=131072&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;--gpu-memory-utilization=0.90&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;--chat-template-content-format=string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;--enable-auto-tool-choice&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;--tool-call-parser=apertus&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    ports&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;containerPort&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;8000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    readinessProbe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      httpGet&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/health&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;port&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;8000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Routing belongs a layer higher. The &lt;a href=&quot;https://github.com/kubernetes-sigs/gateway-api-inference-extension&quot;&gt;Gateway API Inference Extension&lt;/a&gt; (v1.6.0 of 17 August 2026) brings the &lt;code&gt;InferencePool&lt;/code&gt; and an endpoint picker that evaluates queue depth and KV cache utilisation. A round-robin load balancer, by contrast, hits a replica whose cache does not hold the prefix and pays for the prefill twice.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;apiVersion&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;inference.networking.k8s.io/v1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;kind&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;InferencePool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;metadata&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;apertus-70b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;spec&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  selector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    matchLabels&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;apertus-70b&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  targetPorts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;8000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  endpointPickerRef&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;apertus-70b-epp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    port&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;9002&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    failureMode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;FailOpen&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;llm-d (v0.9.0 of 17 August 2026) combines both into disaggregated serving, i.e. prefill and decode on separate pods. For most installations of this size that is a size too big.&lt;/p&gt;
&lt;h2 id=&quot;where-sovereignty-actually-breaks&quot;&gt;Where sovereignty actually breaks&lt;/h2&gt;
&lt;p&gt;The model is rarely the problem. The breaking points sit in the periphery that nobody perceives as an AI component and therefore nobody checks.&lt;/p&gt;
&lt;div class=&quot;scroll-x scroll-x--diagram&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Diagramm, seitlich scrollbar&quot;&gt;&lt;svg id=&quot;mermaid-0&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; class=&quot;flowchart&quot; style=&quot;width:1216.234375px&quot; viewBox=&quot;0 0 1216.234375 835&quot; role=&quot;graphics-document document&quot; aria-roledescription=&quot;flowchart-v2&quot;&gt;&lt;style&gt;#mermaid-0{font-family:&quot;Geist&quot;,sans-serif;font-size:16px;fill:#f5f5f5;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-0 .error-icon{fill:#121315;}#mermaid-0 .error-text{fill:#edecea;stroke:#edecea;}#mermaid-0 .edge-thickness-normal{stroke-width:1px;}#mermaid-0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-0 .marker{fill:#c4c4c4;stroke:#c4c4c4;}#mermaid-0 .marker.cross{stroke:#c4c4c4;}#mermaid-0 svg{font-family:&quot;Geist&quot;,sans-serif;font-size:16px;}#mermaid-0 p{margin:0;}#mermaid-0 .label{font-family:&quot;Geist&quot;,sans-serif;color:#f5f5f5;}#mermaid-0 .cluster-label text{fill:#f5f5f5;}#mermaid-0 .cluster-label span{color:#f5f5f5;}#mermaid-0 .cluster-label span p{background-color:transparent;}#mermaid-0 .label text,#mermaid-0 span{fill:#f5f5f5;color:#f5f5f5;}#mermaid-0 .node rect,#mermaid-0 .node circle,#mermaid-0 .node ellipse,#mermaid-0 .node polygon,#mermaid-0 .node path{fill:#1b1d20;stroke:#363739;stroke-width:1px;}#mermaid-0 .rough-node .label text,#mermaid-0 .node .label text,#mermaid-0 .image-shape .label,#mermaid-0 .icon-shape .label{text-anchor:middle;}#mermaid-0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-0 .rough-node .label,#mermaid-0 .node .label,#mermaid-0 .image-shape .label,#mermaid-0 .icon-shape .label{text-align:center;}#mermaid-0 .node.clickable{cursor:pointer;}#mermaid-0 .root .anchor path{fill:#c4c4c4!important;stroke-width:0;stroke:#c4c4c4;}#mermaid-0 .arrowheadPath{fill:#f6f5f3;}#mermaid-0 .edgePath .path{stroke:#c4c4c4;stroke-width:1px;}#mermaid-0 .flowchart-link{stroke:#c4c4c4;fill:none;}#mermaid-0 .edgeLabel{background-color:#090a0c;text-align:center;}#mermaid-0 .edgeLabel p{background-color:#090a0c;}#mermaid-0 .edgeLabel rect{opacity:0.5;background-color:#090a0c;fill:#090a0c;}#mermaid-0 .labelBkg{background-color:rgba(9, 10, 12, 0.5);}#mermaid-0 .cluster rect{fill:#121315;stroke:#232325;stroke-width:1px;}#mermaid-0 .cluster text{fill:#f5f5f5;}#mermaid-0 .cluster span{color:#f5f5f5;}#mermaid-0 .node .collapsed-indicator{fill:#232325;stroke:none;opacity:0.6;}#mermaid-0 .node .collapsed-separator{stroke:#232325;stroke-width:0.75px;}#mermaid-0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:&quot;Geist&quot;,sans-serif;font-size:12px;background:#121315;border:1px solid hsl(220, 0%, 17.6470588235%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#f5f5f5;}#mermaid-0 rect.text{fill:none;stroke-width:0;}#mermaid-0 .icon-shape,#mermaid-0 .image-shape{background-color:#090a0c;text-align:center;}#mermaid-0 .icon-shape p,#mermaid-0 .image-shape p{background-color:#090a0c;padding:2px;}#mermaid-0 .icon-shape .label rect,#mermaid-0 .image-shape .label rect{opacity:0.5;background-color:#090a0c;fill:#090a0c;}#mermaid-0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-0 .node .neo-node{stroke:#363739;}#mermaid-0 [data-look=&quot;neo&quot;].node rect,#mermaid-0 [data-look=&quot;neo&quot;].cluster rect,#mermaid-0 [data-look=&quot;neo&quot;].node polygon{stroke:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].swimlane.cluster rect{filter:none;}#mermaid-0 [data-look=&quot;neo&quot;].node path{stroke:url(#mermaid-0-gradient);stroke-width:1px;}#mermaid-0 [data-look=&quot;neo&quot;].node .outer-path{filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].node .neo-line path{stroke:#363739;filter:none;}#mermaid-0 [data-look=&quot;neo&quot;].node circle{stroke:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].node circle .state-start{fill:#000000;}#mermaid-0 [data-look=&quot;neo&quot;].icon-shape .icon{fill:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].icon-shape .icon-neo path{stroke:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 :root{--mermaid-font-family:&quot;Geist&quot;,sans-serif;}&lt;/style&gt;&lt;g&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointEnd&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;5&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;8&quot; markerHeight=&quot;8&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 0 L 10 5 L 0 10 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointStart&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;4.5&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;8&quot; markerHeight=&quot;8&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 5 L 10 10 L 10 0 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointEnd-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 11.5 14&quot; refX=&quot;11.5&quot; refY=&quot;7&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;10.5&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 0 L 11.5 7 L 0 14 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointStart-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 11.5 14&quot; refX=&quot;1&quot; refY=&quot;7&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11.5&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;polygon points=&quot;0,7 11.5,14 11.5,0&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/polygon&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleEnd&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;11&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleStart&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;-1&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleEnd-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refY=&quot;5&quot; refX=&quot;12.25&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;14&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleStart-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;-2&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;14&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossEnd&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 11 11&quot; refX=&quot;12&quot; refY=&quot;5.2&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 l 9,9 M 10,1 l -9,9&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossStart&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 11 11&quot; refX=&quot;-1&quot; refY=&quot;5.2&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 l 9,9 M 10,1 l -9,9&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossEnd-margin&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 15 15&quot; refX=&quot;17.7&quot; refY=&quot;7.5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;12&quot; markerHeight=&quot;12&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 L 14,14 M 1,14 L 14,1&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2.5&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossStart-margin&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 15 15&quot; refX=&quot;-3.5&quot; refY=&quot;7.5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;12&quot; markerHeight=&quot;12&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 L 14,14 M 1,14 L 14,1&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2.5;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;g class=&quot;root&quot;&gt;&lt;g class=&quot;clusters&quot;&gt;&lt;g class=&quot;cluster&quot; id=&quot;mermaid-0-US&quot; data-look=&quot;classic&quot;&gt;&lt;rect style=&quot;&quot; x=&quot;8&quot; y=&quot;112&quot; width=&quot;330&quot; height=&quot;715&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;cluster-label&quot; transform=&quot;translate(67.5546875, 112)&quot;&gt;&lt;foreignObject width=&quot;210.890625&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Jurisdiction USA, CLOUD Act&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;cluster&quot; id=&quot;mermaid-0-EU&quot; data-look=&quot;classic&quot;&gt;&lt;rect style=&quot;&quot; x=&quot;358&quot; y=&quot;522&quot; width=&quot;330&quot; height=&quot;128&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;cluster-label&quot; transform=&quot;translate(467.90625, 522)&quot;&gt;&lt;foreignObject width=&quot;110.1875&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Jurisdiction EU&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;cluster&quot; id=&quot;mermaid-0-CH&quot; data-look=&quot;classic&quot;&gt;&lt;rect style=&quot;&quot; x=&quot;708&quot; y=&quot;241&quot; width=&quot;500.234375&quot; height=&quot;409&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;cluster-label&quot; transform=&quot;translate(870.0234375, 241)&quot;&gt;&lt;foreignObject width=&quot;176.1875&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Jurisdiction Switzerland&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgePaths&quot;&gt;&lt;path d=&quot;M173,62L173,66.167C173,70.333,173,78.667,173,87C173,95.333,173,103.667,173,111.333C173,119,173,126,173,129.5L173,133&quot; id=&quot;mermaid-0-L_CLIENT_CDN_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_CLIENT_CDN_0&quot; data-points=&quot;W3sieCI6MTczLCJ5Ijo2Mn0seyJ4IjoxNzMsInkiOjg3fSx7IngiOjE3MywieSI6MTEyfSx7IngiOjE3MywieSI6MTM3fV0=&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M173,191L173,195.167C173,199.333,173,207.667,173,216C173,224.333,173,232.667,281.929,244.338C390.857,256.01,608.715,271.019,717.643,278.524L826.572,286.029&quot; id=&quot;mermaid-0-L_CDN_GW_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_CDN_GW_0&quot; data-points=&quot;W3sieCI6MTczLCJ5IjoxOTF9LHsieCI6MTczLCJ5IjoyMTZ9LHsieCI6MTczLCJ5IjoyNDF9LHsieCI6ODMwLjU2MjUsInkiOjI4Ni4zMDM2MDUyNTQxNjg5fV0=&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M927.758,320L927.758,324.167C927.758,328.333,927.758,336.667,927.758,344.333C927.758,352,927.758,359,927.758,362.5L927.758,366&quot; id=&quot;mermaid-0-L_GW_V_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_GW_V_0&quot; data-points=&quot;W3sieCI6OTI3Ljc1NzgxMjUsInkiOjMyMH0seyJ4Ijo5MjcuNzU3ODEyNSwieSI6MzQ1fSx7IngiOjkyNy43NTc4MTI1LCJ5IjozNzB9XQ==&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M987.016,448L996.385,454.167C1005.755,460.333,1024.495,472.667,1033.865,485C1043.234,497.333,1043.234,509.667,1043.234,519.333C1043.234,529,1043.234,536,1043.234,539.5L1043.234,543&quot; id=&quot;mermaid-0-L_V_OBJ_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_V_OBJ_0&quot; data-points=&quot;W3sieCI6OTg3LjAxNTUyMjIwMzk0NzQsInkiOjQ0OH0seyJ4IjoxMDQzLjIzNDM3NSwieSI6NDg1fSx7IngiOjEwNDMuMjM0Mzc1LCJ5Ijo1MjJ9LHsieCI6MTA0My4yMzQzNzUsInkiOjU0N31d&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M927.758,448L927.758,454.167C927.758,460.333,927.758,472.667,927.758,485C927.758,497.333,927.758,509.667,882.623,522.97C837.489,536.273,747.22,550.547,702.085,557.683L656.951,564.82&quot; id=&quot;mermaid-0-L_V_VDB_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_V_VDB_0&quot; data-points=&quot;W3sieCI6OTI3Ljc1NzgxMjUsInkiOjQ0OH0seyJ4Ijo5MjcuNzU3ODEyNSwieSI6NDg1fSx7IngiOjkyNy43NTc4MTI1LCJ5Ijo1MjJ9LHsieCI6NjUzLCJ5Ijo1NjUuNDQ0NDk4MDYwMTgyNn1d&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M846.875,448L834.086,454.167C821.297,460.333,795.719,472.667,782.93,485C770.141,497.333,770.141,509.667,692.947,524.107C615.753,538.547,461.365,555.094,384.171,563.367L306.977,571.641&quot; id=&quot;mermaid-0-L_V_MON_0&quot; class=&quot;edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_V_MON_0&quot; data-points=&quot;W3sieCI6ODQ2Ljg3NTMwODM4ODE1NzksInkiOjQ0OH0seyJ4Ijo3NzAuMTQwNjI1LCJ5Ijo0ODV9LHsieCI6NzcwLjE0MDYyNSwieSI6NTIyfSx7IngiOjMwMywieSI6NTcyLjA2NjkzMzU2MzU5NzN9XQ==&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M173,625L173,629.167C173,633.333,173,641.667,173,652C173,662.333,173,674.667,173,686.333C173,698,173,709,173,714.5L173,720&quot; id=&quot;mermaid-0-L_MON_SUP_0&quot; class=&quot;edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_MON_SUP_0&quot; data-points=&quot;W3sieCI6MTczLCJ5Ijo2MjV9LHsieCI6MTczLCJ5Ijo2NTB9LHsieCI6MTczLCJ5Ijo2ODd9LHsieCI6MTczLCJ5Ijo3MjR9XQ==&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabels&quot;&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_CLIENT_CDN_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_CDN_GW_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_GW_V_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_V_OBJ_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(927.7578125, 485)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_V_VDB_0&quot; transform=&quot;translate(-95.4765625, -12)&quot;&gt;&lt;foreignObject width=&quot;190.953125&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;context retrieval, cleartext&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(770.140625, 485)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_V_MON_0&quot; transform=&quot;translate(-42.140625, -12)&quot;&gt;&lt;foreignObject width=&quot;84.28125&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;traces, logs&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(173, 687)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_MON_SUP_0&quot; transform=&quot;translate(-25.5859375, -12)&quot;&gt;&lt;foreignObject width=&quot;51.171875&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;access&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;nodes&quot;&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-GW-0&quot; data-look=&quot;classic&quot; transform=&quot;translate(927.7578125, 293)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-97.1953125&quot; y=&quot;-27&quot; width=&quot;194.390625&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-67.1953125, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;134.390625&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Inference gateway&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-V-1&quot; data-look=&quot;classic&quot; transform=&quot;translate(927.7578125, 409)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-130&quot; y=&quot;-39&quot; width=&quot;260&quot; height=&quot;78&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;vLLM pods, Apertus 1.5 70B&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-OBJ-2&quot; data-look=&quot;classic&quot; transform=&quot;translate(1043.234375, 586)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-130&quot; y=&quot;-39&quot; width=&quot;260&quot; height=&quot;78&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Object storage, model weights&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-VDB-3&quot; data-look=&quot;classic&quot; transform=&quot;translate(523, 586)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-130&quot; y=&quot;-39&quot; width=&quot;260&quot; height=&quot;78&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Vector database, managed SaaS&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-CDN-4&quot; data-look=&quot;classic&quot; transform=&quot;translate(173, 164)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-81.5390625&quot; y=&quot;-27&quot; width=&quot;163.078125&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-51.5390625, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;103.078125&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;CDN and WAF&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-MON-5&quot; data-look=&quot;classic&quot; transform=&quot;translate(173, 586)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-130&quot; y=&quot;-39&quot; width=&quot;260&quot; height=&quot;78&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Monitoring SaaS, traces with prompt content&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-SUP-6&quot; data-look=&quot;classic&quot; transform=&quot;translate(173, 763)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-130&quot; y=&quot;-39&quot; width=&quot;260&quot; height=&quot;78&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Support access from a third country&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-CLIENT-7&quot; data-look=&quot;classic&quot; transform=&quot;translate(173, 35)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-51.3671875&quot; y=&quot;-27&quot; width=&quot;102.734375&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-21.3671875, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;42.734375&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Client&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;defs&gt;&lt;filter id=&quot;mermaid-0-drop-shadow&quot; height=&quot;130%&quot; width=&quot;130%&quot;&gt;&lt;feDropShadow dx=&quot;4&quot; dy=&quot;4&quot; stdDeviation=&quot;0&quot; flood-opacity=&quot;0.06&quot; flood-color=&quot;#000000&quot;&gt;&lt;/feDropShadow&gt;&lt;/filter&gt;&lt;/defs&gt;&lt;defs&gt;&lt;filter id=&quot;mermaid-0-drop-shadow-small&quot; height=&quot;150%&quot; width=&quot;150%&quot;&gt;&lt;feDropShadow dx=&quot;2&quot; dy=&quot;2&quot; stdDeviation=&quot;0&quot; flood-opacity=&quot;0.06&quot; flood-color=&quot;#000000&quot;&gt;&lt;/feDropShadow&gt;&lt;/filter&gt;&lt;/defs&gt;&lt;linearGradient id=&quot;mermaid-0-gradient&quot; gradientUnits=&quot;objectBoundingBox&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;&lt;stop offset=&quot;0%&quot; stop-color=&quot;#363739&quot; stop-opacity=&quot;1&quot;&gt;&lt;/stop&gt;&lt;stop offset=&quot;100%&quot; stop-color=&quot;hsl(220, 0%, 17.6470588235%)&quot; stop-opacity=&quot;1&quot;&gt;&lt;/stop&gt;&lt;/linearGradient&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Monitoring SaaS is the most common breaking point.&lt;/strong&gt; An OpenTelemetry trace around an LLM call carries attributes such as &lt;code&gt;gen_ai.input.messages&lt;/code&gt; and &lt;code&gt;gen_ai.output.messages&lt;/code&gt;, i.e. the full content of the conversation. Pointing the collector at a US provider exports the prompt, not just the latencies. That is not a misconfiguration but the default behaviour of common instrumentation. The data protection consequences hanging off it are in &lt;a href=&quot;https://devs-group.ch/en/blog/revdsg-compliant-ai-architecture/&quot;&gt;A revDSG-compliant AI architecture&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The vector database stores the cleartext alongside.&lt;/strong&gt; Every RAG implementation puts the source text chunk as payload next to the vector, otherwise it cannot place it in the prompt. A managed vector database with region Ireland is therefore a copy of your document corpus outside Switzerland.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Support access is in the contract, not in the architecture diagram.&lt;/strong&gt; Whether a provider delivers second-level support from a third country, and whether that team has read access to production systems, is a question for procurement. It is rarely asked.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The CDN sees every request before it reaches the origin.&lt;/strong&gt; Putting a US CDN in front of the inference endpoint means the prompt is in cleartext at a provider subject to CLOUD Act access.&lt;/p&gt;
&lt;p&gt;Then the quieter ones: the embedding service, often a different provider from the LLM service, and the object storage for model weights, whose bucket region nobody checks any more. Auditing these points does more for sovereignty than the choice of model. The same discipline was required for the distributed platform we built for &lt;a href=&quot;https://devs-group.ch/en/projects/onocoy/&quot;&gt;onocoy&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;when-sovereign-inference-is-the-wrong-answer&quot;&gt;When sovereign inference is the wrong answer&lt;/h2&gt;
&lt;p&gt;For the majority of the use cases we see, running your own GPUs is the more expensive and slower solution. Below roughly a million requests a month you pay a premium for sovereignty with no technical return.&lt;/p&gt;
&lt;p&gt;An API provider with a clean processor agreement, a documented data location and a commitment on model training generally covers the revDSG requirements for business data. Infomaniak, for example, processes in Geneva and Zurich. That is in production in days, not weeks.&lt;/p&gt;
&lt;p&gt;Self-operation earns its keep when one of these applies: volume is durably above the break-even; the data falls under professional secrecy under Art. 321 of the Swiss Criminal Code; or you need a pinned model version because a validation depends on it.&lt;/p&gt;
&lt;p&gt;What we advise against: the halfway solution with your own cluster but monitoring, vector database and CDN at a US provider. That costs the full operational effort and still does not deliver sovereignty.&lt;/p&gt;
&lt;h2 id=&quot;the-decision-matrix&quot;&gt;The decision matrix&lt;/h2&gt;















































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Criterion&lt;/th&gt;&lt;th&gt;API purchase&lt;/th&gt;&lt;th&gt;Dedicated GPU in Switzerland&lt;/th&gt;&lt;th&gt;On-prem or colocation&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Request volume&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;up to around 1 m per month&lt;/td&gt;&lt;td&gt;from around 3 m per month, predictable&lt;/td&gt;&lt;td&gt;durably high, over a 3-year horizon&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Data class&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;business data without special categories&lt;/td&gt;&lt;td&gt;sensitive personal data (Art. 5 lit. c revDSG)&lt;/td&gt;&lt;td&gt;professional secrecy (Art. 321 SCC), export-restricted data&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Model control&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;provider decides version and deprecation&lt;/td&gt;&lt;td&gt;you pin version and quantisation&lt;/td&gt;&lt;td&gt;plus driver and kernel&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Latency control&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;none, batching sits with the provider&lt;/td&gt;&lt;td&gt;batch size, KV cache, prefix routing&lt;/td&gt;&lt;td&gt;plus the network path&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Operational effort&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;API key and an invoice&lt;/td&gt;&lt;td&gt;cluster, DRA driver, vLLM, gateway, capacity planning&lt;/td&gt;&lt;td&gt;plus hardware lifecycle&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Time to production&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;hours&lt;/td&gt;&lt;td&gt;days to weeks&lt;/td&gt;&lt;td&gt;months&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The rows are not equally weighted. Almost always the second decides it: which class of data flows through the prompt. The rest is a question of cost after that.&lt;/p&gt;
&lt;h2 id=&quot;frequently-asked&quot;&gt;Frequently asked&lt;/h2&gt;
&lt;h3 id=&quot;what-swiss-alternatives-to-chatgpt-are-there&quot;&gt;What Swiss alternatives to ChatGPT are there?&lt;/h3&gt;
&lt;p&gt;Apertus is the open Swiss language model from EPFL, ETH Zurich and CSCS, available in version 1.5 since 24 July 2026. It can be obtained through Infomaniak AI Services, the Swisscom Swiss AI Platform, Phoenix Technologies, or from Hugging Face for self-operation. Alongside it, Swiss providers run endpoints for open models from Mistral, Qwen and Google.&lt;/p&gt;
&lt;h3 id=&quot;what-does-a-self-hosted-llm-cost-compared-with-the-api&quot;&gt;What does a self-hosted LLM cost compared with the API?&lt;/h3&gt;
&lt;p&gt;By our model calculation of 19 August 2026, four RTX Pro 6000 at Exoscale running continuously cost CHF 6,200 a month. The same load through the Infomaniak API costs CHF 0.00335 per request at 3,000 input and 500 output tokens. The arithmetic break-even is 1.85 million requests a month; with headroom for peaks and failures, closer to three to four million.&lt;/p&gt;
&lt;h3 id=&quot;what-hardware-do-i-need-for-my-own-llm&quot;&gt;What hardware do I need for my own LLM?&lt;/h3&gt;
&lt;p&gt;For Apertus-v1.5-70B the model card gives four GPUs at the full context window: 72 billion parameters in BF16 are 144 GB of weights alone, plus the KV cache. The 8B model runs comfortably on one 48 GB card. Quantisation lowers the requirement but costs quality and has to be measured for the specific use case.&lt;/p&gt;
&lt;h3 id=&quot;does-swiss-hosting-protect-against-the-us-cloud-act&quot;&gt;Does Swiss hosting protect against the US CLOUD Act?&lt;/h3&gt;
&lt;p&gt;Only if the operator is not subject to US law. 18 U.S.C. § 2713 obliges US providers to hand over data regardless of where it is stored. What decides is therefore the provider’s corporate structure, not the address of the data centre. A data centre in Zurich operated by a Swiss subsidiary of a US group remains within reach.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;strong&gt;Let’s discuss the inference architecture.&lt;/strong&gt; Thirty minutes with one of our engineers on your concrete case: which model, what volume, where the data sits, and where the periphery leaves the jurisdiction. No sales pitch, no preparation needed.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;A slot is available here&lt;/a&gt;. If you would rather write first, you can reach us through &lt;a href=&quot;https://devs-group.ch/en/contact/&quot;&gt;contact&lt;/a&gt;. What we work on is under &lt;a href=&quot;https://devs-group.ch/en/services/artificial-intelligence/&quot;&gt;AI and machine learning&lt;/a&gt; and &lt;a href=&quot;https://devs-group.ch/en/services/kubernetes-and-infrastructure/&quot;&gt;Kubernetes and infrastructure&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>Artificial Intelligence</category><author>Robert Pupel</author></item><item><title>The agents nobody approved</title><link>https://devs-group.ch/en/blog/agent-governance-shadow-it/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/agent-governance-shadow-it/</guid><description>OpenClaw runs locally, without procurement, with far-reaching rights. Agent governance means: an allowlist, permission classes, detection.</description><pubDate>Sat, 22 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Anyone who has not settled which agents may run with which rights on which code has still made that decision, just unconsciously. Agent platforms such as OpenClaw run on a developer’s own machine, need no procurement, and appear in no inventory. Agent governance therefore has three parts: an allowlist, graded permission classes, and a form of detection that does not require suspecting anyone.&lt;/p&gt;
&lt;p&gt;The typical trigger is a subordinate clause in a daily standup. Someone mentions that the agent did the refactoring overnight, kicked off over Telegram, from a personal MacBook. And at that point the question is no longer whether such a thing is in use, but since when.&lt;/p&gt;
&lt;h2 id=&quot;a-repository-with-386890-stars-that-never-went-through-procurement&quot;&gt;A repository with 386,890 stars that never went through procurement&lt;/h2&gt;
&lt;p&gt;On 20 August 2026 OpenClaw stands at 386,890 GitHub stars, making it the sixth most-starred repository there is, well ahead of React at 247,443 (&lt;a href=&quot;https://api.github.com/search/repositories?q=stars:%3E250000&amp;amp;sort=stars&quot;&gt;GitHub API&lt;/a&gt;, retrieved 20 August 2026). That is not a vendor claim but the platform’s own counter.&lt;/p&gt;
&lt;p&gt;What sits behind it: an open agent platform by Peter Steinberger that runs on your own machine and is operated through ordinary chat apps — WhatsApp, Telegram, Discord, Slack, Teams. The project puts it itself as “Your assistant. Your machine. Your rules.” No SaaS, therefore no contract, no invoice, no procurement process a CTO ever sees.&lt;/p&gt;
&lt;p&gt;For inventory purposes the naming history matters too. The weekend project “WhatsApp Relay” from November 2025 became “Clawd”, then “Moltbot”, and since 29 January 2026 it has been OpenClaw. Anyone maintaining a list of permitted tools and searching for the old name finds nothing.&lt;/p&gt;
&lt;h2 id=&quot;what-rights-an-agent-like-this-actually-has&quot;&gt;What rights an agent like this actually has&lt;/h2&gt;
&lt;p&gt;OpenClaw can read files, execute commands, install plugins and open network connections, and the project itself says the decisive sentence about it: “It is not a sandbox.” The &lt;a href=&quot;https://docs.openclaw.ai/gateway/security&quot;&gt;security documentation&lt;/a&gt; describes boundaries instead — path protection, a proxy — but concedes that a plugin with shell rights can still do anything shell rights permit.&lt;/p&gt;
&lt;p&gt;That openness is more honest than the marketing of most commercial tools. But it describes precisely the class of software that should not run unattended on a machine holding client code.&lt;/p&gt;
&lt;p&gt;That the permission system can break is documented. &lt;a href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-33579&quot;&gt;CVE-2026-33579&lt;/a&gt; describes a privilege escalation with CVSS v4.0 9.4 and CVSS v3.1 9.9, rated CRITICAL by the CNA VulnCheck, CWE-863, published 31 March 2026. All versions before 2026.3.28 are affected: a caller with pairing but not admin rights could approve device requests carrying elevated rights including admin, because the scope check was missing from the approval path.&lt;/p&gt;
&lt;p&gt;If you cannot say right now which agents run on your development machines and with which rights: taking exactly that inventory is half a day with your team, with no tool recommendation at the end. Write to us through &lt;a href=&quot;https://devs-group.ch/en/contact/&quot;&gt;contact&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-local-operation-triggers-under-the-revised-swiss-dpa&quot;&gt;What local operation triggers under the revised Swiss DPA&lt;/h2&gt;
&lt;p&gt;An agent that sends repository contents to a model is, in data protection terms, a disclosure — and usually one abroad. Where the code contains personal data, for instance in fixtures, logs or migration scripts, Art. 16 and Art. 9 of the &lt;a href=&quot;https://www.fedlex.admin.ch/eli/cc/2022/491/de&quot;&gt;revised DPA&lt;/a&gt; apply.&lt;/p&gt;
&lt;p&gt;Art. 9 para. 3 is particularly awkward: a processor may transfer processing “to a third party only with the prior authorisation of the controller”. A self-installed agent that reaches an additional model provider creates exactly that unauthorised third party, and nobody in the company knows about it.&lt;/p&gt;
&lt;p&gt;The sanction lands on people, not departments. Art. 61 of the revised DPA provides for fines up to CHF 250,000 against private persons, on application, in cases of intent. Covered are, among other things, disclosure abroad without the conditions of Art. 17, and handover to a processor without the conditions of Art. 9 paras. 1 and 2.&lt;/p&gt;
&lt;p&gt;How that translates into architecture we set out provision by provision in &lt;a href=&quot;https://devs-group.ch/en/blog/revdsg-compliant-ai-architecture/&quot;&gt;A revDSG-compliant AI architecture&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;three-permission-classes-rather-than-one-approval&quot;&gt;Three permission classes rather than one approval&lt;/h2&gt;
&lt;p&gt;A binary approval fails, because “permitted” means something different for an autocomplete than for an agent that commits at night. So we work with three classes, and the class follows from the access rather than from the product name.&lt;/p&gt;





































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Class&lt;/th&gt;&lt;th&gt;Visible code&lt;/th&gt;&lt;th&gt;Execution&lt;/th&gt;&lt;th&gt;Network&lt;/th&gt;&lt;th&gt;Approved by&lt;/th&gt;&lt;th&gt;Typical case&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;A&lt;/strong&gt; Assistance&lt;/td&gt;&lt;td&gt;open workspace, no secrets&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;td&gt;model providers on the allowlist&lt;/td&gt;&lt;td&gt;team lead&lt;/td&gt;&lt;td&gt;autocomplete, chat in the editor&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;B&lt;/strong&gt; Delegated&lt;/td&gt;&lt;td&gt;one repository, secrets through a vault&lt;/td&gt;&lt;td&gt;yes, in a container or VM, result as a pull request&lt;/td&gt;&lt;td&gt;allowlist plus approved MCP servers&lt;/td&gt;&lt;td&gt;tech lead plus data protection officer&lt;/td&gt;&lt;td&gt;coding agent on assignment&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;C&lt;/strong&gt; Autonomous&lt;/td&gt;&lt;td&gt;several repositories, close to production&lt;/td&gt;&lt;td&gt;yes, outside working hours, unobserved&lt;/td&gt;&lt;td&gt;any outbound connection possible&lt;/td&gt;&lt;td&gt;executive, time-limited, with a record&lt;/td&gt;&lt;td&gt;general-purpose agent over a chat app&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The line that matters lies between B and C. Anything falling in class C belongs on a dedicated machine with its own operating system account, not on the laptop that will open a client project tomorrow. OpenClaw’s documentation itself recommends separating trust boundaries on shared hosts, with separate OS users or separate hosts.&lt;/p&gt;
&lt;div class=&quot;scroll-x scroll-x--diagram&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Diagramm, seitlich scrollbar&quot;&gt;&lt;svg id=&quot;mermaid-0&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; class=&quot;flowchart&quot; style=&quot;width:557.71484375px&quot; viewBox=&quot;0 0 557.71484375 1254&quot; role=&quot;graphics-document document&quot; aria-roledescription=&quot;flowchart-v2&quot;&gt;&lt;style&gt;#mermaid-0{font-family:&quot;Geist&quot;,sans-serif;font-size:16px;fill:#f5f5f5;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-0 .error-icon{fill:#121315;}#mermaid-0 .error-text{fill:#edecea;stroke:#edecea;}#mermaid-0 .edge-thickness-normal{stroke-width:1px;}#mermaid-0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-0 .marker{fill:#c4c4c4;stroke:#c4c4c4;}#mermaid-0 .marker.cross{stroke:#c4c4c4;}#mermaid-0 svg{font-family:&quot;Geist&quot;,sans-serif;font-size:16px;}#mermaid-0 p{margin:0;}#mermaid-0 .label{font-family:&quot;Geist&quot;,sans-serif;color:#f5f5f5;}#mermaid-0 .cluster-label text{fill:#f5f5f5;}#mermaid-0 .cluster-label span{color:#f5f5f5;}#mermaid-0 .cluster-label span p{background-color:transparent;}#mermaid-0 .label text,#mermaid-0 span{fill:#f5f5f5;color:#f5f5f5;}#mermaid-0 .node rect,#mermaid-0 .node circle,#mermaid-0 .node ellipse,#mermaid-0 .node polygon,#mermaid-0 .node path{fill:#1b1d20;stroke:#363739;stroke-width:1px;}#mermaid-0 .rough-node .label text,#mermaid-0 .node .label text,#mermaid-0 .image-shape .label,#mermaid-0 .icon-shape .label{text-anchor:middle;}#mermaid-0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-0 .rough-node .label,#mermaid-0 .node .label,#mermaid-0 .image-shape .label,#mermaid-0 .icon-shape .label{text-align:center;}#mermaid-0 .node.clickable{cursor:pointer;}#mermaid-0 .root .anchor path{fill:#c4c4c4!important;stroke-width:0;stroke:#c4c4c4;}#mermaid-0 .arrowheadPath{fill:#f6f5f3;}#mermaid-0 .edgePath .path{stroke:#c4c4c4;stroke-width:1px;}#mermaid-0 .flowchart-link{stroke:#c4c4c4;fill:none;}#mermaid-0 .edgeLabel{background-color:#090a0c;text-align:center;}#mermaid-0 .edgeLabel p{background-color:#090a0c;}#mermaid-0 .edgeLabel rect{opacity:0.5;background-color:#090a0c;fill:#090a0c;}#mermaid-0 .labelBkg{background-color:rgba(9, 10, 12, 0.5);}#mermaid-0 .cluster rect{fill:#121315;stroke:#232325;stroke-width:1px;}#mermaid-0 .cluster text{fill:#f5f5f5;}#mermaid-0 .cluster span{color:#f5f5f5;}#mermaid-0 .node .collapsed-indicator{fill:#232325;stroke:none;opacity:0.6;}#mermaid-0 .node .collapsed-separator{stroke:#232325;stroke-width:0.75px;}#mermaid-0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:&quot;Geist&quot;,sans-serif;font-size:12px;background:#121315;border:1px solid hsl(220, 0%, 17.6470588235%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#f5f5f5;}#mermaid-0 rect.text{fill:none;stroke-width:0;}#mermaid-0 .icon-shape,#mermaid-0 .image-shape{background-color:#090a0c;text-align:center;}#mermaid-0 .icon-shape p,#mermaid-0 .image-shape p{background-color:#090a0c;padding:2px;}#mermaid-0 .icon-shape .label rect,#mermaid-0 .image-shape .label rect{opacity:0.5;background-color:#090a0c;fill:#090a0c;}#mermaid-0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-0 .node .neo-node{stroke:#363739;}#mermaid-0 [data-look=&quot;neo&quot;].node rect,#mermaid-0 [data-look=&quot;neo&quot;].cluster rect,#mermaid-0 [data-look=&quot;neo&quot;].node polygon{stroke:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].swimlane.cluster rect{filter:none;}#mermaid-0 [data-look=&quot;neo&quot;].node path{stroke:url(#mermaid-0-gradient);stroke-width:1px;}#mermaid-0 [data-look=&quot;neo&quot;].node .outer-path{filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].node .neo-line path{stroke:#363739;filter:none;}#mermaid-0 [data-look=&quot;neo&quot;].node circle{stroke:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].node circle .state-start{fill:#000000;}#mermaid-0 [data-look=&quot;neo&quot;].icon-shape .icon{fill:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].icon-shape .icon-neo path{stroke:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 :root{--mermaid-font-family:&quot;Geist&quot;,sans-serif;}&lt;/style&gt;&lt;g&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointEnd&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;5&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;8&quot; markerHeight=&quot;8&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 0 L 10 5 L 0 10 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointStart&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;4.5&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;8&quot; markerHeight=&quot;8&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 5 L 10 10 L 10 0 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointEnd-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 11.5 14&quot; refX=&quot;11.5&quot; refY=&quot;7&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;10.5&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 0 L 11.5 7 L 0 14 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointStart-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 11.5 14&quot; refX=&quot;1&quot; refY=&quot;7&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11.5&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;polygon points=&quot;0,7 11.5,14 11.5,0&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/polygon&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleEnd&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;11&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleStart&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;-1&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleEnd-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refY=&quot;5&quot; refX=&quot;12.25&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;14&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleStart-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;-2&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;14&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossEnd&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 11 11&quot; refX=&quot;12&quot; refY=&quot;5.2&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 l 9,9 M 10,1 l -9,9&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossStart&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 11 11&quot; refX=&quot;-1&quot; refY=&quot;5.2&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 l 9,9 M 10,1 l -9,9&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossEnd-margin&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 15 15&quot; refX=&quot;17.7&quot; refY=&quot;7.5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;12&quot; markerHeight=&quot;12&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 L 14,14 M 1,14 L 14,1&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2.5&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossStart-margin&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 15 15&quot; refX=&quot;-3.5&quot; refY=&quot;7.5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;12&quot; markerHeight=&quot;12&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 L 14,14 M 1,14 L 14,1&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2.5;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;g class=&quot;root&quot;&gt;&lt;g class=&quot;clusters&quot;&gt;&lt;/g&gt;&lt;g class=&quot;edgePaths&quot;&gt;&lt;path d=&quot;M188.938,62L188.938,66.167C188.938,70.333,188.938,78.667,188.938,86.333C188.938,94,188.938,101,188.938,104.5L188.938,108&quot; id=&quot;mermaid-0-L_A_B_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_A_B_0&quot; data-points=&quot;W3sieCI6MTg4LjkzNzUsInkiOjYyfSx7IngiOjE4OC45Mzc1LCJ5Ijo4N30seyJ4IjoxODguOTM3NSwieSI6MTEyfV0=&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M131.671,332.734L120.664,348.445C109.656,364.156,87.64,395.578,76.633,435.456C65.625,475.333,65.625,523.667,65.625,547.833L65.625,572&quot; id=&quot;mermaid-0-L_B_K1_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_B_K1_0&quot; data-points=&quot;W3sieCI6MTMxLjY3MTQ3MzY4OTcwNTU4LCJ5IjozMzIuNzMzOTczNjg5NzA1Nn0seyJ4Ijo2NS42MjUsInkiOjQyN30seyJ4Ijo2NS42MjUsInkiOjU3Nn1d&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M246.204,332.734L257.211,348.445C268.219,364.156,290.235,395.578,301.242,416.789C312.25,438,312.25,449,312.25,454.5L312.25,460&quot; id=&quot;mermaid-0-L_B_C_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_B_C_0&quot; data-points=&quot;W3sieCI6MjQ2LjIwMzUyNjMxMDI5NDQyLCJ5IjozMzIuNzMzOTczNjg5NzA1Nn0seyJ4IjozMTIuMjUsInkiOjQyN30seyJ4IjozMTIuMjUsInkiOjQ2NH1d&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M251.864,681.614L239.396,697.845C226.929,714.076,201.994,746.538,189.526,792.102C177.059,837.667,177.059,896.333,177.059,955C177.059,1013.667,177.059,1072.333,178.243,1107.182C179.428,1142.03,181.798,1153.059,182.983,1158.574L184.168,1164.089&quot; id=&quot;mermaid-0-L_C_K3_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_C_K3_0&quot; data-points=&quot;W3sieCI6MjUxLjg2Mzk5NjEwODcwNTIsInkiOjY4MS42MTM5OTYxMDg3MDUyfSx7IngiOjE3Ny4wNTg1OTM3NSwieSI6Nzc5fSx7IngiOjE3Ny4wNTg1OTM3NSwieSI6OTU1fSx7IngiOjE3Ny4wNTg1OTM3NSwieSI6MTEzMX0seyJ4IjoxODUuMDA3ODEyNSwieSI6MTE2OH1d&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M361.954,692.296L369.997,706.747C378.041,721.197,394.128,750.099,402.171,770.049C410.215,790,410.215,801,410.215,806.5L410.215,812&quot; id=&quot;mermaid-0-L_C_D_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_C_D_0&quot; data-points=&quot;W3sieCI6MzYxLjk1Mzg3MTEwNTcyNDY2LCJ5Ijo2OTIuMjk2MTI4ODk0Mjc1M30seyJ4Ijo0MTAuMjE0ODQzNzUsInkiOjc3OX0seyJ4Ijo0MTAuMjE0ODQzNzUsInkiOjgxNn1d&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M424.968,1079.247L425.992,1087.872C427.016,1096.498,429.065,1113.749,430.089,1129.874C431.113,1146,431.113,1161,431.113,1168.5L431.113,1176&quot; id=&quot;mermaid-0-L_D_K2_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_D_K2_0&quot; data-points=&quot;W3sieCI6NDI0Ljk2ODA0NzczMzY1MjcsInkiOjEwNzkuMjQ2Nzk2MDE2MzQ3Mn0seyJ4Ijo0MzEuMTEzMjgxMjUsInkiOjExMzF9LHsieCI6NDMxLjExMzI4MTI1LCJ5IjoxMTgwfV0=&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M354.182,1037.967L343.71,1053.473C333.239,1068.978,312.295,1099.989,294.401,1121.253C276.507,1142.516,261.663,1154.032,254.241,1159.79L246.819,1165.548&quot; id=&quot;mermaid-0-L_D_K3_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_D_K3_0&quot; data-points=&quot;W3sieCI6MzU0LjE4MjEwODc2OTU0MDMsInkiOjEwMzcuOTY3MjY1MDE5NTQwM30seyJ4IjoyOTEuMzUxNTYyNSwieSI6MTEzMX0seyJ4IjoyNDMuNjU4MTUxNzI2OTczNywieSI6MTE2OH1d&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabels&quot;&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_A_B_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(65.625, 427)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_B_K1_0&quot; transform=&quot;translate(-9.140625, -12)&quot;&gt;&lt;foreignObject width=&quot;18.28125&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;no&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(312.25, 427)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_B_C_0&quot; transform=&quot;translate(-12.65625, -12)&quot;&gt;&lt;foreignObject width=&quot;25.3125&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;yes&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(177.05859375, 955)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_C_K3_0&quot; transform=&quot;translate(-9.140625, -12)&quot;&gt;&lt;foreignObject width=&quot;18.28125&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;no&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(410.21484375, 779)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_C_D_0&quot; transform=&quot;translate(-12.65625, -12)&quot;&gt;&lt;foreignObject width=&quot;25.3125&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;yes&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(431.11328125, 1131)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_D_K2_0&quot; transform=&quot;translate(-9.140625, -12)&quot;&gt;&lt;foreignObject width=&quot;18.28125&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;no&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(305.875, 1109.49525)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_D_K3_0&quot; transform=&quot;translate(-12.65625, -12)&quot;&gt;&lt;foreignObject width=&quot;25.3125&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;yes&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;nodes&quot;&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-A-0&quot; data-look=&quot;classic&quot; transform=&quot;translate(188.9375, 35)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-108.046875&quot; y=&quot;-27&quot; width=&quot;216.09375&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-78.046875, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;156.09375&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Agent to be deployed&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-B-1&quot; data-look=&quot;classic&quot; transform=&quot;translate(188.9375, 251)&quot;&gt;&lt;polygon points=&quot;139,0 278,-139 139,-278 0,-139&quot; class=&quot;label-container&quot; transform=&quot;translate(-138.5, 139)&quot;&gt;&lt;/polygon&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Does it execute commands?&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-K1-3&quot; data-look=&quot;classic&quot; transform=&quot;translate(65.625, 603)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-57.625&quot; y=&quot;-27&quot; width=&quot;115.25&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-27.625, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;55.25&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Class A&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-C-5&quot; data-look=&quot;classic&quot; transform=&quot;translate(312.25, 603)&quot;&gt;&lt;polygon points=&quot;139,0 278,-139 139,-278 0,-139&quot; class=&quot;label-container&quot; transform=&quot;translate(-138.5, 139)&quot;&gt;&lt;/polygon&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Does it run in a container or VM?&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-K3-7&quot; data-look=&quot;classic&quot; transform=&quot;translate(193.38671875, 1207)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-130&quot; y=&quot;-39&quot; width=&quot;260&quot; height=&quot;78&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Class C: dedicated machine, or no&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-D-9&quot; data-look=&quot;classic&quot; transform=&quot;translate(410.21484375, 955)&quot;&gt;&lt;polygon points=&quot;139,0 278,-139 139,-278 0,-139&quot; class=&quot;label-container&quot; transform=&quot;translate(-138.5, 139)&quot;&gt;&lt;/polygon&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Does it see more than one repository?&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-K2-11&quot; data-look=&quot;classic&quot; transform=&quot;translate(431.11328125, 1207)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-57.7265625&quot; y=&quot;-27&quot; width=&quot;115.453125&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-27.7265625, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;55.453125&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Class B&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;defs&gt;&lt;filter id=&quot;mermaid-0-drop-shadow&quot; height=&quot;130%&quot; width=&quot;130%&quot;&gt;&lt;feDropShadow dx=&quot;4&quot; dy=&quot;4&quot; stdDeviation=&quot;0&quot; flood-opacity=&quot;0.06&quot; flood-color=&quot;#000000&quot;&gt;&lt;/feDropShadow&gt;&lt;/filter&gt;&lt;/defs&gt;&lt;defs&gt;&lt;filter id=&quot;mermaid-0-drop-shadow-small&quot; height=&quot;150%&quot; width=&quot;150%&quot;&gt;&lt;feDropShadow dx=&quot;2&quot; dy=&quot;2&quot; stdDeviation=&quot;0&quot; flood-opacity=&quot;0.06&quot; flood-color=&quot;#000000&quot;&gt;&lt;/feDropShadow&gt;&lt;/filter&gt;&lt;/defs&gt;&lt;linearGradient id=&quot;mermaid-0-gradient&quot; gradientUnits=&quot;objectBoundingBox&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;&lt;stop offset=&quot;0%&quot; stop-color=&quot;#363739&quot; stop-opacity=&quot;1&quot;&gt;&lt;/stop&gt;&lt;stop offset=&quot;100%&quot; stop-color=&quot;hsl(220, 0%, 17.6470588235%)&quot; stop-opacity=&quot;1&quot;&gt;&lt;/stop&gt;&lt;/linearGradient&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;p&gt;For class B the frame can be captured in configuration. OpenClaw has a restrictive baseline that works as a starting point: gateway on loopback only, execution denied by default.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;json5&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  gateway&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    mode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;local&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    bind&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;loopback&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    auth&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;mode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;token&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&amp;lt;long-random-token&amp;gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  tools&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    deny&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;group:automation&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;group:runtime&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;gateway&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;cron&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    exec&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;security&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;deny&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ask&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;always&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  agents&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    defaults&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      sandbox&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;mode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;all&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;scope&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;agent&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      workspaceAccess&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;ro&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The file itself belongs locked down: &lt;code&gt;~/.openclaw&lt;/code&gt; at 700 and &lt;code&gt;~/.openclaw/openclaw.json&lt;/code&gt; at 600. The project ships a check that also finds configuration drift:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;openclaw&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; security&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; audit&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --deep&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;openclaw&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; security&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; audit&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --fix&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;how-to-detect-use-without-hunting-anyone&quot;&gt;How to detect use without hunting anyone&lt;/h2&gt;
&lt;p&gt;Detection here means inventory, not surveillance: you ask the machine, not the colleague. Three signals are enough for a first pass, and all three fit into an onboarding script or an MDM check.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 1. Globally installed agent packages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ls&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -g&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --depth=0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; 2&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/dev/null&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -Ei&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;openclaw|opencode|codex|claude-code&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 2. Configuration directories of long-running agents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ls&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -ld&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~/.openclaw&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~/.claude&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~/.codex&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; 2&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/dev/null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 3. The OpenClaw gateway listens by default on 127.0.0.1:18789&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lsof&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -nP&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -iTCP:18789&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -sTCP:LISTEN&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; 2&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/dev/null&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second part concerns not the machines but the repositories. An agent sees whatever the working directory offers, and that is regularly more than assumed:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ls-files&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wc&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -l&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; grep&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -nIE&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;(api[_-]?key|secret|BEGIN [A-Z ]*PRIVATE KEY)&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;:!*_test*&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; head&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If that second query finds something, the agent question is secondary. You have a secret management problem that affects every clone of the repository.&lt;/p&gt;
&lt;h2 id=&quot;tool-lock-in-is-the-second-quieter-risk&quot;&gt;Tool lock-in is the second, quieter risk&lt;/h2&gt;
&lt;p&gt;Thinking about governance only as a security question misses the consolidation happening in the market. Windsurf no longer exists as its own brand: on 20 August 2026 &lt;code&gt;windsurf.com&lt;/code&gt; answers with HTTP 308 to &lt;code&gt;devin.ai/desktop&lt;/code&gt;, checked with a simple redirect test. Cursor has been building its own code hosting with “Origin” since 17 August 2026, complete with repositories and pull requests, in open beta for all paid plans according to the changelog, with an opt-out for enterprise administrators.&lt;/p&gt;
&lt;p&gt;Usage shares are moving at the same pace. The &lt;a href=&quot;https://blog.jetbrains.com/research/2026/08/ai-coding-agent-adoption-2026/&quot;&gt;JetBrains Developer Ecosystem Survey 2026&lt;/a&gt; with over 15,000 respondents, collected between May and July 2026, measures a redistribution within a few months. To note: JetBrains operates in the same market and is therefore not a neutral surveyor.&lt;/p&gt;






























&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Tool&lt;/th&gt;&lt;th&gt;Before&lt;/th&gt;&lt;th&gt;Survey May–July 2026&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Claude Code&lt;/td&gt;&lt;td&gt;18 %&lt;/td&gt;&lt;td&gt;39 %&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GitHub Copilot&lt;/td&gt;&lt;td&gt;29 %&lt;/td&gt;&lt;td&gt;21 %&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Codex&lt;/td&gt;&lt;td&gt;3 %&lt;/td&gt;&lt;td&gt;16 %&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cursor&lt;/td&gt;&lt;td&gt;18 %&lt;/td&gt;&lt;td&gt;12 %&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;A tool that sheds or doubles a third of its user base within two quarters is not a platform decision but a snapshot. What binds you should therefore not be the tool but whatever survives switching it.&lt;/p&gt;
&lt;p&gt;Three things are portable today: the Model Context Protocol in the specification version of 28 July 2026; the &lt;code&gt;AGENTS.md&lt;/code&gt; format, which according to agents.md over 60,000 open-source projects use and which is maintained under the Linux Foundation; and A2A in version 1.0, also handed to the Linux Foundation.&lt;/p&gt;
&lt;p&gt;Putting your project conventions there rather than in tool-specific settings means changing tools without a migration project. What building your own MCP server looks like we described in &lt;a href=&quot;https://devs-group.ch/en/blog/running-mcp-servers-in-go/&quot;&gt;Running MCP servers in Go in production&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The MCP specification is notably clear on security: tool descriptions and annotations are to be treated as untrusted unless they come from a trusted server. At the same time it records that the protocol cannot enforce those principles. Enforcement lies with the host, which is to say with you.&lt;/p&gt;
&lt;h2 id=&quot;what-belongs-in-the-policy-and-what-we-advise-against&quot;&gt;What belongs in the policy, and what we advise against&lt;/h2&gt;
&lt;p&gt;An agent policy fits on two pages. It only gets read at greater length when an audit is due.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;An allowlist with versions&lt;/strong&gt;, not only product names. OpenClaw has run an extended-stable channel since 30 July 2026, the first such release being 2026.6.33. What you approve is a channel, not “the tool”.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A permission class per deployment&lt;/strong&gt;, per the table above, documented with one sentence of reasoning.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Named outbound destinations.&lt;/strong&gt; Which model providers, which MCP servers. GitHub introduced MCP allowlists in enterprise settings on 6 August 2026; that is the right lever.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Personal subscriptions are not corporate access.&lt;/strong&gt; Not because the tool would not work, but because the contract is a different one: Free, Pro and Max run under consumer terms where training use hangs off a personal account setting the employer neither sees nor controls. Anthropic describes this explicitly for using Claude Code from such accounts. Which plans and purchasing routes exist instead is in &lt;a href=&quot;https://devs-group.ch/en/blog/coding-agents-and-data-protection/&quot;&gt;Using coding agents in line with data protection law&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A reporting route with no consequence.&lt;/strong&gt; Someone reporting a previously unapproved agent gets a class assigned, not a reprimand.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What we advise against: a ban. A ban produces better-hidden shadow IT and costs you exactly the information you need.&lt;/p&gt;
&lt;p&gt;We equally advise against letting the agent sign off its own work. Review, tests and the merge gate stay outside the agent, otherwise the system checks itself. Why that matters architecturally is in &lt;a href=&quot;https://devs-group.ch/en/blog/ai-code-without-technical-debt/&quot;&gt;AI writes the code. Who writes the architecture?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When our recommendation does not fit: in a two-person team with a single repository and no personal data, the permission class matrix is overhead. There a maintained list in the README suffices, plus the rule that no agent executes commands outside a container.&lt;/p&gt;
&lt;h2 id=&quot;frequently-asked&quot;&gt;Frequently asked&lt;/h2&gt;
&lt;h3 id=&quot;is-openclaw-fundamentally-unsuitable-for-companies&quot;&gt;Is OpenClaw fundamentally unsuitable for companies?&lt;/h3&gt;
&lt;p&gt;No. OpenClaw is a technically impressive project with open security communication that names its own limits. What is unsuitable is the operating model on a work machine holding client code: local, broadly permissioned, driven through consumer chat apps. On a dedicated machine with its own OS account, loopback binding and a restrictive tool policy, sanctioned use is possible.&lt;/p&gt;
&lt;h3 id=&quot;how-do-i-tell-whether-an-agent-has-sent-personal-data-to-a-model&quot;&gt;How do I tell whether an agent has sent personal data to a model?&lt;/h3&gt;
&lt;p&gt;From the outbound traffic and the agent session, not from the repository. The workable route is the reverse: check which directories the agent was allowed to read, and treat everything in them as disclosed. That is why class B specifies &lt;code&gt;workspaceAccess: &amp;quot;ro&amp;quot;&lt;/code&gt; and a single repository. Less visibility is what makes the data protection assessment under Art. 16 revDSG tractable in the first place.&lt;/p&gt;
&lt;h3 id=&quot;is-a-container-enough-protection&quot;&gt;Is a container enough protection?&lt;/h3&gt;
&lt;p&gt;A container limits the damage, it does not prevent it. It protects the host’s file system and separates agents from one another, but changes nothing about which data the agent sees inside the container and where it sends them. Container plus a tight tool policy plus named outbound destinations work together. Individually, none of the three works.&lt;/p&gt;
&lt;h3 id=&quot;what-do-we-do-with-an-agent-that-has-been-running-unnoticed-for-months&quot;&gt;What do we do with an agent that has been running unnoticed for months?&lt;/h3&gt;
&lt;p&gt;Inventory first, classify second, decide third. Check the version, particularly against CVE-2026-33579 and the 2026.3.28 boundary; record the rights and the scope of access; rotate the secrets that were within its reach. Only then ask whether it keeps running or gets switched off. Switching it off immediately destroys the information about what the agent actually did.&lt;/p&gt;
&lt;h2 id=&quot;the-next-step&quot;&gt;The next step&lt;/h2&gt;
&lt;p&gt;Take 30 minutes with one of our engineers, no sales pitch: we go through your tool list, assign each entry a permission class, and name the places where revised DPA obligations are engaged. &lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;Book a slot&lt;/a&gt; or write informally through &lt;a href=&quot;https://devs-group.ch/en/contact/&quot;&gt;contact&lt;/a&gt;; both reach the same person.&lt;/p&gt;
&lt;p&gt;If it turns into more than a policy — an agent that should run cleanly bounded inside your environment, say — that is our day job under &lt;a href=&quot;https://devs-group.ch/en/services/artificial-intelligence/&quot;&gt;Artificial Intelligence &amp;amp; Machine Learning&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>AI-Assisted Engineering</category><author>Robert Pupel</author></item><item><title>Running MCP servers in Go in production</title><link>https://devs-group.ch/en/blog/running-mcp-servers-in-go/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/running-mcp-servers-in-go/</guid><description>What the July 2026 MCP specification changes: a stateless core, OAuth instead of a shared token, and the tool design mistakes we see most often.</description><pubDate>Fri, 21 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;An MCP server following the specification of 28 July 2026 holds no session state. The revision removes the &lt;code&gt;initialize&lt;/code&gt; handshake and the &lt;code&gt;Mcp-Session-Id&lt;/code&gt; header: every request carries the protocol version and client identity itself, in &lt;code&gt;_meta&lt;/code&gt;. For a production server in Go that means three things: round-robin behind an ordinary load balancer, state in server-issued handles, identity in an OAuth token per call.&lt;/p&gt;
&lt;p&gt;The primary source is the &lt;a href=&quot;https://modelcontextprotocol.io/specification/2026-07-28/changelog&quot;&gt;changelog for revision 2026-07-28&lt;/a&gt;. All the code examples below are written against the official &lt;a href=&quot;https://github.com/modelcontextprotocol/go-sdk/releases&quot;&gt;Go SDK v1.7.0&lt;/a&gt;, which went stable the same day.&lt;/p&gt;
&lt;h2 id=&quot;what-the-specification-of-28-july-2026-changes&quot;&gt;What the specification of 28 July 2026 changes&lt;/h2&gt;
&lt;p&gt;Revision 2026-07-28 turns MCP from a bidirectional, stateful protocol into a request/response protocol. The changelog lists nine points as “major changes”, and six of them force work on an existing server.&lt;/p&gt;








































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Before (2025-11-25)&lt;/th&gt;&lt;th&gt;After (2026-07-28)&lt;/th&gt;&lt;th&gt;What you have to change&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;initialize&lt;/code&gt; handshake, &lt;code&gt;Mcp-Session-Id&lt;/code&gt; header&lt;/td&gt;&lt;td&gt;every request self-describing through &lt;code&gt;_meta&lt;/code&gt;; new mandatory RPC &lt;code&gt;server/discover&lt;/code&gt;&lt;/td&gt;&lt;td&gt;turn off sticky sessions at the load balancer, remove process-local session storage&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;server-initiated requests (&lt;code&gt;roots/list&lt;/code&gt;, &lt;code&gt;sampling/createMessage&lt;/code&gt;, &lt;code&gt;elicitation/create&lt;/code&gt;) over a held stream&lt;/td&gt;&lt;td&gt;multi round-trip requests: the server answers with &lt;code&gt;resultType: &amp;quot;input_required&amp;quot;&lt;/code&gt;, the client repeats with &lt;code&gt;inputResponses&lt;/code&gt;&lt;/td&gt;&lt;td&gt;encode intermediate state in &lt;code&gt;requestState&lt;/code&gt; rather than holding it in the process&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;routing by inspecting the JSON body&lt;/td&gt;&lt;td&gt;mandatory &lt;code&gt;Mcp-Method&lt;/code&gt; header on every streamable HTTP POST, &lt;code&gt;Mcp-Name&lt;/code&gt; additionally on &lt;code&gt;tools/call&lt;/code&gt;, &lt;code&gt;resources/read&lt;/code&gt; and &lt;code&gt;prompts/get&lt;/code&gt;&lt;/td&gt;&lt;td&gt;switch gateway rules to headers, otherwise &lt;code&gt;HeaderMismatchError&lt;/code&gt; (&lt;code&gt;-32020&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;list results without a cache hint&lt;/td&gt;&lt;td&gt;&lt;code&gt;ttlMs&lt;/code&gt; and &lt;code&gt;cacheScope&lt;/code&gt; are mandatory fields on &lt;code&gt;tools/list&lt;/code&gt;, &lt;code&gt;prompts/list&lt;/code&gt;, &lt;code&gt;resources/list&lt;/code&gt;, &lt;code&gt;resources/read&lt;/code&gt; and &lt;code&gt;resources/templates/list&lt;/code&gt;&lt;/td&gt;&lt;td&gt;set the values deliberately; for user-dependent tool lists, &lt;code&gt;cacheScope: &amp;quot;private&amp;quot;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;roots, sampling and logging active&lt;/td&gt;&lt;td&gt;all three deprecated (SEP-2577), still functional&lt;/td&gt;&lt;td&gt;log to &lt;code&gt;stderr&lt;/code&gt; or through OpenTelemetry, replace sampling with a direct call to the provider API&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Dynamic Client Registration (RFC 7591) as the standard route&lt;/td&gt;&lt;td&gt;deprecated in favour of Client ID Metadata Documents&lt;/td&gt;&lt;td&gt;support CIMD, keep DCR only as a fallback&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;For planning purposes the governance part matters: the specification now has a &lt;a href=&quot;https://modelcontextprotocol.io/community/feature-lifecycle&quot;&gt;feature lifecycle and deprecation policy&lt;/a&gt; with a minimum window of twelve months. Anyone using roots, sampling or logging does not have to rebuild this week. Anyone building new does not touch them at all.&lt;/p&gt;
&lt;p&gt;A common misunderstanding concerns tasks: they have not moved into the core but out of the experimental core into the &lt;code&gt;io.modelcontextprotocol/tasks&lt;/code&gt; extension. MCP Apps (&lt;code&gt;io.modelcontextprotocol/ui&lt;/code&gt;) and enterprise-managed authorization also sit outside the core, but were never in it: both have been extensions from the start. Extensions are opt-in and negotiated through the &lt;code&gt;extensions&lt;/code&gt; field.&lt;/p&gt;
&lt;h2 id=&quot;why-an-mcp-server-gets-built-in-go&quot;&gt;Why an MCP server gets built in Go&lt;/h2&gt;
&lt;p&gt;Go is the language the agent infrastructure is already written in: Kubernetes controllers, inference gateways, agentgateway, kagent. Why we build critical backends in Go generally is in a &lt;a href=&quot;https://devs-group.ch/en/blog/why-go-for-critical-backends/&quot;&gt;separate article on Go for long-lived systems&lt;/a&gt;, and the rest of this text does not repeat it.&lt;/p&gt;
&lt;p&gt;One point is new for MCP. A tool server is a short-lived process that restarts often, runs in many instances, and is occasionally deployed separately per tenant. A static binary in a &lt;code&gt;FROM scratch&lt;/code&gt; image is a different starting position for that than an interpreter with a package tree.&lt;/p&gt;
&lt;p&gt;The Go SDK requires at least Go 1.25.0 in its &lt;code&gt;go.mod&lt;/code&gt;. As a base, Go 1.26 at patch level 1.26.6 of &lt;a href=&quot;https://go.dev/doc/devel/release&quot;&gt;13 August 2026&lt;/a&gt; is a sensible choice.&lt;/p&gt;
&lt;h2 id=&quot;stateless-means-the-state-gets-a-name&quot;&gt;Stateless means the state gets a name&lt;/h2&gt;
&lt;p&gt;A production MCP server holds no session state in the process. State belongs in external storage and is addressed through a server-issued handle that travels back and forth as an ordinary tool argument. That is how SEP-2567 describes it.&lt;/p&gt;
&lt;p&gt;The gain: the second call may land on a different instance, a deployment in the middle of an agent session destroys nothing, and the load balancer needs no stickiness. In the Go SDK it is one field, &lt;code&gt;StreamableHTTPOptions.Stateless&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The four examples in this article are complete programs: copy them, &lt;code&gt;go get github.com/modelcontextprotocol/go-sdk@v1.7.0&lt;/code&gt;, &lt;code&gt;go run .&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Complete program. go get github.com/modelcontextprotocol/go-sdk@v1.7.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;package&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;encoding/json&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fmt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;net/http&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sync&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;github.com/modelcontextprotocol/go-sdk/mcp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Store is the external state store. In production: Redis, Valkey, Postgres.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Just not the process, because the next call lands on a different instance.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Store&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; interface&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;	Put&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;handle&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;v&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ttl&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; time&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Duration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;	Get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;handle&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;v&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// memStore is the throwaway variant, so this example runs without Redis.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; memStore&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{ m &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;memStore&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Put&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;h&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;v&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; time&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Duration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	b, err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; json.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Marshal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(v)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; err&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	s.m.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Store&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(h, b)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;memStore&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;h&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;v&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	b, ok &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s.m.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Load&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(h)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ok {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;handle &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%q&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; unknown or expired&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, h)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; json.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Unmarshal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(b.([]&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), v)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; exportState&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	UserID &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   `json:&amp;quot;user_id&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Rows   []&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `json:&amp;quot;rows&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; startIn&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Tenant &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `json:&amp;quot;tenant&amp;quot; jsonschema:&amp;quot;Tenant code, three uppercase letters&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; startOut&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Handle &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `json:&amp;quot;handle&amp;quot; jsonschema:&amp;quot;Opaque handle, pass back unchanged to export_page&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Total  &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    `json:&amp;quot;total&amp;quot; jsonschema:&amp;quot;Number of rows in the export&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; pageIn&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Handle &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `json:&amp;quot;handle&amp;quot; jsonschema:&amp;quot;Handle from export_start&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; addExport&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;store&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Store&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	mcp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddTool&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Tool&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Name:        &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;export_start&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Description: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Starts a bookings export and returns a handle. Fetches no data itself.&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolRequest&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; startIn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;startOut&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		rows &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; []&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;B-1&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;B-2&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;B-3&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		handle &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Sprintf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;exp_&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%d&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, in.Tenant, time.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UnixNano&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		st &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; exportState&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{UserID: &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;userID&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(req), Rows: rows}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; store.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Put&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ctx, handle, st, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;15&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;time.Minute); err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;			return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;startOut&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{}, fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;could not store the handle: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%w&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, err)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;startOut&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Handle: handle, Total: &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;len&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(rows)}, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	mcp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddTool&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Tool&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Name:        &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;export_page&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Description: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Returns the rows of an export begun with export_start.&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolRequest&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; pageIn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		var&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; st &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;exportState&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; store.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ctx, in.Handle, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;st); err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;			return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%w&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;, please call export_start again&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, err)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;		// The handle belongs to the caller, or it is not served.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; st.UserID &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; userID&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(req) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;			return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;handle &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%q&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; belongs to a different identity&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, in.Handle)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		b, _ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; json.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Marshal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(st.Rows)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Content: []&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Content&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TextContent&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Text: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(b)}}}, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// userID returns the identity from the bearer token, see the authentication section.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; userID&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolRequest&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; req.Extra &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; req.Extra.TokenInfo &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; req.Extra.TokenInfo.UserID&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	store &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;memStore&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{} &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// one instance, outside the request path&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	handler &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mcp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewStreamableHTTPHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mcp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewServer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Implementation&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Name: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;erp-bridge&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, Version: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;1.4.0&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;		addExport&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s, store)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;StreamableHTTPOptions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;		// No Mcp-Session-Id, no held connection, no process memory.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Stateless:    &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		JSONResponse: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Fatal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Addr:              &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;:8080&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Handler:           handler,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		ReadHeaderTimeout: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time.Second,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ListenAndServe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The handle gets a TTL and an owner. Without a TTL the store grows without bound; without the ownership check in &lt;code&gt;export_page&lt;/code&gt;, a second user can redeem somebody else’s handle. Both are two lines, and both are missing from most example projects.&lt;/p&gt;
&lt;h2 id=&quot;authentication-how-a-tool-call-is-bound-to-a-user-identity&quot;&gt;Authentication: how a tool call is bound to a user identity&lt;/h2&gt;
&lt;p&gt;A tool call is bound to an identity through an OAuth 2.1 access token whose &lt;code&gt;aud&lt;/code&gt; claim contains the canonical URI of exactly this MCP server. The specification is unusually clear here: MCP servers must verify that a token was issued with them as the audience, and they must not accept or pass on any other tokens.&lt;/p&gt;
&lt;p&gt;This is the section the example projects leave out. A prototype runs with a shared bearer token in an environment variable, and every tool call reaches the database under the same service account. At that point the log can no longer establish which human triggered what.&lt;/p&gt;
&lt;p&gt;There is also an obligation that is not new but regularly missing: the server must serve OAuth 2.0 Protected Resource Metadata under &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9728&quot;&gt;RFC 9728&lt;/a&gt;, otherwise no client finds the responsible authorization server. That has been in the spec since revision 2025-06-18.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Complete program. go get github.com/modelcontextprotocol/go-sdk@v1.7.0 github.com/golang-jwt/jwt/v5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;package&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fmt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;net/http&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;slices&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;strings&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;github.com/golang-jwt/jwt/v5&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;github.com/modelcontextprotocol/go-sdk/auth&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;github.com/modelcontextprotocol/go-sdk/mcp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;github.com/modelcontextprotocol/go-sdk/oauthex&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;	// canonicalURI is the resource indicator under RFC 8707. Exactly this value&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;	// must appear in the aud claim, otherwise the token was for another service.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	canonicalURI&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;https://mcp.example.ch/mcp&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	issuer&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;       =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;https://login.example.ch/realms/erp&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// claims extends the standard claims with scope, as Keycloak and Entra ID deliver it.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; claims&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Scope &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `json:&amp;quot;scope&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;	jwt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;RegisteredClaims&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; verifyToken&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;parser&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;jwt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Parser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;keyFunc&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; jwt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Keyfunc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;auth&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TokenVerifier&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;raw&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;auth&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TokenInfo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		var&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;claims&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; _, err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; parser.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ParseWithClaims&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(raw, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;c, keyFunc); err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;			return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%w&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%v&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, auth.ErrInvalidToken, err)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c.Issuer &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; issuer {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;			return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%w&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;: unexpected issuer &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%q&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, auth.ErrInvalidToken, c.Issuer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;slices.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Contains&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(c.Audience, canonicalURI) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;			return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%w&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;: token not issued for &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, auth.ErrInvalidToken, canonicalURI)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c.Subject &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;			return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%w&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;: no sub claim, no identity&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, auth.ErrInvalidToken)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;auth&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TokenInfo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			UserID:     c.Subject,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			Scopes:     strings.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Fields&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(c.Scope),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			Expiration: c.ExpiresAt.Time,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		}, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; bookingIn&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Account &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `json:&amp;quot;account&amp;quot; jsonschema:&amp;quot;Account number under the Swiss SME chart of accounts&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Rappen  &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    `json:&amp;quot;amount_rappen&amp;quot; jsonschema:&amp;quot;Amount in rappen, integer&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	IdemKey &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `json:&amp;quot;idempotency_key&amp;quot; jsonschema:&amp;quot;UUID generated by the caller. The same key never books twice.&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; book&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolRequest&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; bookingIn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	tok &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; req.Extra.TokenInfo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tok &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;call without identity&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;slices.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Contains&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(tok.Scopes, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;accounting:write&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;scope accounting:write missing&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;	// From here every query runs under tok.UserID, not under a service account.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Printf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;booking user=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; account=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; rappen=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%d&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; idem=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, tok.UserID, in.Account, in.Rappen, in.IdemKey)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Content: []&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Content&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TextContent&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Text: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;booked&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}}}, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	server &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mcp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewServer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Implementation&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Name: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;erp-bridge&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, Version: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;1.4.0&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	mcp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddTool&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(server, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Tool&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Name:        &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;booking_create&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Description: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Records a booking. Writes. The same idempotency key never creates a second booking.&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Annotations: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToolAnnotations&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{IdempotentHint: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}, book)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	mcpHandler &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mcp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewStreamableHTTPHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; server },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;StreamableHTTPOptions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Stateless: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	var&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; keyFunc &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;jwt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Keyfunc&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // from the issuer&amp;#39;s JWKS, cached&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	requireToken &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; auth.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;RequireBearerToken&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;		verifyToken&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(jwt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewParser&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(jwt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WithValidMethods&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([]&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;RS256&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;ES256&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})), keyFunc),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;auth&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;RequireBearerTokenOptions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			Scopes:              []&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;accounting:read&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			ResourceMetadataURL: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;https://mcp.example.ch/.well-known/oauth-protected-resource&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	mux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; http.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewServeMux&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	mux.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Handle&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;/mcp&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;requireToken&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(mcpHandler))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;	// RFC 9728. The specification makes this document mandatory: without it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;	// no client finds the responsible authorization server.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	mux.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Handle&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;/.well-known/oauth-protected-resource&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, auth.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ProtectedResourceMetadataHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;oauthex&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ProtectedResourceMetadata&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			Resource:             canonicalURI,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			AuthorizationServers: []&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{issuer},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			ScopesSupported:      []&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;accounting:read&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;accounting:write&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		}))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Fatal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(http.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ListenAndServe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;:8080&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, mux))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The decisive point is in a single line: &lt;code&gt;tok.UserID&lt;/code&gt; comes from the &lt;code&gt;sub&lt;/code&gt; claim and is available to every tool handler from then on. The agent thereby inherits exactly the rights of the signed-in human, not those of a service account.&lt;/p&gt;
&lt;p&gt;In Switzerland that is not merely good style. Article 4 of the &lt;a href=&quot;https://www.fedlex.admin.ch/eli/cc/2022/568/de&quot;&gt;Data Protection Ordinance (DPO, SR 235.11)&lt;/a&gt;, the implementing ordinance to the revised DPA, obliges private controllers to log as soon as they process sensitive personal data automatically on a large scale and preventive measures cannot ensure data protection. Paragraph 4 says what has to be in it: information about “the identity of the person who carried out the processing, the type, the date and the time of the processing”.&lt;/p&gt;
&lt;p&gt;Paragraph 5 sets retention at a minimum of one year, separated from the processing system. A shared service account does not satisfy that technically. What the revised DPA demands of an agent system beyond that, provision by provision, is in &lt;a href=&quot;https://devs-group.ch/en/blog/revdsg-compliant-ai-architecture/&quot;&gt;A revDSG-compliant AI architecture&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is exactly where most prototypes stop, and usually only when the security review arrives. If that is where you are: we will look at your chain from identity provider to tool handler together once and say where it breaks. &lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;Book a slot&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;tool-design-the-mistakes-we-see-most-often&quot;&gt;Tool design: the mistakes we see most often&lt;/h2&gt;
&lt;p&gt;The three most common tool design mistakes are too many tools, vague descriptions, and missing idempotency. All three only show up in operation, as wrongly chosen tools and duplicate bookings.&lt;/p&gt;
&lt;p&gt;A description is the only context the model has when choosing. It belongs written with boundaries: what the tool does, what it does not do, and which other tool covers the adjacent case.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Complete program, speaks streamable HTTP on :8080.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;package&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fmt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;net/http&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;github.com/modelcontextprotocol/go-sdk/mcp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; searchIn&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	CustomerNo &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `json:&amp;quot;customer_no&amp;quot; jsonschema:&amp;quot;Customer number in the format K-00000, exactly as held in Abacus&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Year       &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    `json:&amp;quot;year&amp;quot; jsonschema:&amp;quot;Financial year, four digits, between 2015 and 2030&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; searchOut&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Hits      []&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; `json:&amp;quot;hits&amp;quot; jsonschema:&amp;quot;Invoice numbers, newest first&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Total     &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      `json:&amp;quot;total&amp;quot; jsonschema:&amp;quot;Total number of hits, even where the list is truncated&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Truncated &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;bool&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;     `json:&amp;quot;truncated&amp;quot; jsonschema:&amp;quot;true when truncated to 50 entries&amp;quot;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; search&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolRequest&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; searchIn&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;searchOut&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; in.Year &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2015&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; in.Year &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2030&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;		// Error text as an instruction, not a status code. The model reads it.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;searchOut&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{}, fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;			&amp;quot;year &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%d&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is outside the maintained range 2015 to 2030, please call again with a year in that range&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, in.Year)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;searchOut&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Hits: []&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;RE-2026-0184&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}, Total: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	server &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mcp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewServer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Implementation&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Name: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;erp-bridge&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, Version: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;1.4.0&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	mcp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddTool&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(server, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Tool&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Name: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;invoices_search&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Description: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Searches a customer&amp;#39;s accounts receivable invoices in a financial year. &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;			&amp;quot;Returns at most 50 invoice numbers and changes nothing. &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;			&amp;quot;Do not use to load a known invoice number; invoice_read is for that.&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Annotations: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToolAnnotations&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{ReadOnlyHint: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, Title: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Search invoices&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}, search)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	handler &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mcp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewStreamableHTTPHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; server },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;StreamableHTTPOptions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Stateless: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Fatal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(http.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ListenAndServe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;:8080&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, handler))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can call it with &lt;code&gt;curl&lt;/code&gt;. Since 2026-07-28 a streamable HTTP POST needs three headers and two mandatory fields in &lt;code&gt;_meta&lt;/code&gt;, otherwise the server answers with &lt;code&gt;-32020&lt;/code&gt; or &lt;code&gt;-32602&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -sN&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -X&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; POST&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; http://localhost:8080/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  -H&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  -H&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;Accept: application/json, text/event-stream&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  -H&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;Mcp-Protocol-Version: 2026-07-28&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  -H&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;Mcp-Method: tools/call&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  -H&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;Mcp-Name: invoices_search&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  -d&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;{&amp;quot;jsonrpc&amp;quot;:&amp;quot;2.0&amp;quot;,&amp;quot;id&amp;quot;:1,&amp;quot;method&amp;quot;:&amp;quot;tools/call&amp;quot;,&amp;quot;params&amp;quot;:{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &amp;quot;name&amp;quot;:&amp;quot;invoices_search&amp;quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &amp;quot;arguments&amp;quot;:{&amp;quot;customer_no&amp;quot;:&amp;quot;K-00042&amp;quot;,&amp;quot;year&amp;quot;:1999},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &amp;quot;_meta&amp;quot;:{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          &amp;quot;io.modelcontextprotocol/protocolVersion&amp;quot;:&amp;quot;2026-07-28&amp;quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          &amp;quot;io.modelcontextprotocol/clientCapabilities&amp;quot;:{},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          &amp;quot;io.modelcontextprotocol/clientInfo&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;curl&amp;quot;,&amp;quot;version&amp;quot;:&amp;quot;1&amp;quot;}}}}&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# event: message&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# data: {&amp;quot;jsonrpc&amp;quot;:&amp;quot;2.0&amp;quot;,&amp;quot;id&amp;quot;:1,&amp;quot;result&amp;quot;:{&amp;quot;content&amp;quot;:[{&amp;quot;type&amp;quot;:&amp;quot;text&amp;quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#   &amp;quot;text&amp;quot;:&amp;quot;year 1999 is outside the maintained range 2015 to 2030,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#           please call again with a year in that range&amp;quot;}],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#   &amp;quot;isError&amp;quot;:true,&amp;quot;resultType&amp;quot;:&amp;quot;complete&amp;quot;}}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The error case comes back as &lt;code&gt;isError: true&lt;/code&gt; with readable text, not as a JSON-RPC error. That is deliberate: a model can read a sentence and correct the call; a status code it can only guess at.&lt;/p&gt;
&lt;p&gt;One detail that stands out when rebuilding this: the Go SDK sets &lt;code&gt;cacheScope: &amp;quot;public&amp;quot;&lt;/code&gt; on &lt;code&gt;tools/list&lt;/code&gt; by default. Anyone serving a user-dependent tool list has to set &lt;code&gt;private&lt;/code&gt; themselves, otherwise a gateway may show one user’s list to the next.&lt;/p&gt;
&lt;p&gt;For write tools the same logic applies as for a payments API: the caller sends a self-generated key, the server reserves it before processing, and a repeat returns the same answer without a second effect. How we build that is in our article on &lt;a href=&quot;https://devs-group.ch/en/blog/idempotency-in-payment-apis/&quot;&gt;idempotency in payments APIs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With agents, repetition is more frequent than with humans, because after a timeout a model simply calls again.&lt;/p&gt;
&lt;p&gt;On quantity: twenty sharply drawn tools beat sixty overlapping ones. And since this revision &lt;code&gt;tools/list&lt;/code&gt; is supposed to be deterministically sorted, which improves clients’ prompt cache hit rate.&lt;/p&gt;
&lt;h2 id=&quot;operations-timeouts-rate-limits-and-what-belongs-in-the-logs&quot;&gt;Operations: timeouts, rate limits, and what belongs in the logs&lt;/h2&gt;
&lt;p&gt;A failed tool call is reconstructable when a single log line contains the tool name, identity, protocol version, duration and correlation ID. Revision 2026-07-28 documents the OpenTelemetry conventions for &lt;code&gt;_meta&lt;/code&gt; for the first time: &lt;code&gt;traceparent&lt;/code&gt;, &lt;code&gt;tracestate&lt;/code&gt; and &lt;code&gt;baggage&lt;/code&gt; travel inside the protocol.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Complete program. Every tool call produces exactly one JSON log line.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;package&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;log/slog&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;net/http&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;os&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;github.com/modelcontextprotocol/go-sdk/mcp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// logToolCalls writes one line per tool call, from which a failure can be&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// reconstructed without asking anyone.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; logToolCalls&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;logger&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;slog&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Middleware&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MethodHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MethodHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;method&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Result&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			call, ok &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; req.(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolRequest&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;			if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ok &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; method &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;tools/call&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;				return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ctx, method, req)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			start &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			res, err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ctx, method, req)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			attrs &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; []&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;				slog.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;tool&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, call.Params.Name),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;				slog.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;protocol_version&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, call.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ProtocolVersion&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;				slog.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Duration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;duration&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, time.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Since&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(start)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;			// traceparent travels in _meta under the 2026-07-28 specification.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;			if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tp, ok &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; call.Params.Meta[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;traceparent&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;); ok {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;				attrs &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; append&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(attrs, slog.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;traceparent&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, tp))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;			if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; extra &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; call.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetExtra&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); extra &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; extra.TokenInfo &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;				attrs &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; append&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(attrs, slog.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;user_id&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, extra.TokenInfo.UserID))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;			if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;				logger.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;tool_call failed&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;append&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(attrs, slog.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;error&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, err.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()))&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			} &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;				logger.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Info&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;tool_call&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, attrs&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;			return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; res, err&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	logger &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; slog.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;New&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(slog.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewJSONHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(os.Stderr, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	server &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mcp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewServer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Implementation&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Name: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;erp-bridge&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, Version: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;1.4.0&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	server.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddReceivingMiddleware&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;logToolCalls&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(logger))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	mcp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddTool&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(server, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Tool&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Name:        &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;ping&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Description: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;Answers with pong. Serves only to check the logging chain.&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Annotations: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToolAnnotations&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{ReadOnlyHint: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolRequest&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{}) (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CallToolResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Content: []&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Content&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TextContent&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Text: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;pong&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}}}, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	handler &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mcp.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewStreamableHTTPHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Server&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; server },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mcp&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;StreamableHTTPOptions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{Stateless: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Fatal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(http.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ListenAndServe&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;:8080&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, handler))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two operational values are better set explicitly before the first incident sets them for you. First, a timeout per tool rather than per server: a full-text search may take ten seconds, an account balance may not.&lt;/p&gt;
&lt;p&gt;Second, a rate limit per identity rather than per IP. Behind an IP sits an entire company; behind an identity sits an agent that loops when something fails. What belongs on the dashboards and what does not is in &lt;a href=&quot;https://devs-group.ch/en/blog/observability-you-actually-read/&quot;&gt;three dashboards instead of thirty&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;is-a-pod-the-right-unit-for-an-agent&quot;&gt;Is a pod the right unit for an agent?&lt;/h2&gt;
&lt;p&gt;One pod per agent is the obvious answer but not obviously the right one. Lin Sun of Solo.io asked the question on 14 July 2026 in the CNCF blog under exactly that title: &lt;a href=&quot;https://www.cncf.io/blog/2026/07/14/is-a-pod-the-right-deployment-unit-for-an-ai-agent/&quot;&gt;Is a Pod the right deployment unit for an AI agent?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A pod of its own brings process isolation and, through the ServiceAccount, a Kubernetes identity, but it costs baseline load: an agent is active for seconds and then idle for a long time.&lt;/p&gt;
&lt;p&gt;For an MCP server doing pure tool execution that is good news: it is an ordinary stateless service and does not need this discussion. It only becomes relevant once the server executes foreign code. Then the pod is the wrong boundary, and a real sandbox is required.&lt;/p&gt;
&lt;h2 id=&quot;what-we-advise-against&quot;&gt;What we advise against&lt;/h2&gt;
&lt;p&gt;Do not build an MCP server when an ordinary HTTP API will do. MCP solves a discovery problem, not an integration problem: it describes tools so that a model can find and choose them at runtime without anyone hard-wiring the integration beforehand. If your application knows exactly which endpoint it wants to call, a &lt;code&gt;POST /invoices/search&lt;/code&gt; is cheaper and simpler to operate.&lt;/p&gt;
&lt;p&gt;The trade-off is real. MCP costs you an authorization server, an RFC 9728 document, maintained tool descriptions, and a protocol in motion. In return you get tools any MCP-capable client can discover without you building anything for it.&lt;/p&gt;
&lt;p&gt;Our line: an MCP server earns its keep when more than one client needs the same tools, or when choosing the tools is itself part of the task. For the AI agent platform AIgent that applies.&lt;/p&gt;
&lt;p&gt;For a single internal automation with three known calls it does not. We say so even when a project was budgeted differently.&lt;/p&gt;
&lt;p&gt;And: do not start the deprecation migration while authentication is missing. Roots, sampling and logging have twelve months. A server without identity binding does not have a deadline, it has a problem.&lt;/p&gt;
&lt;h2 id=&quot;frequently-asked&quot;&gt;Frequently asked&lt;/h2&gt;
&lt;h3 id=&quot;what-is-the-model-context-protocol&quot;&gt;What is the Model Context Protocol?&lt;/h3&gt;
&lt;p&gt;The Model Context Protocol is an open protocol through which AI applications discover and call a server’s tools, resources and prompts at runtime. It builds on JSON-RPC and knows two transports: STDIO for local processes and streamable HTTP for network services. Since revision 2026-07-28 the protocol core is stateless, and every request describes itself.&lt;/p&gt;
&lt;h3 id=&quot;how-do-i-build-an-mcp-server-in-go&quot;&gt;How do I build an MCP server in Go?&lt;/h3&gt;
&lt;p&gt;With the official &lt;code&gt;github.com/modelcontextprotocol/go-sdk&lt;/code&gt;, currently v1.7.0. A server is created with &lt;code&gt;mcp.NewServer&lt;/code&gt;, tools are registered through the generic function &lt;code&gt;mcp.AddTool&lt;/code&gt;, which derives the input and output schema from your Go types by reflection. For network operation add &lt;code&gt;mcp.NewStreamableHTTPHandler&lt;/code&gt;; for local processes &lt;code&gt;server.Run&lt;/code&gt; with &lt;code&gt;mcp.StdioTransport&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;how-do-i-authenticate-an-ai-agents-tool-calls&quot;&gt;How do I authenticate an AI agent’s tool calls?&lt;/h3&gt;
&lt;p&gt;Through OAuth 2.1 with the MCP server as resource server. The client obtains an access token whose &lt;code&gt;aud&lt;/code&gt; claim names the server’s canonical URI and sends it as a bearer token in every HTTP request. The server verifies signature, issuer and audience and translates the &lt;code&gt;sub&lt;/code&gt; claim into an identity under which the tool handlers work. A shared static token does not satisfy this.&lt;/p&gt;
&lt;h3 id=&quot;do-i-have-to-migrate-my-existing-mcp-server-now&quot;&gt;Do I have to migrate my existing MCP server now?&lt;/h3&gt;
&lt;p&gt;Not immediately, but the direction is clear. Roots, sampling, logging, the HTTP+SSE transport and dynamic client registration are deprecated and continue to work for at least twelve months. The move to stateless, by contrast, is not a deprecation matter but a protocol change: sessions and &lt;code&gt;Mcp-Session-Id&lt;/code&gt; are removed in 2026-07-28. Anyone building new builds stateless.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;strong&gt;Let’s talk about your agent infrastructure.&lt;/strong&gt; Thirty minutes with one of our Go engineers: which tools you actually want to expose, how identity is passed from login through to the tool handler, and whether MCP is the right answer for your case. No sales pitch, no preparation on your side.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;Book a slot&lt;/a&gt; · &lt;a href=&quot;https://devs-group.ch/en/contact/&quot;&gt;Get in touch&lt;/a&gt; · More on &lt;a href=&quot;https://devs-group.ch/en/services/artificial-intelligence/&quot;&gt;AI and agent systems&lt;/a&gt; and &lt;a href=&quot;https://devs-group.ch/en/services/backend-and-frontend/&quot;&gt;backend engineering&lt;/a&gt;&lt;/p&gt;</content:encoded><category>Architecture</category><author>Ralph Segi</author></item><item><title>Guardrails for coding agents: never its own reviewer</title><link>https://devs-group.ch/en/blog/guardrails-for-coding-agents/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/guardrails-for-coding-agents/</guid><description>Scale review depth by task type rather than by agent confidence. What belongs in the CI pipeline and what a human has to read.</description><pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A coding agent must not sign off its own work. Verification belongs in an authority the agent does not control: deterministic gates in CI, and a human who reads the whole diff whenever existing code is touched. How deeply something is checked is decided by the task type, not by the agent’s confidence score.&lt;/p&gt;
&lt;p&gt;Robert Pupel set out three rules we hold to in &lt;a href=&quot;https://devs-group.ch/en/blog/ai-code-without-technical-debt/&quot;&gt;AI writes the code. Who writes the architecture?&lt;/a&gt; This article describes the mechanism underneath: which gates sit in the pipeline, what triggers them, and where a human cannot be replaced. Guardrails for coding agents are not a process document but code in the repository.&lt;/p&gt;
&lt;h2 id=&quot;three-sources-arriving-independently-at-the-same-conclusion&quot;&gt;Three sources arriving independently at the same conclusion&lt;/h2&gt;
&lt;p&gt;Research, consulting and practice arrived from different directions at the same rule in 2026: an agent must not be its own reviewer.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.thoughtworks.com/radar&quot;&gt;Thoughtworks Technology Radar Vol. 34&lt;/a&gt; of April 2026 lists “Putting coding agents on a leash” as one of four themes. Their reasoning: because agents deliver better results, humans increasingly step out of the loop, and teams therefore start investing in harnesses. Thoughtworks is a consultancy that sells AI transformation projects, and declares the Radar itself to be practitioner opinion rather than empirical work.&lt;/p&gt;
&lt;p&gt;Octomind, a test tooling vendor, &lt;a href=&quot;https://octomind.run/blog/octomind-0-44-2-release&quot;&gt;removed&lt;/a&gt; self-verification from its own agent in August 2026. The sentence from the release that names the problem precisely: “a model that wants to be done has a way to &lt;em&gt;look&lt;/em&gt; done: advance the checklist, mark tasks complete, close the plan.” An external plan manager has owned progress since then, and the agent has no access to that tool. This is a vendor’s account of itself, but one with a mechanism you can follow.&lt;/p&gt;
&lt;p&gt;The third source is the most interesting, because it is countable.&lt;/p&gt;
&lt;h2 id=&quot;what-932791-agent-generated-pull-requests-show&quot;&gt;What 932,791 agent-generated pull requests show&lt;/h2&gt;
&lt;p&gt;Since February 2026 there has been a defensible evidence base for statements about coding agents. The &lt;strong&gt;AIDev&lt;/strong&gt; dataset (Li, Zhang, Hassan, &lt;a href=&quot;https://arxiv.org/abs/2602.09185&quot;&gt;arXiv:2602.09185&lt;/a&gt;) collects 932,791 agent-generated pull requests from 116,211 repositories belonging to 72,189 developers, produced by five agents: OpenAI Codex, Devin, GitHub Copilot, Cursor and Claude Code. Open, academic, with no declared vendor funding.&lt;/p&gt;
&lt;p&gt;Building on it, Ferdous, Banik, Chowdhury and Shamim compared 7,191 agent-generated against 1,402 human pull requests from Python repositories and detected breaking changes through AST analysis (&lt;a href=&quot;https://arxiv.org/abs/2603.27524&quot;&gt;arXiv:2603.27524&lt;/a&gt;, submitted 29 March 2026). The result is counter-intuitive:&lt;/p&gt;

























&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Context&lt;/th&gt;&lt;th&gt;Breaking change rate&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Greenfield, &lt;strong&gt;agents&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;3.45 %&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Greenfield, &lt;strong&gt;humans&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;7.40 %&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Agents on &lt;strong&gt;refactoring&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;6.72 %&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Agents on &lt;strong&gt;chore changes&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;9.35 %&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;On a green field, then, agents break compatibility less often than humans. The moment they touch existing code, the picture inverts. Precisely the opposite of what most teams instinctively guard against.&lt;/p&gt;
&lt;h2 id=&quot;the-confidence-trap-why-the-self-assessment-fails-as-a-filter&quot;&gt;The confidence trap: why the self-assessment fails as a filter&lt;/h2&gt;
&lt;p&gt;A high confidence score from the agent says nothing about whether the pull request stays compatible. The same work by Ferdous et al. names this the “confidence trap”: pull requests with a high self-reported confidence score contain breaking changes regardless. The authors derive from it, in so many words, the need for stricter checking on maintenance work, “regardless of reported confidence score”.&lt;/p&gt;
&lt;p&gt;That is the practically most important number of the year, because it destroys a widespread short circuit. Hanging merge gates on a self-assessment has made the agent its own reviewer, just with more steps.&lt;/p&gt;
&lt;p&gt;If you are about to let an agent loose on an existing Go backend and are not sure which gates need to be in place: that is a 30-minute conversation, not a project. We look at the pipeline you have and say what is missing.&lt;/p&gt;
&lt;h2 id=&quot;review-depth-by-task-type-not-by-confidence&quot;&gt;Review depth by task type, not by confidence&lt;/h2&gt;
&lt;p&gt;What the data implies is a matrix with two lanes. It replaces the question “how confident is the agent?” with the question “what does this pull request touch?”.&lt;/p&gt;



































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Task type&lt;/th&gt;&lt;th&gt;Evidenced rate&lt;/th&gt;&lt;th&gt;Automated gates&lt;/th&gt;&lt;th&gt;Human review&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Greenfield&lt;/strong&gt;, new files, no existing code touched&lt;/td&gt;&lt;td&gt;3.45 %&lt;/td&gt;&lt;td&gt;Build, tests, &lt;code&gt;govulncheck&lt;/code&gt;, lint, coverage threshold&lt;/td&gt;&lt;td&gt;Read the interface and the data model, not every line&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Refactoring&lt;/strong&gt; of existing code&lt;/td&gt;&lt;td&gt;6.72 %&lt;/td&gt;&lt;td&gt;plus &lt;code&gt;gorelease&lt;/code&gt; against the last tag, rendered manifest diff&lt;/td&gt;&lt;td&gt;Full diff, second pair of eyes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Chore&lt;/strong&gt;, dependencies, build, configuration&lt;/td&gt;&lt;td&gt;9.35 %&lt;/td&gt;&lt;td&gt;plus lockfile diff, image digest pinning, manifest diff&lt;/td&gt;&lt;td&gt;Full diff, second pair of eyes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Processing of personal data&lt;/strong&gt; touched&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;td&gt;everything above&lt;/td&gt;&lt;td&gt;Mandatory, whatever the task type&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The last row is not a finding from the data but Swiss law. Art. 7 DPA requires data protection by design from the planning stage, and Art. 2 DPO names traceability as one of four protection goals. A merge nobody has signed off by name does not satisfy that. What it means for the architecture is in &lt;a href=&quot;https://devs-group.ch/en/blog/revdsg-compliant-ai-architecture/&quot;&gt;A revDSG-compliant AI architecture&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-the-two-lanes-look-like-in-a-ci-pipeline&quot;&gt;What the two lanes look like in a CI pipeline&lt;/h2&gt;
&lt;p&gt;Assignment to a lane has to be deterministic and out of the agent’s reach. It must not depend on how the agent titled its pull request.&lt;/p&gt;
&lt;div class=&quot;scroll-x scroll-x--diagram&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Diagramm, seitlich scrollbar&quot;&gt;&lt;svg id=&quot;mermaid-0&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; class=&quot;flowchart&quot; style=&quot;width:586px&quot; viewBox=&quot;0 0 586 887.859375&quot; role=&quot;graphics-document document&quot; aria-roledescription=&quot;flowchart-v2&quot;&gt;&lt;style&gt;#mermaid-0{font-family:&quot;Geist&quot;,sans-serif;font-size:16px;fill:#f5f5f5;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-0 .error-icon{fill:#121315;}#mermaid-0 .error-text{fill:#edecea;stroke:#edecea;}#mermaid-0 .edge-thickness-normal{stroke-width:1px;}#mermaid-0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-0 .marker{fill:#c4c4c4;stroke:#c4c4c4;}#mermaid-0 .marker.cross{stroke:#c4c4c4;}#mermaid-0 svg{font-family:&quot;Geist&quot;,sans-serif;font-size:16px;}#mermaid-0 p{margin:0;}#mermaid-0 .label{font-family:&quot;Geist&quot;,sans-serif;color:#f5f5f5;}#mermaid-0 .cluster-label text{fill:#f5f5f5;}#mermaid-0 .cluster-label span{color:#f5f5f5;}#mermaid-0 .cluster-label span p{background-color:transparent;}#mermaid-0 .label text,#mermaid-0 span{fill:#f5f5f5;color:#f5f5f5;}#mermaid-0 .node rect,#mermaid-0 .node circle,#mermaid-0 .node ellipse,#mermaid-0 .node polygon,#mermaid-0 .node path{fill:#1b1d20;stroke:#363739;stroke-width:1px;}#mermaid-0 .rough-node .label text,#mermaid-0 .node .label text,#mermaid-0 .image-shape .label,#mermaid-0 .icon-shape .label{text-anchor:middle;}#mermaid-0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-0 .rough-node .label,#mermaid-0 .node .label,#mermaid-0 .image-shape .label,#mermaid-0 .icon-shape .label{text-align:center;}#mermaid-0 .node.clickable{cursor:pointer;}#mermaid-0 .root .anchor path{fill:#c4c4c4!important;stroke-width:0;stroke:#c4c4c4;}#mermaid-0 .arrowheadPath{fill:#f6f5f3;}#mermaid-0 .edgePath .path{stroke:#c4c4c4;stroke-width:1px;}#mermaid-0 .flowchart-link{stroke:#c4c4c4;fill:none;}#mermaid-0 .edgeLabel{background-color:#090a0c;text-align:center;}#mermaid-0 .edgeLabel p{background-color:#090a0c;}#mermaid-0 .edgeLabel rect{opacity:0.5;background-color:#090a0c;fill:#090a0c;}#mermaid-0 .labelBkg{background-color:rgba(9, 10, 12, 0.5);}#mermaid-0 .cluster rect{fill:#121315;stroke:#232325;stroke-width:1px;}#mermaid-0 .cluster text{fill:#f5f5f5;}#mermaid-0 .cluster span{color:#f5f5f5;}#mermaid-0 .node .collapsed-indicator{fill:#232325;stroke:none;opacity:0.6;}#mermaid-0 .node .collapsed-separator{stroke:#232325;stroke-width:0.75px;}#mermaid-0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:&quot;Geist&quot;,sans-serif;font-size:12px;background:#121315;border:1px solid hsl(220, 0%, 17.6470588235%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#f5f5f5;}#mermaid-0 rect.text{fill:none;stroke-width:0;}#mermaid-0 .icon-shape,#mermaid-0 .image-shape{background-color:#090a0c;text-align:center;}#mermaid-0 .icon-shape p,#mermaid-0 .image-shape p{background-color:#090a0c;padding:2px;}#mermaid-0 .icon-shape .label rect,#mermaid-0 .image-shape .label rect{opacity:0.5;background-color:#090a0c;fill:#090a0c;}#mermaid-0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-0 .node .neo-node{stroke:#363739;}#mermaid-0 [data-look=&quot;neo&quot;].node rect,#mermaid-0 [data-look=&quot;neo&quot;].cluster rect,#mermaid-0 [data-look=&quot;neo&quot;].node polygon{stroke:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].swimlane.cluster rect{filter:none;}#mermaid-0 [data-look=&quot;neo&quot;].node path{stroke:url(#mermaid-0-gradient);stroke-width:1px;}#mermaid-0 [data-look=&quot;neo&quot;].node .outer-path{filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].node .neo-line path{stroke:#363739;filter:none;}#mermaid-0 [data-look=&quot;neo&quot;].node circle{stroke:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].node circle .state-start{fill:#000000;}#mermaid-0 [data-look=&quot;neo&quot;].icon-shape .icon{fill:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].icon-shape .icon-neo path{stroke:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 :root{--mermaid-font-family:&quot;Geist&quot;,sans-serif;}&lt;/style&gt;&lt;g&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointEnd&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;5&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;8&quot; markerHeight=&quot;8&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 0 L 10 5 L 0 10 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointStart&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;4.5&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;8&quot; markerHeight=&quot;8&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 5 L 10 10 L 10 0 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointEnd-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 11.5 14&quot; refX=&quot;11.5&quot; refY=&quot;7&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;10.5&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 0 L 11.5 7 L 0 14 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointStart-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 11.5 14&quot; refX=&quot;1&quot; refY=&quot;7&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11.5&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;polygon points=&quot;0,7 11.5,14 11.5,0&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/polygon&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleEnd&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;11&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleStart&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;-1&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleEnd-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refY=&quot;5&quot; refX=&quot;12.25&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;14&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleStart-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;-2&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;14&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossEnd&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 11 11&quot; refX=&quot;12&quot; refY=&quot;5.2&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 l 9,9 M 10,1 l -9,9&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossStart&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 11 11&quot; refX=&quot;-1&quot; refY=&quot;5.2&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 l 9,9 M 10,1 l -9,9&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossEnd-margin&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 15 15&quot; refX=&quot;17.7&quot; refY=&quot;7.5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;12&quot; markerHeight=&quot;12&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 L 14,14 M 1,14 L 14,1&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2.5&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossStart-margin&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 15 15&quot; refX=&quot;-3.5&quot; refY=&quot;7.5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;12&quot; markerHeight=&quot;12&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 L 14,14 M 1,14 L 14,1&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2.5;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;g class=&quot;root&quot;&gt;&lt;g class=&quot;clusters&quot;&gt;&lt;/g&gt;&lt;g class=&quot;edgePaths&quot;&gt;&lt;path d=&quot;M293,62L293,66.167C293,70.333,293,78.667,293,86.333C293,94,293,101,293,104.5L293,108&quot; id=&quot;mermaid-0-L_PR_K_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_PR_K_0&quot; data-points=&quot;W3sieCI6MjkzLCJ5Ijo2Mn0seyJ4IjoyOTMsInkiOjg3fSx7IngiOjI5MywieSI6MTEyfV0=&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M244.312,239.172L226.594,253.453C208.875,267.734,173.437,296.297,155.719,321.245C138,346.193,138,367.526,138,386.859C138,406.193,138,423.526,138,435.693C138,447.859,138,454.859,138,458.359L138,461.859&quot; id=&quot;mermaid-0-L_K_N_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_K_N_0&quot; data-points=&quot;W3sieCI6MjQ0LjMxMjQxMTA0MDcxODkzLCJ5IjoyMzkuMTcxNzg2MDQwNzE4OTN9LHsieCI6MTM4LCJ5IjozMjQuODU5Mzc1fSx7IngiOjEzOCwieSI6Mzg4Ljg1OTM3NX0seyJ4IjoxMzgsInkiOjQ0MC44NTkzNzV9LHsieCI6MTM4LCJ5Ijo0NjUuODU5Mzc1fV0=&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M341.688,239.172L359.406,253.453C377.125,267.734,412.563,296.297,430.281,316.078C448,335.859,448,346.859,448,352.359L448,357.859&quot; id=&quot;mermaid-0-L_K_B_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_K_B_0&quot; data-points=&quot;W3sieCI6MzQxLjY4NzU4ODk1OTI4MTEsInkiOjIzOS4xNzE3ODYwNDA3MTg5M30seyJ4Ijo0NDgsInkiOjMyNC44NTkzNzV9LHsieCI6NDQ4LCJ5IjozNjEuODU5Mzc1fV0=&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M138,519.859L138,524.026C138,528.193,138,536.526,138,546.193C138,555.859,138,566.859,138,572.359L138,577.859&quot; id=&quot;mermaid-0-L_N_A1_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_N_A1_0&quot; data-points=&quot;W3sieCI6MTM4LCJ5Ijo1MTkuODU5Mzc1fSx7IngiOjEzOCwieSI6NTQ0Ljg1OTM3NX0seyJ4IjoxMzgsInkiOjU4MS44NTkzNzV9XQ==&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M448,415.859L448,420.026C448,424.193,448,432.526,448,440.193C448,447.859,448,454.859,448,458.359L448,461.859&quot; id=&quot;mermaid-0-L_B_A2_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_B_A2_0&quot; data-points=&quot;W3sieCI6NDQ4LCJ5Ijo0MTUuODU5Mzc1fSx7IngiOjQ0OCwieSI6NDQwLjg1OTM3NX0seyJ4Ijo0NDgsInkiOjQ2NS44NTkzNzV9XQ==&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M448,519.859L448,524.026C448,528.193,448,536.526,448,544.193C448,551.859,448,558.859,448,562.359L448,565.859&quot; id=&quot;mermaid-0-L_A2_A3_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_A2_A3_0&quot; data-points=&quot;W3sieCI6NDQ4LCJ5Ijo1MTkuODU5Mzc1fSx7IngiOjQ0OCwieSI6NTQ0Ljg1OTM3NX0seyJ4Ijo0NDgsInkiOjU2OS44NTkzNzV9XQ==&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M138,635.859L138,642.026C138,648.193,138,660.526,138,670.193C138,679.859,138,686.859,138,690.359L138,693.859&quot; id=&quot;mermaid-0-L_A1_M1_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_A1_M1_0&quot; data-points=&quot;W3sieCI6MTM4LCJ5Ijo2MzUuODU5Mzc1fSx7IngiOjEzOCwieSI6NjcyLjg1OTM3NX0seyJ4IjoxMzgsInkiOjY5Ny44NTkzNzV9XQ==&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M448,647.859L448,652.026C448,656.193,448,664.526,448,672.193C448,679.859,448,686.859,448,690.359L448,693.859&quot; id=&quot;mermaid-0-L_A3_M2_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_A3_M2_0&quot; data-points=&quot;W3sieCI6NDQ4LCJ5Ijo2NDcuODU5Mzc1fSx7IngiOjQ0OCwieSI6NjcyLjg1OTM3NX0seyJ4Ijo0NDgsInkiOjY5Ny44NTkzNzV9XQ==&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M138,775.859L138,780.026C138,784.193,138,792.526,154.331,802.172C170.663,811.817,203.326,822.775,219.657,828.254L235.989,833.733&quot; id=&quot;mermaid-0-L_M1_MG_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_M1_MG_0&quot; data-points=&quot;W3sieCI6MTM4LCJ5Ijo3NzUuODU5Mzc1fSx7IngiOjEzOCwieSI6ODAwLjg1OTM3NX0seyJ4IjoyMzkuNzgxMjUsInkiOjgzNS4wMDUzNDI3NDE5MzU1fV0=&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M448,775.859L448,780.026C448,784.193,448,792.526,431.669,802.172C415.337,811.817,382.674,822.775,366.343,828.254L350.011,833.733&quot; id=&quot;mermaid-0-L_M2_MG_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_M2_MG_0&quot; data-points=&quot;W3sieCI6NDQ4LCJ5Ijo3NzUuODU5Mzc1fSx7IngiOjQ0OCwieSI6ODAwLjg1OTM3NX0seyJ4IjozNDYuMjE4NzUsInkiOjgzNS4wMDUzNDI3NDE5MzU1fV0=&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabels&quot;&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_PR_K_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(138, 388.859375)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_K_N_0&quot; transform=&quot;translate(-9.140625, -12)&quot;&gt;&lt;foreignObject width=&quot;18.28125&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;no&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(448, 324.859375)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_K_B_0&quot; transform=&quot;translate(-54.03125, -12)&quot;&gt;&lt;foreignObject width=&quot;108.0625&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;yes / unknown&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_N_A1_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_B_A2_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_A2_A3_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_A1_M1_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_A3_M2_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_M1_MG_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_M2_MG_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;nodes&quot;&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-PR-0&quot; data-look=&quot;classic&quot; transform=&quot;translate(293, 35)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-64.1640625&quot; y=&quot;-27&quot; width=&quot;128.328125&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-34.1640625, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;68.328125&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Agent PR&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-K-1&quot; data-look=&quot;classic&quot; transform=&quot;translate(293, 199.9296875)&quot;&gt;&lt;polygon points=&quot;87.9296875,0 175.859375,-87.9296875 87.9296875,-175.859375 0,-87.9296875&quot; class=&quot;label-container&quot; transform=&quot;translate(-87.4296875, 87.9296875)&quot;&gt;&lt;/polygon&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-48.9296875, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;97.859375&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Existing code&lt;br/&gt;touched?&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-N-3&quot; data-look=&quot;classic&quot; transform=&quot;translate(138, 492.859375)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-85.0703125&quot; y=&quot;-27&quot; width=&quot;170.140625&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-55.0703125, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;110.140625&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Greenfield lane&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-B-5&quot; data-look=&quot;classic&quot; transform=&quot;translate(448, 388.859375)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-97.8359375&quot; y=&quot;-27&quot; width=&quot;195.671875&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-67.8359375, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;135.671875&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Existing-code lane&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-A1-7&quot; data-look=&quot;classic&quot; transform=&quot;translate(138, 608.859375)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-120.9609375&quot; y=&quot;-27&quot; width=&quot;241.921875&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-90.9609375, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;181.921875&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Build, tests, govulncheck&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-A2-9&quot; data-look=&quot;classic&quot; transform=&quot;translate(448, 492.859375)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-120.9609375&quot; y=&quot;-27&quot; width=&quot;241.921875&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-90.9609375, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;181.921875&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Build, tests, govulncheck&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-A3-11&quot; data-look=&quot;classic&quot; transform=&quot;translate(448, 608.859375)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-130&quot; y=&quot;-39&quot; width=&quot;260&quot; height=&quot;78&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;gorelease, Helm diff, lockfile diff&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-M1-13&quot; data-look=&quot;classic&quot; transform=&quot;translate(138, 736.859375)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-130&quot; y=&quot;-39&quot; width=&quot;260&quot; height=&quot;78&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Review: interface, data model&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-M2-15&quot; data-look=&quot;classic&quot; transform=&quot;translate(448, 736.859375)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-130&quot; y=&quot;-39&quot; width=&quot;260&quot; height=&quot;78&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Review: whole diff, two pairs of eyes&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-MG-17&quot; data-look=&quot;classic&quot; transform=&quot;translate(293, 852.859375)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-53.21875&quot; y=&quot;-27&quot; width=&quot;106.4375&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-23.21875, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;46.4375&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Merge&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;defs&gt;&lt;filter id=&quot;mermaid-0-drop-shadow&quot; height=&quot;130%&quot; width=&quot;130%&quot;&gt;&lt;feDropShadow dx=&quot;4&quot; dy=&quot;4&quot; stdDeviation=&quot;0&quot; flood-opacity=&quot;0.06&quot; flood-color=&quot;#000000&quot;&gt;&lt;/feDropShadow&gt;&lt;/filter&gt;&lt;/defs&gt;&lt;defs&gt;&lt;filter id=&quot;mermaid-0-drop-shadow-small&quot; height=&quot;150%&quot; width=&quot;150%&quot;&gt;&lt;feDropShadow dx=&quot;2&quot; dy=&quot;2&quot; stdDeviation=&quot;0&quot; flood-opacity=&quot;0.06&quot; flood-color=&quot;#000000&quot;&gt;&lt;/feDropShadow&gt;&lt;/filter&gt;&lt;/defs&gt;&lt;linearGradient id=&quot;mermaid-0-gradient&quot; gradientUnits=&quot;objectBoundingBox&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;&lt;stop offset=&quot;0%&quot; stop-color=&quot;#363739&quot; stop-opacity=&quot;1&quot;&gt;&lt;/stop&gt;&lt;stop offset=&quot;100%&quot; stop-color=&quot;hsl(220, 0%, 17.6470588235%)&quot; stop-opacity=&quot;1&quot;&gt;&lt;/stop&gt;&lt;/linearGradient&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;p&gt;The commit type under Conventional Commits is a usable hint but not evidence: the agent writes the title. The hard criterion comes from the diff itself. Deleted lines mean existing code was touched.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# .github/workflows/agent-gate.yml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;agent-gate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;pull_request&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  lane&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    runs-on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    outputs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      lane&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;${{ steps.choose.outputs.lane }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    steps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;actions/checkout@v7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          fetch-depth&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;choose&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        env&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;          # Never interpolate the title directly into `run`: script injection.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          TITLE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;${{ github.event.pull_request.title }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          BASE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;${{ github.event.pull_request.base.sha }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          case &amp;quot;$TITLE&amp;quot; in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            feat:*|&amp;quot;feat(&amp;quot;*) LANE=greenfield ;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            *)               LANE=existing ;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          esac&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          CHANGED=$(git diff --numstat &amp;quot;$BASE&amp;quot;...HEAD | awk &amp;#39;$2 &amp;gt; 0&amp;#39; | wc -l)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          if [ &amp;quot;$CHANGED&amp;quot; -gt 0 ]; then LANE=existing; fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          echo &amp;quot;lane=$LANE&amp;quot; &amp;gt;&amp;gt; &amp;quot;$GITHUB_OUTPUT&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unknown lands in the strict lane. That is the whole trick: the default is strict, not lenient.&lt;/p&gt;
&lt;h2 id=&quot;two-gates-that-actually-bite-in-go-and-kubernetes-work&quot;&gt;Two gates that actually bite in Go and Kubernetes work&lt;/h2&gt;
&lt;p&gt;The existing-code lane needs checks that make compatibility breaks mechanically visible rather than leaving them to a review.&lt;/p&gt;
&lt;p&gt;In a Go module, &lt;code&gt;gorelease&lt;/code&gt; from &lt;a href=&quot;https://pkg.go.dev/golang.org/x/exp/cmd/gorelease&quot;&gt;golang.org/x/exp/cmd/gorelease&lt;/a&gt; does that. It compares the public API of the checked-out state against a base version and exits non-zero on incompatible differences from major version 1 onwards. That makes the study’s AST finding exactly what the gate measures.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  api-contract&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    needs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;lane&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;needs.lane.outputs.lane == &amp;#39;existing&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    runs-on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    steps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;actions/checkout@v7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          fetch-depth&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;actions/setup-go@v7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          go-version-file&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;go.mod&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;go install golang.org/x/exp/cmd/gorelease@latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;gorelease -base=latest&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the Kubernetes side the equivalent is the rendered manifest diff. Do not compare the chart, compare the result: an agent that renames a value leaves a harmless line in the chart and a missing environment variable in the deployment.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Renders base and PR state and compares the result, not the source.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;helm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; template&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; svc&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./chart&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /tmp/new.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; worktree&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /tmp/base&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$BASE_SHA&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;helm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; template&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; svc&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /tmp/base/chart&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /tmp/base.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;diff&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /tmp/base.yaml&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /tmp/new.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;what-the-pipeline-cannot-catch&quot;&gt;What the pipeline cannot catch&lt;/h2&gt;
&lt;p&gt;Static analysis alone is not a security gate. Firouzi and Ghafari held 1,080 LLM-generated code samples against a human-validated reference (&lt;a href=&quot;https://arxiv.org/abs/2602.05868&quot;&gt;arXiv:2602.05868&lt;/a&gt;, 5 February 2026): only 65 % of Semgrep’s and 61 % of CodeQL’s findings matched the reference. In aggregate both tools looked plausible; per sample they diverged considerably.&lt;/p&gt;
&lt;p&gt;The need is real. Peng, Wang and Zhu found across 3,700 snippets from the LLMSecEval benchmark that 68.8 % violated at least one security requirement, rising to 79.1 % for hard-coded credentials (&lt;a href=&quot;https://arxiv.org/abs/2607.12089&quot;&gt;arXiv:2607.12089&lt;/a&gt;). Ma et al. report average vulnerability rates above 56 % across eight models (&lt;a href=&quot;https://arxiv.org/abs/2607.23088&quot;&gt;arXiv:2607.23088&lt;/a&gt;, poster). The absolute figures are not comparable between these works; the direction is consistent across more than a dozen independent studies.&lt;/p&gt;
&lt;p&gt;What measurably helps is downstream repair rather than better prompts: Sriram, Pradhan and Saha combined compiler diagnostics, CodeQL and symbolic execution and reduced CodeLlama 7B’s security defects in C code from 49 % to 19 % (&lt;a href=&quot;https://arxiv.org/abs/2607.21641&quot;&gt;arXiv:2607.21641&lt;/a&gt;). So a repair pass belongs in the pipeline; a free pass drawn from it does not.&lt;/p&gt;
&lt;h2 id=&quot;separate-planning-from-execution&quot;&gt;Separate planning from execution&lt;/h2&gt;
&lt;p&gt;The plan has to exist before the code and be signed off by a human. Boris Tane describes this as the most-shared working pattern there is: “The separation of planning and execution is the single most important thing I do”, and following from it, “never let Claude write code until you’ve reviewed and approved a written plan” (&lt;a href=&quot;https://boristane.com/blog/how-i-use-claude-code/&quot;&gt;boristane.com&lt;/a&gt;, February 2026).&lt;/p&gt;
&lt;p&gt;That is the interface between the agent and your team. An approved plan can be checked; a finished 2,000-line diff cannot. Where the plan contains an architecture decision it belongs in the repository anyway, see &lt;a href=&quot;https://devs-group.ch/en/blog/documenting-architecture-decisions/&quot;&gt;Decisions you can still understand in two years&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A word on context files, because a lot of effort drains away here: Gloaguen, Mündler, Müller, Raychev and Vechev of ETH Zurich evaluated &lt;code&gt;AGENTS.md&lt;/code&gt; files systematically (&lt;a href=&quot;https://arxiv.org/abs/2602.11988&quot;&gt;arXiv:2602.11988&lt;/a&gt;, revised 23 June 2026). Finding: context files do not generally improve the success rate, and they raise inference costs by more than 20 %. Instructions are followed; repository overviews buy nothing. So write conventions and pitfalls into them, not directory trees.&lt;/p&gt;
&lt;h2 id=&quot;what-we-advise-against&quot;&gt;What we advise against&lt;/h2&gt;
&lt;p&gt;Do not build a merge gate that only knows true or false. A single green tick across all task types is either too expensive for greenfield work or too cheap for maintenance, and usually both.&lt;/p&gt;
&lt;p&gt;Do not use a second agent as the sole reviewer. A review agent is useful for structuring a diff and flagging candidates. It is not a verifier, because it shares failure classes with the agent it is checking, and because the chain then ends in a self-report again.&lt;/p&gt;
&lt;p&gt;And the honest trade-off: the lenient greenfield lane lets errors through. The 3.45 % is better than the human figure, but it is not zero. Adopting this grading is a deliberate decision to move review effort where it statistically buys more. For a team of two with a prototype the whole apparatus is not worth it. For a product with external API consumers and a cluster in production it is.&lt;/p&gt;
&lt;h2 id=&quot;frequently-asked&quot;&gt;Frequently asked&lt;/h2&gt;
&lt;h3 id=&quot;why-should-agents-be-checked-more-strictly-on-maintenance-than-on-greenfield-work&quot;&gt;Why should agents be checked more strictly on maintenance than on greenfield work?&lt;/h3&gt;
&lt;p&gt;Because that is what the data shows. In the comparative study by Ferdous et al. across 7,191 agent-generated pull requests, the breaking change rate was 3.45 % on greenfield work, 6.72 % on refactoring and 9.35 % on chore changes. Maintenance presupposes knowledge of existing contracts, which is often only partially present in the context window. Greenfield work defines the contracts itself.&lt;/p&gt;
&lt;h3 id=&quot;can-i-use-the-agents-confidence-score-as-a-merge-criterion&quot;&gt;Can I use the agent’s confidence score as a merge criterion?&lt;/h3&gt;
&lt;p&gt;No. The same study describes a “confidence trap”: pull requests with a high self-reported confidence score contain breaking changes regardless. The score is a self-report from the system whose work is meant to be checked. Use criteria the agent does not influence instead: was existing code changed, does the public API break, does the rendered manifest change.&lt;/p&gt;
&lt;h3 id=&quot;is-a-security-scanner-in-the-pipeline-enough-as-a-gate&quot;&gt;Is a security scanner in the pipeline enough as a gate?&lt;/h3&gt;
&lt;p&gt;Not as the only gate. Firouzi and Ghafari found across 1,080 validated samples that only 65 % of Semgrep’s and 61 % of CodeQL’s findings matched the human-validated reference. Scanners belong in the pipeline and their findings are hints. More effective is a downstream repair pass that combines compiler diagnostics with static analysis and re-checks the result.&lt;/p&gt;
&lt;h3 id=&quot;how-do-i-classify-pull-requests-when-the-agent-writes-the-title&quot;&gt;How do I classify pull requests when the agent writes the title?&lt;/h3&gt;
&lt;p&gt;By the diff, not the title. A pull request that deletes lines has touched existing code and belongs in the strict lane. &lt;code&gt;git diff --numstat&lt;/code&gt; gives you that in one line. The commit type under Conventional Commits remains a useful hint for humans but does not work as a gate, because the subject of the check sets it.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;strong&gt;A walk through your pipeline, not through your strategy.&lt;/strong&gt; Thirty minutes with one of our Go engineers: which gates you have today, which task types currently run through unchecked, and how much of that can be retrofitted in a week. No sales pitch.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;Book a slot&lt;/a&gt; · &lt;a href=&quot;https://devs-group.ch/en/contact/&quot;&gt;Get in touch&lt;/a&gt; · How we build AI systems and agent infrastructure is under &lt;a href=&quot;https://devs-group.ch/en/services/artificial-intelligence/&quot;&gt;Artificial Intelligence &amp;amp; Machine Learning&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>AI-Assisted Engineering</category><author>Ralph Segi</author></item><item><title>Using coding agents in line with data protection law: the gap in the subscription</title><link>https://devs-group.ch/en/blog/coding-agents-and-data-protection/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/coding-agents-and-data-protection/</guid><description>A personal Pro subscription used on company code falls under consumer terms. Which plans and hosting routes make coding agents compliant.</description><pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A developer working on company code with a personal Pro or Max subscription falls under Anthropic’s consumer terms, not the commercial ones. There, training use is an account setting, explicitly “including when you use Claude Code from these accounts”, and with the setting enabled the data is held for five years rather than thirty days.&lt;/p&gt;
&lt;p&gt;This is not a scandal and not hidden small print. Anthropic documents the differences on a &lt;a href=&quot;https://code.claude.com/docs/en/data-usage&quot;&gt;public data usage page&lt;/a&gt;, in more detail than most providers. The gap opens inside companies that never settled the difference between a consumer and a business plan. Using coding agents in line with data protection law therefore starts with the account, not with the tool.&lt;/p&gt;
&lt;h2 id=&quot;how-a-personal-subscription-ends-up-on-company-code-at-all&quot;&gt;How a personal subscription ends up on company code at all&lt;/h2&gt;
&lt;p&gt;Coding agents enter a company through the developer, not through procurement. Somebody tries Claude Code on their own project in the evening, likes it, types &lt;code&gt;claude&lt;/code&gt; in the client repository the next morning, and is still signed in with the personal account. There is no installation dialogue that notices.&lt;/p&gt;
&lt;p&gt;The second route is the expense claim. A team without a licence reimburses twenty dollars a month as an expense. The subscription is paid for, but contractually it remains a consumer agreement between Anthropic and a private individual. The employer is not a party to it.&lt;/p&gt;
&lt;p&gt;Both routes are technically inconspicuous. &lt;code&gt;/status&lt;/code&gt; in Claude Code shows the login method and the organisation, but nobody looks there as long as nothing is broken.&lt;/p&gt;
&lt;h2 id=&quot;is-company-code-a-data-protection-matter-at-all&quot;&gt;Is company code a data protection matter at all?&lt;/h2&gt;
&lt;p&gt;Company code becomes a data protection matter as soon as personal data appear in it, and that happens more often than assumed. Test fixtures with real names, a database dump for a migration, a log extract somebody pastes into the prompt: in those moments a coding agent is processing personal data.&lt;/p&gt;
&lt;p&gt;On 24 September 2025 the Swiss Federal Data Protection Commissioner recorded in &lt;a href=&quot;https://www.edoeb.admin.ch/de/ki-und-datenschutz&quot;&gt;AI and data protection&lt;/a&gt; that “the Swiss Data Protection Act (DPA) is directly applicable to AI-supported data processing”. No special AI legislation is needed for obligations to apply. Which provisions engage individually, and who is liable for a breach, is in our article on &lt;a href=&quot;https://devs-group.ch/en/blog/revdsg-compliant-ai-architecture/&quot;&gt;a revDSG-compliant AI architecture&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Where no personal data are involved, two other bindings remain: the client’s trade secrets, and the confidentiality clause in the framework agreement. Whether a particular clause covers disclosure to an inference provider is a question of the individual case and belongs checked before the first prompt, not after.&lt;/p&gt;
&lt;h2 id=&quot;what-anthropic-commits-to-per-plan&quot;&gt;What Anthropic commits to per plan&lt;/h2&gt;
&lt;p&gt;The commitments differ between consumer and business plans in three respects: training, retention and negotiability. All figures come from the &lt;a href=&quot;https://code.claude.com/docs/en/data-usage&quot;&gt;data usage page&lt;/a&gt; and the &lt;a href=&quot;https://claude.com/pricing&quot;&gt;pricing page&lt;/a&gt;, retrieved 20 August 2026.&lt;/p&gt;



































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Plan&lt;/th&gt;&lt;th&gt;Training on your code&lt;/th&gt;&lt;th&gt;Retention at Anthropic&lt;/th&gt;&lt;th&gt;List price (USD)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Free, Pro, Max&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;depends on an account setting, including Claude Code use from those accounts&lt;/td&gt;&lt;td&gt;5 years with the setting enabled, otherwise 30 days&lt;/td&gt;&lt;td&gt;Pro 17 annually, 20 monthly; Max from 100&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Team&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;no training under commercial terms&lt;/td&gt;&lt;td&gt;30 days&lt;/td&gt;&lt;td&gt;20 per standard seat, 100 per premium seat, both annual&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Enterprise&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;no training under commercial terms&lt;/td&gt;&lt;td&gt;30 days; zero data retention on request&lt;/td&gt;&lt;td&gt;20 per seat plus usage at API rates&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;API, Bedrock, Google Cloud, Microsoft Foundry&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;no training; the Development Partner Program is not available at all through Bedrock and Google Cloud&lt;/td&gt;&lt;td&gt;30 days; with Foundry “Hosted on Azure” prompts and completions stay in Azure&lt;/td&gt;&lt;td&gt;usage-based&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Three details that regularly get misreported in procurement conversations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zero data retention is not included in the standard Enterprise plan.&lt;/strong&gt; Anthropic writes that ZDR is available for qualified accounts and is enabled per organisation by the account team after an eligibility check. If you need ZDR, you negotiate it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transcripts shared through &lt;code&gt;/feedback&lt;/code&gt;, &lt;code&gt;/bug&lt;/code&gt; and &lt;code&gt;/share&lt;/code&gt; are retained for five years&lt;/strong&gt;, whatever the plan. Session transcripts uploaded voluntarily through the quality survey for up to six months.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error reports are active only with Pro and Max login&lt;/strong&gt;, from version 2.1.198 and only on a direct connection to the Claude API. That too is a consumer property that does not arise on a business plan.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-closing-the-gap-costs&quot;&gt;What closing the gap costs&lt;/h2&gt;
&lt;p&gt;Moving from personal subscriptions to team seats costs a twelve-person team three dollars per head per month. A model calculation from the list prices: twelve annual Pro subscriptions cost 12 × 17 = 204 USD a month, twelve team standard seats 12 × 20 = 240 USD. Delta: 36 USD a month, 432 USD a year.&lt;/p&gt;
&lt;p&gt;For heavy users the arithmetic is different and cheaper still. Someone on a Max subscription at 100 USD a month costs the same 100 USD as an annual team premium seat. At that usage profile the move is price-neutral.&lt;/p&gt;
&lt;p&gt;The honest caveat: a standard seat is not a substitute for a Max subscription, the usage quotas differ. Anyone currently expensing Max needs a premium seat, not the cheapest one. Even so the order of magnitude holds, and it is why this gap is irritating: it costs almost nothing except the decision.&lt;/p&gt;
&lt;p&gt;If you do not know which accounts your team works under today, that is an hour’s work, not a week’s. &lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;Thirty minutes with one of our engineers&lt;/a&gt; is enough to turn it into an inventory and an order of operations.&lt;/p&gt;
&lt;h2 id=&quot;three-data-flows-that-remain-even-on-a-business-plan&quot;&gt;Three data flows that remain even on a business plan&lt;/h2&gt;
&lt;p&gt;A commercial plan governs training, not every data flow. Three of them remain and belong in the record of processing activities, because otherwise they appear in no contract review.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Local transcripts in the clear.&lt;/strong&gt; Claude Code stores session transcripts under &lt;code&gt;~/.claude/projects/&lt;/code&gt;, by default for 30 days. The documentation is unambiguous: “Transcripts and history are not encrypted at rest. OS file permissions are the only protection.” If a tool reads a &lt;code&gt;.env&lt;/code&gt; file, its contents end up in the &lt;code&gt;.jsonl&lt;/code&gt; file. Controllable through &lt;code&gt;cleanupPeriodDays&lt;/code&gt;, &lt;code&gt;CLAUDE_CODE_SKIP_PROMPT_HISTORY&lt;/code&gt; and &lt;code&gt;claude project purge&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The WebFetch domain check.&lt;/strong&gt; Before every fetch, WebFetch sends the requested hostname to &lt;code&gt;api.anthropic.com&lt;/code&gt; to check it against a blocklist. Only the hostname, not the full URL. This runs independently of the model provider, so also through Bedrock, and &lt;code&gt;CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC&lt;/code&gt; does not cover it. It can only be turned off through &lt;code&gt;skipWebFetchPreflight&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Telemetry and error reports.&lt;/strong&gt; Controllable through &lt;code&gt;DISABLE_TELEMETRY&lt;/code&gt;, &lt;code&gt;DISABLE_ERROR_REPORTING&lt;/code&gt;, &lt;code&gt;DISABLE_FEEDBACK_COMMAND&lt;/code&gt; and &lt;code&gt;CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC&lt;/code&gt;. On Amazon Bedrock, Google Cloud, Microsoft Foundry and Claude Platform on AWS, metrics, error reports and &lt;code&gt;/feedback&lt;/code&gt; are already off by default. Session surveys and the WebFetch check still run there.&lt;/p&gt;
&lt;h2 id=&quot;which-purchasing-route-for-which-kind-of-code&quot;&gt;Which purchasing route for which kind of code?&lt;/h2&gt;
&lt;p&gt;The purchasing route follows the contents of the repository, not the size of the company. So the question is not “which plan do we buy” but “what is in this repository and what from it ends up in the prompt”.&lt;/p&gt;



































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;What is in the repository and in the prompt&lt;/th&gt;&lt;th&gt;Purchasing route&lt;/th&gt;&lt;th&gt;What else is needed&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Own tooling, open-source contributions, no client data&lt;/td&gt;&lt;td&gt;Team seat&lt;/td&gt;&lt;td&gt;nothing beyond the internal policy&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Client code under NDA, no personal data&lt;/td&gt;&lt;td&gt;Team or Enterprise&lt;/td&gt;&lt;td&gt;check the client’s confidentiality clause, document the egress&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Personal data in fixtures, dumps or logs&lt;/td&gt;&lt;td&gt;Enterprise, negotiate ZDR&lt;/td&gt;&lt;td&gt;processor agreement under Art. 9 DPA, deletion policy for local transcripts&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Sensitive personal data under Art. 5 lit. c DPA&lt;/td&gt;&lt;td&gt;Bedrock with customer-managed KMS keys, or Foundry “Hosted on Azure”&lt;/td&gt;&lt;td&gt;check whether a data protection impact assessment is required, no production data in the prompt&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Guaranteed data residency or professional secrecy&lt;/td&gt;&lt;td&gt;own hosting, open models&lt;/td&gt;&lt;td&gt;see &lt;a href=&quot;https://devs-group.ch/en/blog/sovereign-ai-inference-switzerland/&quot;&gt;sovereign AI inference in Switzerland&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Amazon Bedrock encrypts with AES-256 and allows customer-managed keys through AWS KMS; Google Cloud supports CMEK. With Microsoft Foundry and “Hosted on Azure”, prompts and completions stay in Azure, and only usage metadata and content flagged by security systems go to Anthropic. Which regions are concretely available is not stated on the data usage page and has to be checked with the respective cloud provider.&lt;/p&gt;
&lt;h2 id=&quot;how-to-enforce-compliant-use-technically&quot;&gt;How to enforce compliant use technically&lt;/h2&gt;
&lt;p&gt;Login to a particular organisation can be enforced, and it is the only point on this whole list that works without discipline. &lt;code&gt;forceLoginOrgUUID&lt;/code&gt; and &lt;code&gt;forceLoginMethod&lt;/code&gt; belong in managed settings and are rolled out through your device management: on macOS to &lt;code&gt;/Library/Application Support/ClaudeCode/managed-settings.json&lt;/code&gt;, on Linux to &lt;code&gt;/etc/claude-code/managed-settings.json&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &amp;quot;forceLoginMethod&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;claudeai&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &amp;quot;forceLoginOrgUUID&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;550e8400-e29b-41d4-a716-446655440000&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &amp;quot;cleanupPeriodDays&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &amp;quot;env&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &amp;quot;DISABLE_TELEMETRY&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &amp;quot;DISABLE_ERROR_REPORTING&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &amp;quot;DISABLE_FEEDBACK_COMMAND&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &amp;quot;permissions&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &amp;quot;deny&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &amp;quot;Read(./.env)&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &amp;quot;Read(./.env.*)&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &amp;quot;Read(./secrets/**)&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four limitations that Anthropic names itself in the &lt;a href=&quot;https://code.claude.com/docs/en/authentication&quot;&gt;authentication documentation&lt;/a&gt; and that you have to know before ticking the box: &lt;code&gt;claude setup-token&lt;/code&gt; and &lt;code&gt;/install-github-app&lt;/code&gt; enforce only &lt;code&gt;forceLoginMethod&lt;/code&gt;, so they can create a token in a different organisation. Sessions through cloud providers are not blocked; you restrict those through IAM. &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; and &lt;code&gt;apiKeyHelper&lt;/code&gt; are blocked at startup, because organisational membership cannot be verified there. And if you additionally distribute server-managed settings, both keys have to appear in both places, because the sources do not merge.&lt;/p&gt;
&lt;p&gt;The second half belongs in the repository rather than on the device, so that it is visible in code review. A &lt;code&gt;.claude/settings.json&lt;/code&gt; with deny rules applies to everyone who checks the project out:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &amp;quot;permissions&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &amp;quot;deny&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &amp;quot;Read(./deploy/*.key)&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &amp;quot;Read(./fixtures/prod-*.sql)&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And a check script that runs as an MDM check or during onboarding:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Checks whether the Claude Code policy is in force on this device.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Exit 1 as soon as a requirement is missing.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -euo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pipefail&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;case&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;$(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;uname&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;)&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DBEDFF&quot;&gt;  Darwin&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; managed&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;/Library/Application Support/ClaudeCode/managed-settings.json&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  *)&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      managed&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;/etc/claude-code/managed-settings.json&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;esac&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fail&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;note&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() { &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;printf&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;%-26s %s\n&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;$1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;$2&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$managed&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  org&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;jq&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -r&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;.forceLoginOrgUUID // empty&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$managed&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  [ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$org&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;note&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;forceLoginOrgUUID&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;missing&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; fail&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  days&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;jq&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -r&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;.cleanupPeriodDays // empty&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$managed&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  note&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;managed-settings.json&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;not present&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  fail&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Cleartext transcripts older than our own retention period&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;old&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$HOME&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/.claude/projects&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -name&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;*.jsonl&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -mtime&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; +&amp;quot;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;days&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; 2&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/dev/null&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wc&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -l&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; tr&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -d&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[ &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$old&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;0&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;note&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;old transcripts&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$old&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; files past retention&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; fail&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$fail&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;openai-codex-same-check-different-names&quot;&gt;OpenAI Codex: same check, different names&lt;/h2&gt;
&lt;p&gt;At OpenAI the check runs along the same three questions; the labels differ. The business plan costs, according to the &lt;a href=&quot;https://learn.chatgpt.com/codex/pricing&quot;&gt;Codex pricing page&lt;/a&gt;, 20 USD per user per month on an annual plan and carries the commitment “No training on your business data by default”. Enterprise adds controls for retention and data residency, SCIM, external key management, role-based access control, and audit logs through the compliance API.&lt;/p&gt;
&lt;p&gt;For companies wanting to avoid the data flow to OpenAI entirely: since release 0.148.0 the Codex CLI supports Amazon Bedrock Runtime as a built-in provider, including AWS profile and region. That gives Codex the same route as Claude Code — inference in your own AWS account rather than at the model provider.&lt;/p&gt;
&lt;p&gt;The practical consequence is inconvenient: anyone running both tools in-house needs two reviews and two policy lines. A single paragraph saying “AI tools are permitted” does not cover it.&lt;/p&gt;
&lt;h2 id=&quot;what-belongs-in-the-policy-on-monday&quot;&gt;What belongs in the policy on Monday&lt;/h2&gt;
&lt;p&gt;Six lines are enough, and they take an hour to write. In order, because the order determines the effort:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Inventory.&lt;/strong&gt; Who works with which tool under which account? &lt;code&gt;/status&lt;/code&gt; answers that per device in seconds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expenses rule.&lt;/strong&gt; Personal AI subscriptions are no longer reimbursed. That line closes the second route in.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enforce login.&lt;/strong&gt; &lt;code&gt;forceLoginOrgUUID&lt;/code&gt; and &lt;code&gt;forceLoginMethod&lt;/code&gt; through device management, with the four limitations above in mind.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository classification.&lt;/strong&gt; One column in the repository inventory: does it contain personal data, yes or no? The purchasing route from the table follows from that.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Document the egress.&lt;/strong&gt; &lt;code&gt;api.anthropic.com&lt;/code&gt; belongs on the allowlist and in the record of processing activities — because of the WebFetch check, even when you run through Bedrock.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Settle the feedback path.&lt;/strong&gt; Either set &lt;code&gt;DISABLE_FEEDBACK_COMMAND&lt;/code&gt;, or record in the team that &lt;code&gt;/feedback&lt;/code&gt; and &lt;code&gt;/share&lt;/code&gt; hand code to a recipient who keeps it for five years.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The guardrails belong in the repository and in device management, not in a wiki, for the same reason we put them there in &lt;a href=&quot;https://devs-group.ch/en/blog/ai-code-without-technical-debt/&quot;&gt;who writes the architecture&lt;/a&gt;: what is not in the tooling stops applying after three weeks.&lt;/p&gt;
&lt;h2 id=&quot;what-we-advise-against&quot;&gt;What we advise against&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;A ban.&lt;/strong&gt; Banning coding agents does not stop people using them, it stops people saying so. The result is exactly the case this article describes: personal account, unknown plan, no oversight. A paid team seat is the cheaper control.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zero data retention as a reflex.&lt;/strong&gt; ZDR is not included in the standard Enterprise plan, is enabled per organisation, and requires a negotiation with the account team. For repositories with no personal data, thirty days of retention plus a documented processor agreement solves the same problem without a procurement project.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;skipWebFetchPreflight&lt;/code&gt; as a default.&lt;/strong&gt; Turning the check off means fetching URLs without comparison against the blocklist, and for an agent pulling web content into its context that is not a good trade. The better route is the allowlist plus an entry in the record.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The trade-off we will name openly:&lt;/strong&gt; Bedrock and Foundry buy data residency with operational effort. You manage regions, IAM roles and your own keys, you lose billing through a single seat price, and new model versions tend to reach you later. For a repository with health data that is right. For the internal admin tool it is effort without a return.&lt;/p&gt;
&lt;h2 id=&quot;frequently-asked&quot;&gt;Frequently asked&lt;/h2&gt;
&lt;h3 id=&quot;may-a-developer-with-a-personal-claude-pro-subscription-work-on-client-code&quot;&gt;May a developer with a personal Claude Pro subscription work on client code?&lt;/h3&gt;
&lt;p&gt;Contractually possible, operationally hard to defend. Consumer plans are subject to consumer terms, training use hangs off a personal account setting the employer neither sees nor controls, and with the setting enabled retention is five years. Whether data protection or confidentiality obligations are additionally breached in a given case depends on the repository and on the client contract.&lt;/p&gt;
&lt;h3 id=&quot;is-a-team-plan-enough-or-do-we-need-enterprise&quot;&gt;Is a team plan enough, or do we need Enterprise?&lt;/h3&gt;
&lt;p&gt;For the training question, team is enough: under commercial terms Anthropic does not train on code or prompts, and retention is thirty days. You need Enterprise for SSO, role-based permissions, the compliance API, and managed policy settings for organisation-wide configuration. Zero data retention is only available that way, and even there only after an eligibility check.&lt;/p&gt;
&lt;h3 id=&quot;how-do-we-get-zero-data-retention&quot;&gt;How do we get zero data retention?&lt;/h3&gt;
&lt;p&gt;Through the account team, not through a switch in the admin area. Anthropic describes ZDR as available for qualified accounts and enabled per organisation after confirmed eligibility, explicitly not as part of the standard Enterprise plan. Allow for lead time, and settle beforehand which repositories actually need it.&lt;/p&gt;
&lt;h3 id=&quot;does-amazon-bedrock-solve-our-data-protection-questions&quot;&gt;Does Amazon Bedrock solve our data protection questions?&lt;/h3&gt;
&lt;p&gt;Bedrock solves the residency and key questions, not the processing question. Encryption with AES-256 and customer-managed keys through AWS KMS are documented, and telemetry and error reports are off there by default. What remains are the local cleartext transcripts, the WebFetch domain check against &lt;code&gt;api.anthropic.com&lt;/code&gt;, and your own obligations around purpose limitation, information and logging.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;strong&gt;Take your list of developers and add a column for which account each person is signed in under.&lt;/strong&gt; If a personal subscription shows up there, you already hold the conclusion of this article.&lt;/p&gt;
&lt;p&gt;Thirty minutes with an engineer on plan choice, purchasing route and managed settings, no sales pitch: &lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;book a slot&lt;/a&gt;. Prefer writing? Through &lt;a href=&quot;https://devs-group.ch/en/contact/&quot;&gt;contact&lt;/a&gt;. How we build and secure AI systems is under &lt;a href=&quot;https://devs-group.ch/en/services/artificial-intelligence/&quot;&gt;Artificial Intelligence &amp;amp; Machine Learning&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;All product and contract details verified against Anthropic’s and OpenAI’s primary sources on 20 August 2026. This article describes product and contract terms and derives technical consequences from them; it is not legal advice.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>AI-Assisted Engineering</category><author>Ralph Segi</author></item><item><title>AI productivity: METR&apos;s 19 per cent is out of date</title><link>https://devs-group.ch/en/blog/ai-productivity-studies-2026/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/ai-productivity-studies-2026/</guid><description>METR&apos;s follow-up study flips the sign. What the research actually shows in August 2026, and why both camps quote it wrong.</description><pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The most-quoted number on AI productivity is out of date. In 2025 METR measured, across 16 experienced open-source developers, 19 per cent &lt;strong&gt;more&lt;/strong&gt; time needed with AI tooling. The same organisation’s follow-up study, running from August 2025 with 57 developers, measures 18 per cent &lt;strong&gt;less&lt;/strong&gt; time for the same people. Both intervals are so wide that neither result is established.&lt;/p&gt;
&lt;h2 id=&quot;why-the-19-per-cent-figure-is-quoted-wrongly-today&quot;&gt;Why the 19 per cent figure is quoted wrongly today&lt;/h2&gt;
&lt;p&gt;The 19 per cent comes from a randomised study by &lt;a href=&quot;https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/&quot;&gt;METR&lt;/a&gt;, February to June 2025: 16 experienced developers, 246 issues in their own repositories averaging over 22,000 stars. The tooling was mostly Cursor Pro with Claude 3.5 and 3.7 Sonnet.&lt;/p&gt;
&lt;p&gt;The finding was uncomfortable and therefore widely quoted: the developers expected 24 per cent speed-up beforehand, believed afterwards they had gained 20 per cent, and were measured to be 19 per cent slower.&lt;/p&gt;
&lt;p&gt;METR continued the work. The &lt;a href=&quot;https://metr.org/blog/2026-02-24-uplift-update/&quot;&gt;update of 24 February 2026&lt;/a&gt; reports a second study running from August 2025: 57 developers, 10 of them from the original study and 47 new, 143 repositories, over 800 tasks. All values in the same metric — time needed with AI against without, where a minus means less time:&lt;/p&gt;

























&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Group&lt;/th&gt;&lt;th&gt;Point estimate&lt;/th&gt;&lt;th&gt;Confidence interval&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Original study, Feb–June 2025&lt;/td&gt;&lt;td&gt;&lt;strong&gt;+19 %&lt;/strong&gt;, i.e. more time&lt;/td&gt;&lt;td&gt;+2 % to +39 %&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Same developers, second study&lt;/td&gt;&lt;td&gt;&lt;strong&gt;−18 %&lt;/strong&gt;, i.e. less time&lt;/td&gt;&lt;td&gt;−38 % to +9 %&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Newly recruited developers&lt;/td&gt;&lt;td&gt;&lt;strong&gt;−4 %&lt;/strong&gt;, i.e. less time&lt;/td&gt;&lt;td&gt;−15 % to +9 %&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The result has not weakened, it has changed sign. More important is the difference in the intervals: the original study’s excludes zero, both new ones include it. Neither slowdown nor speed-up is established.&lt;/p&gt;
&lt;h2 id=&quot;metr-distrusts-its-own-result-and-gives-clean-reasons&quot;&gt;METR distrusts its own result, and gives clean reasons&lt;/h2&gt;
&lt;p&gt;METR itself writes that the data does not work as a productivity measure. Verbatim, on selection bias: “Because of the selection effects in our experiment, our data is only very weak evidence for the size of this increase.” And on the central estimate: “we believe it is likely a bad proxy for the real productivity impact.”&lt;/p&gt;
&lt;p&gt;The reason is the most interesting point in the whole debate. METR cut compensation from 150 to 50 dollars an hour. That changed who took part at all, and which tasks got submitted.&lt;/p&gt;
&lt;p&gt;“30% to 50% of developers told us that they were choosing not to submit some tasks because they did not want to do them without AI”, the report says. One participant, in their own words: “I avoid issues like AI can finish things in just 2 hours, but I have to spend 20 hours.”&lt;/p&gt;
&lt;p&gt;That is exactly where measurability breaks. Filtering out the tasks where AI helps most measures systematically low. METR concedes that the results probably underestimate the true effect. On top of that, some participants ran several agents in parallel, which devalues the timing.&lt;/p&gt;
&lt;p&gt;If you are writing a business case for AI tooling right now and notice you are missing the defensible number: that is not a failure of your research. &lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;Half an hour with somebody who has read the primary sources&lt;/a&gt; is cheaper at this point than another week of searching.&lt;/p&gt;
&lt;h2 id=&quot;the-counter-figure-from-the-other-camp-is-from-2022&quot;&gt;The counter-figure from the other camp is from 2022&lt;/h2&gt;
&lt;p&gt;The famous 55 per cent speed-up is a vendor number from 2022. &lt;a href=&quot;https://github.blog/news-insights/research/research-quantifying-github-copilots-impact-on-developer-productivity-and-happiness/&quot;&gt;GitHub Next published it on 7 September 2022&lt;/a&gt;, together with Microsoft’s Office of the Chief Economist. Ninety-five developers, randomly split into two groups, one single task: write an HTTP server in JavaScript.&lt;/p&gt;
&lt;p&gt;The Copilot group took 1 hour 11 minutes, the control group 2 hours 41 minutes. The confidence interval runs from 21 to 89 per cent.&lt;/p&gt;
&lt;p&gt;Three things about it are worth knowing before putting the number in a presentation. It comes from the maker of the tool being tested. It measures a self-contained laboratory task with no existing codebase, no review and no operations. And it is four years old, from before agentic tooling existed.&lt;/p&gt;
&lt;h2 id=&quot;who-collected-which-number-and-who-paid-for-it&quot;&gt;Who collected which number, and who paid for it&lt;/h2&gt;
&lt;p&gt;The comparison below sorts the six numbers that actually circulate in this debate by origin, funding and shelf life. All retrievals as of 20 August 2026.&lt;/p&gt;






















































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Number&lt;/th&gt;&lt;th&gt;Who collected it&lt;/th&gt;&lt;th&gt;Who paid&lt;/th&gt;&lt;th&gt;Sample&lt;/th&gt;&lt;th&gt;Status in August 2026&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;55 % faster&lt;/td&gt;&lt;td&gt;GitHub Next, Microsoft&lt;/td&gt;&lt;td&gt;GitHub, i.e. Microsoft&lt;/td&gt;&lt;td&gt;95 people, one lab task&lt;/td&gt;&lt;td&gt;Vendor number from 2022, not valid for today’s tools&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;19 % slower&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/&quot;&gt;METR&lt;/a&gt;&lt;/td&gt;&lt;td&gt;non-profit, no vendor&lt;/td&gt;&lt;td&gt;16 people, 246 issues&lt;/td&gt;&lt;td&gt;Superseded by its own follow-up&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;18 % / 4 % faster&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://metr.org/blog/2026-02-24-uplift-update/&quot;&gt;METR&lt;/a&gt;&lt;/td&gt;&lt;td&gt;non-profit, no vendor&lt;/td&gt;&lt;td&gt;57 people, 143 repos, 800+ tasks&lt;/td&gt;&lt;td&gt;Current, but the interval includes zero&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AI acts as an amplifier&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://dora.dev/research/&quot;&gt;DORA&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Google Cloud&lt;/td&gt;&lt;td&gt;State of DevOps 2025&lt;/td&gt;&lt;td&gt;Latest state, no 2026 report&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3.45 % vs 7.40 % breaking changes&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://arxiv.org/abs/2603.27524&quot;&gt;Ferdous et al.&lt;/a&gt;&lt;/td&gt;&lt;td&gt;academic, arXiv&lt;/td&gt;&lt;td&gt;7,191 agent PRs, 1,402 human PRs&lt;/td&gt;&lt;td&gt;Current&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Context files ineffective, +20 % inference cost&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://arxiv.org/abs/2602.11988&quot;&gt;Gloaguen et al., ETH Zurich&lt;/a&gt;&lt;/td&gt;&lt;td&gt;academic, arXiv&lt;/td&gt;&lt;td&gt;SWE-bench plus real repositories&lt;/td&gt;&lt;td&gt;Current, revised June 2026&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The funding column is not a courtesy. DORA is a Google Cloud programme, and Google sells competing tools in Gemini, Jules and Antigravity. That does not make the methodology bad; it is rightly well regarded. But a report that helps companies defend AI budgets has a sender with an interest in AI budgets.&lt;/p&gt;
&lt;h2 id=&quot;the-amplifier-finding-is-the-most-defensible-statement-in-the-research&quot;&gt;The amplifier finding is the most defensible statement in the research&lt;/h2&gt;
&lt;p&gt;The most stable insight is that AI tools amplify existing engineering maturity rather than replacing it. DORA puts it this way in the &lt;a href=&quot;https://dora.dev/research/&quot;&gt;State of DevOps 2025&lt;/a&gt;: “AI acts as an amplifier, but the greatest returns come from focusing on the underlying sociotechnical systems.”&lt;/p&gt;
&lt;p&gt;Two academic papers from 2026 show the same mechanism from below. The study &lt;a href=&quot;https://arxiv.org/abs/2603.27524&quot;&gt;“Safer Builders, Risky Maintainers”&lt;/a&gt; by Ferdous, Banik, Chowdhury and Shamim compared 7,191 agent-generated pull requests with 1,402 human ones from Python repositories and detected breaking changes through AST analysis.&lt;/p&gt;

























&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Context&lt;/th&gt;&lt;th&gt;Breaking change rate&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Code generation, agents&lt;/td&gt;&lt;td&gt;3.45 %&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Code generation, humans&lt;/td&gt;&lt;td&gt;7.40 %&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Agents on refactoring&lt;/td&gt;&lt;td&gt;6.72 %&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Agents on chore changes&lt;/td&gt;&lt;td&gt;9.35 %&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;On new construction, agents break compatibility less often than humans. On maintenance work that inverts. The authors additionally describe a “confidence trap”: pull requests with a high self-reported confidence score from the agent contain breaking changes too. The score does not work as a review filter.&lt;/p&gt;
&lt;p&gt;The second paper, &lt;a href=&quot;https://arxiv.org/abs/2607.26375&quot;&gt;“(Im)Paired Programming”&lt;/a&gt; by Balepur and colleagues, had 54 students build a website, with an agent or with a chatbot, and then tested comprehension through questions and through an extension task without an agent. Finding: agents help you finish and harm understanding, and it is precisely the comfortable interaction forms — copy-paste prompts, auto-accepted edits — that correlate with the worst understanding. The caveat belongs with it: 54 students are not an engineering team.&lt;/p&gt;
&lt;p&gt;Why this supports the amplifier thesis: in both papers it is not the tool that decides but where and how it is used. The same observation from the practice side, without numbers, is in &lt;a href=&quot;https://devs-group.ch/en/blog/ai-code-without-technical-debt/&quot;&gt;AI writes the code. Who writes the architecture?&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-the-range-means-for-capacity-planning&quot;&gt;What the range means for capacity planning&lt;/h2&gt;
&lt;p&gt;The second study’s confidence interval is too wide to budget with. Let us work it through in Swiss francs so the order of magnitude is visible.&lt;/p&gt;
&lt;p&gt;A model calculation, not a survey: a team of ten developers, 1,800 working hours per person per year, so 18,000 hours. As an hourly rate we take CHF 160, the midpoint from the published worked example at &lt;a href=&quot;https://www.gryps.ch/produkte/softwareentwicklung-13/individualsoftware-kosten/&quot;&gt;gryps.ch&lt;/a&gt; for software engineering in German-speaking Switzerland (retrieved 20 August 2026). The annual capacity therefore corresponds to CHF 2,880,000.&lt;/p&gt;





























&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Scenario from the METR interval&lt;/th&gt;&lt;th&gt;Time needed&lt;/th&gt;&lt;th&gt;Hours per year&lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Lower bound&lt;/td&gt;&lt;td&gt;−38 %&lt;/td&gt;&lt;td&gt;6,840 hours freed&lt;/td&gt;&lt;td&gt;CHF 1,094,400&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Point estimate&lt;/td&gt;&lt;td&gt;−18 %&lt;/td&gt;&lt;td&gt;3,240 hours freed&lt;/td&gt;&lt;td&gt;CHF 518,400&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Upper bound&lt;/td&gt;&lt;td&gt;+9 %&lt;/td&gt;&lt;td&gt;1,620 hours extra&lt;/td&gt;&lt;td&gt;CHF 259,200&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Between the two bounds lies CHF 1,353,600. Nobody plans a budget on a range like that, and that is the research’s honest answer rather than its failure.&lt;/p&gt;
&lt;p&gt;The calculation deliberately transfers METR’s values crudely onto an annual capacity. METR measured issue-shaped tasks in mature open-source repositories, not full working years with meetings, operations and incidents. Anyone transferring the numbers one to one onto their team makes the same mistake as the camps they are criticising.&lt;/p&gt;
&lt;h2 id=&quot;measure-your-own-baseline-instead&quot;&gt;Measure your own baseline instead&lt;/h2&gt;
&lt;p&gt;The most useful response to an unclear body of research is your own before-measurement. Two metrics are enough to start, and both already sit in your repository.&lt;/p&gt;
&lt;p&gt;First, a proxy for change failure rate, out of the git log over 90 days:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# All commits in the last 90 days&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; log&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --since=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;90 days ago&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --no-merges&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --oneline&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wc&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -l&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Of those, reverts and hotfixes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; log&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --since=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;90 days ago&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --no-merges&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --oneline&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  --grep=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;^Revert&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --grep=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;^hotfix&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --grep=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;^fix!&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wc&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -l&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Second, the size of changes per author, to separate agent-generated from human pull requests:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gh&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; pr&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; list&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --state&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; merged&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --limit&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 200&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  --json&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; number,author,additions,deletions,mergedAt&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  --jq&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;#39;.[] | [.author.login, .additions + .deletions] | @tsv&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Collect both a month before rollout and three months after. Those two numbers say more about your team than any study in this article. How to record the decisions behind them so the measurement is still interpretable in a year is in &lt;a href=&quot;https://devs-group.ch/en/blog/documenting-architecture-decisions/&quot;&gt;Documenting architecture decisions&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-does-not-exist-in-august-2026&quot;&gt;What does not exist in August 2026&lt;/h2&gt;
&lt;p&gt;Three frequently expected sources do not exist at the time of writing, and anyone citing them is citing something invented.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No State of DevOps report 2026.&lt;/strong&gt; &lt;a href=&quot;https://dora.dev/research/&quot;&gt;dora.dev/research&lt;/a&gt; lists 2025 as the most recent entry. The only new thing is an interim product, the “ROI of AI-assisted Software Development report”, last updated 22 April 2026, with an ROI calculator.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No Stack Overflow Developer Survey 2026.&lt;/strong&gt; The &lt;a href=&quot;https://survey.stackoverflow.co/&quot;&gt;archive&lt;/a&gt; lists 2011 to 2025. The 2025 edition remains current.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No Technology Radar Vol. 35.&lt;/strong&gt; &lt;a href=&quot;https://www.thoughtworks.com/radar&quot;&gt;Vol. 34 of April 2026&lt;/a&gt; is the current issue, with “Putting coding agents on a leash” as one of the four themes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-we-advise-against&quot;&gt;What we advise against&lt;/h2&gt;
&lt;p&gt;We advise against three things, and the first concerns the numbers in this article itself. First: building an ROI business case on a point estimate, from whichever camp. The defensible statement from the research is a range, not a value.&lt;/p&gt;
&lt;p&gt;Second: using an agent’s confidence score as a review filter. The confidence trap from “Safer Builders, Risky Maintainers” is exactly that mistake, empirically demonstrated. Grade review depth by task type instead. Greenfield work can run more loosely; refactoring and maintenance need strict control.&lt;/p&gt;
&lt;p&gt;Third: auto-accept as the default setting. According to “(Im)Paired Programming” it is the interaction form with the worst comprehension outcome.&lt;/p&gt;
&lt;p&gt;Where our recommendation does not fit: throwaway prototypes and spikes. If you are checking feasibility for a week and deleting the code afterwards, you do not need graded review depth. The effort pays from the moment somebody has to read the code a year later.&lt;/p&gt;
&lt;p&gt;And a Swiss detail that gets lost in the international debate: the methodologically cleanest work on context files for coding agents comes from ETH Zurich. Gloaguen, Mündler, Müller, Raychev and Vechev &lt;a href=&quot;https://arxiv.org/abs/2602.11988&quot;&gt;showed in February 2026&lt;/a&gt;, revised in June, that &lt;code&gt;AGENTS.md&lt;/code&gt; files do not generally improve the success rate but raise inference costs by more than 20 per cent. Three of the authors are affiliated with LogicStar AG in Zurich; Martin Vechev leads the Secure, Reliable and Intelligent Systems Lab at ETH.&lt;/p&gt;
&lt;h2 id=&quot;frequently-asked&quot;&gt;Frequently asked&lt;/h2&gt;
&lt;h3 id=&quot;is-it-true-that-ai-makes-developers-19-per-cent-slower&quot;&gt;Is it true that AI makes developers 19 per cent slower?&lt;/h3&gt;
&lt;p&gt;That was the finding of a METR study from February to June 2025 with 16 experienced open-source developers. The same organisation’s follow-up, running from August 2025 with 57 developers and over 800 tasks, measures the opposite: 18 per cent less time needed among the returning participants. Anyone offering the 19 per cent as evidence today is quoting a superseded result.&lt;/p&gt;
&lt;h3 id=&quot;where-does-the-55-per-cent-faster-figure-come-from&quot;&gt;Where does the “55 per cent faster” figure come from?&lt;/h3&gt;
&lt;p&gt;From a study by GitHub Next and Microsoft’s Office of the Chief Economist, published 7 September 2022. Ninety-five developers wrote an HTTP server in JavaScript, with or without Copilot. It is a vendor number from 2022, measured on a single laboratory task with no existing codebase, no review and no operations.&lt;/p&gt;
&lt;h3 id=&quot;which-statement-from-the-research-is-the-most-defensible&quot;&gt;Which statement from the research is the most defensible?&lt;/h3&gt;
&lt;p&gt;The amplifier finding. DORA states in the State of DevOps 2025 that AI acts as an amplifier and that the greatest returns come from work on the underlying sociotechnical systems. Two academic papers from 2026 show the same mechanism in detail: the effect depends on the task type and the way of working, not on the tool.&lt;/p&gt;
&lt;h3 id=&quot;why-is-the-productivity-effect-so-hard-to-measure&quot;&gt;Why is the productivity effect so hard to measure?&lt;/h3&gt;
&lt;p&gt;Because the participants influence the measurement. When METR cut compensation from 150 to 50 dollars an hour, 30 to 50 per cent of developers said they were not submitting certain tasks because they did not want to do them without AI. Filtering out exactly the tasks where AI helps most measures systematically low.&lt;/p&gt;
&lt;h3 id=&quot;what-should-you-measure-in-your-own-team-instead&quot;&gt;What should you measure in your own team instead?&lt;/h3&gt;
&lt;p&gt;Two metrics from your own repository, once before and once three months after introduction: the rate of reverts and hotfixes over 90 days as a proxy for change failure rate, and the change size per author, to separate agent-generated from human pull requests. Both take a few minutes with &lt;code&gt;git log&lt;/code&gt; and &lt;code&gt;gh pr list&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;lets-put-your-numbers-next-to-them&quot;&gt;Let’s put your numbers next to them&lt;/h2&gt;
&lt;p&gt;Bring your git history and the question your management asked you. We collect the baseline together and tell you which of the studies quoted here actually applies to your case and which does not.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;Book a slot&lt;/a&gt; or &lt;a href=&quot;https://devs-group.ch/en/contact/&quot;&gt;write to us&lt;/a&gt;. How we support teams using these tools is under &lt;a href=&quot;https://devs-group.ch/en/services/artificial-intelligence/&quot;&gt;Artificial Intelligence &amp;amp; Machine Learning&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>AI-Assisted Engineering</category><author>Robert Pupel</author></item><item><title>Implementing the CRA reporting obligation from 11.9.2026</title><link>https://devs-group.ch/en/blog/cra-24-hour-reporting-obligation/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/cra-24-hour-reporting-obligation/</guid><description>Twenty-four hours to the first report. What you need in place in SBOM, monitoring and reporting chain before the Cyber Resilience Act applies.</description><pubDate>Thu, 13 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The CRA reporting obligation hits manufacturers of products with digital elements from 11 September 2026: report actively exploited vulnerabilities provisionally within 24 hours, fully within 72 hours, and file the final report at the latest 14 days after a corrective measure becomes available. Under Art. 14(1) CRA the report goes simultaneously to the coordinating CSIRT and to ENISA, through a single submission.&lt;/p&gt;
&lt;h2 id=&quot;what-applies-concretely-from-11-september-2026&quot;&gt;What applies concretely from 11 September 2026&lt;/h2&gt;
&lt;p&gt;Article 14 of &lt;a href=&quot;https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=OJ%3AL_202402847&quot;&gt;Regulation (EU) 2024/2847&lt;/a&gt; applies from 11 September 2026; the remaining manufacturer obligations only from 11 December 2027. The reporting obligation therefore arrives fifteen months before everything else that hits a manufacturer. Anyone waiting for full applicability has already missed the first deadline.&lt;/p&gt;
&lt;p&gt;A third date regularly gets lost: Chapter IV on notified bodies (Articles 35 to 51) has applied since 11 June 2026. It concerns conformity assessment bodies, not you as a manufacturer.&lt;/p&gt;
&lt;p&gt;Two triggers, two chains of deadlines:&lt;/p&gt;























&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Trigger&lt;/th&gt;&lt;th&gt;Early warning&lt;/th&gt;&lt;th&gt;Notification&lt;/th&gt;&lt;th&gt;Final report&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Actively exploited vulnerability in the product&lt;/td&gt;&lt;td&gt;24 h from awareness&lt;/td&gt;&lt;td&gt;72 h from awareness&lt;/td&gt;&lt;td&gt;14 days after a corrective or mitigating measure is available&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Severe security incident&lt;/td&gt;&lt;td&gt;24 h from awareness&lt;/td&gt;&lt;td&gt;72 h from awareness&lt;/td&gt;&lt;td&gt;one month after the incident notification&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The clock runs from awareness, not from confirmation. Running an internal approval round first burns half the budget. Breaches of Articles 13 and 14 carry fines under Article 64(2) CRA of up to EUR 15 million or 2.5 % of worldwide annual turnover, whichever is higher.&lt;/p&gt;
&lt;h2 id=&quot;does-the-cyber-resilience-act-apply-to-swiss-companies&quot;&gt;Does the Cyber Resilience Act apply to Swiss companies?&lt;/h2&gt;
&lt;p&gt;Yes, as soon as a product with digital elements is placed on the market in the EU. Being based in Kreuzlingen, Zug or Zurich changes nothing. The only interesting question is which CSIRT a manufacturer without an establishment in the Union reports to.&lt;/p&gt;
&lt;p&gt;Article 14(7) CRA solves that through a cascade: what governs is the member state where the authorised representative sits for the largest number of products. Failing that, the importer. Failing that, the distributor. If that remains open, the member state with the most users decides.&lt;/p&gt;
&lt;p&gt;That is an architecture decision, not a legal footnote. Anyone who does not know today which national CSIRT is competent will not register in time on 11 September.&lt;/p&gt;
&lt;h2 id=&quot;where-the-report-goes-and-who-your-counterpart-is&quot;&gt;Where the report goes, and who your counterpart is&lt;/h2&gt;
&lt;p&gt;The report has two recipients and is nonetheless submitted only once. Article 14(1) CRA obliges the manufacturer to report an actively exploited vulnerability simultaneously to the CSIRT designated as coordinator and to ENISA. For severe security incidents, paragraph 3 says the same.&lt;/p&gt;
&lt;p&gt;The technical route is one. Under Article 14(7) CRA the report goes through the reporting endpoint of the CSIRT designated as coordinator in the member state of the main establishment, and is simultaneously accessible to ENISA. The Commission puts it this way on its &lt;a href=&quot;https://digital-strategy.ec.europa.eu/en/policies/cra-reporting&quot;&gt;page on CRA reporting obligations&lt;/a&gt;: “Manufacturers report only once through the CRA Single Reporting Platform (SRP)”.&lt;/p&gt;
&lt;p&gt;The party that comes back with questions is the CSIRT. Under Article 14(6) it may request an interim report, and it validates your access to the platform in advance. ENISA builds and operates it. If you want to know who you call in a real event: the CSIRT of your main establishment.&lt;/p&gt;
&lt;p&gt;For implementation, three details from the &lt;a href=&quot;https://www.enisa.europa.eu/topics/product-security/single-reporting-platform-srp/frequently-asked-questions&quot;&gt;ENISA FAQ on the SRP&lt;/a&gt; (as of 3 August 2026) matter more than any legal question:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;There is no API for now.&lt;/strong&gt; You can automate everything up to submission; the submission itself is typed by a human into a browser form.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Registration runs through EU Login&lt;/strong&gt; (&lt;code&gt;ecas.ec.europa.eu&lt;/code&gt;), with validation afterwards through the competent CSIRT. Both take time and belong before the real event.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The platform was not yet live in mid-August 2026&lt;/strong&gt;, and the public URL not yet published. It is meant to be operational by 11 September.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the last metre of your chain is manual. All the more important that the forty-nine metres before it are not.&lt;/p&gt;
&lt;p&gt;Two authorities regularly get filed wrongly here. Germany’s BSI has been the notifying and market surveillance authority for the CRA since &lt;a href=&quot;https://www.bsi.bund.de/DE/Service-Navi/Presse/Pressemitteilungen/Presse2025/251007_CRA_BSI_marktueberwachende_Behoerde.html&quot;&gt;October 2025&lt;/a&gt;. Market surveillance, however, is not receiving reports.&lt;/p&gt;
&lt;p&gt;And Switzerland’s BACS is not an address in the CRA context at all. It is the addressee of the Swiss ISG reporting obligation, a separate regime with its own legal basis. Conflating the two means reporting to the wrong place.&lt;/p&gt;
&lt;h2 id=&quot;the-cra-and-the-swiss-isg-reporting-obligation-side-by-side&quot;&gt;The CRA and the Swiss ISG reporting obligation side by side&lt;/h2&gt;
&lt;p&gt;Two regimes, two addressees, two legal bases, both with a 24-hour deadline. A Swiss software manufacturer that supplies the EU and whose products are used by Swiss critical infrastructure falls under both. We built this comparison because we could not find one anywhere. Every row is evidenced against the statutory text.&lt;/p&gt;
















































































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;CRA (EU)&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;ISG reporting obligation (CH)&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Legal basis&lt;/td&gt;&lt;td&gt;Reg. (EU) 2024/2847, Art. 14&lt;/td&gt;&lt;td&gt;ISG (SR 128), Art. 74a–74h, plus CSV (SR 128.51)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;In force since&lt;/td&gt;&lt;td&gt;11.09.2026&lt;/td&gt;&lt;td&gt;01.04.2025, sanctions from 01.10.2025&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Who reports&lt;/td&gt;&lt;td&gt;manufacturers of products with digital elements&lt;/td&gt;&lt;td&gt;operators of critical infrastructure, including manufacturers of hardware or software whose products are used by critical infrastructure, where there is remote maintenance access or where they are used to control and monitor operational technology or for public safety (Art. 74b(1)(u))&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Trigger&lt;/td&gt;&lt;td&gt;actively exploited vulnerability in the product, or a severe incident&lt;/td&gt;&lt;td&gt;cyberattack on one’s own IT assets under four criteria (Art. 74d)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Subject&lt;/td&gt;&lt;td&gt;the shipped product&lt;/td&gt;&lt;td&gt;one’s own operation&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Addressee&lt;/td&gt;&lt;td&gt;coordinating CSIRT of the main establishment and ENISA simultaneously, one submission (Art. 14(1) and (7))&lt;/td&gt;&lt;td&gt;BACS&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Channel&lt;/td&gt;&lt;td&gt;CRA Single Reporting Platform, EU Login account, no API&lt;/td&gt;&lt;td&gt;reporting form in the BACS Cyber Security Hub, alternatively an email form&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;First report&lt;/td&gt;&lt;td&gt;24 h from awareness&lt;/td&gt;&lt;td&gt;24 h from discovery (Art. 74e(1))&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Second stage&lt;/td&gt;&lt;td&gt;72 h&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Closure&lt;/td&gt;&lt;td&gt;14 days after the correction is available, one month for incidents&lt;/td&gt;&lt;td&gt;14 days to supplement, granted by BACS (Art. 16(1) CSV)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Inform users&lt;/td&gt;&lt;td&gt;yes, Art. 14(8), machine-readable where possible&lt;/td&gt;&lt;td&gt;not provided for&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Extraterritorial&lt;/td&gt;&lt;td&gt;yes, through placing on the EU market&lt;/td&gt;&lt;td&gt;yes, for attacks with effect in Switzerland, including systems abroad (Art. 74b(3))&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Sanction&lt;/td&gt;&lt;td&gt;up to EUR 15 m or 2.5 % of worldwide annual turnover (Art. 64(2))&lt;/td&gt;&lt;td&gt;fine up to CHF 100,000 against natural persons, subsidiarily up to CHF 20,000 against the business (Art. 74h)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Enforcement&lt;/td&gt;&lt;td&gt;market surveillance authorities of the member states&lt;/td&gt;&lt;td&gt;BACS sets a deadline, then issues an order with a penalty warning; prosecution by the cantons (Art. 74g)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The most interesting difference is in the last row. The CRA sanctions the omission directly. The ISG does not: under &lt;a href=&quot;https://www.fedlex.admin.ch/eli/cc/2022/232/de&quot;&gt;Art. 74g ISG&lt;/a&gt; BACS first sets a deadline, then issues an order, and only wilful disregard of that order is punishable under Art. 74h.&lt;/p&gt;
&lt;p&gt;We know the same indirect construction from the revised Swiss DPA, where the Federal Data Protection Commissioner likewise has no direct sanctioning power. Anyone concluding from this that the ISG obligation is toothless is overlooking Art. 74h(1): the fine lands on natural persons, not on the company.&lt;/p&gt;
&lt;p&gt;The figures come from BACS itself. In its &lt;a href=&quot;https://www.ncsc.admin.ch/dam/ncsc/de/dokumente/dokumentation/lageberichte/BACS-HJB-2025-2_DE.pdf.download.pdf/BACS-HJB-2025-2_DE.pdf&quot;&gt;semi-annual report 2025/2&lt;/a&gt; (published 30 March 2026) the office processed 145 reportable cyber incidents in the second half of 2025, distributed across the public sector (25 %), IT and telecommunications (18 %) and finance and insurance (15.7 %). Alongside that, 64,733 voluntary reports across the full year.&lt;/p&gt;
&lt;h2 id=&quot;what-you-have-to-build-so-that-24-hours-is-enough&quot;&gt;What you have to build so that 24 hours is enough&lt;/h2&gt;
&lt;p&gt;Four artefacts have to be finished before the clock starts. None of them can be improvised during an incident.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;An SBOM per release, stored immutably.&lt;/strong&gt; &lt;a href=&quot;https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=OJ%3AL_202402847&quot;&gt;Annex I Part II point 1 CRA&lt;/a&gt; requires a software bill of materials in a common machine-readable format covering at least the top-level dependencies. That is the legal floor, not the goal: stopping at transitive dependencies means missing Log4Shell-class problems.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A backwards-looking answer.&lt;/strong&gt; The question in a real event is not “is the library vulnerable” but “which shipped versions contain it and who runs those”. That has to be a query, not a reconstruction.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An escalation path with a named person.&lt;/strong&gt; Not “the security team” but a name and a deputy, with access to the registered EU Login account. At three on a Sunday morning.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An incident log with UTC timestamps.&lt;/strong&gt; You do not only have to report within the deadline, you have to be able to prove that you did.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;five-years-of-support-from-11-december-2027&quot;&gt;Five years of support, from 11 December 2027&lt;/h3&gt;
&lt;p&gt;Article 13(8) CRA sets the support period at a minimum of five years, shorter only where the product is expected to be in use for less time. Five years of security updates means five years of a working build pipeline for every still-supported release branch. That is the most expensive obligation in the whole CRA, and it appears in no reporting deadline.&lt;/p&gt;
&lt;p&gt;If you are not sure whether your chain from detection to submitted form holds within a working day, run it once against a real, already-fixed vulnerability and time it. As an exercise that is an afternoon. What stands out is rarely the tooling and almost always the handover between two people.&lt;/p&gt;
&lt;h2 id=&quot;sbom-in-the-pipeline-a-setup-that-answers-retrospectively&quot;&gt;SBOM in the pipeline: a setup that answers retrospectively&lt;/h2&gt;
&lt;p&gt;The SBOM belongs bound to the image digest, not to the tag. Tags get overwritten; digests do not. The workflow below produces both common formats, checks against CVE feeds, and signs the result keyless over OIDC. Versions as of August 2026: Syft v1.51.0, Grype v0.117.0, cosign v3.1.3.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# .github/workflows/release.yml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;release&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  push&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    tags&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;v*&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;permissions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  contents&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;read&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  packages&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;write&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  id-token&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            # keyless signing, no key material in the repo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  sbom&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    runs-on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ubuntu-24.04&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    env&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      IMAGE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ghcr.io/${{ github.repository }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    steps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;actions/checkout@v7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;docker/login-action@v4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          registry&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ghcr.io&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          username&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;${{ github.actor }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          password&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Build, push, capture the digest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          docker build -t &amp;quot;$IMAGE:$GITHUB_REF_NAME&amp;quot; .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          docker push &amp;quot;$IMAGE:$GITHUB_REF_NAME&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          DIGEST=$(docker buildx imagetools inspect &amp;quot;$IMAGE:$GITHUB_REF_NAME&amp;quot; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                     --format &amp;#39;{{ .Manifest.Digest }}&amp;#39;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          echo &amp;quot;REF=$IMAGE@$DIGEST&amp;quot; &amp;gt;&amp;gt; &amp;quot;$GITHUB_ENV&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Pin the tools to fixed versions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          curl -sSfL https://get.anchore.io/syft  | sh -s -- -b /usr/local/bin v1.51.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          curl -sSfL https://get.anchore.io/grype | sh -s -- -b /usr/local/bin v0.117.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;sigstore/cosign-installer@v4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          cosign-release&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;v3.1.3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Produce the SBOM in both formats&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          syft registry:&amp;quot;$REF&amp;quot; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            -o cyclonedx-json=sbom.cdx.json \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            -o spdx-json=sbom.spdx.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Check against CVE feeds, stop the build on anything critical&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;grype &amp;quot;sbom:sbom.cdx.json&amp;quot; --fail-on critical&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Bind the SBOM to the digest and sign it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;cosign attest --yes --type cyclonedx --predicate sbom.cdx.json &amp;quot;$REF&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;actions/upload-artifact@v7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;        with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;sbom-${{ github.ref_name }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          path&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;sbom.*.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;          retention-days&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;90&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;       # GitHub&amp;#39;s maximum, so only a buffer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;upload-artifact&lt;/code&gt; step is the weakest point and deliberately left that way: in production the SBOMs belong in object storage with versioning and object lock, queryable by component. GitHub artefacts are an intermediate step, not an archive. More on this in our &lt;a href=&quot;https://devs-group.ch/en/services/kubernetes-and-infrastructure/&quot;&gt;work on CI/CD and supply chain&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-incident-log-that-evidences-your-deadline&quot;&gt;The incident log that evidences your deadline&lt;/h2&gt;
&lt;p&gt;An incident log is not a ticket history but an append-only event stream with UTC timestamps. NDJSON is enough, one line per event, &lt;code&gt;ts&lt;/code&gt; always first. What matters is the machine-recorded moment of awareness, which nobody has to reconstruct later from a Slack history. The revised Swiss DPA demands the same discipline for personal data; what &lt;a href=&quot;https://devs-group.ch/en/blog/revdsg-compliant-ai-architecture/&quot;&gt;an audit log that survives a subject access request&lt;/a&gt; looks like we have described separately.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;jsonl&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;ts&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;2026-09-14T07:12:41Z&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;event&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;aware&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;incident&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;INC-2026-0007&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;cve&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;CVE-2026-31337&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;product&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;acme-gateway&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;affected_versions&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;3.4.0&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;3.4.1&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;component&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;pkg:golang/github.com/example/parser@1.8.2&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;evidence&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;WAF signature 8841, 3 hits from 2 source ASNs&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;actively_exploited&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;detected_by&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;grype-nightly+SIEM&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;owner&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;r.segi&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;ts&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;2026-09-14T07:41:02Z&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;event&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;triaged&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;incident&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;INC-2026-0007&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;regime&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;cra&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;isg&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;cra_trigger&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;actively_exploited_vulnerability&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;due_early_warning&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;2026-09-15T07:12:41Z&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;due_notification&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;2026-09-17T07:12:41Z&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;csirt&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;NCSC-NL&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;owner&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;r.segi&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;ts&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;2026-09-14T09:03:55Z&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;event&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;reported&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;incident&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;INC-2026-0007&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;channel&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;eu-srp&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;stage&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;early_warning&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;reference&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;SRP-2026-114872&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;elapsed_h&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.85&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;submitted_by&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;r.segi&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;ts&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;2026-09-14T10:26:33Z&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;event&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;reported&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;incident&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;INC-2026-0007&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;channel&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;ch-bacs-csh&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;stage&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;first_report&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;reference&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;BACS-2026-3391&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;elapsed_h&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3.23&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;submitted_by&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;r.segi&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;ts&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;2026-09-14T11:48:07Z&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;event&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;users_informed&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;incident&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;INC-2026-0007&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;format&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;csaf-2.0&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;https://acme.example/security/ACME-SA-2026-004&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The deadlines in the &lt;code&gt;triaged&lt;/code&gt; event are computed, not typed. That is a function, not a calendar entry:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Deadlines under Art. 14 CRA, computed from the moment of awareness in UTC.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Deadlines&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	EarlyWarning &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Time&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // 24 h from awareness&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Notification &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Time&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // 72 h from awareness&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Final        &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Time&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // 14 days after a corrective measure is available&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; CRADeadlines&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;awareAt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;fixAvailableAt&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; time&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Time&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Deadlines&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	awareAt &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; awareAt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UTC&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Deadlines&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		EarlyWarning: awareAt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;24&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time.Hour),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Notification: awareAt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;72&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time.Hour),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		Final:        fixAvailableAt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UTC&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddDate&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;14&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why go to this trouble for a log that will hopefully never be needed: because in a dispute the burden of proof is yours. Anyone wanting to go deeper on the principle will find it worked out in &lt;a href=&quot;https://devs-group.ch/en/blog/observability-you-actually-read/&quot;&gt;observability you actually read&lt;/a&gt; and in &lt;a href=&quot;https://devs-group.ch/en/blog/documenting-architecture-decisions/&quot;&gt;documenting architecture decisions&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-not-to-do-now&quot;&gt;What not to do now&lt;/h2&gt;
&lt;p&gt;Do not buy a compliance tool before you have run the reporting chain once against a real vulnerability. This is the recommendation we get the most pushback on, and we are sticking with it.&lt;/p&gt;
&lt;p&gt;The market for CRA platforms has been loud since spring 2026, and the products almost all solve the same problem: they produce documents. The problem that breaks your deadline is the hour between “an alert came in” and “somebody with access decided this is reportable”.&lt;/p&gt;
&lt;p&gt;The trade-off, named honestly: a tool is procured in two weeks; the rehearsal costs an afternoon plus the corrections afterwards. Anyone with nothing at all in September 2026 is better off with a purchased tool plus a rehearsal.&lt;/p&gt;
&lt;p&gt;Where our recommendation does not fit: with more than a handful of product lines with different authorised representatives in the EU, “which product reports to which CSIRT” becomes a data management question. Then a tool is right from day one. With one product and one CSIRT it is burning money.&lt;/p&gt;
&lt;p&gt;The second thing not to do: postponing registration until the real event. Create the EU Login, determine the CSIRT, start the validation. One morning, and the only task that cannot be caught up on 11 September.&lt;/p&gt;
&lt;h2 id=&quot;frequently-asked&quot;&gt;Frequently asked&lt;/h2&gt;
&lt;h3 id=&quot;from-when-does-the-cra-reporting-obligation-apply&quot;&gt;From when does the CRA reporting obligation apply?&lt;/h3&gt;
&lt;p&gt;From 11 September 2026, through Article 14 of Regulation (EU) 2024/2847. Three deadlines then run: 24 hours to the early warning, 72 hours to the full notification, and 14 days after a corrective measure becomes available to the final report — one month for incidents.&lt;/p&gt;
&lt;p&gt;The remaining manufacturer obligations only become applicable on 11 December 2027. Chapter IV on notified bodies has applied since 11 June 2026.&lt;/p&gt;
&lt;h3 id=&quot;who-has-to-report-under-the-cra&quot;&gt;Who has to report under the CRA?&lt;/h3&gt;
&lt;p&gt;The manufacturer of the product with digital elements, regardless of where it is based, as soon as the product is placed on the market in the EU. Reportable are actively exploited vulnerabilities in the product and severe security incidents affecting its security. Existing products are included. Affected users must additionally be informed, under Article 14(8) in a structured, machine-processable format where possible.&lt;/p&gt;
&lt;h3 id=&quot;what-is-an-sbom-and-what-do-i-need-it-for&quot;&gt;What is an SBOM and what do I need it for?&lt;/h3&gt;
&lt;p&gt;An SBOM (software bill of materials) is a machine-readable inventory of all components of a software artefact, usually in CycloneDX or SPDX format. The CRA requires one in Annex I Part II point 1, at least for top-level dependencies. In practice you need it to answer, within hours, which shipped versions contain a component that has just become known to be vulnerable.&lt;/p&gt;
&lt;h3 id=&quot;does-a-swiss-company-have-to-report-twice&quot;&gt;Does a Swiss company have to report twice?&lt;/h3&gt;
&lt;p&gt;Possibly yes, but to different places and for different reasons. The CRA addresses you as the manufacturer of an EU product and means its vulnerabilities. The ISG obligation addresses you as an operator of critical infrastructure and means attacks on your own systems. A ransomware incident in your build system can trigger both at once.&lt;/p&gt;
&lt;h3 id=&quot;what-happens-if-the-single-reporting-platform-is-not-running-on-11-september&quot;&gt;What happens if the Single Reporting Platform is not running on 11 September?&lt;/h3&gt;
&lt;p&gt;The obligation arises from Article 14, not from the availability of the platform. In mid-August 2026 the SRP was not yet live and its URL not yet published. Record the moment of awareness and every attempt to submit, with a note where submission was technically impossible. In that situation a complete log is the only thing you hold.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;strong&gt;If the chain is not yet in place.&lt;/strong&gt; Two weeks remain to 11 September, and probably longer to the first realistic report. If you want to know whether your chain from detection to submitted form holds: thirty minutes with an engineer who has built SBOM pipelines and worked in regulated environments such as the &lt;a href=&quot;https://devs-group.ch/en/projects/form-service-foitt/&quot;&gt;form service for the Swiss FOITT&lt;/a&gt;. No sales pitch, no slides.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;Book a slot directly&lt;/a&gt; or write briefly through &lt;a href=&quot;https://devs-group.ch/en/contact/&quot;&gt;contact&lt;/a&gt; about what you are building. We will tell you where we see the deadline breaking.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;All legal references verified on 19 August 2026 against EUR-Lex and Fedlex. This article describes the technical implementation; it is not legal advice.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>Security &amp; Compliance</category><author>Ralph Segi</author></item><item><title>A revDSG-compliant AI architecture: what actually counts</title><link>https://devs-group.ch/en/blog/revdsg-compliant-ai-architecture/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/revdsg-compliant-ai-architecture/</guid><description>The fine of up to CHF 250,000 lands on the person who acted, not the company. What that means for an AI system&apos;s architecture, provision by provision.</description><pubDate>Tue, 11 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Under the revised Swiss Data Protection Act, liability for a breach falls on the natural person who acted, with a fine of up to CHF 250,000 — not on the company. That is the difference from the GDPR that shapes most architecture decisions, and it appears not in the catalogue of duties but in the criminal provisions.&lt;/p&gt;
&lt;p&gt;That does not make the use of AI prohibited. The revised DPA requires four things: purpose-bound processing under the principles of Art. 6 DPA, transparency about collection under Art. 19, data security under Art. 8 in conjunction with the Data Protection Ordinance (DPO), and, for automated individual decisions, the right to review by a natural person under Art. 21.&lt;/p&gt;
&lt;p&gt;The legal department asks about compliance. What you find on the subject are opinions: they say what applies, not which technical measure satisfies it. The second half is here.&lt;/p&gt;
&lt;h2 id=&quot;which-four-requirements-does-the-revised-dpa-place-on-an-ai-system&quot;&gt;Which four requirements does the revised DPA place on an AI system?&lt;/h2&gt;
&lt;p&gt;The revised DPA is the fully revised Federal Act on Data Protection of 25 September 2020 (SR 235.1), in force since 1 September 2023. Four of its provisions determine how an AI system has to be built. What binds is the &lt;a href=&quot;https://www.fedlex.admin.ch/eli/cc/2022/491/de&quot;&gt;statutory text on Fedlex&lt;/a&gt;, not a summary.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Art. 6 DPA, principles.&lt;/strong&gt; Personal data may only be collected for a specific purpose that is evident, and must go as soon as they are no longer required for it. The purpose has to be recordable per processing step, not per product.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Art. 19 DPA, duty to inform.&lt;/strong&gt; The data subject learns the identity of the controller, the purpose, and the categories of recipients. An inference provider is a recipient. Where data goes abroad, para. 4 adds the state and the safeguards under Art. 16 para. 2.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Art. 8 DPA, data security.&lt;/strong&gt; The minimum requirements are in the &lt;a href=&quot;https://www.fedlex.admin.ch/eli/cc/2022/568/de&quot;&gt;DPO&lt;/a&gt;. Art. 2 DPO names four goals: confidentiality, availability, integrity, traceability. AI systems fail on the fourth.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Art. 21 DPA, automated individual decision.&lt;/strong&gt; Where a decision is based exclusively on automated processing and has legal consequences or significantly affects the data subject, a duty to inform applies. The data subject can demand that a human review the decision.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To that add Art. 7 DPA: processing must be technically designed from the planning stage so that the rules are met. The article that turns the legal question into an architecture question.&lt;/p&gt;
&lt;h2 id=&quot;who-pays-the-fine-when-an-ai-system-breaches-the-revised-dpa&quot;&gt;Who pays the fine when an AI system breaches the revised DPA?&lt;/h2&gt;
&lt;p&gt;The natural person pays, not the company. Arts. 60 to 62 DPA make private persons liable to a fine of up to CHF 250,000. The business comes into it only subsidiarily: under Art. 64 para. 2 DPA the authority may sentence it instead of the persons acting, where a fine of at most CHF 50,000 is in question.&lt;/p&gt;
&lt;p&gt;The provision relevant to an AI project is usually not Art. 60 but &lt;strong&gt;Art. 61 DPA, breach of duties of care&lt;/strong&gt;. It catches precisely the three mistakes an architecture makes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Disclosure abroad contrary to Art. 16 paras. 1 and 2, without the conditions of Art. 17 being met (lit. a)&lt;/li&gt;
&lt;li&gt;Handover to a processor contrary to Art. 9 paras. 1 and 2 (lit. b)&lt;/li&gt;
&lt;li&gt;Failure to meet the minimum data security requirements under Art. 8 para. 3 (lit. c)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Federal Data Protection Commissioner does not impose these fines. Under Art. 65 para. 1 DPA, prosecution and adjudication fall to the cantons, generally to the public prosecutors. He can issue orders and file complaints, as he notes himself on his &lt;a href=&quot;https://www.edoeb.admin.ch/de/strafbestimmungen&quot;&gt;page on the criminal provisions&lt;/a&gt;.&lt;/p&gt;


















































&lt;div class=&quot;scroll-x scroll-x--table&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Tabelle, seitlich scrollbar&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Point&lt;/th&gt;&lt;th&gt;revised DPA&lt;/th&gt;&lt;th&gt;GDPR&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Addressee of the sanction&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;the natural person who acted (Arts. 60–62 DPA); the business only subsidiarily (Art. 64 para. 2)&lt;/td&gt;&lt;td&gt;the controller or processor as an undertaking (Art. 83)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Level of fine&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;up to CHF 250,000; company fine up to CHF 50,000&lt;/td&gt;&lt;td&gt;up to EUR 20 m or 4 % of worldwide annual turnover, whichever is higher (Art. 83(5))&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Fault&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;intent only; Arts. 60(1), 61 and 62 are prosecuted on complaint&lt;/td&gt;&lt;td&gt;negligence is sanctioned too (Art. 83(2)(b))&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Who prosecutes&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;cantonal criminal prosecution authorities (Art. 65 para. 1); the Commissioner issues orders but does not sanction&lt;/td&gt;&lt;td&gt;the supervisory authority imposes the fine itself&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Reporting a security breach&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;”as soon as possible” to the Commissioner where a high risk is likely (Art. 24 para. 1)&lt;/td&gt;&lt;td&gt;without undue delay, where feasible within 72 hours (Art. 33(1))&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Automated individual decision&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;permitted; duty to inform, review by a human on request (Art. 21)&lt;/td&gt;&lt;td&gt;prohibited in principle, permitted only in the cases of Art. 22(2)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Processing on behalf&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;conditions in Art. 9; sub-processing only with prior authorisation (Art. 9 para. 3)&lt;/td&gt;&lt;td&gt;detailed catalogue of contractual obligations (Art. 28)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Limitation of prosecution&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;five years (Art. 66)&lt;/td&gt;&lt;td&gt;not regulated in the GDPR, governed by national law&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The practical consequence: under the GDPR, compliance is a balance sheet risk and gets delegated. Under the revised DPA it is personal. Signing off an agent system means signing with your own name.&lt;/p&gt;
&lt;h2 id=&quot;what-changes-with-sensitive-personal-data&quot;&gt;What changes with sensitive personal data?&lt;/h2&gt;
&lt;p&gt;With sensitive personal data three duties tighten. Art. 5 lit. c DPA lists the categories exhaustively: health, the intimate sphere and affiliation to a race or ethnicity, genetic data, biometric data uniquely identifying a person, religious, ideological, political or trade-union views, data on administrative and criminal proceedings, and data on social assistance measures.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consent must be explicit&lt;/strong&gt; (Art. 6 para. 7 lit. a DPA). A note in the terms of use is not enough.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data protection impact assessment&lt;/strong&gt; for large-scale processing (Art. 22 para. 2 lit. a DPA).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logging&lt;/strong&gt;, where they are processed automatically on a large scale or high-risk profiling takes place, and preventive measures cannot ensure data protection (Art. 4 para. 1 DPO). Both conditions have to coincide.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Processing regulations&lt;/strong&gt; already where they are processed on a large scale or high-risk profiling takes place (Art. 5 para. 1 DPO). Without the restriction that applies to the logging duty.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The catch for an agent system is classification at runtime. A free-text prompt from an HR or support channel contains such data without any database field marking it. Failing to recognise them means logging too little. That is why the audit log below carries a &lt;code&gt;data_classes&lt;/code&gt; field.&lt;/p&gt;
&lt;h2 id=&quot;where-do-personal-data-actually-end-up-in-an-agent-system&quot;&gt;Where do personal data actually end up in an agent system?&lt;/h2&gt;
&lt;p&gt;Personal data end up in seven places, and the model is the most harmless of them. Sovereignty almost never breaks at the inference provider but at the periphery, because that is where nobody looks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Prompt.&lt;/strong&gt; What the user types. The only place most concepts look at.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System context.&lt;/strong&gt; Retrieval results, few-shot examples, master data from the CRM. Often more personal data than in the prompt, and nobody put them there deliberately.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool calls.&lt;/strong&gt; The agent calls an API with arguments. Those arguments are personal data and go to a second system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vector database.&lt;/strong&gt; A vector index over customer correspondence is a collection of personal data in an unfamiliar file format, and it needs the same deletion paths as the source.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logs.&lt;/strong&gt; A stack trace contains the payload; a debug log the entire prompt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Telemetry.&lt;/strong&gt; OpenTelemetry’s GenAI semantic conventions define &lt;code&gt;gen_ai.input.messages&lt;/code&gt;, &lt;code&gt;gen_ai.output.messages&lt;/code&gt; and &lt;code&gt;gen_ai.system_instructions&lt;/code&gt; as span attributes. Even after moving to their own repository with release v1.42.0 of 12 June 2026 they remain at status “Development” (&lt;a href=&quot;https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/&quot;&gt;attribute registry&lt;/a&gt;), and they carry the full content of the conversation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitoring and eval SaaS.&lt;/strong&gt; Error tracking, tracing, prompt evaluation. Three services that rarely appear in a contract review and often run in a third country.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;scroll-x scroll-x--diagram&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Diagramm, seitlich scrollbar&quot;&gt;&lt;svg id=&quot;mermaid-0&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; class=&quot;flowchart&quot; style=&quot;width:2704px&quot; viewBox=&quot;0 0 2704 710.8225708007812&quot; role=&quot;graphics-document document&quot; aria-roledescription=&quot;flowchart-v2&quot;&gt;&lt;style&gt;#mermaid-0{font-family:&quot;Geist&quot;,sans-serif;font-size:16px;fill:#f5f5f5;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-0 .error-icon{fill:#121315;}#mermaid-0 .error-text{fill:#edecea;stroke:#edecea;}#mermaid-0 .edge-thickness-normal{stroke-width:1px;}#mermaid-0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-0 .marker{fill:#c4c4c4;stroke:#c4c4c4;}#mermaid-0 .marker.cross{stroke:#c4c4c4;}#mermaid-0 svg{font-family:&quot;Geist&quot;,sans-serif;font-size:16px;}#mermaid-0 p{margin:0;}#mermaid-0 .label{font-family:&quot;Geist&quot;,sans-serif;color:#f5f5f5;}#mermaid-0 .cluster-label text{fill:#f5f5f5;}#mermaid-0 .cluster-label span{color:#f5f5f5;}#mermaid-0 .cluster-label span p{background-color:transparent;}#mermaid-0 .label text,#mermaid-0 span{fill:#f5f5f5;color:#f5f5f5;}#mermaid-0 .node rect,#mermaid-0 .node circle,#mermaid-0 .node ellipse,#mermaid-0 .node polygon,#mermaid-0 .node path{fill:#1b1d20;stroke:#363739;stroke-width:1px;}#mermaid-0 .rough-node .label text,#mermaid-0 .node .label text,#mermaid-0 .image-shape .label,#mermaid-0 .icon-shape .label{text-anchor:middle;}#mermaid-0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-0 .rough-node .label,#mermaid-0 .node .label,#mermaid-0 .image-shape .label,#mermaid-0 .icon-shape .label{text-align:center;}#mermaid-0 .node.clickable{cursor:pointer;}#mermaid-0 .root .anchor path{fill:#c4c4c4!important;stroke-width:0;stroke:#c4c4c4;}#mermaid-0 .arrowheadPath{fill:#f6f5f3;}#mermaid-0 .edgePath .path{stroke:#c4c4c4;stroke-width:1px;}#mermaid-0 .flowchart-link{stroke:#c4c4c4;fill:none;}#mermaid-0 .edgeLabel{background-color:#090a0c;text-align:center;}#mermaid-0 .edgeLabel p{background-color:#090a0c;}#mermaid-0 .edgeLabel rect{opacity:0.5;background-color:#090a0c;fill:#090a0c;}#mermaid-0 .labelBkg{background-color:rgba(9, 10, 12, 0.5);}#mermaid-0 .cluster rect{fill:#121315;stroke:#232325;stroke-width:1px;}#mermaid-0 .cluster text{fill:#f5f5f5;}#mermaid-0 .cluster span{color:#f5f5f5;}#mermaid-0 .node .collapsed-indicator{fill:#232325;stroke:none;opacity:0.6;}#mermaid-0 .node .collapsed-separator{stroke:#232325;stroke-width:0.75px;}#mermaid-0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:&quot;Geist&quot;,sans-serif;font-size:12px;background:#121315;border:1px solid hsl(220, 0%, 17.6470588235%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#f5f5f5;}#mermaid-0 rect.text{fill:none;stroke-width:0;}#mermaid-0 .icon-shape,#mermaid-0 .image-shape{background-color:#090a0c;text-align:center;}#mermaid-0 .icon-shape p,#mermaid-0 .image-shape p{background-color:#090a0c;padding:2px;}#mermaid-0 .icon-shape .label rect,#mermaid-0 .image-shape .label rect{opacity:0.5;background-color:#090a0c;fill:#090a0c;}#mermaid-0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-0 .node .neo-node{stroke:#363739;}#mermaid-0 [data-look=&quot;neo&quot;].node rect,#mermaid-0 [data-look=&quot;neo&quot;].cluster rect,#mermaid-0 [data-look=&quot;neo&quot;].node polygon{stroke:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].swimlane.cluster rect{filter:none;}#mermaid-0 [data-look=&quot;neo&quot;].node path{stroke:url(#mermaid-0-gradient);stroke-width:1px;}#mermaid-0 [data-look=&quot;neo&quot;].node .outer-path{filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].node .neo-line path{stroke:#363739;filter:none;}#mermaid-0 [data-look=&quot;neo&quot;].node circle{stroke:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].node circle .state-start{fill:#000000;}#mermaid-0 [data-look=&quot;neo&quot;].icon-shape .icon{fill:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 [data-look=&quot;neo&quot;].icon-shape .icon-neo path{stroke:url(#mermaid-0-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#mermaid-0 :root{--mermaid-font-family:&quot;Geist&quot;,sans-serif;}#mermaid-0 .leak&gt;*{stroke-dasharray:6,4!important;}#mermaid-0 .leak span{stroke-dasharray:6,4!important;}&lt;/style&gt;&lt;g&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointEnd&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;5&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;8&quot; markerHeight=&quot;8&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 0 L 10 5 L 0 10 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointStart&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;4.5&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;8&quot; markerHeight=&quot;8&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 5 L 10 10 L 10 0 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointEnd-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 11.5 14&quot; refX=&quot;11.5&quot; refY=&quot;7&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;10.5&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 0 0 L 11.5 7 L 0 14 z&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-pointStart-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 11.5 14&quot; refX=&quot;1&quot; refY=&quot;7&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11.5&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;polygon points=&quot;0,7 11.5,14 11.5,0&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/polygon&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleEnd&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;11&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleStart&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;-1&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:1;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleEnd-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refY=&quot;5&quot; refX=&quot;12.25&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;14&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-circleStart-margin&quot; class=&quot;marker flowchart-v2&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;-2&quot; refY=&quot;5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;14&quot; markerHeight=&quot;14&quot; orient=&quot;auto&quot;&gt;&lt;circle cx=&quot;5&quot; cy=&quot;5&quot; r=&quot;5&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:0;stroke-dasharray:1, 0&quot;&gt;&lt;/circle&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossEnd&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 11 11&quot; refX=&quot;12&quot; refY=&quot;5.2&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 l 9,9 M 10,1 l -9,9&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossStart&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 11 11&quot; refX=&quot;-1&quot; refY=&quot;5.2&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;11&quot; markerHeight=&quot;11&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 l 9,9 M 10,1 l -9,9&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossEnd-margin&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 15 15&quot; refX=&quot;17.7&quot; refY=&quot;7.5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;12&quot; markerHeight=&quot;12&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 L 14,14 M 1,14 L 14,1&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2.5&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;marker id=&quot;mermaid-0_flowchart-v2-crossStart-margin&quot; class=&quot;marker cross flowchart-v2&quot; viewBox=&quot;0 0 15 15&quot; refX=&quot;-3.5&quot; refY=&quot;7.5&quot; markerUnits=&quot;userSpaceOnUse&quot; markerWidth=&quot;12&quot; markerHeight=&quot;12&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M 1,1 L 14,14 M 1,14 L 14,1&quot; class=&quot;arrowMarkerPath&quot; style=&quot;stroke-width:2.5;stroke-dasharray:1, 0&quot;&gt;&lt;/path&gt;&lt;/marker&gt;&lt;g class=&quot;root&quot;&gt;&lt;g class=&quot;clusters&quot;&gt;&lt;g class=&quot;cluster&quot; id=&quot;mermaid-0-US&quot; data-look=&quot;classic&quot;&gt;&lt;rect style=&quot;&quot; x=&quot;8&quot; y=&quot;313&quot; width=&quot;753.8125&quot; height=&quot;184.4264678955078&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;cluster-label&quot; transform=&quot;translate(324.4140625, 313)&quot;&gt;&lt;foreignObject width=&quot;120.984375&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Jurisdiction USA&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;cluster&quot; id=&quot;mermaid-0-EU&quot; data-look=&quot;classic&quot;&gt;&lt;rect style=&quot;&quot; x=&quot;781.8125&quot; y=&quot;313&quot; width=&quot;300.71875&quot; height=&quot;184.4264678955078&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;cluster-label&quot; transform=&quot;translate(877.078125, 313)&quot;&gt;&lt;foreignObject width=&quot;110.1875&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Jurisdiction EU&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;cluster&quot; id=&quot;mermaid-0-CH&quot; data-look=&quot;classic&quot;&gt;&lt;rect style=&quot;&quot; x=&quot;1102.53125&quot; y=&quot;8&quot; width=&quot;1593.46875&quot; height=&quot;694.8225631713867&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;cluster-label&quot; transform=&quot;translate(1811.171875, 8)&quot;&gt;&lt;foreignObject width=&quot;176.1875&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Jurisdiction Switzerland&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgePaths&quot;&gt;&lt;path d=&quot;M1834.063,135L1834.063,139.167C1834.063,143.333,1834.063,151.667,1834.063,159.333C1834.063,167,1834.063,174,1834.063,177.5L1834.063,181&quot; id=&quot;mermaid-0-L_U_APP_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_U_APP_0&quot; data-points=&quot;W3sieCI6MTgzNC4wNjI1LCJ5IjoxMzV9LHsieCI6MTgzNC4wNjI1LCJ5IjoxNjB9LHsieCI6MTgzNC4wNjI1LCJ5IjoxODV9XQ==&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M1842.5,239L1844.427,245.167C1846.354,251.333,1850.208,263.667,1852.135,276C1854.063,288.333,1854.063,300.667,1854.063,310.333C1854.063,320,1854.063,327,1854.063,330.5L1854.063,334&quot; id=&quot;mermaid-0-L_APP_VDB_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_APP_VDB_0&quot; data-points=&quot;W3sieCI6MTg0Mi41LCJ5IjoyMzl9LHsieCI6MTg1NC4wNjI1LCJ5IjoyNzZ9LHsieCI6MTg1NC4wNjI1LCJ5IjozMTN9LHsieCI6MTg1NC4wNjI1LCJ5IjozMzh9XQ==&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M1916.023,223.049L1981.486,231.874C2046.948,240.7,2177.872,258.35,2243.335,273.342C2308.797,288.333,2308.797,300.667,2308.797,312.333C2308.797,324,2308.797,335,2308.797,340.5L2308.797,346&quot; id=&quot;mermaid-0-L_APP_CRM_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_APP_CRM_0&quot; data-points=&quot;W3sieCI6MTkxNi4wMjM0Mzc1LCJ5IjoyMjMuMDQ5MzM2ODAwMTg0M30seyJ4IjoyMzA4Ljc5Njg3NSwieSI6Mjc2fSx7IngiOjIzMDguNzk2ODc1LCJ5IjozMTN9LHsieCI6MjMwOC43OTY4NzUsInkiOjM0OS45OTk5OTYxODUzMDI3M31d&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M1787.284,239L1776.6,245.167C1765.916,251.333,1744.548,263.667,1733.864,276C1723.18,288.333,1723.18,300.667,1611.234,319.884C1499.288,339.101,1275.396,365.201,1163.45,378.252L1051.504,391.302&quot; id=&quot;mermaid-0-L_APP_INF_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_APP_INF_0&quot; data-points=&quot;W3sieCI6MTc4Ny4yODM4MTM0NzY1NjI1LCJ5IjoyMzl9LHsieCI6MTcyMy4xNzk2ODc1LCJ5IjoyNzZ9LHsieCI6MTcyMy4xNzk2ODc1LCJ5IjozMTN9LHsieCI6MTA0Ny41MzEyNSwieSI6MzkxLjc2NDk5NjMxMTExMDJ9XQ==&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M1047.531,393.418L1178.62,380.015C1309.708,366.612,1571.885,339.806,1702.974,320.236C1834.063,300.667,1834.063,288.333,1834.063,276.667C1834.063,265,1834.063,254,1834.063,248.5L1834.063,243&quot; id=&quot;mermaid-0-L_INF_APP_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_INF_APP_0&quot; data-points=&quot;W3sieCI6MTA0Ny41MzEyNSwieSI6MzkzLjQxODM4Nzk0MzA3MTZ9LHsieCI6MTgzNC4wNjI1LCJ5IjozMTN9LHsieCI6MTgzNC4wNjI1LCJ5IjoyNzZ9LHsieCI6MTgzNC4wNjI1LCJ5IjoyMzl9XQ==&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M1916.023,233.205L1943.591,240.338C1971.159,247.47,2026.294,261.735,2053.862,275.034C2081.43,288.333,2081.43,300.667,2081.43,322.202C2081.43,343.738,2081.43,374.475,2081.43,405.213C2081.43,435.951,2081.43,466.689,2081.43,488.224C2081.43,509.76,2081.43,522.093,2113.079,538.471C2144.728,554.849,2208.027,575.271,2239.677,585.482L2271.326,595.693&quot; id=&quot;mermaid-0-L_APP_LOG_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_APP_LOG_0&quot; data-points=&quot;W3sieCI6MTkxNi4wMjM0Mzc1LCJ5IjoyMzMuMjA1MzE4NTEwNTY0Mzh9LHsieCI6MjA4MS40Mjk2ODc1LCJ5IjoyNzZ9LHsieCI6MjA4MS40Mjk2ODc1LCJ5IjozMTN9LHsieCI6MjA4MS40Mjk2ODc1LCJ5Ijo0MDUuMjEzMjMzOTQ3NzUzOX0seyJ4IjoyMDgxLjQyOTY4NzUsInkiOjQ5Ny40MjY0Njc4OTU1MDc4fSx7IngiOjIwODEuNDI5Njg3NSwieSI6NTM0LjQyNjQ2Nzg5NTUwNzh9LHsieCI6MjI3NS4xMzI4MTI1LCJ5Ijo1OTYuOTIxMDk5NTMyNjQ4Mn1d&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M1916.023,219.19L2023.961,228.658C2131.898,238.126,2347.773,257.063,2455.711,272.698C2563.648,288.333,2563.648,300.667,2563.648,315.036C2563.648,329.404,2563.648,345.809,2563.648,354.011L2563.648,362.213&quot; id=&quot;mermaid-0-L_APP_REV_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_APP_REV_0&quot; data-points=&quot;W3sieCI6MTkxNi4wMjM0Mzc1LCJ5IjoyMTkuMTg5Njk0NDk3MDkyNzR9LHsieCI6MjU2My42NDg0Mzc1LCJ5IjoyNzZ9LHsieCI6MjU2My42NDg0Mzc1LCJ5IjozMTN9LHsieCI6MjU2My42NDg0Mzc1LCJ5IjozNjYuMjEzMjMzOTQ3NzUzOX1d&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M2563.648,444.213L2563.648,453.082C2563.648,461.951,2563.648,479.689,2563.648,494.724C2563.648,509.76,2563.648,522.093,2544.794,536.652C2525.939,551.211,2488.231,567.995,2469.376,576.387L2450.522,584.779&quot; id=&quot;mermaid-0-L_REV_LOG_0&quot; class=&quot;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_REV_LOG_0&quot; data-points=&quot;W3sieCI6MjU2My42NDg0Mzc1LCJ5Ijo0NDQuMjEzMjMzOTQ3NzUzOX0seyJ4IjoyNTYzLjY0ODQzNzUsInkiOjQ5Ny40MjY0Njc4OTU1MDc4fSx7IngiOjI1NjMuNjQ4NDM3NSwieSI6NTM0LjQyNjQ2Nzg5NTUwNzh9LHsieCI6MjQ0Ni44NjcxODc1LCJ5Ijo1ODYuNDA1MzU3NDQ3NjA3N31d&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M1752.102,219.758L1653.078,229.132C1554.055,238.506,1356.008,257.253,1256.984,272.793C1157.961,288.333,1157.961,300.667,1002.939,320.786C847.917,340.905,537.872,368.811,382.85,382.764L227.828,396.716&quot; id=&quot;mermaid-0-L_APP_OBS_0&quot; class=&quot;edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_APP_OBS_0&quot; data-points=&quot;W3sieCI6MTc1Mi4xMDE1NjI1LCJ5IjoyMTkuNzU4NDQ5NzUyMTQwNn0seyJ4IjoxMTU3Ljk2MDkzNzUsInkiOjI3Nn0seyJ4IjoxMTU3Ljk2MDkzNzUsInkiOjMxM30seyJ4IjoyMjMuODQzNzUsInkiOjM5Ny4wNzQ4NDg3NzY3NDQ4M31d&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M1752.102,222.06L1678.862,231.05C1605.622,240.04,1459.143,258.02,1385.904,273.177C1312.664,288.333,1312.664,300.667,1180.704,320.099C1048.743,339.532,784.823,366.063,652.862,379.329L520.902,392.595&quot; id=&quot;mermaid-0-L_APP_EVAL_0&quot; class=&quot;edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_APP_EVAL_0&quot; data-points=&quot;W3sieCI6MTc1Mi4xMDE1NjI1LCJ5IjoyMjIuMDYwNDQ0NDE3ODA2Njh9LHsieCI6MTMxMi42NjQwNjI1LCJ5IjoyNzZ9LHsieCI6MTMxMi42NjQwNjI1LCJ5IjozMTN9LHsieCI6NTE2LjkyMTg3NSwieSI6MzkyLjk5NTA1MTEzNDAyNzh9XQ==&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M1752.102,228.815L1713.771,236.68C1675.44,244.544,1598.779,260.272,1560.448,274.303C1522.117,288.333,1522.117,300.667,1390.229,320.729C1258.342,340.79,994.566,368.581,862.678,382.476L730.79,396.371&quot; id=&quot;mermaid-0-L_APP_ERR_0&quot; class=&quot;edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link&quot; style=&quot;&quot; data-edge=&quot;true&quot; data-et=&quot;edge&quot; data-id=&quot;L_APP_ERR_0&quot; data-points=&quot;W3sieCI6MTc1Mi4xMDE1NjI1LCJ5IjoyMjguODE1NDQ3NDE5MTY5MDN9LHsieCI6MTUyMi4xMTcxODc1LCJ5IjoyNzZ9LHsieCI6MTUyMi4xMTcxODc1LCJ5IjozMTN9LHsieCI6NzI2LjgxMjUsInkiOjM5Ni43OTA0Nzk1Mjk0ODYyfV0=&quot; data-look=&quot;classic&quot; marker-end=&quot;url(#mermaid-0_flowchart-v2-pointEnd)&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabels&quot;&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_U_APP_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_APP_VDB_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_APP_CRM_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(1723.1796875, 276)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_APP_INF_0&quot; transform=&quot;translate(-90.8828125, -12)&quot;&gt;&lt;foreignObject width=&quot;181.765625&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;prompt + system context&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_INF_APP_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(2081.4296875, 405.2132339477539)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_APP_LOG_0&quot; transform=&quot;translate(-84.8671875, -12)&quot;&gt;&lt;foreignObject width=&quot;169.734375&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;proposal, before review&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_APP_REV_0&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;foreignObject width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(2563.6484375, 534.4264678955078)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_REV_LOG_0&quot; transform=&quot;translate(-30.359375, -12)&quot;&gt;&lt;foreignObject width=&quot;60.71875&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;decision&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(1157.9609375, 276)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_APP_OBS_0&quot; transform=&quot;translate(-35.4296875, -12)&quot;&gt;&lt;foreignObject width=&quot;70.859375&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;telemetry&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(1312.6640625, 276)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_APP_EVAL_0&quot; transform=&quot;translate(-99.2734375, -12)&quot;&gt;&lt;foreignObject width=&quot;198.546875&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;traces with prompt content&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;edgeLabel&quot; transform=&quot;translate(1522.1171875, 276)&quot;&gt;&lt;g class=&quot;label&quot; data-id=&quot;L_APP_ERR_0&quot; transform=&quot;translate(-90.1796875, -12)&quot;&gt;&lt;foreignObject width=&quot;180.359375&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;labelBkg&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;edgeLabel&quot;&gt;&lt;p&gt;stack trace with payload&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;nodes&quot;&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-U-0&quot; data-look=&quot;classic&quot; transform=&quot;translate(1834.0625, 84)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-130&quot; y=&quot;-51&quot; width=&quot;260&quot; height=&quot;102&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -36)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;72&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Employee&lt;br/&gt;input containing personal data&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-APP-1&quot; data-look=&quot;classic&quot; transform=&quot;translate(1834.0625, 212)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-81.9609375&quot; y=&quot;-27&quot; width=&quot;163.921875&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-51.9609375, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;103.921875&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Agent runtime&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-VDB-2&quot; data-look=&quot;classic&quot; transform=&quot;translate(1854.0625, 405.2132339477539)&quot;&gt;&lt;path d=&quot;M0,15.808823529411764 a107.5,15.808823529411764 0,0,0 215,0 a107.5,15.808823529411764 0,0,0 -215,0 l0,102.80882352941177 a107.5,15.808823529411764 0,0,0 215,0 l0,-102.80882352941177&quot; class=&quot;basic label-container outer-path&quot; style=&quot;&quot; transform=&quot;translate(-107.5, -67.21323529411765)&quot;&gt;&lt;/path&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -26)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;72&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Vector database&lt;br/&gt;embeddings = personal data&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-REV-3&quot; data-look=&quot;classic&quot; transform=&quot;translate(2563.6484375, 405.2132339477539)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-97.3515625&quot; y=&quot;-39&quot; width=&quot;194.703125&quot; height=&quot;78&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-67.3515625, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;134.703125&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Human in the loop&lt;br/&gt;Art. 21 para. 2 DPA&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-LOG-4&quot; data-look=&quot;classic&quot; transform=&quot;translate(2361, 624.6245155334473)&quot;&gt;&lt;path d=&quot;M0,14.468695697962193 a85.8671875,14.468695697962193 0,0,0 171.734375,0 a85.8671875,14.468695697962193 0,0,0 -171.734375,0 l0,77.46869569796219 a85.8671875,14.468695697962193 0,0,0 171.734375,0 l0,-77.46869569796219&quot; class=&quot;basic label-container outer-path&quot; style=&quot;&quot; transform=&quot;translate(-85.8671875, -53.20304354694329)&quot;&gt;&lt;/path&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-78.3671875, -14)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;156.734375&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Audit log&lt;br/&gt;Art. 4 DPO, separated&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-CRM-5&quot; data-look=&quot;classic&quot; transform=&quot;translate(2308.796875, 405.2132339477539)&quot;&gt;&lt;path d=&quot;M0,15.808823529411764 a107.5,15.808823529411764 0,0,0 215,0 a107.5,15.808823529411764 0,0,0 -215,0 l0,78.80882352941177 a107.5,15.808823529411764 0,0,0 215,0 l0,-78.80882352941177&quot; class=&quot;basic label-container outer-path&quot; style=&quot;&quot; transform=&quot;translate(-107.5, -55.21323529411765)&quot;&gt;&lt;/path&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-100, -14)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;200&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table;white-space:break-spaces;line-height:1.5;max-width:200px;text-align:center;width:200px&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Line-of-business system / CRM&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default&quot; id=&quot;mermaid-0-flowchart-INF-6&quot; data-look=&quot;classic&quot; transform=&quot;translate(932.171875, 405.2132339477539)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;&quot; x=&quot;-115.359375&quot; y=&quot;-39&quot; width=&quot;230.71875&quot; height=&quot;78&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-85.359375, -24)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;170.71875&quot; height=&quot;48&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Inference endpoint&lt;br/&gt;Annex 1 DPO: adequate&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default leak&quot; id=&quot;mermaid-0-flowchart-OBS-7&quot; data-look=&quot;classic&quot; transform=&quot;translate(133.421875, 405.2132339477539)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;stroke-dasharray:6 4 !important&quot; x=&quot;-90.421875&quot; y=&quot;-27&quot; width=&quot;180.84375&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-60.421875, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;120.84375&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Monitoring SaaS&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default leak&quot; id=&quot;mermaid-0-flowchart-EVAL-8&quot; data-look=&quot;classic&quot; transform=&quot;translate(395.3828125, 405.2132339477539)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;stroke-dasharray:6 4 !important&quot; x=&quot;-121.5390625&quot; y=&quot;-27&quot; width=&quot;243.078125&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-91.5390625, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;183.078125&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Eval and tracing platform&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;g class=&quot;node default leak&quot; id=&quot;mermaid-0-flowchart-ERR-9&quot; data-look=&quot;classic&quot; transform=&quot;translate(646.8671875, 405.2132339477539)&quot;&gt;&lt;rect class=&quot;basic label-container&quot; style=&quot;stroke-dasharray:6 4 !important&quot; x=&quot;-79.9453125&quot; y=&quot;-27&quot; width=&quot;159.890625&quot; height=&quot;54&quot;&gt;&lt;/rect&gt;&lt;g class=&quot;label&quot; style=&quot;&quot; transform=&quot;translate(-49.9453125, -12)&quot;&gt;&lt;rect&gt;&lt;/rect&gt;&lt;foreignObject width=&quot;99.890625&quot; height=&quot;24&quot;&gt;&lt;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display:table-cell;white-space:nowrap;line-height:1.5;max-width:200px;text-align:center&quot;&gt;&lt;span class=&quot;nodeLabel&quot;&gt;&lt;p&gt;Error tracking&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;defs&gt;&lt;filter id=&quot;mermaid-0-drop-shadow&quot; height=&quot;130%&quot; width=&quot;130%&quot;&gt;&lt;feDropShadow dx=&quot;4&quot; dy=&quot;4&quot; stdDeviation=&quot;0&quot; flood-opacity=&quot;0.06&quot; flood-color=&quot;#000000&quot;&gt;&lt;/feDropShadow&gt;&lt;/filter&gt;&lt;/defs&gt;&lt;defs&gt;&lt;filter id=&quot;mermaid-0-drop-shadow-small&quot; height=&quot;150%&quot; width=&quot;150%&quot;&gt;&lt;feDropShadow dx=&quot;2&quot; dy=&quot;2&quot; stdDeviation=&quot;0&quot; flood-opacity=&quot;0.06&quot; flood-color=&quot;#000000&quot;&gt;&lt;/feDropShadow&gt;&lt;/filter&gt;&lt;/defs&gt;&lt;linearGradient id=&quot;mermaid-0-gradient&quot; gradientUnits=&quot;objectBoundingBox&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;&lt;stop offset=&quot;0%&quot; stop-color=&quot;#363739&quot; stop-opacity=&quot;1&quot;&gt;&lt;/stop&gt;&lt;stop offset=&quot;100%&quot; stop-color=&quot;hsl(220, 0%, 17.6470588235%)&quot; stop-opacity=&quot;1&quot;&gt;&lt;/stop&gt;&lt;/linearGradient&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;p&gt;The dotted edges are disclosures abroad that appear in no concept document. Anyone who draws their data flow this way usually finds two or three.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Draw your own flow like this once.&lt;/strong&gt; It takes an hour and is the cheapest data protection measure we know. If you want somebody at the whiteboard who already knows the periphery: &lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;half an hour, no preparation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;How the same data flow can be built at the infrastructure level, with a provider comparison and GPU costs in CHF, is in &lt;a href=&quot;https://devs-group.ch/en/blog/sovereign-ai-inference-switzerland/&quot;&gt;sovereign AI inference in Switzerland&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-does-hosted-in-switzerland-mean-technically-and-where-does-it-stop&quot;&gt;What does “hosted in Switzerland” mean technically, and where does it stop?&lt;/h2&gt;
&lt;p&gt;“Hosted in Switzerland” describes the storage location, not the possibility of access. The two come apart as soon as the provider has a parent company in a third country, its support escalates from another time zone, or its control plane runs elsewhere. Access from outside is a disclosure abroad under Art. 16 DPA.&lt;/p&gt;
&lt;p&gt;The test has two stages: is the recipient state in Annex 1 DPO? If not, a safeguard under Art. 16 para. 2 is required, for instance standard data protection clauses recognised by the Commissioner.&lt;/p&gt;
&lt;p&gt;For the USA an intermediate position applies that is frequently misreported. Since 15 September 2024 it counts as a state with adequate data protection, &lt;strong&gt;but only for companies certified under the Swiss-U.S. Data Privacy Framework&lt;/strong&gt; (&lt;a href=&quot;https://www.edoeb.admin.ch/de/nsb?id=102054&quot;&gt;Commissioner&lt;/a&gt;). Certification is per company, not per country.&lt;/p&gt;
&lt;p&gt;Three technical consequences follow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Egress is enforced, not agreed. A network-level allowlist is the only evidence that survives a review.&lt;/li&gt;
&lt;li&gt;The inference endpoint belongs behind an abstraction of your own. Programming against a provider SDK means you cannot switch when the certification falls.&lt;/li&gt;
&lt;li&gt;Every outbound connection needs a recorded jurisdiction. At &lt;a href=&quot;https://ai-gent.ch&quot;&gt;AIgent&lt;/a&gt; that is a start-up condition: an endpoint without a jurisdiction entry does not come up.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;where-does-the-human-in-the-loop-gate-sit-in-the-agent-graph&quot;&gt;Where does the human-in-the-loop gate sit in the agent graph?&lt;/h2&gt;
&lt;p&gt;The gate sits between decision and effect, not between model response and decision. Art. 21 para. 2 DPA requires review by a natural person, and a decision is only reviewable while it has not been carried out. An approval after the rejection letter has gone out does not satisfy the provision.&lt;/p&gt;
&lt;p&gt;What decides it is the behaviour on timeout: a gate that lets things through after a timeout is not a gate.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;package&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; hitl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;errors&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fmt&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Decision is the agent&amp;#39;s proposal before it takes effect.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Decision&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	RunID       &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	SubjectRef  &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   // pseudonym of the data subject, never the real name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Action      &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   // e.g. &amp;quot;application.reject&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Rationale   &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   // reasoning for the reviewing person&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	DataClasses []&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // e.g. []string{&amp;quot;creditworthiness&amp;quot;, &amp;quot;employment&amp;quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Review is the outcome of review by a natural person.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Review&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	ReviewerID &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Approved   &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	Comment    &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	At         &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Auditor&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; interface&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;	Append&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;runID&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; any&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ReviewQueue&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; interface&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;	Await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;d&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Decision&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Review&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	ErrExpired  &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; errors.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;New&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;hitl: no approval within the deadline, decision discarded&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	ErrRejected &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; errors.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;New&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;hitl: rejected by the reviewing person&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Gate holds an automated individual decision under Art. 21 DPA until a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// natural person has confirmed it. If the deadline passes, the decision counts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// as not taken. There is no path on which a timeout leads to a silent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// approval.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Gate&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Auditor&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;q&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ReviewQueue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;d&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Decision&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;wait&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; time&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Duration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Append&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ctx, d.RunID, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;decision.proposed&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, d); err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;audit: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%w&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, err)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	waitCtx, cancel &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WithTimeout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ctx, wait)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	defer&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; cancel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	r, err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; q.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(waitCtx, d)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;		// The reason for abandoning belongs in the log, even when the context is dead.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		_ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Append&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(context.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WithoutCancel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ctx), d.RunID, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;decision.expired&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, err.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ErrExpired&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	event &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;decision.rejected&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; r.Approved {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		event &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;quot;decision.approved&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Append&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ctx, d.RunID, event, r); err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;audit: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%w&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, err)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r.Approved {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;		return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%w&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, ErrRejected, r.ReviewerID)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two things are set deliberately. The proposal is logged before anyone sees it; otherwise there is no way to evidence that a review took place. And &lt;code&gt;Decision&lt;/code&gt; carries a pseudonym rather than a name: the reviewing person resolves the identity through their own authorisation. The same separation we treat as an interface question in &lt;a href=&quot;https://devs-group.ch/en/blog/documenting-architecture-decisions/&quot;&gt;documenting architecture decisions&lt;/a&gt; is a data protection question here.&lt;/p&gt;
&lt;h2 id=&quot;what-does-an-audit-log-that-survives-a-subject-access-request-look-like&quot;&gt;What does an audit log that survives a subject access request look like?&lt;/h2&gt;
&lt;p&gt;An audit log fit for the revised DPA answers, per data subject, which personal data were processed when, by whom and for what purpose. Art. 4 para. 4 DPO names the mandatory fields: identity of the processing person, type, date and time, and where applicable the recipient. Para. 5 requires at least a year of retention, &lt;strong&gt;separated from the system in which the personal data are processed&lt;/strong&gt;. Anyone supplying software into the EU needs a second, similarly built log from September 2026: the &lt;a href=&quot;https://devs-group.ch/en/blog/cra-24-hour-reporting-obligation/&quot;&gt;incident log for the CRA reporting obligation&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- Its own database, its own credentials, its own backup cycle.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- Art. 4 para. 5 DPO requires separation from the processing system.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; agent_audit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    id           &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;bigserial&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    occurred_at  &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;timestamptz&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; DEFAULT&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; now&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- Art. 4 para. 4 DPO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    run_id       uuid        &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    actor        &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- &amp;#39;user:4711&amp;#39;, &amp;#39;agent:triage@2.3.1&amp;#39;, &amp;#39;reviewer:rp&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    operation    &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   -- the six operations from Art. 4 para. 1 DPO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        CHECK&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (operation &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;IN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;store&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;modify&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;read&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                             &amp;#39;disclose&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;delete&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;#39;destroy&amp;#39;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subject_ref  &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- pseudonym, resolvable only in the identity service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    data_classes &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[]      &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- which categories, not which contents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    recipient    &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,                  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- &amp;#39;llm:apertus@ch-inference&amp;#39;, &amp;#39;crm:abacus&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    jurisdiction &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)     &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- &amp;#39;CH&amp;#39;, &amp;#39;EU&amp;#39;, &amp;#39;US&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    purpose      &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- keep Art. 6 para. 3 DPA demonstrable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    payload_sha  &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;bytea&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;       NOT NULL&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   -- hash instead of content&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- Make an access request under Art. 25 DPA answerable in one query.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; agent_audit (subject_ref, occurred_at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;DESC&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; agent_audit (run_id);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The line most often missing is &lt;code&gt;payload_sha&lt;/code&gt;. An audit log with the prompt in cleartext is a second copy of the personal data that then has to sit there for a year. The trade-off: the hash evidences the processing, but nobody can read back what was in it.&lt;/p&gt;
&lt;p&gt;Two provisions collide here. Art. 6 para. 4 DPA requires destruction as soon as the data are no longer required; Art. 4 para. 5 DPO requires a year of retention for the log. That only resolves with separate lifecycles, as in the &lt;a href=&quot;https://devs-group.ch/en/projects/form-service-foitt/&quot;&gt;form service for the Swiss FOITT&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-we-advise-against&quot;&gt;What we advise against&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;An enterprise subscription as proof of compliance.&lt;/strong&gt; An enterprise plan governs whether the provider uses your data for training. That is the only one of the four requirements it can meet for you. Purpose limitation, the duty to inform, traceability and reviewability under Art. 21 arise in your system, not in their contract.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prompt contents in general observability.&lt;/strong&gt; Writing &lt;code&gt;gen_ai.input.messages&lt;/code&gt; into the same trace backend as your HTTP spans puts the conversation content in a system whose retention is tuned for cost. Prompts belong in the audit log, not in telemetry.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And the trade-off we will name openly:&lt;/strong&gt; self-hosting the model costs speed. You operate GPU capacity, lag behind on model capabilities, and move effort from an invoice into a team.&lt;/p&gt;
&lt;p&gt;An inference provider from a state in Annex 1 DPO, combined with strict control of the periphery, meets the same four requirements with less operational effort. Self-hosting is the right answer for sensitive personal data such as health data, and how to do it is in &lt;a href=&quot;https://devs-group.ch/en/blog/sovereign-ai-inference-switzerland/&quot;&gt;sovereign AI inference in Switzerland&lt;/a&gt;. For an internal knowledge assistant it is the wrong one.&lt;/p&gt;
&lt;p&gt;As in &lt;a href=&quot;https://devs-group.ch/en/blog/ai-code-without-technical-debt/&quot;&gt;who writes the architecture&lt;/a&gt;: the guardrails live in the repository, not in someone’s head.&lt;/p&gt;
&lt;h2 id=&quot;frequently-asked&quot;&gt;Frequently asked&lt;/h2&gt;
&lt;h3 id=&quot;do-i-need-a-processor-agreement-for-an-ai-provider&quot;&gt;Do I need a processor agreement for an AI provider?&lt;/h3&gt;
&lt;p&gt;Yes, as soon as it processes personal data on your behalf, and every hosted inference service does. Art. 9 para. 1 DPA permits the transfer only where the data are processed as you would be permitted to process them yourself. What regularly gets broken is para. 3: sub-processing requires your prior authorisation. That is checkable through the versioned sub-processor list, not through the contract text.&lt;/p&gt;
&lt;h3 id=&quot;may-i-put-personal-data-into-chatgpt&quot;&gt;May I put personal data into ChatGPT?&lt;/h3&gt;
&lt;p&gt;Legally possible, technically rarely clean. You need processing under the principles of Art. 6 DPA, a processor arrangement under Art. 9, the information under Art. 19 including the recipient state, and, where the provider is not Swiss-U.S. DPF certified, a safeguard under Art. 16.&lt;/p&gt;
&lt;p&gt;Which criminal provision applies depends on what is missing: for the safeguard Art. 61 lit. a, for the processor arrangement Art. 61 lit. b, for the information Art. 60 para. 1 lit. b.&lt;/p&gt;
&lt;h3 id=&quot;is-an-enterprise-subscription-enough-for-revdsg-compliance&quot;&gt;Is an enterprise subscription enough for revDSG compliance?&lt;/h3&gt;
&lt;p&gt;No. An enterprise subscription governs training use and usually data residency. It creates neither the log under Art. 4 DPO nor the reviewability under Art. 21 DPA, and it says nothing about your vector database, your logs and your monitoring.&lt;/p&gt;
&lt;h3 id=&quot;do-we-need-a-data-protection-impact-assessment-for-an-ai-assistant&quot;&gt;Do we need a data protection impact assessment for an AI assistant?&lt;/h3&gt;
&lt;p&gt;Where the processing may entail a high risk to personality or fundamental rights, yes (Art. 22 para. 1 DPA). Para. 2 names two standard cases: large-scale processing of sensitive personal data, and systematic monitoring of extensive public areas. Where a high risk remains despite measures, the Commissioner must be consulted (Art. 23).&lt;/p&gt;
&lt;h3 id=&quot;does-the-eu-ai-act-apply-to-our-swiss-company&quot;&gt;Does the EU AI Act apply to our Swiss company?&lt;/h3&gt;
&lt;p&gt;It can apply extraterritorially. Art. 2(1)(c) of the AI Regulation covers providers and deployers from third countries where the output of the AI system is used in the Union. The obligations for high-risk systems under Annex III apply, following &lt;a href=&quot;https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=OJ%3AL_202601744&quot;&gt;Regulation (EU) 2026/1744&lt;/a&gt;, only from 2 December 2027, and for embedded systems under Annex I from 2 August 2028.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&lt;strong&gt;Take your architecture diagram and draw in the jurisdictional boundaries.&lt;/strong&gt; If edges appear that you cannot explain, let’s talk about them.&lt;/p&gt;
&lt;p&gt;Thirty minutes with an engineer about your agent architecture, no sales pitch: &lt;a href=&quot;https://calendar.app.google/KojcyhwDrSDqM9oQ7&quot;&gt;book a slot&lt;/a&gt;. Prefer writing? Through &lt;a href=&quot;https://devs-group.ch/en/contact/&quot;&gt;contact&lt;/a&gt;. What we build is under &lt;a href=&quot;https://devs-group.ch/en/services/artificial-intelligence/&quot;&gt;Artificial Intelligence &amp;amp; Machine Learning&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;All legal references verified on 18 August 2026 against Fedlex and the Federal Data Protection Commissioner. This article is architecture advice, not legal advice.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>Artificial Intelligence</category><author>Robert Pupel</author></item><item><title>Why we build critical backends in Go</title><link>https://devs-group.ch/en/blog/why-go-for-critical-backends/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/why-go-for-critical-backends/</guid><description>Not because of the benchmarks. Because of what a team can still understand after three years.</description><pubDate>Tue, 04 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When we are asked why a backend should be built in Go rather than Node, Java or Rust, the answer rarely comes from the benchmarks. Go is fast, yes. But plenty of things are fast. The reason is a different one: Go forces a kind of simplicity you can still read three years later.&lt;/p&gt;
&lt;h2 id=&quot;the-language-has-few-opinions-and-that-is-the-opinion&quot;&gt;The language has few opinions, and that is the opinion&lt;/h2&gt;
&lt;p&gt;Go has no inheritance, no exceptions, no generics acrobatics, no three ways to write a loop. What looks at first like a shortcoming is a gift in operation: code a junior writes looks almost like code a senior writes. Reviews are about the domain, not about style.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A backend you can no longer read is not a backend any more. It is a mortgage.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For systems meant to run for ten years — and with government, banks and infrastructure that is the rule — readability outweighs any feature list.&lt;/p&gt;
&lt;h2 id=&quot;errors-are-values-not-surprises&quot;&gt;Errors are values, not surprises&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;if err != nil&lt;/code&gt; gets its share of eye-rolling. We like it. Every error is a visible path in the code that you either handle or deliberately pass on. There is no stack that surfaces three levels higher and swallows the request quietly.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;order, err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; repo.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FindOrder&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ctx, id)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; err &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fmt.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Errorf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;find order &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%w&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, id, err)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The error chain that results reads in the log like a sentence: &lt;code&gt;handle payment: find order 4711: connection refused&lt;/code&gt;. That is observability you do not have to buy a tool for.&lt;/p&gt;
&lt;h2 id=&quot;one-binary-one-container-no-mystery&quot;&gt;One binary, one container, no mystery&lt;/h2&gt;
&lt;p&gt;Go compiles to a static binary. The deployment is a &lt;code&gt;FROM scratch&lt;/code&gt; container with one file in it — no runtime image, no package manager, hardly any attack surface. For Kubernetes environments that means small images, fast starts, little CVE noise. We carry the same pattern into new protocols, for instance when &lt;a href=&quot;https://devs-group.ch/en/blog/running-mcp-servers-in-go/&quot;&gt;running MCP servers in Go in production&lt;/a&gt;. What we offer concretely as &lt;a href=&quot;https://devs-group.ch/en/services/golang-development/&quot;&gt;Go engineers from Switzerland&lt;/a&gt; is on the services page.&lt;/p&gt;
&lt;h2 id=&quot;concurrency-you-can-explain&quot;&gt;Concurrency you can explain&lt;/h2&gt;
&lt;p&gt;Goroutines and channels are not magic, but they let you write concurrent flows the way you draw them on a whiteboard: a worker pool, a pipeline, a fan-out. Once you have understood the model, you read someone else’s code without fear.&lt;/p&gt;
&lt;h2 id=&quot;when-we-do-not-reach-for-go&quot;&gt;When we do &lt;em&gt;not&lt;/em&gt; reach for Go&lt;/h2&gt;
&lt;p&gt;Honesty belongs here too: for data-heavy exploration and ML pipelines, Python remains the right choice. For frontends, TypeScript obviously. And where a team is already deep in an ecosystem and doing well with it, a change of language is rarely the most important decision available.&lt;/p&gt;
&lt;p&gt;But where a system is a company’s critical infrastructure — payments, identity, administration — Go is for us the language that asks the smallest price for clarity.&lt;/p&gt;</content:encoded><category>Architecture</category><author>Ralph Segi</author></item><item><title>AI writes the code. Who writes the architecture?</title><link>https://devs-group.ch/en/blog/ai-code-without-technical-debt/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/ai-code-without-technical-debt/</guid><description>How we use AI-assisted engineering so that less code exists at the end, not more.</description><pubDate>Tue, 21 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The amount of code a team can produce per week has multiplied with AI assistants. The amount of code a team can &lt;em&gt;understand&lt;/em&gt; per week has not. The technical debt of the next few years is being created in exactly that gap.&lt;/p&gt;
&lt;h2 id=&quot;the-problem-is-not-the-ai-it-is-the-missing-brake&quot;&gt;The problem is not the AI. It is the missing brake.&lt;/h2&gt;
&lt;p&gt;An assistant told to “build me this” builds it. It does not ask whether this is the third repository pattern in the same service, whether the new dependency is necessary, or whether the function it is writing already exists. That is not a criticism of the tools. It is a description of their job. The brake has to come from outside.&lt;/p&gt;
&lt;h2 id=&quot;three-rules-we-hold-to&quot;&gt;Three rules we hold to&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Architecture first, then prompt.&lt;/strong&gt; Before a feature goes to an assistant, it is settled which module it belongs in, which interface it uses, and what it must &lt;em&gt;not&lt;/em&gt; touch. Those guardrails live in the repository, not in someone’s head.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Every change is read by a human — all of it.&lt;/strong&gt; Not skimmed. A review that only checks the diff statistics is not a review. If a diff is too large to read, the prompt was too large.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Deleting counts as progress.&lt;/strong&gt; We do not measure how much code appears, but how much complexity is left per problem solved. An assistant is excellent at finding duplicates and merging them — when you ask it to.&lt;/p&gt;
&lt;h2 id=&quot;what-this-changes&quot;&gt;What this changes&lt;/h2&gt;
&lt;p&gt;The developer’s role shifts from writing to deciding. That is harder, not easier: you have to know what a system should look like before you can describe it. Anyone who can do that gets a lever from AI unlike anything before it. Anyone who cannot gets a great deal of code very quickly.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Human intelligence steering the artificial kind. Not as a slogan, but as a division of labour.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;an-example-from-an-ordinary-week&quot;&gt;An example from an ordinary week&lt;/h2&gt;
&lt;p&gt;A client needed an import for a new data format. The first AI proposal: a new parser, a new data model, 600 lines. After ten minutes of architecture conversation: an 80-line adapter feeding the existing model. Both would have worked. Only one of them will still be understood by somebody in a year.&lt;/p&gt;</content:encoded><category>AI-Assisted Engineering</category><author>Robert Pupel</author></item><item><title>Kubernetes is not a goal</title><link>https://devs-group.ch/en/blog/kubernetes-is-not-a-goal/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/kubernetes-is-not-a-goal/</guid><description>When a cluster earns its keep, when a single VM is the better architecture, and how to tell the difference.</description><pubDate>Tue, 30 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Kubernetes is an excellent tool. It is also the most frequently misunderstood one. We regularly see clusters running three containers, maintained by two people, costing more than the application on top of them.&lt;/p&gt;
&lt;h2 id=&quot;the-honest-question-before-any-cluster&quot;&gt;The honest question before any cluster&lt;/h2&gt;
&lt;p&gt;Not “can we do Kubernetes?” but: &lt;strong&gt;what happens when this service falls over at three in the morning, and who looks at it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If the answer is “an alert, a runbook, a restart”, you do not need an orchestrator. If the answer is “twelve services have to come back in the right order, with rolling updates and zero downtime”, then you do.&lt;/p&gt;
&lt;h2 id=&quot;when-kubernetes-earns-its-keep&quot;&gt;When Kubernetes earns its keep&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Several teams deploy into the same environment independently of each other.&lt;/li&gt;
&lt;li&gt;Load spikes are unpredictable and scaling has to happen automatically.&lt;/li&gt;
&lt;li&gt;Compliance demands reproducible, declarative infrastructure with an audit trail.&lt;/li&gt;
&lt;li&gt;There are people who &lt;em&gt;operate&lt;/em&gt; the cluster, not only use it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;when-it-does-not&quot;&gt;When it does not&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;One application, one database, one team.&lt;/li&gt;
&lt;li&gt;Traffic is known and fits on a well-sized machine.&lt;/li&gt;
&lt;li&gt;Nobody on the team has time for upgrades, network policies and certificates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In those cases a VM with &lt;code&gt;systemd&lt;/code&gt;, a reverse proxy and a clean deploy script is not backward. It is the right size. If you want the decision backed by numbers, we have &lt;a href=&quot;https://devs-group.ch/en/blog/what-kubernetes-costs-in-switzerland/&quot;&gt;worked through what a cluster costs at Swiss providers&lt;/a&gt;, including the operational effort that appears on no price list. The numbers do not make the decision harder, they make it easier: they force you to name the goal before choosing the tool.&lt;/p&gt;
&lt;h2 id=&quot;what-we-build-first-instead&quot;&gt;What we build first instead&lt;/h2&gt;
&lt;p&gt;Cluster or VM, three things always come first:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;A deployment anyone can trigger.&lt;/strong&gt; One command, one result, no knowledge living in one person’s head.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Observability you actually read.&lt;/strong&gt; Few metrics, clear alerts, logs with context.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A rollback faster than the analysis.&lt;/strong&gt; Roll back, then understand.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A team with those three can grow from a VM into a cluster whenever it needs to. A team without them just has a bigger problem with Kubernetes.&lt;/p&gt;</content:encoded><category>Cloud &amp; Infrastructure</category><author>Ralph Segi</author></item><item><title>Decisions you can still understand in two years</title><link>https://devs-group.ch/en/blog/documenting-architecture-decisions/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/documenting-architecture-decisions/</guid><description>Architecture Decision Records are the cheapest tool against systems nobody can explain any more.</description><pubDate>Tue, 09 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every system older than a year has places where a new team member asks: “why is this like this?” And the honest answer is too often: “the only person who knows has left.” The remedy costs almost nothing: document architecture decisions at the moment they are made, with the context that forced them. Not as prose for posterity, but as a short, dated note with three things in it: what was decided, which alternatives were on the table, what tipped it.&lt;/p&gt;
&lt;h2 id=&quot;an-adr-is-a-page-not-a-document&quot;&gt;An ADR is a page, not a document&lt;/h2&gt;
&lt;p&gt;An Architecture Decision Record records &lt;em&gt;what&lt;/em&gt; was decided, &lt;em&gt;why&lt;/em&gt;, and &lt;em&gt;what you accept in exchange&lt;/em&gt;. It has four sections and fits on one screen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Context&lt;/strong&gt; – what was the situation? What constraints were there?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decision&lt;/strong&gt; – what are we doing?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alternatives&lt;/strong&gt; – what did we reject, and why?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consequences&lt;/strong&gt; – what becomes easier, what becomes harder?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It lives in the repository, next to the code it explains. Not in the wiki, not in a ticket, not in a Slack message.&lt;/p&gt;
&lt;h2 id=&quot;why-this-beats-any-documentation&quot;&gt;Why this beats any documentation&lt;/h2&gt;
&lt;p&gt;In practice the distinction is simple. Documentation describes how a system &lt;em&gt;is&lt;/em&gt;. It goes out of date the moment the system changes. An ADR describes why it &lt;em&gt;became&lt;/em&gt; that way. That never goes out of date — even when the decision is later reversed, the record stays true. You add a new one that references the old.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A system without a decision history is like a contract without a date. You can read it, but you cannot place it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;how-we-handle-it-day-to-day&quot;&gt;How we handle it day to day&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Every decision that is hard to reverse gets an ADR. Database choice, module boundaries, auth model: yes. Button colour: no.&lt;/li&gt;
&lt;li&gt;The ADR is written &lt;em&gt;before&lt;/em&gt; implementation and reviewed in the same pull request.&lt;/li&gt;
&lt;li&gt;Numbered, chronological, never deleted. A record is superseded by a new one, not by editing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The cost: twenty minutes per decision. The return: a team that after the third round of staff changes still knows why the system looks the way it does.&lt;/p&gt;</content:encoded><category>Architecture</category><author>Robert Pupel</author></item><item><title>Idempotency: the least conspicuous feature of a payments API</title><link>https://devs-group.ch/en/blog/idempotency-in-payment-apis/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/idempotency-in-payment-apis/</guid><description>Networks are unreliable and clients retry. Why an idempotency key is not a detail but the foundation.</description><pubDate>Tue, 19 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A request goes out and the response never comes back. Timeout. The client tries again. Has the payment now happened once, or twice? If an API cannot answer that question unambiguously, it is not fit for money. That holds for every payments API, whatever the provider and whatever the protocol.&lt;/p&gt;
&lt;h2 id=&quot;the-principle&quot;&gt;The principle&lt;/h2&gt;
&lt;p&gt;An endpoint is idempotent when the same request, repeated any number of times, has the same effect as making it once. For &lt;code&gt;GET&lt;/code&gt; that is a given. For &lt;code&gt;POST /payments&lt;/code&gt; you have to build it.&lt;/p&gt;
&lt;p&gt;The client sends a self-generated key with every write request:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;POST&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; /v1/payments&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Idempotency-Key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 8f1c2a4e-3b7d-4c9e-a1f0-5d6e7f8a9b0c&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server remembers the result of the first processing under that key. Every repetition with the same key gets the same response, without executing the payment again.&lt;/p&gt;
&lt;h2 id=&quot;what-often-gets-missed&quot;&gt;What often gets missed&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The key has to match the request.&lt;/strong&gt; The same key with a different body is an error, not a retry. We check a hash of the body and answer with &lt;code&gt;422&lt;/code&gt; when it differs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Concurrent retries.&lt;/strong&gt; Two identical requests arrive at the same moment. The key has to be reserved &lt;em&gt;before&lt;/em&gt; processing — an &lt;code&gt;INSERT ... ON CONFLICT DO NOTHING&lt;/code&gt; in the same transaction — otherwise both proceed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Expiry.&lt;/strong&gt; Keys do not live forever. Twenty-four hours is a good default; after that a repetition is a new request.&lt;/p&gt;
&lt;h2 id=&quot;a-fragment-in-go&quot;&gt;A fragment in Go&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;go&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Service&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CreatePayment&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; PaymentRequest&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Payment&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s.idem.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ctx, key, req.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Hash&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Payment&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s.payments.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Create&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ctx, req)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The whole of the logic — reserve, execute, store the result, return it on a repeat — lives in one package that every write endpoint uses. No endpoint decides for itself whether it is idempotent.&lt;/p&gt;
&lt;h2 id=&quot;why-this-is-the-foundation&quot;&gt;Why this is the foundation&lt;/h2&gt;
&lt;p&gt;Retries are not an error case, they are the normal case: mobile networks, load balancers, timeouts, queues with at-least-once delivery. An API that does not recognise repetitions as repetitions moves the problem into the customer’s accounting department. It is more expensive there.&lt;/p&gt;</content:encoded><category>Backend</category><author>Ralph Segi</author></item><item><title>Three dashboards instead of thirty</title><link>https://devs-group.ch/en/blog/observability-you-actually-read/</link><guid isPermaLink="true">https://devs-group.ch/en/blog/observability-you-actually-read/</guid><description>Observability rarely fails because of the tooling. It fails because nobody looks at it any more.</description><pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Almost every environment we take over has Grafana. And almost every one has dashboards nobody has opened in months. The problem is not too little observation, it is too much of it with no question behind it. Observability you actually read therefore starts not with the tool but with the question of which three statements about the system have to be answerable at any moment: is it running, is it getting slower, and where is it stuck right now. Anything that does not answer one of those three is decoration, however pretty the panel.&lt;/p&gt;
&lt;h2 id=&quot;every-dashboard-answers-exactly-one-question&quot;&gt;Every dashboard answers exactly one question&lt;/h2&gt;
&lt;p&gt;Before a panel exists, it has to be clear which decision it supports. “Is the service healthy?” is a question. “CPU usage across all pods” is not — it is a number.&lt;/p&gt;
&lt;p&gt;We almost always get by with three views:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Are users all right?&lt;/strong&gt; Error rate, latency at the edge, successful core actions per minute.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Is the system all right?&lt;/strong&gt; Saturation of the scarce resources: connections, queue length, memory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What changed recently?&lt;/strong&gt; Deployments, configuration changes, feature flags — as markers on the timeline.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Everything beyond that is diagnosis. Diagnosis belongs in ad-hoc queries, not on a dashboard.&lt;/p&gt;
&lt;h2 id=&quot;alerts-you-can-take-seriously&quot;&gt;Alerts you can take seriously&lt;/h2&gt;
&lt;p&gt;An alert that misfires weekly gets muted. At that point it is no longer an alert. Our rule: every alert has a name, a threshold derived from the promise made to users, and a runbook. If one of those is missing, the alert is deleted rather than extended.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An alert is a request for human attention. You send those sparingly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;logs-with-context-not-with-volume&quot;&gt;Logs with context, not with volume&lt;/h2&gt;
&lt;p&gt;Structured, with a request ID, with the &lt;em&gt;why&lt;/em&gt; rather than the &lt;em&gt;what&lt;/em&gt;. &lt;code&gt;payment declined: limit exceeded, limit=500 amount=620&lt;/code&gt; says everything. &lt;code&gt;error in handler&lt;/code&gt; says nothing, and it is nonetheless in every second log we see.&lt;/p&gt;
&lt;h2 id=&quot;what-is-left-at-the-end&quot;&gt;What is left at the end&lt;/h2&gt;
&lt;p&gt;Fewer panels, fewer alerts, fewer log lines — and a team that during an incident knows within thirty seconds where to look. That is observability. The rest is disk space.&lt;/p&gt;</content:encoded><category>Cloud &amp; Infrastructure</category><author>Ralph Segi</author></item></channel></rss>