# 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.29.0 — 2026-08-16

- The onchain identity can now be judged and not only found: `GET /v1/reputation`
  reads the ERC-8004 reputation registry for this deployment's agent and answers
  the aggregate — how many clients rated it, over how many entries, at what
  value — together with the exact `giveFeedback(...)` call that adds a rating.
  The average is read over the addresses that already gave feedback, as the
  registry requires, so it is not a number anyone with gas can move, and no
  feedback reads as no rating rather than as a bad one. An RPC that will not
  answer still leaves the instructions. Read where it matters: the endpoint is
  linked from the price quote, from `llms.txt` and from the registration file:
  [docs/erc-8004.md](docs/erc-8004.md).

## 1.28.0 — 2026-08-16

- `GET /v1/funnel` now reports the callers that never make a key. Paying per
  call is the route an agent walks unattended, and it was the one route with no
  numbers on it: the endpoint gains an `x402` section counting quotes, refused
  payments, settlements and answers given again, split by transport and mode,
  plus how many payers came back. Payers are counted by digest, so "returning"
  is answerable without recording who paid. It reads from the stats file, so it
  needs `MSVC_STATS`: [docs/adoption.md](docs/adoption.md).

## 1.27.0 — 2026-08-16

- The service can be found as an agent, and not only as a URL:
  `/.well-known/agent-registration.json` serves the ERC-8004 registration file
  an `agentURI` resolves to, listing the web, A2A and MCP endpoints, whether
  payment per call over x402 works, and `reputation` as the one trust model this
  deployment can stand behind. The registry entry is an ERC-721 with a
  reputation registry beside it, so it is both somewhere an agent that never
  heard of this service can look and somewhere callers can leave feedback a
  later caller weighs. The mint itself is a funded transaction, taken by an
  owner and not by this process: [docs/erc-8004.md](docs/erc-8004.md), after
  which `MSVC_ERC8004_AGENT_ID` makes the file claim the identity. Until then it
  claims none — an id this deployment does not own would point a caller at
  somebody else's agent.

## 1.26.0 — 2026-08-16

- The mech sells the sandbox now: `python_code_execute` is a third tool name, so
  a requester agent can buy a run against its own examples the way it already
  buys a check (`python_code_validator`, `static`) or a repair
  (`python_code_repair`, `repair`). It is the one tool that does not fall back —
  the fallback runs nothing, and a verdict reached without running the code is
  not what was bought, so an unreachable service is reported as "the code was not
  run" instead of as a degraded answer. Buyable only once the on-chain metadata
  lists the name: see [docs/olas-mech.md](docs/olas-mech.md).

## 1.25.2 — 2026-08-16

- The MCP tools reach the catalogue under a URL that can be called. A catalogue
  appends the tool name to the resource URL itself, and the URL carried it too,
  so CDP's index recorded `…/mcp#repair_python#repair_python`. The resource is
  `…/mcp` now, and the name stays where a catalogue reads it: in the entry.

## 1.25.1 — 2026-08-16

- Base mainnet payments are signable now. The EIP-712 domain quoted for mainnet
  USDC said `USDC`, but the contract's `name()` is `USD Coin` — and the domain
  has to repeat it exactly or the signature recovers to nobody, so every mainnet
  payment would have been refused as an invalid payload while Base Sepolia, whose
  USDC really is named `USDC`, worked. Both names are now what the contracts
  return, checked against each chain's own `DOMAIN_SEPARATOR()`.

## 1.25.0 — 2026-08-16

- The service can now talk to Coinbase's x402 facilitator, which is the only one
  that settles Base mainnet and the only one that catalogues resources in the
  Bazaar an agent searches. It authenticates with a JWT signed per request rather
  than a standing token, so `MSVC_CDP_API_KEY_ID` and `MSVC_CDP_API_KEY_SECRET`
  hold the key itself and each call to `verify` or `settle` carries a token good
  for that one method and path for two minutes. Either shape the portal hands out
  works — base64 Ed25519 or an EC key in PEM, newlines escaped or not — and a key
  that cannot be read refuses the call instead of falling back to no
  authentication. The key signs no payment and holds no funds: the money still
  moves on the caller's signature, straight to the operator's address.

