Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0c1fe5d
feat(scripts): X02 S7 — acceptance sweep harness + AUDIT_OPEN evidenc…
im-tyler Sep 23, 2026
a95e5af
feat(config): AppliedManifestView — read side of the redacted applied…
im-tyler Sep 23, 2026
987a74e
feat(cli): plan effect set + durable plan record — routing/env/storag…
im-tyler Sep 23, 2026
194130c
feat(state,deploy): C01-1 replacement-owner reconciliation on lock ta…
im-tyler Sep 23, 2026
46749fa
feat(cli): teploy apply — drift-invalidated plan execution through th…
im-tyler Sep 23, 2026
13c7dd9
feat(deploy,caddy,docker,state): C01-2 guarded pre-commit effects
im-tyler Sep 23, 2026
3a28454
feat(targetguard): C01-1 slice 1 — on-demand target-side critical sec…
im-tyler Sep 23, 2026
92d261c
docs: AUDIT_OPEN — C01-1 slice 1 receipt (live-proven invariants + ha…
im-tyler Sep 23, 2026
0c02c11
fix(config): compose import preserves the web service's environment a…
im-tyler Sep 23, 2026
89a6a73
feat(contracts): plan-apply capability token + plan-record corpus + C…
im-tyler Sep 23, 2026
f833304
fix(targetguard): localExecutor implements the full ssh.Executor inte…
im-tyler Sep 23, 2026
90f356c
feat(caddy,deploy): C01-3 owner-tagged fenced shared Caddy lock + con…
im-tyler Sep 23, 2026
bc386eb
fix(targetguard): export TEPLOY_DEPLOYMENTS_ROOT in the linux test ha…
im-tyler Sep 23, 2026
583a8d7
fix(targetguard): CI-runner hardening — per-invocation harness script…
im-tyler Sep 23, 2026
d15f15f
feat(deploy,config): C03 request drain + readiness/liveness/stop/drai…
im-tyler Sep 23, 2026
1645f72
fix(targetguard): quote the effect in the invocation — unquoted, cmd'…
im-tyler Sep 23, 2026
d0fc737
merge: C05 plan/apply (parallel lane) — plan effect set, drift-invali…
im-tyler Sep 23, 2026
6ccbcdb
merge: C01-2/3 guarded effects + takeover reconciliation + C03 drain …
im-tyler Sep 23, 2026
08cfb1b
fix(contracts): corpus defect fix — server-status schema $defs + app-…
im-tyler Sep 23, 2026
78d07d4
fix(contracts): app-list golden carries the encoder's initialized pre…
im-tyler Sep 23, 2026
27d2dc6
Merge branch 'main' into sync/main-20260923e
im-tyler Sep 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
340 changes: 340 additions & 0 deletions AUDIT_OPEN.md

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@

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

## [Unreleased]

### Added

- **Plan/apply with drift invalidation (C05).** `teploy plan` now renders
the full effect set — routing (domain/ingress/port/publishes),
environment keys, storage volumes, resource limits, accessories —
alongside the container diff, and classifies image data honestly:
resolved-by-digest, mutable-tag-resolved-at-plan-time, or
unresolved-awaiting-build (a build plan binds its build inputs —
context fingerprint + Dockerfile identity — instead of guessing).
`teploy plan --out FILE` records a plan identity (effective-config
digest, target version, server/app, deployed-state generation).
`teploy apply FILE` re-derives every binding input and refuses,
naming what drifted (config edit, overlay flip, moved git version,
changed build inputs, or any deploy/rollback in between — the state
generation), then executes through the normal deploy engine with the
plan id stamped into the release's provenance receipt. A floating-tag
plan (timestamp version) is refused outright — it can never be
bound. Under `--json` a drift refusal classifies as the error
envelope's `conflict` code; the `plan-apply` capability token is
advertised.
- **Compose import: the web service's `environment:` and `volumes:` are
now translated** into `env:`/`volumes:` (host binds keep their full
path). Both were silently DROPPED — only accessories were parsed —
so an imported stack deployed without any of the web service's
environment or storage.

## [0.1.37] - 2026-09-22

### Fixed
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ port: 3000
build_local: true
platform: linux/amd64
stop_timeout: 30
drain_seconds: 10 # request drain: keep the old container serving in-flight
# requests this long AFTER the traffic switch, before it is
# stopped (0 = stop immediately, the default). The full graceful
# budget is drain_seconds + stop_timeout. Teploy's Caddy routing
# cannot count in-flight requests per upstream, so the window IS
# the drain policy — size it to your longest normal request.
memory: 2g # cgroup RAM cap (docker units: 512m, 2g). Unset = unlimited.
cpu: "1.5" # CPU cap in cores. Unset = unlimited.
keep_versions: 3 # auto-prune older versions after deploy (0 = keep all, default)
Expand Down Expand Up @@ -336,6 +342,9 @@ teploy exec <server> <cmd> # run a command on the server (SSH)
teploy app exec -- <cmd> # run a command in the app container (migrations, etc.)
teploy validate # check config and server readiness
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)
teploy plan # preview what a deploy would change (containers, routing, env, storage, resources; read-only)
teploy plan --out plan.json # write a bound plan record (config digest + target identity + generation)
teploy apply plan.json # execute a reviewed plan; refuses naming what drifted since it was made
teploy scale <count> # multi-server deploy + LB update
teploy version / update # version info and self-update
```
Expand Down
3 changes: 3 additions & 0 deletions contracts/MANIFEST.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ Neutron/Nucleus dependency and a public mirror.

| Corpus rev | Emitting CLI | Machine Interface | Notes |
|---|---|---|---|
| 3 (amended) | main (C05 plan-record corpus + defect fix) | 1 | C05 added the plan-record artifact + plan-apply token (see git history); amendment: server-status schema now carries its own $defs (its $refs never resolved), and app-list fixtures emit [] where the encoder emits [] (null fixtures failed schema + the real dash decode - found by dash's new contracts CI job, fixed here). |
| 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. |
| 2 | post-v0.1.37 main (S6) | 1 | observation-envelope: schema corrected from the S2 draft shape to the ADR §2.4 canonical form (resource/collected_at/freshness tri-state/error/source/last_known) before any consumer existed; fixtures generated from teploy-dash's real constructors (fresh, stale, unknown-unreachable, unreachable-last-known). |
| 3 | post-v0.1.37 main (C05) | 1 | plan-record: the `teploy plan --out` / `teploy apply` binding record (build + prebuilt-digest valid fixtures, tampered-id invalid fixture); `plan-apply` capability token added to the version handshake (additive). |

## Artifact status

Expand All @@ -25,6 +27,7 @@ Neutron/Nucleus dependency and a public mirror.
| attempt-name | yes (pattern) | valid + invalid examples | teploy-cli |
| preview-state | yes (canonical/legacy) | valid + legacy + ambiguous | teploy-cli |
| observation-envelope | yes (§2.4 canonical, rev 2) | valid x4 (fresh, stale, unknown-unreachable, unreachable-last-known; dash encoder) | teploy-dash |
| plan-record | yes | valid x2 (build unresolved-awaiting-build, prebuilt resolved-by-digest) + invalid tampered-id | teploy-cli |
| operation-record | yes | pending S5/S6 (dash) | teploy-dash |

## Rules
Expand Down
2 changes: 1 addition & 1 deletion contracts/fixtures/app-list-envelope/legacy/pre-mi.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"apps": [],
"errors": null,
"errors": [],
"host": "srv.example.com",
"observed_at": "2026-09-23T12:00:00Z"
}
12 changes: 7 additions & 5 deletions contracts/fixtures/app-list-envelope/valid/mi1.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
]
},
"previous_release": {
"version": "",
"ports": null
"version": "2",
"ports": [
3000
]
},
"containers": [
{
Expand All @@ -29,13 +31,13 @@
"version": "3"
}
],
"processes": null,
"processes": [],
"lock": null,
"maintenance": false,
"observed_at": "2026-09-23T12:00:00Z",
"errors": null
"errors": []
}
],
"observed_at": "2026-09-23T12:00:00Z",
"errors": null
"errors": []
}
29 changes: 29 additions & 0 deletions contracts/fixtures/plan-record/invalid/tampered-id.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"schema_version": 1,
"plan_id": "e6e098f1e0322e3f",
"app": "myapp",
"server": "srv.example.com",
"server_name": "prod",
"target_version": "sha256-aaaaaaaaaaaa",
"version_known": true,
"config_digest": "0000000000000000000000000000000000000000000000000000000000000000",
"image": {
"ref": "registry.example.com/myapp@sha256:abababababababababababababababababababababababababababababababab",
"resolution": "resolved-by-digest",
"digest": "sha256:abababababababababababababababababababababababababababababababab"
},
"target_state": {
"deployed": false,
"generation": 0
},
"effects": {
"containers": [
{
"action": "create",
"name": "myapp-web-sha256-aaaaaaaaaaaa",
"detail": "web container"
}
]
},
"written_at": "0001-01-01T00:00:00Z"
}
48 changes: 48 additions & 0 deletions contracts/fixtures/plan-record/valid/build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"schema_version": 1,
"plan_id": "f5f4997ca665419e",
"app": "myapp",
"server": "srv.example.com",
"user": "root",
"server_name": "prod",
"target_version": "abc1234",
"version_known": true,
"config_digest": "3f2a9c11d8e4b7065a1c9f0e2b8d7a64c5e3f1b9a0d8c7e6f5a4b3c2d1e0f9a8",
"image": {
"needs_build": true,
"resolution": "unresolved-awaiting-build",
"context_path": ".",
"context_fingerprint": "c0ffee11aa22bb33",
"dockerfile": "Dockerfile",
"dockerfile_sha256": "deadbeef11",
"platform": "linux/amd64"
},
"target_state": {
"deployed": true,
"generation": 4,
"current_hash": "old1234",
"manifest_sha256": "aa11bb22"
},
"effects": {
"containers": [
{
"action": "create",
"name": "myapp-web-abc1234",
"detail": "web container"
}
],
"routing": [
{
"action": "change",
"name": "domain",
"from": "old.example.com",
"to": "new.example.com",
"detail": "routes served by this deployment"
}
]
},
"unresolved": [
"image unresolved — built at deploy time; the plan binds the build inputs (context . fingerprint c0ffee11aa22bb..., Dockerfile Dockerfile sha deadbeef11...)"
],
"written_at": "0001-01-01T00:00:00Z"
}
29 changes: 29 additions & 0 deletions contracts/fixtures/plan-record/valid/prebuilt-digest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"schema_version": 1,
"plan_id": "e6e098f1e0322e3f",
"app": "myapp",
"server": "srv.example.com",
"server_name": "prod",
"target_version": "sha256-aaaaaaaaaaaa",
"version_known": true,
"config_digest": "7d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d10",
"image": {
"ref": "registry.example.com/myapp@sha256:abababababababababababababababababababababababababababababababab",
"resolution": "resolved-by-digest",
"digest": "sha256:abababababababababababababababababababababababababababababababab"
},
"target_state": {
"deployed": false,
"generation": 0
},
"effects": {
"containers": [
{
"action": "create",
"name": "myapp-web-sha256-aaaaaaaaaaaa",
"detail": "web container"
}
]
},
"written_at": "0001-01-01T00:00:00Z"
}
1 change: 1 addition & 0 deletions contracts/fixtures/version-handshake/valid/mi1.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"error-envelope",
"health-modes",
"kv-set-stdin",
"plan-apply",
"preview-blue-green",
"preview-canonical-id",
"provenance-records",
Expand Down
81 changes: 81 additions & 0 deletions contracts/schema/plan-record.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://teploy.github.io/contracts/schema/plan-record.schema.json",
"title": "C05 plan record (teploy plan --out; cli/planrecord.go PlanRecord)",
"type": "object",
"required": ["schema_version", "plan_id", "app", "server", "target_version", "version_known", "config_digest", "image", "target_state", "effects"],
"properties": {
"schema_version": {"type": "integer", "minimum": 1},
"plan_id": {"type": "string", "pattern": "^[a-f0-9]{16}$"},
"app": {"type": "string", "minLength": 1},
"server": {"type": "string", "minLength": 1},
"user": {"type": "string"},
"server_name": {"type": "string"},
"destination": {"type": "string"},
"target_version": {"type": "string"},
"version_known": {"type": "boolean"},
"version_explicit": {"type": "boolean"},
"config_digest": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
"image": {
"type": "object",
"required": ["resolution"],
"properties": {
"ref": {"type": "string"},
"needs_build": {"type": "boolean"},
"resolution": {"type": "string", "enum": ["resolved-by-digest", "resolved-by-image-id", "unresolved-mutable-tag", "unresolved-awaiting-build"]},
"digest": {"type": "string"},
"context_path": {"type": "string"},
"context_fingerprint": {"type": "string"},
"dockerfile": {"type": "string"},
"dockerfile_sha256": {"type": "string"},
"platform": {"type": "string"}
}
},
"target_state": {
"type": "object",
"required": ["deployed", "generation"],
"properties": {
"deployed": {"type": "boolean"},
"generation": {"type": "integer", "minimum": 0},
"current_hash": {"type": "string"},
"manifest_sha256": {"type": "string"}
}
},
"effects": {
"type": "object",
"required": ["containers"],
"properties": {
"containers": {"type": "array", "items": {"$ref": "#/$defs/planChange"}},
"routing": {"type": "array", "items": {"$ref": "#/$defs/planEffect"}},
"env": {"type": "array", "items": {"$ref": "#/$defs/planEffect"}},
"storage": {"type": "array", "items": {"$ref": "#/$defs/planEffect"}},
"resources": {"type": "array", "items": {"$ref": "#/$defs/planEffect"}},
"accessories": {"type": "array", "items": {"$ref": "#/$defs/planEffect"}}
}
},
"unresolved": {"type": "array", "items": {"type": "string"}},
"written_at": {"type": "string", "format": "date-time"}
},
"$defs": {
"planChange": {
"type": "object",
"required": ["action", "name"],
"properties": {
"action": {"type": "string", "enum": ["create", "stop", "unchanged"]},
"name": {"type": "string"},
"detail": {"type": "string"}
}
},
"planEffect": {
"type": "object",
"required": ["action", "name"],
"properties": {
"action": {"type": "string", "enum": ["add", "remove", "change"]},
"name": {"type": "string"},
"from": {"type": "string"},
"to": {"type": "string"},
"detail": {"type": "string"}
}
}
}
}
Loading
Loading