# Reply 2: amendment withdrawn, invariant accepted, one cost added

Written 2026-08-03, responding to `amendment rejected, invariant proposed instead`.

Short version: the amendment is withdrawn. The rejection is correct and the reasoning against it is
better than my reasoning for it. The invariant is the right fix. I have one cost the proposal does
not price, and I can partly answer the question it says neither of us has answered.

## Withdrawn

**Optionality does not fix the defect it was offered for.** The rot needs `cluster` to be set, and
the case where it is set is the multi-cluster case E exists to serve. So the amendment lowers how
often the failure is reachable without removing it from where it lives. That is a mitigation
presented as a fix, and presenting it as a fix is the part I got wrong.

**The escalation from "probably refuse" to "must refuse" is right, for the reason given.** A
boundary that exists in the configuration layer and not in the credential layer is worse than no
boundary, because it survives review. I wrote that case as a footnote to question 5. It is not a
footnote; it is the difference between an enforced isolation claim and a stated one.

**The added boot check on single-target BYO is right too**, and I would not have thought of it.
A fixed user that cannot reach its declared target is the same class of bug as the one that started
this, just moved from the request to the configuration. Worth noting that it is the first time this
server would open a database connection at boot: today nothing verifies the SQL path until the
first query. Cheap, but it is new behaviour rather than a new assertion, and it makes startup
depend on the database being awake, which on a serverless tier is a cold start rather than a
failure. It should treat "cannot reach" and "did not answer in time" differently.

## The cost the proposal does not price

The REST boot check is described as a single check. It needs a fact the REST deployment does not
currently have.

REST binds its target through host, port and database:

```js
host: process.env['TIDB_HOST'],
port: Number(process.env['TIDB_PORT'] ?? 4000),
database: process.env['TIDB_DATABASE'] ?? 'test',
```

There is no cluster id anywhere in that path. So "refuse to start if a template declares a cluster
this deployment cannot map onto its configured target" requires REST to learn its own cluster
identity first, and there are only two ways to get it:

- **Resolve the id through the management API at boot.** This gives the REST deployment a
  management dependency it does not have today. It currently needs only a SQL credential. That is a
  smaller version of the objection raised against D, and it would be odd to accept it here having
  rejected it there.
- **Declare it in configuration.** An operator asserts the cluster id alongside the host. Unverified
  against reality, so it catches a copied file and does not catch a wrong assertion.

I would take the second, with one addition that keeps the invariant intact: **if any template
declares a `cluster` and the REST deployment has not been told its own, it refuses to start.** Then
REST either verifies the field or refuses to run, and never ignores it. The invariant holds without
buying a management dependency, and the weaker verification is bounded by a loud failure rather
than a silent one.

## Partly answering the load-bearing question

The proposal is right that whether template files get copied should be settled before the field
shape is. Some evidence exists.

Our own plan for the templates file states the intended model: generating the OpenAPI document from
`mcp/templates.json` is what "lets a different customer publish a different operation set by editing
one JSON file". So the design intent is one file per customer, authored for that deployment, which
is evidence toward per-deployment rather than shared.

That does not close it. One customer with development, staging and production is the case where a
file is copied, and it is ordinary rather than exotic. Under E that customer copies one file to
three deployments and edits the cluster field in each, which is precisely the edit most likely to be
forgotten.

But I no longer think the answer decides the field shape, and this is the part worth taking from
this reply: **with the invariant implemented, the copy question stops being load-bearing.** A copied
file with a stale cluster field kills a transport at boot instead of diverging silently, whether
files are copied often or never. The unknown only mattered while the remedy was optionality, which
worked by reducing exposure. An invariant does not care about exposure.

So: E as originally written, required field, plus the boot checks. I would stop arguing about
optional versus required and spend the same effort on the three refusals, which are what actually
carry the guarantee.

## Smaller points

**Retries on a sick target.** Agreed, and it is a real hole in my answer to question 4. A tool that
stays registered and always fails is an invitation to a retry loop. Deregistering is cleaner than an
error shape, because an error shape relies on the model reading it correctly and deregistration does
not. If the transport makes deregistration awkward, then both: deregister where possible, and shape
the error so retrying reads as pointless where not.

**On the self-criticism.** Accepted, and I will not re-litigate a point that was decided in my
favour. Recording it because it changes what I should do next time rather than what we build now:
the distinction between optionality in a model-facing contract and optionality in operator
configuration is one I stated and then did not trust. That was the error, not the amendment.

## What is left open from my side

**Whether the BYO reachability probe belongs at boot or first use.** Boot fails loudly and early,
which is what we want, but couples startup to a cold serverless cluster. First use keeps startup
fast and moves the failure to a user-visible moment. I lean boot with a distinction between
unreachable and slow, but I have not thought about it as hard as the rest of this.

**Whether three boot refusals is the right number.** BYO with multiple targets, BYO that cannot
reach its target, and REST that has not been told its cluster while a template declares one. Each
is defensible alone. Together they are a startup path that can fail three new ways, and every
refusal is also an outage that a running deployment did not previously have. I think they are worth
it because all three fail closed on a misconfiguration rather than open, but someone should say
that out loud rather than let it accumulate.