## 1.24.1 — 2026-08-16

- Every payment now carries what a catalogue indexes. A facilitator learns of a
  resource from the payments it processes, and the spec has the *caller* copy the
  resource description and the `bazaar` entry across from the 402 — which callers
  do not do, so both were reaching the facilitator empty and the service was
  earning without ever appearing in the Bazaar. They are filled in on the way to
  `verify` and `settle` now, from the same description the 402 carries, without
  overwriting anything the caller sent and without touching the authorization it
  signed. Over MCP the entry is keyed by tool name, because one URL serves three
  tools and the URL alone would collapse them into one.

## 1.24.0 — 2026-08-16

- The same x402 payment now works over MCP, where the agents are: a `tools/call`
  the caller cannot pay for with a key is answered by the *tool* — a result with
  `isError` whose `structuredContent` holds the price, the asset, the network,
  the address and, in its `bazaar` extension, the tool name, transport, argument
  schema and an example call — and the same call repeated with the signed payment
  in `_meta["x402/payment"]` is served, its receipt coming back in
  `_meta["x402/payment-response"]`. MCP has no headers, so this is where the two
  objects have to travel. Verified before the work and settled after it, per
  payer, with no second settlement for an answer already given, exactly as on the
  HTTP routes. Keys, credits and the free tier are untouched: the free tier still
  answers first, and the payment is what the refusal after it offers instead of
  telling an agent to go and buy credits.

## 1.23.0 — 2026-08-16

