Skip to content

Commit 2df2a05

Browse files
authored
Merge pull request #8 from useteploy/sync/main-20260923b
Main sync: C01 evidence, C03 health modes, C04 provenance, X02-S1 machine interface, C09 doctor, X02-S2 contracts corpus
2 parents dace006 + d53c5bf commit 2df2a05

72 files changed

Lines changed: 7462 additions & 171 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎AUDIT_OPEN.md‎

Lines changed: 562 additions & 6 deletions
Large diffs are not rendered by default.

‎CHANGELOG.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,38 @@
22

33
All notable changes to teploy are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
44

5+
## [0.1.37] - 2026-09-22
6+
7+
### Fixed
8+
9+
- **Webhook deploys build the authenticated commit.** A delivery now
10+
pins the build to the payload's exact commit (fetch-verify-reset);
11+
if that commit was force-pushed away the deploy fails loudly naming
12+
both commits instead of silently building the moving branch tip.
13+
The pin rides the durable admission ledger through supersede and
14+
crash-resume.
15+
- **Preview updates no longer take the preview down.** A preview update
16+
now runs blue/green: the candidate starts under a version-suffixed
17+
name with its own network alias, passes a readiness gate, the route
18+
switches, and only then is the predecessor retired — a failed
19+
candidate leaves the old preview serving. `teploy preview prune`
20+
prunes expired previews across all apps (both record eras,
21+
idempotent, 72h default TTL) and is cron-able; the deploy-time prune
22+
uses the same core.
23+
- **SSH host-key mismatches now name what was presented and what
24+
known_hosts holds** (key algorithms included), instead of a bare
25+
mismatch error (found via ship's delivery provisioning).
26+
27+
### Added
28+
29+
- **Crash-recovery evidence for deploys (C01 design obligations):**
30+
readiness receipts (exact candidate IDs + probe outcomes) and
31+
predecessor snapshots (exact container IDs) persist per attempt at
32+
the moment they become true, so recovery can distinguish
33+
compensable states from inspect-only ones and restore exactly what
34+
was displaced; the deploy log records DEGRADED outcomes (traffic
35+
switched but retirement partially failed) instead of clean success.
36+
537
## [0.1.36] - 2026-09-22
638

739
### Fixed

‎README.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,25 @@ processes:
202202
web: "npm start"
203203
worker: "npm run worker"
204204

205+
# Readiness gate — what "healthy" means before traffic switches, and how
206+
# long to wait. mode selects the probe:
207+
# http — status-based only: GET path, 200 = ready. A 404/redirect FAILS
208+
# (no fallback). Best when the app has a real health endpoint.
209+
# tcp — a TCP dial against the published port; nothing is fetched.
210+
# For apps with no HTTP surface (game servers, TCP brokers).
211+
# Setting `path` alongside is rejected — nothing would fetch it.
212+
# auto — compatibility default (also what an omitted mode means): HTTP
213+
# GET first; a 404/3xx falls back to a TCP dial. The historical
214+
# behavior, kept so existing configs deploy identically.
215+
# timeout_seconds is the TOTAL deadline for the gate (not per-try); the
216+
# deploy output states the mode and deadline before the gate runs, e.g.
217+
# "Readiness: HTTP GET /healthz (30s deadline)".
218+
health:
219+
mode: http # http | tcp | auto (default auto/compat)
220+
path: /healthz # default /health (http/auto only)
221+
timeout_seconds: 30 # total gate deadline (default 30)
222+
interval_seconds: 1 # time between attempts (default 1)
223+
205224
# Per-process HEALTHCHECK overrides. disable: true passes --no-healthcheck
206225
# so the container ignores the image's HEALTHCHECK — useful when a worker
207226
# shares an image with web but has no HTTP listener for the inherited probe.
@@ -316,6 +335,7 @@ teploy log # deploy history
316335
teploy exec <server> <cmd> # run a command on the server (SSH)
317336
teploy app exec -- <cmd> # run a command in the app container (migrations, etc.)
318337
teploy validate # check config and server readiness
338+
teploy doctor [--server <name>] # read-only diagnostics: toolchain, SSH, Docker, registry, Caddy, disk, compatibility, repair debt (--json for machines; exit 1 if any check fails, never 2)
319339
teploy scale <count> # multi-server deploy + LB update
320340
teploy version / update # version info and self-update
321341
```

‎contracts/MANIFEST.md‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Teploy contracts corpus — MANIFEST
2+
3+
The machine-interface fixture corpus (X02 S2; ADR `_internal/
4+
X02_RESOURCE_CONTRACT_ADR_2026-09-22.md` §4, adopted by
5+
DELEGATED_DECISIONS_2026-09-23 D15). teploy-cli owns the corpus because it
6+
produces the envelopes and sits at the bottom of the stack with no
7+
Neutron/Nucleus dependency and a public mirror.
8+
9+
## Revision table
10+
11+
| Corpus rev | Emitting CLI | Machine Interface | Notes |
12+
|---|---|---|---|
13+
| 1 | post-v0.1.37 main (S2 skeleton) | 1 | First goldens: version handshake, app-list envelope (MI + pre-MI legacy), error envelope (config-invalid, internal, invalid-code), release-record, attempt-name grammar, preview-state eras. |
14+
15+
## Artifact status
16+
17+
| Artifact | Schema | Fixtures | Producer |
18+
|---|---|---|---|
19+
| version-handshake | yes | valid (real `writeVersion` encoder) | teploy-cli |
20+
| app-list-envelope | yes | valid (real DTO tags) + legacy pre-MI | teploy-cli |
21+
| server-status-envelope | yes (appStatus root) | pending S2 tail (live `server status` capture) | teploy-cli |
22+
| error-envelope | yes | valid x2 + invalid code | teploy-cli |
23+
| release-record | yes | valid container | teploy-cli |
24+
| attempt-name | yes (pattern) | valid + invalid examples | teploy-cli |
25+
| preview-state | yes (canonical/legacy) | valid + legacy + ambiguous | teploy-cli |
26+
| observation-envelope | yes | pending S6 (dash encoder) | teploy-dash |
27+
| operation-record | yes | pending S5/S6 (dash) | teploy-dash |
28+
29+
## Rules
30+
31+
- Fixtures under `valid/` and `legacy/` are GENERATED from the real
32+
encoders where a CLI producer exists (`internal/cli/
33+
contracts_golden_test.go`, run with `TEPLOY_UPDATE_CONTRACTS=1` to
34+
rewrite). Hand-authored fixtures say so in this file. Never edit a
35+
generated fixture by hand.
36+
- `invalid/` and `ambiguous/` fixtures MUST fail schema validation /
37+
adoption respectively — they pin refusals, not shapes.
38+
- A corpus change lands in the SAME commit as the code that changed the
39+
contract, with this manifest's revision table bumped. Non-additive
40+
changes bump `machine_interface` (D8) and are coordinated with
41+
teploy-dash's decoder first.
42+
- Legacy fixtures are first-class forever: an id-less server, a pre-MI
43+
envelope, a slug-keyed preview are states real deployments carry.
44+
45+
## Regeneration
46+
47+
```
48+
cd teploy-cli
49+
TEPLOY_UPDATE_CONTRACTS=1 go test ./internal/cli/ -run TestContracts
50+
```
51+
52+
CI runs the same test WITHOUT the env var: any drift between the corpus
53+
and the encoders fails the build.
54+
55+
## Known downgrade hazard (from the ADR §5 row 1)
56+
57+
An older CLI rewriting `~/.teploy/servers.yml` silently drops unknown
58+
fields, so an `id` minted by a newer CLI can vanish on downgrade. The
59+
file itself cannot enforce it; the mitigation is consumer-side (dash
60+
treats id-vanished as ambiguous-legacy requiring explicit re-binding,
61+
never auto-re-mint). Consumers MUST NOT treat a missing
62+
`machine_interface` field as MI 0 — it means "pre-MI producer", the
63+
legacy decode path.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"apps": [],
3+
"errors": null,
4+
"host": "srv.example.com",
5+
"observed_at": "2026-09-23T12:00:00Z"
6+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"machine_interface": 1,
3+
"host": "srv.example.com",
4+
"apps": [
5+
{
6+
"app": "myapp",
7+
"domain": "myapp.example.com",
8+
"type": "container",
9+
"ingress": "caddy",
10+
"current_release": {
11+
"version": "3",
12+
"ports": [
13+
3000
14+
]
15+
},
16+
"previous_release": {
17+
"version": "",
18+
"ports": null
19+
},
20+
"containers": [
21+
{
22+
"id": "9f31c02",
23+
"name": "myapp-web-3",
24+
"image": "nginx:1.27",
25+
"state": "running",
26+
"status": "Up 4 minutes",
27+
"created_at": "2026-09-23T11:55:00Z",
28+
"process": "web",
29+
"version": "3"
30+
}
31+
],
32+
"processes": null,
33+
"lock": null,
34+
"maintenance": false,
35+
"observed_at": "2026-09-23T12:00:00Z",
36+
"errors": null
37+
}
38+
],
39+
"observed_at": "2026-09-23T12:00:00Z",
40+
"errors": null
41+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
"deadb17ecafef00d",
3+
"ABC1234.deadb17ecafef00d",
4+
"abc1234.DeadB17eCafef00d",
5+
"abc1234.deadb17ecafef00",
6+
"../escape.attempt0000000"
7+
]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[
2+
"abc1234.deadb17ecafef00d",
3+
"9f31c02.0123456789abcdef"
4+
]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"machine_interface": 1,
3+
"code": "kaboom",
4+
"message": "x"
5+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"machine_interface": 1,
3+
"code": "config-invalid",
4+
"message": "invalid teploy configuration",
5+
"detail": "teploy.yml: services.0.name: required"
6+
}

0 commit comments

Comments
 (0)