Fenced locks, attempt artifacts, Caddy adapt gate, strict-env opt-in - #3
Merged
Merged
Conversation
Every auto lock now carries a unique owner token (the fencing token) and is renewed in the background every staleLockTTL/3; staleness is measured from the last renewal, so a live-but-slow deploy is never falsely broken (the stranding hazard the register warned about) while a dead holder's lock still self-heals after the historical 30-minute window. Effect sites verify the fence before mutating: deploy/rollback/static check before every effectful phase, and the atomic state commit itself (state.WriteFenced) renames under the guard — a broken holder's late write is refused with ErrFenceLost instead of interleaving with the new holder. Recovery paths (restoring displaced containers, route rollback) are deliberately NOT fenced: refusing to clean up one's own partial effects is how a fencing design strands an app mid-incident. The owner token doubles as the fencing token; a separate monotonic counter adds nothing in this topology (the .lock dir on the target is the single authority — refusal is exactly 'does it still name us'). MockExecutor now evaluates the guard fragment against its recorded file state, so fence tests prove a refused effect never executes. A nil *Lock is the unfenced legacy shape (DeployLocked), kept honest rather than silently faked. TCL-05's containment is subsumed (release still detached/bounded); the caddy file lock stays short-lived and unfenced by design (seconds-held, no long-holder hazard).
…s under the deploy lease The three artifacts a deploy attempt generates were shared per-app paths: the build context (/deployments/<app>/build, rsynced before the app lock was ever taken), the resolved env file (.deploy-env), and the TLS cert/key (/deployments/caddy/tls/<app>.crt). Racing attempts interleaved writes on all three, and the F14 record's env-file reference named a path a later attempt would overwrite. Every attempt now mints (app, hash, random id) and writes its artifacts immutable, in the releasemeta namespace: - build context: /deployments/<app>/meta/att/<hash>.<id>/build, with the previous attempt's build dir as rsync --link-dest so a fresh directory still transfers incrementally and hardlink-shares unchanged files - env file: meta/att/<hash>.<id>/env — the record's EnvFiles now names bytes no later attempt can touch - TLS: /deployments/caddy/tls/att/<hash>.<id>/ (container path /etc/caddy/tls/att/…), deliberately kept under the caddy tls dir — the one mount every custom-TLS server provably has; written atomically The terminal deploy path acquires the fenced lease BEFORE artifact generation (the autodeploy path already locked before fetch), so attempts serialize at the source; attempt-scoped paths make the immutability structural even without the lease (teploy build, lockless by design, now builds into its own attempt dir and cannot interleave with a deploy's rsync). Retention: PruneAttempts runs after a committed deploy with the same protection window as version pruning (current + previous + pinned releases; unparsable entries kept, F78 parity). TLS nil-attempt callers (rollback, LB upload) keep the legacy shared paths — pre-F14 records still reference them and F14-recorded releases override from the record.
…e, parser-based adoption New vendored structural parser (routes.go): top-level site blocks with verbatim bodies, global-options blocks, snippets, comments, quoted strings, multi-line backtick literals (the maintenance page's own shape), and heredocs. It fails loudly — error, no edit — on unbalanced braces and top-level import (imports resolve against things this parser does not model; guessing is how another host's routes get deleted). F49: foreign-block adoption is decided on the parsed structure. All hosts requested -> whole-block adoption (the old rule, now structural); PARTIAL overlap -> the adopted hosts are removed from the foreign block's address line and the block survives for its remaining hosts with its directives untouched — previously the leftover duplicate site address failed at reload and took the whole edit down with it. Managed (TEPLOY-marked) regions are never adopted. F48: SetMaintenance extracts the current block's tls directive and basic_auth/forward_auth spans (ExtractPolicy) and carries them into the maintenance block — enabling maintenance no longer silently drops HTTPS termination and auth for the duration. An unextractable policy fails the toggle rather than guessing. Adapt-API integration (adapt.go): the HARD pre-write gate runs the SERVER's own binary (docker exec -i caddy caddy adapt, content over stdin) so validation uses the exact caddy that will serve the config. A LOCAL caddy in PATH is the advisory/debug surface and the cross-check oracle for the parser — deliberately not a gate, because version/module drift makes a local binary reject legitimate caddy_extra directives from custom server builds (found live: rate_limit under stock caddy). Tests: parser/adoption/policy behavioral tests; stub-binary tests for the local adapt plumbing (no caddy exists in this environment — stated, not faked); a PATH-gated cross-check against real caddy (v2.10.2 built from source) proving the parser's host extraction agrees with adapt's JSON and that F48/F49 outputs adapt cleanly — skipped where no binary is installed (CI).
…rict-env)
One persistent flag, default off, two behaviors:
- TCL-32: expandEnvTemplates gains a strict mode that fails the deploy
listing every unset ${VAR} referenced by teploy.yml's env: — a typo'd
variable name currently deploys fine and breaks at runtime as an
empty string. Available on the terminal deploy path and the resident
autodeploy path (serve --strict-env, or TEPLOY_STRICT_ENV=1 in the
already-installed systemd unit — the env var is the config surface
that needs no setup changes).
- F57: LoadAppWithDestination gains OverlayOptions{Strict}. Under
strict, a destination overlay that NAMES a map/list key with an empty
value (env: null, env: {}, publish: [], TOML publish = []) explicitly
CLEARS the base's field — the one thing the historical non-zero merge
could not express, which is what made presence-aware semantics a
schema decision rather than a bug. Non-empty overlays merge exactly
as before (key-merge for maps, no replace semantics introduced);
scalars are not clearable (a zero scalar already has no merge effect
and port: 0 is more likely a mistake than a reset).
Default behavior is byte-identical: both strict paths are opt-in only,
preserving product compat per the register's owner decision.
…nnotations Records the 2026-09-18 family: fenced locks with renewal (F16), attempt-scoped immutable artifacts (F08 — whose F04 dependency turned out not to hold), structured Caddy routes with server-side adapt gate (F48/F49), and the opt-in strict-env mode (F57/TCL-32). F04's entry now carries the dependency notes from this session's stay-out: the attempt id is the artifact-side generation token, and DeployFenced's lock handle is where a generation token rides when F04's design pass starts. F45/TCL-39 annotations updated for F48's landing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
F16 owner-token fenced locks with renewal; F08 attempt-scoped immutable artifacts; F48/F49 server-binary adapt gate + structured partial adoption (cross-checked against real caddy v2.10.2); F57/TCL-32 opt-in --strict-env with presence-aware overlays. Race suite green.