- A caller can pay for one call over [x402](https://docs.x402.org) and hold
  nothing: `POST /v1/x402/{static,repair,execute}` without a key answers `402`
  with a `PAYMENT-REQUIRED` header saying what it costs, in which asset, on which
  network and to which address, and the same request repeated with a
  `PAYMENT-SIGNATURE` header is served. No key, no account, no page to open, and
  no balance to keep — the routes that need one all still work, but this is the
  only one a caller nobody told about this service can walk. Priced off the same
  table as credits ($0.01, $0.03, $0.10 by default), verified before the work and
  settled after it, so a call the service refuses costs the caller nothing and an
  answer that payer already has leaves its signature unspent.
- That `402` carries the request schema and an example answer in its `bazaar`
  extension, which is what makes the endpoint callable by something that found it
  in a catalogue rather than in the documentation — `options.examples` included,
  since a check bought without saying what the code is for is the weakest check
  on offer.
- Off until `MSVC_X402_PAY_TO` is set, and off on any network whose asset is not
  spelled out: the USDC contract and EIP-712 domain ship for Base and Base
  Sepolia only, because an asset guessed here is a payment the caller signs for
  the wrong contract.

## 1.22.1 — 2026-08-16

- The front page says what the 41% is measured against: `ruff` and `mypy` flag
  the defect in none of the 22 documented QuixBugs cases, which is what makes the
  number mean anything, and links the published figures.

## 1.22.0 — 2026-08-16

- The CI client keeps its free key between runs (`VALIDATOR_KEY_FILE`, which the
  action fills from the workflow cache, one key per repository per day). Minting
  a key per run started every run at zero, so the daily allowance was never
  reached and a repository running the checks all day never saw that there was
  anything to buy. A kept key the service no longer knows is replaced once,
  silently.
- The action summarises its run in one comment on the pull request, edited in
  place on later pushes: what was accepted, what was repaired and what is left of
  the allowance. Annotations vanish as soon as the diff changes, so nothing about
  the service accumulated where a reviewer looks. It needs
  `pull-requests: write` and writes nothing without it, failing no job over it.

## 1.21.0 — 2026-08-12

- The free tier is a tier again. A key costs nothing, so a per-key allowance was
  advisory: a caller that spent one minted the next, and no limit anywhere was
  ever reached. `POST /v1/keys` is now metered per address as well
  (`MSVC_FREE_TIER_KEYS_PER_CALLER`, three a day), and the refusal carries
  `buy_credits` instead of "try again tomorrow". The address is stored as a
  fingerprint, like everything else in the key store.
- The daily allowance of a free key is 25 static checks rather than 100: enough
  to try the service and to check a small project, not enough to run on.

## 1.20.4 — 2026-08-12

- `GET /v1/funnel` reports `by_route`: how the paying keys paid, card or wallet.
  Both routes credited the same counter, so the numbers could not say whether the
  card was worth having. The route is recorded at the first payment of a key, and
  a key that paid before this existed counts as `unknown`.

## 1.20.3 — 2026-08-12

- A card payment that settles after the page is credited too. Stripe reports a
  method that clears out of band — iDEAL, SEPA, a bank app finishing later —
  as an `unpaid` session first and sends the money in
  `checkout.session.async_payment_succeeded`, which the webhook ignored: the
  payment would have been taken and never become credits. Both event types now
  credit, and only a session Stripe calls `paid` does.

## 1.20.2 — 2026-08-12

- The Stripe calls now go out on API version 2025-03-31.basil. Managed Payments
  is refused on anything older, and pinning a version older than the account is
  what a payment path does to survive an upgrade — so the pin moved forward
  rather than away.

## 1.20.1 — 2026-08-12

- A card payment now names what it sells in Stripe's tax vocabulary
  (`txcd_10103001`, software as a service). Without it Stripe refuses the
  session outright on any account with Managed Payments, which is on by default,
  so the first live checkout answered `card_unavailable`; the code also decides
  the VAT rate, and is settable with `MSVC_STRIPE_TAX_CODE`.

## 1.20.0 — 2026-08-12

- Credits can be bought with a card: `POST /v1/keys/checkout` answers with a
  Stripe Checkout page and a signed `checkout.session.completed` webhook puts the
  credits on the key. The wallet route is the only one an agent can walk by
  itself, and the one the person configuring that agent stops at — a team with a
  budget holds a card, not xDAI. Nothing about it is manual on this side: no
  invoice, no account to approve, and Stripe issues the receipt.
- What travels to Stripe is the key's fingerprint, not the key, and only a
  signed event within Stripe's five-minute window credits anything, so neither a
  forged nor a replayed event buys calls. A session that was already credited is
  answered as settled, because an endpoint that errors is one Stripe retries for
  days.
- A refusal now names whichever routes are configured, card first; a deployment
  with only a card sells through `/v1/pricing` and `/v1/keys/checkout` without a
  wallet configured at all.

## 1.19.3 — 2026-08-11

- The page and `GET /icon.svg` now carry the StateMind mark instead of the
  placeholder box, and the status dot moved from in front of the name to behind
  `API v1`, where it reads as a status rather than as bullet punctuation.

## 1.19.2 — 2026-08-11

- The MCP handshake now says who is answering: a display title, the site and an
  icon (`GET /icon.svg`, drawn by the server), plus `instructions` — what a
  client should know before its first call, which is where `options.examples`
  and the free repeat belong. Directories score a server on exactly these
  fields and show a slug and a placeholder without them, and every listing that
  has to be filled in by hand goes stale; this is answered from the spec
  instead.

## 1.19.1 — 2026-08-11

- The front door now sells what the service actually does. The description, the
  landing page, `llms.txt` and the tag list led with lint, security and SMIRE
  transpilation — the pitch from before intent checking existed — so an agent or
  a human landing on the domain read "another linter" and never learned about
  `options.examples`, the QuixBugs numbers or the free repeat. All of it is one
  text, and the registry listing already said this; the server did not.

## 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.
