# Changelog

Versions follow the service: `GET /v1/info` and the landing page report the same
number, and the client in
[python-code-validator](https://github.com/jkanselaar/python-code-validator) is
released under it, so a caller can say which validator it talked to.

## 1.19.0 — 2026-08-11

- When no single edit satisfies the caller's examples, the configured model may
  propose a whole rewrite — and it goes through the same gate as every other
  candidate: it must define what the original defined, and every example must
  pass when it is run, or it is discarded and the submitted code kept. The model
  proposes, the sandbox decides, and the fix says which of the two happened
  (`took the model's rewrite`, never just `repaired`).
- A call that repeats one the same key made in the last ten minutes — same
  mode, same code, same options — is answered from that answer and charged
  nothing (`x-msvc-repeat: 1`). The instruction file now tells agents to check
  their work at every step, and the answer to an unchanged question cannot
  differ, so charging for it again would sell nothing. Held in memory and per
  key: one caller is never answered from another's submission.
- Off by default (`MSVC_AI_ENABLED`), because it is a trade rather than a free
  improvement: on the 22 QuixBugs defects with gpt-4o-mini behind it, 13 are
  repaired instead of 9 (41% → 59%) and 4 instead of 2 come back satisfying the
  stated examples while failing the hidden inputs. Still 0 false alarms on the
  corrected programs.

## 1.18.0 — 2026-08-11

- Repair against the caller's examples tries three more kinds of edit, and
  fixes 9 of the 22 QuixBugs defects instead of 4 (18% → 41%), still with no
  false alarm on the corrected programs. All three are mistakes that leave a
  program running: a binary expression written the wrong way round
  (`result + alphabet[i]`), an index or bound that is one out (`range(0, r)`,
  `dp[i - 1, j]`), and a base case returned one step too empty (`[]` where
  `[[]]` or `[n]` was meant). Acceptance is unchanged — a candidate is returned
  only when every example passes when it is run.

## 1.17.1 — 2026-08-11

- `/v1/funnel` counts a key on the credit tier as paid even when it has no
  payment stamp: every key that paid so far predates the stamp, so the first
  reading of the number a deployment cares about would otherwise be zero.

## 1.17.0 — 2026-08-11

- `GET /v1/funnel` counts how far callers get — a keyless trial, then a key,
  then a payment — split by where they said they came from (`?src=` or an
  `x-client` header). Totals per tier cannot answer whether a listing or a post
  produced anything, because a thousand trials that never return look exactly
  like a thousand that do; a conversion is only countable at the moment a key is
  minted from an address that already spent a trial, which is where it is now
  recorded. Configured keys only: it says as much about the business as about
  the service.

## 1.16.0 — 2026-08-11

- `MSVC_TRIAL_DAILY` allows a handful of `static` validations with no key at
  all, counted per caller address. Minting a key is one POST, but an agent that
  just discovered the service will not make it: the call it is about to make is
  the one that has to work. When the trial runs out the refusal points at
  `POST /v1/keys`, which is a better moment to ask, because by then the caller
  has seen a real verdict. Off by default, and never for `repair` or `execute`:
  an address is not something a caller can be charged against.

## 1.15.0 — 2026-08-11

- `options.examples` lets the caller say what the code is supposed to do,
  because generated code almost never documents itself and the agent that asked
  for it always knows what it asked for. Doctest lines (`>>> total([1, 2])` /
  `3`) or plain assertions (`assert total([1, 2]) == 3`) are accepted; execute
  mode runs them in the sandbox and treats them exactly like examples found in
  the source, mismatch and single-token repair included. A failing example the
  caller supplied is reported without a line number, since it is not in the
  file.
- `make bench-supplied` scores the QuixBugs defects that way — examples taken
  out of the file and passed in the call — and gets the same 4 of 22 repaired,
  17 of 22 refused, 0 false alarms as when they are left in. The point of the
  measurement is that nothing is lost by the source being undocumented, which
  is the state generated code arrives in.

## 1.14.0 — 2026-08-11

- Execute mode runs the examples the code documents, and repairs against them.
  A `>>> f(127)` / `7` pair anywhere in a string literal is a statement of what
  the program is for, and it is the only thing a source-only validator can use
  to catch code that parses, lints, type-checks, runs — and returns the wrong
  answer. A broken promise is a `python:example-mismatch` error; when one
  fails, single-token variations of the source (an operator, a comparison, a
  small constant, two arguments in each other's place, one local read for
  another) are tried in a single sandbox run and one is returned only if every
  example then passes. Nothing is guessed: a search that finds nothing leaves
  the code alone and keeps the mismatch.
- `make bench-intent` scores that on the QuixBugs defects whose files carry
  examples: 4 of 22 repaired and 17 of 22 refused, against 0 of 29 without the
  examples, with 0 false alarms on the corrected programs. The hidden test
  inputs, not the examples, decide whether a repair was right, so satisfying
  the documented case alone counts as wrong.
- Hand-written examples are compared leniently about spacing: `[1,5,0,5,1]` and
  `[1, 5, 0, 5, 1]` are the same answer, and refusing correct code over the
  spacing in its own comment would teach callers to delete their examples.

## 1.13.1 — 2026-08-10

- The benchmarks grew a corpus this project did not write. `make bench-quixbugs`
  scores repair against 29 QuixBugs programs — real algorithm defects, each
  judged by running the result against the correct version's output — and the
  answer today is 0 of 29, with static validation flagging none of them either.
  That is the boundary of a source-only validator: those programs run fine and
  simply compute the wrong answer, and nothing in them says what the right one
  was.
- `make harvest` turns the live service's log of refused code into corpus
  cases: what the validator says about a real snippet today becomes what it must
  keep saying tomorrow. Prose (most of what the mech is sent), anything matching
  a credential pattern, and anything already in the corpus are skipped, and a
  cursor beside the corpus makes a second run add only what is new.

## 1.13.0 — 2026-08-10

- Execute mode works in a containerised deployment. Turning it on produced
  `internal_error: the docker CLI is not installed`, and mounting the socket
  would not have been enough: the service wrote the program to its own
  filesystem and asked the daemon to bind-mount that path, which the daemon
  resolves on the host, where it does not exist. The image now ships the Docker
  client, `make docker-up` adds the socket overlay when `MSVC_ALLOW_EXECUTE=1`,
  and the program is piped to the container on stdin so no path has to agree.
- An execute call is refused with `engine_unavailable`, before the key is
  charged, when the sandbox cannot be reached at all.

## 1.12.2 — 2026-08-10

- A mode the deployment cannot serve is refused before the key pays for it.
  Execute costs ten credits, and they were charged at the door while the
  handler refused the call a moment later, so a deployment with
  `MSVC_ALLOW_EXECUTE` unset billed for work it never did. All three entry
  points (HTTP, A2A, MCP) now check first; over MCP the refusal still reads as
  the tool answering, with its remedy, rather than as a transport error.

## 1.12.1 — 2026-08-10

- Repair gives a block header back the colon it lost without needing SMIRE.
  The fixer existed only in SMIRE, which is an optional dependency, so a
  deployment without it repaired 0 of 17 missing colons and the repair rate
  read 71% instead of 82% — a gap CI has been failing on since the benchmark
  gained its floor. `expected ':'` names the line, so the repair is the colon
  Python asks for, kept only when the result parses.

## 1.12.0 — 2026-08-10

- A key can be topped up from a wallet that has no data field. `POST
  /v1/keys/deposit` reserves an exact amount for a key — the price of the
  credits asked for, plus a random remainder below the price of one — and a
  transfer of exactly that many wei is credited to the key that reserved it.
  That is what the data field did, without the setting MetaMask hides and
  without the gas estimate that fails on a transfer carrying data to a Safe.
  Two open reservations never share an amount, a reservation lasts a day, and
  posting someone else's hash still credits the reserving key rather than the
  poster's.
- The same endpoint answers with the key's `deposit_data`, which until now was
  only visible in the reply to `POST /v1/keys`: a caller that lost it had no
  way to pay for the key it already had.

## 1.11.0 — 2026-08-10

- The MCP endpoint can be added by a client that will not ask its user for a
  key. Claude, VS Code and the ChatGPT connectors log in by opening a browser,
  and they find where to open it the way the MCP authorization spec says: a
  refused call now carries `resource_metadata` in `WWW-Authenticate`, pointing
  at `/.well-known/oauth-protected-resource`, which names this server as its
  own authorization server. The flow is OAuth 2.1 as those clients expect it —
  self-registration (RFC 7591) as a public client, authorization code with
  mandatory PKCE `S256`, refresh tokens, and resource indicators (RFC 8707) so
  a token issued for this server is refused at any other.
- Since there are no accounts here, the consent page asks the only question
  that is open: paste a key to connect an existing balance, or leave it empty
  for a new free one. The token names that key by its digest and is signed, so
  no grant stores anything that could be replayed as a key, and a call over
  OAuth is metered and charged exactly like the key it stands for.
- OAuth is on wherever the free tier is: a key store plus `MSVC_FREE_TIER_SIGNUP`.
  A deployment without them answers the metadata endpoints with a 404 rather
  than advertising a login it cannot complete.

## 1.10.0 — 2026-08-10

- The service now states what it will not change, and CI enforces it.
  `contract/v1.json` records every endpoint, MCP tool name, error code, enum
  member and wire field with its type and whether it is required; `make
  contract` fails on anything that takes something away — a removed or renamed
  endpoint, a field that changed type, an optional field that became required,
  a new required request field. Adding is free. What is deliberately *not*
  promised is written down too: which diagnostics fire, how `score` is scaled
  and what `fixed_code` returns are the product, and they will keep changing.
  See [docs/stability.md](docs/stability.md).
- Every response carries `x-service-version` and `x-api-version`, health checks
  and refusals included, so a caller can tell which build answered without a
  successful body to read it from. CI checks that the version being served is
  the newest changelog entry and that no released version disappears from the
  file.
- Availability is measured from outside the machine being measured:
  `tools/probe.py` gets a free key, submits a program that must be accepted and
  one that must be refused, and fails on a wrong verdict or a slow answer just
  as it fails on a refused connection. `.github/workflows/uptime.yml` runs it
  every fifteen minutes and opens an issue that closes itself when the service
  recovers.

## 1.9.0 — 2026-08-10

- Static validation now catches eight kinds of mistake that lint clean,
  type-check clean and raise anyway: the result of a function that returns
  nothing used as a value (`matches = collect(rows)`), a constant index or key
  a literal does not have, a loop counting past the end of a literal it walks,
  a division by a zero the file wrote down itself, `self.total` in a class that
  never assigns `total`, a dict iterated as if it yielded pairs, and an
  unpacking whose two sides are written out at different lengths. Ruff's
  format-call rules (`F501`-`F509`, `F521`-`F525`) are reported as errors
  rather than lint: a `.format` call short of arguments raises when it is
  reached.
- Each of these only fires where the source decides the answer in full: a list
  handed to a function may have grown, a class with a base may inherit the
  attribute, and `range(len(values))` is the correct spelling. mypy's
  `attr-defined` stays a warning for the same reason — with dependencies not
  installed it fires on working code.
- `make bench-runtime` puts a number on it: twenty programs that crash when run,
  scored on how many are refused *without* running them, against the twenty
  working programs as a control. 16 of 20 caught, 14 of those by a check no
  linter makes, 0 false alarms. A single false alarm fails the run.

## 1.8.0 — 2026-08-10

- Repair acts on the mistakes it used to only report. `make bench-repair` went
  from 56 of 173 broken programs (32%) to 125 of 152 (82%), each one verified by
  running the result and comparing its output to the original's. New fixers:
  the bracket or quote left open (closed where the interpreter says it opens,
  not at the end of the file), a misspelled name read as the one that is defined
  nearby, a forgotten standard-library import, the method mypy names in
  `maybe "append"?`, a line indented where Python rejects it, a quoted number a
  call requires as a number, and the answer a function computed and dropped —
  which runs before the lint pass, because a linter's answer to a dead
  assignment is to delete the computation.
- Every rewrite is kept only when the code still parses and the checkers report
  strictly less than before, so no fix trades one mistake for another; where the
  intent is not decidable — a name resembling nothing defined, a dependency that
  is not installed, a string that was meant to be a string — nothing changes.
- The benchmark counts a truncated generation as undecidable: cutting the last
  lines removes statements, and no analysis recovers a `print` never written.
  Those cases still run, to prove the service does not make them worse.

## 1.7.4 — 2026-08-09

- `tools/mech_report.py` answers in about five seconds instead of twelve, and
  two of its four RPC endpoints work again: publicnode and drpc answer 403 to
  urllib's default agent string, so the report now names itself. The week of
  `eth_getLogs` chunks and the IPFS verdict fetches are done concurrently.
- The payment verifier sends the same header, so a top-up no longer depends on
  which public endpoint is lenient today.

## 1.7.3 — 2026-08-09

- A static call is roughly ten times faster: bandit ran in a fresh interpreter
  per request, where loading its plugins cost more than every check combined,
  and ruff was invoked as `python -m ruff` rather than the binary next to it.
  Bandit only parses what it scans, so it now runs in the worker process, with
  its configuration loaded once. Locally 140 ms became 16 ms.

## 1.7.2 — 2026-08-09

- Every refusal has the same shape. A malformed body used to come back as
  FastAPI's own 422 — a list of pydantic errors with no error code and no
  request id — so a caller that handles our errors had to special-case it. It is
  now `400 bad_request` in the standard envelope, with the per-field breakdown
  kept under `fields`.
- The agent card also answers at `/.well-known/agent-card.json`, the name A2A
  0.3 gave it; the old path stays.
- `/llms.txt` says what the server is and where the entrances are, for a model
  that lands on the domain and will not read a hundred kilobytes of OpenAPI.
- Tool descriptions: dropped the sentence that repeated "nothing is executed",
  moved the payment details behind `GET /v1/pricing`, and said the parameter
  edges the schema cannot — empty code is 400, oversized is 413, unknown
  options are ignored rather than refused, and code that does not parse is
  answered rather than refused.

## 1.7.1 — 2026-08-09

- A payment names the key it pays for. A transaction hash is public the moment
  it is mined, so a transfer that was bound to nothing could be redeemed by
  whoever posted it first — including a bystander watching the Safe. `POST
  /v1/keys` now returns `deposit_data` (the SHA-256 of the key) alongside the
  key, the transfer has to carry it in its data field, and the top-up names the
  key it credits. The hash gives the key away no more than a password hash does.
  A deployment that sells therefore hands out keys even with the free tier off,
  since without a key there is nothing to address a payment to; such a key has
  no free calls and answers 402 until it is paid for.

## 1.7.0 — 2026-08-09

- Callers can buy calls without asking anyone. Paid access meant a key the
  operator handed out by hand, which an agent cannot obtain, so all HTTP traffic
  was either free-tier or the operator's own. A caller now sends xDAI to the
  deployment's Safe and posts the transaction hash to `POST /v1/keys/topup`; the
  service reads the transfer off the chain — right address, right chain, receipt
  succeeded, confirmed, never redeemed before — and returns a key with credits
  on it. Priced per mode (static 1, repair 3, execute 10) because the modes cost
  wildly different amounts, and every refusal that money would fix now carries
  the address, the price and the endpoint. See `docs/payments.md`.

## 1.6.7 — 2026-08-09

- Each tool names the tools to use instead of it. Three tools that all take
  Python and all return a verdict read as interchangeable, and `execute_python`
  is the expensive way to learn what `validate_python` would have said. The
  alternatives are built from the modes a deployment actually serves, so one
  without a sandbox does not point at a tool that is missing from its list, and
  the descriptions lost the repetition that came with saying it by hand.

## 1.6.6 — 2026-08-09

- Repair carries a `None` check to the place the value is used. The pattern
  under most of the remaining type errors is a value checked once and read
  twice — `if row["draft"] is None: continue` followed by
  `float(row["draft"])` — where the second read is unchecked by the type
  checker and by the reader. The value is now named at the guard and the use
  reads that name. An `or` chain (`if i >= len(xs) or xs[i] is None: continue`)
  is first written out as the guards it stands for, because reading `xs[i]`
  above that chain is what the `or` prevents. Guards with a call in them are
  left alone, and a rewrite is kept only when both checkers report strictly
  fewer problems.

## 1.6.5 — 2026-08-09

- Repair applies ruff's own fixes instead of only reporting them. Validating
  four of our own repositories rejected 16 files, and more than half of the
  findings were an unused import or an unused local — which ruff already knows
  how to remove, and which the service was reporting for the caller to fix by
  hand. Two passes: ruff's safe fixes, then its unsafe fix for `F841`, which
  keeps the right-hand side as a statement because `x = f()` may be there for
  what `f()` does. A pass is kept only when the result parses and its
  diagnostics are a strict subset of the ones it started with.

## 1.6.4 — 2026-08-09

- The tool descriptions say what the schema cannot: which option does anything
  in which tool. Every tool takes the same `options` object, so an agent reading
  only the schema sets `timeout_s` on a call that never runs anything, or waits
  for `expected_output` to be checked where there is no output. Each description
  now names the knobs that act, their ranges and defaults, the limits of *this*
  deployment (`timeout_s` is capped below the 60 the schema allows, and a larger
  value is refused), and the interactions that only show at runtime — execute
  runs the *repaired* source, and `fixed_code: null` means "no fix", not an
  error. The schema's own field descriptions were filled in to match.

## 1.6.3 — 2026-08-09

- Code cut off inside a string is read as code again. 1.6.2 closed the brackets
  an interrupted generation left open but never the quote, so `name = "bob` —
  the most common way a truncated generation ends — was refused, and 1.6.1 had
  accepted it. The open quote and the placeholder brace of an f-string are now
  closed too, `def f(:` is read as the header it meant to be, and an assignment
  or a keyword left dangling (`from os import `, `del `) counts on its own.
- What parses but says nothing is refused, wherever it comes from. `yes` was
  already refused, but `yes.` and `yes,` were completed into an attribute and a
  tuple and let through — the test now applies to the completed program as well,
  so a one-word answer with punctuation on it is not a sample either. English
  ending in a colon (`for each item in the list:`) no longer passes for a block
  header: the header has to parse with a body under it.
- Text that is *valid* Python is still treated as code, even when it reads like
  a sentence (`note(this is important)` is a call). Keeping prose out of the
  corpus is the point; another language slipping in costs far less than refusing
  a program.

## 1.6.2 — 2026-08-09

- The Python/prose test no longer refuses code that was cut off mid-thought.
  1.6.1 looked for a line shaped like a program, which missed exactly what a
  truncated generation produces (`compute(1,`, `for x in y`, `1 +`) and still
  accepted English that opens with a keyword ("return the probability as a
  decimal"). The text is now Python when some small completion of it parses:
  closing the brackets left open, finishing a header with its colon, giving a
  trailing operator an operand, wrapping a bare `return` in a function. A single
  bare word ("yes") parses but is not a program, so it is refused.

## 1.6.1 — 2026-08-09

- The Mech tool answers only Python. The marketplace routes work by price and
  availability, not by tool, so prediction-market prompts arrived here and were
  told they had invalid syntax; four out of five collected training samples were
  English rather than code. A prompt with no line shaped like a program is now
  refused, and the training log and the export drop it too. Code that does not
  parse is still collected — that is what the corpus is for.

## 1.6.0 — 2026-08-09

- Repair mode now fixes two of the mistakes 1.4.0 only reported: a method
  written without `self` gets it, and a loop that changes what it walks is given
  a copy to walk (`for value in list(values):`). Both are verified before they
  are kept — the result has to parse and the diagnostic has to be gone — and a
  method the class itself calls as `Counter.bump()` is left alone, because
  adding a parameter there breaks the call that works today.
- Every deterministic fix is also a verified broken/fixed pair in the training
  export, so this widens the corpus the nightly loop learns from.

## 1.5.0 — 2026-08-09

- The CI client can apply what it reports: `--write` asks for repair mode and
  writes the repaired source back over the file, as the `python-code-repair`
  pre-commit hook or `write: true` on the action. A rewritten file fails the run
  even when the result is clean, because what is staged is no longer what is on
  disk.
- A refused call now shows the service's `remedy` hint in the job log instead of
  the raw JSON body, so "402" is not where the trail ends.

## 1.4.1 — 2026-08-09

- An MCP tool call that is refused after the key checks out — a free key asking
  for `repair_python` or `execute_python` — now carries the same `remedy` object
  the HTTP and A2A surfaces return, and repeats it in the text for clients that
  render nothing else. Only the key and quota checks used to answer that way, so
  a paid-tier refusal told an agent it had failed without telling it what to do.

## 1.4.0 — 2026-08-09

- Six classes of mistake that ruff and mypy both accept are now reported, with
  the line and column of the offending expression: a coroutine call nobody
  awaits, a method that takes no `self`, an `except Exception: pass`, code after
  a `return`, a file opened and never closed, and a collection changed while it
  is being iterated. The first, the second and the last are errors — the program
  is wrong, not untidy.
- `repair` mode inserts the missing `await`, verified: the rewrite is kept only
  when the file still parses and the diagnostic is gone, so a coroutine called
  from a synchronous function is reported and left alone rather than broken.

## 1.3.0 — 2026-08-09

- MCP lists three tools instead of one: `validate_python`, `repair_python` and
  `execute_python`. A model picks tools by name, so the name now says what
  happens — the mode is no longer an argument it can get wrong in either
  direction, and only `execute_python` is annotated as running code. The old
  `python_code_validator` tool, with its `mode` argument, still answers a
  `tools/call` for clients that already configured it, but is no longer listed.

## 1.2.5 — 2026-08-09

- The image installs with `uv` instead of `pip`: the install step drops from 9s
  to 2s and a full build from 15s to 9s, on every deploy. The binary is mounted
  during the build rather than copied in, and no `.pyc` is shipped, so the image
  is 22 MB smaller than before. Nothing at runtime changes.

## 1.2.4 — 2026-08-08

- The URLs in a refusal and in the agent card came out as `http://…` on the
  hosted deployment: the proxy terminates TLS and forwards to plain HTTP, so the
  app never saw the scheme its callers use. An agent that followed the key
  endpoint from a `401` therefore left `https`. `MSVC_PUBLIC_URL` now sets the
  address callers are pointed at, and the container trusts `X-Forwarded-Proto`
  when it is not set.

## 1.2.3 — 2026-08-08

An agent that is refused has no operator to ask, so a bare error ended the
attempt and the free tier stayed unused. Every refusal now carries the way out.

- `401`, `402` and `429` answer with a `remedy`: the endpoint that mints a key,
  the modes a free key covers, or the moment the quota resets. MCP and A2A carry
  the same object in `error.data.remedy`. A `429` now also carries
  `x-quota-reset`, which the remedy refers to.
- The agent card states its security scheme and an `x-onboarding` block naming
  `POST /v1/keys`, so a caller can get from the card to a working call on its
  own. A deployment that hands out no keys says so instead.
- The landing page prints the `mechx request --priority-mech` command when
  `MSVC_MECH_ADDRESS` is set: a marketplace request goes to the mech the caller
  names, so the address has to be where a caller looks.

## 1.2.2 — 2026-08-08

- A refused `/mcp` call answers the caller's own JSON-RPC `id` instead of
  `null`. A client matches responses to the message it sent, so an unaddressed
  error left it waiting until it timed out rather than showing "needs a key".
- A 401 carries `WWW-Authenticate: Bearer ...`, pointing at `POST /v1/keys`.

## 1.2.1 — 2026-08-08

Directories and clients read a server before anyone has a key, and this one
refused them, so it listed as unhealthy and unusable.

- `POST /mcp` answers `initialize`, `ping` and `tools/list` without a key. A
  key is still required for `tools/call`, which is the only method that does
  work and the only one that spends quota.
- The MCP tool now says what a call does to the world: what each mode is, that
  `execute` runs the code in a sandbox, that `repair` and `execute` need a paid
  key while the free tier covers `static` at its daily cap, and what the verdict
  contains. It ships an `outputSchema` and the standard annotations, so a client
  can see the tool is not read-only before calling it.

## 1.2.0 — 2026-08-08

The verdict now covers the mistake that survives every other check: code that
parses, lints and scans clean, and still raises the moment it runs.

### Verdicts

- `static` type-checks the submitted code with mypy. `totaal / "4"` is a
  `mypy:operator` error and makes the verdict invalid; codes that mean "this
  will raise" (`operator`, `arg-type`, `call-arg`, `return-value`, `index`,
  `call-overload`, `func-returns-value`, `valid-type`) are errors, everything
  else mypy says is a warning and does not reject the code. Missing stubs and
  uninstalled imports are ignored: the snippet arrives alone.
- Bodies of unannotated functions are checked too (`--check-untyped-defs`),
  which is where generated code keeps its mistakes.
- The check runs in-process against a cache that is warmed at startup, so a
  validation costs about 15 ms more, not the half second a cold mypy takes.

### Repairs

- `repair` types a number written as a string: a name bound once to `"4"` and
  then used in arithmetic becomes `4`, keeping the rest of the line. The
  rewrite is kept only when the result parses and the type error is gone, so a
  string that is meant to be a string is left alone.

## 1.1.0 — 2026-08-07

Everything that turned the 1.0.0 prototype into a service other people use:
adoption paths, a paid channel, capacity, and a feedback loop that keeps the
model improving.

### Reaching callers

- Self-service free tier: `POST /v1/keys` returns a key with a daily allowance
  of `static` validations, no account and no payment.
- MCP over HTTP at `/mcp`, plus a stdio bridge for clients and sandboxes that
  cannot reach a remote server; published in the MCP registry.
- `GET /v1/client` serves the CI client, so a repository adopts the checks
  without needing access to this one; a GitHub Action and a pre-commit hook
  ship in the public client repository.
- Sold as an Olas Mech on Gnosis (service 3783, agent 4107) at 0.01 xDAI per
  request. The tool now refuses to sell the fallback check silently: a delivery
  answered without the validator is marked `degraded`, with the time it
  happened.

### Verdicts

- Calls hidden behind a dynamic import or a runtime attribute lookup are
  resolved before the security policy runs, so `getattr(os, "system")("id")` is
  caught like `os.system("id")`.
- The repair loop can ask a local StateMind model when no deterministic fixer
  applies, and reports what the model contributed.
- The parse-failure rule no longer varies with the Python version.

### Running it

- `MSVC_WORKERS` uvicorn workers, three by default on a four-core host: one
  worker does ~9 validations/s and further concurrency only queues, three do
  ~24/s. The key store, the usage totals and the training log take an `flock`,
  so workers cannot lose each other's updates.
- Every worker beats on its own timer, so an idle worker keeps its row and the
  reported worker count no longer wanders on quiet traffic.
- `GET /v1/stats` reports uptime, workers, calls in progress and totals per
  tier, surviving a restart when `MSVC_STATS` points at a volume. The landing
  page shows only the dot next to the title; the numbers stay in the endpoint,
  for operators.
- The sandbox and the security policy were hardened, and the example
  environment no longer disables authentication.

### Learning from traffic

- Every rejected snippet is recorded (`MSVC_TRAINING_LOG`, with rotation) and
  served from `GET /v1/training-data?since=<cursor>` to an authenticated
  caller.
- A nightly loop on the GPU machine pulls what is new, turns verified
  broken→repaired pairs into StateMind repair tasks, retrains, and promotes the
  candidate only when it scores better than the model in production.

## 1.0.0 — 2026-08-05

First release: `POST /v1/validate` with `static`, `repair` and `execute` modes,
syntax and lint diagnostics, security scanning (bandit, AST policy, credential
scan), deterministic auto-fixes, sandboxed execution, the A2A adapter and the
agent card.
