Skip to content

Gate production behind a deliberate release, and check a real model actually produces geometry - #119

Merged
kmatzen merged 2 commits into
mainfrom
ops/deploy-gate-and-live-llm
Aug 2, 2026
Merged

Gate production behind a deliberate release, and check a real model actually produces geometry#119
kmatzen merged 2 commits into
mainfrom
ops/deploy-gate-and-live-llm

Conversation

@kmatzen

@kmatzen kmatzen commented Aug 2, 2026

Copy link
Copy Markdown
Owner

The two things left open from the goal. Both are process/verification rather than features.

1. Merging to main was a production release

There was no gate between "PR approved" and "live".

Changing it surfaced a second problem I hadn't spotted: deploy.yml and ci.yml both triggered on the same push and ran concurrently. Nothing made the deploy wait for the tests, and this build is faster than the e2e suite — so in practice production was published while main's own CI was still running. A red main could reach users, and usually would have.

Publishing is now:

git tag v1.4.0 && git push origin v1.4.0     # leaves a record of what shipped
gh workflow run deploy.yml --ref main         # or publish by hand

Neither implies tests ran — a tag can point at any commit — so a guard job looks up the completed CI run for that exact SHA and refuses anything that isn't success, including "no CI run found". An untested commit can't be published by tagging it.

Verified the guard's query against the live API before trusting it:

SHA result
560b514 (green main) success → deploys
0000000 (nonexistent) empty → fails closed

Rollback is the same mechanism against an older ref; the guard allows it because any previously-shipped commit has its own green CI. DEPLOY.md updated, including why the push trigger shouldn't be restored.

This changes your workflow — merging no longer ships. That's the point, but it's the one thing here you may want to feel out before merging.

2. Nothing verified a real model produces geometry

The wire is tested, the parser is tested, and chatStore.send.test.ts joins them with both stubbed. So the entire suite can be green while chat produces nothing printable — which is how two bugs got to a user this year rather than to the suite.

src/llm/live.test.ts uses the real system prompt, real wire and real parser, and asserts on evaluated geometry rather than JSON shape. A reply can parse perfectly and still describe an empty union or a shape at the wrong scale.

  • "Make a 20mm cube" → bounds 20 mm on every axis. The falsifiable one; also catches the prompt failing to establish millimetres, which is how this goes subtly wrong rather than obviously wrong.
  • An L-bracket → samples inside its own bounds are inside the solid, on a 5×5×5 grid rather than the centre, because an L's centre is fresh air.
  • An edit to an existing box → reaches the branch of the system prompt that serialises the current tree, which no first-turn test touches, and checks the untouched axis stayed put.

Verified it isn't vacuous — a skipIf test that never runs is worse than no test. With a deliberately bogus key, all three fail with a genuine 401 from OpenRouter, so they really do reach the network:

LLMAuthError: OpenRouter rejected the connection (401)  {"error":{"message":"User not found.","code":401}}
 ❯ streamOpenAI src/llm/llmService.ts:297:21
 ❯ ask src/llm/live.test.ts:53:16

Default npm test: 503 passed | 3 skipped, green.

Runs weekly, not per-PR — what breaks this path is a provider changing its wire format or a model drifting out of the requested format, neither of which our commits trigger, and a paid third-party-dependent check on every PR gets disabled within a week. The job fails loudly if the secret is missing, so an absent credential can't read as a pass.

What you need to do

To turn on the weekly run, add a repo secret LIVE_LLM_API_KEY (OpenRouter by default). Until then the scheduled job will fail deliberately rather than pretend. You can also just run it locally:

SINTER_LIVE_API_KEY=sk-or-... npm run test:live

I can't run it against a real model myself — that needs your credits — so this PR proves the harness works, not that the feature does. The first real run is yours.

🤖 Generated with Claude Code

kmatzen and others added 2 commits August 2, 2026 14:46
Merging a PR published to production. There was no gate between "approved" and
"live", which is the single largest process risk in the repo.

While changing it, a second and less obvious problem: `deploy.yml` and `ci.yml`
both triggered on the same push to `main` and ran concurrently. Nothing made the
deploy wait for the tests, and this build is faster than the e2e suite, so in
practice production was published while `main`'s own CI was still running. A red
main could reach users and usually would have.

Publishing is now a tag (`v1.2.3`), which leaves a record of what shipped, or a
manual `workflow_dispatch` against any ref. Neither implies tests ran — a tag can
point at any commit — so a `guard` job looks up the completed CI run for that
exact SHA and refuses anything that is not `success`, including "no CI run
found", so an untested commit cannot be published by tagging it.

Verified the guard's query against the live API: a known-green SHA returns
"success", an unknown SHA returns empty and so fails closed.

Rollback is the same mechanism against an older ref, which the guard permits
because any previously-shipped commit already has its own green CI. Runbook
updated, including why the push trigger should not be restored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The headline feature had no end-to-end coverage. The wire is tested, the parser
is tested, and `chatStore.send.test.ts` joins them with both stubbed — so the
whole suite can be green while chat produces nothing a user can print. Two bugs
this year shipped through exactly that gap and were found by a person using the
app, not by the suite.

Adds `src/llm/live.test.ts`: the real system prompt, the real wire, the real
parser, asserting on evaluated geometry rather than on JSON shape. A reply can
parse perfectly and still describe an empty union, or a shape at the wrong
scale; bounds and field samples are what separate "the wire works" from "the
feature works".

Three cases, written against capability rather than exact output because the
subject is non-deterministic:

  - "Make a 20mm cube" -> bounds are 20mm on every axis. The falsifiable one,
    and it also catches the prompt failing to establish millimetres, which is
    how this goes subtly wrong rather than obviously wrong.
  - An L-bracket -> samples inside its own bounds are inside the solid, on a
    5x5x5 grid rather than the centre, since an L's centre is fresh air.
  - An edit to an existing box -> reaches the branch of the system prompt that
    serialises the current tree, which no first-turn test touches, and checks
    the untouched axis stayed put.

Skipped unless SINTER_LIVE_API_KEY is set, since it costs real money.

Verified it is not vacuous: with a deliberately bogus key all three fail with a
genuine 401 from OpenRouter, so the tests really do reach the network rather
than passing without running. Default `npm test` reports 3 skipped, suite green.

Runs weekly in CI rather than per-PR. What breaks this path is a provider
changing its wire format, a model drifting out of the requested response format,
or a credential expiring — none of which our own commits trigger, and a paid
third-party-dependent check on every PR gets disabled within a week. The job
fails loudly if the secret is missing, so an absent credential cannot read as a
pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

🚀 Preview deployed: https://ops-deploy-gate-and-live-llm.sinter.pages.dev

(updates on every push to this PR)

@kmatzen
kmatzen merged commit 99e3fe4 into main Aug 2, 2026
6 checks passed
@kmatzen
kmatzen deleted the ops/deploy-gate-and-live-llm branch August 2, 2026 21:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant