Files
werkator/docs/prs/2026-10-01-PR#87-self-deploy-only-from-its-repository.md
miandmhoennig dbb6d9e4be
werkator build succeeded after 02:06
werkator/deploy build succeeded after 00:07
fix(config): only the repository the instance deploys itself from may restart it (#87)
`tools/remote repo-add` installed Werkator's own `builds.deploy` (restartsInstance, unsandboxed) into werkcrappit on mih09. Its `main@deploy` ran on the host with werkcrappit's build command and would have restarted the instance on every green main.

- Registry entries get `selfDeploys`; with a registry, `restartsInstance` is refused in every repository whose entry lacks it (repository not served, logged). At most one entry may say it.
- `init --apply` repeats; fragments are merged in order.
- `tools/remote`: `WERKATOR_SELF_DEPLOY_CONFIG` for the watched repository alone; `repo-add` refuses a shared fragment with `restartsInstance`; a failed fragment upload now aborts.
- v1.26.0

**Before merging:** add `selfDeploys: true` to the `werkator` entry of `~/.werkator.yml` on mih09, or the self-deployed v1.26.0 will not serve `werkator`.

PR-doc: `docs/prs/2026-10-01-PR#87-self-deploy-only-from-its-repository.md`

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #87
2026-10-01 14:17:39 +02:00

5.5 KiB

WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.

The Problem

tools/remote repo-add runs werkator init --apply with the instance fragment named by WERKATOR_INIT_CONFIG. Since PR#61, the fragment of mih09 also carries Werkator's own builds.deploy: afterSuccessOf: default, branches: [main], restartsInstance: true, werkdock.enabled: false. On 2026-10-01, repo-add installed this block into the newly registered repository werkcrappit. Its main@deploy then ran on the host, outside the sandbox, with werkcrappit's inherited buildCommand (./gradlew build). It failed only because the host has no JDK 21. With a JDK, every green main build of werkcrappit would have restarted the instance.

Werkator accepted the block, because nothing in a repository's own files can say whether the instance runs from that repository. The fragment mixed two things: the sandbox policy that every repository gets, and the deployment that belongs to one repository.

Non-Goals

  • No detection of the repository the instance runs from; the operator says it in the instance file.
  • No change to an instance without a registry: it serves one repository, and that one may restart it.
  • No change to the pinning of restartsInstance against branch layers; it already holds.

The Scenarios

Feature: only the repository the instance deploys itself from may restart it

Scenario#87.01: Several fragments are merged in order

So that the self-deployment can travel in its own fragment, apart from the sandbox policy.

  • Given two fragments, the second one overriding a key of the first and adding a build
  • When init --apply gets both
  • Then the applied layer holds their merge, the later fragment winning
  • And a header names both files
Verified by

Scenario#87.02: restartsInstance needs selfDeploys in the registry

So that a deployment block copied into a foreign repository cannot restart the instance.

  • Given a registry with werkator marked selfDeploys: true and werkcrappit unmarked, both with a restartsInstance build
  • When their configurations are loaded
  • Then werkator loads with the restarting build
  • And werkcrappit is refused, naming the build, the instance file and selfDeploys: true
  • And a directory outside the registry is refused too, and so is a branch layer on a refused configuration
Verified by
  • ConfigLoaderTest — "only the registry entry marked selfDeploys may restart the instance"

Scenario#87.03: A refused repository is not served

So that the error shows at start, and the other repositories keep building.

  • Given an unmarked registry entry whose repository sets restartsInstance
  • When the registry opens
  • Then that repository is skipped with an error, and the others are served
Verified by
  • RepoRegistryTest — "a repository that would restart the instance without being marked selfDeploys is not served"

Scenario#87.04: One repository at most deploys the instance

So that "the repository the instance runs from" names one repository.

  • Given two registry entries with selfDeploys: true
  • When the registry opens
  • Then the start aborts, naming both entries
Verified by

The Solution

RepositoryEntry gets selfDeploys. ConfigLoader.loadPrimary refuses any restartsInstance: true in builds or the legacy branches when a registry exists and the repository's entry does not say selfDeploys: true. The paths are compared after resolving symlinks, so ~/werkator and its real path match. The check sits in loadPrimary, so every load — the registry at start, the watcher per poll, the branch layer at build time — stands on it. RepositoryEntry.directory now resolves an entry's path for the registry and the check alike.

init --apply repeats, and ConfigLoader.applyInstanceFragments merges the fragments in order. tools/remote gets WERKATOR_SELF_DEPLOY_CONFIG: repo-init and instance-start apply it on top of WERKATOR_INIT_CONFIG, repo-add never does. repo-add dies when WERKATOR_INIT_CONFIG sets restartsInstance: true, before it installs anything. A failed upload inside a command substitution now ends the script; set -e does not reach into one.

Rollout on mih09: add selfDeploys: true to the werkator entry of ~/.werkator.yml before the new version starts. An older Werkator ignores the key, so the order is safe; the other way round, the new version would not serve werkator and could not deploy itself again. Then split .env.mih09.yml locally into the sandbox policy and .env.mih09.self-deploy.yml.

Additional Changes

  • docs/configuration.md, docs/deployment.md, docs/llms.md, AGENTS.md and the architecture skill describe selfDeploys, the repeated --apply and the second fragment.
  • Version 1.26.0 with its release note.