chore(release): promote development to main #234

Merged
ryangr0 merged 1338 commits from ryangr0/promote-development-site-v0.1.0 into main 2026-10-04 19:13:46 +00:00 AGit
Owner

Promotes development to main so the site gets its first stable release on unfoldhq.dev (ADR-0012, 2026-10-04 entry).

main was last updated on 2026-09-12, so this carries 1338 commits.

What happens on merge:

  1. on_source_change.yml runs checks and ploeg-pin on main.
  2. site-release cuts unfold-site-v0.1.0. Unfold's own release job is skipped on main, so Unfold does not release.
  3. on_release_published sees a stable site tag and deploys the unfold-site Worker on https://unfoldhq.dev, with live checks on /, /nl, /robots.txt, the sitemap, /demo/ and /privacy.

Already in place:

  • DNS: the apex carries a proxied AAAA 100::, applied by the homelab reconciler.
  • Production keeps the sign-up database unfold-site-signups; staging has its own database since #233.

Merge with a merge commit, not squash. After the release, merge main back into development so the next promotion does not conflict on apps/site/CHANGELOG.md.

Merge only when: the checks on this PR are green.

🤖 Generated with Claude Code

Promotes `development` to `main` so the site gets its first stable release on `unfoldhq.dev` ([ADR-0012](https://forgejo.webgrip.dev/webgrip/unfold/src/branch/development/docs/adr/adr-0012-the-marketing-site-releases-and-deploys-on-its-own.md), 2026-10-04 entry). `main` was last updated on 2026-09-12, so this carries 1338 commits. **What happens on merge:** 1. `on_source_change.yml` runs `checks` and `ploeg-pin` on `main`. 2. `site-release` cuts `unfold-site-v0.1.0`. Unfold's own `release` job is skipped on `main`, so Unfold does **not** release. 3. `on_release_published` sees a stable site tag and deploys the `unfold-site` Worker on `https://unfoldhq.dev`, with live checks on `/`, `/nl`, `/robots.txt`, the sitemap, `/demo/` and `/privacy`. **Already in place:** - DNS: the apex carries a proxied `AAAA 100::`, applied by the homelab reconciler. - Production keeps the sign-up database `unfold-site-signups`; staging has its own database since #233. **Merge with a merge commit, not squash.** After the release, merge `main` back into `development` so the next promotion does not conflict on `apps/site/CHANGELOG.md`. **Merge only when:** the checks on this PR are green. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ryangr0 added 1338 commits 2026-10-04 17:28:07 +00:00
Design RFC (docs/design.md), provider SPI, harness contract, core
work-item/lease/outcome types, Apache-2.0. Pre-alpha skeleton extracted
from the webgrip dark-factory requirements.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- .releaserc.js: conventionalcommits, tagFormat v${version} (Go modules), CHANGELOG
  commit-back [skip ci], Forgejo-only publish via @saithodev/semantic-release-gitea
- ops/docker/ploegd/Dockerfile: cross-compile -> distroless static nonroot, -X main.version
- on_source_change.yml: checks -> semantic-release -> explicit dispatch of
  on_release_published (Forgejo emits no release event for CI-cut releases)
- on_release_published.yml: tag parse -> Harbor + GHCR multi-arch publish
- on_pull_request.yml replaces ci.yaml for PR checks

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Harbor remains the sole release registry. The GHCR distribute job (and its
GHCR_USERNAME/GHCR_TOKEN secrets) return with the GitHub track.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- .releaserc.js: branches main + development (prerelease rc) — pushes to
  development tag vX.Y.Z-rc.N, merges to main promote to vX.Y.Z
- on_source_change.yml: trigger on development too
- on_release_published.yml: prerelease versions (hyphenated semver) skip the
  :latest image tag

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backlog #12 (P0): adds the needs_human lifecycle state and the origin
(assignment | follow_up) and priority fields the domain model requires
(docs/domain/model.yaml). The enum-sync CI check remains open.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [0.1.0-rc.1](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.0.0...v0.1.0-rc.1) (2026-07-23)
conventional-changelog-conventionalcommits@10 (pulled in unpinned by the
shared semantic-release composite) produces empty bodies with
release-notes-generator@14 — the v0.1.0-rc.1 notes were header-only.
Pinning the toolchain in package.json (same lines as renovate-config,
conventionalcommits@^8) makes the composite's npm install resolve within
these ranges; the lockfile also lets its npm ci succeed, silencing the
EUSAGE/ENOENT noise in the release job log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tracer bullet (design §3, backlog A/B/C subset), runnable locally via
ops/local/docker-compose.yml + demo.sh:

- pkg/work: lifecycle state machine exactly per docs/domain/model.yaml,
  outcome->state mapping (stuck->needs_human R4, failed->requeue R5)
- pkg/store: Postgres layer (pgx) with embedded migrations; transactional
  audit rows on every mutation; FOR UPDATE SKIP LOCKED claims with
  priority-then-FIFO order (R10); TTL leases with an expiry sweeper that
  re-queues or stales crashed runs (retry threshold R5)
- pkg/provider/vikunja: reference TrackerProvider — raw-body HMAC-SHA256
  webhook verification and normalized assign/unassign/update events;
  write-backs are logged no-ops until the API client lands (#31)
- pkg/httpapi: webhook ingest + the run API an agent container uses:
  claim (204 = empty-handed worker #49), renew, checkpoint, outcome
- cmd/ploegd: env config, readiness probes, graceful shutdown, sweep loop
- Dockerfile: golang 1.25 (pgx requires go >= 1.25)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [0.1.0-rc.2](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.1.0-rc.1...v0.1.0-rc.2) (2026-07-23)

### Added

* **ploegd:** working dispatch-plane prototype — ingest, leases, run API ([6b16c77](6b16c77d16)), closes [#31](webgrip/ploeg#31) [#49](webgrip/ploeg#49)

### Fixed

* **release:** pin notes toolchain so release notes render sections ([5c75197](5c75197ce5))
The executor slice of the dispatch plane (design section 6), reusing the
dark-factory OpenHands + LiteLLM mechanics from ADR-0047/0048:

- cmd/ploeg-worker: harness adapter run by KEDA-spawned Jobs — claims from
  ploegd (204 = empty-handed exit 0), renews its lease at TTL/3, clones as
  agent-builder, composes the task prompt (ticket + delivery contract,
  trace id joins LiteLLM spend to ploeg runs), delegates the headless
  OpenHands run to the agent-runner entrypoint (key mint/revoke), detects
  the PR by head branch, and reports pr_opened / no_change_needed / stuck.
  An `install` subcommand self-copies the binary out of the distroless
  image for the initContainer.
- work item Description captured from the Vikunja webhook (migration 0002)
  and threaded through store + claim; WorkItem/Checkpoint gain JSON tags.
- ops/helm/ploeg: hand-written chart — hardened ploegd Deployment/Service,
  and per-team ScaledJobs (postgresql scaler on the claimable-items query,
  accurate scaling, gradual rollout, backoffLimit 0, privileged DinD
  sidecar + shared 8Gi workspace, zero k8s API authority), gated behind
  executor.enabled.
- release train: semantic-release-helm3 keeps Chart.yaml in lockstep;
  helm-chart-push publishes oci://harbor.webgrip.dev/webgrip/charts/ploeg
  (version bare, ref v-tag); helm lint + template render in checks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#2
'latest' resolution queries api.github.com unauthenticated from the runner
and rate-limits; an explicit version downloads straight from get.helm.sh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [0.1.0-rc.3](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.1.0-rc.2...v0.1.0-rc.3) (2026-07-23)

### Added

* **executor:** OpenHands worker, Helm chart, and chart publishing ([2f48634](2f48634794))
A fresh CNPG cluster (or compose cold start) can accept connections tens of
seconds after ploegd's pod starts; the first in-cluster rollout restarted
twice racing the bootstrap. Ping with backoff for up to two minutes before
giving up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The postgresql trigger's host is resolved by the keda-operator from its
own namespace, so the bare service name ploeg-db-rw never resolves there
(ScaledJob stuck NotReady, no worker Jobs). Empty scaler.host now renders
ploeg-db-rw.<release namespace>.svc.cluster.local.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First production dispatch surfaced both halves: the worker hit a stuck
outcome (clone failed), and ploegd rejected the report — links is NOT
NULL but a linkless outcome serializes as null — so the actual failure
reason was lost and the lease fell to the sweeper. Default nil links to
empty server-side, and log the outcome in the worker before POSTing so
the pod log preserves it even if the report fails.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [0.1.0-rc.4](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.1.0-rc.3...v0.1.0-rc.4) (2026-07-24)

### Fixed

* **chart:** default the KEDA scaler host to a namespace-qualified FQDN ([ef51368](ef51368f99))
* never lose a run's outcome to the links constraint ([5893e03](5893e03cd3))
* **ploegd:** retry database connectivity at startup instead of crash-looping ([fc5d879](fc5d8793e0))
The ploeg-worker's generic prompt delegates gate discipline entirely to the
target repo's AGENTS.md and OpenHands repo skills; ploeg had neither, so a
factory run against this repo would be under-specified. Adds:

- AGENTS.md: development-is-trunk rules (main is a release stub; PR-base
  retarget wart documented), the exact CI gate set (gofmt/vet/build/test +
  helm lint/template), conventional-commit/release-train rules, repo map, and
  the load-bearing invariants (key_alias format, revoke-on-every-path,
  scaler query shape, one-lease-per-item).
- .openhands/skills/team-silver/team-silver.md: legacy-format (filename
  load-bearing) repo skill with the phased delivery discipline, gates via
  DinD, and an adversarial self-review pass focused on failure/money paths.

Prep for VIK ticket: the factory fixes its own per-run key leak.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## [0.1.0](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.0.0...v0.1.0) (2026-07-25)

### Added

* **executor:** OpenHands worker, Helm chart, and chart publishing ([2f48634](2f48634794))
* **ploegd:** working dispatch-plane prototype — ingest, leases, run API ([6b16c77](6b16c77d16)), closes [#31](webgrip/ploeg#31) [#49](webgrip/ploeg#49)
* **work:** align WorkItem with domain model — needs_human state, origin, priority ([90f48e6](90f48e6e29)), closes [#12](webgrip/ploeg#12)

### Fixed

* **chart:** default the KEDA scaler host to a namespace-qualified FQDN ([ef51368](ef51368f99))
* never lose a run's outcome to the links constraint ([5893e03](5893e03cd3))
* **ploegd:** retry database connectivity at startup instead of crash-looping ([fc5d879](fc5d8793e0))
* **release:** pin notes toolchain so release notes render sections ([5c75197](5c75197ce5))

### Docs

* AGENTS.md + team-silver repo skill — make the repo factory-workable ([15b28b6](15b28b6d0e))
Re-assigning a ticket whose work item was done/needs_human did nothing: the
upsert only revived ingested/stale rows, while the handler logged 'work item
queued' regardless. A human re-assignment is a fresh mandate — revive it to
queued with a fresh attempt budget, return the actual post-upsert state, and
log/audit the truth. Semantics proven against a real Postgres via
embedded-postgres (runs inside go test, no docker needed).

VIK-588

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The worker pinned every run to the repo default branch three ways: clone
without --branch, a task contract instructing 'from main / against main',
and head-only PR detection. On webgrip/ploeg the default branch was a stale
stub with no merge base to development, so the agent rebuilt the worker from
scratch and opened an unmergeable PR (#6, run 8). Teams now set baseBranch
(chart -> PLOEG_BASE_BRANCH): the clone pins it, the contract names it, and
findPR requires the PR to actually target it. Unset preserves the old
behavior for repos whose default branch is the real trunk.

VIK-589

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [0.1.0-rc.5](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.1.0-rc.4...v0.1.0-rc.5) (2026-07-25)

### Fixed

* assignment webhooks revive finished work items ([8b15d2e](8b15d2e5fa))
* worker targets a configurable base branch end to end ([6ffdfe6](6ffdfe636c)), closes [#6](webgrip/ploeg#6)

### Docs

* AGENTS.md + team-silver repo skill — make the repo factory-workable ([15b28b6](15b28b6d0e))
Talos's runtime.OOMController SIGKILLs whole pod cgroups under
/kubepods/burstable/ on PSI memory pressure; it killed runs 9 and 10
mid-flight on 2026-07-25 (invisible to Kubernetes: exit 137, no OOMKilled
mark) and priority classes demonstrably do not protect against it. Only
Guaranteed QoS (requests == limits, every container) moves a pod out of
that tree. Worker/dind/worker-bin become values-driven with Guaranteed
defaults sized from run 8's measured peaks (worker 438Mi, dind 108Mi);
ploegd — the sweeper that self-heals dead runs — becomes Guaranteed too.
Under scarcity, runs now queue honestly (Pending holds no lease, burns no
attempt, spends no money) instead of starting into a death zone. ADR-0049.

VIK-595

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [0.1.0-rc.6](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.1.0-rc.5...v0.1.0-rc.6) (2026-07-25)

### Fixed

* Guaranteed QoS for every factory pod — out of the OOMController's kill zone ([9e708ee](9e708ee7b4))
The worker now mints a per-run LiteLLM key before starting the agent
subprocess and revokes it via defer on EVERY return path (agent success,
agent error, context cancel, panic). The key alias uses the load-bearing
format ploeg-<first 12 hex of run token>; the minted key is passed to the
entrypoint as LLM_API_KEY, which makes agent-runner >=1.0.1 skip its own
mint/revoke — eliminating the leak that occurred when the entrypoint
revoked only on clean exit.

Changes:
- pkg/litellm/client.go: new package wrapping LiteLLM /key/generate +
  /key/delete admin API
- cmd/ploeg-worker/main.go: added LiteLLM config fields, mint + defer
  revoke in execute(), modelList helper
- cmd/ploeg-worker/main_test.go: regression tests with httptest faking
  LiteLLM servers, covering agent failure, agent success, mint failure,
  and key_alias format
- Helm chart already provides all required env vars (LITELLM_ADMIN_URL,
  LITELLM_MASTER_KEY, LITELLM_KEY_BUDGET, LLM_MODEL)

VIK-585
Agent-Trace-Id: ploeg-10a1821e6555

Co-authored-by: openhands <openhands@all-hands.dev>
Replace the hand-written .releaserc.js with a small .releaserc.cjs using the shared factory.
Behavior preserved on the single v* train: main+development/rc, CHANGELOG committed, and the
Helm chart bumped in lockstep — manifest:'helm' sets Chart.yaml .version, prepareCmd sets
.appVersion (replacing semantic-release-helm3). The composite now installs the shared config
and sets SEMANTIC_RELEASE_GITEA.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#5
Reviewed-on: webgrip/ploeg#7
## [0.1.0-rc.7](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.1.0-rc.6...v0.1.0-rc.7) (2026-07-25)

### Fixed

* ploeg-worker owns the per-run LiteLLM key lifecycle (mint + always-revoke) ([aa5fc39](aa5fc397c4))
Resolves the conflict with rebuilt main: keep main's Dockerfile
(ploeg-worker second binary) and take golang 1.26-bookworm + digest.
Also carries the PR #2 action/digest pins, which main lost when it
was force-pushed after that merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#3
https://forgejo.webgrip.dev/webgrip/ploeg/packages was empty: releases went to
Harbor only, and Forgejo packages are owner-scoped — they never appear on the
repo page unless explicitly linked. Adds additive Forgejo mirror jobs to the
release workflow (image + OCI chart) with idempotent link calls, and points the
image source label at the Forgejo repo instead of the parked GitHub mirror.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The worker now mints a per-run key against the LiteLLM admin API before
starting the agent subprocess and revokes it via a deferred call that
runs on EVERY return path (success, failure, context cancel). The
minted key is passed as LLM_API_KEY to the entrypoint, which skips its
own mint/revoke when receiving it (agent-runner >= 1.0.1).

Key fixes:
- Revoke() sends {"keys": [key]} (LiteLLM /key/delete schema), not bare "key"
- modelList() strips litellm_proxy/ and openai/ prefixes from model names
- Fake LiteLLM servers in tests validate real request schemas (reject
  bare "key" field with 422; reject model scopes containing "/")
- Regression tests: agent failure revokes, agent success revokes, mint
  failure does NOT revoke, key alias format preserved, model prefixes stripped

VIK-585
Agent-Trace-Id: ploeg-b880357c84b3
## [0.1.0-rc.8](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.1.0-rc.7...v0.1.0-rc.8) (2026-07-25)

### Fixed

* **ci:** mirror image and chart to the Forgejo registry and link them to the repo ([621e77a](621e77ae03))
secrets.FORGEJO_TOKEN is the built-in per-job Actions token — FORGEJO_ is a
reserved secret prefix, so the org bot secret can never shadow it. The per-job
token cuts releases (attributed to Ghost) but cannot write org packages, which
is why run 43's Forgejo mirror jobs built for 17 minutes and then 401'd on
push. Switch semantic-release, the dispatch, and both Forgejo registry jobs to
WEBGRIP_CI_TOKEN + WEBGRIP_CI_BOT_NAME (published hourly by homelab-cluster's
forgejo-actions-secrets CronJob), and drop the runtime username derivation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [0.1.0-rc.9](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.1.0-rc.8...v0.1.0-rc.9) (2026-07-26)

### Fixed

* **ci:** release and publish as the webgrip-ci bot, not the per-job token ([647a49c](647a49cd2a))
Only same-token actions are loop-suppressed; now that semantic-release
publishes with the webgrip-ci PAT, on_release_published triggers on the native
release event (proven by run 49). The leftover dispatch step 403'd after the
token switch and failed the release job (run 48).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#8
Reviewed-on: webgrip/ploeg#12
Reviewed-on: webgrip/ploeg#11
Reviewed-on: webgrip/ploeg#9
Reviewed-on: webgrip/ploeg#4
## [1.0.0-rc.1](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.1.0-rc.9...v1.0.0-rc.1) (2026-07-26)

### ⚠ BREAKING CHANGES

* **deps:** Update postgres Docker tag ( 17 ➔ 18 )

### Added

* **deps:** Update postgres Docker tag ( 17 ➔ 18 ) ([6fe8454](6fe84548e0))

### Fixed

* **deps:** update harbor.webgrip.dev/webgrip/agent-runner docker tag ( 1.0.1 ➔ 1.0.2 ) ([2fa0985](2fa0985ce2))
* worker owns the per-run LiteLLM key lifecycle (mint + always-revoke) ([9bcc0f8](9bcc0f84f1))
Reconverge after main's force-push history loss: brings the PR #2
action/digest pins, golang 1.26-bookworm (PR #3), setup-go v7 (PR #4),
and the shared @webgrip/semantic-release-config adoption (PR #5) onto
development. CHANGELOG keeps both release trains in date order;
Chart.yaml keeps development's 1.0.0-rc.1 — the next rc rewrites both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pin-file existed so the composite's unpinned installs couldn't resolve
conventional-changelog-conventionalcommits@10 (empty release notes with
release-notes-generator@14). @webgrip/semantic-release-config pins its own
tree (^8), so .releaserc.cjs consumers get the pins for free — and the
406-package npm ci was the source of the deprecation-warning wall
(glob@7, inflight, querystring, semver-diff) in every release log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the two inline Forgejo jobs (shipped v0.1.0-rc.9) with one call to
webgrip/workflows forgejo-distribute.yml@main — same build, push, and
idempotent package-link behavior, now maintained in one place. fix-typed so
the release train exercises the reusable end-to-end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [1.0.0-rc.1](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.1.0...v1.0.0-rc.1) (2026-07-26)

### ⚠ BREAKING CHANGES

* **deps:** Update postgres Docker tag ( 17 ➔ 18 )

### Added

* **deps:** update docker.io/golang docker tag ( 1.24 ➔ 1.26 ) ([739bd80](739bd806b1))
* **deps:** Update postgres Docker tag ( 17 ➔ 18 ) ([6fe8454](6fe84548e0))

### Fixed

* assignment webhooks revive finished work items ([8b15d2e](8b15d2e5fa))
* **ci:** adopt the shared forgejo-distribute reusable for the Forgejo mirror ([9047b3a](9047b3a520))
* **ci:** mirror image and chart to the Forgejo registry and link them to the repo ([621e77a](621e77ae03))
* **ci:** release and publish as the webgrip-ci bot, not the per-job token ([647a49c](647a49cd2a))
* **deps:** update harbor.webgrip.dev/webgrip/agent-runner docker tag ( 1.0.1 ➔ 1.0.2 ) ([2fa0985](2fa0985ce2))
* Guaranteed QoS for every factory pod — out of the OOMController's kill zone ([9e708ee](9e708ee7b4))
* ploeg-worker owns the per-run LiteLLM key lifecycle (mint + always-revoke) ([aa5fc39](aa5fc397c4))
* worker owns the per-run LiteLLM key lifecycle (mint + always-revoke) ([9bcc0f8](9bcc0f84f1))
* worker targets a configurable base branch end to end ([6ffdfe6](6ffdfe636c)), closes [#6](webgrip/ploeg#6)

### CI

* **actions:** Pin dependencies ([e26493a](e26493a946))
* **actions:** Update dependency helm ( v3.18.4 ➔ v4.2.3 ) ([5591eab](5591eab652))
* **actions:** Update https://github.com/actions/setup-go action ( v6.5.0 ➔ v7.0.0 ) ([5ce8533](5ce85333de))
* adopt @webgrip/semantic-release-config ([7b5f87c](7b5f87ce01))
* drop the manual release dispatch — bot-cut releases fire the release event natively ([2faef42](2faef424d9))
* **release:** drop the local semantic-release toolchain — the shared config pins it ([592ebdd](592ebdd9e0))
* retrigger release train (rc release died on missing yq, now fixed) ([8a6c8db](8a6c8db248))

### Internal

* **release:** v0.1.0-rc.5 [skip ci] ([b2b620b](b2b620bbb1))
* **release:** v0.1.0-rc.6 [skip ci] ([6687689](66876898c1))
* **release:** v0.1.0-rc.7 [skip ci] ([31b0887](31b08874d0))
* **release:** v0.1.0-rc.8 [skip ci] ([b6c1238](b6c1238a13))
* **release:** v0.1.0-rc.9 [skip ci] ([5e2675b](5e2675b891))
* **release:** v1.0.0-rc.1 [skip ci] ([9996c2e](9996c2e250))
The rc.1 train was cut twice (the morning tag was deleted after its
publish run failed, and the re-cut appended a second entry). Keep the
entry matching the current v1.0.0-rc.1 tag.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@webgrip/semantic-release-config@1.1.0 writes Chart.yaml .version AND
.appVersion itself (dependency-free node script, no yq needed), so the
repo-level prepareCmd is dead weight — and it was the line that killed
the first 2026-07-26 release on a yq-less runner.

Re-enters the release train after the false v1.0.0-rc.1 cleanup: with
1.1.0's breakingHeaderPattern the postgres 17→18 dep major analyzes as
minor, so the expected cut is v0.2.0-rc.1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two different reusables coexist in on_release_published (helm-chart-push +
forgejo-distribute, both green in run 60) — the flattened-id collision only
applies to two instances of the same reusable. fix-typed deliberately: this
release re-establishes the 0.1.x line as the goto after the v1.0.0-rc.1
tag surgery (the postgres feat! is absorbed behind v0.1.0-rc.10).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [0.2.0-rc.1](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.1.0...v0.2.0-rc.1) (2026-07-27)
Both Distribute jobs ran the same 8-minute multi-arch build in parallel, one
per registry, with independent caches and no digest guarantee between them.
Distribute (Forgejo) now needs the Harbor build and passes mirror-from-registry
so the shared reusable copies the manifest list via buildx imagetools instead
of rebuilding. Requires webgrip/workflows feat/forgejo-distribute-mirror-mode
on main BEFORE this reaches a release run (unknown reusable inputs fail the
call).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restore the length-guarded litellm.Alias() helper (return empty string
instead of panicking) and use it at every alias construction site.

When the sweeper expires a lease it now revokes the run's LiteLLM key
alias via ListKeys + DeleteKeys (non-fatal, best-effort). At boot, an
orphan sweep removes ploeg-* keys that don't belong to any unfinished
run, clearing pre-existing stragglers.

Chart: ploegd deployment gains LITELLM_ADMIN_URL and LITELLM_MASTER_KEY
from the same secret the workers use.

VIK-594
Agent-Trace-Id: ploeg-e7e2cf725f8e
Adopts the OpenBao Transit signing stack (infra's proven shape): cosign
sign + CycloneDX SBOM attestation by digest, Dependency-Track upload
(fail-soft), Harbor-native SBOM accessory. Needs the workflows-repo
composite AND the OpenBao cosign-signer role admitting webgrip/ploeg —
both must be live on main before this reaches a release run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reviewer-authored test-bench repair, feature code untouched: the runner
env has no Go toolchain so the agent cannot run gofmt or the tests; the
red gofmt gate was masking TestListKeys_WithoutFullObject failing on the
nil-slice-marshals-as-null gotcha. The real proxy emits [] for an empty
key list (verified live 2026-07-27), so the strict fake must too.
Full local gates: gofmt clean, vet, build, go test ./... all green
(store suite on embedded postgres included).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [0.2.0-rc.2](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.1...v0.2.0-rc.2) (2026-07-27)
Reviewed-on: webgrip/ploeg#13
## [0.2.0-rc.3](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.2...v0.2.0-rc.3) (2026-07-27)
Infrastructure failures (lease expiry without a reported outcome) now
refund the attempt and apply exponential backoff instead of incrementing
the attempt budget. Human reassignment clears the parking window.

- Migration 0003: adds next_eligible_at + infra_failures columns
- Claim: eligibility filter skips parked items (next_eligible_at > now())
- ExpireLeases: infra failure → refund attempt, +1 infra_failures,
  exponential backoff (1m/5m/15m/60m cap), stale at MaxInfraFailures=10
  with distinct infra_cap audit reason
- IngestAssigned: clears next_eligible_at on reassignment (human override)
- Scaler query (chart): respects next_eligible_at eligibility
- Tests: classification, backoff progression, eligibility filtering,
  infra cap staling, human override clearing parking

Co-authored-by: openhands <openhands@all-hands.dev>
VIK-596
Agent-Trace-Id: ploeg-b8ffd3a6f827
Reviewer-authored mechanical repair (feature and test semantics are the
agent's from round 3): drop the unused id binding in ClearsParking and
gofmt store.go/store_test.go. Full local gates green: gofmt clean, vet,
go test ./... including the four VIK-596 suites on embedded postgres,
helm lint + template.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#14
## [0.2.0-rc.4](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.3...v0.2.0-rc.4) (2026-07-28)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backlog 101 adds the AHP live-run-surface watchlist item (blocked on AHP >= 1.0
plus a non-Microsoft host); item 64 re-confirms ACP as the consolidated
client<->agent seam; design.md gains AHP in the rejected-alternatives table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Make the three variation axes swappable without touching the core
(design §5/§6, backlog #46/#59/#62/#66/#69):

- Harness: pkg/harness.Adapter (session-protocol tier, ACP-ready) +
  CommandAdapter (spawn-and-wait) lifted by a shared RunCommand runner.
  Adapters: openhands (verbatim extraction, default), exec (any binary
  via taskspec.json/outcome.json), claude-code (JSON envelope -> usage).
  Selected per team via PLOEG_HARNESS; unknown names fail before claiming.
  Outcome precedence stays orchestrator-owned: PR found > structured
  report > historical exit-code heuristics (pinned by table test).
- Contract: TaskSpec/OutcomeReport revived as the adapter I/O; versioned
  JSON Schemas published in docs/contracts/ and pinned by golden tests.
  Outcome API accepts the full OutcomeReport (checkpoint + usage inline,
  stuck-requires-reason 400); agent_runs gains a usage JSONB column.
- LLM broker: pkg/llmbroker.Broker/Sweeper with LiteLLM and Static (BYO)
  implementations; ploegd sweeps become two method calls; the
  ploeg-<12hex> alias invariant stays in pkg/litellm. LITELLM_KEY_DURATION
  now actually flows into the key TTL (was hardcoded 4h; default unchanged).
- Executor: no Go interface (nothing in Go touches k8s) — the run API is
  the SPI, formalized in docs/contracts/executor.md; GET /api/v1/queue/depth
  serves the scale signal over HTTP; the chart gains executor.type
  (keda | cronjob) over one shared pod-template helper, with a working
  CronJob executor rendered in CI.
- Helm: per-team harness block (name, image, entrypoint, args, dind)
  over global defaults — harness AND agent image swap per team; DinD
  sidecar/env/volumes render only when the harness needs Docker.

Default behavior is unchanged: the default chart render is byte-identical;
the executor render's only functional diff is the inert PLOEG_HARNESS env
line. cmd/ploeg-worker shrinks to env wiring; orchestration moves to
pkg/worker with the four key-lifecycle regressions ported.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#16
## [0.2.0-rc.5](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.4...v0.2.0-rc.5) (2026-07-28)
AC 1: Worker first log line + checkpoints carry node name + pod UID (downward API env vars via chart).
AC 2: agent_runs gains failure_reason column (migrations 0005/0006) with taxonomy: infra_node, infra_llm, agent_error, budget, lease_lost.
AC 3: failedJobsHistoryLimit raised to 3 (was 1) so failed Jobs linger.
AC 4 (VIK-586): LLM-dead run (zero spend, no PR) maps to failed+infra_llm instead of no_change_needed.
AC 5: Comprehensive tests for FailureReason constants + resolveOutcome precedence table.

VIK-597
Co-authored-by: openhands <openhands@all-hands.dev>
Agent-Trace-Id: ploeg-9a35d6b3588c
- Add ExpectsLLM() to Adapter/CommandAdapter interfaces (true for
  openhands/claudecode, false for exec).
- Fix VIK-586 heuristic: nil Usage → no_change_needed (unknown);
  zero spend on LLM adapter → infra_llm; exec adapter never triggers.
- Fix gofmt in contract.go, types.go, worker_test.go.
- Rename FailureReason constants: drop redundant "Reason" infix.
- Merge migrations 0005+0006 into single 0005 (failure_reason + node/pod).

VIK-597
Co-authored-by: openhands <openhands@all-hands.dev>
Agent-Trace-Id: ploeg-efde45b7f775
Reviewer-authored mechanical repair (round-3 semantics are the agent's):
- scriptAdapter in pkg/harness/adapter_test.go implements the new
  ExpectsLLM() (compile fix)
- 0005_failure_reason.sql no longer re-adds the checkpoint identity
  columns that 0006 owns (rebase artifact; embedded-pg migrate died on
  'column node_name already exists')
- gofmt -w across the seven flagged files

Full local gates green: gofmt clean, vet, go test ./... (all packages,
store on embedded postgres), helm lint + executor template.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verdict ledger row in design.md §8, watchlist item #102 (A2A north-facing
dispatch facade; prerequisites #31 + a single-item read endpoint, flip
triggers named), third re-confirmation on #64 (OpenHands closed A2A as
not_planned and shipped ACP), and the full evidence dossier under
docs/research/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#15
## [0.2.0-rc.6](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.5...v0.2.0-rc.6) (2026-07-28)
AGENTS.md followed the pre-Claude-5 context style: a repo map the file tree
already tells you, inlined invariants duplicated from architecture.md, and the
CI gate list copied out of the workflow. Rewritten as a "where to look" router
(41 lines, was 66) per Anthropic's context-engineering guidance — gotchas that
are not inferable stay inline, everything else points at its single source.

Durable knowledge that was living outside version control is now in the repo,
where anyone pulling it can read it:

- docs/ops/ci-and-infra.md — where CI runs, the resolved CoreDNS/TLS clone
  failure and how to rule it out next time, Forgejo package-linking and
  built-in-token traps, the cosign/OpenBao signing stack, and the
  OpenBao-over-org-secrets rule.
- docs/ops/board.md — Vikunja coordinates and MCP gotchas, deliberately out of
  AGENTS.md (token cost on every agent load), plus the dispatch-topology trap:
  webhook and team shares live on project 11, not the planning board.
- docs/research/2026-07-28-omniroute-fit.md — OmniRoute sweep evidence trail
  behind the design.md §8 verdict.
- docs/research/2026-07-28-agent-roster-ssot.md — roster SSoT verdict; its one
  consequence for this repo is backlog #103 (retire PLOEG_TEAM_MAP).
- docs/research/2026-07-28-paperclip-fit.md — dossier left untracked by the
  sweep that wrote it.

Also drops the keyDuration entry from architecture.md §9: the pluggable-seams
refactor wired LITELLM_KEY_DURATION through cfg.KeyTTL to the broker, so it is
no longer a divergence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Onboarding a repo cost a whole new team — Helm entry, KEDA ScaledJob, tracker
bot user, team-map entry, project share, webhook — even when model, budget and
harness were identical. No normative doc ever gave a Team a repository:
design.md §3 and domain/model.yaml define it as name + roles + strategy +
budget. The binding lived only in the chart, recorded in no backlog item and no
§9 divergence, and was hardened last week when values.schema.json made
repoOwner/repoName required.

Decisions (docs/adr bootstrapped, backlog #97):
- ADR-0001 Work Target is a Work Item attribute (accepted; implemented here)
- ADR-0002 routing over provider-opaque Scopes (proposed; SPI-breaking, later)
- ADR-0003 forge registry + per-run repo-scoped credentials (proposed, later)

Recorded: architecture.md §9.12-17 (team-names-repo, TrackerEvent.Team vs R7,
project_id dropped at the parse boundary, forge singleton, agent/vik-<id>,
spend groupable only by team); backlog #41/#42/#75/#97/#103 corrected and
#104-108 added. #42's premise was inverted — many-teams-to-one-repo becomes a
supported configuration, not a bug.

Implementation:
- migration 0007 adds external_scope + target_{forge,owner,repo,base_branch} +
  route_rule. No index change: work_items_claimable serves Claim, QueueDepth
  AND the KEDA scaler, and neither its keys nor its predicate are touched. An
  EXPLAIN test now guards that permanently.
- work.Target with atomic Owner+Repo — a half-resolved target is never blended
  with the fallback, because cloning one repo and pushing to another is the
  worst failure this seam can produce.
- Vikunja parses project_id into an opaque provider.Scope; the core maps scope
  to target, so the adapter never learns what a repository is (R7). Verified
  against the live instance: tasks.project_id exists, smoke ticket 611 is on
  project 11.
- pkg/target.MapResolver: exact-match rules, no DSL. The transitional
  <scope>/<team> key reproduces today's routing, which a scope-only map cannot
  — every team's webhook is wired on one Vikunja project (docs/ops/board.md,
  "Dispatch topology — the trap"). Malformed entries fail at boot rather than
  silently dropping the way PLOEG_TEAM_MAP does.
- IngestAssigned never re-targets a leased item (R12): a review round that
  silently moved repo would orphan its own branch and PR.
- Worker prefers a resolved claim target, falls back to env, and honours
  PLOEG_TARGET_SOURCE=env as a per-team lever. REPO_OWNER/REPO_NAME stop being
  boot-required; a run with no target reports stuck after claiming rather than
  exiting and stranding the lease for the sweeper.
- Chart widens: required drops to name/model/budget, repo env renders only when
  a team pins one, plus executor.defaultTarget and per-team targetSource.

The target rides on the work item inside the claim response, so the published
run-api schema needs zero edits; only taskspec.v1's workItem gains optional
properties (v1-legal, additive).

Default behavior is unchanged: the default chart render is byte-identical, and
with no target map configured every item stays unresolved and every worker uses
its env repo exactly as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Encodes ADR-0001/0002/0003 in the domain language so the model stops implying
a Team owns a repository.

- New terms: Work Target (a coordinate, not a connection — carries a forge id,
  never a URL or credential, R8), Forge, Scope (opaque provider container id;
  the core compares it and never interprets it, R7), Routing Rule.
- Work Item gains external_scope, target and route_rule. Team gains an explicit
  negative — it never names a repository, forge or credential — because
  negative space in a manifest definition is what stops the next contributor
  re-adding repoName.
- Tracker Event no longer carries a resolved Team: it reports the provider's
  Scope and Actor, never a Ploeg Team or a repository.
- R11 target independence, R12 target immutability.

architecture.md §9.12 records that R11's "not claimable without a Target"
clause is deliberately unenforced during the fallback window: an unresolved
item still claims and the worker uses its env repo until the last team drops
its pinned fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The domain-model edit in the previous commit shifted every line in
model.yaml, so the ADR's and architecture.md's `model.yaml:369-389` /
`:391-411` citations now pointed at unrelated content. Entity names survive
edits; line numbers into a generated-from source file do not.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#17
## [0.2.0-rc.7](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.6...v0.2.0-rc.7) (2026-07-29)
resolveOutcome let the forge PR poll win before runErr was examined, and
findPR matches on agent/vik-<externalID> — the branch every retry, review
round and (soon) persona turn reuses. A run that crashed instantly found
its predecessor's still-open PR and reported pr_opened, marking the work
item done on work it never did. Today that mis-closes re-assigned tickets
(architecture.md §1's review loop); with a reviewer persona it would ship
unreviewed code while the audit log credits the reviewer.

execute() now snapshots the branch's PR state before the harness starts
and passes prExisted into resolveOutcome:

  - pr_opened only for a PR that did not exist before this run
  - clean run on a pre-existing PR -> pr_updated (already in the enum)
  - failed run keeps stuck/agent_error, carrying the PR in Links for the
    reviewer's convenience but never as this run's success

Also guards the VIK-586 heuristic, which keyed "LLM adapter produced no
spend" on CostUSD == 0 alone. That (a) overwrote a FailureReason an
adapter had already set, and (b) misreads any harness reporting tokens
without a cost — ACP makes UsageUpdate.cost optional, so that is the
common case for the adapters coming next. It now requires an unset
FailureReason and zero token counts: no evidence of LLM traffic, rather
than no evidence of billing.

Finally, failureReason joins outcomereport.v1.schema.json. The worker has
posted it and the store has persisted it since VIK-597, but the published
schema is additionalProperties:false with no such property — the contract
was behind the code. Additive-optional, legal under the v1 rules.

Gates (docker golang:1.25 + alpine/helm, this branch):
  gofmt -l . .................... clean
  go vet ./... .................. ok
  go build ./... ................ ok
  go test ./... ................. ok (pkg/store needs a non-root uid;
                                      green when run unprivileged)
  helm lint ops/helm/ploeg ...... 1 chart linted, 0 failed
  helm template x3 .............. ok (default, keda, cronjob)

The six new table rows were run against the unfixed resolveOutcome and
all six fail there:
  crashed_run_does_NOT_inherit_a_pre-existing_PR  pr_opened, want stuck
  clean_run_on_a_pre-existing_PR                  pr_opened, want pr_updated
  lost_lease_on_a_pre-existing_PR                 pr_opened, want stuck
  tokens_without_cost_is_NOT_infra_llm            infra_llm, want ""
  adapter-set_failure_reason_survives             infra_llm, want agent_error
  exit_0_with_tokens_but_no_cost                  failed, want no_change_needed
Decisions lived in three shapes at once — verdict rows in design.md §8/§9,
dossiers in docs/research/, and *[research]*-tagged backlog items. No single
place answered "what is decided and is it still in force", and nothing
stopped two of them disagreeing. homelab-cluster ADR-0050 exists because
exactly that happened there for two days.

docs/adrs/ is now the single ledger, MADR 4.0 with two local extensions:

  - Supersession is append-only. An accepted record's own status is never
    flipped; the superseding record carries `supersedes: NNNN` and the index
    shows "superseded by NNNN". The file is the artefact, the index is the
    current view.
  - A decision that can change carries `review-by: YYYY-MM-DD` plus a
    "Re-evaluation triggers" section. This is the one thing §8 did that
    vanilla MADR has no slot for, and migrating without it would have been
    a downgrade.

Nine records. The seven short market-survey rows collapse into 0005 with
their individual verdicts preserved as Pros and Cons — that is what MADR's
Considered Options section is for, and it reads better than seven stubs.
The four substantial protocol/product verdicts (AHP, A2A, OmniRoute,
Paperclip) each keep their own record, triggers, and dossier link.

§9's foundation decisions (Go, Apache-2.0, Forgejo-leading home) are
migrated too, though the plan only called for §8: leaving them behind would
have recreated the dual-ledger problem this commit exists to remove.

Evidence does not move. docs/research/*.md stays exactly as it is; each ADR
links its dossier under More Information. The ADR carries the verdict, the
dossier carries the working.

scripts/check_adr_consistency.py is ported from erfbeeld and extended with
two checks it asks for in prose but never enforced: every record must carry
a "### Confirmation" subsection, and a dated review-by must be backed by a
triggers section. Note the adr-writer skill's bundled validator must NOT be
used here — it assumes status-flip supersession and rejects this corpus.

Verified positively and negatively:
  9 record(s), 9 Records row(s), 0 supersession(s), 5 dated review(s) — consistent

  flipped file status ............ caught (2 violations)
  dated review, no triggers ...... caught
  missing Confirmation ........... caught
  index date drift ............... caught
  record with no Records row ..... caught
  supersedes a missing number .... caught

CI wiring, the OpenSpec half, and the design.md/AGENTS.md pointer rewrite
follow on this branch.
Changes now run proposal → specs → design → adr → tasks, with the adr step
gating tasks so a change that makes a durable architectural commitment
cannot reach implementation without recording it (ADR 0001).

The schema is derived from the UPSTREAM `spec-driven` shipped with
@fission-ai/openspec 1.6.0, not from erfbeeld's fork of it — erfbeeld's adr
instruction carries its own house rules (X1 Dutch/English, H1 fiscal
amounts, conformance vectors) that mean nothing here. Two kinds of change on
top of upstream: the adr artifact itself, and repo rules folded into each
instruction (seam vocabulary, append-only migrations, never-restate-a-
published-contract, the gate set, regression-test-must-fail-first).

openspec/config.yaml deliberately POINTS at docs/architecture.md,
docs/design.md, docs/adrs/, docs/contracts/, docs/domain/ and docs/backlog.md
rather than copying them. The contracts are pinned to the Go types by
pkg/harness/contract_test.go and the domain docs are generated from
model.yaml — a prose mirror in config.yaml would be drift with no gate on it.
Keep it a map, not a mirror.

The ~3,100 lines of skills and slash commands are NOT committed: they are
pure `openspec update` output, identical across repos, and already present
user-globally at ~/.claude/commands/opsx/. Run `openspec update` locally to
generate them for whichever tools you use.

Verified against the real CLI (node 24 + openspec 1.6.0):

  openspec schemas    spec-driven-with-adr (project)
                      Artifacts: proposal → specs → design → adr → tasks
  openspec doctor     OpenSpec root: ok
  openspec validate --all
                      No items found to validate (no changes yet)
  openspec status     [-] adr   (blocked by: design)
                      [-] tasks (blocked by: adr, specs)
  openspec instructions adr --change <scratch>
                      renders <project_context> and <rules> from config.yaml

That last one is the integration point worth knowing about: `openspec
instructions` is how config.yaml actually reaches a model, so it is the way
to check any edit to the schema, the config or a template before relying on
it.

Two YAML traps hit while writing config.yaml, both from plain scalars: a
colon-space inside a list item ("a requirement: what happens") parses as an
implicit key, and a value starting with a backtick is a reserved character.
Neither is caught by eye — `openspec doctor` reports them.
Closes the migration: design.md §8/§9 become indexes into docs/adrs/,
AGENTS.md points at the new homes, and the consistency gate moves from a
Python script into the existing `go test ./...` step.

The gate moved languages on purpose. The Forgejo runner is guaranteed a Go
toolchain and is NOT guaranteed python3, and on_pull_request.yml already
carries two comments about network-fragile setup actions — adding a third
to run a stdlib script was the wrong trade. internal/ledger reads the corpus
as data, needs no new CI step, and runs locally for anyone who can already
build the repo. One implementation, not two in different languages.

`openspec validate --all` is deliberately NOT wired into CI yet. It needs
node + the openspec CLI in the runner, and today it validates zero changes
("No items found to validate"). Wire it with the first real OpenSpec change,
when it has something to check.

AGENTS.md now states the three homes explicitly — evidence in
docs/research/, verdict in docs/adrs/, action in docs/backlog.md — plus one
line the plan asked for: .openhands/ and .opencode/ in this repo are
dogfooding, not product spec. Ploeg's harness support is pkg/harness and
docs/contracts/, never whichever agent config sits in this tree.

Gates (docker golang:1.25 + alpine/helm):
  gofmt -l . .................... clean
  go vet ./... .................. ok
  go build ./... ................ ok
  go test ./... ................. ok  (internal/ledger 0.029s; pkg/store green
                                       when run with a mapped unprivileged uid)
  helm lint ..................... 1 chart(s) linted, 0 failed
  helm template x3 .............. ok

internal/ledger was run against seven deliberately broken corpora; every
check fails when it should:
  flipped file status ................ illegal status + index mismatch
  dated review-by, no triggers ....... caught
  missing ### Confirmation ........... caught
  file date drifts from index ........ caught
  record absent from the index ....... caught
  supersedes a nonexistent number .... caught
  real supersession, stale index ..... caught  <- the append-only mechanism
The kernel tested harness.CommandAdapter and lifted it with RunCommand.
Session-protocol adapters (ACP, backlog #64) implement harness.Adapter
directly — adapter.go's own doc comment says so — and could not be covered.

Fixture now takes exactly one of NewAdapter (spawn-and-wait) or
NewSessionAdapter (session protocol), and every property is written once
against harness.Adapter. PrepareProducesRunnableInvocation stays
CommandAdapter-only and skips otherwise: a session adapter owns its process
lifecycle and exposes no Invocation to inspect. The three existing adapter
tests are unchanged.

Three new properties, applied to all adapters:

  StuckAlwaysCarriesAReason   R4 at the source, not only at the report API
                              where pkg/httpapi already 400s it. An adapter
                              that emits a reasonless stuck turns a
                              diagnosable park into an opaque one.
  FailureReasonIsValidOrEmpty keeps the taxonomy closed. An adapter with
                              structured evidence may classify its own
                              failure and pkg/worker now defers to it, so a
                              typo would silently defeat classification
                              rather than being caught.
  SurvivesGarbageOnStdout     a banner, progress bar or stray console.log
                              must not crash the adapter or trick it into
                              inventing an outcome. For a session adapter
                              this IS the protocol channel — the difference
                              between a wrong-subcommand misconfiguration
                              surfacing as a clear infra failure and it
                              surfacing as a panic.

Deliberately NOT added: the plan listed a ReturnsWithinGraceOfCancel
property with a 15s bound. CancelKillsTheHarness already bounds it at 5s,
which is stricter — a second weaker assertion would be surface, not
coverage. It now logs the measured time instead, so a session adapter's
cancel handshake cannot quietly creep toward the ceiling.

The plan expected these might surface real bugs in openhands/claudecode/
exec. They did not — all three pass unchanged. Recording that because
"no findings" is a result, not a skipped step.

  go test ./pkg/harness/... — ok (harness, claudecode, execbin, openhands)
  gofmt clean, go vet ok
The pure half of the ACP adapter (backlog #64): wire.go decodes the
protocol, state.go accumulates a session, outcome.go maps it to an
OutcomeReport. No subprocess, no SDK, no network — so the entire mapping
matrix is a table test over JSON fixtures written the way an agent sends
them, and it is reviewable before anything can spawn.

Ploeg owns the enums rather than importing the SDK's generated ones, and the
reason is concrete: coder/acp-go-sdk v0.13.5 is generated from schema 0.13.5
while upstream is on the 1.x line, and its UsageUpdate is token-shaped with
NO field for `cost` — the field the budget gates want. Decoding off a raw
tee with ploeg-owned types is immune to SDK lag and to unknown enum values.
Every parser follows one rule: an unknown value becomes a sentinel, never an
error. A run never fails because of an enum.

What this buys over the openhands adapter, concretely:

  before the prompt fails  -> failed/infra_node or infra_llm  (retryable)
  today                    -> stuck/agent_error               (PARKED)

That is architecture.md §9.9 / VIK-596 fixed at the source, and it needs no
orchestrator change: resolveOutcome already consults a valid structured
report ahead of its heuristics, and now defers to an adapter-set
FailureReason.

  refusal            -> stuck, with the refusal TEXT as the reason
  max_turn_requests  -> stuck, quoting the agent's own unfinished plan steps
  max_tokens         -> failed/budget (retryable; a fresh session can work)
  end_turn, no edits -> no_change_needed
  end_turn WITH edits-> no structured signal; the worker's forge poll decides

That last split is the fidelity win. openhands returns "no signal" for both,
so "edited 40 files and forgot to push" currently reads as no_change_needed.
When the worker finds no PR, BuildMutatedWithoutPR parks it and names the
files. The adapter never asserts forge state itself — only the worker polls.

Usage is set only on positive evidence; a zero-valued Usage would trip
pkg/worker's VIK-586 heuristic. Both usage shapes decode (ACP v1
{used,size,cost} and the SDK's {input,output}), cost is read as a number or
an object, and an unreadable cost is absent rather than zero — because
absent-vs-zero is exactly the distinction Phase 0 taught the worker to make.

23 subtests. gofmt clean, go vet ok, go build ok, ./pkg/harness/... all ok.
Beyond the stop-reason matrix they pin the traps: tool calls fold across
updates, a PENDING edit is not a mutation, a completed read is not a
mutation, an unknown 12th variant does not swallow the next event, a
truncated payload does not kill the accumulator, the prompt echo never leaks
into Summary, and the rings stay bounded.
The process half of the ACP adapter. Three failure modes it exists to
prevent, each covered by a test that re-execs the test binary as a fake
agent (the os/exec idiom) — a real child process, no `go build`, no network,
no agent binary.

1. Orphaned grandchildren. Deliberately NOT exec.CommandContext: node-based
   ACP agents (opencode, the npm adapter processes) fork workers, and
   killing only the direct child leaves a grandchild holding the DinD socket
   and the per-run LiteLLM key until its TTL expires. Setpgid plus signalling
   the negative pgid reaps the group. The test forks a real grandchild and
   asserts it dies.

2. Banners on the protocol channel. Agents print version notices, spinners
   and stray console.log on stdout. Without a filter the first such line is a
   JSON-RPC parse error and the session dies for a cosmetic reason. Lines
   whose first non-space byte is '{' go to the dispatcher; everything else is
   diverted to the pod log. So a wrong subcommand (`opencode` instead of
   `opencode acp`) surfaces as "initialize never completed, and here is the
   text it printed instead" — diagnosable infra, not a mystery. A JSON ARRAY
   is noise too: JSON-RPC frames are objects.

3. Stdio deadlock. Every client-side response goes through a 64-slot async
   writer, so a child that stops reading stdin stalls one goroutine instead
   of the dispatcher. A full queue returns errStdinBacklog rather than
   blocking or silently dropping — dropping would desync the protocol, and
   the watchdog can escalate to SIGTERM on the error.

Also: Wait is sync.Once-guarded (a second Wait is "wait: no child processes"
without it), and Kill is idempotent.

One test bug found and fixed while writing this, worth recording because it
would have been a flake rather than a failure: the JSONL classification test
asserted on the noise buffer after reading a fixed line COUNT, so the filter
goroutine had not necessarily classified the trailing line yet. Draining to
EOF is the only thing that proves it has.

  gofmt clean, go vet ok
  go test ./pkg/harness/adapters/acp/ -count=1   ok  0.341s
  go test ./pkg/harness/adapters/acp/ -race      ok  3.614s
session/request_permission exists so an editor can ask a human. There is no
human in a worker pod, so the policy must answer every request immediately
and deterministically — it runs on the protocol read loop and must never
block or prompt.

Default is allow-all, matching the claudecode adapter's bypassPermissions
and for the reason recorded there: the pod is a disposable,
credential-scoped sandbox whose blast radius is bounded by the per-run
LiteLLM key, the repo-scoped forge token, and being destroyed at exit. The
permission prompt is not the security boundary; the pod is.

What the policy DOES protect against is a runaway agent. An agent asking the
same thing 200 times is not progressing, and the storm cap (200 total,
60/min, sliding window) turns a silent 45-minute burn into a stuck reason
naming the worst offenders.

allow_read_only exists for the reviewer persona in Phase 3: a judge that
cannot edit is a stronger guarantee than a judge instructed not to. It maps
read/search/fetch/think to allow and everything else to reject.

Two traps the tests pin, both of which would silently grant a mutation:
  - never fall back to "the first option" when nothing matches. A deny
    decision with only allow-shaped options answers cancelled instead.
  - "Disallow this tool" contains the substring "allow". The heuristic
    tier excludes it explicitly.

Also: allow_once is preferred over allow_always so a grant never widens
beyond the call that asked for it, and an unrecognised mode string is
reported as invalid rather than defaulting to allow-all — the caller fails
startup instead of running wide open by accident.

The clock is injectable, so the sliding rate window is tested without
sleeping: three requests in one minute storm, and the same asker two minutes
later does not.

  gofmt clean, go vet ok
  go test ./pkg/harness/adapters/acp/ -run TestPermission   8/8 PASS
  go test ./... — all green except pkg/store, which needs a non-root uid
                  (embedded-postgres; unrelated to this change)
Completes the working adapter: a phase machine (launch → initialize →
session/new → session/prompt → shutdown) that records HOW FAR it got,
because a failure before the prompt is an infrastructure problem with the
pod rather than a problem with the ticket. That is architecture.md §9.9 /
VIK-596 fixed at the source — and it needs no orchestrator change, since
resolveOutcome already prefers a valid structured report.

The dependency was audited before being taken, not after. `go list -deps`
shows the SDK compiles in with ZERO external packages — all 13 modules in
its graph are its own test dependencies. Repo goes from 3 direct + 7
indirect to 4 direct, with nothing new linked into the binary.

TWO REAL DEFECTS FOUND, both by tests rather than by reading:

1. The SDK drops usage silently, and my first defence was in the wrong
   place. coder/acp-go-sdk v0.13.5 is generated from schema 0.13.5, whose
   session/update union has no ACP v1 usage variant ({used,size,cost}) — so
   an agent sending v1 usage had it discarded by the SDK's decoder before
   ploeg's tolerant decoder ever ran. A decoder placed AFTER the SDK
   inherits whatever the SDK failed to parse. State is now folded from the
   RAW protocol line via a tap in the JSONL filter, BEFORE the SDK sees it;
   client.SessionUpdate is a deliberate no-op. This is the single most
   important line of defence in the adapter and it only held once the
   ordering was right.

2. conn.SetLogger races with the SDK's own read loop.
   NewClientSideConnection starts that loop before returning, so setting the
   logger afterwards is a data race — the detector flags it every run. We
   drop the call; the agent's output already reaches the pod log through the
   launcher's noise channel. Worth reporting upstream.

Also fixed: harness.TailBuffer was not concurrency-safe. RunCommand never
noticed because it reads the tail only after cmd.Run() returns, but a
session adapter pumps stderr on its own goroutine and reads the tail on
early-return paths. Fixed at the shared type (mutex + Bytes returns a copy)
rather than locally, so no future adapter has to remember. And a `cp :=`
that shadowed the outer checkpointer would have leaked its emitter
goroutine — caught while fixing the call sites.

Client half is deliberately two methods. fs/* is refused: pkg/worker embeds
AGENT_BUILDER_TOKEN in the clone URL so it lands in .git/config, and an
fs/read of that file would hand the forge token to the model provider
through a protocol-blessed path. terminal/* is refused: tool_call updates
already carry the command, its status and its output at the same fidelity.
elicitation/* is refused permanently — an agent needing a human IS the stuck
state.

Profiles: opencode (flagship, per homelab-cluster ADR-0051) and custom. The
opencode provider-config key names are the one thing needing verification
against a real binary, so PLOEG_ACP_CONFIG_JSON overrides the whole document
without an image rebuild. Config is written 0600 and trace-scoped, because
ScratchDir is os.TempDir() and shared across concurrent runs.

The conformance kernel now drives this adapter with plain shell scripts that
do NOT speak ACP — the likeliest real misconfiguration — and every property
holds. One kernel assumption had to be corrected: "exit 0 means success" is
spawn-and-wait-shaped; for a session adapter a binary that exits 0 without a
handshake never spoke the protocol, so an error there is correct. The
property now asserts what both shapes actually owe: never fabricate an
outcome.

  gofmt clean · go vet ok · go build ok
  go test ./pkg/harness/... -race    all ok (acp 7.178s)
  go test ./pkg/store/... ./internal/...  ok (unprivileged uid)
  helm lint + 3 templates            ok
os/exec copies a child's stderr on its own goroutine, and that copy is only
guaranteed complete once Wait returns. finish() read the tail buffer
immediately, so an agent that printed its complaint and exited at once raced
us and lost it.

That is precisely the wrong case to lose. `opencode: unknown command 'acp'`
— a wrong subcommand or a wrong image, the likeliest misconfiguration of this
adapter — arrived as a bare "peer disconnected before response", which names
nothing an operator can act on.

finish() now runs the shutdown sequence before reading the tail. Total wall
time is unchanged: the deferred shutdown ran before Run returned anyway.

Caught by TestRun_NonACPBinaryIsRetryableInfra, which had been passing on
timing luck; now verified with -race -count=3.

Also drops the stale `// indirect` on acp-go-sdk, which pkg/harness/adapters/acp
imports directly (go mod tidy).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wires the finished adapter into the three places a harness is chosen:

- worker.NewAdapter gains `case "acp"`. Profile resolution and permission-mode
  parsing both happen there, so a misconfigured team fails at worker startup
  rather than after leasing a ticket it cannot work — the same fail-before-Claim
  property the other adapters have. ACP is a session adapter, so it implements
  harness.Adapter directly and takes no RunCommand lift.
- cmd/ploeg-worker parses PLOEG_ACP_{PROFILE,ARGV,PERMISSION_MODE,
  PROMPT_TIMEOUT,IDLE_TIMEOUT,CONFIG_JSON}. The two durations are strict: a
  typo in a watchdog timeout that silently reverts to its default fires at the
  wrong moment and reads as an agent bug rather than a config error.
- The chart grows harness.acp, overridden per team by the existing
  field-by-field pattern (explicit hasKey, not sprig merge). A custom profile
  without argv fails at template time, not at run time.

New adapters_test.go pins the registry's one safety property: every
misconfiguration is an error at construction. Without it a bad harness value
surfaces as a pod that dies holding a lease — indistinguishable from an agent
crash, and retried MaxAttempts times before anyone notices.

ci/executor-values.yaml gains a third team so helm template exercises the ACP
env branch and the argv guard on every PR.

Gates: gofmt clean, go vet ok, go build ok, go test ./... ok (store under an
unprivileged uid), helm lint 1/0 failed, all three renderings ok, both chart
guards negative-tested.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Records the decomposition that lets several personas work one ticket at
once, and closes the `run vs job` ambiguity model.yaml has held open since
2026-07-22 — "a lease-level grouping term is deliberately not introduced
until parallel strategies demand one". They now do. The term is Shift.

0010 — a Shift owns the Work Item, its branch, its budget pool and its round
counter; a Lease narrows to WRITE access on that branch. The three jobs one
lease row used to do (exclusion, liveness, accounting) separate by lifetime.
Two consequences are part of the decision: Runs are writers or readers, and a
Round is either a fan-out of readers or one writer, never both — which is the
entire concurrency control, because readers need no lease and so have nothing
to coordinate over. Runs in a Round cannot observe each other; the evidence
says that is the stronger arrangement, not a compromise (debate is a
martingale on belief, weak models correct only 3.6% of stance biases and
conform instead, and debate can score below a single agent).

0011 — findings live on the pull request; Ploeg is only the courier. A reader
needs no new capability: it already writes an OutcomeReport, and ploegd
publishes and re-injects. No new table, no agent-side tooling, and readers
keep zero write access, which is what 0010's safety rests on. No standard was
adopted because none fits: A2A has no shared state and keeps context inside
agent processes, which R6 forbids.

0012 — a Shift holds the budget pool; each Run authorizes against it and
settles on report, the shape of a card payment. Concurrency safety is one
conditional UPDATE, the same compare-and-swap the lease already relies on.
Borrowing falls out for free because nothing is pre-allocated. Exhaustion is
needs_human with a reason (R4), never failed — retrying cannot fix running out
of money. Crash safety reuses the lease sweeper rather than adding a reaper.

This supersedes the plan's Phase 3 design, which released the lease between
personas and therefore needed a plan array, a cursor, a parallel rework array,
a role-scoped Claim and a KEDA scaler query mirroring it exactly. All of that
existed only to carry state across a boundary that design created. It also
contradicted model.yaml without recording the divergence — the failure
ADR-0001 exists to prevent. It is captured as a rejected option in 0010.

model.yaml: Shift and Round added to terms and entities, Lease narrowed, Team
and Role updated, four events added, and two tensions recorded as OPEN rather
than papered over — the `leased` state name now describes the wrong thing, and
whether a re-queue after needs_human resumes a Shift or opens a new one.

Generated domain views (docs/domain/*.md) still need a regen pass from
model.yaml; no generator lives in this repo.

Gates: go test ./internal/ledger/ passes all four (front matter, confirmation,
dated-review triggers, index parity); model.yaml parses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Runs the domain-language skill's generator over model.yaml:

    python scripts/generate_docs.py docs/domain/model.yaml -o docs/domain/

overview.md gains Shift in the ER diagram and both new open ambiguities at
the top; glossary.md gains Shift and Round with the narrowed Lease;
entities.md gains the Shift attribute table and Lease's new shift_id/run_id
shape; events.md gains ShiftOpened, RoundStarted, BudgetAuthorized and
BudgetSettled. rules.md is unchanged — no rule was edited.

Corrects the previous commit message, which said no generator exists in this
repo. True but beside the point: the generator is the domain-language skill,
and the views were left stale for one commit describing a Lease as unique per
Work Item, which ADR-0010 had already changed.

Health check: 9.8/10. The only deduction is four open ambiguities, two of
which this branch added on purpose — the `leased` state name and the Shift
close rule. Per the skill's own guidance, honest open questions beat a higher
score bought by deleting them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Lease recorded which Run may write the branch while every worker pod carried
the same static AGENT_BUILDER_TOKEN. The row asserted a right it could not
enforce, and ADR-0010 made that worse rather than better: reading Runs — the
population that will grow fastest — held push access to the very branch they
were reviewing, leaving the writer/reader split a convention rather than a
boundary.

0013 binds push rights to the Lease. A writing Run gets a repository-scoped
token minted for it and revoked when the Lease settles or lapses; a reading Run
gets none. Holding the Lease and being able to push become one fact instead of
two that can disagree. This is ADR-0008's reasoning applied to a second
resource: pkg/llmbroker already proves that an agent handed a $2 key cannot
spend $3, and money was the only thing enforced by capability.

Sequenced in two tiers because their costs differ by an order of magnitude.
Read-only credentials for readers need no new authority and close the hole
ADR-0010 opens; mint-and-revoke per writer closes the zombie-writer case and
rides the sweeper already being built for budget release (ADR-0012).

The cost is named rather than buried: ploegd gains authority to mint forge
credentials, trading one shared static token for a minting authority
concentrated in one service. Accepted on the same reasoning as
LITELLM_MASTER_KEY, with the same mitigation — the secret never reaches a
worker pod.

One fact is explicitly unverified and flagged in the record: whether Forgejo's
native API-token expiry (forgejo#8837) has shipped. Revocation alone suffices;
expiry would only be a backstop, and the ADR says not to rely on it unchecked.

0010 gains the driver that settled it and a cross-reference. model.yaml: Lease
redefined as a capability, a Push Credential term added, forge_token_id on the
Lease entity, and a PushRightsRevoked event.

The health check caught the first draft's Lease definition running to 766
characters — the historical note belonged in the ADR, not the glossary. Split
and tightened; model health back to 9.8/10, the only deduction being four
deliberately open ambiguities.

Gates: go test ./internal/ledger/ passes; domain views regenerated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found by running the local stack rather than by reading the code:

    POST /api/v1/runs/<token>/outcome
    {"outcome":"failed","failureReason":"vibes"}   -> HTTP 204

and `vibes` was stored verbatim in agent_runs.failure_reason.

This is a gap in my own Phase 0 change. It added failureReason to
docs/contracts/outcomereport.v1.schema.json as a five-value enum and pinned
the schema to the Go types in pkg/harness/contract_test.go — but nothing
evaluates that schema at request time. handleOutcome checked Outcome.Valid()
and the R4 stuck-reason rule, then passed failureReason straight through.

It matters beyond untidiness: pkg/worker's classification DEFERS to an
adapter-set failureReason, so `infra-llm` would not error — it would silently
override the orchestrator's own judgement about whether a failure is
retryable. pkg/harness/harnesstest holds adapters to this rule, but adapters
are not the only callers; ops/local/demo.sh posts with curl, and so will
anything implementing docs/contracts/executor.md.

Validation is extracted to a pure validateOutcomeReport so the rules are
testable without a database — pkg/httpapi had no test file at all. Two tests
cover the closed enums, R4, and that every published taxonomy value survives
the boundary (a conformant adapter must never be rejected for using a
documented value).

Adds ops/local/probe.sh, the negative half of demo.sh: demo.sh shows the happy
path works, probe.sh shows the guards hold. It exercises R4, the closed
taxonomy and R2 crash-safety over the real wire, and exits non-zero so it can
gate CI later.

The general lesson, worth watching for elsewhere: a published schema that
nothing evaluates at runtime is documentation, not a gate.

Verified: probe.sh green from a clean database (lease reclaimed after ~30s, a
swept run's report refused 404); gofmt clean, go vet ok, go test ./... ok
across all 16 packages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The foundation for several personas working one Work Item, some of them at
once. Implements ADR-0010 (Shift owns the item, Lease owns the branch) and
ADR-0012 (two-level budgets, authorized and settled).

The load-bearing choice, which improved on the ADRs while implementing them:
a Round MATERIALISES its Runs. Opening a Round inserts one pending agent_runs
row per Role; claiming flips a row to running. That buys three things at once:

  * The claim predicate and the KEDA scaler query become the SAME statement —
    pending runs for (team, role), oldest first. ADR-0010 flagged the mirrored
    scaler query as the design's highest-risk drift point, where undershoot
    stalls items with no error anywhere, forever. Materialising the round
    dissolves the hazard rather than guarding it.
  * Reserved budget is SUM(authorized) over running rows, so it cannot drift
    from what is actually in flight the way a counter can. ADR-0012 specified
    a `reserved` column; a derived sum is strictly better and one column less.
  * The roster is explicit and queryable instead of reconstructed.

Concurrency safety is the existing discipline, not a new one: ClaimRole locks
the Shift row before the pool arithmetic, so five readers starting together
cannot each see the full pool. The unique partial index shifts_one_live_per_item
means two Teams racing to open a Shift is a database error, not a race one of
them silently wins.

Readers take no Lease — that is what lets a fan-out run at once — and
OpenRound refuses a Round that mixes a writer with readers, or carries two
writers, rather than trusting callers with the one rule the whole concurrency
model rests on.

Tests are the acceptance criteria the ADRs named for themselves, plus two the
implementation turned up: a zero budget means unmetered (every team today), and
a refused claim must not consume its slot.

One migration fix while writing them: agent_runs.started_at has been NOT NULL
since 0001, correct when a row only existed once a pod was running. Pending
runs predate their pod, so the NOT NULL is dropped; the DEFAULT stays, leaving
the pre-Shift Claim path untouched.

Not yet wired: nothing opens a Shift or a Round, settlement does not run, and
the sweeper does not release holds. Store-level only.

Gates: gofmt clean, go vet ok, go build ok, 9 new store tests pass, all 8
pre-existing store tests still pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wiring settlement into ReportOutcome surfaced a defect in ADR-0010 that only
appeared on contact with the existing code.

ReportOutcome opened with `DELETE FROM leases WHERE run_token = $1 RETURNING`
— the compare-and-swap only one transaction can win, and the reason
advance-once is structurally impossible rather than merely tested. But readers
hold no Lease. Every reader's report would have failed with ErrUnknownRun, and
the whole reader population ADR-0010 exists to enable could never have
reported an outcome.

The CAS moves to the Run's own state transition: 'running' -> 'finished',
which exactly one caller wins, for readers and writers alike. The lease DELETE
stays but is now permitted to affect zero rows.

The same shape appears in liveness. ExpireLeases cannot reclaim a dead reader,
because there is no lease to expire — an OOM-killed reviewer would sit
'running' forever, holding budget nothing releases and leaving its Round
unable to complete. Liveness is per-Run because a Run is what dies, so
agent_runs carries its own deadline and ExpireRuns sweeps it. This is the
split described when the Lease's three jobs were separated: exclusion on the
Lease, liveness and accounting on the Run.

Settlement itself needs no release statement. `reserved` is summed over
running Runs, so a Run that stops running stops holding money — a missed
release is impossible rather than unlikely. Spend is recorded from the
reported usage; unspent authorization returns to the pool automatically, which
is the borrowing behaviour ADR-0012 promised.

Also adds RoundComplete, the derived signal that drives a Shift forward.

Migration 0007 amended rather than patched by an 0008: it has never been
applied outside ephemeral test databases and is unmerged, and an 0008 fixing
an 0007 nobody ran would be worse for readers. Two corrections: agent_runs.state
defaults to 'running' (an INSERT on the pre-Shift Claim path means a pod has
started) with historical rows corrected by finished_at, and expires_at is added
for per-Run liveness.

NOTE: ADR-0012 specifies `UPDATE shifts SET reserved = ...`. This implements
reserved as a derived SUM instead — strictly better, one column fewer, and it
cannot drift. The record and the code now disagree and the ADR needs amending.

Gates: gofmt clean, go vet ok, 13 Shift tests pass, all 8 pre-existing store
tests still pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two records claimed things the code disproved. Correcting the ledger rather
than letting it drift is the whole point of ADR-0001, and the drift was one
commit old.

ADR-0012 specified `UPDATE shifts SET reserved = ...`. The implementation
derives reserved as a SUM over running Runs instead — one column fewer, and it
cannot disagree with what is actually in flight. More importantly it removes
the release statement entirely: a Run that stops running stops holding money,
so a missed release became impossible rather than unlikely. The record now
describes what was built and says plainly that it replaces the counter an
earlier draft specified.

ADR-0010 gains a "What the implementation changed about this record" section
covering the two defects only contact with the code exposed:

  * the advance-once CAS could not stay on the Lease. ReportOutcome opened
    with DELETE FROM leases ... RETURNING, and readers hold no Lease, so every
    reader's report would have failed with ErrUnknownRun — the population the
    record exists to enable could never have reported an outcome;
  * liveness could not stay there either, for the same reason: an OOM-killed
    reader has no lease to expire and would hold budget forever.

Both Confirmation sections now name the tests that actually exist, and 0012
states which of its promises is still owed by the orchestration change (the
needs_human transition on an exhausted pool) rather than implying it is green.

architecture.md gains §10, the complete Shift picture, with five new Mermaid
diagrams: how the Lease's three jobs split by lifetime, the Round state
machine, the full loop as a sequence (readers concurrently, one writer, review
round, human pulled in to merge), the money flow, and the data model. §10.6 is
a per-component build-status table — the store layer is built and tested,
everything that would drive it is not, and the table says so component by
component rather than leaving a reader to infer it. §§1-9 still describe only
what is deployed today.

model.yaml: Lease narrowed to exclusion with liveness moved onto the Run, and
the Run entity gains round, writes, state, authorized and expires_at. Domain
views regenerated; health 9.8/10, the deduction being four deliberately open
ambiguities.

Gates: gofmt clean, go vet ok, go build ok, go test ./... ok across 13 packages
(13 Shift tests among them), helm lint 1/0 failed, both chart renderings ok,
ADR ledger validator green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The doc debt this branch has been carrying. Every entry now says what is true
rather than what was planned.

backlog #64 (ACP adapter) LANDED, with the shape that was actually built and
the one assumption still unverified — the opencode profile's provider-config
keys against a real binary, with PLOEG_ACP_CONFIG_JSON as the escape hatch
until someone checks.

backlog #63 (opencode adapter) CLOSED, superseded by #64. opencode speaks ACP
natively, so a bespoke `opencode serve` HTTP route would be a second way to do
one thing.

backlog #44 (budget plumbing) half-landed: the pre-dispatch gate it argued for
exists, ErrBudgetExhausted spawns nothing and burns no attempt. What is still
owed is the needs_human transition and the native harness levers.

backlog #69 (conformance suite) extended to session adapters plus the three
properties the ACP work demanded.

design.md §5 no longer calls ACP "a candidate standard to track" — it is
adopted, wire version 1 pinned, v2 explicitly not.

architecture.md §9: entry 2 gains the acp adapter, entry 4 (teams with
roles/strategies) is half-closed with an explicit note that nothing drives the
store layer yet, and entry 9 (the stuck/failed inversion) is partly closed —
honestly scoped to ACP-driven harnesses, since openhands and exec still infer
from exit codes and still park infra failures.

And the ACP-vs-ACP disambiguation, finally somewhere a reader will hit it
rather than buried in an ADR footnote: architecture §9.2 and design §5 both
say plainly that this is Zed's Agent CLIENT Protocol, not IBM's Agent
COMMUNICATION Protocol, which merged into A2A and is archived. That collision
cost a real detour in this branch's design discussion; a footnote was not
enough.

CHANGELOG.md deliberately untouched — semantic-release generates it.

Gates: ADR ledger validator green, contract tests green, all links resolve.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	docs/domain/entities.md
#	docs/domain/glossary.md
#	docs/domain/model.yaml
#	docs/domain/overview.md
#	ops/helm/ploeg/ci/executor-values.yaml
#	ops/helm/ploeg/templates/_helpers.tpl
The merge left two ADR directories side by side, each with its own numbering
and only one of them enforced. That is the exact failure ADR-0001 exists to
prevent, and CI was green throughout because the validator only ever looked at
docs/adrs.

  docs/adrs/  13 records, NNNN-title.md, gated by go test ./internal/ledger/
  docs/adr/    3 records + template, adr-NNNN-title.md, ungated

The three Work Target records move in as 0014-0016 and docs/adr/ is deleted.
Renumbered rather than renamed in place because the numbers collided: their
0001-0003 are this corpus's "ADRs are the decision ledger", "Go is the
implementation language" and "Apache-2.0". Every cross-reference between them
was repointed, along with the seven links from architecture.md and backlog.md.

Content is untouched. This is a reformat, not a rewrite: the reasoning, the
options and the Confirmation sections are the author's, and all three already
carried a Confirmation, so only frontmatter had to be added.

The two `proposed` records get `review-by: none` rather than a date I would
have had to invent triggers for. A proposed record is not in force, so a dated
review is meaningless — the decision to accept or drop it IS the review. The
accepted one (0014) is already implemented and needs no trigger either.

docs/adr/adr-0000-template.md is dropped in favour of docs/adrs/adr-template.md,
which encodes this repo's two local MADR extensions; the generic template would
produce records the validator rejects.

backlog #97 (ADRs) moves from "bootstrapped" to CLOSED: the retro-ADRs it asked
for exist, and the drift guard it wanted is Go rather than the
validate_adr_consistency.py it proposed — the runner guarantees Go, not python3.

Gates: 16 records, 16 index rows, all four ledger checks pass; every relative
link under docs/ resolves (0 broken); gofmt clean, go vet ok, go test ./... ok
across 14 packages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#18
The first change authored through the spec-driven-with-adr workflow adopted in
this branch, and the plan for getting from "the Shift store is built" to one
real run: several agents on different harnesses and different images, one PR,
reviewed by an agent that did not write it, ending with a human asked to merge.

proposal.md names the seams (tracker ingest, store/lease, run API, executor,
harness adapter — four of six, flagged rather than hidden) and four new
capabilities: shift-orchestration, role-claim, blackboard, and the first
forge-provider-forgejo implementation.

specs/ carries the requirements as scenarios, so acceptance criteria exist
before any code does. The ones worth reading are the negative cases: a Round
with one live reader must not advance, a swept Run must not block its Round
forever, a reviewer pod must be unable to push to the branch it is reviewing,
and a failed tracker write-back must not lose the outcome.

Non-goals are explicit because this change could sprawl: no parallel writers,
no agents conversing mid-Round, no ADR-0013 tier 2 credential minting, no
org.yaml reconciler, and no retro-fitting Shifts onto the spawn-and-wait
adapters' outcome inference.

The genuinely blocking dependency is external and stated as such: an
ACP-capable agent image must exist in webgrip/infrastructure. The chart already
references harbor.webgrip.dev/webgrip/opencode-runner:0.1.0 but its existence
is UNVERIFIED, and until one exists "different images" cannot be demonstrated
at all. The opencode ACP profile's provider-config keys are likewise unverified
against a real binary.

Validated with `openspec validate --all --strict` (1 passed, 0 failed).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#19
The three artifacts the spec-driven-with-adr workflow requires between the
proposal and code. design.md fixes the change-local decisions (after-commit
shift open + sweeper repair, one advancement engine, engine-owned item
transitions for shift runs, findings as markdown on the OutcomeReport, plan
config via PLOEG_TEAM_PLANS, ploegd as branch producer, Authorized-driven key
mint, honest tier-1 reader credentials, role-partitioned workloads with the
agent_runs_claimable scale predicate, and the delivery order that keeps
plan-less teams byte-identical until the final flip). adr.md records that no
new durable decision is introduced — verdict-driven advancement is explicitly
deferred to close-the-review-loop and its ADR-0017. tasks.md breaks the work
into ten dependency-ordered groups with the repo's gate rules attached.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Shift layer (0008) shipped with no way to close a Shift, no way to find
one, and three latent defects its first callers would have hit. This change
completes the store surface the shift engine needs, all dead code until the
engine lands:

- CloseShift: records why, cancels leftover pending runs so the scale signal
  drops to zero, idempotent (fast-path and sweeper may both close), and
  releases the shifts_one_live_per_item slot for a later re-mandate.
- LiveShifts / LiveShiftForItem: the sweeper's worklist and the idempotency
  read EnsureShift needs.
- OpenRound gains a compare-and-swap fromRound guard: two evaluators can no
  longer double-advance a round and materialise a duplicate roster.
- ReportOutcome returns OutcomeResult{WorkItemID, ShiftID}, persists findings
  (migration 0009, ADR-0011), and for shift runs leaves the work_items
  transition to the engine - three readers reporting must not flip the item's
  state three times. Legacy shift-less runs keep today's behaviour bit for bit.
- RoundReports: the blackboard read serving both the PR comment and the next
  round's prompt. ShiftsBelowFloor: the parking worklist for exhausted pools.

Fixed, each with a regression test proven to fail against the unfixed code:

- ClaimRole's RETURNING omitted the target_* columns, so a role-claimed run
  silently lost the resolved Work Target (ADR-0014) and fell back to the env
  repo.
- Renew only touched leases: a reader (no Lease, ADR-0010) got ErrUnknownRun
  on its first renew and cancelled itself at TTL/3; a writer outliving one
  TTL kept a live Lease while ExpireRuns reclaimed its run underneath it.
- Checkpoint resolved the item via leases, so readers could not checkpoint.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Team plan is the ordered list of Rounds a Shift works through — a reader
fan-out or a single writer per round (ADR-0010), each role with a per-Run cap
against the team's budget-as-pool (ADR-0012). Plans are config, not state:
executor.teams[].plan in Helm values is serialised once via toJson into
PLOEG_TEAM_PLANS on the ploegd deployment, so the shift engine (next change)
and the workload partitioning (later chart change) read one source of truth.

pkg/plan parses and validates fail-fast at boot, mirroring PLOEG_TARGET_MAP:
a malformed plan — mixed reader/writer round, two writers, non-DNS role name,
a role flipping its writes flag between rounds — refuses to start rather than
leasing a ticket it cannot work. Money fields accept both JSON numbers and
the chart's string-typed values. Chart-only role keys (model, harness,
maxReplicaCount) are deliberately ignored by the parser.

No fixture sets a plan, so every rendering is byte-identical to before; the
env block renders only when a plan exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#22
Reviewed-on: webgrip/ploeg#20
Reviewed-on: webgrip/ploeg#21
pkg/shiftengine is the one owner of every lifecycle rule, called from two
places with the same idempotent semantics — the fast path (webhook ingest
opens via EnsureShift, an outcome report evaluates via EvaluateItem) and the
sweeper tick (EvaluateAll repairs whatever a crash left behind). The same
claim/sweeper split the lease manager uses: the pipeline never depends on a
request handler surviving to the end of its function (R2), and advancement is
derived from Run state, never reported by an agent.

The rules, per the shift-orchestration spec: a queued item of a planned team
gets exactly one live Shift (branch derived server-side, pool from the plan);
a Round advances only when no Run in it is pending or running; a stuck Run
anywhere freezes the plan and parks the item; an exhausted plan closes the
Shift and asks a person to merge; a pool below the viable floor parks with a
reason naming the spend — no Run spawned, no key minted, no attempt burned.
A plan removed mid-Shift closes loudly instead of stranding the item.

Riding along, both engine prerequisites:
- Store.MarkNeedsHuman — the engine's item transition (ReportOutcome leaves
  shift-run items alone since the store change; somebody must move them).
- ExpireLeases now skips shift leases: a Shift writer's liveness is its Run's
  expires_at (ExpireRuns reclaims it and drops the lease), and the legacy
  sweep would have bounced the item to queued mid-Shift and charged the
  infra backoff for a lifecycle it does not own. Regression test included.
- The sweeper tick gains ExpireRuns (dead READERS hold no lease and were
  invisible to every sweep until now) and revokes their per-run keys too.

Inert in production: the engine is constructed only when PLOEG_TEAM_PLANS
names a team, and no live values do yet. Engine tests run the crash-state
inventory against a real Postgres: queued-without-Shift, complete-but-not-
advanced, stuck-not-closed, below-floor, expired reader blocking its round,
plan-less team untouched, re-mandate after close.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The run API learns Shifts, additively. POST /api/v1/claim accepts an optional
role: with one it claims that (team, role)'s oldest pending Run and authorizes
its budget in the same transaction; without one it is the pre-Shift claim,
byte-identical. The response gains shift/role/round/writes/branch/authorized
plus a briefing — earlier Rounds' findings, attributed per Role, so the agent
gains no client, tool or credential to reach the blackboard (ADR-0011, R6).
Runs in the SAME Round never appear in it (ADR-0010).

An exhausted Shift pool answers 204, not an error: nothing spawns, no key is
minted, no attempt is burned, and the sweeper parks the item with a reason
naming the spend. GET /api/v1/queue/depth gains a role filter answered by
PendingRuns — the identical predicate ClaimRole drains, tested by draining
against it, because overshoot wastes a pod while undershoot stalls Work Items
silently and forever.

harness.OutcomeReport gains findings, harness.TaskSpec gains briefing; all
three contract schemas change with the Go types, additively, in this commit.

Two behaviours found while testing this seam and pinned rather than left to be
discovered:

- An uncapped Role in a metered multi-role Round reserves the WHOLE pool on
  its first claim and starves its siblings — the fan-out silently becomes a
  queue of one. pkg/plan now refuses such a plan at boot: caps are what make
  readers concurrent, not a nicety.
- A dry Shift's pending Run head-of-line blocks the funded Shifts behind it in
  its (team, role) queue, because the claim refuses on budget rather than
  skipping. Deliberate — skipping would grow the claim a clause PendingRuns
  lacks, and that divergence is the bug this design is arranged to avoid — and
  bounded to one sweep interval, since closing the dry Shift cancels its
  pending Runs. Test documents it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PLOEG_ROLE selects what a worker claims; the branch now comes from the claim
(ploegd derived it at Shift open) with the old local derivation as fallback —
the same string either way, so a mixed-version rollout cannot split a ticket
across two branches. TaskSpec.Role is finally populated, after existing empty
since the seam was carved, and the minted credential is capped at the Shift's
authorization when there is one (ADR-0012's missing half) or the env budget
when there is not.

ComposePrompt gains three things. A reader contract: no branch, no commits,
no PR, findings delivered instead — scheduling and credentials enforce the
split (ADR-0010), but an agent that has not been told wastes its whole run
discovering it. A briefing section carrying earlier Rounds' findings
attributed per Role, size-capped so a verbose reader cannot crowd out the
ticket, and framed as evidence to weigh rather than instructions to follow
(the text is another model's output arriving in a higher-trust context,
backlog #9). And, for a writer, an update-the-open-PR clause instead of the
open-a-PR one, which required moving the pre-run PR lookup ahead of prompt
composition.

Fixed while wiring it: the openhands adapter's ParseOutcome returned an empty
report unconditionally, so a reading Run on the DEFAULT harness could never
return findings at all — the blackboard would have been dead on arrival for
milestone A. It now declares an optional outcome file, names it to the agent
via PLOEG_OUTCOME_FILE, clears any stale one from a previous run in the shared
scratch dir, and parses it when written. A writing Run is never asked for one
and leaves it absent, so the forge poll remains the sole ground truth there
and today's behaviour is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#25
Reviewed-on: webgrip/ploeg#23
Reviewed-on: webgrip/ploeg#24
Rescued from an uncommitted working tree in the shared checkout, where it had
been sitting since 2026-07-28 while development moved 35 commits ahead. Two
additions, rebased onto the current file so the ADR-index pointer that landed
meanwhile survives:

- the multi-session rule that prompted it — stage paths, never the tree, and
  treat git-status dirt you did not create as someone else's work;
- the warning that architecture.md §9 goes stale in BOTH directions, so its
  claims get checked against the code rather than repeated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
TestLauncher_KillReapsTheWholeProcessGroup has been failing the pipeline, and
the launcher was never at fault. Diagnosed by dumping the process table at the
point of failure:

    99 (sh) Z 1 93 ...
      PID  PPID  PGID STAT COMMAND
        1     0     1 Ssl  go
       99     1    93 Z    sh

State Z means the group signal DID arrive — the grandchild is a corpse, not a
survivor. It lingers because killing the group also killed its parent, so it
was reparented to PID 1, and PID 1 reaps orphans only if PID 1 is an init.
Under `docker run golang go test` (how AGENTS.md tells contributors without a
local toolchain to run the gates) and on the CI runner, PID 1 is the go
driver, which reaps nothing. kill(pid, 0) keeps succeeding for a zombie, so
the probe reported the corpse as alive and the assertion tested the
container's init rather than the launcher.

alive() now discounts zombies, reading Linux's process state from /proc and
locating the state character from the LAST ')' — the comm field is
parenthesised and may itself contain spaces and parens. On darwin, where PID 1
is launchd and does reap, there is nothing to correct for and the absence of
/proc reports false.

Verified in both directions: green as written, and still red with the
group-kill deliberately removed from execLauncher, so the property it protects
— a grandchild does not outlive the run holding the DinD socket and the
per-run LiteLLM key — is still enforced.

No production behaviour changes: the launcher is untouched, and a worker pod
runs one run and exits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## [0.2.0-rc.8](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.7...v0.2.0-rc.8) (2026-07-29)
Both executors now range over (team, Role) rather than team. A team with no
plan yields exactly one Role-less workload whose rendered manifest is
unchanged; a planned team yields one per distinct Role, each with its own
image, harness, model, dind setting and replica cap. That is what makes
"different agents, different harnesses, different images" literally true —
pod shape is fixed at render time, so it cannot be a runtime decision.

Role workloads scale on the role predicate, quoted beside the query it must
match byte-for-byte: pending runs for (team, role), served index-only by the
agent_runs_claimable index migration 0008 created verbatim for it. Overshoot
wastes a pod that exits 0; undershoot stalls Work Items with no error
anywhere, so this third copy of the predicate carries its leash in a comment.

SECURITY — the PolicyException stops tracking workload names.

The privileged DinD sidecar needs a Kyverno waiver, and the exception in
webgrip/homelab-cluster matched `scaledjob.keda.sh/name` with one entry per
team. Under role partitioning that meant a security-repo change for every new
writer Role, which is how waivers rot.

Two things were wrong with that shape, and both are fixed by keying the
exception to the HAZARD instead:

  * exception-governance forbids wildcards in resources.names[] — it says
    nothing about label selectors, and this exception never used names. The
    per-team convention was self-imposed, not required.
  * the Pod-level match was `app.kubernetes.io/name: ploeg-worker`, the label
    EVERY worker pod carries. Copper (dind: false) is waived today for
    privileged-containers, run-as-non-root and drop-all-capabilities while
    running no privileged container at all, and every reader would have
    inherited the same over-grant.

The chart now emits `ploeg.webgrip.dev/privileged-dind: "true"` only where it
actually renders the privileged sidecar. The waiver follows the privilege:
adding a Role costs no security-repo change, and readers — which run
dind: false — fall outside it entirely. Strictly narrower than today.

ADR-0013 tier 1 lands as far as this repo can take it: a reading Role draws
AGENT_BUILDER_TOKEN from executor.forgejo.readTokenSecret when configured, so
the reader/writer split is enforced by the forge and not only by scheduling.
Unset is documented as a known gap rather than a safe default — the repos are
private, so "no credential" cannot clone, and closing it is one OpenBao entry
plus one ExternalSecret, no chart or code change.

Also: LITELLM_KEY_BUDGET degrades to the Role's own cap rather than the team's
budget, because for a planned team that value is the SHIFT POOL and handing
one Run the whole pool would be wrong if the fallback ever applied.

Guarded by committed golden renders (scripts/helm-golden.sh, wired into CI).
The pod is a security boundary and the waiver is keyed on a label this chart
emits, so a manifest change now has to appear in the diff. Verified in both
directions: green as committed, and red when the privileged-dind label is
widened to pods that take no privilege. Two render-time guards added with
tests of their own: a Role defined twice with different settings fails the
render (one Role is one workload, so its shape cannot change between rounds),
and a workload name over 63 characters fails rather than being truncated into
a collision.

CI fixture gains a planned team covering the whole new branch — reader/writer
credential split, the dind-less reader pods, and a Role recurring across
rounds collapsing to one workload.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
provider.ForgeProvider has been declared since the SPI was carved with zero
implementations. This gives it its first one and its first caller in the same
change, because the two are the same story: the blackboard (ADR-0011) is a
reading Run's findings travelling to where a human is already looking.

pkg/provider/forgejo does two things and no more. Comment posts to a pull
request through the ISSUES endpoint — Forgejo models a PR as an issue with a
branch attached, and /pulls/{n}/comments would be a review comment on a diff
hunk, which a round's findings are not. ParseWebhook verifies the raw body
against X-Forgejo-Signature BEFORE any JSON parsing and normalizes
review_submitted / check_failed / merge_state_dirty, so the Follow-Ups of R9
need no further provider work. Events Ploeg does not act on are dropped
without error: a forge subscribes wider than the core consumes, and erroring
on every unrelated push is how a webhook ends up disabled.

The Vikunja provider's stubs become real. FetchItem gives the thin-payload
rule its authoritative half; Comment creates with PUT, not POST (the trap
recorded in docs/ops/board.md — a POST there silently does something else);
SetStatus writes only `done`, because needs_human and stale are NOT done and
marking them so would hide the item from the very board that has to act on
it. Inventing a label mapping for needs_human would put a Ploeg concept
inside the provider (R7); what a human needs — why it stopped and which PR to
look at — travels in the comment instead.

Both are opt-in by credential. Without a URL and token they keep the
prototype's logging no-op, so a deployment that has not been given tracker
credentials still finishes runs; it just does not update the board.

The engine now publishes each reading Run's findings when its Round completes
rather than at close, so a human watching the thread sees the review while the
writer is still working from it, and writes back to the tracker when the Shift
closes — the PR link plus a request to merge, which is what turns the handoff
from something noticed into something announced.

Publication is best-effort, everywhere and deliberately. A forge outage, a
tracker outage, an unresolved Work Target or a Shift with no pull request yet
must not stall the pipeline or lose an Outcome: every failure is logged and
none is returned into the lifecycle, and the tracker write-back happens AFTER
the state is durable so an outage can never leave a Shift open. Tests drive
both outages through a full plan and assert the Shift still closes and the
item still reaches needs_human. Round-1 readers routinely run before any PR
exists; their findings are not lost, they reach the writer through the
briefing on its claim.

Duplicate comments are possible and accepted: two evaluators can both observe
a completed Round before one wins the advance CAS. A duplicated comment is
visible and harmless; a missing one loses a review a human is waiting for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Until now an item was worked one of two ways depending on whether its team had
a plan, so "what is happening with this item" had two answers and only one of
them was queryable. Uniform dispatch gives a plan-less team a synthesized plan
— one Round, one writing Role — so every item has a Shift, a round counter, a
roster and a close reason.

This is the only change in the series that alters behaviour for teams nobody
reconfigured, which is why it ships behind PLOEG_SHIFTS_UNIFORM (default on).
Turning it off restores the pre-Shift path and needs nothing but a ploegd
restart: no chart change, no rollback, no migration.

Two things make it a bookkeeping change rather than a semantic one.

The synthesized Role has an EMPTY name, so its Run is claimable by exactly the
role-less worker every plan-less team already runs. The role-less claim now
tries the Shift path first and falls through to the pre-Shift claim when there
is no pending Run, so one pod serves both worlds and the kill switch really is
one.

And a synthesized Shift settles its item on the run's OUTCOME, not at
needs_human. A configured plan parks there because several specialists worked
the item and the last word is "a person is asked to merge"; a synthesized one
is the same single engagement as before, so pr_opened still means done.
Flipping every plain team's successful run to needs_human would have silently
rewritten what the board means. Stuck still parks (R4), and failed still
re-queues under the attempt cap and stales past it (R5) — Store.SettleItem
carries the same retry rule ReportOutcome had, because the engine now owns
that transition for Shift runs.

The sweeper's repair worklist moves from "iterate the configured teams" to a
database query for queued items with no live Shift, since under uniform
dispatch a team with no plan — or one whose plan was removed — is in scope
too. Switching the flag off under a live synthesized Shift closes it loudly
and hands the item to a person rather than stranding it.

Tests cover what must NOT change: the role-less claim shape, unmetered budget,
each terminal outcome's legacy meaning, the retry threshold, the kill switch
restoring the legacy claim, and sweeping an unplanned team.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
run-multi-agent-shifts puts several agents on one item and gets a reviewer's
findings onto the pull request. It cannot act on them: a plan is a fixed list
of Rounds, so a reviewer that finds a real defect has nowhere to send it, and
if the reviewer was last the Shift closes with the defect recorded and
unfixed. A human requesting changes has the same problem by a different route
— there is no forge webhook endpoint, so the only way back in is re-assigning
the ticket by hand.

ADR-0017 decides the shape: a reading Role may return approve or
request_changes, and a request re-opens the plan's OWN last writing Round,
bounded in order by the Shift pool, a maxFixRounds cap, and the verdict. The
count of fix rounds is derived from the round counter rather than stored,
following ADR-0012's reserved-is-a-sum discipline.

The boundary is the point of the record. A verdict cannot name a Role, author
a Round, raise the cap or extend the budget — it is one bit that may re-run
work the operator already configured. An agent that lies about needing changes
wastes at most maxFixRounds writer Runs against a pool that was already
bounded, which is the same exposure as a writer that loops on its own. A
verdict from a writing Role is ignored, because a writer approving its own
work would be the loop grading itself.

Rejected alternatives are recorded with their reasons: fixed plans padded with
fix rounds (pays for a round it usually does not need and still cannot handle
a second round of feedback), letting the reviewer nominate the next Round
(hands an agent authorship of the work plan, which is what R2 exists to
prevent), and looping until a reviewer reports no findings (never terminates
for a reviewer with opinions about style).

The change also lands the forge webhook endpoint, giving
ForgeProvider.ParseWebhook the caller it has never had. Deliberately inert
beyond auditing: routing a review into a re-mandate needs the branch-to-item
lookup backlog #107 owes it, and the cluster network path is blocked in both
directions today.

ADR-0017 stays PROPOSED. It is not mine to accept, and it is the first place
an agent's output influences what runs next — the tasks file sequences the
implementation behind a human ratifying it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Implements ADR-0017. A reading Run may return approve or request_changes in
its OutcomeReport; a request re-opens the plan's OWN last writing Round with
the findings attached, then the review Round after it. Each pair is one fix
round. Before this, a reviewer that found a real defect had nowhere to send
it — the plan's next entry opened regardless, and if the reviewer was last the
Shift closed with the defect recorded and unfixed.

MERGING THIS RATIFIES ADR-0017, which is still `proposed`. It is the first
place an agent's output influences what runs next, so the boundary is worth
reading before it lands: a verdict names no Role, authors no Round, raises no
cap and extends no budget. It is one bit that may re-run work the operator
already configured.

The bounds are checked in the record's order, and each closes with its own
reason so "why did this item stop" stays a query rather than a reconstruction:

  1. the pool — money is the limit that cannot be argued with, so no Run is
     ever spawned that the Shift cannot pay for;
  2. maxFixRounds — the cap, default off, configured per Team;
  3. the verdict — the only bound an agent influences, and checked last.

A writer's verdict is ignored twice over: the store blanks it on write
(CASE WHEN writes THEN ''), and the loop skips writing Runs when it looks. A
writer approving its own work would be the loop grading itself, and one guard
could be refactored away without the other noticing.

The fix-round count is derived from the Shift's round counter against the
plan's length, never stored — the same discipline ADR-0012 applies to
`reserved`. It cannot drift from what happened, and it survives a restart
mid-loop for free, which a test pins directly.

pkg/plan refuses maxFixRounds > 0 on a plan with no writing Round at boot,
rather than at the moment a reviewer first asks for changes: a plan that
cannot fix anything would otherwise look healthy for hours and then quietly
ignore its first real verdict.

The reviewer prompt now asks for the verdict and says what each value does —
including that request_changes sends work back to the writer, so it is for
things that must change rather than for thoroughness. Migration 0010 adds the
column with a CHECK constraining it to the two values plus empty; the schema
enum, the Go type and the boundary validator change together.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
POST /webhooks/forge/{provider} gives ForgeProvider.ParseWebhook the caller it
has never had. The signature is verified against the raw body before anything
is parsed (backlog #2), the handler does no expensive work — Forgejo's
DELIVER_TIMEOUT is 5 seconds and a slow endpoint becomes a disabled one
(backlog #3) — and every accepted event lands in the audit log.

It acts on nothing, deliberately. Routing a submitted review into a re-mandate
needs the branch-to-Work-Item lookup backlog #107 owes it, and there are now
TWO paths that mean "keep going" — an agent's verdict (ADR-0017) and a human's
review. Reconciling them is a decision, not a merge order, and ADR-0017 names
the arrival of this route as the trigger to make it. What lands now is the
endpoint, so the events are recorded from the day the network path opens
rather than from the day somebody notices it was never wired.

Dedup is a table, not a cache (migration 0011). A forge retries what it thinks
failed, and a retry that acts twice turns one review into two fix rounds; an
in-memory set would forget across exactly the restart a redelivery is most
likely to follow. The insert IS the check — ON CONFLICT DO NOTHING — so two
concurrent deliveries of one id cannot both conclude they are first. Ids are
swept with the leases after 48 hours, well past any forge's retry window.

A missing delivery header is treated as fresh rather than as a duplicate: a
forge that sends none must not have every event silently dropped.

The event BODY is not stored. It is text written outside the factory
(backlog #9), and an audit row is read by humans and future prompts alike; the
metadata is what routing will need.

Tests: verified event recorded, wrong and missing signatures rejected with
nothing touched, three deliveries of one id acting once, no-delivery-header
not deduped, unknown provider 404, and a push webhook creating neither an
audit row nor a work item.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The change is built, so its specs are promoted to openspec/specs/ and the
change folder is archived. architecture.md §9 is the file that "goes stale in
BOTH directions" by its own warning, so it gets corrected rather than
appended to:

- §9.4 (teams with roles/strategies) closes. The engine opens, advances and
  closes Shifts; a plan renders one workload per (team, role); uniform
  dispatch gives a plan-less team a synthesized one-writer Shift. Still no
  teams table — a team remains Helm values, and that stays recorded.
- §9.6 (tracker write-backs) closes, with the constraint that matters kept
  visible: they are opt-in by credential, and SetStatus writes only `done`
  because needs_human has no Vikunja column and inventing one would put a
  Ploeg concept in the provider.
- §9.1 (PR follow-up ingestion) moves to PARTLY closed, and says exactly what
  is still missing rather than reading as done: the provider and the verified,
  deduplicated route exist, but nothing acts on an event, origin=follow_up is
  still never produced, and — the part a reader would otherwise discover in
  production — the route is unreachable in the live cluster, because
  forgejo→ploeg is blocked in both directions. That is ops work, not a code
  change, and it is now written down where someone debugging will find it.

§7 gains the forge route and the role-scoped claim and depth parameters; §8
describes plans, per-(team, role) workloads and the uniform switch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The engine looks up Forges[target.Forge] to publish findings, but ploegd
registered the provider under fj.Name(). Those are different things
(ADR-0016): the forge ID identifies an INSTANCE, the dialect name identifies
the API shape, and one deployment could hold two Forgejo instances under
different ids. With PLOEG_TARGET_FORGE set to anything but "forgejo" — which
is exactly what the routing map configures — the lookup matched nothing and
every publication was silently skipped, logging a warning nobody would read
until they noticed the PR had no review comments on it.

Registered under both now: the target id (what the engine looks up) and the
dialect name (what a webhook path names). Found by wiring the cluster values
against the code rather than by either alone, so the regression test asserts
the engine's key IS the target's id by making the fake's Name() deliberately
different from it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#26
## [0.2.0-rc.9](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.8...v0.2.0-rc.9) (2026-07-29)
Two changes that were asked for directly, plus the ADR the second one needed.

CONFIGURATION MOVES OUT OF ENVIRONMENT VARIABLES.

PLOEG_TARGET_MAP, PLOEG_TEAM_MAP and PLOEG_TEAM_PLANS were three hand-rolled
DSLs with no schema, no comments and no diff worth reading. They are replaced
by one YAML file, rendered from Helm values into a ConfigMap and mounted at
/etc/ploeg/ploeg.yaml. A typo'd key now fails the boot (KnownFields) instead
of silently taking a default.

The sharper half is the magic numbers. "11/bronze=webgrip/ploeg@development"
put a Vikunja project ID into cluster config, where a bare 11 says nothing
about which board it is, cannot be reviewed, and silently routes work to the
wrong repository the day the project is rebuilt with a new id. Projects are
NAMED now:

    trackers:
      vikunja:
        projects:
          - name: "Ploeg Test"
            repo: webgrip/ploeg
            branch: development

ploegd asks the tracker which id that name has at boot, logs what it resolved,
and refuses to start if the name matches nothing — with the available names in
the error, so the operator can fix it. `id:` remains as an escape hatch for a
board whose names are not unique. The env vars still work when the file is
absent, so this migrates one deployment at a time.

It renders to the same wire format pkg/target already parses, so there is one
routing resolver in the codebase, not two that drift.

PUSH RIGHTS ARE MINTED PER RUN (ADR-0013 tier 2, now accepted).

Tier 1 gave readers a weaker static token, which closes the hole that matters.
This closes the other one: a writer pod partitioned from ploegd keeps running
after its Lease expires, and with a shared static credential it can still push
to a branch another Run has since taken over. Now a writing Run gets a
write:repository token minted for it alone, named ploeg-run-<12hex>-<repo> so
a token in the forge UI traces to a Run, a Shift and a ticket the way the
LiteLLM alias does. Revoked on report, on lease expiry by the sweeper, and by
a boot sweep for whatever a crashed ploegd left behind — the same three-layer
shape ADR-0008 uses for spend, because that pattern is proven here and a
second novel one would be a second thing to get wrong.

If the mint fails, nothing runs: the Run is finished as a retryable infra
failure and the pod exits empty-handed, rather than proceeding with a
credential we did not intend to hand out. Readers get nothing minted at all —
no Lease, no business pushing.

The admin credential lives only in ploegd, never in a worker pod (R6). That is
a real escalation and ADR-0013 accepts it explicitly, on ADR-0008's reasoning.
Unset, everything falls back to the shared token and the worker path is
identical, so this ships dark.

Honest limitation, recorded in the code: Forgejo's token API scopes by
permission, not by repository, so the repo in the token name is audit rather
than enforcement — the bot's own repository access is still what bounds it.

ADR-0017 flipped proposed -> accepted: its implementation merged in #26.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#27
## 0.2.0-rc.10 (2026-07-29)

* Merge pull request 'feat(config): routing and roster as a file, and push rights minted per Run' (#27 ([c166e32](c166e32)), closes [#27](webgrip/ploeg#27)
* feat(config): routing and roster as a file, and push rights minted per Run ([45b343f](45b343f))
Validate() keyed its duplicate check on the project label alone, so two
entries naming the same board with different teams were rejected as
"routed twice". Twenty lines further down TargetSpec() renders exactly
that config as "<id>/<team>=repo" entries, and pkg/target sorts the
team-specific rule ahead of the bare one — one tracker project serving
several repositories is the transitional routing the resolver exists to
express. Validation forbade the config the same package generates.

The consequence was not a lint failure: config.Load() is called at
ploegd boot and a validation error is fatal, so rc.10 with per-team
routing in its ConfigMap crash-looped before it could serve a webhook.

Key the check on project AND team. The same team twice on one project is
still refused — that one really is ambiguous, and pkg/target would
silently keep whichever rule sorted first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The release commit bumped Chart.yaml version/appVersion to 0.2.0-rc.10
but left the golden renders at rc.9, so `helm-golden.sh check` failed on
this branch. Regenerated with the pinned CI helm (v4.2.3).

Diff is version strings only — the three chart-version labels per object
and the ploegd image tag. No securityContext, privileged container, or
ploeg.webgrip.dev/privileged-dind label changed, so the Kyverno
PolicyException in webgrip/homelab-cluster is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#29
## 0.2.0-rc.11 (2026-07-30)

* Merge pull request 'fix(config): per-team routing on one project is valid, not a duplicate' (#29) fr ([591bbc5](591bbc5)), closes [#29](webgrip/ploeg#29)
* chore(helm): refresh chart goldens for v0.2.0-rc.10 ([b3b5089](b3b5089))
* fix(config): per-team routing on one project is valid, not a duplicate ([0b5a05b](0b5a05b))
Every rc rewrites Chart.yaml version/appVersion, and those strings land
in three labels on every rendered object plus the ploegd image tag. The
goldens went stale on rc.9 -> rc.10 and again on rc.10 -> rc.11, each
time failing the next pull request on a diff that carried no decision —
a check that cries wolf on a machine-generated bump is a check people
learn to update without reading, which is exactly the habit the goldens
exist to prevent.

Substitute the current chart version for a fixed token on both sides of
the diff. Nothing is given up: the substitution is keyed on the version
Chart.yaml currently holds, so an image tag that stopped tracking
.Chart.AppVersion no longer matches it and still shows up.

Verified both directions with the CI-pinned helm (v4.2.3): bumping
Chart.yaml to 9.9.9-rc.99 leaves the check green, and adding a label
next to ploeg.webgrip.dev/privileged-dind in _helpers.tpl still fails it
with the new line in the diff.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## 0.2.0-rc.12 (2026-07-30)

* fix(ci): substitute the chart version out of the helm goldens ([bc24da2](bc24da2))
A Shift that opened a pull request told the board nothing. close() gated the
write-back on `next == StateNeedsHuman`, but a plan-less team's pr_opened
settles StateDone, so the successful path — the one that matters — skipped
notifyHuman entirely. No error was logged because no call was made. Observed
in production 2026-07-30: PR #30 opened, ticket 580 left untouched.

- work.Terminal() gates the write-back instead. queued is the only settle
  result that is not terminal; announcing a stopped Shift mid-retry is a lie.
- SettleItem returns the state it actually wrote. queued coerces to stale at
  the attempt cap, and the caller could not tell "failed, retrying" from
  "failed, gave up". The audit row now records the effective state too — it
  used to claim work_item.queued for a row that went stale.
- CloseShift reports whether THIS call won the CAS. Both racers still settle
  the item (crash repair), but only the winner comments, so the outcome
  fast-path and the sweeper stop double-posting.
- notifyHuman -> notifyTracker, with per-terminal-state wording extracted to
  a pure trackerMessage() that is table-testable without Postgres.

Ploeg still never closes a task. This deployment's Definition of Done is
"in production, monitored, first telemetry observed" — Ploeg opens a PR and
stops, so it is never in a position to know. The status write stays
needs_human, now as a documented rule rather than an unreachable accident.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AssigneeTeams() ranged a Go map and let the last writer win, so a username
listed under two teams resolved to a different team on different boots of the
SAME config. Measured on rc.12 in one process: 168/200 vs 32/200. Validate()
had no check for it, so the config loaded clean and misrouted quietly.

Reject it at boot, naming both teams and the username. Cross-team only — the
same name twice inside one team is a harmless typo that resolves identically
either way, and failing on it would be a gratuitous outage.

AssigneeTeams() itself now walks teams in sorted order too. Validate() only
runs from Load(), so a File built anywhere else would keep the coin flip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two lines that misled during the 2026-07-30 e2e audit.

The worker logged "using a per-run forge credential" whenever the claim
carried a token — but with PLOEG_FORGEJO_ADMIN_TOKEN unset, forgebroker.Static
returns the SHARED token in that same field, with no id. ploegd said per-run
credentials were disabled while the worker said it was using one, and both
were reading the same run. cred.ID was already the truth predicate; surface it
as claimResponse.forgeTokenPerRun so the worker can say which it holds.

resolveTarget logged only its two failure branches, so a CORRECT routing
decision was invisible in ploegd's log and first appeared a hop later in the
worker — after a pod was scheduled and an agent had started. That is the
wrong end of the pipeline to learn where the work is going.

Also fixes contract drift: run-api.v1.schema.json declares
additionalProperties:false on claimResponse and never listed forgeToken at
all, so the wire had been violating its own published contract.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps found while sizing a multi-Role Shift against a real cluster.

Worker pods named no ServiceAccount, so the API server gave them 'default'.
That trips Kyverno's require-non-default-serviceaccount and makes every
workload in the namespace indistinguishable in an audit log. They now get
<fullname>-worker, created by the chart and overridable via
executor.serviceAccountName. Deliberately not ploegd's account: ploegd holds
the LiteLLM master key and the forge admin credential, and a future
RoleBinding on that name must not reach the agent pods. The token stays
unmounted — this is identity, not privilege.

Resources were executor-wide only, so every reader in a fan-out Round asked
for a whole writer-sized pod. Three readers at 1 CPU / 1Gi do not fit a
one-node worker pool, and readers do not build anything. roles[] now accept
workerResources / dindResources, same field-by-field override shape as the
existing per-Role harness.

Verified by rendering: builder keeps the executor default (1 / 1Gi), reviewer
takes its override (500m / 384Mi), both on sa=ploeg-worker. Goldens are
additions only and the privileged-dind label count is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#31
## 0.2.0-rc.13 (2026-07-30)

* fix(worker,httpapi): tell the truth about the forge credential, log routing ([51817e2](51817e2))
* Merge pull request 'fix(shiftengine): tell the board when a Shift finishes' (#31) from fix/tracker-w ([3ad7473](3ad7473)), closes [#31](webgrip/ploeg#31)
* feat(chart): worker ServiceAccount and per-Role resources ([2a9b184](2a9b184))
* fix(config): reject an assignee shared by two teams ([3ec972f](3ec972f))
* fix(shiftengine): write back to the tracker on every terminal settle ([93ade62](93ade62)), closes [#30](webgrip/ploeg#30)
All three broke, or hid, real dispatch on 2026-07-30.

1. The worker ServiceAccount guard inferred create from name.
   `if and .Values.executor.enabled (not .Values.executor.serviceAccountName)`
   read a name as "an external account exists", so naming the chart's OWN
   default suppressed the account the pod template then referenced and every
   worker Job died with `serviceaccount "ploeg-worker" not found`. Naming the
   default and saying nothing must be equivalent; they were opposite. Split
   into executor.serviceAccount.{create,name}, and route the object and the
   reference through one helper so they cannot disagree again.

2. The DinD hazard label never reached the Job.
   It was stamped on the pod TEMPLATE only. Kyverno autogens a Job rule for
   pod-security-baseline-enforce, and a Job selector matches the JOB's own
   labels — so the PolicyException could not admit the Job and every DinD team
   was rejected at admission. KEDA copies ScaledJob labels onto its Jobs, so
   the label now goes there too, gated by the same role->team->global dind
   resolution the pod template uses (extracted to ploeg.roleUsesDind).
   Consumers can then key their exception on the hazard instead of on a list
   of workload names.

3. maxFixRounds was never serialised.
   PLOEG_TEAM_PLANS was built as {pool, rounds}, dropping the field entirely,
   so ADR-0017's request_changes loop was unreachable from Helm values — a
   reviewer verdict could never re-open the writing Round.

Verified by rendering: naming the default now both creates and references the
account; create:false skips creation and keeps the external reference; the
hazard label appears on a dind role's ScaledJob and pod template and on
neither for a dind:false role; and maxFixRounds:2 reaches PLOEG_TEAM_PLANS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A reading Run could not review anything, and was trusted not to write on the
strength of a sentence that was false.

- It never received the code. The clone is `--depth 50 --branch <base>`, and
  --depth implies --single-branch, so the writer's branch was absent from the
  repository entirely — while the review contract told the agent "the
  repository checkout is your working directory, on branch <shift branch>".
  Readers now fetch that branch and stand on it, with the base still present
  so `git diff base...branch` is the change under review.

- It was never told the pull request existed. priorPR was interpolated only
  into the writer's contract, so "review the pull request" was unsayable.

- It held a full push credential. AGENT_BUILDER_TOKEN is requireEnv on every
  worker pod, BaseEnv: os.Environ() handed it to the agent, and authURL baked
  it into origin. The contract said "you hold no write credential, so a push
  will be rejected by the forge" — untrue on every deployment. A reading run
  now has the token scrubbed from its agent's environment (matched by value,
  not by name, so a rename cannot reopen it) and its origin reset to a
  credential-free URL. The contract now describes that control instead of
  asserting a fiction.

- A clean reader was recorded as pr_updated. It stands on the writer's branch
  and finds the writer's PR, so the runErr==nil && prURL!="" arm credited it
  with a push it cannot perform — on any plan ending in review, that is every
  successful Shift. Readers resolve to no_change_needed and carry their
  findings.

Mutation-tested both ways: with the scrub disabled the reading-run tests fail,
with it restored they pass, and the writer-keeps-its-token case holds
throughout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
trackerMessage keyed its opening line on the settled state alone. A CONFIGURED
plan settles needs_human on SUCCESS by design — the last word is "a person is
asked to merge" — so every successful multi-Round Shift opened its board
comment with "Ploeg stopped working this item" directly above a link to the
pull request it had just produced.

The state says who owes the next move; it does not say how the run went. A
pull request existing now outranks it, and the genuinely empty-handed case
gets wording that names what is missing.

Regression case added to the existing table and mutation-tested: with the old
condition restored it fails and the other four cases keep passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The branch fetch I added would have parked silver's analyst Role on arrival.
A plan can open with a reading Round — recon the ticket BEFORE the builder
writes — and there is no branch to fetch then, so treating a missing branch as
stuck fails a Round that was never going to find one.

A missing branch now leaves the reader on the base and says so in the log, and
the contract tells the agent which situation it is in: standing on the work
with a diff to read, or ahead of the work with only the existing code. Either
way it is true, which is the whole point of the change it rides on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ploeg is the trial for webgrip/workflows ADR-0005: the release toolchain becomes
an image instead of an install. Run 122 is the before picture — 413 packages in
2m, 15 more in 21s, an npm audit summary nobody reads, and a yq download, all to
spend ~25s cutting v0.2.0-rc.9. None of it locked, so that release and the next
one were not guaranteed to run the same plugin versions.

harbor.webgrip.dev/webgrip/semantic-release:0.1.2 carries node, git, yq,
semantic-release 25 and @webgrip/semantic-release-config from a committed
lockfile, and exports SEMREL_PREBAKED — which is all the composite needs to skip
installing entirely. Built in webgrip/infrastructure.

Two deliberate limits on this commit:

The container is on the release job ONLY. `checks` needs go and helm, which this
image does not carry, and gluing them in would make the release toolchain a
build farm — the thing rust-releaser already is and this family exists not to be.

The composite is pinned to a COMMIT on the semrel branch, not @main and not the
branch name. @main still installs at release time and would ignore the image;
pinning the branch would let a later push change what this release runs. Flip it
back to @main once that PR merges — this pin is not meant to outlive the trial.

This is the first time the container path runs anywhere. If the image is missing
something ploeg's config reaches for, this is where it surfaces: the config is
makeConfig({manifest:'helm'}) with the dependency-free Chart.yaml bump, which
needs nothing outside the image, so the honest expectation is a clean release
with no npm output at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## 0.2.0-rc.14 (2026-07-30)

* Merge pull request 'fix(worker,shiftengine,chart): make a reading Role able to review — and unable t ([a124eff](a124eff)), closes [#32](webgrip/ploeg#32)
* fix(chart): three defects found by running rc.13 in production ([1cb5fdd](1cb5fdd))
* fix(shiftengine): a successful review must not read as a stoppage ([848c911](848c911))
* fix(worker): a reading Round may run before any branch exists ([f0cd7bd](f0cd7bd))
* fix(worker): give a reader the work, and take away the credential ([e2d4f25](e2d4f25))
Reviewed-on: webgrip/ploeg#33
Run 143 was half a test. It ran the composite in
harbor.webgrip.dev/webgrip/semantic-release:0.1.2 and showed exactly what the toolchain
image is for — zero npm output, 44s for the whole composite against 3m45s of installing —
but it analyzed two commits, found neither releasable, and stopped. Everything after
analyzeCommits is still unproven on this path: prepare, the makeConfig({manifest:'helm'})
Chart.yaml version/appVersion bump, @semantic-release/git's push-back through the new
public-host extraheader, and the Gitea publish.

This commit is a `fix:` on purpose. It releases, so the rest of the path runs.

The pin moves to 2c5c68c, which changes the composite's prebake guard from "is there a
file at $SEMREL_PREBAKED/.bin/semantic-release" to "is that tree actually the ADR-0005
toolchain". ploeg is not what that fixes — in the container the answer is yes either way —
but ploeg is what proves the probe does not break the case that already worked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## [0.2.0-rc.15](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.14...v0.2.0-rc.15) (2026-07-31)
Five defects, all found by running rc.14 in production on 2026-07-30 and then
reading what it actually did rather than what it logged.

1. Every review ever written was silently discarded. PLOEG_TARGET_FORGE was
   read twice in cmd/ploegd: once with a "forgejo" default for the forge
   registry key, once with NO default for the target resolver. So the registry
   was keyed "forgejo" while every resolved Target carried forge="", and
   publishRound looked the empty string up as a literal key and missed. Seen
   live as `findings not published: no provider for forge forge="" shift=4`
   moments after a reviewer returned verdict=approve on erfbeeld#9.

   One value now, forgeIDFromEnv(), feeding all three consumers. And
   publishRound honours what pkg/work.Target and pkg/config have documented
   the whole time — "empty = the default forge" — which is what makes the rows
   already in the database publishable rather than needing a migration. The
   fallback is a configured id, never an inference from len(Forges): with two
   forges registered, guessing posts an internal review onto an unrelated
   public pull request.

2. Per-run keys had no expiry at all. MintRequest sent `max_hours_ttl`, which
   is not a field LiteLLM has — GenerateKeyRequest ignores unknown keys, so
   LITELLM_KEY_DURATION was parsed, converted, serialised and dropped on the
   floor while three separate comments called the TTL "the backstop". Now
   `duration`, in seconds so nothing rounds away.

3. Budget 0 minted an UNLIMITED key, silently, because max_budget is
   omitempty and the worker discarded strconv.ParseFloat's error. Both ends
   fail closed now.

4. The orphan sweep ran only at boot. A run that finishes normally but whose
   deferred revoke fails is marked finished, and neither ExpireLeases nor
   ExpireRuns will ever look at it again — both only see state='running'. Its
   key outlived every mechanism until the next restart. That is not
   hypothetical: eight keys for finished runs accumulated 2026-07-24..27 and
   stayed live for about a day. Now also on a 15m ticker.

5. shifts.spent was 0.0000 on every row ever written, so the Shift pool
   bounded nothing. Settlement adds COALESCE(usage->>'costUsd', 0) and the two
   adapters actually deployed (openhands, exec) report no usage at all — the
   COALESCE that was meant to tolerate a missing cost permanently masked its
   absence. The worker now settles the run's cost from the GATEWAY before
   revoking the credential, polling until the figure stops moving because
   LiteLLM writes spend asynchronously. Authoritative, not self-reported: the
   adapters that could answer this all report their own spend, and asking an
   agent what it cost is not a control.

   This one is load-bearing for the review loop. reviewloop.go checks money
   before the fix-round cap, deliberately, and that bound reads shifts.spent —
   so enabling maxFixRounds while spent stayed 0 would have run the loop with
   its first bound inert.

Tests. The forge fix is mutation-tested both ways: reverting the default makes
TestPublish_EmptyForgeIDUsesTheDefault fail, restoring it passes, and
TestPublish_EmptyForgeIDWithNoDefaultPublishesNothing holds in both directions
so a crashing publisher can't pass for a working one.

Worth naming why the existing suite missed this, because the shape recurs:
every layer was tested and the seam between them was not. cmd/ had no test
files at all, so the value's producer and its consumer were never in the same
test process. publish_test.go even has a test whose comment says it was "found
by wiring the two ends together" — the two ends it wires are two test literals
four lines apart. In resolver_test.go the only cases asserting on .Forge are
the two that pass a non-empty default; the five that pass "" — the production
configuration — never look at the field. Same for the TTL: the old assertion
checked MaxHoursTTL == 4 and passed for months while the receiver had nowhere
to put it. A value reaching the wire says nothing about the wire meaning
anything.

Chart: `perRunBudget`, additive and optional. `budget` meant the Shift pool
for a planned team and the per-run key ceiling for a plan-less one, one word
for two ceilings, and the HelmRelease comments had drifted from the values in
both directions. Goldens verified unchanged under the pinned helm v4.2.3 —
locally helm 3 renders trailing blank lines differently, which is churn this
commit deliberately does not carry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
settleSpend polls until the gateway's figure stops CHANGING, not until it is
non-zero, and both halves of that matter:

- LiteLLM writes spend asynchronously, so the first read after a run ends is
  routinely stale. Settling on the first non-zero reading would record a
  partial cost as the final one.
- A run that genuinely spent nothing — the exec harness never calls an LLM —
  must settle immediately instead of burning the whole 20s budget waiting for
  a number that will never arrive.

Also pins that a failed first read surfaces as an error rather than a
confident 0.00. A silent zero is precisely how shifts.spent stayed 0.0000 for
this system's entire life: the settlement SQL COALESCEs a missing cost to
zero, so "no figure" and "free" were indistinguishable downstream.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Semantic Release job failed twice (tasks 24571, 24581) with an identical
workflow payload to the run that succeeded 11 minutes earlier — same md5, same
container image, same pinned composite action. Both failures died in 2-3s with
the single step never started and no log flushed, which is a job-container
setup failure rather than anything semantic-release did.

Both coincided with the rc.15 'Distribute (Harbor)' image build, which ran for
~10 minutes on fringe-workstation while that node was OOM-killing a cgroup
every ~3 minutes. That build has finished; retrying on a quieter node.
Records the field survey (SWE-bench Verified, Terminal-Bench, HAL/Inspect,
CR-Bench, mutation testing, pass@k vs pass^k) and the four-layer design that
separates dispatch-plane conformance from patch correctness, review quality
and cost — so "the sweeper ate the run" can never read as "the model was bad".

Section 8 records what changed when every load-bearing claim was checked
against the code. Two of the fourteen corrections are live defects: a reading
Run cannot return findings on claude-code or acp (the delivery contract names
PLOEG_OUTCOME_FILE, which neither adapter sets, so ADR-0017's review loop is
inert on those harnesses), and a structured reader report loses the PR link.

Evidence only — no decision is ratified here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ComposePrompt tells every reading Run, on every harness, to deliver its
review by writing JSON to the file named by PLOEG_OUTCOME_FILE. Only
openhands and exec ever set that variable. On claude-code it was never
exported at all, and acp had no notion of a drop box, so a reviewer on
either wrote its findings to the empty string and Ploeg read nothing:
agent_runs.findings and verdict stayed blank, requestsChanges was
therefore always false, and ADR-0017's review loop was inert — every
Shift closed review_approved regardless of what the reviewer found, with
nothing published to the pull request for a human to read either.
values.yaml documents claude-code on a role as supported, so this was
reachable configuration, not a hypothetical.

The drop box is now one implementation in pkg/harness rather than a copy
per adapter, and openhands moves onto it. MergeDropBox fixes the
precedence the two shapes need: findings and verdict are the agent's and
always survive (a run that reviewed and then failed its shutdown
handshake still did the review), while outcome and summary only fill a
gap the adapter left — an adapter that classified a launch failure, a
lost lease or a watchdog timeout has evidence the agent does not, and an
agent must not overwrite it by writing a cheerful file.

resolveOutcome's structured-report arm returned the harness report with
no Links, so a reader that correctly wrote a drop box lost the PR URL
while one that returned nothing kept it. publishRound finds the pull
request by scanning reported links, so a review-only Shift published its
findings nowhere. Ploeg polled the forge and knows the URL; it now fills
the gap the agent could not.

harnesstest gains ReadingRunFindingsSurviveTheAdapter, which every
adapter package already runs. It fails against claude-code and acp before
these fixes and is what stops this recurring on the fifth adapter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The goldens are generated with the helm CI pins; helm 3 and helm 4
disagree about the blank line before a document separator, so a local
helm 3 shows a whitespace-only diff on a branch that changed nothing
under ops/helm. The failure message advised running 'update', which
would commit that churn and break the CI check it exists to serve.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Records the verdict behind the harness fix: one drop box defined in
pkg/harness, honoured by every adapter, with a fixed merge precedence.
Findings and verdict are the agent's and always survive; outcome and
summary fill only a gap the adapter left, because an adapter that
classified a launch failure or a lost lease holds evidence the agent
does not and an agent must not overturn it (R2).

Also records what the sweep found but did not fix: settling spend for
swept runs, the PR head SHA nothing durable records, findPR's missing
pagination, the process-global ScratchDir, the unaudited lease on the
Shift path, TaskSpec's missing round/writes, and ADR-0017's own
close_reason misreporting at maxFixRounds 1 — backlog 109-115.

architecture.md gains divergence 18 and a correction to 11: "no metrics"
is a live-observability gap, not an absence of timing data — started_at
and finished_at are exact per Run after the fact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#35
evaluate froze the plan on a stuck Outcome and had no case for failed.
A Run the sweeper reclaimed is, to RoundComplete, simply finished — so
the Round completed and the next one opened over work that was never
done: the reviewer reviewed a branch that had never been written,
approved it, and the Shift closed review_approved with no pull request,
while the tracker comment correctly said Ploeg had stopped without
opening one. The two disagreed, and the reassuring one is the field an
operator greps.

The shift-orchestration spec's "a swept Run does not block its Round
forever" was reasoned about READERS, and for a reader it still holds — a
missing opinion is not worth stalling an item over. The distinction it
was missing is `writes`.

A failed writing Role now re-opens the round the Shift is ALREADY on.
In place, because shifts.round doubles as the index into the plan
(tp.Rounds[si.Round]): opening a fresh Round to retry would silently
skip the next planned one, and the resulting bug — a reviewer that never
runs — is harder to see than the one this fixes. The attempt count is
derived from the Runs in that (shift, round, role), never stored, which
is the discipline ADR-0012 sets for reserved. At MaxRunAttempts the
Shift parks at needs_human naming the repeated failure.

Planned Shifts only. Under uniform dispatch a synthesized one-writer
Shift already settles by its run's own Outcome, and failed maps to
queued — the attempt-capped requeue R5 requires. There is no later Round
there to step over, which is why TestUniform_FailedRequeues keeps
passing unchanged.

MaxRunAttempts is deliberately separate from MaxAttempts:
work_items.attempts increments per role claim, so it stopped meaning
"attempts at this work" the day Shifts landed, and reusing it would have
let a three-role plan exhaust its budget in one clean pass.

Rebuilt on development after #35 merged. ADR-0019 sits alongside 0018,
and the architecture and backlog entries slot in after that change's own
rather than replacing them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#36
The probe that mattered: LiteLLM retains key_alias in /spend/logs after
the key is deleted. Zero live ploeg-* keys, 35 aliases still in the spend
log, and 34 of 40 recent runs join by alias — the six misses made no LLM
call. So a crashed run's cost IS recoverable, and the design inverts: the
gateway is authoritative and always available, agent_runs.usage is the
cross-check, and the unattributed bucket is unnecessary.

Found while probing: production has no cost data at all — 45 runs, none
with usage; 6 shifts, none with spend — while the gateway holds it. Not a
defect in current code. The cluster runs rc.14 and settleSpend landed in
eb6c8dc after that tag; it is stuck there because rc.15 never published
an image to Harbor.

The consequence is worth stating: ClaimRole authorizes budget - spent -
reserved, so with spent permanently 0 the pool bounds concurrency rather
than total spend. Deploying current trunk closes it — a release, not a
code change.

Also records the measured -race threshold (200x5000, 5 of 5) now pinned
in PB-01's grade.json, and that the bench project resolves a Target.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The plan said n=10 before believing a gap under ~15 points. Computing
the Wilson widths says 15 points needs 167 trials per configuration, and
10 points needs 381. At n=5 the widest interval spans 65 points, so only
a near-total difference is real there — 60% and 80% are not
distinguishable, and stats.Distinguishable now says so rather than
letting the larger number win.

This renames what the matrix is for rather than sinking it. L1
conformance, the L2 gate vector and L3 recall are per-trial and
deterministic, so they stay sharp at n=5; and pass^k is informative at
small n in a way pass@1 is not, because 5/5 against 3/5 is a statement
about consistency rather than a rate estimate.

Records the protocol that follows: n=5 to rank and to shake out
infrastructure, escalation only on a specific hypothesis, and never a
published winner from overlapping intervals. At EUR 0.50-2 per trial, one
15-point comparison at n=167 costs EUR 170-670 across two configurations
— not a homelab experiment, and the design should not pretend otherwise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I wrote that rc.15 never published an image. It did. The release pipeline
strips the leading v, so the image is ploegd:0.2.0-rc.15 and it exists; I
probed for v0.2.0-rc.15, got a 404, and concluded too much from it.
Forgejo run 1441 shows the release event succeeding on 2026-07-31.

The real blocker is worse than the one I reported: the release JOB has
failed on every push to development since 2026-07-31, so no rc has been
cut since rc.15 and the fixes merged on 2026-08-08 have no image at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The release job has failed on every push to development since 2026-07-31 —
seven runs, no rc cut, so the fixes merged on 2026-08-08 have no image.

The job never reached its first step. It pinned webgrip/workflows'
semantic-release composite at 2c5c68c9, which was amended off
fix/semrel-parity-and-hardening (head is now ee06ca5, same parent, same tree
but for reworded comments). The commit lives on in local clones, so the pin
looked fine from a workstation; on the server it is unreachable from any ref,
and the Forgejo runner resolves an action by cloning branch refs over
anonymous HTTPS. Hence, every run:

  could not determine the commit ID of 2c5c68c9...^{commit}: fatal:
  ambiguous argument ... unknown revision or path not in the working tree

Re-pinned to ee06ca5. The comment now records that a SHA pin is only as
immutable as the branch it hangs off, and corrects the stale claim that
publish had never run on the container path — run 144 cut v0.2.0-rc.15 on it,
Chart.yaml bump and git push-back included.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Renovate resolves these through the forgejo-tags datasource, which looks refs
up as TAGS. webgrip/workflows had never been tagged, so every @main lookup
404'd and Renovate exited non-zero — failing the ENTIRE run for this repo, not
just the one dependency.

webgrip/workflows v1.0.0 was cut today from its main at f49c65cb, the exact
commit @main already resolved to, so these four repins are byte-identical in
behaviour; only the mutability goes away.

DELIBERATELY NOT REPINNED: the semantic-release composite in
on_source_change.yml, pinned to ee06ca51 — the tip of webgrip/workflows PR #40
(fix/semrel-parity-and-hardening), which is still OPEN and 9 commits divergent
from main. Repointing it at v1.0.0 would silently strip unmerged work from this
repo's release path. It needs PR #40 merged and a v1.1.0 cut, not a repin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
## [0.2.0-rc.16](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.15...v0.2.0-rc.16) (2026-08-09)
This was the last ref in the repo that Renovate could not resolve. It pointed at
ee06ca51 — the raw tip of webgrip/workflows PR #40 (fix/semrel-parity-and-hardening),
unmerged at the time — so the forgejo-tags datasource 404'd on it and failed the
ENTIRE Renovate run for this repo, even after the other four refs were tagged.

PR #40 merged 2026-08-09 07:12Z and webgrip/workflows' new self-release cut v1.2.0
automatically. Ancestry verified before repinning rather than inferred from the
version number: ee06ca51 IS an ancestor of v1.2.0 and is NOT an ancestor of v1.1.0,
so v1.2.0 is the first release that actually carries this work. The composite
changed substantially across that boundary (135 insertions / 126 deletions).

The remaining four refs stay at v1.0.0 deliberately — Renovate can see and bump
those now that tags exist, which is its job, not this commit's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
## [0.2.0-rc.17](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.16...v0.2.0-rc.17) (2026-08-09)
1ca543a moved this ref to v1.2.0 but left the comment above it saying
"TEMPORARY PIN — flip back to @main once webgrip/workflows PR #40 is merged"
and, at the end, "prefer a merged-to-main SHA the moment #40 lands". #40 has
landed and the ref is already a tag on main, so both instructions now point
the next reader at work that is done — and one of them, flipping to @main,
would undo it.

Says instead what the ref costs and what it bought. v1.2.0 moved the
composite 135/126 lines against v1.1.0, but ploeg felt none of that: it was
already on ee06ca5, PR #40's tip, and the composite is byte-identical between
the two. The repin bought immutability, not behaviour.

Keeps the failure story, because it is the reason for the shape: a SHA pin is
only as immutable as the branch it hangs off, and it fails in a way local
checks cannot see — 2c5c68c9 still resolved from a workstation and still
fetched over SSH after it had stopped being reachable from any server ref.

`ci:` on purpose — not releasable, so this does not cut an rc.18 identical to
rc.17. The release job still runs and still resolves the ref, which is the
part worth proving.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
GitHub was parked on 2026-07-23 (ci: park GHCR publish) while pre-alpha with no
outside consumers. It returns as one job on the shared reusable rather than the
old inline rebuild: webgrip/workflows github-distribute.yml@v1.9.0 (PR #48)
force-pushes main/development and all tags to github.com/webgrip/ploeg, creates
the GitHub Release with notes fetched from the Forgejo release API, and
digest-copies the Harbor image to ghcr.io/webgrip/ploegd with buildx imagetools
— byte-identical manifest list, no duplicate multi-arch build.

Needs org secrets GHCR_USERNAME + GHCR_TOKEN (one classic PAT, repo +
write:packages); until they exist only this job fails, the Harbor/Forgejo
train is untouched. fix(ci), not ci: deliberately releasable — the rc that
carries this commit is the first to exercise the track.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [0.2.0-rc.18](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.17...v0.2.0-rc.18) (2026-08-24)
Both rc.18 distribution attempts died in one second: the binfmt digest 404s from
harbor.webgrip.dev/dockerhub while Docker Hub upstream serves it fine (it is
still :latest). Root cause is in Harbor, not the pin: harbor-proxy-config
reports registry 'docker-hub' (id=1) failing its health check — upstream auth
expired/revoked — so the proxy cannot re-fetch anything the weekly
delete_untagged GC evicted. Same digests, pulled direct: binfmt-image override
plus REGISTRY_DOCKERHUB=docker.io (~2 anonymous pulls per release against the
100/h limit). distroless already pulls gcr.io direct; nothing else on this
train touches the dockerhub proxy.

Temporary by construction — revert to the proxy once the OpenBao
harbor/registry-proxy DOCKERHUB_TOKEN is rotated and the endpoint health check
is green again. fix(ci): releasable on purpose, so this cuts the rc that
proves distribution end to end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [0.2.0-rc.19](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.18...v0.2.0-rc.19) (2026-08-25)
rc.19 got past binfmt (direct pull worked) and then died on the composite's
NEXT dockerhub-proxy default: the SBOM scanner image, 'not found' for the same
dead-upstream reason. buildkit-image only survived because that runner node
happened to hold a stale local copy — a fresh node would have failed the same
way. All three image inputs now bypass the proxy at the composite's own pinned
digests, alongside REGISTRY_DOCKERHUB. Revert with the rest once the Hub token
in OpenBao harbor/registry-proxy is rotated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [0.2.0-rc.20](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.19...v0.2.0-rc.20) (2026-08-25)
Trivy on 0.2.0-rc.20 (sha256:452ec975) reported nine High findings from
exactly two inputs, both fixed here:

golang.org/x/text v0.29.0 -> v0.41.0 covers CVE-2026-56852 (fix lands in
0.39.0). `go mod tidy` pulled golang.org/x/sync 0.17.0 -> 0.22.0 with it;
both are indirect.

The other eight are stdlib against v1.26.5, all fixed in 1.26.6, so the
lever is the builder pin rather than anything in go.mod: the reported
stdlib version is whatever toolchain baked the binaries. Re-pinned
golang:1.26-bookworm to sha256:38f30937, whose config blob reports
GOLANG_VERSION=1.26.7 (checked against the registry, not the tag).

The distroless runtime base contributed none of the nine, so its digest
is untouched.

fix(deps): releasable on purpose — the fix only reaches Harbor when a
release rebuilds and pushes the image, so this needs to cut an rc.

Verified: gofmt, go build, go vet clean; go test ./... passes except the
pre-existing internal/ledger TestADRIndexParity failure (ADR 0017
proposed vs Records accepted), confirmed failing on a clean tree too.
govulncheck reports zero non-stdlib module vulnerabilities across the
graph. Not verified locally: an actual Trivy scan of the built image —
no Docker daemon available on this machine, so Harbor's scan of the next
release is the confirming check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Picks up the credential preflight and the non-interactive push: a rejected
GH_TOKEN now fails both GitHub-track jobs by name in seconds instead of
`denied: denied` on GHCR and an indefinite hang on the ref mirror (run 170).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## [0.2.0-rc.21](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.20...v0.2.0-rc.21) (2026-08-25)
Harbor tags are immutable, so re-pushing an existing version is a hard failure
rather than a no-op. The rc.21 backfill (run 173) died on

    failed to push harbor.webgrip.dev/webgrip/ploegd:0.2.0-rc.21:
      'ploegd:0.2.0-rc.21' configured as immutable

and took the whole chain with it — signing skipped, both mirrors dead, and the
two GitHub jobs never even instantiated, since every one of them needs this job.

An already-published version is the desired end state, not an error: downstream
jobs only ever copy the image out of Harbor by digest, and that digest is
already there. So probe the registry first and skip the build if the manifest
resolves. Fail-open — anything other than a clean 200 falls through to the
build, so a fresh tag 404s and builds exactly as before. This is inert on the
normal release path and only bites on a re-publish.

The label verification is gated with the build for the same reason: it asserts
image.created against the timestamp stamped in THIS run, and an image that was
already published legitimately carries the one from when it was built.

Not fixed here: the Harbor CHART push has the identical flaw (412, immutable).
That job is independent, so a re-publish now gets the image, the signature and
both mirrors, and only the chart step fails. Noted in the dispatch comment,
which until now advertised a backfill path that could never work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## [0.2.0-rc.22](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.21...v0.2.0-rc.22) (2026-08-25)
rc.22 (run 175) built and pushed a perfectly correct image and then failed
verification on it, taking the signing job and both mirrors down with it. Two
independent bugs in the check, both the same class of mistake — matching text
instead of data:

  grep -qF '"org.opencontainers.image.created":"<value>"'

looks for `"key":"value"`, while `imagetools inspect --format '{{ json .Image }}'`
pretty-prints `"key": "value"` with a space. Every one of the three assertions
failed on labels that were present and correct.

The 1970 guard was wrong for the same reason: it searched the whole config blob
for the epoch, and BuildKit stamps every layer's history entry with exactly that
for reproducibility — 12 of them in the rc.22 image. A good build always
contains the string, so the guard could only ever fire on a false positive.

Both are replaced by one jq assertion over the parsed document, comparing the
labels themselves against the expected values — which covers the ARG-default
case precisely, since the default is never the expected timestamp. `.Image` is
normalised so the single-platform object and the multi-platform map assert
identically, and a failure now prints the actual per-platform labels instead of
dumping the entire config.

Verified by running the step body against the real rc.22 image JSON recovered
from run 175 (passes), a single-platform variant (passes), and a copy with one
platform's label corrupted to the 1970 default (fails, and names the platform).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## [0.2.0-rc.23](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.22...v0.2.0-rc.23) (2026-08-25)
Picks up the REST-API release creation. run 181 mirrored every ref to
github.com and then died on 'gh: command not found', so the repo is mirrored
but carries no GitHub Release yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
helm-chart-push gains the already-published probe, so a re-publish no longer
dies on Harbor's immutable chart tag (run 183, HTTP 412). github-distribute
gains the chart job, landing ops/helm/ploeg at ghcr.io/webgrip/charts/ploeg
alongside the image.

helm-chart-push.yml was byte-identical between v1.0.0 and v1.10.0 apart from
that probe, so the pin jump carries no other change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
::error:: creates an annotation on GitHub and nothing on Forgejo — act_runner
re-emits the line with a marker but keeps the raw text, and the job view has no
annotations surface. This was the only occurrence left in ploeg after the
estate-wide sweep in webgrip/workflows.

Plain ERROR: prefix, with the reason on the following lines, matching the
convention now documented in the workflows README. Behaviour is unchanged: same
condition, same exit 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## [0.2.0-rc.24](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.23...v0.2.0-rc.24) (2026-08-25)
Bumps docker-build-push-registry-fast and cosign-sign-attest from v1.0.0 to
v1.10.0. The only drift on those two files across that range is the
::error::/:⚠️: sweep — verified with git diff, 15 changed lines, all of
them the marker prefix. No behaviour change.

That completes the sweep for ploeg: nothing on this repo's release path emits a
GitHub annotation command any more, which on Forgejo only ever produced an
uglier log line.

Also corrects the workflow_dispatch comment. It claimed the Harbor chart push
still fails on a re-publish; v1.10.0 probes the registry and skips an
already-published version, and run 184 was green end to end on exactly that
path. And drops the temporary GHCR_TOKEN fingerprint workflow — it did its job:
the stored secret was a reconciled value from OpenBao, not the UI edit we kept
making.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## [0.2.0-rc.25](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.24...v0.2.0-rc.25) (2026-08-25)
Ploeg shipped one provider per seam — Vikunja and Forgejo — which is enough to
prove the SPI and not enough to run anywhere else. The second estate's staging cluster is
GitLab and ClickUp, so both seams needed a second implementation before Ploeg
could be exercised there at all.

Both are pure SPI implementations: no core change, nothing vendor-shaped
escapes either package (R7), and each is registered under its dialect name so
the existing /webhooks/{tracker,forge}/{provider} routes reach them unchanged.

GitLab differs from Forgejo in three ways a copy-paste gets silently wrong,
and each is commented where it bites:

  - GitLab does NOT sign webhooks. It echoes a shared secret in X-Gitlab-Token,
    which authenticates the sender but not the payload. Compared in constant
    time, since a bare == leaks a secret a byte at a time.
  - A merge request has two numbers; only `iid` works in an API path.
  - Projects live at arbitrary subgroup depth, so the path is URL-encoded whole
    rather than split into owner/name.

Review outcomes also arrive as merge_request actions rather than a review
object, and a branch pipeline has no MR — reported as PR 0, which the core
reads as "nothing to route this to" rather than as merge request zero.

ClickUp differs from Vikunja in three ways, likewise commented:

  - Auth is the raw token, no "Bearer " prefix (ClickUp 401s the prefixed form).
  - Priority is inverted — id 1 is urgent — and is flipped on the way in rather
    than leaking backwards ordering into scheduling.
  - There is no global "done": status is a per-List custom string, so SetStatus
    needs DoneStatus configured and skips loudly without it instead of guessing
    a name that would 400 or move the task somewhere nobody chose.

ClickUp's webhook is thin and carries no List, so Scope is left empty at parse
time and resolved in FetchItem — the thin-payload rule doing exactly what it is
for.

Wiring: trackers become a registry built once rather than two inline literals,
and the forge instance id (ADR-0016) is bound explicitly. With one forge
configured it takes the id; with two and PLOEG_TARGET_FORGE naming neither,
nothing is bound and it says so — publishing findings to the wrong forge is
worse than not publishing.

New env: PLOEG_CLICKUP_{SECRET,TOKEN,URL,DONE_STATUS},
PLOEG_GITLAB_{URL,TOKEN,SECRET}. Absent, behaviour is byte-identical to before.

Gates: go build, go vet, go test ./... (all green, including cmd/ploegd's
existing wiring tests), gofmt clean, helm lint and all three chart renderings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#37
## [0.2.0-rc.26](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.25...v0.2.0-rc.26) (2026-08-25)
Neither published package is connected to a repository on GHCR, for two
different reasons, and both are one line of metadata.

GHCR links a package to a repository by matching the OCI annotation
org.opencontainers.image.source against a github.com URL. The image declared
"https://forgejo.webgrip.dev/webgrip/ploeg", which matches nothing on GitHub,
and the chart declared no source at all — Helm derives that annotation from
Chart.yaml sources[0], and there was no sources field.

image.source now points at the github.com mirror, which is the URL GHCR can
resolve and the one a stranger can actually open. The canonical home moves to
image.url, so Forgejo being the source of truth is still stated, just in the
field that means "where to find out more" rather than the one GitHub matches
on. Same split for the chart: sources[0] is the mirror, home is Forgejo.

Verified by pushing the packaged chart to a throwaway local registry and
reading the manifest back, rather than assuming Helm's mapping:

    org.opencontainers.image.source = https://github.com/webgrip/ploeg
    org.opencontainers.image.url    = https://forgejo.webgrip.dev/webgrip/ploeg

Linking applies to versions pushed AFTER this ships; rc.25 and earlier stay
orphaned, and the packages must also be public before the link is visible to
anyone not signed in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ADR-0020. The Dockerfile and Chart.yaml changes in the previous commit make a
claim about which forge an artifact points at, and that claim deserves to be
argued somewhere other than a commit message — it looks like an inconsistency
until explained, which is exactly what 0004 said about the module path.

This is 0004's asymmetry applied to a second artifact class: the module path
names the mirror because Go resolves modules over a public host; published
artifacts name the mirror because OCI consumers resolve source over a public
host. Same reason, same accepted trade-off. Forgejo is not demoted, it moves to
image.url, and the governance claim lives in the ledger rather than in a label.

The record also captures the constraint that removed most of the option space —
the image is built once and digest-copied, so one label serves all three
registries — and the two alternatives that were weighed and rejected.

The Confirmation is now a real gate rather than a promise: the release
workflow's `Verify image metadata labels` step asserts image.source against
EXPECTED_SOURCE for every platform, so a regression to the Forgejo value fails
the Harbor job instead of silently orphaning the GHCR packages again. Exercised
against a correct image (passes) and one carrying the old value (fails, and
names the offending platform).

Gates: go build/vet/test ./... green including internal/ledger (the ADR
consistency validator), gofmt clean, helm lint and all three renderings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#38
## [0.2.0-rc.27](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.26...v0.2.0-rc.27) (2026-08-25)
Shift 73 parked work item 98 at needs_human on 2026-08-13 after three pods
were killed mid-run. The agents were working correctly in all three —
LiteLLM's ledger records 19, 17 and 1 completed model calls with context
growing 18k -> 53k tokens, each pod dying within seconds of a call that had
just succeeded, for $0.0237 of a $8.00 pool. Nothing about the work was ever
tried, and the close reason sent a person to read agent logs.

Two defects, one behind the other.

ploeg-worker installed no signal handler at all, so SIGTERM killed it on the
default disposition: no outcome reported, no per-run credential revoked, and
the Lease left to the sweeper a full 15 minutes later, attributed to nothing.
RunContext now takes a cancellable parent and cmd/ploeg-worker hands it
SIGINT/SIGTERM. The run context is deliberately NOT derived from that parent
— the harness must die with it, but revoke, settle and report have to survive
it, or the shutdown reports nothing and we are back where we started.
context.WithCancelCause carries WHY: errTerminated maps to failed/infra_node,
errLeaseLost keeps the existing stuck/lease_lost, and the two now mean
opposite things to the Round.

Then the budget. store.ExpireLeases has always counted infrastructure apart
on the pre-Shift path — refunding the attempt, tracking infra_failures,
capping at MaxInfraFailures. The Shift path inherited none of it and charged
every `failed` Run to the same three attempts, which for a Round whose only
producer of `failed` is the sweeper means the ticket's budget was being spent
entirely on the cluster. FailedRunsInRound now returns InfraAttempts beside
Attempts, partitioned in SQL by work.InfraFailureReasons() so the query and
the engine cannot drift; retryFailedWriter bounds them separately and closes
with `writing_run_killed_repeatedly` when the infrastructure is at fault —
a close reason that points at evictions and node pressure rather than at the
ticket. An unset or unknown failure_reason counts against the agent: a reason
nobody set must not buy unlimited retries.

Also fixed on the way: settleSpend ran on the run context, so a cancelled run
lost its cost settlement entirely — which is why shift 73 reads spent=0.0000
against real gateway spend. It runs detached now.

terminationGracePeriodSeconds (new value, default 90) is load-bearing: the
30s default SIGKILLed the pod mid-report and left the handler inert.

Not fixed here: there is still no backoff between infra retries on the Shift
path, where ExpireLeases has 1/5/15/60-minute steps. Gating a reopened Run on
a time would change ClaimRole and the KEDA trigger predicate together, and
those must stay byte-identical. Recorded as an accepted cost and a
re-evaluation trigger in ADR-0021.

Refs: ADR-0021 (refines ADR-0019, whose first re-evaluation trigger fired
here), openspec shift-orchestration.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A stray double blank line from appending the tests, which `gofmt -l`
in the checks workflow rejects. No behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The earlier commit set 0017's Records row to `accepted` to match its
frontmatter. That reading came from a checkout eight commits behind
development, where the file said `accepted` and the row said `proposed`.
Upstream had already fixed the same drift the other way, in f9b157c: the
file is `proposed` and the row is `proposed`, consistently.

Rebasing carried my edit onto the resolved state and broke parity in the
opposite direction, which is what TestADRIndexParity was failing on. It also
would have flipped a decision's recorded status from proposed to accepted —
a governance change, not the clerical fix I took it for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#39
## [0.2.0-rc.28](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.27...v0.2.0-rc.28) (2026-08-26)
ADR-0020 got the decision right and the mechanism wrong. GHCR resolves a
package's repository link from the MANIFEST; a Dockerfile LABEL lands in each
platform's image config, which `docker inspect` reads and a registry does not.
The chart linked because Helm writes a real OCI annotation. The image did not,
and stayed orphaned through rc.24-rc.27 with the label set correctly the whole
time.

The Confirmation gate shared the mistake — it asserted the label, so it was
green on all four of those releases. A gate that passes while the thing it
guards is broken is worse than no gate, so it now also asserts
.annotations["org.opencontainers.image.source"] on the raw index. Checked
against an un-annotated index: it fails, meaning it would have caught this.

Annotations are set at BUILD time, not at copy time, and that was measured
rather than assumed: a plain `buildx imagetools create` preserves index
annotations and leaves the index digest byte-identical, so one annotation
reaches Harbor, Forgejo and GHCR while ADR-0020's identical-digest property
stands. Annotating during the copy would have diverged GHCR from Harbor.

The LABEL stays. It is what `docker inspect` surfaces; the annotation is what
registries read. Two readers, two mechanisms.

Separately, GHCR was carrying an unsigned mirror of a signed image.
`imagetools create` copies the manifest list, so BuildKit's SLSA provenance and
SBOM ride along inside the index for free — both were already there — but
cosign's signature and CycloneDX attestation live as separate .sig/.att tags and
were left behind. copy-accessories mirrors them with `cosign copy`. A copied
signature verifies at the mirror because cosign checks the digest; its payload
keeps the Harbor reference, so Kyverno goes on verifying against Harbor, which
it already does.

ADR-0020 is amended append-only: the body stands as decided, two dated entries
record the corrected mechanism and the measurement behind it, and the index row
mirrors the new date.

Requires webgrip/workflows v1.11.0 (PR #53) for the `annotations` and
`copy-accessories` inputs.

Gates: go build/vet, gofmt clean, internal/ledger (the ADR validator), helm lint
and all three chart renderings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#41
## [0.2.0-rc.29](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.28...v0.2.0-rc.29) (2026-08-26)
rc.29 shipped with org.opencontainers.image.created=1970-01-01T00:00:00Z — the
exact label this step exists to protect, reached by a route it did not guard.

Forgejo returns a ZERO TIME rather than an empty string for a release published
through the API, which is how semantic-release cuts every rc. The guard rejected
Go's zero value (0001-01-01) and nothing else, so the Unix epoch went straight
through, was ISO-8601 shaped, passed the format assertion, and was baked into
the image. The downstream label gate then verified it as correct, because that
gate compares the image against this value — a wrong value here is confirmed,
not caught.

Enumerating sentinels is a losing game, so the test is now plausibility: nothing
this project publishes was created before the project existed, so anything
earlier is a sentinel whatever it spells itself. One predicate serves both the
candidate choice and the final assertion, because a value good enough to pick is
exactly a value good enough to ship.

Two behaviour changes, both deliberate:

- published_at and created_at are each tested, so an unusable published_at now
  falls through to a usable created_at instead of skipping to build time. The
  old code only consulted created_at when published_at was EMPTY.
- an unusable timestamp no longer fails the build. It cannot produce a wrong
  label any more, so refusing the release buys nothing; it degrades to build
  time, which is the OCI-conventional meaning anyway. Every rejection is logged
  so the forge's behaviour stays visible rather than inferred.

Verified across eight inputs: dispatch, real published_at, Go zero, Unix epoch,
epoch-with-real-created_at, both-sentinels, garbage, and a pre-2020 year.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/ploeg#42
## [0.2.0-rc.30](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.29...v0.2.0-rc.30) (2026-08-26)
Picks up the absolute cosign-installer URL. A bare `uses: sigstore/...` resolves
against the Forgejo instance, which does not mirror that action, so rc.29 lost
Distribute image (GHCR) at action-clone before any step ran — and rc.30 will
too, since a release runs the workflow from its own tag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Picks up the preferred-preinstalled-binary seam. A no-op until cosign and syft
are baked into the ci-runner image, which is the point: pinning it now means
that image change takes effect here without also needing a pin bump remembered
weeks later.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The GHCR mirror stopped being an unsigned copy of a signed image in rc.30; the
Forgejo mirror had the identical defect and kept it, because both take the same
`imagetools create` path. imagetools copies the manifest list, so BuildKit's
SLSA provenance and SBOM ride along inside the index for free, while cosign's
signature and CycloneDX attestation — separate .sig/.att tags — were left
behind. Until now "the image is signed" was true of Harbor alone.

forgejo-distribute moves from v1.0.0 to v1.11.3 for the input. The diff across
that range is 50 insertions and no deletions, all of them this change, so the
pin jump carries nothing else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## [0.2.0-rc.31](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0-rc.30...v0.2.0-rc.31) (2026-08-26)
The mark is a steel that bends away at the top — half capital P, half
ploughshare. The steel carries, the blade cuts, and that distinction is
everything the two-tone encodes. Everything else derives from one measure:
the 13-unit stroke on a 64 grid.

Two construction details are load-bearing and easy to get wrong:

- The mark is ONE continuous silhouette with the steel laid over it as a
  filled shape. The obvious build — two separate strokes from the same
  point — produces a *curved* colour seam, because two round caps at a
  shared origin bulge on one side and end flat on the other. It reads as a
  pill dropped on a bar rather than one instrument. The seam is now a
  straight line at y=21.
- fill="none" sits on every stroke path, not only on the <svg>. A path
  copied out on its own otherwise renders as a black shape.

The wordmark is Archivo at wght 800 / wdth 110, chosen by measurement rather
than taste: the mark's stroke is 27.1% of its height, and that instance puts
the stem at 25.1% of cap height — the closest of eight OFL candidates while
staying a touch lighter than the mark, which is what a lockup wants. It ships
as outlines, so rendering it needs no font.

Colour carries measured contrast, and one result shaped the palette: Klei
(#E4572E) is 3.39:1 on Kalk — enough for a graphic, not for text. Hence Klei
Diep for small text on light grounds and the reverse on dark, switched by
tokens.css.

ADR-0022 settles the mark's terms. A CC licence is the wrong instrument: it
is irrevocable, it grants the right to modify an origin-identifying sign, and
CC themselves warn it can cost the trademark outright. So the name and mark
are trademarks under docs/brand/TRADEMARK.md, the files stay under the repo's
Apache-2.0 (§6 already withholds marks), and scripts/brand-marks.sh gates it
in the pull-request workflow.

One commit rather than a series: the identity, the policy and the CI gate are
interdependent — an intermediate commit would fail its own check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rendered with resvg at exact 512 and 1024 px heights; every variant
except mark-currentcolor, which has no resolvable colour outside CSS.
For the places that won't take an SVG.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [0.2.0](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.1.0...v0.2.0) (2026-08-27)
The worker sandbox's NetworkPolicy is deny-by-default with pinholes for
the LLM gateway, the forge, and the tracker — no registry egress at all.
AGENTS.md, mise.toml and the team-silver skill still instructed agents to
run the PR gates "through the DinD daemon with the golang and alpine/helm
images", which can only end in a dial timeout (observed on run vik-777:
dind POST /images/create -> registry-1.docker.io i/o timeout). Say the
truth instead: run what the local toolchain can, never retry a pull, and
name the gates left to CI in the PR body — CI is the enforcement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two-tone build draws the full silhouette in Klei and lays the steel
over it, so hidden orange sat flush against the steel's outline. Wherever
that outline is anti-aliased — the bottom cap in the pixel-aligned
exports, the entire stem at arbitrary render sizes — edge pixels mixed
steel with the orange underneath, an orange zweem on the silhouette.

An evenodd clipPath on the Klei stroke now removes everything under the
steel except a one-unit strip below the naad, which keeps the seam
covered without letting hidden orange reach any anti-aliased edge. Same
knip on the favicon and tile geometries; geometry itself is untouched.
Applied to all nine two-tone SVGs and the marks inlined in merkgids,
documented in the construction notes, PNGs re-rendered with resvg.

Verified by pixel scan: zero mixed pixels off the naad in every export,
including odd-size renders where nothing is pixel-aligned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every release from v0.1.0-rc.1 and v0.2.0-rc.1 through v0.2.0 (except the
rc.10–rc.14 window) shipped a heading-only CHANGELOG entry and Forgejo
release body: conventional-changelog-conventionalcommits v10 is incompatible
with release-notes-generator 14's Handlebars writer, which renders the
heading and silently drops every commit line. Root cause and forward fix live
in webgrip/semantic-release-config PR #10, webgrip/workflows PR #57 and
webgrip/infrastructure PR #131.

These 28 bodies are regenerated with the fixed toolchain (generator
15.0.0-beta.2) over each tag's real commit range, mirroring the shared
config's generator options exactly. Original heading dates are preserved;
chore(release) bump commits are excluded — each tag's own bump commit never
existed at generation time, and the intermediate ones only duplicate the rc
headings they belong to. The matching Forgejo release bodies are patched via
the releases API separately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## [0.2.1-rc.1](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.0...v0.2.1-rc.1) (2026-08-28)
The DinD daemon phase-4 instruction is retired: homelab-cluster ADR-0053 makes
the agent plane daemonless (dind:false estate-wide, no docker CLI in the
runner image), so a dispatched agent pushes and iterates on the PR pipeline
instead of docker-running golang:1.25 gates in-pod. Local runs with real
toolchains still run gates directly. Evidence rules unchanged: paste what you
saw run, never what you didn't.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Image 0.1.2 pairs conventionalcommits v10 with release-notes-generator 14,
which renders heading-only notes (every rc since 2026-07-31, incl.
v0.2.1-rc.1). 0.3.1 bakes the compatible pair flat (generator 15 beta,
analyzer 14 beta — infrastructure PR #131) and, once it carries shared
config 1.2.3, also the chore(deps) release-veto fix and the load-time
toolchain guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Run 226 failed ~25s in with logs out of anonymous reach; the 0.3.1 tag
exists in Harbor (Kyverno resolved its attestations) and the toolchain tree
is verified locally, so this probes whether the failure was a cold-pull
transient.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runs 226 and 227 both failed in 15–25s on image 0.3.1 (notes toolchain pair,
config 1.2.2); the same job shape is green on 0.1.2. Job logs are not
anonymously readable, the tag itself exists in Harbor (Kyverno resolved its
attestations), and the 0.3.1 npm tree passes a local dry-run — cause unknown
until the run 226/227 logs are read. Empty release notes are the cost of
staying on 0.1.2; a broken release job is worse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0.3.1 failed here (runs 226/227) because the DHI alpine base carried no bash
while every composite step declares shell: bash — infrastructure PR #132
adds it, bakes shared config 1.2.3 (notes toolchain guard + deps release
rules), and proves the runtime contract at image build time so an unrunnable
image can no longer release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0.3.2's baked tree held the gitea plugin's own got@10/globby@10 (npm ignores
a dependency's overrides; the image manifests carried only the notes-plugin
pins). Verify and no-release paths were green; a real publish died after the
tag push (renovate-config v1.6.1). 0.3.3 bakes the shared config's complete
override set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Routing the twente.dev board (VIK-779) surfaced the full friction chain:
helm MR + Flux for one list entry, human-pasted webhook secret, silent
403s on missing shares, wiring state that lives in YAML comments, and
six byte-identical tickets from an idempotency gap. The field report
names the target UX (one declarative act, converge-don't-instruct, loud
failure written back to the ticket, wiring as queryable data, TTFR as
the metric); backlog §L (117-124) carries the build order.

VIK-781

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The advice created more mess than it prevented: four worktrees accumulated,
three of them holding branches already merged upstream, and a fourth holding a
staged-but-uncommitted security fix that a `worktree remove --force` would have
destroyed silently. Creation was documented; teardown never was.

The rest of the paragraph — stage paths not the tree, leave other sessions'
dirt alone, check the log before assuming your edits are uncommitted — is what
actually protects concurrent sessions, and it stands on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude Code hardcodes discovery of both filenames; the symlink makes the
Claude-conventional name resolve to the one source of truth without creating a
second copy that can drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ploegd has spoken GitLab since rc.31 — pkg/provider/gitlab comments on a merge
request and verifies inbound webhooks. ploeg-worker never learned: it polled
/api/v1/repos/{owner}/{name}/pulls and its briefing named the Forgejo API as
the way to open one. On a GitLab target no change request was ever created, so
the Shift had none for a reviewer to comment on, publishRound logged "no pull
request on this shift yet", and the review loop could not close. Silent all the
way to a human.

Less was missing than it looks. Three things were already forge-agnostic and
are untouched here: git.go's authURL/plainURL build owner/name into a URL that
is already correct for a GitLab subgroup; shiftengine's prPathRe already
matches /merge_requests/(\d+); and the Shift takes its change-request URL from
the OutcomeReport links, not from the poll. The gap was two places that name a
forge, so that is what this changes.

RepoRef gains Forge, the API DIALECT. Empty means forgejo, so every stored
target, every taskspec and every deployment that never set it keeps its exact
current meaning. The dialect travels on the work item — pkg/work.Target has
carried Forge all along — and falls back to the worker's configured default,
matching the "empty = the default forge" promise ploegd's own registry makes.

- findPR dispatches. GitLab filters source_branch server-side, so unlike the
  Forgejo call it cannot be defeated by a repo with 50+ open requests. The
  project is addressed by URL-ENCODED full path: acme/internal/widgets is
  three segments and the slashes must survive as %2F.
- The briefing dispatches, in vocabulary as well as endpoint. An agent told to
  open a "pull request" on GitLab looks for an endpoint that is not there, and
  the noun is what it searches its tools and the repo's docs for. Noun and
  endpoint come out of the same switch so they cannot drift.
- An unknown dialect fails loudly. Falling back to Forgejo would poll a real
  endpoint shape against the wrong host and report "no change request" forever
  — indistinguishable from an agent that never opened one.

Chart: executor.forge selects one active forge and executor.gitlab configures
it. The new ploeg.forge helper resolves whichever is active into one shape, so
no template touches .forgejo or .gitlab directly. That also removes a trap: the
worker template used to dereference .Values.executor.forgejo.url
unconditionally, which made `forgejo: null` — the documented way to empty an
unused block — a nil pointer the moment executor.enabled flipped true. The new
GitLab fixture renders with forgejo null precisely so that cannot come back.

FORGE_URL is the name; FORGEJO_URL is emitted alongside it and still accepted,
so a ScaledJob starting a pod from the previous image mid-upgrade still finds a
forge.

A fourth golden, executor-gitlab, because selecting a forge changes the worker
pod — a different credential Secret and a different API — and the worker pod is
the boundary the goldens exist to police. It pins the ADR-0013 tier-1 split
holding on GitLab: readers draw agent-reader-token, the writer draws
agent-builder-token.

Tier 2 is deliberately absent. ploegd mints per-run push credentials through
/api/v1/admin/users/forge, a Forgejo admin endpoint with no GitLab equivalent;
the nearest analogue is a project access token, a different escalation that
deserves its own ADR rather than an implied one. Unset means the shared token,
which is the documented pre-tier-2 behaviour.

Gates run locally with the pinned toolchain (helm v4.2.3 as CI pins; 4.2.4
disagrees about the blank line before a document separator, as scripts/
helm-golden.sh warns):

  gofmt -l .              clean
  go vet ./...            clean
  go build ./...          ok
  go test ./...           ok, all packages incl. pkg/store
  helm lint               1 chart linted, 0 failed
  helm-golden.sh check    ok, 4 renders

No VIK trailer: this did not come from a board ticket.
RepoRef.Forge landed without its schema edit, which docs/contracts/README.md
does not allow: v1 changes additively, and the Go type and the published schema
change together. `repo` is additionalProperties:false, so a Task Spec carrying
the field would have been rejected by any consumer validating against the
published contract.

The gate did not catch it because fullTaskSpec — the fixture whose whole job is
to carry every field — did not set Forge, and omitempty dropped it.

Fixed in the order the tasks rules ask for: the fixture first, observed to fail
with

    at '/repo': additional properties 'forge' not allowed

then the schema. The enum is constrained to the dialects the worker actually
implements, so a Task Spec naming a third forge fails at the contract rather
than at run time.
The change was built before it was proposed, which is the wrong order and is
why this is a separate commit rather than a backdated one. AGENTS.md routes
non-trivial changes through OpenSpec — proposal → specs → design → adr → tasks
— and the adr step gates tasks precisely so a durable commitment cannot reach
implementation unrecorded. It did. This records it and leaves the verdict open.

ADR-0023: the forge DIALECT is a property of the Work Item and varies per Run;
the forge URL and credential stay deployment-global because a worker pod holds
one of each. Status proposed — a human ratifies it or does not. The record
states the two options genuinely weighed and what each would have cost
(per-Team re-couples capability to codebase, undoing ADR-0014; per-deployment
makes a second forge a second release and leaves Target.Forge meaning one thing
to the engine and nothing to the worker), so the ratification is a decision
rather than a rubber stamp. adr.md says all of this out loud.

Tier 2 is a named non-goal, not an omission: per-run push credentials are
minted through /api/v1/admin/users/forge, which GitLab has no equivalent for,
so GitLab runs on the shared token until that is decided on its own evidence.
It is a re-evaluation trigger on 0023, alongside a third forge arriving — two
dialects justify a switch, three justify an SPI and a supersession.

New capability spec `forge-dialect`: where the dialect is decided, that an
unknown one stops the Run loudly rather than falling back, that the poll is
scoped to the Run and filtered server-side where the forge allows it, that the
delivery contract uses the forge's own vocabulary, and that selecting a forge
shows up in a committed golden of the worker pod.

architecture.md §9 item 15 corrected. Two of its claims were true when written
and are not now — ForgeProvider has implementations, and pkg/worker/forge.go no
longer hardcodes one dialect — which is the staleness that section warns about
in both directions. What remains a singleton (URL, credential) and what remains
open (tier 2) are stated rather than implied.

tasks.md group 5 carries the downstream wiring in the second estate's staging cluster,
which is what this change exists for and what proves the loop closes. It
includes the ordering constraint that matters: the chart has no
additionalProperties:false, so executor.forge against rc.31 is silently
ignored — values must not land before the OCIRepository bump.

openspec validate --all: 6 passed, 0 failed.
go test ./internal/ledger/: ok
Review feedback. Every comment this change added is gone; the diff now adds
none. What the prose carried is carried by names, types and the ADR instead.

  prMatches            -> isRunChangeRequest(changeRequest, runBranch, base)
  findPR               -> findOpenChangeRequest
  listForgejoPRs       -> listForgejoPullRequests
  listGitLabMRs        -> listGitLabMergeRequests
  forgeGet             -> getJSON
  openChangeRequest    -> openChangeRequestInstruction
  Config.Forge         -> Config.DefaultForge
  changeRequest{URL,Head,Base} -> {URL,HeadBranch,BaseBranch}

The "no silent fallback" comment is now errUnsupportedForge, a named sentinel
the test asserts with errors.Is rather than a substring. The subgroup comment
is RepoRef.ProjectPath, which joins and never splits. The chart's helper
comment is the helper's own shape.

TWO NAMES FOR ONE VALUE, REMOVED. requireEnvOneOf("FORGE_URL", "FORGEJO_URL")
was a shim for a rolling upgrade that cannot happen: the release train keeps
chart and appVersion in lockstep, so the chart and the image it configures move
together. The worker now requires FORGE_URL and nothing else, and the chart
emits only that. requireEnvOneOf is deleted.

The dialect had the same problem in a worse form: PLOEG_FORGE on the worker was
a second spelling of PLOEG_TARGET_FORGE, which ploegd has read since the forge
registry landed — the same concept, the same default, two names, one of them
invented here. The worker now reads PLOEG_TARGET_FORGE too, and the chart
renders it once for both binaries from executor.forge, so they cannot disagree
about which forge is the default.

Both renames are breaking for anyone setting these by hand and neither is for a
chart-driven deployment. That trade is the point: two names for one value is a
worse thing to own than a rename under a version bump.

values.yaml loses its comment blocks; the meaning moved into
values.schema.json descriptions, which is where a Helm chart keeps structured
intent — validated, machine-readable, and shown by tooling rather than only to
whoever opens the file.

Goldens regenerated (helm v4.2.3, as CI pins). Gates: gofmt clean, go vet
clean, go build ok, go test ./... ok, helm lint ok, 4 renders ok,
helm-golden.sh check ok, go test ./internal/ledger/ ok, openspec validate --all
6 passed.
## [0.3.0-rc.1](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.2.1-rc.1...v0.3.0-rc.1) (2026-09-02)

### Added

* **worker:** open change requests on GitLab, not only Forgejo ([a2a5547](a2a5547ce4))

### Fixed

* **harness:** taskspec.v1 carries the forge dialect ([08826e4](08826e4dcb))

### Changed

* **worker:** name things instead of explaining them ([79407f1](79407f1ac1))

### Docs

* **agents:** add CLAUDE.md as a symlink to AGENTS.md ([7996ce8](7996ce86cd))
* **changelog:** backfill v0.2.1-rc.1 — cut on the old toolchain after the first backfill ([fd8ecb8](fd8ecb8f31))
* onboarding field report — five manual acts, all failing silently ([4f2b008](4f2b0084ad))
* **openspec:** record the forge dialect decision as ADR-0023 ([8770c29](8770c29776))
* **skills:** team-silver gates run in CI — the dispatched harness is daemonless ([e15c9d1](e15c9d1842))
* stop prescribing a worktree per change ([70bd3fd](70bd3fdd4e))

### CI

* **release:** back to toolchain image 0.1.2 — 0.3.1 fails the release job ([3026c31](3026c31b82))
* **release:** cut releases in toolchain image 0.3.1 — notes render, dep bumps release ([594593e](594593e194)), references [#131](webgrip/ploeg#131)
* **release:** rerun the release job on toolchain image 0.3.1 ([76823cf](76823cffc1))
* **release:** toolchain image 0.3.2 — the alpine base now ships bash ([8b1d2cf](8b1d2cfad0)), references [#132](webgrip/ploeg#132)
* **release:** toolchain image 0.3.3 — the publish path now resolves got 11 ([d1f9c73](d1f9c730cf))
ploeg.workerPodTemplate hardcoded

    nodeSelector:
      node.webgrip.io/pool: worker

with no values hook. That label exists in the homelab and on no other cluster,
so every worker Job on any other estate is Pending forever. Nothing reports it:
the ScaledJob is created, KEDA scales it, the pod never schedules, and the Work
Item sits queued with a healthy-looking release above it.

It is the same trap as the ploegd nodeSelector default, one layer down and
worse, because ploegd's could at least be overridden.

The default is unchanged, so the homelab renders what it rendered before — the
goldens move by exactly one thing, the inline comment that toYaml does not
carry. An estate with different labels sets its own; one with none clears the
block with null. {} does not clear it, because Helm deep-merges maps, which is
the same footgun the existing nodeSelector and forge blocks already carry and
is now said once in the schema.

ADR-0002's constraint is intact: the reason for a selector is keeping DinD off
control-plane nodes. That reason is a deployment's to enforce with its own
labels, not the chart's to assume with someone else's.

Gates: gofmt clean, go vet clean, go build ok, go test ./... ok, helm lint ok,
4 renders ok, helm-golden.sh check ok.
## [0.3.0-rc.2](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.3.0-rc.1...v0.3.0-rc.2) (2026-09-02)

### Fixed

* **chart:** the worker node selector is a value, not a constant ([53f8ad6](53f8ad6969))
pkg/provider/clickup has been a complete TrackerProvider since it landed —
verified webhooks, reads, write-backs, assignee routing — but the config file
only knew `trackers.vikunja`, so a ClickUp deployment had to route through the
legacy PLOEG_TARGET_MAP env DSL the file format exists to retire. And the chart
could not deliver PLOEG_CLICKUP_TOKEN or _SECRET at all: they are secrets, and
the plain `env:` map is the only passthrough it had.

Config: `trackers.clickup` mirrors `trackers.vikunja`, with two deliberate
differences.

  A clickup entry REQUIRES a pinned id (the List id). The provider has no name
  resolver yet, and a name-only entry would boot into a resolution step with
  nothing to ask. The error names the entry; when a resolver lands, the
  restriction lifts and names become the norm here too.

  Scope ids share ONE namespace across trackers, and validation now says so:
  the target map keys on the container id alone, so a vikunja project and a
  clickup List sharing an id would silently route each other's work. One
  `seen` map across both providers turns that into a boot failure instead.

Chart: `tracker.clickup` — url, tokenSecret, webhookSecret, doneStatus.
Ingest and write-backs stay independent, exactly as the provider promises:
either secret without the other degrades rather than fails, and an empty block
renders nothing, so no Secret is demanded before it exists.

doneStatus is configuration, not convention, because ClickUp statuses are
per-List custom strings — "for review" on one board, "done" on another —
and guessing would 400 or move tasks to a status nobody chose.

The webhook secret is CAPTURED, never invented: ClickUp generates it on
webhook registration and returns it in the response. The values comment says
so, because the natural instinct after the vikunja block is to generate one.

Golden movement is exactly the new env on the gitlab fixture, which now
carries the full acme shape: GitLab forge, ClickUp tracker.

Gates: gofmt clean, go vet clean, go build ok, go test ./... ok, helm lint ok,
4 renders ok, helm-golden.sh check ok (helm v4.2.3 as CI pins).
## [0.3.0-rc.3](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.3.0-rc.2...v0.3.0-rc.3) (2026-09-02)

### Added

* **config,chart:** route ClickUp Lists through the config file ([e0a4823](e0a4823018))
The Project type has carried this comment since routing moved into the file:

    Team routes this project's work to one team. Empty means the
    assignee decides, via the team's `assignees` list below.

Only the second sentence was ever implemented. `team:` was consumed solely as
a routing-table qualifier — "<scope>/<team>=repo" disambiguates WHICH REPO a
team's work lands in — while the team itself always came from the assignee
mapping, falling through to PLOEG_DEFAULT_TEAM for anyone unmapped. A config
that pinned a project to `team: app` routed nothing to app unless the
assignee independently mapped there, which makes the pin a comment about an
intention rather than a control.

The gap has a concrete cost on a real board. On ClickUp, assignees are
licensed workspace members — there are no bot users to invent, and the
validator's one-assignee-one-team rule means a PERSON can only ever trigger a
single tier. The natural gesture — three Lists, one per tier, drop a task in
the right one — was unexpressible.

Now the pin decides. After mirror() has fetched the item (a thin clickup
webhook carries no List, so the scope is only known post-fetch, which is why
this cannot live in a provider), ingest overrides the assignee's team with the
container's pinned team, then resolves the target — so the team-qualified
routing rules see the team the work will actually run as. Unpinned containers
are byte-for-byte unchanged: the assignee still decides.

Config grows ScopeTeams(): every project with both a pinned id and a team.
Name-resolved projects are deliberately absent until a resolver hands their
ids back — the shape that needs pinning is the shape that pins ids.

Gates: gofmt clean, go vet clean, go build ok, go test ./... ok, goldens
untouched (no chart change).
## [0.3.0-rc.4](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.3.0-rc.3...v0.3.0-rc.4) (2026-09-02)

### Added

* **ingest:** a container's pinned team decides, as the config always claimed ([cdb8d19](cdb8d195f9))
Adds a Repo rules section carrying the shared comment paragraph. Go doc
comments above exported identifiers are read by godoc, so they stay; the
estate decision is ADR 0006 in workflows.

VIK-828

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Capture the failing command, exit status and redacted stderr tail for git
steps and the OpenCode launch, expose it as failure.detail, and render it
in the browser and VS Code failure notices.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add a docker workspace backend that clones and serves OpenCode inside a hardened
container from the pinned agent image over the Engine socket, with only the
session's scoped LiteLLM key inside and candidate capture after a confirmed stop.
Make placement a per-session choice among runtime.backends, exposed through the
bootstrap, session creation, task import and the browser. Add an explicit
environment allow-list for host backends and agentSecrets for pods. Record the
decision in ADR 0009, including why agent-runner is not the interactive body and
why the API server proxy cannot carry OpenCode's authentication.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Session panel leads with a situation sentence and next action, answers
permissions and questions inline with scope and confirmation, lists
per-file changes and check outcomes, filters a chronological activity
stream, and reports four instruction delivery states. Open panels follow
the server event stream with polling as fallback and state when they
were last observed. The sidebar shows spend, role and age, expands into
decisions, crew, evidence and candidate, and badges waiting decisions;
the status bar opens the next decision. Creation and import run through
a back-navigable wizard with budget presets. Evidence documents use
stable URIs and panels survive window reloads. Notifications, a Get
Started walkthrough, copy link, tracker links and admin budget top-ups
were added. Client tests cover the stream and budget routes; the webview
check covers the new surfaces with hostile content.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add the estate's workflow set: pull-request gates, source-change checks plus
semantic-release, release-published distribution and docs deploy. One v<semver>
train versions the chart, both package.json files and the VSIX; the release
workflow builds both images from the tag, runs the CVE budget gate at zero
critical and zero high before cosign signing, pushes the chart to Harbor,
publishes the extension to Open VSX and optionally the Marketplace, and mirrors
to Forgejo and GitHub. Rebuild both images on Docker Hardened Images Alpine:
the workbench on the shell-less runtime, the agent on the dev variant with the
musl OpenCode binary fetched at build time and packages upgraded from the
hardened feed, both scanning clean at every severity. Default the chart's
images to appVersion, read the served version from package.json, and remove
the GitHub workflow tree.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add a worker relay on the workbench and a dial-out worker baked into the
agent image. In pull mode a Docker container publishes no port and a
Kubernetes pod needs no Service or ingress rule: the worker long-polls the
workbench with a per-workspace token, forwards each request to the local
OpenCode server, streams the response back and honours cancellation. The
Docker probe passes in pull mode against the hardened image, and the
sandbox landscape research is recorded under docs/research.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add a dependency-free WebSocket server and an AHP 0.9.0 host that maps
De Vloer sessions to ahp-session, ahp-chat and ahp-changeset channels:
initialize, subscribe, listSessions, createSession, createChat, dispatchAction,
resourceRead and reconnect, with durable events projected into turns, parts,
tool calls, permission confirmations and changeset files. Any number of
clients attach with a personal connection token and receive the same action
envelopes. Tokens are issued through POST /api/agent-host/tokens and fit the
chat.remoteAgentHosts setting in VS Code.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Each ready candidate now carries two DSSE envelopes signed with the
workbench's Ed25519 attestation key: an in-toto Statement v1 whose subjects
are the bundle, patch and manifest digests plus the git commit and tree, with
a predicate describing the session, crew, runs, workspace and spend; and an
Agent Trace 0.1.0 record mapping every changed file to the session and model.
The public key is served at /api/attestations/public-key, both envelopes are
downloadable formats, and scripts/verify-candidate.mjs verifies a downloaded
candidate offline.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add a sandbox provisioner for the Kubernetes backend: a cold Sandbox under a
RuntimeClass with a volume claim template and no Service, or a SandboxClaim
against a warm pool whose bound pod receives its session, repository and relay
token over the relay's pool endpoint, clones through the worker's exec control
and starts OpenCode on loopback. Candidates for pull-mode pods are captured in
place through the same control channel instead of an export pod. The chart
exposes the transport, relay address, provisioner, RuntimeClass and warm pool
and grants the workbench the CRD verbs. Record the relay, agent host, sandbox
and signed-candidate decisions as ADRs 0011 to 0014 with the research behind
them, and update the operations docs, API contract and validation evidence.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
## [0.3.0-rc.1](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.2.0...v0.3.0-rc.1) (2026-09-10)

### Added

* let sandboxes dial out through a pull-based relay instead of exposing a port ([7ebe8d6](7ebe8d6aa4))
* release images, chart and extension on one hardened Forgejo train ([3cf87fa](3cf87fa4f2))
* run agent workspaces in a sandboxed container and choose placement per session ([bd5aca3](bd5aca3e46))
* serve every session as an Agent Host Protocol host over WebSocket ([f3c88c3](f3c88c3fa5))
* show the recorded cause of workspace failures ([5c226d4](5c226d441a))
* sign every captured candidate with in-toto provenance and an Agent Trace record ([b56967e](b56967e725))
* **vscode:** attach the workbench as an agent host from the command palette ([5e8863a](5e8863a18c))
* **vscode:** rebuild the operator experience for review, decisions and live progress ([e985884](e9858843c1))
* warm Kata sandboxes through the Sandbox CRDs, pool assignment and in-place capture ([2144acc](2144acc590))

### Docs

* record which release prerequisites are done and which are deferred ([b4ad5e7](b4ad5e7253))

### CI

* build the docs site from the repository root like the generate step ([90365cb](90365cbe74))

### Internal

* pin opencode 1.18.30 through mise and ignore local launch scripts ([d23e5c9](d23e5c9859))
The Gitea release plugin publishes an asset-bearing release through a draft
that it flips to published afterwards; with the tag already pushed Forgejo
reports that flip as a release updated event, so v0.3.0-rc.1 never reached
the publish workflow. Drop the release asset from semantic-release and let
the publish workflow attach the VSIX it builds from the tagged tree instead,
idempotently. Record the hosted runs so far in the validation matrix.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
## [0.3.0-rc.2](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.1...v0.3.0-rc.2) (2026-09-10)

### Fixed

* **release:** create the release directly so Forgejo emits the published event ([6dec99c](6dec99c731))
The first full release chain published both signed images, the chart and
the mirrors, and attached the VSIX. Only the GitHub mirror failed, because
github.com/webgrip/de-vloer does not exist; its GHCR copies are org-scoped
and publish without it. Disable that job and record the run's evidence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Kubernetes 1.36 made hostUsers generally available, so a plain workspace
pod can map container root to an unprivileged uid on the node. It is opt-in
through kubernetes.userNamespaces and the chart's workspaceUserNamespaces
because the runtime must support it, and it is refused with the sandbox
provisioner, where a Kata guest kernel already draws that boundary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## [0.3.0-rc.3](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.2...v0.3.0-rc.3) (2026-09-10)

### Added

* **kubernetes:** offer user namespaces for workspace pods ([cd8913a](cd8913a321))

### Fixed

* **release:** hold the GitHub mirror until its repository exists ([de584c0](de584c0f3b))
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
v0.3.0-rc.3's "Distribute image (Forgejo)" job died in cosign-installer
on four consecutive HTTP 500s from github.com, after the image itself had
already been mirrored. workflows v2.6.2 gives both distribute lanes the
preinstalled-binary guard that cosign-sign-attest and helm-chart-push
already carry, so they use the ci-runner's /usr/local/bin/cosign.

forgejo-distribute.yml and github-distribute.yml are byte-identical from
the pinned v1 tags through v2.6.1, so this crosses the major without
taking any behaviour change but the fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## [0.3.0-rc.4](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.3...v0.3.0-rc.4) (2026-09-10)

### Fixed

* **release:** take the distribute lanes that use the runner's own cosign ([49b98db](49b98db662))

### Docs

* record the outcome of the first three hosted releases ([3c2585d](3c2585d5ef))
The research record compares both trunks as documented, as built and as
intended. ADR 0015 proposes a GET-only, bearer-authenticated operator API on
Ploeg and a projection in De Vloer, with PV-079 to PV-081 sequencing it and
PV-024 narrowed to the write half. The model gateway page turns the LiteLLM
1.99 and 1.100 release notes into capabilities De Vloer can offer and what
each needs. Live operation documents running a second estate as a profile.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ADR 0016 replaces administrator-seeded forge and tracker tokens with OIDC
login, per-person links, sessions without a repository, and publication as a
workbench action performed as the signed-in person. PV-082 to PV-085
sequence it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A 5xx from the gateway's management API is a connectivity failure whose
remediation is service health, not credentials and policy; the failure now
carries the call and status. The clone program wrote nothing on failure, so
a private repository without a credential surfaced as a bare exit code; it
now forwards the tail of git's stderr, which the failure detail redacts.
The backlog test counts the seven tickets added for ADRs 0015 and 0016.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A GitLab OAuth link per signed-in person, PKCE with a public application
so no client secret exists, tokens encrypted at rest beside the database
and refreshed before use, revocable from the Linked accounts view. When a
session starts on a repository from the linked GitLab, the clone step
receives the token as a git authorization header for that origin, in the
local, Docker and Kubernetes backends; the agent container never does.
The first half of ADR 0016; login and publication stay open.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The manager replaced the repository with its configured copy before handing
it to Docker or Kubernetes, so the GitLab link never reached the clone and
git asked for a username.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A session in a container or pod can be created with approval auto, or switched
to it while running, which answers the waiting permissions and creates later
roles with allow rules; read roles keep their edit, bash and task denials and
the local backend refuses it. The maintenance tick now reads each held gateway
key while a session runs and records budget.observed, so the budget panel
shows what the gateway has attributed instead of zero until settlement. A turn
refused because the key's ceiling is reached is classified budget_exhausted
with the remediation to authorize more and resume, instead of a gateway
rejection that sends people to an administrator.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The agent image installs ripgrep, which OpenCode's grep and glob tools need
and which every session was missing. Tool events now carry the part id,
input, output and error, the stream collapses each call to one card that
opens on demand, and every run records a transcript artifact with the model
that answered each message. Assistant text renders as markdown and the JSON
verdict block is stripped from summaries. Only the final role of a crew is a
reviewer; an earlier read role is an analysis role that is prompted to answer
with evidence and returns no verdict, so an investigation crew completes on
the challenger's approval. The budget panel lists per-model usage read from
the gateway's request ledger, including the routed group for auto-routers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The broker reads the gateway's request ledger per session key and records one
row per request: provider and endpoint host, inference region, routed group
with the router's tier, cause and savings, retries, fallbacks, guardrails,
cache hits and cached tokens, tokens, cost, duration and time to first token,
the calling harness, the call id for trace lookup and the error class on
refusal. The engine attributes rows to roles by time, refreshes them every
fifteen seconds while running and once more at settlement, and the signed
provenance records the usage and the set of providers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gatewayPolicy names the providers and regions a workbench allows. Before a
start the crew's models are resolved through the gateway catalogue, following
an auto-router into its tiers, and a model served elsewhere refuses the start.
Every ledger row is then checked; the first one outside policy stops the
session, revokes its key and marks the row. run.started records the composed
brief in sections with the model and a SHA-256 of the exact prompt, which the
provenance keeps per run. The budget panel draws cumulative cost against the
ceiling from the gateway's rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An OIDC authorization-code flow with PKCE, state and nonce for the browser,
completed server-side: discovery from the configured issuer, signing keys from
its JWKS, and an identity token checked for signature, issuer, audience,
expiry and nonce. The role comes from a role claim when the provider sends
one, otherwise from the groups configured for admin, operator and viewer; a
person in none of them is refused with a message naming the group. Users are
keyed by issuer and subject and refreshed on every sign-in. The login page
offers the provider first and keeps the local bootstrap account.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An observability block names the estate's Grafana, its dashboard uids and its
trace and log datasource uids. The Environment view lists the dashboards, the
budget panel links to the spend board whose per-run table is keyed by the
session's alias, and the Gateway tab opens Explore on the trace and log
datasources for the session's window or one request's window; query
templates accept the call id, alias and session id once the estate records
them. The acme profile lists the strict aliases that turn dropped
parameters into recorded refusals.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The session panel gains a Gateway tab with the per-request ledger, an
observed-spend budget card with per-model usage and a cost curve, tool cards
that collapse per call and open on input, output and error, a brief card per
role, transcripts on the Brief tab, an approval control for isolated
sessions, and a gateway-policy failure label. The tree labels analysis and
independent review, counts only the final read role as the reviewer and shows
approval and observed spend. New commands set approval and link or unlink
GitLab from the editor; the create wizard offers automatic approval for a
container or pod placement.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The backlog gained the operator-API, OIDC sign-in and account-linking
tickets without a rebuild, so `npm run design:check` failed the source
gate on a stale docs/PRODUCT-DESIGN.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A failed session offers "Try again" once spend has settled: the workspace is
released, runs return to queued, artifacts and ledger rows are cleared, and
the crew starts over with the same brief; "Duplicate as a new session" fills
the form from it instead. A clone refused for credentials names the link to
make or remake. Links are provider-generic: ClickUp joins GitLab with its
secret-bearing OAuth exchange, a task connection without its own token reads
with the signed-in person's link, and the Tasks view says when that link is
missing. The editor gains the retry command and links any configured
provider.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
## [0.3.0-rc.5](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.4...v0.3.0-rc.5) (2026-09-10)

### Added

* **auth:** sign in with the estate's identity provider ([00f2654](00f26546f8))
* **links:** let a person link their GitLab account and clone private repositories with it ([4a8d390](4a8d3905d5))
* **sessions:** a Gateway tab that shows who served each request and what the gateway did ([f855279](f855279b27))
* **sessions:** automatic tool approval in isolated placements, live observed spend, and a budget-exhausted failure ([da48c58](da48c585cb))
* **sessions:** gateway policy that fails closed, the brief each role received, and a cost curve ([aeaac5b](aeaac5bead))
* **sessions:** link every session to the estate's dashboards, traces and logs, and list strict aliases ([6c2264d](6c2264dd2a))
* **sessions:** show what the crew did, which model answered, and let analysis roles answer without a verdict ([38fc0ec](38fc0ecdc8))
* **sessions:** try a failed session again, duplicate it, and link ClickUp beside GitLab ([24071ad](24071ad1bb))
* **vscode:** bring the editor to parity with the workbench ([45835eb](45835eb75e))

### Fixed

* **links:** name the GitLab exchange failure and explain a confidential application ([972e9cf](972e9cfa8d))
* **runtime:** tell a gateway outage from a refusal, and let the clone step report git's error ([0383266](0383266e5a))
* **ui:** declare the observed spend before the session template uses it ([6d2bb99](6d2bb99ef9))
* **workspace:** keep the linked credential on the repository the manager validates ([66b16f9](66b16f9770))

### Docs

* propose that people sign in with the estate and link their own accounts ([4ac1df0](4ac1df0ffc))
* record what Ploeg and De Vloer each do, propose the operator read API, list gateway capabilities ([00bcb9c](00bcb9c8ed))
* regenerate the consolidated design for tickets PV-079 through PV-085 ([8fe8487](8fe8487b65))
A person links ClickUp from the Linked accounts page by pasting the personal
API token ClickUp issues them; the workbench verifies it against the account
endpoint and stores it encrypted for that person only. GitLab accepts a
personal access token the same way and uses it for private clones and the
API. OAuth remains optional on top, offered only when a workbench registers
an application. ClickUp is listed whenever a task connection uses it. The
editor gains the same paste action.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
## [0.3.0-rc.6](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.5...v0.3.0-rc.6) (2026-09-10)

### Added

* **links:** link ClickUp or GitLab by pasting a personal token, with no application registered ([d6618d6](d6618d65ea))
Connecting the extension to a workbench with single sign-on offers the
provider first. The editor asks the workbench for a one-time code and a
secret only it holds, opens the browser on the workbench's own sign-in with
that code, and polls until the person has signed in; the callback binds a
fresh session to the code, the browser lands on a "return to your editor"
notice, and the editor collects the session once with its secret. No client
registration of its own, and the editor acts as the same person as the
browser. The local account stays as a second choice.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
## [0.3.0-rc.7](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.6...v0.3.0-rc.7) (2026-09-10)

### Added

* **auth:** the editor signs in through the browser with the estate's identity provider ([ef6b084](ef6b084666))
A brief under twenty characters or four words is refused at creation. At
start the cheapest listed model is asked, with the session's own credential,
whether the brief is actionable; if not the session waits with its questions
and starts once the operator answers, with the answers appended to the brief.
Each role has a tool-call limit and is stopped as a runaway beyond it. An
investigation crew completes with its final reader's verdict instead of
failing on it, and the reviewer guidance returns inconclusive at once when
the brief was missing. Gateway rows keep their role across resumes by using
the run.started events, observed spend never trails the ledger total, and
settlement records an event only when something changed. The stream hides
budget ticks, names questions and permissions by role, and the model choice
shows the gateway's route for it. The editor's Gateway tab links traces,
logs and dashboards.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
## [0.3.0-rc.8](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.7...v0.3.0-rc.8) (2026-09-10)

### Added

* **sessions:** screen the brief before spending, cap runaway roles, and tidy what the session shows ([4c0fbd0](4c0fbd08f1))
"Remote" is gone from the editor: the view is Sessions, the panel names the
placement as a container on this machine, a pod in the cluster or a working
directory, and status lines follow. The create flow gains a model step that
shows the gateway's route for each choice, and ends in a review step listing
every choice, each selectable to change it, with create-and-start or
create-only instead of a modal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
## [0.3.0-rc.9](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.8...v0.3.0-rc.9) (2026-09-10)

### Added

* **vscode:** say where a session runs, choose the model, and review before creating ([fd52717](fd527178c5))
"Ready for human review" named a step that did not exist. A completed
session now reads "Awaiting your review" until a person records a decision:
accept with an optional note, or reject with a required reason the next
attempt receives. The decision, the reviewer and the time are on the session
and in its history, in both the web view and the editor, which gains a
Record Review command. While the gateway settles the last requests the
history says so instead of announcing a zero.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
## [0.3.0-rc.10](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.9...v0.3.0-rc.10) (2026-09-10)

### Added

* **sessions:** a person accepts or rejects a completed session, and settlement says what it is doing ([c904752](c904752630))
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
## [0.3.0-rc.11](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.10...v0.3.0-rc.11) (2026-09-10)

### Fixed

* **ui:** the status label helper called itself ([626ae1f](626ae1fd2b))
Amounts under a cent showed as zero for cheap open-weight models; both
interfaces now widen to five decimals below a cent and four below a dollar.
The editor panel re-rendered on every fifteen-second observation, dropping
focus and table scroll; it now skips a render when nothing but the freshness
badge changed, restores horizontal scroll on tables, and gives answer fields
ids so focus returns. Gateway rows made before the first role are attributed
to the brief check instead of showing an empty role.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
## [0.3.0-rc.12](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.11...v0.3.0-rc.12) (2026-09-10)

### Fixed

* **ui:** show sub-cent spend, keep the editor panel still while it refreshes, and name the brief check ([d58632c](d58632cc38))
Record the shared domain language, proposed ADRs and OpenSpec requirements for
operator sessions, control-plane credentials, canonical tracker binding and
verified candidate delivery. Preserve explicit qualification boundaries and
proposed decision status.

Validation: staged snapshot go build, ADR ledger tests and all nine OpenSpec
items pass. The complete implementation tree also passes the full Go and Helm
gates before this commit sequence.
Mint inference-only capabilities, reject invalid budgets, and block keys while
retaining their accounting identities. Bound and sanitize gateway responses,
query spend by hashed identity, and reference the inference environment from
OpenCode configuration instead of writing its credential to disk.

Validation: the independently exported staged tree passes go build ./...,
go vet ./... and go test ./..., including the existing worker and HTTP suites.
Keep freshly fetched container scope when webhook payloads are thin or stale.
Expose explicit open status and native revision from configured Vikunja and
ClickUp instances, and resolve registered forge repository coordinates for
subsequent canonical execution binding. Unknown status remains ineligible.

Validation: the independently exported staged tree passes go build ./...,
go vet ./..., all provider tests and the complete HTTP suite. The scope
regression was observed failing against the original mirror before its fix.
Make Ploeg the authenticated authority for workbench execution, worker
capabilities and conservative inference accounting. Bind tracker selections
to the existing pristine Work Item, fence unattended dispatch, and retain
canonical candidates, trusted verification, approval and publication barriers.

Wire controller-only secrets and named operator consumers through Helm. Keep
worker/controller authentication, generation checks and their database
migrations together so this layer is independently deployable after its
explicit configuration migration. Publish the contracts and operating guide.

Validation: independently exported staged tree passes go build ./...,
go vet ./..., go test ./..., gofmt, Helm lint, all four chart golden renders,
brand checks and all nine OpenSpec validations. Real cross-service tracker
and Docker delivery qualifications passed without model calls or publication.

BREAKING CHANGE: managed worker authentication is now the default. Configure
controller signing/bootstrap Secret references and LLM policies, remove
administrative worker credentials, and roll controller and workers together.
Compatibility requires explicit legacy authentication and static-compatibility
inference. See docs/ops/managed-workers.md for the existing-deployment upgrade.
## [1.0.0-rc.1](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.3.0-rc.4...v1.0.0-rc.1) (2026-09-11)

### ⚠ BREAKING CHANGES

* **control-plane:** managed worker authentication is now the default. Configure
  controller signing/bootstrap Secret references and LLM policies, remove
  administrative worker credentials, and roll controller and workers together.
  Compatibility requires explicit legacy authentication and static-compatibility
  inference. See docs/ops/managed-workers.md for the existing-deployment upgrade.

### Added

* **control-plane:** unify managed execution and verified delivery ([9028cf0](9028cf0718))

### Fixed

* **llm:** scope gateway keys and preserve accounting identities ([762c27d](762c27d5a1))
* **tracker:** retain authoritative scope and expose fresh execution state ([e00209e](e00209ed14))

### Docs

* **agents:** adopt the estate no-comments rule ([259a817](259a817a3f))
* **architecture:** define unified execution and delivery authority ([1a8b00c](1a8b00c216))
## [0.3.0-rc.13](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.12...v0.3.0-rc.13) (2026-09-11)

### Added

* **delivery:** verify canonical candidates before human approval ([4f3ec05](4f3ec05549))
* **demo:** launch a local Ploeg and workbench test stack ([977a21d](977a21d18a))
* **ploeg:** bind human sessions and tracker imports to shared execution ([60f6cae](60f6cae447))
* **vscode:** inspect scoped Ploeg work and execution bindings ([9d4e495](9d4e49511a))

### Fixed

* **demo:** finish Git metadata writes before confirming pause ([efeac15](efeac15e64))
* **demo:** recover interrupted workspace initialization ([164e928](164e928a2b))
* **vscode:** resolve workbench guide in packaged extension ([e1018e5](e1018e562f))

### Docs

* record unified architecture and qualification evidence ([6423765](6423765ae8))
## [0.3.0-rc.5](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.3.0-rc.4...v0.3.0-rc.5) (2026-09-11)

### ⚠ BREAKING CHANGES

* **control-plane:** managed worker authentication is now the default. Configure
  controller signing/bootstrap Secret references and LLM policies, remove
  administrative worker credentials, and roll controller and workers together.
  Compatibility requires explicit legacy authentication and static-compatibility
  inference. See docs/ops/managed-workers.md for the existing-deployment upgrade.

### Added

* **control-plane:** unify managed execution and verified delivery ([9028cf0](9028cf0718))

### Fixed

* **llm:** scope gateway keys and preserve accounting identities ([762c27d](762c27d5a1))
* **release:** keep experimental Ploeg releases on zero major ([46056cf](46056cf7b9))
* **tracker:** retain authoritative scope and expose fresh execution state ([e00209e](e00209ed14))

### Docs

* **agents:** adopt the estate no-comments rule ([259a817](259a817a3f))
* **architecture:** define unified execution and delivery authority ([1a8b00c](1a8b00c216))
* **domain:** ground work in tickets and useful research ([cf02ac7](cf02ac7dbc))
* **release:** record withdrawal of the mistaken 1.x release ([b8f5dc8](b8f5dc83d4))

### Internal

* **release:** v1.0.0-rc.1 [skip ci] ([d854f15](d854f15413))
## [0.3.0-rc.6](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.3.0-rc.5...v0.3.0-rc.6) (2026-09-11)

### Fixed

* **release:** use available outputs to enable Forgejo publishers ([fff3d3d](fff3d3daec))

### Docs

* **release:** record corrected prerelease publication ([32039f3](32039f3d54))
## [0.3.0-rc.14](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.13...v0.3.0-rc.14) (2026-09-11)

### Fixed

* **kubernetes:** frame API request bodies and use writable state directory ([b02ef2a](b02ef2a079))

### Docs

* **product:** clarify tickets and research outcomes ([a874ac3](a874ac3578))
Adds a plain-language guide to Ploeg, De Vloer and the surrounding
systems: C4 views, bottleneck analysis, open product questions, the
domain model and glossary, and the alternatives/implementation evidence
behind them. Ships a single-file interactive explorer with the diagrams
pre-rendered, built by scripts/build-landscape.mjs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both 1.0.0-rc.1 changelog sections were duplicates carrying no content of
their own: the 2026-09-11 "Withdrawn" section repeats 0.3.0-rc.5 entry for
entry, and the 2026-07-26 section repeats 0.2.0-rc.1 — the other half of the
duplicate that 772f3ab started removing. The breaking-change notice for
managed worker authentication survives where it belongs, under 0.3.0-rc.5.

Also gone: the withdrawal narrative in the release-versioning runbook, the
release-bot bullets for the two 1.0.0-rc.1 cuts, and the doc commits that
existed only to describe the incident. The withdrawn-v1.0.0-rc.1 tag is
removed from Forgejo and from local clones.

The policy itself is untouched on purpose. ADR 0028, .releaserc.cjs,
scripts/release-policy.cjs and its tests all stay exactly as they are — the
fixtures naming 1.0.0-rc.1 are what prove a stray 1.x tag is refused instead
of producing another one, and this repo has cut a mistaken 1.x twice. Erasing
the record is worth doing; erasing the guard is not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The chart and both images have only ever existed on harbor.webgrip.dev, which
answers 401 to anything without a robot credential. the second estate's staging cluster
holds none, so De Vloer could not be deployed there at all — not a config gap,
a registry one.

The GitHub distribute job was already wired and sitting at enabled: false;
it now runs on any release with a version, mirroring tags, the release, the
de-vloer image and the chart.

The agent image needs its own job. image-name takes a single string, and a
second instance of the same reusable in one caller collides under Forgejo v15
flattening — the constraint already recorded against the chart push. Copying
it by digest with imagetools carries the manifest list, so provenance and SBOM
travel inside the index; cosign accessories stay Harbor-only, which is where
Kyverno verifies against anyway.

Leaving the agent image behind would have published a control plane that
pulls fine and then cannot start a single session, surfacing as
ImagePullBackOff on a workspace pod long after the release looked green.

Both GHCR packages are created private by the first push and need flipping
public once, or anonymous pulls fail the same way Harbor does today.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## [0.3.0-rc.15](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.14...v0.3.0-rc.15) (2026-09-11)

### Added

* **release:** publish de-vloer to GHCR so Harbor stops being the only door ([48d37f3](48d37f3518))

### Docs

* **landscape:** add ecosystem explanation, domain model and explorer ([abe8294](abe8294f02))
The LICENSE carried the Apache appendix placeholders since the first commit. Adds NOTICE, inbound contribution terms, a license-check gate and REUSE 3.3 compliance, and canonicalises the commit identity with .mailmap.
Mark, wordmark, lockups, palette and social assets generated from one construction in scripts/build-brand.mjs. Apache-2.0 recorded as a decision with its copyright line filled, NOTICE, inbound terms and a license check; REUSE 3.3 via REUSE.toml.
Replaces the improvised CSS mark and the lowercase wordmark with the real mark, rebases the palette on the brand tokens, serves Archivo with its OFL text instead of falling back to Arial, and swaps the stale editor icon.
npm run license:reuse called uvx, which is not on the runner PATH (mise is a local-only toolchain here), so the gate failed at step 11 and blocked the release. The REUSE invariants that can actually drift — REUSE.toml present, declaring the repository licence, with a matching text in LICENSES/ — are now asserted by the dependency-free license check. The full reuse lint stays available locally.
The reuse lint needed uvx, which is not on the runner PATH. The REUSE invariants that can drift are now asserted by the dependency-free license check; the full lint stays a local recipe.
## [0.3.0-rc.7](https://forgejo.webgrip.dev/webgrip/ploeg/compare/v0.3.0-rc.6...v0.3.0-rc.7) (2026-09-11)

### Fixed

* **ci:** stop the licence gate depending on a network tool install ([94c7c8e](94c7c8e2dc))

### Docs

* **release:** drop the 1.x candidate from the record ([6dfb43f](6dfb43f6f1))
* **release:** point test deployments at the corrected publisher ([166e85c](166e85caa7))

### Internal

* **licence:** name the copyright holder and hold Apache-2.0 in CI ([5681fb4](5681fb4577))
## [0.3.0-rc.16](https://forgejo.webgrip.dev/webgrip/de-vloer/compare/v0.3.0-rc.15...v0.3.0-rc.16) (2026-09-11)

### Added

* **brand:** add the De Vloer identity and hold Apache-2.0 in CI ([63615ee](63615ee96e))
* **extension:** publish through Open VSX and mirror releases to GHCR ([32c1d79](32c1d79a82))
* **ui:** repaint the workbench on the brand palette and self-host Archivo ([fcb99c6](fcb99c6c3d))

### Fixed

* **ci:** stop the licence gate depending on a network tool install ([1d617dc](1d617dc14a))
Publishing to the Visual Studio Marketplace needs an Azure DevOps
organisation, which since 2026 must be linked to an active Azure
subscription, and the PAT it issues is retired on 2026-12-01. Its
successor needs that same subscription. Both routes therefore cost the
same prerequisite and one of them expires, so doing the expiring one
first buys nothing.

Open VSX reaches Cursor, Windsurf, VSCodium, code-server, Gitpod, Theia,
Kiro and Antigravity; only plain VS Code is left to the release asset.
The Marketplace step stays in the release job and stays inert until a
credential appears, so enabling it later is a secrets change rather than
a workflow change, and its missing credential is now a notice rather
than a warning on every stable release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Forgejo's push_mirrors API requires repository administration, so the
preview preflight failed with HTTP 403 under the write-scoped CI bot.
Compare the development head, tags and release-channel notes between
Forgejo and GitHub with git ls-remote instead, which proves the mirror
outcome without widening the bot's access. Correct the migration notes
that still described the Open VSX bridge and OpenBao signing role as
scoped to the old repositories.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The imported semantic-release note refs disappeared from Forgejo, which
failed the import verifier in the checks job. The mirror job still ran
under always() and pruned GitHub to the empty set, destroying the only
remote copy. Require checks and release-policy to succeed before the
copy, keeping always() so a skipped release does not block it, and state
the case in the workflow policy tests. The 67 note refs were restored
from a clone that matches the import manifest.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Record a dated dossier for BAND (band.ai / Thenvoi AI), its competitors and
the interoperability standards landscape, with the verdict reject-as-dependency
and mine-for-design. BAND fails on seam fit before maturity: neither
application has an agent-to-agent channel, so there is no facade to watchlist.

The reusable finding is the layer map. Authority, leases, provenance and
ticket-to-merge delivery are claimed by no protocol, and MCP's own maintainers
state that boundary deliberately. Cost enforcement exists only in gateways,
which is where ADR-0008 already puts it; the join between spend and a
delegated identity is claimed by nobody.

Add the shortlist row and section to the market landscape, a BAND and
delegated-authority entry to the protocol appendix, and Ploeg backlog item 125
pointing at AP2 and the delegation-receipts draft as prior art for the budget
seam. Amend the standing AHP and A2A records with new evidence: independent
AHP hosts exist while Microsoft acknowledges none, and A2A's missing lease
semantics are now confirmed by spec grep rather than inferred.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four gateways enforce spend caps, not three: LangSmith's LLM Gateway does it at
organisation, workspace, API-key and user level with per-trace attribution. The
pattern is unchanged and reinforced, since every one of them is a gateway.

Record AG2 v1.0's Network as BAND's closest architectural analogue: a hub with
a registry, audit log and typed channels over a pluggable WebSocket transport,
Apache-2.0. Also note LangGraph Platform serves A2A at /a2a/{assistant_id}, so
A2A adoption is wider than the two GA surfaces the Linux Foundation named.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The 9 September shortlist understated Kandev: it ships an inter-agent message
bus with interrupt semantics, workflow gates and profile handoffs on a
per-session Kubernetes executor, so coordination and placement are covered.
Budgets, provenance, ACP and replay are still absent.

Record the empty columns across the whole category, because five of the six
are this product's existing design: provenance on candidates, a budget bound
to a ticket, replay as a contract, declared reviewer crews, per-customer
tenancy, and a self-hosted control plane. Note OpenHands Enterprise as the
closest funded alternative, budgeting through the same LiteLLM seam.

Record the finding that cuts the other way: ACP has won the editor seam and
AHP has no adoption outside this repository, so a second editor surface should
be justified by a named client and would argue for ACP. Added as a trigger,
not a reversal of ADR-0012, whose VS Code justification still holds.

Add the budget-matched topology evidence (arXiv 2609.13890): crews buy 2.4
points on easy tasks and 21.1 on hard ones at ten times the tokens, which
argues against crews by default and for per-task cost accounting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Slack already occupies BAND's seam. Agents are @mentionable channel members
on every plan, the Slack-hosted MCP server went GA on 2026-02-17 with
user-token OAuth so an agent inherits the caller's permissions, admin approval
gates every server, and every AI action writes an audit log. BAND's remaining
differentiators against the incumbent are peer transport and cross-org
contacts, neither of which has demonstrated demand. Glue and Teamily AI are
direct analogues, and Humans& raised $480M at $4.5B on the coordination
thesis, so "nobody funded this proposition" was wrong.

The spend-and-identity join is not unclaimed either: ServiceNow AI Control
Tower attributes first- and third-party token spend by asset, user and
department. The claim that survives is narrower and still unoccupied - a
budget bound to a task, minted as a scoped credential and revoked when the
task closes. Stated that way it cannot be contradicted by a ServiceNow
customer, which the broader version could.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The layer-map claims about cost, provenance and lease semantics were inferred
from spec prose. They are now counted directly in schema/2026-07-28/schema.ts
across all 3,197 lines: budget, billing, quota, provenance, attest, signature,
signing, sbom, slsa, lease, exactly-once and fencing are all zero. The only
four cost hits are ModelPreferences.costPriority, a model-selection hint that
lives inside Sampling, which this revision deprecates.

Against SEP-2663, task creation is strongly consistent and that is the whole
durability guarantee; update and cancel are eventually consistent, cancellation
is cooperative and ttlMs is a retention hint. No ownership, claim, fencing
token or exactly-once delivery.

Record that MCP frames multi-agent as an identity problem on its own roadmap,
not a messaging one, and that the Agents Extension is an open evaluation with
no champion and no date. Waiting for MCP to grow an agent-to-agent layer is
waiting on a question.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Land the conformance and roadmap sweep behind ADR 0012, with its evidence,
the ADR update, the index status line and the Ploeg backlog re-confirmation.

VS Code 1.138.0 stable offers 0.9.0 down to 0.5.1, so the handshake matches
today, and remoteAgentHostsEnabled and remoteAgentHostsAutoConnect both
default to true. Independent hosts now exist. A third-party host extension
API is named as a non-goal in Microsoft's own roadmap, so the host seam is
the supported path rather than a waypoint.

Accepting 0.9.x alone is recorded as a defect: minors land every 13 days and
each is a wire break, and the spec directs hosts to hold a declared set and
pick the highest offered. The host must also persist serverSeq and evict
projections. SessionStatus carries IsRead and IsArchived bits nothing stands
behind, which makes PV-048 a conformance gap as well as an operational one.

Terminals and resource writes stay declined while AHP#266 is unresolved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Het preset gaat zelf van een gepinde verwijzing uit: default.json draagt een
soak-uitzondering voor preset-updates (die alleen bestaat als er een versie te
bumpen valt) en een pinDigests: false waarvan de beschrijving zegt dat de
verwijzing 'is already pinned to a protected semver release tag'.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The no-fallback guard checked only the confirmed execution binding. When
the admit response was lost and the execution configuration was then
removed, the session could launch standalone. The persisted admission
intent now marks the session managed: launch, retry and budget increases
refuse it without Ploeg authority, and a restart records it as blocked
until it is reconciled with Ploeg or cancelled by an operator.

Adds tests for the lost admission response, its reconciliation by
replay, the authority_required guard and mid-run heartbeat loss.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each launch mints a LiteLLM key for the remaining budget, and the key is
never extended in place. The increase was refused only when an active
session already held a key, so an increase between launch and mint left
the key below the recorded budget. It is now refused whenever the session
executes or holds unreconciled keys, and applies to the key minted on the
next resume.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An admitted execution whose admit response was lost never received a
start command, so the expiry sweep (which only looked at running and
stop-pending states) never touched it. Its Run stayed running, its Lease
stayed held and its Shift budget stayed reserved indefinitely.

The sweep now cancels admissions past their deadline, closes the Run,
Lease and Shift through the same path the cancel command uses, and
returns them so the controller blocks the untouched inference account.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Record the owner's 2026-09-22 decision and the inventory that informed it.
AGENTS.md now states Ploeg authority for every run and Ploeg's vocabulary.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Store.ReconcileLLMAccount had no production caller, so managed-mode
holds were never replaced by actual spend and a Shift's pool filled
with holds from finished Runs.

ploegd's sweep loop now pages finished Runs whose account is still
reserved, or blocked and unchanged for PLOEG_LLM_SETTLE_AFTER (default
15m). Accounts with no durable sign of a mint settle at zero with
mint-never-began evidence; blocked accounts settle at the gateway's
spend for their alias. The store's invariants still apply: no
settlement below a recorded observation, idempotent replays, and
minting, issued or unknown accounts are never settled automatically.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Agent Host Protocol connection tokens never expired and nothing revoked
them. A token now expires after auth.sessionHours without use, each
connection or message renews it, and it is bound to the sign-in that
issued it: signing out or the end of that sign-in revokes it and closes
its open connections. Tokens issued before this change carry no expiry
and are rejected; issue a new one.

The VS Code extension re-issues the token of an existing
chat.remoteAgentHosts entry after sign-in and removes the entry it issued
on sign-out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rewrite the start page, README and llms.txt around Glide's goal, add
concept pages for the work loop and architecture, restructure the nav by
page type and restate the documentation policy with page types, one
answer per question and record handling.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every tracker item was given agent/vik-<id>, so ClickUp tasks were
named as if they came from Vikunja and could collide with a Vikunja
task of the same id. work.Branch now keeps the deployed Vikunja form
byte for byte and gives other trackers agent/<provider>-<id> with
ref-unsafe characters replaced.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The foundation patch for the former external Ploeg repository was never
applied, and Ploeg's source now lives in this monorepo. Remove it and the
links that pointed at its handoff.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
work.CanTransition and its transition map had no caller outside their
own test; the store moves Work Items with SQL. The Work Item lifecycle
stays documented in docs/domain/model.yaml and backlog #11 remains the
place to reintroduce an enforced state machine.

StateIngested is kept: the ingest upsert still matches 'ingested' rows,
the operator API accepts it as a filter, and the TaskSpec, operator API
and tracker execution schemas publish it as an enum value.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every live launch needs a scoped model credential, from the LiteLLM
broker or from Ploeg, and the external OpenCode backend refuses to
receive one. A live deployment with OPENCODE_URL or runtime.backend
external therefore failed at every launch. Configuration validation now
rejects that combination at startup with an explanation. The backend
itself is kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A key's running total on LiteLLM_VerificationToken is written by an
async batch writer and disappears when the key is deleted, so it reads
close to zero at run exit (VIK-769). Settlement now sums the gateway's
spend logs for the account's recorded hashed key and any key still
carrying its alias, through an optional llmbroker.Settler capability.
A broker without that capability never settles a minted account.

VIK-769
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The chart's sources and home still pointed at webgrip/ploeg while the
ploegd image, like both Vloer images, names github.com/webgrip/glide
and the Forgejo Glide repository. Helm derives the chart's OCI source
and URL annotations from these fields, so the published chart linked to
a repository it is no longer built from. Chart name and version are
unchanged; the stale explanatory YAML comments are dropped in favour of
the dated note in ADR-0020.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A fresh clone has no refs/notes, so the import verifier failed the daily
gate. Setup now fetches the notes when an origin remote exists. Locally the
verifier checks tree, tags and ancestry and skips absent notes with an
explicit message; GLIDE_REQUIRE_IMPORT_NOTES=true keeps CI strict.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The npm:opencode-ai pin moves from [tools] to the live and probe-opencode
tasks, so helm lint and every other mise exec under apps/vloer no longer
installs it. Renovate's mise manager still reads task-level tools, keeping
the pin grouped with the agent image.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
mirror-source-metadata force-pushed refs/notes to GitHub with --prune on
every green development push and on manual runs. It now needs a push event
and GLIDE_RELEASES_ENABLED, like the other publication jobs. The CI verify
step sets GLIDE_REQUIRE_IMPORT_NOTES so absent notes fail the source gate
instead of letting the mirror prune GitHub to an empty set.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ploegd was built and signed without the Grype and OpenVEX budget gate that
Vloer's images pass. The gate moves to the shared root actions and runs on
the ploegd digest before the signing job, with an enforced zero critical and
zero high budget; a local Grype scan of the release Dockerfile found none.
Workflow policy tests cover both CVE gates, the notes mirror gate and the
strict import check, and the artifact guide describes both.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Renovate reads only the root renovate.json; the application copies were
byte-identical. Vloer's mkdocs.yml and TechDocs hook predate the shared
root docs build and nothing invokes them; the research record now links
their last revision. browser-ploeg-check.mjs had no script, workflow or
document entry point. Ploeg's mise header described a pre-monorepo Go
toolchain that the root configuration now pins.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The cross-application run only admitted zero-model demo executions, so
Ploeg's reservation, key issuance and blocking never ran across both
applications. A local fake LiteLLM admin API now backs Ploeg's real broker
while De Vloer drives a completed and a cancelled managed execution. The
driver asserts two capped, scoped keys, both blocked, and no inference
request; the report keeps zero model calls and zero spend and names the
fake gateway in its limits.

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

# Conflicts:
#	README.md
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A role pointed at a harness binary missing from the runner image claimed
a Run, then failed at exec time and parked the item after burning an
attempt. The worker now resolves the harness program the same way the
adapter does and exits non-zero with the program's name before it
contacts ploegd. Programs relative to the clone or built from a task
placeholder are left to run time.

VIK-770
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A worker that dies without reporting (SIGKILL, OOM kill, node loss)
held its Lease and Run for the chart's 15m TTL before the sweeper
reclaimed it. Workers renew at TTL/3 and give up after three failed
renewals, and ploegd's own default is 60s, so a 5m TTL keeps the same
renewal ratios while cutting reclaim to about 5m plus one sweep
interval. It still rides out a ploegd outage of roughly three to five
minutes before live runs cancel themselves. Deployment values that set
the TTL explicitly are unaffected.

VIK-766
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Align the product model with ADR-0002: Ploeg is the Authority for every
Run and Vloer runs only its deterministic demo without Ploeg. Rule R8 and
the standalone open questions now cite the decision, and the product-level
Execution term is retired in favour of Shift and Run.

Add plain-language terms for Glide, Vloer, Ploeg, Admission, Authority,
Crew, Step, Verdict, Qualification, Cutover, AHP, TechDocs, Zensical and
OpenSpec. Record competing meanings as not-to-be-confused-with entries.

Generate one combined glossary at docs/reference/glossary.md from both
models with `mise run domain`, and make docs-check fail when it or any
per-model page is stale or when two models define the same term.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Vloer copy of the product model was byte-identical to docs/domain and
its pages only pointed there. Links now target the root domain pages and
the combined glossary. Old repository links still resolve through the
document path aliases, and legacy /de-vloer/domain/ redirects already
point at the root pages.

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

# Conflicts:
#	apps/vloer/mkdocs.yml
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The validator now accepts the Vloer ledger's inline "Date: … Status: …"
records as legacy records: status and date are checked, MADR section
checks are skipped. The Vloer index gains ADR, status and date columns so
registry parity is checked; the former status text moves to a scope and
evidence column unchanged. ADR 0008 gains its creation date and leads
with its proposed status. No decision's status or content changed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs/reference/decisions.md lists accepted decisions with their
superseding record, then proposed decisions with an implemented verdict
backed by a source pointer, then every other status. Verdicts live in
decisions-implementation.yaml and default to unknown. Regenerate with
mise run docs-decisions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every link to a Vloer redirect stub now points at the maintained page and
heading, which also repairs the seven anchors that only existed in the
pre-move pages. Entry pages link Glide paths instead of the archived
de-vloer and ploeg repositories where the target exists here. The Vloer
and Ploeg start pages now link their brand, executor, domain and model
gateway pages and the decision register. The landscape explorer and the
design chapter guide are regenerated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs.py now fails on a missing heading anchor, on a current page that
links through a moved path, and on a current page that is neither in the
nav nor linked from a nav page. It validates all three ADR ledgers and
the decision register. Staging gives every history page (research,
evidence, ADR records, design baselines, planning, OpenSpec) front matter
search.exclude and a "Record from <date>; not current guidance." banner
without touching the sources. MkDocs and Zensical 0.0.53 honor that front
matter, so the search index post-processing is removed; the output check
still fails when history reaches a search index. ADR index pages become
searchable as the current view of each ledger.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The openhands, claude-code and exec harnesses ran under a context with
no deadline, so a wedged agent (for example after the OpenHands PTY
reader crash) held its Run until the Job's two-hour deadline.

The worker now bounds each harness run with PLOEG_HARNESS_TIMEOUT and,
for spawned harnesses, ends one that writes no output for
PLOEG_HARNESS_IDLE_TIMEOUT. Either kills the harness's whole process
group, lets the deferred revocation block the key, and reports failed
with the new failure reason timeout, which counts against the agent
retry budget. The chart sets 100m and 15m, below the 7200s Job
deadline, overridable per team and role; 0 disables a bound.

VIK-734
VIK-767
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

# Conflicts:
#	apps/vloer/README.md
#	apps/vloer/docs/architecture.md
#	apps/vloer/docs/contracts/ploeg-execution.md
#	apps/vloer/docs/index.md
#	docs/landscape/explorer.html
#	mise.toml
#	scripts/docs.py
A configured tracker project without a webhook never dispatched
anything, and nothing said so. With Vikunja API credentials, ploegd
now checks each configured project at startup and hourly for a webhook
that sends task.assignee.created to Ploeg, logs a warning naming each
project that lacks one, and lists them in /readyz without failing
readiness. PLOEG_VIKUNJA_WEBHOOK_REGISTER=true opts in to registering a
signed webhook, and requires PLOEG_VIKUNJA_WEBHOOK_URL and the webhook
secret.

VIK-768
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ploeg now settles a successful Shift whose pull request is ready for
human review as awaiting_review. The operator projection validates item
states strictly, so an unknown state would have made the whole Ploeg
view unavailable. The state is accepted, filterable and labelled
"Ready for review".

VIK-765
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A configured plan that completed or was approved with a pull request
parked at needs_human, the same state as a stuck or failed Shift, so
the board could not tell "ready for review" from "broken".

A Work Item now enters awaiting_review when a configured plan closes
as plan_exhausted or review_approved after a writer opened or updated a
pull request, and when a plan-less or pre-Shift run reports pr_opened
or pr_updated. Stuck runs, failures, caps and exhausted budgets still
settle at needs_human. Re-assigning an awaiting_review item re-queues
it like any finished item. The tracker comment says the pull request is
ready for review. Migration 0016 adds a NOT VALID check constraint
listing every Work Item state, and the published schemas, domain model
and OpenSpec scenarios name the new state.

VIK-765
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A plan-less team's Shift has pool 0, so ClaimRole authorized each Run at
0 or less and ReserveLLMAccount refused it: under managed worker auth no
plan-less team could obtain a model key. An unpooled Shift now authorizes
the Role cap, and a zero authorization defers to the team's key policy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Project identifiers stay in the ploegd warning log.

VIK-768

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Record ADR-0003: a Work Item is something decided, or a problem described
well enough to conceive a solution, from any source. Add Ready and
Product R12, retire Ticket, Workload and Repair Subticket, and lead the
entry pages with units of work instead of tracker tickets.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rewrite the root, Ploeg and Vloer AGENTS.md around commands and rules an
agent cannot infer, state repository-wide rules once at the root, and add
CLAUDE.md symlinks so Claude Code loads the same text in every directory.
Move the team-silver OpenHands skill to the repository root, where
OpenHands loads it again after the monorepo import. Rewrite the target
repository guide around offline verify commands, per-harness loading and
treating instruction files as code, and record the research behind it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Enforce word and byte budgets, resolvable links and backticked paths,
known mise and npm commands, a CLAUDE.md bridge beside every AGENTS.md,
no invisible Unicode in agent files, and no sentences duplicated between
the root and application AGENTS.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 1beb24b02d9359614cfd4a874fd5c6c5528e7b8a)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Writers read the nearest AGENTS.md, run its verify command and list checks
the sandbox cannot run instead of pulling CI images. Readers judge the work
against the base branch's AGENTS.md and report changes to agent instruction
files. The tracker reference follows the provider, keeping VIK-<id> for
Vikunja.

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

# Conflicts:
#	docs/how-to/prepare-a-repository.md
The prompt mixed "Ticket" headings with "Work Item" rules. Agents follow
the words they are given, so the prompt now uses the glossary term
throughout (Glide ADR-0003).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A project PreToolUse hook on Bash denies git add -A, --all and . and
git commit -a/--all, because shared checkouts must stage explicit paths.
Ignore local agent worktrees and settings.local.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit d9da1bcfc0eb7854cc149da491ab052a0ada7772)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PLOEG_TEST_PG_PORT_OFFSET shifts the fixed test ports so several
worktrees can run the database-backed suites at the same time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The settlement sweep already reads a managed Run's LiteLLM spend logs to
settle its cost. It now also sums their prompt and completion tokens and
collects their models, and merges inputTokens, outputTokens, models and
costUsd into agent_runs.usage in the settlement transaction. Keys the
harness reported, such as sessionId, are kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vloer's demo, verify and chart tasks and Ploeg's spec-check task were
subsets of the root demo and verify tasks, and nothing in CI, scripts or
docs called them. The root tasks remain visible from each application
directory.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A team with a reading Role and no executor.<forge>.readTokenSecret now fails
the chart render instead of silently falling back to the read-write builder
token. Role workloads carry PLOEG_FORGE_TOKEN_ACCESS, and the worker ends a
reading claim stuck before cloning unless its token is marked read-only. The
values schema rejects a half-set readTokenSecret, and helm-golden.sh checks
that the chart refuses the reader-without-token fixture.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
List the four delivery routes, state the editor sign-in exemption from
the mutation guard, document agent-host token lifetime and sign-out
revocation, and the budget refusals. Map every src module and correct
the workspace backend default to local.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sharpen the Crew definition against the configuration validation and
regenerate the domain pages, combined glossary and landscape explorer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Forgejo reads workflows only from the root .forgejo directory. The
per-application symlinks existed so relative links in application docs
resolved; those links now point at the root workflows directly. The
workflow policy test now asserts the applications carry no .forgejo
directory, and the CI guide links the original Vloer entry points by
their imported commit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brand folders under apps/*/docs/brand are classified as parked history,
except TRADEMARK.md, which the README links as the current mark policy.
Tests pin the design baseline, PRODUCT-DESIGN, go-to-market and backlog
paths as records.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Archives assemble-glide-monorepo, audit-current-documentation,
bind-tracker-execution, govern-candidate-delivery,
guard-zero-major-prereleases and unified-operator-execution with
openspec archive, which folds their deltas into openspec/specs.
Links follow the moved folders.

close-the-review-loop stays open: 19 of 22 tasks are ticked with code
evidence; ADR-0017 ratification, the per-PR gate record and the
network-path runbook note remain.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A merged pull request moves its Work Item from awaiting_review to done; a
pull request closed without merging moves it to needs_human. Forgejo and
GitLab webhooks emit pr_merged and pr_closed events, and a reconcile loop
asks the forge for each awaiting_review pull request every
PLOEG_REVIEW_RECONCILE_INTERVAL (default 10m) to cover missed deliveries.

ForgeProvider gains PullRequestState for both forges. On merge the tracker
gets a comment and, unless PLOEG_TRACKER_DONE_ON_MERGE=false, the done status.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Shift that closes because its pool cannot fund another Round, either
at the sweep's floor check or before a fix round, now adds a Budget
exhausted line to the tracker comment with the spent, reserved and pool
amounts to two decimals. When the Shift has a pull request, the same
notice is posted there once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Ploeg view opens on Awaiting review while it holds work. Selecting an
awaiting_review Work Item shows the pull request link, branch, close
reason, each Run's Role, Round, outcome and verdict, reviewer findings,
Shift spend to two decimals and instruction files named in findings.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The adapter is lifted through RunCommand against a fake claude on PATH that
records its argv and working directory, and asserts that the process receives
--settings '{"disableAllHooks":true}' as one argument and --strict-mcp-config
without an --mcp-config.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A fixture repository carries a root and a nested AGENTS.md that each ask for
a canary token spelled out in parts. TestLiveCanaries runs each harness on
PATH through its adapter with a prompt that names no instruction file and
fails when a sighting contradicts the "Loads by itself" column of the
prepare-a-repository how-to. TestLiveClaudeCodeIgnoresTargetHooksAndMCPServers
plants a hook and an .mcp.json server that write marker files, asserts none
appears under the adapter, and requires a control run without the guards to
fire the hook.

Both are skipped unless PLOEG_HARNESS_CONFORMANCE=1; mise run
harness-conformance sets it. Offline tests pin that the fixture and prompt
never contain the tokens themselves.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Design for retiring Vloer's engine in shippable increments: a Work Item
submission route, a live view over the operator events endpoint, Shift
commands for pause and cancel, steering between Runs, one OpenCode driver
through the ACP adapter, the integration.mjs migration and the deletions
in both applications. Vloer ADR-0023 records the recommended choices as
proposed.

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

# Conflicts:
#	docs/how-to/assign-work-to-an-agent.md
Unassigning a Vikunja or ClickUp item now withdraws its Work Item: the
live Shift closes with reason withdrawn_unassigned, pending Runs are
cancelled, running Runs are finished and their model keys blocked, the
Lease and its push credential are released, and the item moves to the
new withdrawn state. No sweep retries it; a new assignment re-queues it
with attempts reset.

POST /api/v1/operator/work-items/{id}/cancel does the same for an
execute-permitted consumer, closing with withdrawn_by_operator. Items
bound to an Operator Execution are left to that execution's cancel.

Migration 0017 admits the withdrawn state; the operator, task spec and
tracker execution schemas list it, and SettleItem no longer settles a
withdrawn item back into the queue.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
scripts/docs-configuration.py scans the Go packages each binary compiles
for os.Getenv, os.LookupEnv and the helpers that wrap them, and reads the
chart's values.yaml comments and values.schema.json descriptions.
Descriptions the source lacks come from existing docs, kept in
configuration-descriptions.yaml. mise run docs-check fails on drift.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tracker write-back on merge defaults to off: Ploeg's policy treats a
tracker item as done only once the change runs in production, so
PLOEG_TRACKER_DONE_ON_MERGE=true opts in.

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

# Conflicts:
#	apps/vloer/public/ploeg.js
#	docs/concepts/how-work-flows.md
#	docs/how-to/assign-work-to-an-agent.md
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Before the harness starts, the worker walks the clone for AGENTS.md and
CLAUDE.md at any depth, .cursorrules, .mcp.json and everything under
.claude/, .agents/ and .openhands/. The path and SHA-256 of each file ride
on the Run's first checkpoint (instructionFiles, checkpoint.v1 schema) and
land in the checkpoint.written audit entry. Invisible, bidi or zero-width
Unicode in any of them, or a symlink that leaves the clone or loops, ends
the Run stuck before the harness starts, with a reason naming each file,
line and column.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The de-vloer chart had no home or sources, so its published OCI artifact
carried no source or URL annotation while both Vloer images name the Glide
repository. It now matches the Ploeg chart. Name and version are unchanged.
A release test reads both charts' metadata through Helm so neither drifts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Most 12 September blockers are fixed in code or GitOps: notes are back on
Forgejo, the notes mirror is gated, ploegd has a CVE budget, signing waits
before distribution, and Glide is in the signing role, Open VSX bridge and
Renovate discovery. The GitHub mirror lacks all 67 note refs, so the preview
preflight cannot pass until they are copied once. The playbook links the
new record and its summary.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ploegd now serves Prometheus text exposition computed from the database
on scrape and cached for PLOEG_METRICS_CACHE_TTL (default 15s): open and
idle Shifts per team, overdue Leases, issued/unknown inference accounts
past their key TTL, settled spend in the last hour, and the Vikunja
webhook coverage /readyz already reports. The exposition is written by
hand because the Prometheus client is not a dependency and a few SQL
gauges need no registry.

Backlog #39.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A failed check on a Ploeg pull request branch now creates a queued repair
Follow-Up (origin follow_up) that names its source Work Item and pull
request, carries its Work Target and runs on the same branch for the Team
that owns it. One repair is open per pull request at a time, and a
per-team cap bounds how many are ever created.

A person's request for changes on a Ploeg pull request is stored as a
review. A live Shift runs a fix Round that receives it as briefing; a
Work Item awaiting review is queued again for a new Shift that does.
Reviews by Ploeg's own forge login are ignored.

Both are off unless a team sets teams.<name>.forgeFollowUps.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Both are off by default. The rule carries five alerts with configurable
thresholds, for, severity and runbook links: PloegShiftStuck,
PloegLeaseExpired, PloegModelKeyPastTTL, PloegSpendSpike and
PloegTrackerWebhookMissing. A monitoring golden renders them enabled,
and verify lints the new values file.

Backlog #39.

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

# Conflicts:
#	docs/how-to/prepare-a-repository.md
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Backlog #39.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Nothing in src, test, scripts or the VS Code extension calls it. The
engine's crash recovery revokes each alias from aliasesForSession itself.

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

# Conflicts:
#	apps/ploeg/docs/ops/managed-workers.md
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Only broker.test.ts called LiteLLMBroker.extend, and no engine path does:
addBudget refuses while a key is live and the next mint carries the new
budget. The method and its interface members are gone, and test brokers
no longer carry an extend stub.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add five runbooks under apps/ploeg/docs/how-to: recover a stuck Lease
or Run, rotate credentials, drain workers before maintenance,
investigate a Run's spend, and restore after a database outage. Each
states the symptom and goal, gives kubectl and psql commands checked
against the migrations, a verification step and a symptom/cause/fix
table, and labels missing tooling "Not implemented yet".

Link the runbook index from the Ploeg docs index and nest it under
How-to > Operate Ploeg in the site navigation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each backlog file now opens with a note that the external tracker owns
status and priority. The Vloer README note comes from the backlog
generator so backlog check stays green. The path rules already treat
these files as history.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
mise run verify now runs the Vloer, Vloer extension, Ploeg, Helm,
release, integration and docs groups concurrently. Each group stays
sequential, output is buffered per gate and printed in group order with
result and duration, and the first failure cancels running gates and
skips pending ones. Integration gets its own embedded PostgreSQL port
offset so it cannot collide with the Ploeg database suites.

The CI verify action restores the Go module/build caches and the npm
download cache, and sets GOFLAGS=-count=1 so restored caches never turn
into cached test results.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A team can now carry maxRunning, the most Runs it may have running at
once. ploegd enforces it inside the claim transaction behind a per-team
advisory lock, so concurrent claims cannot each see room. A claim over
the cap answers 204 like an empty queue and the worker exits 0.
Operator executions do not count. Unset or 0 keeps the old behaviour.

Configure it as executor.teams[].maxRunning (rendered to
PLOEG_TEAM_MAX_RUNNING) or teams.<name>.maxRunning in PLOEG_CONFIG,
which wins. The chart clamps each workload's KEDA maxReplicaCount to it.

GET /api/v1/queue/depth had no consumer: KEDA reads Postgres, the
CronJob executor polls by spawning, and Vloer reads the operator API.
The route, its worker-auth branch, Store.QueueDepth, its test and the
queueDepthResponse schema are removed; the contracts record the removal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
No HTTP route, AHP method or test calls it. Connection tokens end through
expiry or revokeSignIn, which sign-out calls.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A failed check on a merged, withdrawn or needs_human Work Item's branch no longer creates a repair Follow-Up.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
cmd/ploeg-worker calls RunContext, and no test or package calls Run. The
RunContext doc now carries the claim and empty-queue contract.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Forgejo had no refs/notes on 2026-09-22 although 67 were verified on
2026-09-12. No workflow, semantic-release run or history rewrite in this
repository touched notes on origin; the record lists the evidence. The
repository-side hazard was the GitHub mirror: an empty fetch followed by
push --prune empties GitHub. The mirror now requires every imported note
at its manifest object before pushing, and a release test rejects any
tracked workflow, action, script or mise task that prunes, mirrors or
deletes refs or pushes notes outside that guarded push.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Vloer config disabled webgrip/workflows because its github.com lookup
aborted runs; the merged root config lost that rule. Job-level webgrip/*
reusable workflows now resolve through gitea-tags on Forgejo. Symlinked
application workflow copies are ignored so an update cannot replace a
symlink with a file. The Go overlay manages Ploeg's indirect modules, and
the CI toolchain images used by release-check and docs-site-check are
managed with their workflow copies.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Six KPIs for the Work Item to review-ready loop, each with a formula,
Ploeg tables and columns, baseline method, provisional target, the
decision it changes and its Goodhart pair. SQL for each was run against
migrations 0001-0016. The Grafana spec uses stat and table panels only.

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

# Conflicts:
#	mkdocs.yml
Covers the forgeFollowUps team switch in how work flows, the review how-to, the configuration reference and the domain models, and regenerates the derived pages.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Hardware does not limit throughput yet: owner review time, gateway
provider caps and one-pod ScaledJobs do. Tune before buying.

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

# Conflicts:
#	apps/ploeg/cmd/ploegd/main.go
#	apps/ploeg/docs/reference/configuration.md
#	apps/ploeg/pkg/config/config.go
#	apps/ploeg/pkg/httpapi/server.go
Runs fixture Work Items (small repositories with known-good tests) through
configured variants and records pass/fail, steps, tampered test paths and,
on live runs, observed key spend. By default it uses scripted harnesses and
the local fake LiteLLM gateway, so it makes no model calls. Command
harnesses run only with GLIDE_EVAL_LIVE=1. The deterministic variants
calibrate the grader and run under mise run verify.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Covers picking ten Ready Work Items of mixed size and kind, fixing one Team
and plan, the fields to record per item, and a dated research record
template. Documents the evaluation harness for comparing variants first.

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

# Conflicts:
#	mkdocs.yml
#	scripts/verify.mjs
docs-check now fails when a current nav page, or any page under
concepts/, how-to/ or reference/, lacks valid type, audience, owner and
last_verified. Generated reference pages carry generated_by instead of a
date, and a page known to be out of date states why in unverified.

Adds mise run docs-stale (180-day report, never fails), a demo smoke
script, a pinned lychee external link task and a warning-only Vale
config whose vocabulary is generated from both domain models.

Front matter is added to the nav pages that lacked it. Each
last_verified names the sources checked; landscape/questions.md is
marked unverified because it predates ADR-0002 and ADR-0003.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Source and pull-request workflows gain a tutorial-smoke job that starts
mise run demo-unified until unified-demo.ready, stops it with SIGTERM
and runs the documented --smoke check. It is deterministic, needs no
secrets, gates no release and skips when the runner lacks PostgreSQL or
a non-root user.

on_schedule.yml runs the pinned lychee task every Monday and only
reports. The workflow policy tests cover both.

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

# Conflicts:
#	docs/landscape/explorer.html
#	docs/landscape/generated-sources.json
#	docs/operations/ci.md
#	scripts/docs.py
An outcome report may now carry createdWorkItems (title, description,
ready, kind and an optional team). ploegd stores each accepted entry as a
follow_up Work Item that exists only in Ploeg, names its source Work Item
and Run, sits one level deeper than its source and inherits its Work
Target. Entries over a Team's limits are rejected with a reason recorded
in the audit log.

Each Team gets conservative defaults, overridable under
teams.<name>.createdWork: maxCreatedPerRun 5, maxDepth 2, maxOpen 20, an
item budget of 2.00 USD that caps the created Work Item's Shift pool, and
a 10.00 USD pool per root Work Item. Created work waits as proposed until
an operator approves or rejects it through the new
/api/v1/operator/work-items/{id}/approve and /reject routes, unless the
Team sets autoDispatch. Work that is not Ready goes to a configured
refinement team or planner Role, and otherwise stays proposed.

A plan Role marked planner gets a planner prompt that asks for
createdWorkItems instead of code.

VIK-1122

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
VIK-1122

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ploeg ADR-0031 (proposed) records the created-work mechanism, its
defaults and the two questions the owner has not answered: whether
created Work Items are written back to the tracker, and whether a person
approves them before dispatch. The domain models, the Work that creates
work table and the generated references follow.

VIK-1122

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Renumber the created-work migration to 0019 and reuse the
source_work_item_id column that forge follow-ups added in 0018. A Work
Item a Run created now owns its own branch and does not count toward its
source's repair cap.

VIK-1122

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GET /api/v1/operator/summary?window=24h|7d|30d reports per-team and total
Work Item counts, current and windowed Run counts, settled and reserved
spend, and the latest audit activity. GET /api/v1/operator/runs lists Runs
newest first with team, state and outcome filters and a before cursor.
GET /api/v1/operator/events accepts order=desc with a before cursor; the
default ascending response is unchanged.

Migration 0020 adds partial indexes for the windowed and per-item reads.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ploeg opens on an Overview of per-team counts and spend for a 24h, 7d
or 30d window, with Activity, Runs and Proposed tabs before the existing
lanes. Activity pages older events and refreshes every 15 s without
duplicates or a scroll jump. Proposed work shows Approve and Reject to
operators and administrators through an authenticated, CSRF-guarded
proxy that also covers cancel. A Ploeg without the activity routes gets
a clear note. Money uses nl-NL US dollars to two decimals, and the demo
serves fixed records with zero spend.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The parallel verify gate made the 5s wait too tight in CI. Wait up to 30s
for any terminal state and assert completion, so a real failure reports
its reason instead of a timeout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Zensical's 404.html has an <article>, so finalize marked it searchable and
verify-publication counted one page more than the reading scope.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
macOS has no timeout binary, so mise run verify failed locally.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The pr_opened arm dropped runErr, so a writer that pushed and then died was
recorded identically to a clean one. The outcome stays pr_opened; the
failure reason, a summary note and the log tail are now kept. Ported from
webgrip/ploeg#45.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A plan that ran out while the reviewer of the pull request still requested
changes settled at awaiting_review. It now settles at needs_human. Replaces
webgrip/ploeg#43.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Static broker hands back the shared org-wide token, which every writer
pod already holds as AGENT_BUILDER_TOKEN from the same secret. Returning it
on the wire bought nothing and exposed the forge's strongest credential in
a response body. Ported from webgrip/ploeg#45.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Seven-agent sweep of ~80 projects against Ploeg's seams. Nothing removes
Ploeg's viability; leases and event-driven execution are no longer
exclusive, spend authorization is. Proposes ADR-0005/0009 amendments.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
0032 restates 0005: keep the dispatch plane, retire event-driven dispatch
and the lease as differentiators, compete on authorized spend, and run the
second executor on agent-sandbox v1beta1. 0033 restates 0009 with corrected
facts and extends it to Multica.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The harness runs as the worker's user in the worker's container, so it could
read /proc/<worker>/environ and take the long-lived worker bootstrap token and
builder forge token. The worker now marks itself non-dumpable before anything
else runs. A Linux test shows a same-user child reads the secret without the
call and cannot with it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
executor.type: sandbox keeps the KEDA ScaledJob and its claim predicate, but
the Job's pod becomes a launcher (ploeg-worker sandbox-launch) that creates one
cold SandboxClaim on kubernetes-sigs/agent-sandbox v1beta1 and waits for it.
The worker pod comes from a chart-rendered SandboxTemplate built from the same
worker pod template, with an optional RuntimeClass for Kata or gVisor.

Backstops: pod activeDeadlineSeconds, claim shutdownTime with shutdownPolicy
Delete, and an owner reference to the launcher's Job. Only the launcher holds
an API token, scoped to sandboxclaims create/get/delete. Plain REST, no
client-go. Opt-in; keda stays the default. Implements proposed ADR-0032 and
replaces backlog #58's v1alpha1 plan (OpenSpec add-agent-sandbox-executor).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With PLOEG_LLM_KEY_ISOLATION=proxy (chart: executor.litellm.keyIsolation) the
harness receives a random placeholder and a loopback base URL; a reverse proxy
inside the now non-dumpable worker attaches the real key on each request to
LiteLLM, streaming unchanged, and stops when the Run ends. A prompt-injected
harness can still spend within its budget but cannot leak a key into a commit
or pull request. Opt-in until each harness is qualified: one that calls the
model from inside DinD cannot reach the worker's loopback.

Proposes ADR-0034: the harness gets placeholders, the worker keeps the
credentials; the forge token is next.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With PLOEG_FORGE_TOKEN_ISOLATION=proxy (chart: executor.forgeTokenIsolation)
the worker serves a loopback proxy scoped to the Run's own repository: git is
redirected to it with url.insteadOf, the prompt names it as the forge URL, and
AGENT_BUILDER_TOKEN holds a placeholder. The proxy adds the real token for git
over HTTP and that repository's Forgejo or GitLab API paths only, and answers
403 to other repositories, user and admin endpoints and path traversal. A
leaked token can no longer outlive the Run in a commit or PR body.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
executor.sandbox.networkPolicy switches each SandboxTemplate to a Managed
NetworkPolicy with exactly the given ingress and egress rules, so a cluster
without its own worker policies can still limit a Run to DNS, ploegd, the model
gateway and the forge. Empty keeps Unmanaged; agent-sandbox's secure default is
never used because it blocks all three.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vloer had no guard: one breaking commit would have computed 1.0.0-rc.1, which
the homelab Renovate rule (/^0\./) never offers. Vloer now mirrors Ploeg's
policy: breaking changes raise the minor version and verifyRelease refuses
anything but 0.x.y-rc.N from development. The release-policy action runs the
new suite; the isolation test copies both applications' policy scripts.

The weekly schedule gains a release-notes job that fails when Forgejo no
longer holds every imported semantic-release note, after the second loss on
2026-09-23/24 went unnoticed until a release preview. Also fixes ADR-0028's
stale v${version} tag wording.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Forgejo lost all 67 refs/notes/semantic-release-* refs again, so checks
failed in verify-import.py. The notes were pushed back unchanged from a
clone matching the import manifest; this commit starts a fresh run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Glide ADR-0004: since ADR-0002 Vloer runs real work only through Ploeg,
so separate vloer-v and ploeg-v versions describe pairs nobody runs.
One semantic-release train in apps/.releaserc.cjs (package-path apps,
package-name glide) now tags glide-v<version>; only commits under apps/
count. scripts/release-policy.cjs keeps ADR-0028's zero-major candidate
policy for both, and scripts/release-prepare.mjs sets both charts,
Vloer's manifests and the extension to the same version.

A glide-v tag publishes both applications' artifacts under unchanged
names. Ploeg's final distribution waits for Vloer's so the two never
create the same GitHub release at once, and each attaches its own
release-artifacts-<app>.json.

Release-channel notes now stay on Forgejo: the GitHub notes mirror and
its prune are removed, the preflight compares branches and tags only,
and test_release_refs.py forbids any pruning or notes push.

The annotated baseline tag glide-v0.3.0 at cec9f13 marks where
vloer-v0.3.0-rc.16 and ploeg-v0.3.0-rc.7 met; against the real history
the first candidate computes to glide-v0.4.0-rc.1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Releases were enabled (GLIDE_RELEASES_ENABLED=true) and the owner asked to
start the first Glide release. This empty commit only triggers evaluation;
the version comes from the releasable commits since glide-v0.3.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.1](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.3.0...glide-v0.4.0-rc.1) (2026-09-27)

### Added

* **docs:** enforce page front matter and report stale pages ([04ac8ec](04ac8ec324))
* **docs:** publish Glide through Zensical with source exports ([7fc4da7](7fc4da7a7c))
* **ploeg:** act on failed checks and requested changes behind a team switch ([ecd6034](ecd6034a4a))
* **ploeg:** add an experimental agent-sandbox executor ([d136491](d1364911d5))
* **ploeg:** add operator activity summary, Run list and newest-first events ([bba3657](bba3657705))
* **ploeg:** add optional ServiceMonitor and PrometheusRule to the chart ([f700f2b](f700f2b88c)), references [#39](webgrip/glide#39)
* **ploeg:** cap running Runs per team and drop the unused queue depth route ([d0bd049](d0bd0492d2))
* **ploeg:** expose operational gauges at GET /metrics ([d4e54d0](d4e54d02b1)), references [#39](webgrip/glide#39)
* **ploeg:** keep a writer's forge token out of the harness ([6066cb6](6066cb6409))
* **ploeg:** keep the per-Run model key out of the harness ([7f77fa4](7f77fa426c))
* **ploeg:** let a Run create Work Items that wait for approval ([8971081](89710817f4))
* **ploeg:** let sandbox Runs carry an egress allowlist ([eee198e](eee198e246))
* **ploeg:** never hand a reading Role the read-write forge token ([bd1848e](bd1848e9f9))
* **ploeg:** rank repository instructions below the delivery contract in the worker prompt ([7dbaae6](7dbaae6ffd))
* **ploeg:** record gateway tokens and models on settled Runs ([92c1cb7](92c1cb79aa))
* **ploeg:** report Vikunja projects that have no Ploeg webhook ([cf782df](cf782df5f9))
* **ploeg:** say so when a Shift runs out of budget ([0e9faa9](0e9faa95ec))
* **ploeg:** scan agent instruction files before the harness runs ([cb62c69](cb62c69a4e))
* **ploeg:** settle awaiting_review items when their pull request merges or closes ([d63e7cb](d63e7cb5a2))
* **ploeg:** settle successful Shifts as awaiting_review ([7a6afed](7a6afedfe5))
* **ploeg:** stop target repository hooks and MCP servers under claude-code ([f340805](f340805c65))
* **ploeg:** withdraw tracker work on unassignment or operator cancel ([d28f906](d28f9064f0))
* **vloer:** accept Ploeg's awaiting_review work item state ([c7b4a1c](c7b4a1c241))
* **vloer:** accept the proposed Ploeg work-item state ([fa2f00d](fa2f00dea3))
* **vloer:** accept the withdrawn Ploeg work-item state ([91253ea](91253ea786))
* **vloer:** add an Awaiting review lane and a read-only review screen ([4f22e0d](4f22e0d3ed))
* **vloer:** show what Ploeg has been doing ([d0d3309](d0d3309efc))

### Fixed

* **adr:** make the Vloer ledger pass the consistency validator ([d15c343](d15c343616))
* **ploeg:** bound hung harness runs with a timeout and an idle watchdog ([dd064ce](dd064ceac2))
* **ploeg:** call the unit of work a Work Item in the agent prompt ([1885d5c](1885d5c58c))
* **ploeg:** cancel operator admissions that expire unstarted ([767078c](767078cd42))
* **ploeg:** check the harness program exists before claiming ([8b4e2b8](8b4e2b8ae9))
* **ploeg:** default the chart's lease TTL to 5m ([660f689](660f6895bb))
* **ploeg:** derive the work branch from the item's tracker ([fb9cb4d](fb9cb4d5fc))
* **ploeg:** fund managed Runs on Shifts without a budget pool ([1700ddd](1700ddd0a6))
* **ploeg:** hide the worker's environment from the harness it starts ([19f085c](19f085c35f))
* **ploeg:** name Glide as the Helm chart's source ([c60db9e](c60db9e00d))
* **ploeg:** park an exhausted plan whose last review asked for changes ([4dc7091](4dc709136f)), references [webgrip/ploeg#43](webgrip/ploeg#43)
* **ploeg:** put only a minted forge token in a claim response ([fbe1f19](fbe1f19a6d)), references [webgrip/ploeg#45](webgrip/ploeg#45)
* **ploeg:** record why a Run that opened a PR then failed ([58b1a01](58b1a01701)), references [webgrip/ploeg#45](webgrip/ploeg#45)
* **ploeg:** repair only pull requests that are still awaiting review ([0dd34d4](0dd34d48fc))
* **ploeg:** report only webhook coverage counts on the unauthenticated readiness probe ([e25cda0](e25cda0efd))
* **ploeg:** settle finished managed inference accounts from the controller ([9d825d5](9d825d5a25))
* **ploeg:** settle managed accounts from LiteLLM spend logs ([e95ba87](e95ba87f99))
* **release:** publish verified Glide artifacts to internal and public registries ([957565d](957565d566))
* **vloer:** bound agent host connection tokens and revoke them on sign-out ([6d79090](6d79090ee4))
* **vloer:** give the demo core test room on a contended runner ([9cac30a](9cac30a089))
* **vloer:** keep sessions with a persisted Ploeg admission intent managed ([d393b62](d393b62684))
* **vloer:** let the stop-signal test observe the child's exit on a busy runner ([05291bc](05291bcce7))
* **vloer:** name Glide as the Helm chart's source ([7cb584a](7cb584a4cd))
* **vloer:** refuse standalone budget increases while a model key is live ([5cf4bba](5cf4bbac10))
* **vloer:** reject an external OpenCode endpoint in live configuration ([414baa8](414baa86e7))

### Changed

* **ploeg:** remove the unused CanTransition lifecycle table ([13c4b15](13c4b158e6)), references [#11](webgrip/glide#11)

### Docs

* **adr:** record the 2026-09-17 agent host roadmap sweep ([52999da](52999dad9e)), references [AHP#266](https://forgejo.webgrip.dev/AHP/issues/266)
* **agents:** trim instruction files to non-inferable rules and bridge CLAUDE.md ([714b036](714b0360c8))
* align Vloer pages with ADR-0002 and the combined glossary ([af7ac46](af7ac463c6))
* describe forge events that create and return work ([1c68194](1c68194a70))
* **domain:** unify the glossaries under Ploeg's execution vocabulary ([d29acfa](d29acfa8e0))
* link real pages instead of redirect stubs and archived repositories ([57475eb](57475eb92a))
* make the Work Item the unit of work and let work create work ([c27d0c6](c27d0c6438))
* mark the Ploeg and Vloer backlogs as frozen planning records ([7123444](7123444005))
* **ploeg:** add operator runbooks as how-to pages ([cde4881](cde48813a4))
* **ploeg:** describe the metrics and what to check for each alert ([e277518](e2775184ef)), references [#39](webgrip/glide#39)
* **ploeg:** generate the configuration reference from source and chart ([8ddafe0](8ddafe0b5b))
* **ploeg:** propose ADRs 0032 and 0033 from the landscape survey ([3d2dac4](3d2dac4a20))
* **ploeg:** propose ranking target repository instructions below the delivery contract ([f8039b8](f8039b8096))
* **ploeg:** record the 20k-star agent-orchestration landscape fit survey ([b2ac80c](b2ac80c191))
* **ploeg:** regenerate the configuration reference after merges ([508d523](508d5239c1))
* **ploeg:** regenerate the configuration reference for concurrency caps ([ff122d6](ff122d62fc))
* **ploeg:** regenerate the configuration reference for metrics settings ([44ac075](44ac0755c9))
* record how Runs create Work Items and the open owner questions ([596c283](596c283ca6))
* **research:** add gateway budget enforcement and AG2 Network to the BAND survey ([a09a679](a09a6790a6))
* **research:** correct two overstated claims in the BAND survey ([ef52b06](ef52b06e56))
* **research:** refresh the coding-agent workbench category and correct Kandev ([8e99317](8e99317f00))
* **research:** survey BAND and the agent interaction layer ([7355fbd](7355fbdcd4))
* **research:** verify the MCP absences against the normative schema ([b91abd5](b91abd59a0))
* **vloer:** align the HTTP contract and architecture map with the code ([1661d33](1661d33e91))
* **vloer:** propose Vloer as Ploeg's front end ([3a9ad7d](3a9ad7d5ee))
* **vloer:** remove the duplicate product model and its moved-page stubs ([34b44ae](34b44ae736))

### Tests

* **integration:** mint and block managed keys against a fake LiteLLM ([e394fca](e394fca103))
* **ploeg:** add opt-in canary conformance for harness instruction loading ([bb90cf6](bb90cf6d7e))
* **ploeg:** drain launcher output before noise assertions ([a350052](a3500528fe))
* **ploeg:** let parallel runs offset the embedded Postgres ports ([91d2ee5](91d2ee59ff))
* **ploeg:** run a fake claude to prove target hooks and MCP servers stay off ([41d2927](41d292712a))

### Build

* **vloer:** install OpenCode only for the tasks that run it ([e489be9](e489be9a08))

### CI

* align Glide with Webgrip workflow entry points ([2bface8](2bface847d))
* hold the Ploeg image to a CVE budget before signing ([17e05df](17e05df1dd))
* **release:** hold Vloer at zero-major and watch the imported release notes ([ed6d631](ed6d631281))
* **release:** release Vloer and Ploeg under one Glide version ([3316717](3316717407))

### Internal

* **ci:** remove the application workflow symlinks ([ef15e68](ef15e68805))
* drop application mise tasks that duplicate root tasks ([f8fdf9e](f8fdf9e5de))
* **ploeg:** archive the six completed OpenSpec changes ([f09fdbc](f09fdbc4d9))
* **ploeg:** remove the uncalled Worker.Run shim ([fa1e9a7](fa1e9a7bdb))
* remove duplicated and orphaned files left by the import ([5c055c2](5c055c2ee8))
* **vloer:** remove AgentHost.revokeToken, which no route reaches ([49b4cc0](49b4cc006a))
* **vloer:** remove the unapplied Ploeg patch ([5d1f59d](5d1f59dfca))
* **vloer:** remove the uncalled LiteLLMBroker.revokeSession ([bd13d7a](bd13d7a4ae))
* **vloer:** remove the unused broker extend operation ([dcaa678](dcaa678d27))
glide-v0.4.0-rc.1 left de-vloer-agent unsigned: the CVE gate counted one
High, CVE-2026-85091 in Alpine zlib 1.3.2. No Alpine branch ships the
1.3.3 fix yet, and the flaw is reachable only through gzprintf()/
gzvprintf() after a stalled non-blocking gzwrite(). No ELF file in the
image imports the gz file API, so the owner approved an OpenVEX
not_affected statement (vulnerable_code_not_in_execute_path); with it,
grype reports no High or Critical finding for the image.

The build now proves that claim instead of asserting it:
check-gz-imports.mjs reads every ELF file's dynamic import table and
fails the image build if any imports gzprintf, gzvprintf, gzwrite,
gzopen or gzdopen. It passes on the released image and fails once
binutils' libctf, which imports gzwrite, is added.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
In glide-v0.4.0-rc.1 Ploeg's final distribution was skipped because it
needed Vloer's, which the de-vloer-agent CVE gate stopped. Ploeg still
runs after Vloer so the two never create the same GitHub release at
once, but always() lets it run whatever Vloer's result.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.2](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.1...glide-v0.4.0-rc.2) (2026-09-27)

### Fixed

* **vloer:** record the zlib CVE-2026-85091 exposure of the workspace image and enforce it ([4036286](4036286b34))
glide-v0.4.0-rc.2's final distribution failed for both applications:
Forgejo answers HTTP 400 when linking a package that is already linked,
and de-vloer, de-vloer-agent, ploegd and both charts are still linked to
the archived webgrip/de-vloer and webgrip/ploeg repositories. The
publisher now reads the current link: already on webgrip/glide is a
no-op, so a retry cannot fail on it; a link to another repository is
unlinked first; an unlinked package is linked.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A read-only measurement of Ploeg 0.4.0-rc.2 and its whole history: 6 of
17 agent pull requests merged, all before the Shift engine; no Shift
settled ready for review; spend recorded for 3 of 56 unattended Runs.
It also finds the loop disconnected in production: the Ploeg tracker
project routes to the archived repository, bronze is paused and the
board has no assignment webhook.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
All fourteen tasks are done and shipped in glide-v0.4.0-rc.1. The change's
requirements now live in the sandbox-executor spec, and the executor
contract points there instead of at the open change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
executor.teams[].executorType picks keda or sandbox for one team while
executor.type is keda or sandbox, so a cluster can qualify the
experimental agent-sandbox executor on a low-risk team before moving the
whole deployment. Teams without it render exactly as before; the chart
refuses it under the CronJob executor. A golden pins the mixed render and
a refusal case pins the CronJob rule.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.3](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.2...glide-v0.4.0-rc.3) (2026-09-27)

### Added

* **ploeg:** let one team run under the sandbox executor ([58a5ee5](58a5ee58a4))

### Docs

* **ploeg:** archive the add-agent-sandbox-executor change ([cb9872b](cb9872b287))
nextFixRound checked the pool, then the cap, then the verdict. A reviewer
approving the last fix round the cap allowed, or approving with a pool too
thin for another round, closed the Shift as fix_round_cap_reached or
budget_exhausted_before_fix_round, and the approved pull request parked at
needs_human instead of awaiting_review (backlog #115).

The verdict now decides whether a fix round is wanted; the pool and then
the cap bound only a wanted one. A reviewer that gave no verdict closes the
Shift as plan_exhausted rather than review_approved, as the
verdict-advancement spec already required.

ADR-0017 is still proposed, so its bound order is amended in place.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Forgejo and GitLab providers skipped verification when their webhook
secret was empty, and the chart had no way to set PLOEG_FORGEJO_SECRET. A
deployment with a Forgejo forge therefore accepted unsigned deliveries on
POST /webhooks/forge/forgejo from anything that could reach ploegd,
including agent worker pods in the same namespace. Those events settle
Work Items on merge or close and, under forgeFollowUps, queue paid repair
and rework. The chart schema already promised that an unset secret rejects
forge webhooks.

Both providers now reject every delivery without a secret, ploegd warns at
start when a forge has none, and executor.forgejo.webhookSecret renders
PLOEG_FORGEJO_SECRET. The Ploeg ops guide records what a forge webhook
needs, and that the webgrip deployment admits no Forgejo traffic to ploegd
today.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ticks 3.3 with the new bound order, and adds 3.7 and 5.6 for the two fixes
with their regression tests. 5.1 records the verify run that covers the
per-PR gates. 5.5 points at the forge webhook note, which corrects the
design's "blocked in both directions": only Ploeg's ingress policy blocks
Forgejo today.

1.2 stays open. ADR-0017 is proposed, and only a person can accept it, so
the change is not archived.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The evidence named 686c97a, the pre-rebase hash of the forge webhook fix.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
OpenSpec change execute-openspec-work-items: survey integration shape 4.

VIK-1267
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Work Item whose description has a line "openspec: <change-id>" is run from
that change. The worker locates openspec/changes/<id> in the clone (root or
nested, as Glide's apps/ploeg/openspec), briefs the Role from
`openspec instructions apply --json` or, without the CLI, from the change's
proposal, design and tasks, and carries the brief on the new optional
TaskSpec.openSpec field.

After a writer opens or updates its pull request, and after a reviewer on the
branch under review, the worker runs `openspec validate <id> --type change
--strict --json` on the pushed branch. A failing writer reports stuck with the
output; a failing reviewer's verdict becomes request_changes. A gate that
cannot run parks the Run. The CLI is never downloaded and runs with telemetry
off and no credentials.

OpenSpec change: execute-openspec-work-items.

VIK-1267
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The acp harness can now launch Qwen Code (`qwen --acp --auth-type=openai`,
checked against v0.24.6) and Goose (`goose acp`, checked against v1.52.0).
Both point at the Run's model gateway through environment variables only,
read AGENTS.md alone, keep per-Run homes in the scratch directory and switch
off the calls that bypass the gateway (usage statistics, auto-update, web
search, telemetry, session naming).

Each profile sets the agent's own approval mode from the Run's permission
mode, so allow_read_only and deny_all are answered by Ploeg's permission
policy instead of being bypassed; Profile.Prepare and Configure now take the
mode. Goose is configured by environment only and refuses configJson at
startup.

docs/contracts/acp-profiles.md records upstream versions, gateway wiring,
instruction files, stop reasons, shutdown behaviour, what an image needs and
why Codex is not a profile yet. Neither profile is qualified by a live Run.

VIK-574
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Run could not run the target repository's checks: agent-runner carries no
language toolchain by design and the ploeg namespace denies egress, so agents
shipped pull requests with red gates they never saw.

- executor.harness.toolchains mounts toolchain images read-only as image
  volumes; the worker puts their directories first on the harness PATH and
  refuses to claim when one is missing (PLOEG_TOOLCHAINS).
- executor.harness.verify lists operator-owned check commands. The harness
  gets them as $PLOEG_VERIFY_SCRIPT and in the prompt; after a writing Run
  opens or updates a pull request the worker runs them itself, without the
  forge token or model key, and adds the result to the Run's findings and
  summary, so it reaches the pull request and the next Round's briefing.
- pkg/harness/skills embeds ploeg-verify-before-handoff and
  ploeg-review-against-work-item (Agent Skills format) in the worker binary
  and installs them under the Run's HOME for each harness.
- A writer's findings comment no longer claims it could not push.

VIK-601

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
VIK-601

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Forgejo broker called /api/v1/admin/users/{bot}/tokens with an admin
token. Forgejo 15 serves no such route (404) and refuses token
authentication on /api/v1/users/{bot}/tokens ("auth method not allowed"),
so per-run push credentials could never be enabled: every writing claim
would have failed to mint. Checked against a local Forgejo 15.0.9.

ploegd now signs in as the bot with its password
(PLOEG_FORGEJO_BOT_PASSWORD, chart executor.forgejo.botPasswordSecret) and
mints each token with write:repository and a repository list holding only
the Run's repository. Git and the repository API answer 403 for any other
repository the bot can see. The chart refuses the old adminTokenSecret,
ploegd warns and ignores PLOEG_FORGEJO_ADMIN_TOKEN, and a worker refuses to
start with the bot password in its environment.

VIK-733

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
infra_node is written where the machine is known to have ended a Run: a
SIGTERM'd worker, a failed push-credential mint, or an ACP harness that
could not start. The sweeper keeps lease_lost because it sees an expired
lease, not a dead node. The mint path had no test.

VIK-612

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.4](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.3...glide-v0.4.0-rc.4) (2026-09-27)

### Added

* **ploeg:** add qwen-code and goose ACP profiles ([5467350](5467350220))
* **ploeg:** execute a Work Item from the OpenSpec change it names ([df7cd0d](df7cd0d18e))
* **ploeg:** give Runs mounted toolchains, Ploeg skills and a worker-run verification ([853d797](853d797eda))

### Fixed

* **ploeg:** close an approved review loop as approved ([02f0467](02f0467850)), references [#115](webgrip/glide#115)
* **ploeg:** mint per-run Forgejo tokens that reach only the Run's repository ([8c37e41](8c37e4127b))
* **ploeg:** reject forge webhooks when no secret is configured ([8edfc53](8edfc5341f))

### Docs

* **ploeg:** cite the pushed commit in close-the-review-loop task 5.1 ([3e4d23b](3e4d23baa7))
* **ploeg:** propose executing a Work Item from the OpenSpec change it names ([a4dafac](a4dafacfe5))
* **ploeg:** record ADR-0035 and the toolchain-and-checks runbook ([d1a6ca3](d1a6ca35ad))
* **ploeg:** record close-the-review-loop progress ([730e5a7](730e5a7f6e))

### Tests

* **ploeg:** pin infra_node on a failed push-credential mint and document each failure reason's writers ([c07192c](c07192c0d1))
rc.3's final distribution still failed for both applications: Forgejo
answers the unlink call with HTTP 500 ("no permission to unlink package
... from its repository") because the CI bot has no rights on the
archived webgrip/de-vloer and webgrip/ploeg repositories. The link only
decides which repository page lists a package, so a link that cannot be
moved now logs a warning and publication continues to GHCR, the Go
module export and the GitHub release.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The first rc.4 loop test (VIK-1279, Shift 84) failed four builder Runs
with infra_llm and zero spend: OpenHands never called LiteLLM. Since the
harness environment became an allowlist, LLM_MODEL carried the key-scope
name deepseek-chat, which ModelList strips of litellm_proxy/ because
LiteLLM rejects '/' in a key's model scope. OpenHands routes through the
LiteLLM SDK and needs litellm_proxy/deepseek-chat to reach the gateway.

The harness now gets the model as configured; key minting and the
adapters that call the gateway's OpenAI endpoint keep the stripped name.
The image's non-secret LITELLM_LOCAL_MODEL_COST_MAP and
OPENHANDS_SUPPRESS_BANNER pass through the allowlist again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CI run 82 for 901a466 failed "HTTP demo produces real failing and passing
checks…" in the integration gate: sessionUntil stopped polling after 20 s
while the demo was still running its checks on a runner squeezed by a
full worker pool. The same test passes locally. The helper now polls for
up to 60 s and that test's timeout is 90 s, matching 9cac30a's approach
for the demo core test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.5](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.4...glide-v0.4.0-rc.5) (2026-09-27)

### Fixed

* **ploeg:** give the harness the configured model name, prefix intact ([901a466](901a466121))

### Tests

* **vloer:** give the HTTP demo session room on a contended runner ([27f6f7d](27f6f7dd51))
VIK-1283
Agent-Trace-Id: ploeg-37b680c0296c
The agent-sandbox executor's qualification note still told operators to
qualify a RuntimeClass "with the privileged DinD sidecar", which predates
the daemonless agent plane. Describe the qualification as it actually is:
run the daemonless worker pod shape under the RuntimeClass until one Run
reaches a terminal outcome; the DinD sidecar belongs only to harnesses
that build inside a container.

Record in backlog #58 that pkg/sandboxlaunch now supersedes it, and split
its two open threads into their own items: #126 for SandboxWarmPool
support (a warm pod would claim a Run before any claim exists) and #127
for the kind CI qualification against a real controller, which needs the
kind-and-KEDA harness that backlog #89 has not yet built.

The experimental marker in values.yaml is intentionally left in place
until the homelab qualification passes.

VIK-573
Agent-Trace-Id: ploeg-98618a28c505
The status line cited backlog #91/#92, which are Golden fixtures and CI
hardening, not the KPI work; link the KPI set (VIK-1214) and the dashboard
(VIK-1215) instead. The merge settlement d63e7cb is merged and released, so
K5/K6 no longer describe it as pending and data row D4 is marked done.
Refresh the page's last_verified/verified_by metadata.

VIK-1289
Agent-Trace-Id: ploeg-7b153d323153
The agent-sandbox executor took one global executor.sandbox.runtimeClassName,
so every team and Role shared it. The owner runs one team on Kata and one on
gVisor, which the chart could not express.

Resolve the RuntimeClass through the same field-by-field chain harness: uses:
role sandbox.runtimeClassName > team sandbox.runtimeClassName > global
executor.sandbox.runtimeClassName > "" (render no runtimeClassName). The new
ploeg.runtimeClassName helper keeps that resolution in one place, and
sandbox.yaml sets the field only when the chain resolves one. values.schema.json
gains a sandboxOverride definition on executor.teams[] and plan[].roles[], so a
non-string value is rejected. values.yaml documents the Kata (bare metal) and
gVisor systrap (VMs without nested virt; file-I/O penalty on clone-heavy runs)
placement guidance.

Verification: a new golden executor-sandbox-runtimeclass renders three distinct
runtimeClassName values (kata global fallback, gvisor team override,
gvisor-systrap role override) and is registered in helm-golden.sh; the existing
executor-sandbox-team golden already covers the empty-global no-runtimeClassName
case.

Checks run here: the Ploeg verify script (gofmt) passed, as did
docs-configuration.py --check, docs-decisions.py --check and the offline docs
test scripts. helm and the Go module cache are absent in this sandbox, so
helm-golden.sh check and the Go gates are left to CI.

VIK-555
Agent-Trace-Id: ploeg-148042a86311
Co-authored-by: openhands <openhands@all-hands.dev>
The schema criterion needs its negative case, not just the positive render: a
non-string executor.teams[].sandbox.runtimeClassName must make helm refuse the
values. The reject fixture and its helm-golden.sh entry assert that
values.schema.json's sandboxOverride stays string-only.

VIK-555
Agent-Trace-Id: ploeg-148042a86311
Co-authored-by: openhands <openhands@all-hands.dev>
Backlog #127 gains its concrete blocker: the pull-request runner is a remote
Docker daemon with no shared filesystem, so kind can neither provision a node
nor load images, and a kind-capable runner is a homelab-cluster change. The CI
docs carry the same finding under "Cluster end-to-end tests" so the next person
does not land a job that can never pass.

VIK-573
Agent-Trace-Id: ploeg-ee113a5d0613
The #127 sentence linked #58 through a hand-written same-page fragment with
doubled hyphens. docs.py normalises repeated hyphens on both sides so it passed
that check, but mkdocs resolves the slug verbatim and the CI docs gate runs with
--strict, so the malformed anchor would warn and fail the build. Refer to #58 as
plain text instead; item 58 lives in section F, not the section the fragment
named.

VIK-573
Agent-Trace-Id: ploeg-ee113a5d0613
Reviewed-on: webgrip/glide#2
Reviewed-on: webgrip/glide#4
run_llm_accounts.observed_spend is NUMERIC(12,4), so Postgres rounds a
provisional spend half-up when RecordLLMObserved or RecordLLMBlocked store
it. ReconcileLLMAccountWithUsage then compared the unrounded spend-log float
against that rounded observation and refused spend < observed. Any Run whose
spend rounds up was stuck: for example logs 0.242263, observed stored 0.2423.
The settlement sweep retried every tick forever, run_budget_holds kept the
account at its full authorized value, and the spend was never charged.

Normalise spend to the column's own precision inside the settlement
transaction (SELECT round($1::numeric,4)::float8) and use the rounded value
for the undercut compare, the delta, reconciled_spend, usage.costUsd and the
audit detail. A real undercut is still refused with ErrLLMAccountState and
leaves the ledger unchanged; the tolerance is not widened and the guard stays.

Adds TestControllerSettlesBlockedAccountWhoseObservationRoundedUp (observed
= settled = 0.242263) and the store-level case for 0.0032997 -> 0.0033, and
notes the precision rule in the spend investigation how-to.

VIK-1295
Agent-Trace-Id: ploeg-d0901d375c78
VIK-1299
Agent-Trace-Id: ploeg-dad3121b28bb
Reviewed-on: webgrip/glide#6
Reviewed-on: webgrip/glide#5
## [glide-v0.4.0-rc.6](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.5...glide-v0.4.0-rc.6) (2026-09-28)

### Added

* **ploeg:** per-team and per-role sandbox RuntimeClass ([2393847](23938479f9))

### Fixed

* **ploeg:** settle blocked LLM accounts at run_llm_accounts' column precision ([a6e5854](a6e58545ee))

### Docs

* **vloer:** note current GAP-17 status in the gap register ([3e55d61](3e55d6159b))

### Tests

* **ploeg:** refuse a non-string sandbox RuntimeClass ([f2d73c8](f2d73c8f29))
Reviewed-on: webgrip/glide#7
Stuck work reaches the owner as a cited proposal from a lead Role; no agent
applies a decision. Adds an opt-in premise check, a writer rule for partly
infeasible Work Items, and a stuck headline fix. Grounded in VIK-573 / PR #3.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add reconciliation cases that refuse a spend whose NUMERIC(12,4)
rounded value is below the stored observation, and assert the failed
settlement leaves state, reconciled_spend, spent and reserved intact.

VIK-1301
Agent-Trace-Id: ploeg-d18be8894409
The lead stays proposal-only until 90% of at least 20 briefs are accepted
with no wrong decision, Glide's own accepted ADRs are the first decision
source with homelab-cluster ADRs in slice 2, the premise check is on by
default for bronze, budgets are US$0.10 per pre-flight Run and US$1.00
per brief with one brief per stuck Work Item, and Glide gets its own
Vikunja user so owner decisions on the tracker can be authenticated.

Refs: VIK-1316, VIK-1318, VIK-1320

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
agent-sandbox names the worker pod after the claim. A claim named after the
launcher pod made the controller find the launcher pod, refuse it as owned
by the Job, and never start the sandboxed worker. The claim is now
sbx-<launcher pod>, keeping the pod's unique tail within 63 characters.

Refs: VIK-1268

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.7](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.6...glide-v0.4.0-rc.7) (2026-09-28)

### Fixed

* **ploeg:** name the sandbox claim apart from the launcher pod ([a386f97](a386f97072))

### Docs

* **adr-0035:** state exactly what a Run's worker pod can reach on the network ([6ba68ca](6ba68caee2))
Helm writes OCI chart manifests without the optional mediaType field, so
the public chart copy raised KeyError and every release since rc.5 failed
its "Verify and publish all destinations" job. The upload now defaults to
the OCI image manifest type.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds proposed system ADR-0005 to ADR-0009 and two dated records: the
agency offering proposal and a comparison of pricing units.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Records the current state of Run credential isolation, the chart and
cluster steps to enable it, and when an egress gateway replaces the
in-worker proxy. ADR-0009 now requires credentials outside tenant pods.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
A claim the agent-sandbox controller cannot serve stayed Ready=False while
its launcher waited for Finished, holding the Team's only slot until the
shutdown deadline, hours later. The launcher now deletes a claim that is not
Ready within executor.sandbox.startTimeoutSeconds (default 600) and fails
with the controller's reason.

Refs: VIK-1268

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
verify cancels running gates after the first failure, which kills go test
before it stops its embedded Postgres. With fixed ports, each orphan then
failed every later run in every checkout on this machine. A free port per
package removes the collision and the PLOEG_TEST_PG_PORT_OFFSET workaround.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#3
Reviewed-on: webgrip/glide#8
Reviewed-on: webgrip/glide#9
## [glide-v0.4.0-rc.8](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.7...glide-v0.4.0-rc.8) (2026-09-28)

### Fixed

* **ploeg:** delete a sandbox claim that never becomes ready ([4d45655](4d45655a5d))

### Docs

* **ploeg:** correct sandbox executor qualification and split its remaining work ([e69bef1](e69bef167c)), references [#58](webgrip/glide#58) [#126](webgrip/glide#126) [#127](webgrip/glide#127) [#89](webgrip/glide#89)
* **ploeg:** drop an anchor link that strict mkdocs rejects ([e92557a](e92557a763)), references [#127](webgrip/glide#127) [#58](webgrip/glide#58) [#58](webgrip/glide#58)
* **ploeg:** propose ADR-0036, the escalation ladder for stuck Work Items ([fbfe04b](fbfe04b70a)), references [#3](webgrip/glide#3)
* **ploeg:** record the kind runner blocker for the sandbox e2e ([e628d02](e628d02c94)), references [#127](webgrip/glide#127)
* **ploeg:** record the owner's 2026-09-28 decisions in ADR-0036 ([1ca31e1](1ca31e1e1e))

### Tests

* **ploeg:** pin the LLM undercut guard at the column rounding boundary ([71b946a](71b946ae65))
* **ploeg:** start embedded Postgres on an OS-assigned port ([143a7d7](143a7d70cf))
Owner decision 2026-09-28: a team can opt into a registries network
profile; airgapped stays the default. The chart names the profile as a
pod label and proxy/CA environment; homelab-cluster enforces it with a
TLS-terminating Squid allowlist proxy (GET/HEAD only, per-host paths,
Go module-host allowlist) backed by Cilium toFQDNs on the proxy pod.
Amends ADR-0035's first Bad consequence and records the existing DNS
tunnelling hole in airgapped.

VIK-1332
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
TLS terminated for every allowlisted host; silver's builder gets
registries while its reviewer stays airgapped; pip wheels only; git
read-only clones (upload-pack) from an explicit allowlist with
receive-pack always refused; the Go module-host allowlist stays as a
path filter, while the Go egress hosts are only proxy.golang.org and
sum.golang.org.

VIK-1332
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Plan #1305 (a usage and evidence report on every agent pull request) as an
OpenSpec change for owner review: proposal, delta spec, design, ADR review
manifest and tasks. Planning only — no code, chart or test changes.

VIK-1306
Agent-Trace-Id: ploeg-76037b747e28
Renumbers the routing proposal from PR #11 to ADR-0038, since 0036 and 0037
are taken, and evaluates option G (the repository declares itself) against
A-F. Recommends B now, then G1 (registry derived from the homelab repo-config
model, with a readiness gate) and G3 (planner-proposed splits held for
approval); rejects G2 (component labels via Backstage) for routing on
today's catalog. Records the owner's 2026-09-28 answers as decisions.

VIK-1340

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
webgrip/glide has GitHub immutable releases on, which refuse assets once a
release is published. The mirror published first and uploaded afterwards,
so rc.8's release has no assets and every upload returned 422. The mirror
now creates a draft (found again by listing, since tag lookups skip drafts),
uploads, then publishes. A published release that is missing an asset fails
with that reason, and HTTP errors carry the response's first 300 bytes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The checks job failed intermittently on a loaded shared runner: fixed
wall-clock waits (a 12 s startup window, fixed post-restart sleeps and
absolute poll deadlines) expired before the server or durable state was
ready, while the assertions themselves were sound.

Add test/timeframes.ts, a single scale-aware helper that reads
VLOER_TEST_TIMEOUT_SCALE (default 1) and exposes scaledTimeout,
deadlineAfter, testTimeout, settle and waitFor. Every fixed upper bound in
Vloer's tests now goes through it: startup windows and poll deadlines are
readiness-based (poll until the condition holds, fail only past the bound),
test-level timeouts scale, and settle() keeps its original meaning at
scale 1.

No assertion is removed or weakened; timing assertions still exercise the
real behaviour. The verify action sets VLOER_TEST_TIMEOUT_SCALE=2 so CI
keeps headroom while local runs stay strict.

VIK-1353
Agent-Trace-Id: ploeg-c6d6c25c4e01
Name the source of the report's evidence: the writing Run's unstructured
findings/summary prose (ADR-0035), parsed by parseEvidence, with an explicit
"not recorded" state when no verification section exists — so the renderer's
inputs can produce the spec's required output and the no-migration claim holds.

Make Comments page to exhaustion in both forge providers so the marker is found
wherever it sits; this removes the tolerated duplicate the spec forbids. Correct
D3's SPI rationale (PullRequestState also has a caller), name the accepted
openspec validate gate in tasks.md, and align task 3.4/4.1/4.4 with the design.

VIK-1306
Agent-Trace-Id: ploeg-e4e2fbf9ec6c
Direction confirmed as B with strict routing first, then G1 and G3, no G2.
G1 selects targets with a separate agent-target flag on the repo-config
entry, which also carries the agent base branch. An unready target is
refused at config load and again at claim, with a recorded reason.

VIK-1340

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
executor.forgejo.url fed both the workers' FORGE_URL and ploegd's
PLOEG_FORGEJO_URL. Workers should use the in-cluster service, but ploegd
matches Vloer's https repository URLs against its forge URL, so homelab
put the public URL back with a Flux post-render patch. The new optional
executor.forgejo.publicUrl (and executor.gitlab.publicUrl) sets ploegd's
URL; empty falls back to url, so existing values keep their meaning.

VIK-1298

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.9](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.8...glide-v0.4.0-rc.9) (2026-09-28)

### Added

* **ploeg:** give ploegd its own forge URL with executor.forgejo.publicUrl ([3a211aa](3a211aa930))
Vloer's and Ploeg's publishers share one immutable GitHub release. On rc.9
Vloer's job, which runs first, published it, so Ploeg's job could no longer
attach release-artifacts-ploeg.json. Only the last publisher (Ploeg, which
needs vloer-release-distribute) now takes the release out of draft; a test
pins that ordering in the workflow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#12
Reviewed-on: webgrip/glide#13
Reviewed-on: webgrip/glide#14
# Conflicts:
#	apps/ploeg/docs/adrs/README.md
A launcher whose claim never became Ready deleted it and exited, but the
Work Item stayed queued with no Run record, so nothing said why and the
infra retry budget never applied. The launcher now claims one Run for its
Team and Role with the worker bootstrap credential and reports it failed
with infra_node and the controller's Ready reason. The chart gives the
launcher the API URL, Team, Role, worker ID and bootstrap token a worker
gets.

Refs: VIK-1268

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.10](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.9...glide-v0.4.0-rc.10) (2026-09-28)

### Fixed

* **ploeg:** fail a Run as infra_node when its sandbox never starts ([afadc5f](afadc5f928))

### Docs

* **adr-0038:** accept with the owner's 2026-09-28 routing decisions ([499da03](499da031d3))
* **adr-0038:** propose repo-label hints over a derived target registry ([8dc711b](8dc711bccc)), references [#11](webgrip/glide#11)
* **ploeg:** address review of the run-usage report plan ([86bf6c1](86bf6c164c))
* **ploeg:** plan the PR run-usage report as an OpenSpec change ([af4251e](af4251ec80)), references [#1305](webgrip/glide#1305)

### Tests

* **vloer:** make time-bound API tests readiness-based and load-tolerant ([aff26b7](aff26b7a53))
Marking a Vikunja task done left its Work Item queued: ploegd dropped every
task.updated event. The Vikunja adapter now reports a done task as a close,
and ploegd withdraws the item (withdrawn_closed) when none of its Runs has
started or been authorized to spend. Started work keeps running; stopping
it stays an unassignment or an operator cancel.

VIK-1349

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.11](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.10...glide-v0.4.0-rc.11) (2026-09-28)

### Added

* **ploeg:** withdraw a Work Item whose ticket closes before work starts ([5ab862e](5ab862ec25))
The OpenHands CLI (MIT) serves ACP with `openhands acp`; 1.16.0, the
version agent-runner already ships, reads LLM_API_KEY, LLM_BASE_URL and
LLM_MODEL under --override-with-envs. The profile passes the model with
LiteLLM's proxy prefix, writes no config file, refuses configJson and sets
no approval flag, so the adapter answers each permission request from the
Run's mode. ACP gives structured stop reasons and tool calls instead of log
tailing, and keeps OpenHands swappable behind the same seam as the other
agents. The live conformance suite gains acp-openhands; stop reasons,
shutdown and outbound calls are documented as not yet checked.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The landing page's "Ready for review" card counted every completed session,
including ones already accepted or rejected, while the "For review" filter
excluded them. The card now uses the filter's rule: completed and not yet
reviewed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The operator runs list took models only from agent_runs.usage and
reported settledUsd only after settlement, so a running Run showed no
model and no spend. run_llm_accounts already holds the reserved models
and the latest gateway spend observed for a Run's account, and the
runs query already LEFT JOINs it.

Add observedUsd (from run_llm_accounts.observed_spend) and
reservedModels (from run_llm_accounts.models, same string-filtering
rule as usage.models) to each run row in GET /api/v1/operator/runs.
Both fields are optional in the JSON schema; a Run without a
run_llm_accounts row gets observedUsd null and reservedModels [].

VIK-1387
Agent-Trace-Id: ploeg-0976dbe7d067
Inside a Run opens up one Run: KEDA ScaledJob, sandbox launcher and
SandboxClaim under Kata, the worker's steps, harness adapters, credential
isolation, network reach and what production runs on 2026-09-29.

Journeys follows five end-to-end paths: ticket to merged pull request,
merge to production, starting work from Vloer, stopping work, and
infrastructure failures.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vloer opened on Sessions; the owner had to walk four Ploeg tabs and one
team lane at a time to see what needed them. Now is the start view: for
every team the user may read it shows waiting Work Items, running Runs
and recently finished Runs, and stays useful when one Ploeg call fails.

- PloegClient.now(user) reuses items/proposed/runs with per-group error
  capture; GET /api/ploeg/now returns {waiting, running, recent, errors,
  fetchedAt} and keeps the existing 403 ploeg_scope for scoped callers.
- runRow parses the optional observedUsd/reservedModels fields; missing
  model or spend renders as "not reported", never 0.
- waiting rows carry team, state, title, age, cost (2 decimals) and
  tracker/pull-request/Grafana links; awaiting_review detail is fetched
  for at most 10 items, same cap as the Proposed lane.
- public/now.js renders the three groups with inline retry per group;
  registered in the static asset map, route/nav and j/k keyboard
  handling added in app.js, styles in styles.css, demo data extended.
- Tests cover the scope 403, per-group failure and "not reported", plus
  a browser case asserting no horizontal scroll on #now at 390 px.

VIK-1389
Agent-Trace-Id: ploeg-b73fda03b059
## [glide-v0.4.0-rc.12](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.11...glide-v0.4.0-rc.12) (2026-09-29)

### Added

* **ploeg:** add an openhands ACP profile ([fc6eef9](fc6eef9574))

### Fixed

* **vloer:** stop counting reviewed sessions as ready for review ([6478cb3](6478cb366f))
Adds the researched pricing and go-to-market strategy with its five
evidence notes, marks the pricing units record superseded, and lists
the revised numbers in ADR-0006 as open questions for the owner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
The owner accepted ADR-0005 to ADR-0009 and decided segment, editions,
fees, ticket numbers, approval rules, hosting and model routing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Moves the charge point to the agency reviewer's acceptance, records
disclosure and liability rules, and allows the first pilot agency on a
dedicated cluster before shared tenancy exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
The checks job failed in runs 151, 152 and 155: two extension client
tests that drive the real demo server took 31-39 s against a fixed 30 s
timeout. The same tests take 14-25 s on a quiet runner. aff26b7 moved
Vloer's own tests onto test/timeframes.ts, which scales every bound by
VLOER_TEST_TIMEOUT_SCALE (2 in CI), but the extension tests kept their
fixed timeouts.

The extension client tests now use testTimeout and deadlineAfter from the
same helper. Local runs stay strict at scale 1. No assertion changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Renovate only tracks a commit-SHA pin that carries a version comment.
Sixteen of the nineteen actions/checkout pins and all four mise-action
pins had none, so they were invisible: an approved checkout update would
have moved three lines and left sixteen behind. setup-go was the one
action on a mutable tag; it is now pinned to v5.6.0 like the rest.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- engines ranges (engines.vscode, engines.node) are no longer pinned or
  bumped; they are compatibility floors, not dependencies.
- @types/vscode stays below 1.100 while engines.vscode is ^1.99.0,
  because vsce refuses to package a newer one.
- The charts' own ploegd and de-vloer image tags are left to the release
  train instead of being digest-pinned.
- The org preset reference is tracked through forgejo-tags, since the
  renovate-config manager skips local> presets, and bumped to v1.10.0.
- The webgrip/* reusable-workflow manager and its two rules are removed:
  the org forgejo overlay now owns those pins, and both matched the same
  lines twice.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The checks job timed out two extension client tests at a fixed 30 s.
c817ca5 on development scales those timeouts with VLOER_TEST_TIMEOUT_SCALE.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The checks job timed out two extension client tests at a fixed 30 s.
c817ca5 on development scales those timeouts with VLOER_TEST_TIMEOUT_SCALE.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Work Item 138 (VIK-1305) ran three hours on bronze and ended needs_human
with no branch and no pull request. The builder stopped to ask a question
and was labelled no_change_needed; the reviewer's request_changes was
printed instead of written to the outcome file and lost. Twelve Runs that
day died at the 45m idle timeout while making model calls.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Removing the repository rule assumed the org forgejo overlay sets the
CI-only commit shape. It does, but the overlay is runner-level config,
applied before the preset's per-update-type rules, so the first run
titled the workflow-pin PRs fix(actions) and feat(actions).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
gopkg.in/yaml.v3 is archived; the YAML organisation continues it as
go.yaml.in/yaml/v3 with the same API, and only that module receives
fixes. Renovate's dashboard flagged the old module as abandoned once
gomod was scanned. go mod tidy also drops rogpeppe/go-internal, which
only the old module's test tree required.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Helm 4.2.4 prints a blank line before every `---` that 4.2.3 does not,
so Renovate's helm bumps (4.2.4, 4.3.0) failed the byte-for-byte golden
check without any template change; the script already warned about the
same helm 3/4 disagreement. Renders now drop a blank line only when it
directly precedes a separator, and the goldens are regenerated in that
form (32 blank lines removed, nothing else).

Checked on helm 4.2.3, 4.2.4 and 4.3.0: all pass; a template edit and a
blank line inside a document still fail the check on 4.2.3 and 4.2.4.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Twelve specs still carried the TBD Purpose that `openspec archive`
writes. openspec 1.13 reports that placeholder, and `validate --strict`
fails on it, which blocked Renovate's openspec 1.6.0 -> 1.13.2 update.
Each Purpose now summarises the spec's own requirements and names the
change it was archived from; validation passes strictly on both 1.6.0
and 1.13.2.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An image bump in Ploeg's chart values changes rendered manifests, so the
dind digest pin, the agent-runner and the docker tag PRs all failed the
golden check. A postUpgradeTask now runs helm-golden.sh update in the
branch, so each PR carries its own render diff for review. The runner
allows the command since homelab-cluster c533d0a8; the task was verified
inside the runner's Renovate image.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
## [glide-v0.4.0-rc.13](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.12...glide-v0.4.0-rc.13) (2026-09-29)

### Fixed

* **ploeg:** parse YAML with the maintained go.yaml.in/yaml/v3 ([bdea64d](bdea64dd64))

### Docs

* **ploeg:** record the Work Item 138 incident and its non-golden paths ([ce7d726](ce7d72628c))
* **ploeg:** state what each archived OpenSpec capability is for ([d5975f9](d5975f939a))

### Tests

* **ploeg:** ignore helm's blank line before document separators in chart goldens ([6855e14](6855e14060))
* **vloer:** scale the VS Code extension test timeouts on a loaded runner ([c817ca5](c817ca5f88))
Adds ADR-0010 with a diff budget per size, self-explaining pull
requests, attention paths and a CI check that asks the reviewer
questions, plus the matching backlog tickets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ng2mWVgqdp22rHJiL18Pn
v1.11.0 stops digest-pinning action version inputs, which had silently
dropped every mise-action `version:` update (mise 2026.6.14 while
2026.9.17 shipped), and lands preset bumps as chore(renovate). The org
forgejo overlay now tracks local> preset pins for every repository, so
Glide's own tracker would extract the same pins twice. Both
repository-level copies are removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
apps/ploeg/mise.toml has set `lockfile = true` without a lock, so the
setting did nothing and `node = "24"` resolved to whatever was newest.
The lock pins helm 4.2.3, node 24.21.0, uv 0.12.12 and openspec 1.6.0
with checksums for linux-x64, linux-arm64 and macos-arm64. It was
generated with the mise in the Renovate runner image (2026.7.7), which
keeps it current in its branches; mise 2026.9.17 reads it unchanged.
mise 2026.6.14 and older only rewrite the header URL.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
OpenHands CLI 1.16.0 answers session/new with auth_required until
$OPENHANDS_PERSISTENCE_DIR/agent_settings.json exists: its acp entrypoint
never forwards --override-with-envs, and the only advertised auth method
is an OpenHands Cloud device-flow login. The openhands profile now writes
a keyless agent_settings.json (litellm_proxy/<model>, base_url) into a
trace-scoped scratch directory and passes the Run's key or placeholder as
LITELLM_PROXY_API_KEY. Verified against the real 1.16.0 CLI and a stub
gateway through the adapter: session/new succeeds, a tool call is
permitted and the turn ends with end_turn.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AGENTS.md (and CLAUDE.md through its symlink) now names ADR-0005 as the
product direction and says ADRs 0005-0010 are not implemented yet.
llms.txt no longer calls Glide internal-only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.14](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.13...glide-v0.4.0-rc.14) (2026-09-29)

### Fixed

* **ploeg:** write OpenHands agent settings so its ACP session starts ([24e3a05](24e3a054f9))

### Internal

* **ploeg:** commit the mise.lock that lockfile = true expects ([caa2a3a](caa2a3a737))
Adds Offering and Billing contexts with Agency, Tenant, Client, Client
Portal, Client Profile, Size, Quote, Credit, Delivery Fee, Token Charge,
Markup and Delivery, plus Acceptance, Preview Environment, Refinement,
Attention Path and Diff Limit, and rules R13-R16.

Ticket leaves the retired terms and becomes the customer-facing word for
a Work Item with a Quote, allowed only in the Client Portal and sales
material. Acceptance is the Agency's decision and charges the Delivery
Fee; a Client's preview feedback is input to it. Every spending limit is
a qualified Budget. A Client's Definition of Ready adds to Ready. A
self-hosting business is still an Agency. The Delivery context becomes
Release so Delivery can mean an accepted Work Item. The two resolved
ambiguities are removed; their decisions live in ADR-0002 and R8.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The 45 items are in Vikunja: 34 new tickets on the Glide board and
Homelab Roadmap (VIK-1468 to VIK-1501, labels theme/agency-offering and
phase/1-self-hosted or phase/2-hosted), and 11 merged into the tickets
that already covered them. The tracker is the source of truth, and
running import.sh again would duplicate every ticket.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The loop in ADR-0005 ends at the agency's Acceptance; the client's
preview feedback is input to it. Spending limits are named as Budgets,
"Customer (tenant)" becomes Agency, "operator" becomes the agency, and
"diff budget" becomes Diff Limit. ADR-0006 keeps "ticket" as the
customer-facing word the glossary allows and says so once. Each ADR
records the wording change in its history; no decision changed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ploeg publishes one comment per agent pull request that answers what ran,
what it cost and whether it was checked, and keeps it current in place.

- provider SPI gains Comment, Comments and EditComment; Forgejo and GitLab
  implement them following pagination to exhaustion so a marker on any page
  is found and edited rather than duplicated.
- store.ShiftUsage reads a Shift's ledger and finished Runs (usage, account
  state, duration) in one query; UnsettledLLMAccount carries its ShiftID so
  a late settlement refreshes the right report.
- pkg/shiftengine/usage.go is the pure renderer and evidence parser; the
  publishing path finds the marked comment and edits or creates it, and the
  settlement sweep refreshes it after a successful Settle.
- The report is best-effort: a failed read, list or write is logged and
  changes no Outcome, close reason or Work Item state.
- Chart env adds PLOEG_USAGE_REPORT (default on) and the two link bases
  PLOEG_REPORT_GRAFANA_URL and PLOEG_REPORT_VLOER_URL; docs and the config
  reference are updated.

VIK-1305
Agent-Trace-Id: ploeg-7844e1aec06f
A current explanation page for the agency offering, so that ADRs 0005
to 0010 are not the only place its rules live: the self-hosted first
phase, the hosted second phase, the loop from Client request to
Acceptance, where Glide's responsibility ends, the two-part price and
Tenant isolation. Numbers stay in ADR-0006. The page is linked from the
nav, the start page, llms.txt and AGENTS.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Request is a Client's ask and its thread; Refinement turns one Request
into one or more Work Items, each with its own Quote. Dispute is a
billing disagreement between Glide and an Agency only; a Client
objecting to a preview is feedback the Agency decides. Reversal refunds
a Delivery Fee after a defect-caused revert within 14 days. Markup Tier
is the only use of "tier"; a Size maps to a Team. Edition names the
plan an Agency is on, and the middle one is renamed from Agency to
Studio so the two never share a sentence ("Agency Edition" is retired).

How a Request that arrives as a tracker task relates to Ploeg's Tracker
Item mirroring stays open as an ambiguity with a recommendation. ADRs
0005-0007 and the Who Glide is for page use the new words.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ADR-0038: the routing config gains a `targets:` registry, and a project a
`default:` key and an `allow:` list. A `repo/<key>` label read from
FetchItem selects an allowed target; without one the board default applies.
Two different repo labels, an unregistered repo label, a target the board
does not allow, no label on a hint-required board and unreadable labels on
a label-routed board are refused with an audit row and a ticket comment,
with no fallback. A readiness gate refuses archived, mirrored and
AGENTS.md-less targets at load, at ingest and again at claim. The matched
hint is pinned on the Work Item (migration 0021). A config without
`targets:` routes exactly as before.

VIK-1340

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds the how-to for ADR-0038's target registry, links it from the runbook
index, the nav and the assignment how-to, and records on the ADR what is
implemented and which details the implementation settled.

VIK-1340

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Agents API (public beta 2026-09-10) hosts OpenAI's Codex harness and
keeps inference on OpenAI's side. It has no settable session budget and
US-only residency without ZDR, so Runs stay off it for now. Glide reaches
OpenAI's harness through Codex via codex-acp and meets the Agents API
over MCP. ADR-0032 gains a trigger for a settable session budget.

VIK-1514

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Record an eight-agent sweep of MCP 2026-07-28 against Glide's seams:
the dossier, the raw evidence reports, and a proposed system ADR for a
separate read-first ploeg-mcp server on the operator API, where
proposals never dispatch and approval needs a person.

Re-confirm Ploeg ADR-0007 with a dated note, point the protocol ledger's
MCP entry at the new record, and regenerate the decision register and
landscape. Tickets: epic VIK-1512, decision VIK-1502.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Go cache key includes the Go version, so a Renovate Go bump (PRs 28
and 32) restored nothing. go vet then spent about 250 s downloading
modules, and the integration gate's managed check was killed at its 240 s
limit before printing anything, which surfaced as an empty "managed
failed:". The restore keys now fall back to any earlier Go version's
cache, whose modules are version-independent.

The integration script now says when it killed a check for running past
240 s.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The child wrote its pid with writeFileSync, which creates the file before
writing to it, and the test polled only for the file to exist. On a busy
runner it could read an empty file: Number('') is 0, and kill(0, 0)
signals the test's own process group, so the "child is gone" loop ran its
full window and the ESRCH assertion failed after about 11 s (PR 16, run
161). The child now writes to a temporary name and renames it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The goldens placed PLOEG_USAGE_REPORT before PLOEG_SWEEP_INTERVAL, but the
chart renders it after PLOEG_TEAM_MAP, so helm-golden.sh check failed.
Regenerated with `sh scripts/helm-golden.sh update`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The owner accepted the separate ploeg-mcp server on 2026-09-30 and
answered its open questions: remote OAuth is planned in the self-hosted
phase, proposals live in Ploeg only, only the owner holds the steer
toolset, and a client without elicitation is sent to Vloer to approve.

Refs: VIK-1502

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A six-agent sweep of tool design, the go-sdk v1.8.0 source, twelve
clients, security, testing and operations, and advanced patterns. The
guide binds the implementation: one endpoint for both protocol eras,
full answers in both result channels, a portable schema subset, 30 s
tool calls, and approvals gated by grant, sealed single-use state and
forced prompts. It records Authentik's gaps for the OAuth phase as an
open owner decision, and moves the ADR's conformance check to a
fixture build because the production binary cannot pass it.

Refs: VIK-1512

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The owner chose the estate's Authentik over Keycloak and over an
authorization server in front of Authentik. ploeg-mcp checks aud
against the pre-registered client ids because Authentik ignores the
resource parameter.

Refs: VIK-1574

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The server snapshots five lanes, but the tree rendered four and dropped
awaiting_review. Items waiting on a human merge were only reachable through
All work, which pages oldest first at 25, so a team with 25 older items hid
its open pull requests behind "More work in the web workbench".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GET /api/v1/operator/teams now lists, per team, the tracker usernames that
route work to it (config assignees plus PLOEG_TEAM_MAP), so an operator
client can offer "hand to team X" without duplicating Ploeg's routing map.

GET /api/v1/operator/work-items accepts provider + externalId (both or
neither) to find the Work Items for one tracker task across the caller's
teams, in any state unless state narrows it. Both changes are additive
under schemaVersion 1.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A person reading a Vikunja task can now see whether Ploeg holds it and hand
it to a team in one step. The workbench assigns the team's tracker user
(the same webhook intake Ploeg already uses) and comments who handed it
over; take-back removes the assignee while the Work Item is still queued.
Only one team holds a task at a time, so live work is never re-routed.

- GET  /api/task-sources/:sid/tasks/:tid/ploeg: teams, assignment, Work
  Items with state, attempts, branch, spend and pull request.
- POST/DELETE /api/task-sources/:sid/tasks/:tid/handoff: operator-only,
  revision-checked, idempotent; degrades with a clear reason against an
  older Ploeg or a read-only tracker token.
- GET /api/tasks/lookup: which source holds a Vikunja task, so a Ploeg
  Work Item can open its task view.
- Task snapshots carry labels, assignees, priority, due date, identifier
  and Vikunja HTML converted to Markdown; one oversized description no
  longer fails a whole list page.

ADR 0024 records the decision as proposed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Selecting a linked task used to open its raw tracker HTML as plain text.
It now opens a task view: the description rendered as inert Markdown,
labels, assignees, priority and due date, and a Glide card that says
whether Ploeg holds the task, in which state, and what happens next, with
its pull request. Pick a team and "Hand to <team>" (confirmed, last team
remembered), or "Take back" while it is still queued. Ploeg tree rows
open the same view when the workbench knows the tracker source.

The extension also stops flapping to "Workbench unavailable": a failed
refresh keeps the last state with a reconnecting notice and goes offline
only after three failures or an expired sign-in; reads retry once after
an unreachable workbench or a gateway 502/503/504 (mutations never), and
a gateway error page is named as such. The session panel now re-renders
after a reconnect, so its controls re-enable when the session itself did
not change (the browser check failed on this before).

Smaller fixes: "1 role", paused teams marked, the Ploeg snapshot refreshes
every 30 s while visible and shows its time, repository names instead of
ids, readable tooltips, Markdown task-list checkboxes, HTTPS links in
rendered Markdown open through the host.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GET /api/v1/operator/teams lists each team's pinnedScopes: the tracker
container ids configured with an id and a team, whose items run as that
team whoever is assigned. A client that assigns tracker users needs this
to know which team will actually receive the work and which item an
unassignment would withdraw. Additive under schemaVersion 1.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ploeg files one Work Item per tracker task, and a board pin can put it in
a team other than the assignee's; unassigning then withdraws that item even
mid-run. Take-back checked only the named team within the caller's scope,
so it could withdraw started work.

- Take-back and hand-off read the task's Work Items across every team the
  Ploeg consumer sees; take-back is refused once any is started, hand-off
  while any is live or another team's user is assigned. Hidden teams are
  not named.
- Both fail closed (503 handoff_unverified) when Ploeg cannot answer.
- A board Ploeg pins to a team offers only that team.
- The task detail route accepts ?truncate=1 so the task view can open a
  task with an oversized description; import still refuses it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- A reload requested while a poll is in flight is queued instead of
  dropped, and a poll that started before a hand-off or take-back no
  longer overwrites its result.
- Fast polling stops 90 seconds after a hand-off instead of lasting while
  a task waits for Ploeg.
- The view opens oversized tasks shortened; a supervised session re-reads
  the full task first.
- Take back is offered only for queued work, matching the server.
- Markdown backslash escapes render as the characters they protect, in
  the view and in tooltips.
- Reads retry only when the workbench did not answer or its gateway
  answered with a non-API error page, not on the workbench's own errors.
- Task ids from command arguments are validated before building a view.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
public/app.js held every screen, handler and helper in one 934-line
module, so two people could not redesign two screens without editing the
same file. It is now the entry only: it boots, routes each hash to a view
and dispatches the delegated click, input, change, submit and keydown
events through tables that views fill.

- public/core/: shared state and disconnect, the API client with a
  registered 401 handler, DOM helpers (renderHtml keeps focus, selection
  and evidence scroll), formatting, icons, Markdown, lookups,
  observability links, navigation hooks and the view registry.
- public/views/: one module per screen area (now, sessions, session,
  compare, tasks, ploeg, account, system, login, dialogs), each exporting
  a descriptor with match/enter/load/render and its actions, forms,
  inputs, changes and key bindings. createRegistry refuses a second owner
  for an action, form or field selector.
- public/shell.js: sidebar, top bar, page heading and footer, and the
  connection indicator the session stream updates.
- src/http.ts serves /core, /views and /styles modules matching
  ^/(core|views|styles)/[a-z0-9][a-z0-9-]*\.(js|css)$ and /shell.js;
  a missing nested module answers 404.

Rendered markup, texts, action and form names, routes, timers, the event
stream, focus and keyboard handling are unchanged: 100 normalised
snapshots of #app, the dialogs, form values, focus, toast and live region
across the demo and live flows match the previous code exactly. The
compare view and dialog that 24071ad removed stay missing; a test pins
that. The browser check now asserts the #now hash after the Now heading
renders, since reading it right after load raced the bootstrap requests
on the previous code too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The browser check is now a runner that starts the demo and live servers
once and runs one flow module per area from scripts/browser/ (now, tasks,
sessions, shell, settings, feeds, work, login). Each flow exports
run({ page, app, live, password, assert, screenshot }), so parallel work on
different screens edits different files. Assertions are unchanged; the
Sessions heading check moved from before the Tasks steps to the start of
the sessions flow.

The Now page tests from PR #16 move unchanged from ploeg-view.test.mjs to
now-view.test.mjs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each waiting row on /api/ploeg/now now carries what the Ploeg list
payload already had: provider, externalId, priority, attempts,
infraFailures, target (null when the repository is not routed), the
latest Shift's closeReason and a latestShift summary (round,
closeReason, budgetUsd, spentUsd, reservedUsd, closedAt). Proposed rows
keep their provenance (sourceWorkItemId, sourceTitle, createdKind,
ready). The existing fields and the row order are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
core/format.js is the one formatter: US dollars in nl-NL with two decimals
("US$ 1.234,50"), "< US$ 0,01" for a positive amount under a cent with the
exact value in a title, and "Not reported" for an unknown amount, never
zero. Dates read 30-09-2026 21:30 in the browser time zone, relative time is
compact English ("5 min ago", "in 5 min") and turns absolute after seven
days, durations read "1 h 05 min", and plural() counts nouns. A 'browser'
locale preference is available. The earlier money, clock and ago names
delegate to the new formatters, so every view that still calls them now
shows nl-NL amounts and times.

core/states.js holds one vocabulary with a label, a tone and a glyph for
Work Item states, Run states, outcomes, agent verdicts, failure reasons and
session statuses. Done is never "Merged" and an agent verdict never reads as
human review. Session labels now come from it, which fixes two labels: a
completed session without a review reads "Ready for your review", a failed
one "Failed", and the session list shows "Accepted" or "Rejected" once a
review is recorded.

core/reasons.js derives why a Work Item waits on a person from its close
reason, with the corrections from the research critique: plan_exhausted in
needs_human names both causes, the prefixed "budget exhausted", "run stuck"
and "plan removed" reasons are handled, a writer killed by the cluster is
not the Work Item's fault, and an unresolved repository is a secondary
"Not routed" warning. The detail reason adds Ploeg's own needs-human
sentence and the stuck or failing Run. Every reason ends with re-assigning
the task in its tracker, the only way to start again today.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vikunja sends task descriptions as HTML, which the browser showed as
escaped tags. Ploeg Work Items (detail, lanes, proposed) and the task
preview now also carry descriptionMarkdown: Vikunja HTML converted by
src/markdown.ts (copied unchanged from feat/vloer-task-handoff:
DOM-free, input, output and depth capped, no scripts, styles, frames
or non-http(s) links), and any other description copied as it is.

The description field, task revisions, imports, bindings and agent
prompts are unchanged; the display copy is added only to responses.
Known secrets are also redacted in their Markdown-escaped form.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
POST /api/ploeg/work-items/:id/cancel now returns what Ploeg reported:
withdrawn, shiftId, cancelledRuns, stoppedRuns, keysBlocked and any
message, next to the existing workItemId, team, state and demo. A field
an older Ploeg omits, or sends malformed, is null (unknown), never zero
or false. keysBlocked false means Ploeg could not yet confirm the model
key block and its sweep retries it.

The demo no longer refuses with 409 ploeg_demo: it answers 200 with an
explicit demo result (withdrawn false, nothing stopped, state unchanged)
and a message saying nothing was cancelled. A 409 on cancel now says a
workbench session drives the Work Item and must be cancelled instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Static files were read from disk on every request and sent in full,
uncompressed, with no validator. src/static.ts now keeps each file in
memory keyed by path, mtime and size, sends a strong ETag (a SHA-256
content hash), answers a matching If-None-Match with 304, and gzips
text/css, text/javascript, text/html, image/svg+xml and
application/manifest+json when Accept-Encoding allows it. The gzip
body is computed once per file version and carries its own ETag;
Vary: Accept-Encoding is set on those types. Cache-Control stays
no-cache, so browsers revalidate every load, and the CSP and other
security headers are on every response, 304 included.

The shipped JavaScript, CSS and HTML drop from 264 KiB to 78 KiB on
the wire. The served paths and the assets map are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The sidebar is now the one navigation: Now, Work, Proposed, Runs, Activity,
Insights, Tasks, Sessions (shown in demo mode, with shared execution, or
when sessions exist) and Settings. The Ploeg area's second row of tabs is
gone.

views/ploeg.js is split, code moved and behaviour kept, into work.js
(#work, #work/<id>), proposed.js (#proposed), runs.js (#runs), activity.js
(#activity) and insights.js (#insights), with the shared Refresh and Try
again action and Team loading in ploeg-common.js. Linked accounts and
Environment move to #settings/accounts and #settings/environment, with a
Settings sub-navigation.

core/route.js parses #path?query with URLSearchParams and builds hashes.
Filters live in the hash, so reload, back and forward and shared links
work: #work?lane=&team=, #runs?team=&state=&outcome=, #activity?team=&kind=
and #insights?window=. The router redirects every old hash with
history.replaceState: #ploeg to #insights, #ploeg/<id> to #work/<id>,
#ploeg/lane/<lane> to #work?lane=<lane>, the other Ploeg tabs to their
pages, #account and #system to Settings, #compare/* to #sessions and an
empty hash to #now. Links inside the app point at the new routes.

After a route change the page heading (#page-title, tabindex -1) takes
focus and its text is announced in #announcement, and document.title reads
"<Page> · De Vloer".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The illustrative Ploeg records now cover what the redesigned screens
show, and stay labelled as illustrative with no spend:

- needs_human items for every real close reason: plan_exhausted with
  an unrouted target, fix_round_cap_reached, "budget exhausted: pool
  3.00, spent 2.96, reserved 0.10", "run stuck: builder round 2", "run
  stuck: reviewer round 2" and writing_run_killed_repeatedly, each with
  Ploeg's own work_item.needs_human sentence and the matching Runs
  (stuckReason, failureReason, ten infrastructure kills);
- Vikunja-style HTML briefs (paragraphs, lists, bold, https links);
- stale, done (merged, and a rejected proposal) and withdrawn items;
- a Round ladder with request_changes and approve verdicts;
- needs-you work in both teams, delivery and research;
- a per-item audit timeline (queued, round opened, claimed,
  checkpoint, outcome, Shift closed, state change) that is exactly
  that item's slice of the activity feed;
- an item waiting out an infrastructure backoff (nextEligibleAt).

Timestamps are relative to server start instead of 2026-09-10, so
ages read "25 min ago" rather than "20 d ago". Item ids 101 to 107 and
their titles are unchanged; Run, Shift and event ids are renumbered
in creation order. Shift pools are 3.00 while spent, reserved,
authorized, observed and settled amounts stay 0 or null.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
core/prefs.js stores theme, density, single-key shortcuts, live updates,
the number and date format, the last visit and the last Work team as one
JSON object in localStorage, validates every value and keeps working in
memory when storage is blocked or throws. core/theme.js is a classic
script loaded blocking in <head> that applies data-theme and data-density
before first paint, so a chosen dark theme never flashes light.
index.html now declares color-scheme and a theme colour per scheme.

core/live.js is the one scheduler for live updates: one timer, jobs run
only while the tab is visible, someone is signed in, no dialog is open,
live updates are on and their view is current (or the job is global), with
exponential backoff up to five minutes after a failure. Activity's 15 second
poll now runs through it, and each view records its successful loads so the
status strip can say when data was last updated.

core/keys.js holds the shortcut table: ? shows it in the new #shortcuts
dialog, g then n, w, p, r, a, i, t or s goes to a page, / and Ctrl or ⌘ K
open the palette. Character-only shortcuts, including the existing n and
j/k, obey the single-key preference (WCAG 2.1.4), and n now works only on
the Sessions pages. No single key approves, rejects or cancels anything.

New pages: Preferences (#settings/preferences) for theme, density,
single-key shortcuts, live updates and the format, and a command palette
placeholder (#palette, "Coming soon") that the palette work replaces.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
styles.css now declares the layer order legacy, tokens, base, components,
shell, views and imports one file per layer. The old rules move unchanged
into styles/legacy.css, so every new layer wins over them regardless of
selector specificity. The Archivo @font-face rules stay in styles.css for
the licence check. The token, base, component, shell and per-view files
start empty so each owner can fill its own file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
tokens.css carries the design-system roles as light-dark() pairs over the
unchanged brand primitives. The theme follows the system; data-theme on
<html> pins light or dark, and any element can opt into a scheme with
color-scheme (the sidebar stays dark this way). data-density="compact"
switches the density tokens, and reduced motion and coarse pointers adjust
the motion and control tokens.

base.css adds the element foundation: body typography at 13 px, the
heading scale, class-less links, code, forms including role="switch"
checkboxes, focus rings, selection, thin scrollbars, tables, dialogs,
details, meter and progress, reduced motion, print, and the utilities.
Element rules avoid properties the legacy stylesheet sets per context, so
the legacy screens keep their spacing.

Until a screen is rebuilt it stays light: legacy.css pins color-scheme to
light while legacy markup is on the page, because its hard-coded colours
do not follow the theme. The guard goes away with legacy.css.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
components.css styles every shared component from tokens only: buttons,
badges and state badges in seven tones, chips, counts, cards, sections,
the page header, empty states, skeletons, callouts, the budget meter, stat
tiles, key caps, avatars, tabs, segmented controls, disclosures, fact
lists, tables, toolbars, list rows, toasts, dialogs, drawers, dots, the
demo note, the timeline, steps and the Round ladder. Motion only runs when
reduced motion is off, and forced colours keep state visible.

core/ui.js builds that markup as strings without touching the DOM, escapes
every text parameter and renders links only for in-app paths or safe
http(s) URLs. The meter draws settled and reserved spend with SVG
attributes and never shows unknown or demo spend as zero. stateBadge reads
a built-in copy of the Work Item vocabulary until core/states.js lands, and
also accepts a state meta object. Times prefer format.timeHtml when it
exists.

core/icons.js keeps every glyph name and adds the navigation, theme, status
and action glyphs, all drawn in-house at a 1.75 stroke.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The hidden #design page shows every token (resolved in the current theme),
the type scale, space, radii, elevation, every icon and every component
and state from core/ui.js, plus a class reference. A System, Light and
Dark switch and a density switch preview the page without saving
anything, and live buttons open a real modal dialog and drawer. It is the
QA surface for the redesign: check a change here in light and dark.

The view enters through openPage so the registered page list stays as
it is; styles/design.css holds its own layout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The sidebar stays dark (Hal) in both themes. It carries the outlined De
Vloer lockup inlined with currentColor and groups the navigation: Now; Ploeg
with Work, Proposed, Runs, Activity and Insights; Workbench with Tasks and
Sessions (only in demo mode, with shared execution or when sessions exist);
Settings at the bottom. Counts come from state.counts: what waits on you on
Now, proposed work on Proposed, sessions that need you on Sessions. Below
1100 px the sidebar becomes an icon rail with tooltips.

The top bar holds breadcrumbs, the search trigger (Ctrl or ⌘ K), a status
strip with the Ploeg connection, "Updated … ago", a Live or Paused switch
and the DEMO or LIVE badge, and an account menu with the System, Light and
Dark theme switch, Preferences, keyboard shortcuts and Sign out. The
working-agreement block, the demo ribbon and the hard-coded version footer
are gone; the sidebar shows the version only when the bootstrap reports one.
Under 720 px a compact top bar opens the navigation in a <dialog> drawer and
a bottom bar offers Now, Work, Runs and More.

shell(content, { title, subtitle, overline, actions, breadcrumbs, wide })
is the new signature; shell(content, title, subtitle) keeps working.
document.title leads with the waiting count, "(4) Work · De Vloer".
core/counts.js reads GET /api/ploeg/now every minute through the live
scheduler, and the Now page and proposal decisions update it directly.

The skip link now moves focus to the main content instead of routing to
#main. A 401 in the middle of a session closes open dialogs and the sign-in
page says the session expired; the hash survives, and single sign-on keeps
the deep link through sessionStorage.

styles/shell.css is self-contained with token fallbacks and is imported
from styles.css until the layered stylesheet lands.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Work opens on the Team in the hash, then the Team already on screen, then
the Team last chosen in this browser (the `team` preference). When the
remembered Team is no longer in the account's scope, Work forgets it and
opens the default Team instead of a dead end.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The sign-in page replaced the workbench without changing document.title,
so an expired session kept showing "(4) Work · De Vloer" in the tab. It now
reads "Sign in · De Vloer".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The browser's markdown() renderer has no backslash escapes and no
emphasis, so CommonMark output showed 'order\_id \& x', literal
'*Illustrative*' lines and unlinked '[spec \[v2\]](…)'. The converter
gains a 'vloer' dialect (no escapes, emphasis as plain text, headings up
to level four, square brackets in link text as parentheses, mailto as
text, tables as rows, lowercase fence languages) and descriptionMarkdown
uses it. CommonMark stays the default for other consumers. With no
escapes left, the escaped-secret forms are no longer needed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DEMO-10's close reason quoted a spend of 2.96 that the demo never made,
while its Shift reported zero. It now parks the way Ploeg parks a Shift
that spent nothing: a pool of 0.04 is below the least Ploeg authorizes,
so the pending analyst Run is cancelled before it starts and the reason
reads 'pool 0.04, spent 0.00, reserved 0.00'.

DEMO-1 returns to its free-text escalation with a reviewer stuck in
Round 1, which the front end's reason tests read; the run-stuck close
reason stays covered by DEMO-11.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The component builders treated any href starting with one slash as trusted
in-app navigation. A backslash or a tab, newline or carriage return after
that slash slipped through, and browsers resolve "/\evil.example" and
"/\t/evil.example" to an external host, rendered without target, rel or
safeUrl. button, iconButton, chip, stat, segmented and listRow all shared
the gap.

An in-app href must now start with "#", "?" or a single "/" not followed by
another slash or a backslash, contain no backslash or control character,
and keep its origin when resolved against a placeholder base with the URL
parser. Anything else goes through safeUrl() as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
In dark mode --bg-skeleton and --neutral-bg had the same opaque value as
--bg-overlay, so inside a dialog or drawer the budget meter's remaining
track, skeleton blocks, native progress and meter tracks, neutral badges
and disabled buttons lost their fill (1.00:1 against the surface).

Fills that sit on a surface are now translucent ink, so they keep the same
contrast on canvas, cards, sunken areas and overlays in both themes: a new
--bg-track for meter, progress and unknown-spend hatching (1.19:1 light,
1.31 to 1.34:1 dark), and translucent --bg-skeleton, --bg-skeleton-shine,
--neutral-bg and --neutral-bg-hover. Muted text on the neutral fill stays
at 6.3:1 or better. The WebKit progress and meter bar pseudo-elements are
transparent so the translucent track is not painted twice.

The style guide now shows a spend meter and an unknown meter in the
dialog, a badge, progress, skeleton and disabled button in the drawer,
native progress and meter in Progress, and the --bg-track swatch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The status strip's "Updated … ago" moved on every refresh of the nav
counts, which reads GET /api/ploeg/now every 60 s on every page, and it
carried over to pages that never load live data. Work, Proposed, Runs
and Insights therefore looked fresh while their lists were not reloaded,
and Tasks, Sessions and Settings showed another page's time.

The scheduler now records a load only for view-scoped jobs and for
touch(), remembers which view recorded it, and reports nothing while a
different view is current. Views name themselves when they touch, so a
load that finishes after the user moved on cannot mark the new page as
fresh.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	apps/vloer/public/styles.css
#	apps/vloer/public/views/index.js
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The HTTP contract now lists every /api/ploeg route, the reason fields on
Now rows, descriptionMarkdown on Work Items and the task preview, the
cancel result with its unknown-is-null rule and the demo's 200 answer,
and the static ETag, 304 and gzip behaviour.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ADR 0024 (proposed) records the redesign's decision: Now as home, one
sidebar with permanent redirects from old links, one state vocabulary,
one nl-NL formatter, an OKLCH token system with light and dark themes,
legacy CSS in the lowest cascade layer, the view registry and the small
Vloer server additions. The browser UI reference is the contract for
contributors: routes, redirects, view descriptors, core modules, CSS
layers, tokens, theming, accessibility, CSP rules and the #design style
guide. The architecture map lists the new public/ layout and
src/static.ts, src/rich-text.ts and src/markdown.ts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brand book section 7 now maps the unchanged brand colours onto the
tokens.css roles for both themes, lists the seven status tones with why
each hue was chosen, and keeps the design rationale that was stripped
from tokens.css and components.css.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vloer opens on Now, and the old Ploeg tabs are now the Work, Proposed,
Runs, Activity and Insights pages. The journeys, assign, review and
pilot-batch guides, the shared execution guide and the Vloer demo and
live guides use the new page names, the Needs you and Ready for review
lane labels and the Cancel Work Item action, and drop section labels
that the rebuilt Work Item page no longer promises.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Now opens with a "since you were away" digest, a stat row that links
into Runs, Work and Insights, and the waiting work grouped Review, Needs
you, Proposed. Every needs-you row carries its reason chip and the Not
routed warning; review rows show Shift spend and open the pull request.
Running Runs get a budget meter and finished Runs their outcome and
agent verdict. Each group fails on its own with a retry, the empty queue
says what Ploeg is doing instead, and a 30 s live refresh holds new rows
behind an "N new" button. j/k move, Enter opens and o opens the pull
request or tracker item of the focused row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
states.js gains checkpoint phases, audit event labels with tones and glyphs,
actor names and short verdict labels; Work Item glyphs now match the state
badge. reasons.js gains closeReasonLabel and withdrawnReason. Existing
exports keep their shape.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
One toolbar (Team with All teams, one lane control with counts, refresh),
rows with reason and Not routed chips, spend of budget and age, and a
master-detail layout on wide screens. The Work Item page leads with a
decision box (why it needs you and what to do, or the review receipt and
readiness checklist), then the rendered brief, the Round ladder, Runs
failures first, checkpoints, the audit log and technical details. Cancel
asks Ploeg with a consequence-listing confirm, disabled in the demo.
Closing a Work Item no longer reloads the lists, sessions load once, and
the page refreshes every 30 s through core/live.js.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
At laptop widths the side rail squeezed every waiting row onto three
lines. The waiting list now spans the page with Running now and Recently
finished side by side below it, as in the Now wireframe; very wide
screens still get the rail. Tracker and Grafana links become icon
buttons with full accessible names so each row keeps one labelled
action. Separators never start or end a wrapped line, finished Runs sort
by finish time, and focus stays on its row, tile or button through a
live refresh.

Class names no longer collide with the old Now page, and empty-state
bodies carry plain text, so nothing on the page depends on legacy.css.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rows and lane segments draw their focus ring inset so the scrolling list
and lane control never clip it; paragraphs in cards and empty states reset
their own margins, so the Work screens look the same without legacy.css.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds dayKey and dayLabel (Today, Yesterday, or the English weekday with
the nl-NL date) to group Activity by local day, and percent for shares
such as failed Runs. Existing formatter exports are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rebuilds the four Ploeg feed screens on the foundation components, with
no legacy classes left in their markup.

- Proposed: cards with kind, readiness, Team, brief rendered from
  Markdown (long briefs fold), source Work Item, the proposing Run and
  the repository or a Not routed chip. An "If you approve" column names
  the money at stake. Approve opens a confirmation with the Team budget,
  repository and readiness; Reject asks for a reason and says what
  really happens (live: Done without running; demo: withdrawn).
- Runs: a dense table with a sticky header, running work first in its
  own group, badges for state and outcome, verdict and failure notes, a
  budget meter per Run (settled or observed against authorized, never
  an invented zero), timing, model and tokens. Below 60rem it becomes
  a card list with every field.
- Activity: events grouped by day with sticky day headers, a human
  label for every audit action (including needs_human, awaiting_review,
  done, stale, outcome.*, infra_cap and delivery.publication_*), details
  from Ploeg's own reasons, and humanised actors. A live check holds new
  events behind an "N new events" button instead of moving the list.
- Insights: the window switch sits on the Runs and spend section it
  affects; tiles and per-Team tables for Runs and spend and for Work
  Items right now, per-Team cards on phones. Still no charts.

Every screen has a skeleton, a first-load problem state (setup gaps as
guidance, failures with Try again), a stale-data notice that keeps the
last data, empty states and one demo note. Runs and Proposed refresh
every 30 seconds, Insights every minute, Activity every 15 seconds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ghost buttons that start a row sit on the text edge, the back link reads
'All Work Items' from the All lane, a closed Shift without Runs says so,
and the Team select is narrow enough for one toolbar row at 1100 px.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A waiting item in a state Now does not group, such as a stopped-retrying
item from a newer Ploeg, now lands under Needs you with its reason
instead of vanishing, and the stopped-retrying note no longer repeats
it. Missing groups render as empty instead of failing. Group titles line
up with row titles, trailing ghost buttons with the time column, the
stat row keeps two columns until four fit without breaking an amount,
and the loading skeleton has the same shape as the loaded page.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The UI reference, ADR 0024 and the brand book said every colour token is a
light-dark() pair and that unknown spend never reads as zero; the solid fills
keep one value and the legacy Insights and review panel still print zero.
The browser check ignores console errors mentioning 401, 409 or 503, amber
covers more than Needs you, the 30-second refreshes are not built, and the
unconfigured Ploeg errors and the preview's token redaction differ by route.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The guides described the redesigned lanes, Now groups, reason headline,
"Not reported" spend and the Cancel Work Item button as working. At
f75d76b the lanes still read Awaiting review, Needs human, Queue and All
work, Now has one Waiting on you list, unreported spend prints as zero and
no cancel button exists; Vloer's server only proxies the cancel route.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Running and finished Runs are now separate tbody row groups labelled
with scope="rowgroup", so screen readers announce the group with each
row. The Runs table no longer scrolls on its own, so its wrapper is no
longer a focusable region. An empty filtered list clears its filters
with a clear glyph instead of an arrow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The 30 s refresh re-renders only when Ploeg's data changed, reads every
Team in parallel once the Teams are known, and puts keyboard focus back on
the row or control that had it. Busy Refresh, Load more and Cancel stay
focusable. Tracker links say whether they open the task or only the
tracker.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A viewer read "an operator or administrator approves or rejects it" in a
page callout and again on every card. It now appears once per card,
where the Approve and Reject buttons would be. On phones the "Could not
refresh" notice moves Try again under its text instead of squeezing it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A proposed Work Item says its Shift opens after approval and finished
work without a Shift says none ran. On phones the Copy link and Cancel
tools start at the text edge even when the header links move to the
sticky action bar.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The escape-first renderer now reads the Markdown the server emits for
Vikunja briefs and the Markdown forges and agents write: consecutive lines
stay one paragraph with line breaks, numbered lists keep their numbers,
lists nest by indentation, task items show a checkbox, quotes, rules,
strikethrough, emphasis with word boundaries, backslash escapes and bare
or angle-bracket http(s) URLs render. Everything is still escaped before
any tag is added, and pathological input renders in bounded time.

The renderer contract in test/markdown.test.ts and test/rich-text.test.ts
changes on purpose: strikethrough is <del> and a line break stays in its
paragraph instead of the literal ~~ and a paragraph per line, and <ol> joins
the allowed tags for demo item 109's numbered acceptance criteria.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tasks is a master-detail page: the connection's task list with a filter
and refresh beside the selected task's rendered brief, status, revision
and tracker link, then the import card with crew, runtime, placement and
budget. The selection lives in #tasks?source=&task= so reload and shared
links keep it; phones show the list or the task with a back button; j and
k move through the list. Every state is designed: loading skeletons, a
list error with retry or a link prompt, no tasks, no match, a preview
error with retry, a viewer or closed task, a task that already has a
session, a changed source task and the first run without connections.
The connections dialog uses the dialog component, lists what is
registered and puts the administrator steps behind a disclosure.

Browser flow: the hostile description fixture now sets
descriptionMarkdown to the same text so the preview renders it, and the
flow checks the address, a reload, the empty search, j, the rendered
brief and which pane shows at 390 and 1440 px.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Environment is a health checklist for the Ploeg connection, model gateway,
agent runtimes, workspace placements, task connections and dashboards.
Each check has a tone, a glyph and a word, and anything missing says what
to set up next: configuration keys for administrators, "ask an
administrator" for everyone else. The workbench limits, repositories and
crews follow.

Linked accounts shows one card per provider with its link state, who it
signed in as, method, scopes and expiry, and links, unlinks or takes a
personal token. Preferences uses theme and density previews, switches for
single-key shortcuts and live updates, the number and date format, a
desktop-notification switch once the preference exists, and a link to
the style guide. Loading, error and empty states are designed.

Browser flow: the Environment check waits for "Health checks" and six
checks instead of "Execution environment", and the shortcut and live
controls are switches, so the flow finds them by the switch role and
checks that the live switch and the top bar agree.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The sign-in page pairs the brand panel (the outlined lockup and a line of
copy on a Peil floor line) with the form. Single sign-on comes first when
it is configured. A failed attempt keeps the account name, marks both
fields invalid and returns to the password; an expired session says so;
the password can be shown and Caps Lock is flagged. On phones the brand
becomes a band above the form, with no horizontal scroll at 390 px.

Browser flow: the expired-session copy is now "Your session expired" with
"Sign in again to continue where you were.", and the flow adds a failed
attempt that must keep the account name and a 390 px overflow check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The shell's focus rule for every link and button in the app sets a 2 px
outer offset, which beats the list row's inset ring, so the list card's
clipping hid all but the top edge of a focused task row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Views that build controls with ui.js pass `action: 'name'` instead of
writing `data-action="name"`. The registry check now reads both, so a
handler whose only markup comes from a builder no longer looks orphaned.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Compare has thrown "openCompareDialog is not defined" and "renderCompare
is not defined" since 24071ad removed its code. The button is gone from
the session page, the compare action and form are gone, and the view
only replaces an old #compare/... link with #sessions (the router
already redirects it first).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- New session: a clear title, the demo note on one line, labelled fields
  with hints and inline errors instead of the browser bubble (including
  the server's "objective too thin" and budget answers), Runtime, model
  and approval behind a disclosure, and a footer that stays in view on
  phones.
- Confirmation takes a tone and a list of consequences; a destructive
  confirm uses the danger button and starts on the safe one.
- Review: Accept is primary, Reject is the danger button and needs a
  reason, with an example.
- Budget: shows spent so far, the current authorization, the new total
  as you type and the per-session ceiling.
- Work Item rejection: copy that matches live Ploeg (the proposal closes
  without running) and a danger button.
- One close action for every dialog replaces the misnamed close-budget.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Rows are links grouped as Needs you, Open and Closed, newest first,
  with the session's state badge and a line that says where it stands:
  who is working or waiting, what failed, or who accepted or rejected
  it. An accepted or rejected session no longer reads "Awaiting your
  review", and "Needs you" matches the sidebar count.
- One filter with counts and a search that redraws only the list. Both
  live in the hash (#sessions?filter=needs&q=...), so reload and shared
  links keep them. Empty results say why and offer to clear the search.
- The permanent walkthrough card becomes a small Demonstration card; the
  first-run empty state offers the demonstration or a new brief.
- A Workbench card shows slots in use, recorded spend (never a made-up
  zero) and what is configured.
- The list refreshes itself every 30 seconds while it is on screen.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- What the crew waits on comes first, under the title: permission
  requests (the command rendered as code, danger-styled Reject),
  questions with their options as choices, the failure guidance, the
  delivery gate, or "Your review is next." with an evidence receipt
  (agent verdict, files and lines changed, checks before and after,
  spend, time). Accept is a real primary button, and on phones a
  sticky bar keeps Accept and Reject in reach.
- The state badge sits beside the title; lifecycle actions (Start crew,
  Pause, Resume, Cancel as a danger action, Export handoff) are in the
  page header and disable together while a change is in flight.
- An imported session shows the human brief from the tracker; the
  prompt the crew received is behind "What the crew was told".
- Crew progress is a stepper with each role's state and verdict.
- Evidence keeps its ARIA tablist and #evidence-panel-* scroll
  containers. Activity is a readable timeline (tool calls with their
  result, a running call folded into its result, briefs and output
  behind disclosures); Changes has a file summary, line numbers, added
  and removed lines and a wrap toggle; Checks summarise each run
  (passed of total, exit code, each test) with the raw output folded;
  Handoff shows what each role reported and the reviewer's findings.
- The side column holds the budget meter (unknown spend is never zero,
  the demo says it is one), the repository handoff downloads, the
  Ploeg execution, tool approval and details. The cost line chart is
  gone: stats and tables only.
- The page shows a skeleton while it loads and an error with Try again
  for a missing or unreadable session instead of falling back to Now.
  Finished sessions open at the top of their activity; open disclosures
  survive live refreshes.
- Delivery gate: the same three stages on the card and step components.

The Sessions browser flow now checks the phone review bar, the Accept
contrast, the Accepted label in the list and a search that keeps its
field, and reaches the list through the breadcrumb on desktop.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On a phone the four figures (spent, authorized, new total, ceiling)
stacked into one tall column; two columns keep them comparable.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
core/favicon.js paints the favicon mark with an attention dot on a canvas and
points the icon links at the PNG data URL while something waits, restoring the
SVG when nothing does. core/attention.js drives it from the counts and adds
desktop notifications that are off by default, ask permission only from a user
gesture, announce each new waiting spell once per browser, stay quiet while the
tab is focused and fold bursts into one summary. test/attention.test.mjs
covers both.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The palette is an ARIA combobox over a grouped listbox (aria-activedescendant)
with Recent, Go to, Commands, Work Items and Sessions. Matching ignores case
and accents, marks the matched letters and ranks word starts first; a number
always opens that Work Item. Work Items come from the lists the tab loaded
plus one Now read. Each row shows its shortcut, Enter runs, Escape closes and
restores focus, and phones get a full-width sheet. The shortcut help gets the
same visual language and a two-column layout on wide screens.

Closing from the palette restores focus at once. The dialog's close event can
arrive tens of milliseconds late, so it only acts when the palette is still
closed and focus was actually lost.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Markdown link inside an <url> autolink, or a code span or fenced block
inside a link, was substituted into the outer href, so tracker or agent
text could add attributes to the anchor. Link addresses now refuse the
renderer's placeholder characters, and the hostile-input tests cover both
payloads.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Hidden keywords and parents now match only at word starts and numbers only
as written, so 'rou' no longer finds Insights through 'throughput'. A failed
Work Item read names the server's reason. Lane and recent rows show their
state glyph in its tone, phones get the state as tinted text instead of a
badge that crowded out titles, rows follow the density preference, and the
keycaps no longer lean on the legacy kbd margin. Commands that redraw the
page return focus to the search button or the page heading, and a number
offers no Work Item jump without Ploeg.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The search row already shows Esc as its close button.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every Needs-you row now carries a muted line with Ploeg's reason sentence
and the fix, so touch and keyboard users read the why without a tooltip.
Needs you splits by reason once it holds more than five items and a
reason repeats: shared reasons first, largest first, with the sentence
and fix said once in the sub-group header and single reasons pooled.
Long groups show the first rows and link to the rest in Work or Proposed.

Rows get a glyph per reason, the repository as a plain fact, Not routed
as a secondary outline chip, and actions in fixed columns shared across
the card so ages and icons line up. The review row shows the agent
review of its latest Run. Phones see the first decision on the first
screen: no subtitle, a one-line demo note, the digest as one wrapping
line with an icon button, the stat row below the lists and 44 px
buttons on touch. Demo spend shows no amount, stopped-retrying work is
a normal row, and every moment is a time element.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Refresh stays focusable while busy (aria-disabled and aria-busy, clicks
ignored), every Try again and Show button keeps a stable id, and when a
manual load removes the control that started it, focus moves to Refresh
or the page title. Refresh hides while a Try again is on screen and on
phones, where live updates and the status strip cover it.

The lists render as soon as /api/ploeg/now answers; the 24-hour summary
fills the stat row when it arrives, behind its own request guard, so a
slow summary no longer holds back what waits on you. j, k and o work
with Shift or Caps Lock, and the page has no subtitle.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The operator Work Item projection carried no pull request or review
state, so a consumer had to load each item's detail and scan Run links
and checkpoints. Add an optional pullRequest object to the operator item
projection, built inside the existing single SELECT:

- url selects the newest finished writer Run that opened or updated a
  pull request (same ordering as AwaitingReview) and falls back to the
  newest checkpoint pr_url, both through the operator link sanitizer.
- agentVerdict and agentVerdictRound come from the newest finished
  non-writing Run of the latest Shift.
- humanChangesRequested is true when a work_item_reviews row exists.
- repairFollowUps counts follow-up Work Items that were created by a
  review rather than by a failing Run.

The projection stays one SQL statement per page and the object is null
when no Run or Checkpoint ever reported a pull request link.

VIK-1391
Agent-Trace-Id: ploeg-2945bf6f2bc6
The quiet 15 and 30 second refreshes of Insights, Activity, Runs and
Proposed resolved early while a load was running or had been replaced,
so the status strip said "Updated just now" for data nobody read, and
Activity never retried after a failed first load.

- A live refresh now waits for a load a person started, and resolves
  only when its page is still on screen without an error; otherwise the
  scheduler backs off. Activity reads its first page again after a
  failed first load.
- Refresh and Load older are no longer ignored while a background
  refresh runs, and a background refresh redraws only when the data
  changed, so an open filter menu stays open.
- A fresh first page of Runs replaces the Runs in its id range, so a
  Run that finished drops out of a running filter.
- The header Refresh hides while a failed first load shows Try again.
- Insights keeps a non-default window in the address, so a reload or a
  shared link shows the same window.
- The shared dialog gets its classes back when a Ploeg dialog closes.
- Focus moves to the next proposal after Approve or Reject, and the
  reason field names its error only once one is shown.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Needs you lane groups its rows by reason and routing warning, with the
fix once per group, so seventeen identical badges become one or two
decisions. The Work Item page says each thing once: the decision box has no
headline repeat, carries the primary action (the pull request or the
tracker task, disabled with the reason when Ploeg reported no link) and the
phone action bar carries the same action. plan_exhausted now tells no pull
request apart from unresolved changes and no longer quotes Ploeg's
plan-complete sentence; budget stops show their meter; failures read as a
cause and only a live Work Item is said to retry. Checkpoints fold into
Activity, Runs group by Round on phones, and an empty story is one line.

Work also keeps Load more pages across a refresh, re-reads and focuses a
re-opened Work Item, keeps focus on the Team select and after a live
cancel, scrolls the selected row into the list pane and sizes the pane to
the window.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Runs
- The table fits from a 60rem container up (a 1280 px laptop): fixed
  columns Status, Work Item, Started, Spend and Model, with the Role in
  the Work Item's meta line. Model names stack one per line.
- A running Run reads "Running for 20 min" with the start time in the
  title; a pending Run reads "Waiting for a worker" and "Not authorized
  yet". A failure shows its next step on screen, not only in a title.
- The spend meter says "observed so far" to assistive technology while
  a Run has not settled, drops the percentage in the table, and reads
  "Not reported" alone when no budget is known either.
- The demo shows a muted dash with "Demo · no model calls" in the title
  instead of repeating "No model calls" on every row.
- Phone cards are about half as tall: status row, title, one meta line,
  one timing line, model and tokens, then the meter.

Proposed
- One compact footer per card ("Spends from the delivery Team's
  budget" with Reject and Approve); the budget caveat is said once per
  page and viewers get one "who decides" note. "Readiness not
  reported" is gone and the count reads "3 proposals".
- The reject dialog's cancel reads "Keep it proposed" and its reason
  field is described by the hint alone until an error shows.

Activity
- A dot separates the event label from the Work Item title on wide
  screens; on phones the title takes its own line.
- A long Team name is cut short with its full name in the title on
  wide screens and wraps on phones instead of overflowing.

Insights
- The description reads "Last 7 days · 3 Teams"; the settlement note
  moves under the table. With no Teams only the empty state shows.
- Work Item tiles never leave holes (5, 3 + 2, 2 + 2 + 1 full width),
  phone cards keep amounts on one line ("Reserved"), the demo tile reads
  "No model calls", and tables are no tab stop.

All four
- A failed refresh reads "Could not refresh. Showing data from 11:48."
  in the attention tone; on phones Refresh is an icon beside the title
  and the filters sit in a two-column grid, with the disabled Outcome
  filter explained on screen.
- Loading skeletons match the loaded layouts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ladder cells no longer let their labels set the column width, so four
Rounds fit the detail pane beside the list; a long label wraps to a
second line and the cells of a row keep one height. A Shift closed with a
free-text reason keeps "Ploeg" capitalised.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The chip's reason lived only in its title, which touch screens and most
assistive technology never show. The approve confirmation, where the
decision is made, now prints it under the chip.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The dialog rules in sessions.css reached every dialog in the app. They now
apply only to dialogs the session module prepares, which carry the
session-dialog class until they close. New session opens on its title,
rejecting on the reason, the budget dialog on the amount and accepting on the
confirming button, instead of on Close. Labels and icon buttons no longer
take their layout from legacy.css.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The 30 s refresh redraws only the regions whose markup changed and puts
  focus back on the same row, button or filter; rows have stable ids. The
  search summary is one persistent status region, so a refresh no longer
  announces it again.
- A finished session behind the delivery gate reads "Awaiting your approval"
  in the list and on its page, and "Commit approved" once the page knows the
  approval is recorded. The gate card takes the review tone of the decision
  it is.
- Rows drop the lead glyph that repeated the badge; on phones titles wrap to
  two lines and progress and spend get their own line.
- The search keeps the toolbar on one row at 1280 px, the aside goes beside
  the list from 52 rem, the capacity reads "3 active · 2 slots" when over,
  the N hint hides on phones and empty-state paragraphs no longer rely on
  legacy.css margins.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Layout
- Handoff findings are label and value rows again: the evidence-panel grid
  now applies only to the gateway facts, not to row lists.
- The page goes to two columns from 52 rem, so a 1280 px laptop keeps Budget,
  Repository handoff and Details beside the evidence; the one-column fallback
  no longer stretches cards to the tallest one.
- Evidence panels are positioned and use token scrollbars, so visually hidden
  text no longer adds blank page below the Checks tab.
- Gateway requests read When, Answered by, Tokens, Latency and Cost, with
  flags and errors on a full-width line under the request; nothing scrolls
  sideways at 1280 or 1440 px. Latency follows the number-format preference.
- Checks open with one before-to-after line: Baseline 1 of 3, Verification 3
  of 3, Independent review 3 of 3.

States
- Failed: the message, then numbered steps taken from the remediation, then
  one muted line for submission, retry and reconciliation, with the actions
  on the right. The Activity stream shows the failure.
- Queued: the decision slot holds Start crew; the composer hint, the cancel
  confirmation and Agent reviews no longer talk about work that has not
  started; one evidence empty state replaces five empty tabs.
- A missing session says so, offers All sessions and no retry; the stream
  indicator is hidden until a session is open.
- The review receipt shows the verdict's short label and wraps on phones;
  Export handoff waits for evidence; demo sessions do not offer more budget;
  the handoff note reports a recorded review.

Phones and focus
- The back link lives in the top bar instead of between the header actions
  and the status; the review bar reads Your review and can open the changes.
- Question answers and choices have stable ids and are kept across live
  redraws; the error is announced. After Accept, Reject, Authorize or a
  vanished control, focus lands on the review heading or the page title
  rather than the body. Stepper and stream times are time elements.

The sessions browser flow now checks the handoff layout, dialog and review
focus, starting from the decision slot and an answer that survives a live
update; the ambiguous-failure guidance is checked as its four steps.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Create session sits in a bar that sticks to the bottom of the screen,
  above the phone tab bar, and rests under the import form once it is
  in view. The task pane no longer scrolls on its own; the list does.
- Wide screens open the first task beside the list. Loading shows a
  skeleton pane; an error, an empty list or no search match shows the
  list alone at full width.
- Rows are one line of title and one line of #id, status and age
  (about 55 px; one line in compact density). The list is 20-26rem wide.
- The list title is the connection picker when there are several; the
  count reads "9+" when there is a next page.
- Phones show the task full screen with All tasks first, and keep focus
  on All tasks after Enter on a row; j and k skip hidden rows.
- The header drops the revision into the Updated time's tooltip, uses
  the folder icon for the repository and puts the tracker link on the
  meta line. A single runtime shows as text; the budget reads US$ 5.00.
- Trackers show as plain chips; "Sessions here" and "Handed to Ploeg"
  replace the letter tiles and internal owner labels.
- A slow list no longer draws Tasks over the page you moved to, and
  links inside a clipped brief expand it when they take focus.

Browser flow: the first task opens without a click, the budget starts at
5.00, Create session is on screen at 1280x800, phone focus lands on All
tasks, the runtime is read from the form field, and a slow task list
does not replace Now.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Settings pages fill the content width, so the header, the sub-nav and
  every card share one edge on all three tabs.
- Environment: a failed health read makes the gateway check "Unknown"
  with Check again and counts it as to do; agent runtimes read
  "Registered" because nothing probes them. Crews use the repository
  row layout; Mode shows one badge; facts get a 9rem label column,
  "Sessions at once" and "Local database (SQLite) on one server". The
  demo note no longer calls the gateway simulated.
- Linked accounts: cards sit two to a row; Link sits under the reason
  with the token form after it; empty facts are left out; Scopes gets a
  full row; OAuth access reads "Renews when used" and a token that
  expires within a week says so in days with an attention badge. The
  letter tiles are gone.
- Preferences: two cards, Appearance and Behaviour, without restating
  descriptions, and a one-line style guide link. The theme tiles and the
  live switch follow the account menu and the top bar Live button.
- Pages that finish loading after the session expired no longer redraw
  over the sign-in page, and Environment stops asking after a 401.
- The theme preview rail uses a border token instead of a colour.

Browser flow: Linked accounts, Environment and Preferences have the same
content width, runtimes read Registered, and the top bar and account menu
keep the Preferences controls in step.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Dark mode lifts the form side off the Hal brand panel; the brand copy
  sits in the middle with a quiet glimpse of the states you will meet.
- Help spans the form column. Below 900 px the pitch and glimpse go and
  the tagline becomes one quiet line under the lockup.
- Show password keeps its name and reports its state with aria-pressed;
  the icon changes to a crossed-out eye instead of a lock.

Browser flow: the reveal toggle, and an expired session on Environment
that returns to the sign-in page without a script error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The dot was about 5 px wide at 16 px and painted in the muted attention
ochre, so it barely showed on a light tab strip. It now spans nearly half
the icon (radius 15 of 64, with a 3-unit transparent ring) and uses a
high-chroma attention signal, oklch(68% 0.18 50), that stands out on light
and dark tab strips alike. The signal is a local --attention-signal token
until tokens.css adopts it; the favicon falls back to --attention-solid
where the token is missing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The recent list sat in localStorage without an owner, and signing out left
the cached Now read and the Work, Proposed, Runs and Activity lists in
memory, so the next person to sign in in the same tab could see the previous
person's Work Item titles under Recent or in search.

The recent list is now stored with the user id and reads as empty for anyone
else; a list without an owner is dropped. createScope() follows who is
signed in: signing out (or a different user appearing) marks every cached
list present at that moment as stale, the palette's own Now read is dropped,
and search only uses lists loaded after the current user signed in. A read
still in flight when the user changes is discarded. The browser check seeds
another account's list against the live server and signs out and in again
from the palette.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each row now reads label first and keys right-aligned, so single keys no
longer leave a gap after a fixed key column and both columns start at the
same x. The help's own copies of the secondary button and the keycap are
gone; the dialog uses the shared styles until keys.js emits the button()
and kbd() components.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Callout actions stay inside the callout on phones; the queued Start crew
  fills its row without spilling over the edge.
- Cards whose body opens with a note no longer add extra space under the
  header.
- A remediation with one instruction reads as a sentence, not a list of one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rows: a Work Item shows its state once, as tinted text before its facts
(#id, tracker reference, origin, Team), at every width; the bordered state
pill is gone. Recent rows use the same glyph, label and facts as search.
Ploeg-internal ids such as run-53-1 are hidden; a proposal says "by a Run on
#105". Facts drop whole from the end (Team first) instead of being cut
mid-token. Desktop rows have one fixed height per density, a running Run
marks its Work Item as Running, and an unknown state shows the neutral glyph
and no label. The selected row loses its accent bar; matches are semibold
text and only the active row turns accent blue. The query lines up with the
result titles.

Order: before typing, Recent, Commands, then Go to; the list may be taller
and fades at an edge while it can scroll. One letter matches only the start
of a word in a label, groups rank by their best match without the recent
boost, and for one or two letters Go to and Commands come first.

States: the Work Item failure or partial read is a notice above the results
with a Retry button, shown only while the query may be after a Work Item and
only for the newest Now response, so a later Now load clears it. It names
what is missing ("Running Runs could not be read · results may be
incomplete"). Without a match the empty state sits outside the listbox and
one next step, Open Work, is offered; the Move and Open hints hide while
nothing can be chosen.

Keyboard and accessibility: the listbox is no Tab stop, Tab cycles inside
the palette, arrow, page, Enter and typed keys work from any focus in it and
return focus to the field. The phone Cancel button is 44 px tall and named
"Cancel search". A disabled row stays muted and says how to allow
notifications. Theme and live-update commands redraw Preferences, so its
controls stay in sync.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A recent list stored for someone else, or stored without an owner, was
already ignored; it is now also removed from localStorage the first time
the signed-in user's palette reads it, so it does not linger in a shared
browser until this user opens a Work Item.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The sentence about what search covers moves from the Open Work row into the
no-match note, where it explains the empty result, and the row says only
where Work leads ("Every lane of every Team"), so it no longer truncates on
a phone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each screen track filed requests it could not make in files it did not own.
This lands the sound ones and removes the local copies they replace.

- ui.js: stat() gains detailHtml, quiet, text, className, a chevron on linked
  tiles and an injectable glyph renderer (Insights keeps its no-SVG test);
  emptyState() takes the same renderer; meter() gains observed, keeps the
  percentage out of small meters and reads "Not reported" alone without a
  budget; iconButton() says an external link opens a new tab without a second
  glyph; button() takes value and autofocus; table() takes rowHeader, region
  and className; listRow() announces unread rows. Now, Insights, Runs, the
  session links and the confirm dialog use them instead of local copies.
- components.css: stat chevron and quiet values, the unread dot on the title
  line, kbd margins, empty-state and field-hint spacing, callout actions that
  wrap under the text on phones, a neutral demo hatch, and the Markdown task
  list styles under .prose. base.css zeroes p and h1-h3 margins and no longer
  paints unselected radio groups as checked.
- shell: focus rings and 16 px icons are scoped to the chrome, breadcrumbs keep
  their separators and clip instead of overlapping the search, a back option
  replaces the title crumb on phones, the stream indicator, skip link, toast and
  boot screen have their own styles, a single page action can go icon-only on
  phones, key hints hide on phones, and the lockup lives in core/brand.js.
- tokens: --tabbar-height, --bottombar-height and --attention-signal.
- keys: j/k/o/Esc name the pages that implement them, the palette row lists
  both / and Mod K, the help uses the component keycaps and button and opens on
  the single-key switch.
- counts and live: listeners get the Now response, the counts job skips Now
  and keeps running in a hidden tab, and a refresh that read nothing neither
  backs off nor claims an update.
- prefs gains notify; Preferences' Desktop notifications switch goes through
  the attention module.
- Sign-out and expiry forget per-user data; an expiry keeps its notice and
  shows no extra toast.
- reasons carry a glyph per reason and quote Ploeg without doubled stops;
  lease_lost reads "The worker stopped responding"; Now passes the demo flag.
- #proposed?id= focuses that proposal; the Now response says whether its Run
  pages were cut off; format.seconds() replaces the session's local copy.
- Compare's redirect-only view, the unused Work Item reject dialog and the
  en-US usd2 helper are gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A computed-style comparison of every route and dialog at 1440 and 390 px in
both themes, with and without the old stylesheet, found only a few things the
new layers still took from it. Those now live where they belong:

- base.css gives every anchor the inherited colour without an underline (only
  class-less links get the accent), gives buttons the inherited colour and
  keeps clicks on a button's glyph landing on the button.
- The Grafana dashboard links drop the legacy external-link class, share the
  session's link style and go through safeUrl; the unused status badge and
  placement field in lookup.js are gone.
- The shell no longer builds page actions with legacy markup, and the style
  guide calls shell() with options.
- Now drops its override of the old .now-group overflow.

The remaining differences are intended: the skip link hides by translation
instead of legacy's clip, icon buttons lose a stray 1 px margin, and disabled
buttons are no longer faded twice. styles.css loses the legacy import and
layer; the @font-face rules stay there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- ui.stateBadge() now reads core/states.js through stateMeta(), so its glyphs
  match every other screen and 'session:', 'run:' and the other kind prefixes
  pick the right vocabulary. ui.js drops its own Work Item table and its local
  date fallback.
- Leaving the #design style guide restores the saved theme and density with
  applyAppearance(); its preview note says so. The settings browser flow checks
  it.
- Run token counts go through format.count(), and the Ploeg demo note says
  nothing was spent instead of printing a fixed nl-NL amount.
- Markdown links pass safeUrl() like every other external link, so an address
  with credentials stays text.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
base.css hid .sidebar and .topbar in print, classes the shell never used. The
print rules now hide the real chrome (sidebar, top bar, bottom bar, Settings
sub-nav, page actions, account menu, drawer, toast, skip link) and the sticky
action bars, lay the shell out as one column, and let scroll panes and sticky
lists expand so the whole page prints.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Ploeg tree said "Needs human", "In execution", "Queue" and "All work", and
session statuses read "Needs your decision", "Needs attention" and "Awaiting
your review". The tree, its tooltips and the session panel now use the labels
of public/core/states.js: Needs you, Running, Queued, All, Ready for review,
Needs your input, Failed and Ready for your review.

The task preview type gains descriptionMarkdown, which the server added to the
preview only; the import test compares the stored snapshot with the preview
minus that field.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The breadcrumb took only what the search and status strip left, so on a
session at 1440 px the title read "Fix …". The breadcrumb now shrinks
together with the search, the search drops its text and then its key hint
when it gets narrow (a container query), the account name stays on one line
and hides below 1280 px, and "Updated …" hides below 1366 px. Nothing
overflows at 1100 px. The o shortcut label in the help is shorter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The shell's own error rule added a border and an inset bar on top of the
component's solid danger fill, where neither showed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The reference described the foundation: a palette placeholder, a 30-second
refresh "not implemented yet" and screens still on legacy markup. It now
describes what each screen shows as built (Now, Work and the Work Item page
with Cancel Work Item, the four feed pages, Tasks, Sessions and a session,
Settings, sign-in, the command palette and the attention signals), the query
parameters each route reads, the live intervals, the modules added since
(attention, favicon, brand), and what stays proposed. The architecture table
drops the legacy layer and the brand book drops the legacy colour note and
describes the favicon dot.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every screen is rebuilt, legacy.css is deleted and the Work browser flow
covers Cancel Work Item, which was the ADR's bar for implemented. The status
stays proposed for the owner. The decision text now says what was built where
it departs from the brief: Ploeg's needs_human sentence is quoted under
Vloer's explanation rather than used as the headline, and left out for
plan_exhausted. The size consequence is re-measured (65 modules, about
970 KB, 270 KB gzipped) and the decision register is regenerated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The guides marked the redesigned lane names, the Work Item page's reason,
Markdown briefs, "Not reported" spend and the Cancel Work Item button as not
implemented. They are built now, so the guides describe them: the lanes Ready
for review, Needs you, Running, Queued and All; the decision box and its
readiness checklist; Cancel Work Item and its demo behaviour; the feed pages
as they read today; and where the pilot record's values appear. The demo
walkthrough and the README follow the rebuilt Tasks and session pages.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- forgetUserData resets state.now in place, so the Now view stops reading the previous person's data after a
  sign-out or expiry; it also clears linked accounts, models and delivery, bumps a sign-in epoch and runs
  per-view resets registered with onForget.
- Work resets its module state on sign-out and re-learns the Teams from the unscoped read when every per-Team
  read fails; a browser-saved Team is no longer named in the not-available toast.
- Counts and the Sessions list drop answers that land after sign-out.
- confirmAction always starts on the safe button, and Unlink uses the danger style with "Keep it linked".
- Permission requests show the agent's title as literal text instead of Markdown.
- Other tabs redraw only when a shared preference (theme, density, format, single keys, live) changed.
- openSession ignores superseded calls and closes the previous event stream.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Phones reserve scroll padding for the tab bar and the sticky action bars; links under the sticky Runs table
  header and Activity day headers get a scroll margin; the tab bar and action bars stop sticking at short heights.
- The account menu closes when focus leaves it, and Escape hides the rail tooltips until blur or mouse leave.
- Text fields and selects rest on --border-control-strong (at least 3:1); a failed sign-in no longer flags the
  account name field.
- Work Item pages name the browser tab after the item through a new shell documentTitle option.
- Runs, Activity, the Insights window and the Work lanes announce their result counts.
- markdown() takes a baseLevel and never skips a heading level; briefs start below their card title. Three
  renderer assertions in the server tests change with it on purpose.
- CSS separators carry empty alternative text; the sidebar is no longer an unnamed complementary landmark; cards
  are regions only when asked (the decision box); Approve and Reject are described by their proposal title.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Go cache key hashed only go.sum, so every run hit it exactly, skipped
the save, and compiled against a build cache frozen at the last dependency
change. The key now adds the hash of Ploeg's Go sources.

mise downloaded node, go, uv, helm and openspec on every run (~24s). The
installs directory is now cached, keyed on the three mise.toml files.

The tutorial-smoke job always skipped on the host-mode ci-runner (no
PostgreSQL) but still took a runner for every push and pull request. It
now runs on the weekly schedule.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Minor, patch, digest and pin updates no longer need a dashboard tick. They
share one branch, and Renovate merges it once the pull request checks are
green, reading the combined commit status itself instead of relying on
branch protection. Lock-file maintenance and vulnerability fixes merge
themselves too. The preset's release-age soak comes back for unattended
merges, and internalChecksFilter strict keeps unsoaked updates out of the
group. Majors, Go toolchain minors and preset bumps stay
dashboard-approved.

rebaseWhen conflicted replaces behind-base-branch: every push to
development rebased all ~20 open Renovate branches, and each rebase started
a full pull request run on a four-runner pool.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The deterministic demo runtime waits delayMs between each of its six
steps, and the API test helper used the 1000 ms default, so every test
that drove a session to completion slept at least six seconds. The helper
now injects a demo runtime at 100 ms per step, the pace the unified demo
uses in smoke mode. extension:test drops from 26 s to 10 s and the Vloer
suite from 9.0 s to 7.6 s locally; three concurrent runs of both suites
pass, so pause and cancel tests keep enough window.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The idle clock starts before the harness is spawned, so a slow exec on a
loaded runner or a first-exec scan of the freshly written script could eat
the whole 500 ms before the first tick. The test failed twice in local
verify runs. The script now ticks at once and every 100 ms for 1.5 s
against a 1 s timeout: still longer than the timeout, so a watchdog that
ignored output would fail it, with a tenfold margin between ticks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Under load, "command bridge rejects a null record" hung for 675 s: an
orphaned bridge kept its inherited stdout open, the promise never settled,
and in CI only the 40-minute job timeout would have ended it.
--test-timeout alone marks such a test failed but leaves the process
running on the open handle; with --test-force-exit the run ends. Tests
with their own timeout keep it.

VLOER_TEST_CONCURRENCY, when set, becomes --test-concurrency, so CI can
stop node from starting one worker per host core on a 250m pod.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each Vloer, extension, Ploeg and Helm gate gets a key from the committed
Git trees of its inputs, the mise files, both verify scripts, the resolved
tool versions, the result-affecting environment, its command and the
platform. Passing gates leave a marker in ~/.cache/glide-verify, restored
and saved per run. Every CI run records; only a pull request reuses, so a
push to development, the release gate, still runs every gate. A gate with
uncommitted inputs gets no key, and the release, integration and docs
groups always run.

GLIDE_VERIFY_CPUS sets GOMAXPROCS, go -p and Vloer's test concurrency. CI
sets 4 instead of letting seven parallel groups each fan out to every core
of a shared node.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
mise ls --json reports the path of the mise.toml each tool came from, so
the key changed with the checkout location and a second checkout of the
same commit reused nothing. The key now keeps only tool names and their
resolved versions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Work Item links without a lane open beside the lane the item is in and write it into the hash; the phone
  top bar goes back to that lane, Tasks and sessions use the same shell back option, and the in-page back links
  are gone.
- The Work Item page takes its reason chip from the list's code ("Every Round ran" replaces the hedging
  plan_exhausted chip); a unit test checks list and page chips for every demo item.
- core/states.js is the one table for audit labels, close reasons, actors ("Agent · delivery", "You"),
  verdict short labels ("Agent approved", "Agent asked for changes"), tile details and Runs cancelled before
  they started; Activity, Runs, Now, the Work Item page and sessions all read from it.
- A rejected proposal reads as a neutral "Rejected" (circle-slash) in lists and on its page; its state stays done.
- Needs you follows one rule on Now and Work: flat rows with their chip, and a sunken overline band only for a
  reason two or more Work Items share. Now's why line leads with the fix.
- Decision boxes never show a disabled primary: without a link they name the task key with a Copy button.
- The top bar keeps a fixed search field, folds the session stream and "Updated" into the Ploeg dot's tooltip,
  makes auto-refresh an icon button (Pause/Resume auto-refresh), uses the shared avatar, dims the Ploeg group
  while unconfigured, and shows the phone title only once the h1 scrolls away.
- Every page header has a subtitle and one icon Refresh; Work shrinks its h1 on detail pages and the item title
  takes the page-title scale under a "Work Item" overline; Cancel Work Item is a neutral ghost.
- One demo disclaimer wording and layout, hidden on phones; one unconfigured-Ploeg empty state; one budget field
  (US$ prefix, 5,00, comma or full stop); Settings cards use the card component; #settings opens on Preferences.
- Meters draw settled spend in neutral ink; the Round ladder's Shift budget spans the card; Running now sizes to
  its content; the Runs demo drops the Spend and Model columns; meta lines never start a wrapped line with a dot;
  engine jargon (lease deadlines, Shift ids, "Worker lost") is gone; overlays close with the dialog ×; in-app
  headings drop their full stops; avatar text is at least 12 px.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Under three concurrent go test runs on macOS the idle-watchdog test still
failed 10 of 10 at exactly its timeout, and still 9 of 10 with the idle
clock moved after cmd.Start: the delay sat between a successful exec and
the script's first line, while sh opened the freshly written file. The
same body passed via sh -c passes 10 of 10 under the same load. RunCommand
is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Settlement recorded only the set of models a Run called, so a Run that
used two models counted its whole cost toward both. agent_runs.usage now
carries byModel (model, inputTokens, outputTokens, costUsd) summed from
the same LiteLLM spend-log entries, and the per-model KPI query reads it.

This is the accounting a Role that calls more than one model (an advisor,
a harness subagent) needs before it can be judged on cost.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Run's key is scoped to one model, but Claude Code resolves the haiku,
sonnet, opus and fable aliases to its own built-in IDs, so a subagent or
background task asking for one would call a model outside the key's
scope. Every alias now resolves to the Run's model.

The advisor tool is disabled in every Run: LiteLLM v1.102.1 prices advisor
tokens at the executor's rate and the key's scope does not cover the
advisor model, so a target repository's advisorModel setting could spend
under-metered money (ADR-0039).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A second model gets its own Role; the in-Run advisor stays off until the
gateway prices advisor iterations at the advisor's rate. The research note
records LiteLLM v1.102.1's advisor handling and the live spike that has to
pass before a later record may switch it on.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.15](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.14...glide-v0.4.0-rc.15) (2026-09-30)

### Added

* **ploeg:** split a Run's settled spend and tokens per model ([853eda3](853eda3e73))

### Fixed

* **ploeg:** keep claude-code subagents and the advisor on the Run's model ([d9af860](d9af86070a))

### Docs

* **ploeg:** propose ADR-0039 on multi-model Runs and the advisor tool ([5802d1a](5802d1a855))

### Tests

* **ploeg:** give the idle-watchdog test room for a slow exec ([ad7bbcc](ad7bbcc409))
* **ploeg:** run the talking harness inline so a file scan cannot stall it ([dee6fd3](dee6fd366c))
* **vloer:** fail a test that never settles and accept a concurrency cap ([f7f9ff7](f7f9ff7096))
* **vloer:** run the API demo at 100 ms per step instead of 1 s ([553600b](553600b710))
* **vloer:** write the stop-signal child's pid file atomically ([21125f2](21125f28f5))
With minor and patch combined, Renovate offers only the highest
non-major version. For Go that is 1.27.1, a minor held for approval, so
the 1.25.14 patch would never have been proposed and Go would have stayed
on 1.25.0. Go keeps minor and patch apart, and its patches merge
themselves in a Go toolchain patches branch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The first all-non-major PR (#53) carried go 1.25.0 -> 1.27.1: the mise
go pin's packageName is not `go`, so matchPackageNames missed it and the
minor would have merged without approval. Its depName is `go`, as it is
for the Docker golang images, so the three Go rules now match on
matchDepNames.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Release run 358 left glide-v0.4.0-rc.15 unpublished: de-vloer-agent had
three HIGH findings against a zero budget, all in dependencies npm
bundles, advisories published 2026-09-29 (brace-expansion 5.0.9, fixed in
5.0.11/5.0.12; undici 6.28.0, fixed in 6.28.1). No npm release ships the
fixes yet, including 12.1.0.

The fetch stage packs the fixed releases, pinned as annotated
NPM_*_VERSION args so Renovate moves them, and the final stage swaps them
into npm's own node_modules. patch-bundled-npm.mjs replaces only an older
copy of the same major, logs when npm has caught up, and fails the build
unless npm still expands `{a,b}.js` correctly. Verified locally: npm
12.0.2 with brace-expansion 5.0.12 and undici 6.28.1, opencode 1.18.30,
and grype reports neither package.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Release run 358's Ploeg publication died on one config-blob GET:
IncompleteRead(0 bytes read, 4557 more expected). request() now retries
GET and HEAD up to three times on a cut-off body, a reset or remote
disconnect, a timeout, or HTTP 429/5xx, with 2 s and 4 s pauses. Writes
are never repeated, and 404 and auth errors still answer at once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The group, soak, Go toolchain and preset-bump rules that Glide carried
inline since 20fce37 now ship as renovate-config's automerge-non-major
overlay, including both Go fixes (d5fed1e, cf47b6e). renovate.json keeps
only Glide's own managers and exceptions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.16](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.15...glide-v0.4.0-rc.16) (2026-09-30)

### Fixed

* **vloer:** patch npm's bundled brace-expansion and undici in the workspace image ([24e3d85](24e3d85f3b))
The supervisor spawns the bridge in its own process group. When the
supervisor died before forwarding a stop, the bridge survived with PPID 1
and kept the inherited output pipe open, so close never fired and
execute() never settled: "command bridge rejects a null record" hung for
675 s under three concurrent suite runs. A bridge that exited with a
result while a background child held its output hung the same way.

The supervisor now reports the bridge pid over IPC. When the supervisor
exits for any reason, the runtime kills the bridge's process group, so
pipes close and the normal close path runs with every record read; if
they are still open two seconds later, the runtime releases them. Two
regressions cover both cases and hang on the old code; three concurrent
suite runs pass 248 of 248 with no bridge left behind.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.17](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.16...glide-v0.4.0-rc.17) (2026-09-30)

### Fixed

* **vloer:** settle a command turn when its supervisor dies before the bridge ([96ea78d](96ea78ddee))
Reviewed-on: webgrip/glide#51
The two supervisor tests from 96ea78d polled process.kill(pid, 0) until
the bridge disappeared. In the Forgejo runner the orphaned bridge is
re-parented to a PID 1 that never reaps, so the killed process stays a
zombie and kill(pid, 0) keeps succeeding. Both tests failed on every
run since 96ea78d, which blocked checks and every release. A process in
state Z has exited; the test now reads /proc/<pid>/stat and counts it as
gone. Reproduced and verified with node as PID 1 in its own PID
namespace (unshare --pid --fork --mount-proc).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.18](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.17...glide-v0.4.0-rc.18) (2026-09-30)

### Added

* **vloer:** add design tokens with light and dark themes ([826aba4](826aba4067))
* **vloer:** add formatting, state vocabulary and needs-you reasons ([0e24cd7](0e24cd7f72))
* **vloer:** add preferences, live updates and keyboard shortcuts ([9515062](951506217a)), references [#shortcuts](https://forgejo.webgrip.dev/webgrip/glide/issues/shortcuts) [#palette](https://forgejo.webgrip.dev/webgrip/glide/issues/palette)
* **vloer:** add reason fields to Now items ([8cbf09f](8cbf09f786))
* **vloer:** add the component library and its string builders ([8a93236](8a93236e1a))
* **vloer:** add the living style guide at [#design](https://forgejo.webgrip.dev/webgrip/glide/issues/design) ([a185d57](a185d57080))
* **vloer:** convert tracker HTML descriptions to Markdown for display ([8918b8e](8918b8e354))
* **vloer:** fit Runs to laptops and tighten Proposed, Activity and Insights ([1fb777a](1fb777a004))
* **vloer:** format days and shares for the feeds ([aca9331](aca9331314))
* **vloer:** group Needs you by reason and put the next step in the decision box ([0d70d15](0d70d15fb0))
* **vloer:** list sessions by what they need, with honest review labels ([1f319c6](1f319c66bf))
* **vloer:** make the Ploeg demo exercise every needs-you reason ([de59d66](de59d66a3c))
* **vloer:** name checkpoints, audit events, actors and close reasons in plain words ([66ed55f](66ed55f4e0))
* **vloer:** one vocabulary, one grouping rule and one chrome across every screen ([c039058](c039058c92)), references [#settings](https://forgejo.webgrip.dev/webgrip/glide/issues/settings)
* **vloer:** open on a cross-team Now page ([d50c838](d50c838848)), references [#now](https://forgejo.webgrip.dev/webgrip/glide/issues/now)
* **vloer:** pass Ploeg cancel results through ([e2cc151](e2cc151231))
* **vloer:** rebuild Now as the morning triage page ([8a1a3f0](8a1a3f0509))
* **vloer:** rebuild the session dialogs on the dialog components ([86a3a0b](86a3a0b8c5))
* **vloer:** rebuild the session workspace around the decision it waits on ([651d630](651d63075d))
* **vloer:** rebuild the shell with grouped navigation, status strip and theme switch ([ba6b23a](ba6b23a85c))
* **vloer:** rebuild Work as a lane list beside a Work Item decision page ([4b6ccdb](4b6ccdb9e3))
* **vloer:** redesign Proposed, Runs, Activity and Insights ([eab149a](eab149afca))
* **vloer:** redesign Tasks as a list beside the selected task ([1d06412](1d06412b13))
* **vloer:** remember the last Work team per browser ([9d06e77](9d06e7726d))
* **vloer:** render tracker Markdown with line breaks, lists, quotes and emphasis ([480035c](480035ce38))
* **vloer:** replace the palette placeholder with a fuzzy command palette ([e178c49](e178c49f0a))
* **vloer:** route the new information architecture with redirects from old links ([88ed148](88ed148167)), references [#work](https://forgejo.webgrip.dev/webgrip/glide/issues/work) [#proposed](https://forgejo.webgrip.dev/webgrip/glide/issues/proposed) [#runs](https://forgejo.webgrip.dev/webgrip/glide/issues/runs) [#activity](https://forgejo.webgrip.dev/webgrip/glide/issues/activity) [#insights](https://forgejo.webgrip.dev/webgrip/glide/issues/insights) [#ploeg](https://forgejo.webgrip.dev/webgrip/glide/issues/ploeg) [#insights](https://forgejo.webgrip.dev/webgrip/glide/issues/insights) [#account](https://forgejo.webgrip.dev/webgrip/glide/issues/account) [#system](https://forgejo.webgrip.dev/webgrip/glide/issues/system) [#sessions](https://forgejo.webgrip.dev/webgrip/glide/issues/sessions) [#now](https://forgejo.webgrip.dev/webgrip/glide/issues/now) [#page-title](https://forgejo.webgrip.dev/webgrip/glide/issues/page-title) [#announcement](https://forgejo.webgrip.dev/webgrip/glide/issues/announcement)
* **vloer:** say why each Work Item waits on Now and group Needs you by reason ([8ba7188](8ba7188be5))
* **vloer:** Settings with an Environment checklist, Linked accounts and Preferences ([f432e1c](f432e1cd71))
* **vloer:** signal what waits on you with a favicon dot and opt-in desktop notifications ([89348d0](89348d0b27))
* **vloer:** split sign-in page with the outlined lockup ([0d4d83f](0d4d83fc58))

### Fixed

* **vloer:** align Work ghost actions to the text edge and name the All lane ([bcb6bee](bcb6beec37))
* **vloer:** calm the palette rows, rank short queries sensibly and stop stale failures ([9c6a3a9](9c6a3a97ef)), references [#id](https://forgejo.webgrip.dev/webgrip/glide/issues/id)
* **vloer:** drop another account's recent list from the browser when the palette reads it ([3d49298](3d49298af1))
* **vloer:** explain "Not routed" in the approve dialog ([82878ae](82878ae342))
* **vloer:** fit the Round ladder at 1280 and keep Ploeg capitalised in Activity ([a5c59eb](a5c59eb542))
* **vloer:** forget the signed-out person everywhere and keep decision prompts literal ([39e1c39](39e1c39998))
* **vloer:** give the page title room in the top bar at laptop widths ([e1fc2e3](e1fc2e3d81))
* **vloer:** give the sign-in split a real contrast and a steady reveal toggle ([e8d6684](e8d6684dae))
* **vloer:** give the theme previews a visible edge in dark mode ([8bf7e49](8bf7e49175))
* **vloer:** give the waiting list the full width on Now ([4c99ea9](4c99ea9675))
* **vloer:** group Runs as table row groups and drop a needless tab stop ([39feaa0](39feaa0694))
* **vloer:** keep Create session in reach and tighten the Tasks list ([a70365d](a70365d581)), references [#id](https://forgejo.webgrip.dev/webgrip/glide/issues/id)
* **vloer:** keep demo spend at zero and DEMO-1's free-text escalation ([0510900](0510900383))
* **vloer:** keep every waiting item on Now and align its grid ([68e6dd7](68e6dd7acb))
* **vloer:** keep focus visible under fixed chrome and name pages, regions and filters for assistive tech ([1c8e638](1c8e638ce0))
* **vloer:** keep keyboard focus on Now and paint it before the summary ([0d57057](0d57057578))
* **vloer:** keep links and code out of a Markdown link's address ([25b2a5b](25b2a5b632))
* **vloer:** keep meter tracks and skeletons visible on dialogs ([e92b7c2](e92b7c2700))
* **vloer:** keep the palette's recent items and Work Item search to the signed-in user ([ee61b77](ee61b77756))
* **vloer:** keep the Sessions list steady while it refreshes and name the delivery gate honestly ([d17ee58](d17ee58c8d))
* **vloer:** keep the task row focus ring inside the list card ([21e3111](21e3111a77))
* **vloer:** keep Work calm while it refreshes itself ([67cf224](67cf224228))
* **vloer:** keep Work focus rings inside their lists and drop the last legacy spacing ([58c78f4](58c78f4c12))
* **vloer:** let the error toast keep the component's danger style ([fc4740f](fc4740f8b1))
* **vloer:** make every session state answer what to do, and keep focus and answers through live updates ([63f74a8](63f74a81fc))
* **vloer:** make palette results quieter and keep focus after redraws ([d8e624c](d8e624c9b8))
* **vloer:** make the favicon dot big and bright enough to notice in a tab strip ([23edaa1](23edaa1f4b))
* **vloer:** one Settings width, an honest Environment and calmer Preferences ([8f26354](8f26354670))
* **vloer:** print the page without the shell chrome ([97975bf](97975bfed1))
* **vloer:** read state badges from states.js and keep formats and links on the shared helpers ([2278603](2278603a6d)), references [#design](https://forgejo.webgrip.dev/webgrip/glide/issues/design)
* **vloer:** refresh the Ploeg feeds only with data Vloer read ([5795cc4](5795cc4081))
* **vloer:** refuse in-app links that resolve to another host ([b1447da](b1447dab13))
* **vloer:** retire the broken session Compare view ([f39fbc8](f39fbc8c3b)), references [#sessions](https://forgejo.webgrip.dev/webgrip/glide/issues/sessions)
* **vloer:** say what search covers above the no-match next step ([b0a6be8](b0a6be8bd6))
* **vloer:** say who decides once and let the stale notice wrap on phones ([60a810b](60a810bd5a))
* **vloer:** scope the session dialogs' styles and open each on its first field ([2180b46](2180b46f40))
* **vloer:** show "Updated" only for the page on screen ([18013c6](18013c6236))
* **vloer:** state-aware Shift notes and left-aligned phone tools on Work ([786a9b2](786a9b26c2))
* **vloer:** title the sign-in page ([aed3ff5](aed3ff599a))
* **vloer:** use the browser's state vocabulary in the VS Code extension ([6139d40](6139d4040a))
* **vloer:** write tracker Markdown in the subset the browser renderer reads ([80d8b87](80d8b87486))

### Performance

* **vloer:** serve static assets with ETag and gzip ([2ff9ade](2ff9ade8a8))

### Changed

* **vloer:** apply the screens' shared requests to the shell, core and components ([c0df122](c0df122ec0))
* **vloer:** draw the shell from the design tokens without hex fallbacks ([7cbd225](7cbd2250a8))
* **vloer:** move the stylesheet into cascade layers ([a805777](a805777395))
* **vloer:** retire legacy.css ([db00d9a](db00d9a7b2))
* **vloer:** split the browser app into core, shell and view modules ([404f69a](404f69a624)), references [#app](https://forgejo.webgrip.dev/webgrip/glide/issues/app) [#now](https://forgejo.webgrip.dev/webgrip/glide/issues/now)

### Docs

* point the guides at Now, the Ploeg pages and Cancel Work Item ([6528462](6528462227))
* point the guides at the built Now, Work Item page and Cancel Work Item ([f5eaf4d](f5eaf4dd98))
* **vloer:** describe the application palette as token roles and status tones ([730874f](730874fa0d))
* **vloer:** describe the rebuilt screens in the browser UI reference ([7dd7523](7dd7523230))
* **vloer:** document the Ploeg proxy routes, Now fields, cancel result and static caching ([3072b9f](3072b9f6dc))
* **vloer:** list the browser UI reference in llms.txt ([de8ec80](de8ec8037e))
* **vloer:** propose ADR 0024 and add the browser UI reference ([045a087](045a0870e0)), references [#design](https://forgejo.webgrip.dev/webgrip/glide/issues/design)
* **vloer:** record ADR 0024 as implemented and still proposed ([4f41104](4f4110440a))
* **vloer:** state what the legacy screens, tokens and API do today ([b5e890e](b5e890e51d))

### Tests

* **vloer:** count a killed bridge left as a zombie as gone ([71ad105](71ad1059e8))
* **vloer:** count the richer Ploeg demo in the feed and summary checks ([f75d76b](f75d76b52e))
* **vloer:** count ui.js builder actions as markup in the registry check ([68a2954](68a2954171))
* **vloer:** split the browser check into per-area flows ([3e9ab23](3e9ab23580)), references [#16](webgrip/glide#16)

### Style

* **vloer:** drop the duplicate Esc hint from the palette footer ([efbe1aa](efbe1aa794))
* **vloer:** lay the budget dialog's figures out in two columns ([7630133](7630133ba3))
* **vloer:** line up the shortcut help with labels left and keys right ([0709d25](0709d25eae))
* **vloer:** polish the session callouts and cards at phone width ([fded080](fded0804b0))
96ea78d's regressions failed on the CI runner: after the runtime SIGKILLs
the orphaned bridge's group, the dead bridge is reparented to a PID 1 that
never reaps, so it stays a zombie and process.kill(pid, 0) still succeeds.
Reproduced in a Linux container with node as PID 1: /proc state Z, and the
old helper fails both tests exactly as run 370 did. gone() now also
accepts a zombie; both tests pass there and on macOS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
db49e45 was meant to add the zombie-aware reap check, but 71ad105 had
already landed the same fix, and the autostash reapply left conflict
markers that were committed with it. The file is back to 71ad105's
version, unchanged since.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Keep both sides' new concept pages in the docs index and the mkdocs nav:
Inside a Run and Journeys from development, then Who Glide is for.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#15
Resolves the overlap with the redesign: the hand-off ADR moves to 0025
because the redesign took 0024, development's HTML-to-Markdown converter
replaces the branch's copy, and the extension uses the browser's state
vocabulary. A list row keeps its display Markdown for the editor, while
an import reads the task without it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#44
On a connection Ploeg runs, every task that Ploeg did not hold yet showed
"Ploeg binding needs attention", with no way to clear it. On the Glide board
that was every task: Ploeg holds a task only after a team's tracker user is
assigned, and the import binds only a queued Work Item of the execution team.

The task pane now carries a Ploeg card with the editor's status rules: where
the task stands, its Work Items with spend and pull request, Hand to a team
and Take back until Ploeg starts. The import form appears on such a
connection only when the task continues a queued Work Item of the execution
team. List rows name the tracker assignees, and the owner label reads
"Runs on Ploeg".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.19](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.18...glide-v0.4.0-rc.19) (2026-09-30)

### Added

* **ploeg:** report reserved models and observed spend for running Runs ([36ddc03](36ddc0388e))

### Docs

* **ploeg:** plan credential isolation for the cluster ([c4207fc](c4207fcdb5))

### Tests

* **vloer:** count a zombie as a reaped bridge on Linux ([db49e45](db49e45150))
* **vloer:** remove the conflict markers db49e45 committed ([45db471](45db4712eb))
Forgejo sends no webhook when a base-branch push makes an open pull
request conflict, and Ploeg acts on no conflict today. ADR-0040 proposes
that ploegd polls routes that opt in, files a priority ticket per
confirmed conflict, assigns it to a configured Team, and resolves it:
a merge commit in place on same-repository branches, a superseding pull
request for AGit and fork pull requests.

The research note records the Forgejo source behind the design and the
five conflicted pull requests of 2026-09-30.

VIK-1584

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Development took ADR number 0039 for the advisor record, and #54 claims
0040, so the OpenAI Agents API record becomes 0041. Its links in
ADR-0032 and the research note follow. The ADR index keeps both rows,
and docs/reference/decisions.md is regenerated, not hand-merged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs/reference/decisions.md is regenerated from the merged ADR indexes,
not hand-merged. ADR-0037 keeps its number.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs/how-to/review-an-agent-pr.md keeps development's reworded findings
row for the redesigned Vloer and this branch's usage-report row, and
both verification notes.

VIK-1305

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A writer writes {"problem","solution"} to PLOEG_OUTCOME_FILE and opens its
pull request with the same two sections. The drop box keeps both whatever the
adapter concluded, execbin accepts a file without an outcome, and ploegd stores
them on agent_runs for writing Runs only (migration 0022). The operator API
returns both on every Run. They decide nothing. ADR-0042 (proposed).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The card takes the newest writing Run that reported either, from the latest
Shift when one there did, names the Role and Round that wrote it and asks the
reader to check it against the pull request. A Ploeg without the fields parses
as empty. Demo writer Runs carry illustrative accounts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#55
Agent pull request #45 conflicted with development while Vloer showed it
as ready for review. Owner decision 5 makes a conflict clear inside Vloer
on every route. The review reconcile records each awaiting_review pull
request's merge state with the two-poll rule, the operator API returns it,
and Vloer flags it in attention tone. The Work Item's state is unchanged.

VIK-1598
VIK-1599

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ADR index keeps 0040 and development's 0042, and
docs/reference/decisions.md is regenerated, not hand-merged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ADR-0043 retries a failed reading Run in its Round under the writer's
budgets and closes a Shift whose review never came as review_failed
(VIK-1304). ADR-0044 lets an operator requeue stopped work from a
Round they choose, extending the requeue route of VIK-606 (VIK-1596,
VIK-1597). ADR-0019 gets a dated refined-by note.

Case: Work Item 138, Shift 118, PR #45.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Problem side carries a hazard-stripe edge and a large statement; the
Solution side a solid bar and diamond bullets, joined by an arrow node that
turns downward when the card stacks. Mono labels and a source line name who
wrote it, and a missing half reads Not reported. Tokens only, light and dark.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ADR index keeps 0041 and development's 0042, and
docs/reference/decisions.md is regenerated, not hand-merged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#45
Reviewed-on: webgrip/glide#57
Reviewed-on: webgrip/glide#56
Reviewed-on: webgrip/glide#54
Reviewed-on: webgrip/glide#49
Reviewed-on: webgrip/glide#47
Reviewed-on: webgrip/glide#46
Resolve operator_test.go onto development's operatorSchemaGET helper, add
the item's pullRequest field to the tracker-execution contract its lookup
now returns, and give the store test's agent_runs insert an empty links
array instead of NULL.
The idle watchdog only counted stdout and stderr, so a harness waiting
on a long model call looked silent and was stopped. The worker now sees
model traffic through its loopback proxy (the key proxy, or a plain
observer when the key is not isolated) and touches the same activity
clock. A stop by PLOEG_HARNESS_IDLE_TIMEOUT is reported with the new
failure reason `idle`, apart from `timeout`, which now means only the
wall-clock limit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Runs view names Ploeg's new `idle` failure reason, and a job that
ran more than once shows which attempt each Run was and how many
earlier attempts failed on the machine side.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#50
The Vloer view test imports both this branch's attempt helpers and
development's writerAccount.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#58
Reviewed-on: webgrip/glide#48
Reviewed-on: webgrip/glide#42
## [glide-v0.4.0-rc.20](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.19...glide-v0.4.0-rc.20) (2026-09-30)

### Added

* **ploeg:** have a writing Run report the problem and solution a reviewer reads ([2083ba6](2083ba6085))
* **ploeg:** post a usage and evidence report on every agent pull request ([b1e9f24](b1e9f24332))
* **ploeg:** report pull request and review state per work item ([846dee2](846dee2267))
* **ploeg:** report team tracker assignees and find work by tracker task ([f9957f3](f9957f3c5b))
* **ploeg:** report which tracker boards are pinned to each team ([eab1e16](eab1e163e6))
* **ploeg:** route a tracker item by its repo label among registered targets ([c106f79](c106f79e13))
* **vloer:** draw the problem and solution as a before-and-after panel ([4803af9](4803af97ad))
* **vloer:** hand a tracker task to a Ploeg team from the workbench ([627f6b0](627f6b098f))
* **vloer:** open linked tasks in a task view and hand them to Ploeg ([46a5040](46a5040680))
* **vloer:** show idle stops and number retried Runs ([6b66a68](6b66a68d4b))
* **vloer:** show the writer's problem and solution under the Work Item title ([579dad5](579dad5898))

### Fixed

* **ploeg:** count model traffic as harness activity and report idle stops as idle ([6ff1359](6ff135978c))
* **vloer:** check every team before handing a task over or taking it back ([d095b45](d095b451a7))
* **vloer:** make the task view robust to races, long tasks and escapes ([63e4de5](63e4de5499))
* **vloer:** show work awaiting review in the VS Code Ploeg tree ([47c62cb](47c62cb97d))

### Docs

* **adr:** accept ADR-0011 with the remote phase in scope ([a2292af](a2292af77a))
* **glide:** research MCP access and propose ADR-0011 ([71f601a](71f601a0e4))
* **ploeg:** ADR-0040 shows a conflict on an awaiting_review pull request ([cc87fc1](cc87fc1378)), references [#45](webgrip/glide#45)
* **ploeg:** how to route a board that serves several repositories ([a52f799](a52f7996d6))
* **ploeg:** propose ADR-0040, a conflicted pull request becomes a priority ticket ([9988750](9988750471))
* **ploeg:** propose retrying a failed reviewer and restarting from a chosen Round ([2f608d2](2f608d2f98)), references [#45](webgrip/glide#45)
* **ploeg:** record the OpenAI Agents API fit and propose ADR-0039 ([693be37](693be377de))

### Tests

* **ploeg:** regenerate the Helm goldens for PLOEG_USAGE_REPORT ([c52a37c](c52a37ce10))
The owner accepted the record on 2026-10-01 with the decisions of
2026-09-28 already recorded under Owner decisions. The ledger row, the
amendment note in ADR-0035 and the decision register now say accepted.

VIK-1332

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#10
Reviewed-on: webgrip/glide#43
Reviewed-on: webgrip/glide#37
Keep development's webgrip/workflows v2.7.5 pin; the update bumped the retired v1 line.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
golang.org/x/sync v0.23.0 and golang.org/x/text v0.42.0 require Go 1.26, so go.mod moved to go 1.26.0 while mise still pinned 1.25.0 and only worked through toolchain auto-download.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The supervisor's exit could be handled before its bridge.started message, leaving the bridge running. The markdown bound test now scales with VLOER_TEST_TIMEOUT_SCALE like the other time-bound tests; it took 3269 ms on the loaded runner.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	apps/ploeg/ops/docker/ploegd/Dockerfile
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The "Where to dig deeper" section linked /d/spend-attribution?var-team=,
but the Grafana dashboard's uid is dark-factory-spend-attribution and it
has no template variables. The Vloer link went to /ploeg, a path Vloer
does not serve; it routes by hash.

- Spend & Attribution: <grafana>/d/dark-factory-spend-attribution
- Glide — Loop: <grafana>/d/glide-loop, with ?var-team=<team> when known
- Run Explorer: unchanged, the alias is now URL-escaped
- Vloer: <vloer>/#work/<workItemId>, "This Work Item in Vloer"

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A static Astro site in apps/site with an English landing page at / and a
Dutch one at /nl, plus a 404 per locale. Every brand value sits in
src/styles/brand.css, with the wordmark and mark in one component each,
so the brand can be swapped in place. The build validates the meta CSP;
nothing is deployed until a domain is chosen.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
mise run setup installs the site, and mise run verify runs its format,
lint, typecheck, test and build gates as their own group, so the pull
request and source workflows cover it through the shared verify action.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The site lives under apps/ but ships no artifact, so a commit scoped
site no longer versions Glide, breaking or not. The isolation test
covers both cases; the CI guide documents the scope.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The shared Cloudflare deploy workflow installs with pnpm and runs wrangler through pnpm exec, so the site follows webgrip.nl: packageManager pins pnpm for corepack, pnpm-workspace.yaml allows builds only for esbuild and workerd, and wrangler is a pinned devDependency. The package is named glide-site so semantic-release-monorepo's notes match the glide-site-v tags.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Setup installs the site with corepack pnpm and a frozen lockfile, verification runs each site gate through corepack pnpm, and the verify action caches the pnpm store keyed on the site's lockfile.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SITE_URL points at the glide-site workers.dev host with a SUBDOMAIN placeholder, wrangler.toml turns workers_dev on, and SITE_INDEXABLE, derived from SITE_URL, marks every page noindex and makes robots.txt disallow all while the host is a platform hostname.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
apps/site/.releaserc.cjs is a monorepo train with glide-site-v tags. The isolation tests show it counts only site commits, that each train reads only its own tags, and that Glide's release policy and publisher reject glide-site-v tags.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ADR-0012 refines ADR-0004: the marketing site has its own glide-site-v train and deploys to Cloudflare on its own release. The site's deploy guide lists the hostname placeholder, the secrets, the domain path and the workflow jobs that are not wired yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The deploy passes the workers.dev origin Cloudflare reports as
GLIDE_SITE_URL, so no hostname is written into the source. Local builds
fall back to localhost, which is never indexed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
site-release runs the glide-site train after Glide's release. A published
glide-site-v tag resolves the account's workers.dev subdomain from the
Cloudflare API and deploys through the shared cloudflare-deploy workflow
with the org-level credential the bridge publishes from OpenBao.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.21](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.20...glide-v0.4.0-rc.21) (2026-09-30)

### Dependencies

* **deps:** lock file maintenance ([d13c232](d13c232979))

### Added

* **deps:** update docker.io/golang docker tag ( 1.26 ➔ 1.27 ) ([36fae64](36fae64362))

### Docs

* **adr-0037:** accept per-team registry egress through a logged allowlist proxy ([996ffa9](996ffa9cc2))
* **ploeg:** propose ADR-0037, per-team registry egress through a logged allowlist proxy ([49c60c3](49c60c34e7))
* **ploeg:** record the owner's ADR-0037 decisions of 2026-09-28 ([2b8107b](2b8107b853))
Reviewed-on: webgrip/glide#62
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	mise.toml
Reviewed-on: webgrip/glide#59
The renderer bound was a fixed 3 s; on the shared CI runner the case took
5 s while it takes 0.3 s locally. The bounds now follow
VLOER_TEST_TIMEOUT_SCALE like the other timing tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#53
Reviewed-on: webgrip/glide#60
Reviewed-on: webgrip/glide#64
The OpenCode probes asserted a hard-coded 1.18.30, so every Renovate bump
of OPENCODE_VERSION broke them. They now read the version pinned in
ops/agent/Dockerfile. The example configs and live guide name the 1.18.33
agent image.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The fenced-code pattern backtracked over every language length on an
unclosed fence, which made the pathological-input test quadratic and
pushed it past its 3 s bound on the loaded runner (run 443). The
language name is now matched atomically; output is unchanged.

actions/cache v6.1.0 replaces v4.3.0, dropping the bundled punycode
import behind the DEP0040 warning. The mise install cache now falls
back to the latest entry and is saved right after install, so a tool
bump or a failing job no longer leaves it cold.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Renovate (#53) pinned the semantic-release-monorepo image by digest in
every workflow, while the site's release job from #60 still used the bare
0.3.3 tag. The workflow policy requires the site job to run in exactly the
release job's container, so release-policy fails on development and no
Glide release has been cut since rc.21.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#65
## [glide-v0.4.0-rc.22](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.21...glide-v0.4.0-rc.22) (2026-10-01)

### Added

* **deps:** update all non-major dependencies ([99f5867](99f58670ec))
* **site:** add the bilingual static marketing site scaffold ([2309159](2309159b44))
* **site:** serve from workers.dev and stay unindexed there ([75316ae](75316ae887))
* **site:** take the site URL from the build environment ([c4a33e1](c4a33e199a))
* **vloer:** show a task's Ploeg status and hand it off from the Tasks page ([7ee10ba](7ee10baa11))

### Fixed

* **ploeg:** point the usage report links at dashboards that exist ([3885307](3885307c12))
* **vloer:** reap an orphaned bridge whose pid arrives after its supervisor exits ([9a39191](9a391916a9))

### Docs

* **site:** record the separate site release and deploy in ADR-0012 ([7df82f9](7df82f9354))

### Tests

* **vloer:** scale the pathological-input time bounds with the runner load ([fa422d5](fa422d5d52))

### Build

* pin Go 1.26.8 for the updated golang.org/x modules ([cc2d9f8](cc2d9f8b59))
* **release:** keep site commits out of the Glide version ([f12bb75](f12bb75c34))
* **site:** give the site its own glide-site-v release train ([d8b59ca](d8b59ca3b4))
* **site:** switch the site to pnpm and pin wrangler ([4ab487b](4ab487b012))

### CI

* **site:** release the site on its own train and deploy it to workers.dev ([b6adee9](b6adee95b1))
## [glide-site-v0.1.0-rc.1](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-site-v0.0.0...glide-site-v0.1.0-rc.1) (2026-10-01)

### Added

* **site:** add the bilingual static marketing site scaffold ([2309159](2309159b44))
* **site:** serve from workers.dev and stay unindexed there ([75316ae](75316ae887))
* **site:** take the site URL from the build environment ([c4a33e1](c4a33e199a))

### Docs

* **site:** record the separate site release and deploy in ADR-0012 ([7df82f9](7df82f9354))

### Build

* **site:** give the site its own glide-site-v release train ([d8b59ca](d8b59ca3b4))
* **site:** switch the site to pnpm and pin wrangler ([4ab487b](4ab487b012))

### CI

* **site:** release the site on its own train and deploy it to workers.dev ([b6adee9](b6adee95b1))
Checks ADR-0006's pricing against the models Glide runs and turns the
gaps into spikes VIK-1617, VIK-1621, VIK-1623, VIK-1624 and tickets
VIK-1622, VIK-1625. No accepted number changes; amendments are proposed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Forgejo v15 evaluates site-deploy's `with:` while flattening the shared
Cloudflare workflow. For a glide-v tag, site-release-tag was skipped and
had no outputs, so Forgejo failed run 456 before any job started and
glide-v0.4.0-rc.22 published nothing. The gate job now runs on every
release and outputs deploy=false unless the tag is an enabled
glide-site-v tag. The Cloudflare failure message now says what a 401
means.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#68
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The owner accepted both records on 2026-10-01 with the options as
written: a failed reviewer retries under the writer's budgets; a
restart covers needs_human, stale and awaiting_review, resets the
attempt counts and authorizes a pool the operator confirms. Build
order: VIK-1304, then VIK-606, VIK-1596 and VIK-1597.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#69
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#70
Reviewed-on: webgrip/glide#71
Resolve the conflict with bdf17f1 (all non-major site dependencies):
keep development's versions, keep astro at 7.2.8 and its
minimumReleaseAge exception, and regenerate pnpm-lock.yaml with
pnpm 11.8.0. Site gates pass: format:check, lint, typecheck, test,
build.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#66
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.23](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.22...glide-v0.4.0-rc.23) (2026-10-01)

### Added

* **deps:** update all non-major dependencies ([bdf17f1](bdf17f1397))
* **deps:** update dependency astro ( 7.1.6 ➔ 7.2.8 ) [security] ([7054760](70547607e0))

### Fixed

* **ci:** bound the Markdown fence match and keep the mise cache warm ([5fa31c5](5fa31c5b5d))
* **ci:** never skip the site gate job so Glide releases publish ([f7d0a81](f7d0a8190a))

### Docs

* **ploeg:** accept ADR-0043 and ADR-0044 ([2621b2e](2621b2efb0))

### Internal

* **release:** glide-site-v0.1.0-rc.1 [skip ci] ([f0ada8c](f0ada8cb5c))
## [glide-site-v0.1.0-rc.2](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-site-v0.1.0-rc.1...glide-site-v0.1.0-rc.2) (2026-10-01)

### Added

* **deps:** update all non-major dependencies ([bdf17f1](bdf17f1397))
* **deps:** update dependency astro ( 7.1.6 ➔ 7.2.8 ) [security] ([7054760](70547607e0))

### Fixed

* **ci:** never skip the site gate job so Glide releases publish ([f7d0a81](f7d0a8190a))
Reviewed-on: webgrip/glide#52
Reviewed-on: webgrip/glide#67
Adds evidence on Fireworks, open-weight providers (EU residency) and
frontier models, corrects the DeepSeek V4.1 Flash price, and adds spikes
VIK-1651 (EU inference route) and VIK-1652 (Gemini price change).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A push to development started four runner pods: checks, release-policy,
release and site-release. The worker pool is full, so each pod queues for
a slot; in run 467 release-policy waited 2m37s for one. The release job
already runs in the same semantic-release container, so the policy now runs
as its first step and a push needs one pod fewer. Pull requests keep the
separate release-policy job, whose steps the routing test pins to the
release job's.

The CI guide also described the mise cache as having no fallback; since
#70 it restores the nearest older entry and saves under the new key.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#72
VS Code 1.140 (2026-09-30) moved its Copilot harness onto the agent
host and added remote delegation, deep links and portable MCP config.
Two records land here:

- The Vloer fit dossier. AHP is still 0.9.0 and the handshake still
  matches. Read against the 1.140 client, though, the host cannot run
  a session (C1-C4) and leaks sessions and rejected actions across
  users (C5-C6). It lists fifteen findings, each with its ticket.
- The Glide record. It covers what the shared VS Code and JetBrains
  surface offers, ten editor journeys, competitors' editor hand-off and
  market fit for Dutch PHP agencies, where PhpStorm outnumbers VS Code.

Vloer ADR 0012, the protocol ledger and Ploeg backlog item 101 get
dated updates. The landscape explorer is regenerated for the ledger
change. The work is tracked under epic VIK-1644.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The agent host sent root session notifications to every attached
client, whatever its user: sessionAdded, sessionSummaryChanged and
sessionRemoved, carrying titles, budget and spend. It also re-broadcast
rejected actions, payload included, to every subscriber of the channel.
VS Code 1.140 dispatches root/configChanged at every connect, so one
user's client configuration reached every other user. activeSessions
counted all users' sessions. Another user could also read a pending
session's snapshot or dispose it by URI.

Root notifications now go only to clients whose user may view the
session. A rejection reaches only the sender's own clients.
activeSessions counts the receiving user's sessions, and pending
sessions follow the same ownership rule as stored ones. A two-user
test covers each path; it fails without this change.

Refs: VIK-1661

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ADR-0006 replaces markup tiers with a flat 25% markup, sets Shift Budgets
per model tier from measured p90 cost, and adds the currency rule. The
Markup Tier term leaves the domain model. The drill-down lists every
decision and the tickets that carry them (VIK-1686, 1687, 1688).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Read against VS Code 1.140's client, the agent host completed the
handshake but could not run a session:

- VS Code names a session <provider>:/<uuid> and takes the provider
  from the scheme. The host accepted only ahp-session:/<id>.
- VS Code expects the default chat to exist when the session is
  created, at ahp-chat://default/<base64url(session)>. It never calls
  createChat for it.
- The first turn replaced the client's session id with the engine's,
  removing the URI the client had subscribed to.
- Root state advertised multipleChats and multipleWorkingDirectories,
  which the host then refused.
- Summaries exposed the host's workspace path as a working directory.

Sessions now keep the id the client chose. A persisted alias maps it to
the engine's session id, so the engine is unchanged and a restart keeps
the mapping. Channels are matched by kind and session id, so a client
receives envelopes on the spelling it subscribed with. The earlier
ahp-session:/ and ahp-chat:/ spellings are still accepted, while
summaries and new sessions use de-vloer:/. The default chat exists from
creation, the two capabilities are no longer advertised, and no host
path is sent. A pending session whose start fails stays pending, so the
user can send a better first message.

The end-to-end test now replays VS Code's sequence. New tests cover
channel parsing, the earlier spelling, and the id surviving a restart.

Refs: VIK-1662

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-v0.4.0-rc.24](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.23...glide-v0.4.0-rc.24) (2026-10-01)

### Added

* **deps:** update pnpm ( 11.8.0 ➔ 11.11.0 ) [security] ([cfd3112](cfd31122d8))

### Fixed

* **agent:** update opencode ( 1.18.30 ➔ 1.18.33 ) ([a2bbf4c](a2bbf4cd8c))
* **vloer:** read the probed OpenCode version from the agent image pin ([9eadab5](9eadab506e))

### Internal

* **release:** glide-site-v0.1.0-rc.2 [skip ci] ([41a95d5](41a95d58a8))
Reviewed-on: webgrip/glide#73
Reviewed-on: webgrip/glide#74
Reviewed-on: webgrip/glide#75
Regenerates the landscape explorer instead of hand-merging it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-site-v0.1.0-rc.3](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-site-v0.1.0-rc.2...glide-site-v0.1.0-rc.3) (2026-10-01)

### Added

* **deps:** update pnpm ( 11.8.0 ➔ 11.11.0 ) [security] ([cfd3112](cfd31122d8))
## [glide-v0.4.0-rc.24](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.23...glide-v0.4.0-rc.24) (2026-10-01)

### Added

* **deps:** update pnpm ( 11.8.0 ➔ 11.11.0 ) [security] ([cfd3112](cfd31122d8))

### Fixed

* **agent:** update opencode ( 1.18.30 ➔ 1.18.33 ) ([a2bbf4c](a2bbf4cd8c))
* **vloer:** keep each AHP client to its own user's sessions ([5ecd610](5ecd610e25))
* **vloer:** let VS Code 1.140 create and follow an AHP session ([78d4d6f](78d4d6fd29))
* **vloer:** read the probed OpenCode version from the agent image pin ([9eadab5](9eadab506e))

### Docs

* record the VS Code 1.140 fit and the shared-surface plan ([a77b19a](a77b19a6e5))

### Internal

* **release:** glide-site-v0.1.0-rc.2 [skip ci] ([41a95d5](41a95d58a8))
* **release:** glide-site-v0.1.0-rc.3 [skip ci] ([fff1611](fff16112f9))
Run 491 recomputed rc.24 because the published rc.24 release commit
(a90078c) was dropped from development by a stale-base merge of #73.
Its changelog listed #73-#75, which rc.24 does not contain.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The merge of #73 landed on f6c2719 and overwrote 5554efd, so tag
glide-v0.4.0-rc.24 became unreachable and the next release tried to
create it again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Harness adapters now report cache tokens, turns, durations, tool calls
by kind, peak context and per-model usage, and the outcome report and
operator API carry them as optional fields. Forge webhooks and the
review poller record pull request merge facts and every review with its
verdict in new pull_requests and pull_request_reviews tables (migration
0023), and the forge audit row keeps the actor and verdict.

ADR-0045 (proposed) records the decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#78
Reviewed-on: webgrip/glide#76
Reviewed-on: webgrip/glide#77
## [glide-v0.4.0-rc.24](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.23...glide-v0.4.0-rc.24) (2026-10-01)

### Added

* **deps:** update all non-major dependencies ([904208d](904208d3bf))
* **deps:** update pnpm ( 11.8.0 ➔ 11.11.0 ) [security] ([cfd3112](cfd31122d8))
* **ploeg:** keep every run usage figure and merge and review fact ([35d62fd](35d62fd196))

### Fixed

* **agent:** update opencode ( 1.18.30 ➔ 1.18.33 ) ([a2bbf4c](a2bbf4cd8c))
* **vloer:** keep each AHP client to its own user's sessions ([5ecd610](5ecd610e25))
* **vloer:** let VS Code 1.140 create and follow an AHP session ([78d4d6f](78d4d6fd29))
* **vloer:** read the probed OpenCode version from the agent image pin ([9eadab5](9eadab506e))

### Docs

* record the VS Code 1.140 fit and the shared-surface plan ([a77b19a](a77b19a6e5))

### Internal

* **release:** glide-site-v0.1.0-rc.2 [skip ci] ([41a95d5](41a95d58a8))
* **release:** glide-site-v0.1.0-rc.3 [skip ci] ([fff1611](fff16112f9))
* **release:** glide-v0.4.0-rc.24 [skip ci] ([95bdf58](95bdf584a0))
Run 503 recomputed rc.24 because the published tag was still unreachable
from development, and pushed 878869a before its tag step failed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#79
## [glide-v0.4.0-rc.25](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.24...glide-v0.4.0-rc.25) (2026-10-01)

### Added

* **deps:** update all non-major dependencies ([904208d](904208d3bf))
* **ploeg:** keep every run usage figure and merge and review fact ([35d62fd](35d62fd196))

### Fixed

* **release:** drop the duplicate rc.24 release commit ([479893c](479893c919)), references [#73](webgrip/glide#73) [73-#75](https://forgejo.webgrip.dev/73-/issues/75)
* **release:** drop the second duplicate rc.24 release commit ([72cf34d](72cf34d129))
* **vloer:** keep each AHP client to its own user's sessions ([5ecd610](5ecd610e25))
* **vloer:** let VS Code 1.140 create and follow an AHP session ([78d4d6f](78d4d6fd29))

### Docs

* record the VS Code 1.140 fit and the shared-surface plan ([a77b19a](a77b19a6e5))

### Internal

* **release:** glide-site-v0.1.0-rc.3 [skip ci] ([fff1611](fff16112f9))
* **release:** glide-v0.4.0-rc.24 [skip ci] ([878869a](878869a789))
* **release:** glide-v0.4.0-rc.24 [skip ci] ([95bdf58](95bdf584a0))
## [glide-site-v0.1.0-rc.4](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-site-v0.1.0-rc.3...glide-site-v0.1.0-rc.4) (2026-10-01)

### Added

* **deps:** update all non-major dependencies ([904208d](904208d3bf))
GET /api/v1/operator/work-items/{id}/card returns one card per Work
Item with its pull requests as plays, the crew, roster, steward
fallback, usage totals and a provenance timeline. Webhooks and the
review poller now capture diff stats and the combined CI status for
Ploeg pull requests (migration 0024), and Work Targets accept a
cardStyle with a skin and theme.

ADR-0046 (proposed) records the decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A <glide-card> Web Component renders the card Ploeg assembles for each
Work Item through first-party skin packs, starting with Vloer Native: a
front with state, cost, tokens, run time, diff, PR and CI, crew and
steward, and a more-info back with six tabs. Vloer proxies the card
route, hides the card when Ploeg has none, and the demo shows a demo
card without spend.

ADR-0026 (proposed) records the card runtime, skin packs and themes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#82
Reviewed-on: webgrip/glide#80
## [glide-v0.4.0-rc.26](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.25...glide-v0.4.0-rc.26) (2026-10-01)

### Added

* **vloer:** show a run card on the work item page ([ae05d26](ae05d26173))

### Fixed

* **deps:** update pnpm ( 11.28.1 ➔ 11.28.2 ) ([badcd13](badcd13e58))

### Internal

* **release:** glide-site-v0.1.0-rc.4 [skip ci] ([bfe04df](bfe04df4d3))
vsce 4 drops cheerio and keytar, which pulled in the deprecated
whatwg-encoding and prebuild-install. ovsx still asks for vsce 3, so an
override points it at the same copy; ovsx only calls vsce when it builds
the package itself, and the release passes --packagePath.

allowScripts denies the vsce-sign postinstall: it installs the signing
binary, and nothing here signs packages.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
POST /api/v1/deploys, behind a dedicated deploy token, records that a
commit is live in an environment and marks every merged Ploeg pull
request whose merge commit is an ancestor of it, using the forge's
compare API (migration 0025). Cards gain deployments per environment
and a release, falling back to the merge time while a repository has
never reported a deploy to its release environment. Work Targets accept
release.environment.

ADR-0047 (proposed) records the decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The card model reads deployments and release, counts days live and
picks the finish (matte, foil, holo, prism, gilded, infinity). Vloer
Native draws one restrained, pointer-lit layer per finish with a still
version under reduced motion, a day chip on the front and deployments
on the Life tab. Demo Work Items show every finish under the demo
label.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
wrangler 4.144.0 brings miniflare's undici to 7.29.1, which clears all
ten pnpm audit findings (two high). TypeScript stops at 6 because
@astrojs/check and typescript-eslint do not accept 7 yet. The engines
range gains the Node 24 upper bound Vloer has, and the release-age
excludes for two past security updates are gone with those versions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
mise already pins 24.21.0; the hardened base images were two patches
behind because Renovate never proposed dhi.io updates.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
go.mod gains toolchain go1.27.1, the version mise pins. The CI toolchain
fixture follows Go 1.27 with a digest, and ploegd builds on trixie and
runs on static-debian13 now that Debian 12 is on LTS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Renovate 43 holds back updates without a release timestamp, and only
Docker Hub publishes one, so Harbor and dhi.io images were detected but
never proposed. New managers cover the builder images in workflow with:
inputs, the agent image tag in the example configurations, the jsDelivr
imports in the landscape build and Ploeg's CI value files. Bounds keep
undici and brace-expansion on npm's bundled majors, @types/node on 24,
and the site on TypeScript 6.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The extension and Ploeg release jobs used setup-node and setup-go, which
run on node20 and resolved Node from an engines range and Go 1.26.0 from
go.mod. mise-action installs the versions mise pins. The release builder
and SBOM scanner digests are refreshed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v6 stores persisted credentials in a separate file included from the git
config. Release pushes authenticate with the token input, and the notes
fetch works on the public repository either way.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The warm-pool template and the live example configuration named
de-vloer-agent:0.3.0 long after 0.4 shipped. The Vloer prepare step now
rewrites both to the version being released, and the release commit
carries them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#83
Reviewed-on: webgrip/glide#84
## [glide-v0.4.0-rc.27](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.26...glide-v0.4.0-rc.27) (2026-10-01)

### Added

* **ploeg:** assemble a run card per work item from stored facts ([9ddaf41](9ddaf41e3e))
* **ploeg:** learn where a merged change is deployed ([94927a2](94927a2f23))
* **vloer:** show days live and the finish ladder on run cards ([40967f6](40967f6524))
marked 18 renders all nine landscape sources byte for byte as 15 did.
The domain generator told people to install PyYAML into the system
Python; the repository locks it through uv.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [glide-site-v0.1.0-rc.5](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-site-v0.1.0-rc.4...glide-site-v0.1.0-rc.5) (2026-10-01)

### Fixed

* **deps:** update pnpm ( 11.28.1 ➔ 11.28.2 ) ([badcd13](badcd13e58))
Reviewed-on: webgrip/glide#85
mise 2026.9.17 writes a root mise.lock on the first install, and that
lock pins openspec through an aube lock digest. The committed
apps/ploeg/mise.lock predates that format, so the root and Ploeg
configurations resolved openspec to two different install directories.
Each `mise install` treated the other's copy as missing and CI printed
`mise WARN missing: npm:@fission-ai/openspec@1.13.2`.

Commit the root lock and regenerate Ploeg's with the CI mise, so both
record the same aube digest and share one install. `min_version`
refuses an older mise, which would rewrite both lockfiles in its own
format.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The extension tests run the TypeScript sources as ES modules, while tsc
builds the same sources as CommonJS for VS Code, so the manifest cannot
declare a module type. Node then prints MODULE_TYPELESS_PACKAGE_JSON for
every test file and source module it re-parses, which is expected here.
The test run disables that one notice.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Forgejo jobs only succeed or fail, so warnings in a passing setup or
verification step went unnoticed. The verify action now saves the
output of its install, setup and verify steps, and its last step scans
it for npm, pnpm, mise, uv, Node, Python, Go, Actions and build-tool
warnings. A new `warnings` job fails with the list, without holding up
`checks`, releases or publication. scripts/ci-warnings-allow.json
accepts a warning only with a reason; it starts with Node's experimental
notice for stripTypeScriptTypes, which Vloer uses on purpose.

Run 514 lost the glide-v0.4.0-rc.26 release: semantic-release pushed
the tag, Forgejo's push handler inserted a tag-only row for the same
tag, and the release request failed with HTTP 500 (duplicate key
UQE_release_n). The tag and release commit stayed, the release did not,
so nothing was published. When Semantic Release fails, the release and
site-release jobs now create the missing release for the tag at HEAD,
using the release commit's body as notes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The tutorial-smoke assertion hard-coded the actions/checkout v5.1.0
digest. The move to checkout v7 updated every workflow but not this
test, so the release-policy step, which runs it in every pull request
and release job, failed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#86
Reviewed-on: webgrip/glide#87
A `#work/<id>?run=<runId>` link opens that Work Item with the Run card
open, scrolled into view and focused, and each Run card offers a "Copy
link to this Run" button. When `observability.grafanaUrl` is set, a Run
with a key alias links the run explorer and the Work Item header links
the Team loop dashboard; the dashboard uids are constants in
core/observability.js, so no new setting is added. In live mode with
Ploeg configured and an https baseUrl, Vloer announces its own URL with
PUT /api/v1/operator/consumer, retrying with backoff (1 minute capped at
1 hour) until 204, and logs a 404 from an older Ploeg once.

VIK-1638
Agent-Trace-Id: ploeg-72dc948453b9
A notice from a previous Run link survived when the next navigation was
to a different Work Item or the list, so it showed against unrelated
work. Clear it on every enterWork, before the new Run is revealed.

VIK-1638
Agent-Trace-Id: ploeg-72dc948453b9
## [glide-v0.4.0-rc.28](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.27...glide-v0.4.0-rc.28) (2026-10-01)

### Added

* **deps:** update all non-major dependencies ([df62371](df623712e2))

### Fixed

* **build:** lock openspec once so mise stops reinstalling it ([a4aeb47](a4aeb4721b))
* **vloer:** run the extension tests without Node's module-type notice ([770ae9e](770ae9e5fb))

### Build

* **ploeg:** name the Go toolchain and move images to Debian 13 ([fe9660f](fe9660f9c2))
* **release:** keep the released agent image references on the release version ([eb1b8e2](eb1b8e28d6))
* **site:** clear the wrangler advisories and move to TypeScript 6 and pnpm 12 ([d3ab4a1](d3ab4a104e))
* **vloer:** build both images on Node 24.21.0 ([e5b40b8](e5b40b83fe))
* **vloer:** package the extension with vsce 4 ([b9323a3](b9323a3593))

### Internal

* load marked 18 in the landscape build and point the domain generator at uv ([03f11ce](03f11ce983))
* **release:** glide-site-v0.1.0-rc.5 [skip ci] ([2dffb21](2dffb215d1))
A Run card read "Not reported" for cost, tokens and run time until every
Run finished, so a busy Run looked broken. While a Run is running, the card
endpoint now returns a live block: the finished Runs' figures plus the
gateway's spend-log total so far for each running Run, and run time up to
now. The read records nothing and shares a 3 s deadline; when the gateway
cannot be read, cost and tokens stay absent (ADR-0049).

VIK-1692

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The proxy passes Ploeg's live card block through validated. While it is
set, the card shows cost against the authorized budget, tokens and run
time as "so far", and a figure Ploeg could not read as "Not reported yet".
An older Ploeg without live keeps today's card.

VIK-1692

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#89
The grouped Renovate update moved agent-runner to 1.3.0 and dind to
29.8.2 in the chart values; the generated reference still named the old
images, which fails docs-check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
requires-python allowed anything from 3.11, so uv took whatever
interpreter it found: 3.12 on the CI runner, 3.13 on a laptop. 3.12 is
what the runner image ships, so CI downloads nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The site is a static-assets Worker with no script, and 2026-09-26 is the
newest date the pinned workerd runtime supports.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#90
v1.12.2 of webgrip/renovate-config carries the timestamp-optional rule
for registries other than Docker Hub, so the local copy goes. A chart
image bump also changes the configuration reference, which lists the
chart's default images; helm-golden.sh update now regenerates it, and
Renovate commits it with the goldens. The allowed post-upgrade command
is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#91
Run 538 hung for 40 minutes: a gate failed, verify sent SIGTERM to the
other gates, and pnpm 12.8.1 caught it, stopped tsc, and kept running
while holding the site gate's stdout. verify waited for that stream to
close, so it never printed the report naming the failed gate.

A stopped gate now gets SIGKILL after a grace period, and a gate whose
command has exited is collected after the same grace period even if a
descendant still holds its output open; that gate fails as orphaned.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#93
Reviewed-on: webgrip/glide#92
v1.13.0 merges later preset bumps on green without a dashboard tick, so this
is the last one that needs a hand. It also proposes updates from
registries that report no release timestamp (Harbor, dhi.io, ghcr.io,
quay.io).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#94
The extension now ships public/core/states.js, format.js and reasons.js,
copied at compile time, so the editor names every state, reason and amount
as the browser does. It contributes one theme colour per status tone for
tree icons and webviews, and reads Now, a Work Item and its card through
the client. Browser links open #work instead of the legacy #ploeg route.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Now lists what waits on you across every Team (ready for review, needs
you with its reason, proposed) and what runs, from GET /api/ploeg/now.
The badge and status bar count review and needs-you work, not stale
failed sessions. Tasks rows show the state Ploeg holds them in, Ploeg
becomes Work with shared labels, and Sessions moves last, collapsed.
Records the decision as proposed ADR-0027.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The panel reads the Work Item and its card: a head with state, reason,
one primary action, cost marked observed and not settled with the cost
per role, then pull requests with CI and reviews, the writer's account,
Runs by Round and the brief. It opens for Work Items without a tracker
task, never overflows sideways, and filters empty team roles.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#95
The 2026-09-30 BuildKit pin from b4d6845 gives each attestation manifest
a subject, so Harbor lists them as referrers of the platform images.
cosign copy then writes the referrer index for a platform image under
the same sha256-<digest> tag where it already wrote the platform image
itself. Forgejo's registry has no Referrers API, so the copy failed with
"fallback tag manifest is not an OCI image index" and Ploeg rc.28 never
reached the Forgejo and GitHub mirrors. Vloer builds with the shared
action's default BuildKit and was not affected.

Restore the 2026-07-15 digest and keep Renovate off it until the copy no
longer walks referrers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#96
## [glide-v0.4.0-rc.29](https://forgejo.webgrip.dev/webgrip/glide/compare/glide-v0.4.0-rc.28...glide-v0.4.0-rc.29) (2026-10-01)

### Added

* **ploeg:** report a running Run's usage so far on its card ([ba7027e](ba7027e5bc))
* **vloer:** give the editor the browser's vocabulary, formatter and status tones ([6477888](6477888405)), references [#work](https://forgejo.webgrip.dev/webgrip/glide/issues/work) [#ploeg](https://forgejo.webgrip.dev/webgrip/glide/issues/ploeg)
* **vloer:** lead the editor's Work Item panel with state, reason and next action ([1a4d6b8](1a4d6b8aa3))
* **vloer:** name a tracker token without write access when a hand-off fails ([ada2ecd](ada2ecd05e))
* **vloer:** open the editor sidebar on Now ([9dc87ff](9dc87ff2fa))
* **vloer:** show cost, tokens and run time so far on a running Run's card ([d33fc71](d33fc715f8))

### Docs

* **ploeg:** regenerate the configuration reference for the new runner and dind images ([5718bbe](5718bbe50e))
* **vloer:** list the editor redesign's follow-ups in ADR-0027 ([799a08c](799a08ccb1))

### Build

* **site:** move the Worker compatibility date to 2026-09-26 ([a90527e](a90527e1a8))

### Internal

* **renovate:** extend the shared preset at v1.12.2 and regenerate Ploeg's configuration reference on chart bumps ([fb1c713](fb1c713714))
Prose, docs, agent instructions, UI text, Vloer's card component, internal
variables, the site and the release tag format now say Unfold. Releases
are tagged unfold-v and unfold-site-v.

Names outside this repository keep their current spelling until they are
renamed in a coordinated step: the webgrip/glide repository address, the
GLIDE_RELEASES_ENABLED and GLIDE_DOCS_PUBLISH_ENABLED variables, the
repo/glide label, the docs bucket and docs.webgrip.dev/glide. Dated records
(research, evidence, changelogs, published tags, archived OpenSpec changes)
keep the name they were written under.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#99
cosign copy failed against Forgejo's registry once BuildKit gave
attestation manifests a subject. Forgejo has no Referrers API, so
cosign falls back to the tag sha256-<platform digest>, but it had
already written the platform image itself to that tag, and the copy
stopped with "fallback tag manifest is not an OCI image index"
(glide-v0.4.0-rc.28).

`regctl image copy --referrers --digest-tags` copies the index, its
platform images and attestation manifests, their referrers and
cosign's .sig and .att tags in one step. On a registry without the
Referrers API it writes the OCI fallback index under sha256-<digest>,
so nothing collides. It replaces both `docker buildx imagetools
create` and `cosign copy`. An existing destination version must still
match, and cosign still verifies the signature and CycloneDX
attestation at every destination.

Tested against a local Forgejo 15.0.2, the version that runs
forgejo.webgrip.dev, with Ploeg rc.28 from Harbor: cosign copy 2.6.4
reproduces the failure, while regctl 0.11.6 copies with the index digest
unchanged, cosign verify and verify-attestation pass at the
destination, a rerun is a no-op, Vloer rc.28 copies as well, and
credentials come through the Docker credential helper as in CI.

regctl is pinned in the root mise.toml and installed by both mirror
jobs. Ploeg's release build returns to the 2026-09-30 BuildKit, and
the Renovate rule that held it back goes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Work Item page gets a Check out branch dialog: Open in VS Code (a
vscode://webgrip.de-vloer/checkout link) or a git command to copy. The
extension gets a Check Out Branch command in the Work Item panel, on Now
and Work rows, and behind that link. It reads the Work Item from the
workbench, finds the open clone of the target owner/repo, fetches the
branch and switches to it through VS Code's Git extension.

Both surfaces read the branch through public/core/checkout.js, which
offers only branch names that need no shell quoting. Demo Work Items
offer nothing to check out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#98
Reviewed-on: webgrip/glide#100
The rename commit was re-created on a base that already held the
glide-v0.4.0-rc.29 release commit, from a working tree that predated it, so
it set the chart, manifest and extension versions back to rc.28 and dropped
the rc.29 release notes. This re-applies that release commit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The checkout test from #100 was written before the rename and expects the
branch glide/42-explain, while its fixture now names unfold/42-explain.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#101
The Linked Tasks tree only learned Ploeg state from Now's waiting list
(awaiting review, needs human, proposed), so a task handed to Ploeg kept
its plain icon until a pull request was ready. Now also returns an
`active` list of leased and queued Work Items, the tree overlays it, and
a hand-off or take-back from the task panel refreshes the tree at once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#102
## [unfold-v0.4.0-rc.30](https://forgejo.webgrip.dev/webgrip/glide/compare/unfold-v0.4.0-rc.29...unfold-v0.4.0-rc.30) (2026-10-01)

### Added

* **vloer:** check out a Work Item's branch from the browser or VS Code ([b7be09a](b7be09a4ba))

### Fixed

* **vloer:** show queued and running Ploeg work in Linked Tasks ([e85a4f8](e85a4f82d9))

### Changed

* rename the product from Glide to Unfold ([b627d5f](b627d5f531))

### Tests

* **vloer:** match the checkout title to the renamed fixture branch ([bf2e225](bf2e225cc3)), references [#100](webgrip/glide#100)

### Internal

* **release:** restore the rc.29 release metadata that the rename reverted ([6a522a8](6a522a8520))
## [unfold-site-v0.1.0-rc.6](https://forgejo.webgrip.dev/webgrip/glide/compare/unfold-site-v0.1.0-rc.5...unfold-site-v0.1.0-rc.6) (2026-10-01)

### Changed

* rename the product from Glide to Unfold ([b627d5f](b627d5f531))

### Build

* **site:** clear the wrangler advisories and move to TypeScript 6 and pnpm 12 ([d3ab4a1](d3ab4a104e))
* **site:** move the Worker compatibility date to 2026-09-26 ([a90527e](a90527e1a8))
Run 573 failed the warnings job on "[WARN] Tarball download average
speed 13 KiB/s (size 14 KiB) is below 50 KiB/s". The notice measures the
runner's link to the npm registry, not the repository, and on small
tarballs it mostly reflects request latency.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#103
Keep both header tools (Grafana and Check out branch) and both imports in
ploeg.js. now.js uses the shared grafanaTeam from core/observability.js.
The loop dashboard uid stays glide-loop, the uid deployed in
homelab-cluster, so the Now view test expects glide-loop again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The rename turned the Grafana uid glide-loop into unfold-loop in the
configuration reference, which names a dashboard that does not exist. The
uid stays glide-loop; the dashboard's title is now Unfold — Loop, and the
pull request report's link text says so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#104
The name and the Vouwvlieger mark get the same answer as Ploeg's and
Vloer's: a usage policy in docs/brand/TRADEMARK.md, no CC licence and no
second copyright answer. The record stays proposed until a trademark
search is done, because Squarespace lists "Unfold" among its trademarks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Baken replaces the placeholder teal: Baken Diep for accent text, links
and buttons on Vel, Baken Nacht for all of them on Zwerk, with Zwerk
text on dark buttons. Raised surfaces in dark mode stay Zwerk so the
accent keeps 4.5:1.

The header and footer show the horizontal lockup, and the favicon uses
the favicon cut and switches to the night colours in a dark browser.
Mark, Wordmark and Lockup draw from src/brand/geometry.json, written by
the root brand generator, so the site and docs/brand share one source.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
scripts/build-brand.mjs ports the brand book's mark generator: two
filleted triangles with a constant crease, 2.75 units on the master and
4.6 on the favicon cut, centred by bounding box. It writes docs/brand/
and the site's brand geometry, and the wordmark is outlined from the
Archivo instance the site ships, as Vloer's is.

--check, run by mise run verify in a new brand group, fails on a stale
file, overlapping wings, a crease that is not the design gap, an
off-centre cut, a site colour that departs from the palette, or a
missing trademark policy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: webgrip/glide#105
## [unfold-v0.4.0-rc.31](https://forgejo.webgrip.dev/webgrip/glide/compare/unfold-v0.4.0-rc.30...unfold-v0.4.0-rc.31) (2026-10-01)

### Added

* **site:** apply the Unfold brand ([b3dc446](b3dc44634b))

### Fixed

* **ploeg:** link the Loop dashboard by its real uid and new title ([f03a536](f03a5368f3))

### Internal

* **release:** unfold-site-v0.1.0-rc.6 [skip ci] ([bf72bf1](bf72bf1a2d))
Adds dated research records for trading-card design, holo and game
feel, gamification evidence and Dutch and EU law, game theory and the
card metrics catalogue; a Run Cards concept page that separates what is
built from what is proposed; a works council and DPIA pack with a Dutch
and English consent request under WOR art. 27; and the Cards context in
the domain model with its glossary terms and rules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Run cards gain a versioned grade (formula 2026.1): four subgrades,
reliability, durability, delivery and review, combined into a 1-10
overall in half steps, provisional until 180 days live, with labels,
qualifiers and the inputs each subgrade used. Tracker projects can map
statuses to development, test, acceptance and done gates; Ploeg records
gate moves from Vikunja and ClickUp webhooks (migration 0026), detects
bounces with their reasons, and cards show gate history, bounces, right
first time, evolved and qa and acceptor roster roles.

ADR-0050 and ADR-0051 (proposed) record the decisions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
## [unfold-site-v0.1.0-rc.7](https://forgejo.webgrip.dev/webgrip/glide/compare/unfold-site-v0.1.0-rc.6...unfold-site-v0.1.0-rc.7) (2026-10-01)

### Added

* **site:** apply the Unfold brand ([b3dc446](b3dc44634b))
A Work Item page opened on a small "Ready for review" chip above the
problem/solution card, with the Run card as a third-width section beside
empty space, an overflowing cost ring and a truncated Tokens tile.

The Run card now heads the detail across the full content width, states
what happened in one headline (state, verdict, pull request, Rounds) and
carries the primary action. The separate "Run card" section and caption
are gone; the review box keeps only the checks and forge outcomes, folds
the neutral checks into one muted line ("2 not reported: CI, tracker
link") and closes "On the forge". The vloer-native skin holds the ring
amount and "of" amount on two lines and reads "Not reported yet" in
full. An empty lane's list column collapses when an item is open.

VIK-1697
Agent-Trace-Id: ploeg-964232a0f888
Reviewed-on: webgrip/glide#106
Reviewed-on: webgrip/glide#108
Reviewed-on: webgrip/glide#107
## [unfold-v0.4.0-rc.32](https://forgejo.webgrip.dev/webgrip/glide/compare/unfold-v0.4.0-rc.31...unfold-v0.4.0-rc.32) (2026-10-01)

### Added

* **ploeg:** grade run cards and record delivery gates ([f24a4cd](f24a4cdab1))
* **vloer:** make the Run card the head of the Work Item page ([bd35bb3](bd35bb3113))

### Docs

* record the run cards research, concept and works council pack ([df6a47a](df6a47a9f5))

### Internal

* **release:** unfold-site-v0.1.0-rc.7 [skip ci] ([665dc63](665dc6382e))
Ploeg proposes the cards a bug may have come from by file overlap with
earlier merged plays; the fixer names the cause, a second person who is
neither the steward nor the proposer confirms, the steward can dispute
within five working days and an uninvolved referee settles it. Cracks
carry severity, share, discovery and warranty weight; fix plays mend
them and a sweep confirms mends after thirty days without a re-crack.
Reverts and hotfixes are detected from merged pull requests. Cards gain
a condition, a cosigner role and grade formula 2026.2 with the
reliability and durability inputs filled (migration 0027).

ADR-0052 (proposed) records the decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vikunja task relations and ClickUp parents tell Ploeg which Work Items
belong to an epic, counting a child only when the relation existed
before its first Shift (migration 0028). Cards gain a set with the
epic, position and size; an epic's own card lists its children and is
complete when every child is merged, has thirty days live and carries
no unmended crack.

ADR-0053 (proposed) records the decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A forge skin pack draws the Run card as a 3D card with vendored
three.js 0.165 (MIT, imports rewritten for the CSP, drift-checked): an
extruded body with rarity-tinted metal edges, relief from a painted
height map, sixteen shader foils whose coverage grows with days live,
twelve moving art presets, a grading slab, cracks and gold kintsugi,
and a 3D back. One live renderer per page, still frames elsewhere and
under reduced motion, rendering paused off screen, and a fall back to
Vloer Native without WebGL2. The proxy validates grade and condition,
and six demo Work Items show every finish.

ADR-0028 (proposed) records the decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bd35bb3 made the Run card the head of the Work Item page but left the
browser checks pointing at the old layout: a non-exact heading match now
hits the card headline, the phone checks looked for the primary action in
the review box, and the new review-box assertions ran against item 109.
Match the review heading exactly, check the card's actions and folded CI
line for 105, and keep 109's order checks with its real headline.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The forge card now spans the content width of the new page head: a full
width stage capped at 34rem tall with the card centred, the placeholder
sized by height, and the forge browser check finds the card edge and the
off-screen pause without assuming a narrow stage.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GET /api/v1/operator/cards returns full cards whose roster or steward
names any of the given logins, team-scoped, newest activity first
(minted, play opened or merged, released, crack confirmed or mended),
with a since filter and an opaque cursor, at most 50 per page. New
lowercased login indexes keep the candidate query indexed (migration
0029).

ADR-0054 (proposed) records the decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
semantic-release-monorepo 0.3.4 bakes @webgrip/semantic-release-config 1.3.3,
which replaces the archived @saithodev/semantic-release-gitea with a publish
step that adopts an existing release and retries 409/429/5xx. That closes
the race behind run 514, where POST /releases answered 500 on
UQE_release_n after glide-v0.4.0-rc.26 was pushed and the release page was
never created. Bumps all four workflow pins and the release-check task.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When a team opts in with cards.prComment, Ploeg posts and then edits a
single comment on the merged pull request whenever the card reaches a
moment worth showing: merged, released to production, a new finish, a
confirmed mend. The comment carries the card as a deterministic,
escaped SVG rendered in Go, uploaded as a comment attachment, above a
short summary table, and names only the steward (migration 0030).

ADR-0055 (proposed) records the decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Run cards show the delivery gates with bounces and right first time,
cracks and mends, evolved, and an epic's set, on the front and in new
Gates, Grade, Condition and Set tabs. A bug's Work Item gets a Trace
this bug panel: Ploeg's candidates with their shared files and the
attributions on the bug, with propose, confirm, dispute, resolve and
requirement-changed steps shown only to the people Ploeg's rules allow.
Attribution actors come only from admin-mapped forge logins.

ADR-0030 (proposed) records the decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The binder (#binder) shows a person's own copies of Run cards, newest
moment first, with the role from the roster, still forge thumbnails from
one shared renderer, a focused 3D card and personal readouts only.

Packs (#packs) hold the cards with a moment in a period: an ISO week, or
a Team's sprint from Vloer configuration. They seal when the period
ends, open in order and never expire. Each new card gets one seeded
cosmetic pull, HMAC-SHA256 of user, Work Item and pack under a server
key, drawn from the published odds table 2026.1 (#packs/odds) and
stored with its digest, so it is fixed and auditable. Nothing can be
bought, re-rolled or traded.

The rip ceremony tears a 3D foil pack, deals the cards face down and
reveals each with anticipation scaled by the pull alone, a foil wipe,
particles and bloom; it honours reduced motion, can be skipped after
300 ms and keeps sound off by default. "While you were away" replays
card moments since the last visit once. The season page (#season) shows
quarterly Team totals and names nobody. Card logins (#settings/cards)
only find cards to collect; attribution stays with the administrator's
ploeg.forgeLogins mapping.

ADR-0029 (proposed) records the decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Five DOM skin packs (Holo Rarity, Loot Drop, Arcade Cabinet, Ticker
Terminal, Mission Patch) that extend vloer-native and reuse its back.
A shared skin kit gives them pointer light and tilt, an idle budget of
three cards that pauses off screen, on the back and under reduced
motion, and moment detection that marks the card with data-moment and
fires `unfold-card-moment` on <unfold-card>. Each skin draws the finish
ladder one layer per step, cracks and gold kintsugi, a grade slab, the
delivery gates, and a set strip or its own Set Card layout. No rarity;
CSP-safe (no inline styles, no remote assets). Demo cards per skin.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An effects director plays news on a Run card once per person, by tier
from the moment's kind and facts (never rarity): merged, released, a
finish step, a confirmed crack, a mend, a grade change, a completed
set. Ceremonies keep to accessibility rules: at most one card-local
flash, never red, no more than three a second page-wide, coalesced
repeats, one takeover per ten minutes and never while typing, skippable
after 300 ms, and a Card motion preference (Automatic, Full, Calm, Off)
with sound off by default and banks a theme can pick. Every skin emits
moments through the skin kit; per-card seen marks keep a reload from
replaying. The pack rip and the binder's replay run through it, and the
pack scene's sparks now render in colour.

ADR-0032 (proposed) records the decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: #109
Reviewed-on: #111
Reviewed-on: #113
Reviewed-on: #110
Reviewed-on: #114
Reviewed-on: #116
Reviewed-on: #117
Reviewed-on: #118
The repository rename to webgrip/unfold left the Actions cache empty, and
run 624 compiled Ploeg's httpapi tests cold under CPU contention: the
managed qualification was killed at 240 s before its first test finished.
Compile the test binary first with its own 900 s limit, then time only
the qualification run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: #120
Reviewed-on: #119
Themes v1 let a project or client restyle a skin through allow-listed
tokens, frames, foil patterns, art (preset, compiled shader or uploaded
media), a sanitized SVG set symbol and a card back, stored with versions
in Vloer's store or read from a mounted folder and applied without
inline styles. Admins design themes at #settings/cards with a live 3D
preview; optional generated shader art calls a configured gateway key,
is compiled in the browser with up to two retries on the compiler log,
and is stored only after it compiles.

ADR-0031 (proposed) records the decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: #115
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A card's rarity is how exceptional its change was: a versioned challenge
score from reach, sensitive paths, novelty and damped size, predicted from
the first Run and revealed at release. Tiers come from the score's
percentile in its repository-and-quarter cohort, with fixed thresholds
under 30 cards, and are frozen once revealed. An epic's card is legendary
while its set is complete. Ploeg now records lines per changed file so
size can leave lockfiles and generated files out (migration 0031).

Records the decision in ADR-0056.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
index.html, styles.css, the web manifest, the card runtime stylesheet,
the skin pack base and the notification icon now resolve against the
page or their module instead of the server root. The product still
serves the UI at /, so every URL resolves to the same file; the same
files can now also run under a sub-path such as the site's /demo/.
API paths stay root-absolute.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ploeg-demo.ts exports the minute its illustrative records are anchored
on, and DemoRuntime accepts an optional pace hook that replaces its
pause between steps. Neither changes what the demo executes; a recorder
or test uses them to decide when each step runs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
scripts/record-replay.ts runs the demo in-process on a fixed clock,
records every view the UI reads and the demo session with one snapshot
per event plus each review decision, and writes the replay with a
manifest of public/ hashes. --check re-records and fails on masked
drift, hash drift or an /api/ path the replay neither answers nor
refuses. public/replay/ holds the shim the hosted /demo page loads
before app.js; the product never serves that directory.
replay-conformance.mjs drives the real UI against the replay in
Chromium.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
mise run verify gains a demo-replay group that runs Vloer's
replay:check, and the site group's cache key now includes
apps/vloer/public because the site build copies it into /demo.
mise run demo-record regenerates the recording, and
mise run demo-replay-conformance drives it in Chromium (opt-in).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ADR-0015 proposes that the site's /demo is a recorded replay of Vloer's
deterministic demo with Vloer's unchanged UI. The local demo guide, the
CI guide and the README describe the replay, its gate and
mise run demo-record.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
pnpm build first runs scripts/build-demo.mjs: it copies apps/vloer/public
and the recording in replay/ into the ignored public/demo/, and writes
an index.html with its own meta CSP, noindex and a banner outside #app
that names the Vloer commit and recording date. The build fails when
Vloer's public files no longer match the recording's manifest. The
closing call to action links to /demo/ in both locales, and the deploy
guide explains why a Vloer UI change needs a site-scoped re-recording.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CI checks last_verified against the UTC date, which is still
2026-10-01 while the recording was made.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#119 moved three to 0.186.1 in package.json without re-vendoring
public/vendor/three, so Vloer's tests, check and licence gates failed on
development. The forge skin is built and tested against the vendored
0.165.0 (Vloer ADR-0028); an upgrade goes through npm run vendor:three
and a visual check, so Renovate no longer proposes it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The hero sends the Vouwvlieger from a Work Item to a pull request in CSS
alone. How it works plays a walkthrough recorded from Vloer's deterministic
demo and a scripted recording of the same demo in Vloer, with a slot for
the full replay at /demo. Pricing shows the planned model without amounts.

The sign-up form posts to a small Worker that stores email, interest and
consent in D1 by lowercase email, with no IP address, a honeypot, a plain
POST path and an inline fetch path. Privacy, thanks and problem pages in
English and Dutch. A release build refuses to deploy until the EU D1
database id is set in wrangler.toml.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The /demo replay (#122) is a static, English-only page under public/demo
whose committed recording is apps/site/replay/manifest.json, not an Astro
page, so the slot now switches on that file and always links /demo/. The
replay PR took ADR-0015, so the sign-up decision becomes ADR-0016.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: #123
Reviewed-on: #125
Renovate's #119 bumped three in package.json but not public/vendor/three/,
so the pin test, npm run check and the licence check failed on
development. three.js 0.171 dropped the single minified build: the module
build now imports three.core.js, so both are vendored and their imports
rewritten, and three/addons/ specifiers resolve to examples/jsm/. The
vendor script reads the version from package.json, so a future bump only
needs npm run vendor:three.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: #121
Read Ploeg's proposed card.rarity (Ploeg PR #121, ADR-0056) strictly but
additively, model it in the card view, and show it on every skin: frame
metal (steel, bronze, silver, gold, prismatic), a set symbol (black,
silver, gold, mythic orange, iridescent) and the tier's word, with a
ghosted glow while predicted. The forge colours only its frame band
through new front-shader uniforms. A Rarity tab explains the score,
rank, formula and components apart from grade and finish. The reveal
plays once through the effects director, sized by the revealed tier and
without a loss cue for a reveal below its prediction. The binder sorts
and filters by rarity and pack reveals name the tier; odds are unchanged.
Demo cards carry deterministic, illustrative rarity. Vloer ADR 0034.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The EU D1 database unfold-site-signups exists now, so the SIGNUPS binding
names it and the site's release build no longer stops on the placeholder.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: #88
Reviewed-on: #126
Reviewed-on: #127
Both changes edited the release smoke paths, the site build, verify, mise
tasks, the ADR index and the home page. The release smoke checks now cover
the privacy pages and /demo; the build runs the release check and then
assembles /demo; the landing page's replay slot replaces the old closing
link, so the cta.replay key goes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: #122
Reviewed-on: #128
Reviewed-on: #124
Two fixes for #119 crossed: #126 re-vendored three 0.186.1, and 7baa63a
(in #122) pinned package.json back to 0.165.0. Together they leave
package.json at 0.165.0 and public/vendor/three/ at 0.186.1, so the
vendor drift check, the pin test and the licence check fail again on
development. Pin 0.186.1, the copy the forge, rarity and inner-world
browser flows were checked against, and keep 7baa63a's Renovate rule so
upgrades stay manual. The docs that still named 0.165.0 now say 0.186.1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The forge card's art window can now hold a small three.js world (sky
islands, a deep sea or a neon city) that renders into a texture with
the card's own renderer, so the card's tilt looks into it like a
window. It flattens into a posterized picture and springs back (button
or F), its things react to a click or the keyboard, and the effects
director's reveal, crack and mend reach it.

What it holds follows the card's facts, never invented progress: the
time of day follows days live (dawn before release, night with an
aurora after a year), merging unlocks the windmill, 180 days live the
lighthouse, a mend the koi pond, and a crack draws a fissure that turns
to gold when mended.

A person who holds a copy can decorate their own copy. Decorations live
in Vloer's store per person and Work Item behind GET/PUT/DELETE
/api/cards/:id/world, need a sign-in, the request header, a card in
their Teams and a copy, are validated strictly and re-checked against
the card's facts on the server, are shown only to their owner and never
touch grades, rarity, pulls or odds.

Themes gain an optional world (existing themes keep their art; a forge
card without a theme shows the islands) and the designer exposes it.
One world per page, only on screen, sized to the art window, freed with
the card; reduced motion draws it still. ADR 0033 records the decision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The hosted replay (#122) neither answered nor refused
/api/cards/:id/world, so replay:check failed. The replay now refuses it,
a replayed forge card shows its theme's world, and the recording covers
the inner world's public files.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: #134
Every tracker status move on a board with gates or statusKinds is now
recorded in status_transitions (migration 0032), mapped to a gate or not,
with the tracker's time or the receive time marked observed, and no actor.
Ploeg also keeps the tracker's creation time and ClickUp's time estimate.

The card gains an optional flow object (cardFlow): time in every status,
per gate and per kind, lead, cycle and start time, flow efficiency, blocked
time, reopens, queue and agent time, first Run to first pull request, merge
to each environment and to production, and time from a confirmed crack to
its mend. Each human-timeline duration also has working seconds under the
team's calendar (default Mon-Fri 09:00-17:00 Europe/Amsterdam, configurable
with teams.<team>.workingHours). Status kinds default from gate and name and
are overridden per board with statusKinds. Flow never feeds the grade or
the rarity and is never totalled per person.

ADR-0057 records the decision; a how-to covers status kinds and working
hours, and the run cards concept, contracts README, configuration reference
and works council pack are updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each play now carries a timeline (opened, ready, first feedback from a
human other than the author, first and last approval, merge, review
rounds, comments, reviewers, response to a request for changes, commits,
force pushes and coding time), CI timing (runs, failures, reruns, queue,
last green, time to green, job minutes, the slowest jobs and first-pass
green) and, once merged, a change shape (indentation complexity after
Hindle, Godfrey and Holt 2008, counted lines, test ratio, docs touched and
languages). The card sums them up in pipeline and shape.

Ploeg reads the forge's activity and CI history at the existing capture
points: in the background at a merge or close, and at most once per ten
minutes per open pull request, four reads at once, each bounded in pages
and time. Forgejo's job timings come from the commit status history,
since Forgejo 15 has no jobs endpoint; GitLab's from pipeline jobs. The
diff is read once at the merge, at most 1 MiB, and only its numbers are
kept. Comment text, code and CI logs are never stored. Derived figures
are stored per play (migration 0033), so a card read costs no extra
query. A Work Target's cardShape sets its test and docs paths.

The figures never feed the grade or the rarity, both formulas unchanged,
and are never totalled per person. ADR-0058 records the decision; a
how-to covers the test and docs paths, and the run cards concept,
contracts README, configuration reference and works council pack are
updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CI compares last_verified with the UTC date, so a page verified after
midnight in the Netherlands failed as "in the future" for up to two hours:
docs/concepts/run-cards.md, dated 2026-10-02, broke every docs run on
development from 23:15 UTC. A date may now be at most one day ahead of the
UTC date; anything later is still rejected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Reviewed-on: #129
Reviewed-on: #130
Reviewed-on: #133
Reviewed-on: #136
fix(deps): update dependency @types/node ( 24.19.0 ➔ 24.19.1 )
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
4c72d18c79
Merge pull request 'fix(deps): update dependency @types/node ( 24.19.0 ➔ 24.19.1 )' (#137) from renovate/all-non-major into development
Some checks failed
[Workflow] On Source Change / checks (push) Failing after 1m17s
[Workflow] On Source Change / warnings (push) Successful in 1s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
a5ec847809
Reviewed-on: #137
Describe the second estate without naming it, drop links into its
internal GitLab, trim the openspec downstream-consumer tasks to one
line, and delete the inert per-app .mailmap files.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The history rewrite gave Ploeg's last rc.7 commit a new ID, and
github.com/webgrip/ploeg was rebuilt on it. Export new module versions
on 05b93bb4 so they share that history, and link the migration note to
the commit where it now lives.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs: point commit references at the rewritten history
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Successful in 8m54s
[Workflow] On Pull Request / release-policy (pull_request) Failing after 11s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
2bb2e6b359
The history rewrite gave every commit a new ID. Map the 1,035 full IDs
in changelogs, research records and release scripts to their new
values, and refresh the import manifest's notes refs and the checksums
of the files the rewrite changed, so verify-import.py checks the
history that exists.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'chore: follow up the history rewrite' (#138) from ryangr0/chore/neutral-fixtures into development
All checks were successful
[Workflow] On Docs Change / authorize-publication (push) Successful in 0s
[Workflow] On Docs Change / techdocs (push) Successful in 1m4s
[Workflow] On Docs Change / generate-documentation (push) Successful in 0s
[Workflow] On Docs Change / Build (Zensical) + sync to Garage web (push) Successful in 38s
[Workflow] On Docs Change / deploy-docs-site (push) Successful in 0s
[Workflow] On Docs Change / verify-publication (push) Has been skipped
[Workflow] On Source Change / checks (push) Successful in 3m55s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
ce6e2db587
Reviewed-on: #138
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs(research): keep the code quality review's finding details
Some checks failed
[Workflow] On Pull Request / release-policy (pull_request) Failing after 24s
[Workflow] On Pull Request / checks (pull_request) Failing after 2m26s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
fea37a561a
The review's PDF and evidence bundle were deleted. Record, for each
open finding, where it lives, what goes wrong, the proposed fix and the
done-when test, each location re-checked on development.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'docs(research): record the 2026-10-02 code quality review' (#139) from ryangr0/docs/code-quality-review-2026-10-02 into development
All checks were successful
[Workflow] On Docs Change / authorize-publication (push) Successful in 1s
[Workflow] On Docs Change / techdocs (push) Successful in 1m3s
[Workflow] On Docs Change / generate-documentation (push) Successful in 0s
[Workflow] On Docs Change / Build (Zensical) + sync to Garage web (push) Successful in 39s
[Workflow] On Docs Change / deploy-docs-site (push) Successful in 0s
[Workflow] On Docs Change / verify-publication (push) Has been skipped
[Workflow] On Source Change / checks (push) Successful in 3m27s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
dde553514d
Reviewed-on: #139
chore(renovate): skip the executor CI fixture's unpublished harness images
Some checks failed
[Workflow] On Pull Request / release-policy (pull_request) Failing after 19s
[Workflow] On Pull Request / checks (pull_request) Successful in 2m7s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
f390cd6052
custom-agent and opencode-runner in executor-values.yaml only exist to
render every harness branch of the pod template. Renovate looked them up
in Harbor, found nothing, and flagged 'Package lookup failures' on the
dependency dashboard.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'chore(renovate): skip the executor CI fixture's unpublished harness images' (#140) from ryangr0/fix/renovate-fixture-images into development
Some checks failed
[Workflow] On Source Change / checks (push) Successful in 4m11s
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
c6f2daf205
Reviewed-on: #140
fix(ploeg): verify forge webhook signatures before recording the delivery
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
86477f7ded
handleForgeWebhook inserted the delivery id into forge_deliveries before the
provider checked the signature. An unsigned request carrying a real delivery
id got it recorded, and the genuine delivery that arrived later was answered
202 and dropped. Parsing, which verifies the signature against the raw body,
now runs first. Prepared in the 2026-10-02 Execution Pack (AUTH-01).

VIK-1715

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): reject tracker webhooks when no signing secret is set
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
347bc775c4
Vikunja and ClickUp skipped signature verification when their secret was
empty, so anyone who could reach /webhooks/tracker/{provider} could assign
or withdraw work. They now reject every delivery without a configured
secret, the same as the Forgejo and GitLab forge providers, and ploegd
warns at startup when a tracker secret is missing.

VIK-1716

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(vloer): bind OIDC sign-in and account links to the starting browser
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
999883628f
Pending OIDC sign-ins and GitLab/ClickUp link flows were kept in
process-wide maps keyed only by state, so a different browser could finish
them: login CSRF for sign-in, and a victim's provider token attached to an
attacker's account for links. Starting a flow now sets a random HttpOnly
SameSite=Lax cookie whose digest is stored with the pending flow, and a
callback without the matching cookie is refused before the code exchange.
The session cookie stays SameSite=Strict. Prepared in the 2026-10-02
Execution Pack.

VIK-1717

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(vloer): pin a cold sandbox's base and require a healthy workspace
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
84f4dd980a
A cold sandbox never pinned its candidate base, so capture reported
base_unavailable after a successful first run, and the health loop returned
the workspace when its deadline passed even if /global/health never
answered. Provisioning now fails and cleans up when the workspace stays
unhealthy, honors cancellation and the remaining deadline, and pins HEAD as
the base when there is none. Prepared in the 2026-10-02 Execution Pack.

VIK-1745

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ci): keep a joint release in draft until Vloer is distributed
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
47ea28afd8
The final Ploeg publisher ran with always() after vloer-release-distribute
and checked only Ploeg's signing, then took the GitHub release out of
draft. A failed Vloer distribution left a published release that could no
longer receive Vloer's assets. vloer-release-distribute now emits
distributed=true as its last step and the final publisher requires it.
Prepared in the 2026-10-02 Execution Pack.

VIK-1747

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): keep a valid Claude review when its stdout is malformed
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
4497c86164
The Claude Code adapter returned the drop box together with the envelope
decode error, and the generic runner discards every report field on a parse
error, so a banner or truncated JSON on stdout lost a finished review. When
the drop box holds an outcome or verdict it is now returned without the
error; an empty drop box still reports the decode error. Based on the
2026-10-02 Execution Pack patch.

VIK-1746

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'fix(ploeg): verify forge webhook signatures before recording the delivery' (#141) from ryangr0/ploeg-webhook-auth-before-dedup into development
Some checks failed
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
1f08ee82b8
Reviewed-on: #141
Merge pull request 'fix(ploeg): reject tracker webhooks when no signing secret is set' (#142) from ryangr0/ploeg-tracker-webhook-secret into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
2b52b9ae19
Reviewed-on: #142
Merge pull request 'fix(vloer): bind OIDC sign-in and account links to the starting browser' (#143) from ryangr0/vloer-oauth-browser-binding into development
Some checks failed
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
dc53a075b7
Reviewed-on: #143
Merge pull request 'fix(vloer): pin a cold sandbox's base and require a healthy workspace' (#144) from ryangr0/vloer-sandbox-readiness into development
Some checks failed
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
74fbf55463
Reviewed-on: #144
Merge pull request 'fix(ci): keep a joint release in draft until Vloer is distributed' (#146) from ryangr0/ci-release-completion-barrier into development
Some checks failed
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
e163ae9af1
Reviewed-on: #146
Merge pull request 'fix(ploeg): keep a valid Claude review when its stdout is malformed' (#145) from ryangr0/ploeg-claude-outcome-preservation into development
All checks were successful
[Workflow] On Source Change / checks (push) Successful in 5m59s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
c046898f89
Reviewed-on: #145
fix(site): cap the sign-up body by bytes read, not Content-Length
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Successful in 3m0s
[Workflow] On Pull Request / release-policy (pull_request) Failing after 19s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
4769b7a35c
The sign-up handler compared the Content-Length header to the 8 KiB limit,
treated a missing header as zero and then parsed the unbounded body with
request.formData(). A 65,610-byte form sent without the header was accepted
and stored.

The body is now read through a byte-counting reader that cancels the stream
and answers 413 as soon as it passes MAX_BODY_BYTES, before D1 is touched.
The form is parsed from the bytes that were read. Content-Length remains only
as an early rejection when it already declares a body over the limit.

Refs: VIK-1720

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): bound request body reads and idle connections in ploegd
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
fdb59cbe62
ploegd's HTTP server set only ReadHeaderTimeout. Webhook handlers cap the
body size but not the time it takes to arrive, so a client that trickles
its body could hold a connection on an unauthenticated route
indefinitely, and idle keep-alive connections were never reclaimed.

The server is now built by newHTTPServer with ReadTimeout 30s (headers
plus body; a 1 MiB webhook needs only ~35 KiB/s to arrive in time) and
IdleTimeout 120s. No route streams (no SSE, long poll or flushed
responses), but WriteTimeout stays unset because it also bounds handler
time and deploy checks and webhooks call out to forges and trackers.
Tests cover a slow-body client being disconnected within the read
budget and a 1 MiB webhook at normal speed succeeding.

VIK-1725

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): read every page of Forgejo pull requests and GitLab checks
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
1d6e0045e4
The worker asked Forgejo for one page of 50 open pull requests, so a
Run's pull request beyond the first page looked absent: a writer could
open a duplicate and a successful Run could be recorded as no change.
The GitLab provider read one page of 100 commit statuses, so a failed or
pending check on page two was reported as a successful commit, although
CommitStatusReader promises the combined state of every check.

The Forgejo lookup now pages until it finds the Run's pull request or
has seen every open one, using X-Total-Count when the forge sends it and
a short or empty page otherwise. It stops at 20 pages and reports an
error rather than a missing pull request. GitLab commit status follows
X-Next-Page (or a full page when the header is absent) up to 50 pages,
keeps the newest status per check name, and fails the whole read when
any page fails, so a partial read never becomes a success.

VIK-1752

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(vloer): keep health probes constant-cost and page event replay
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
850c3fbba7
/healthz and /readyz called store.listSessions(), which parsed every
session and decrypted its workspace secret on each probe. Probe cost grew
with history, blocked the event loop, and one corrupt old session made
liveness fail. /healthz now answers without touching storage and /readyz
runs a single SELECT 1 through Store.ping(); both keep their {status,
version} response and GET-only method check.

The live event stream read every event after the client's cursor in one
unbounded query, so a client reconnecting from an old cursor loaded the
whole remainder before backpressure applied. It now reads through
Store.eventPage() in batches of eventReplayBatch (200) rows, continuing
from the last sent id until a short page or a buffered-output limit,
and resumes on the next tick. Store.events() keeps returning the full
remainder for the history endpoint, the engine and the AHP host, so no
caller is truncated.

VIK-1724

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): keep the legacy claim off Work Items a live Shift owns
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
c31b804c2b
A role-less claim tried the Shift path first and fell back to
Store.ClaimWithin after ErrNoWork or ErrBudgetExhausted. ClaimWithin
selected any queued item, including one owned by a live Shift. Two
role-less workers could race: one locked the Shift's pending empty-role
Run, the other skipped it under SKIP LOCKED, got ErrNoWork and leased the
same still-queued Work Item through the legacy path. That created a Run
outside the Shift and its pool, and managed mode signed a token for it.
An exhausted Shift pool likewise fell through to an unpooled legacy
claim.

ClaimWithin now excludes items with a live Shift, and a role-less claim
that the Shift path refuses for budget answers 204 without trying the
legacy path. Items without a Shift are claimed as before; the claimable
index test now covers the new predicate.

VIK-1735

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(vloer): send the ClickUp token exchange in the request body
Some checks failed
[Workflow] On Pull Request / release-policy (pull_request) Failing after 21s
[Workflow] On Pull Request / checks (pull_request) Successful in 5m22s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
0f827b3e0d
The ClickUp branch of Links.complete put client_id, client_secret and the
authorization code in the query string of a bodyless POST. ClickUp documents
them as body parameters, and URLs end up in proxy logs, traces and error
messages, which exposed the client secret.

The exchange now posts the three fields as a JSON body with a matching
Content-Type to a URL without a query string. The 10-second timeout and
redirect: 'error' are unchanged. A test with an intercepted fetch asserts the
URL, the body, the headers and that a refused exchange never mentions the
secret; the fake ClickUp server now reads the fields from the body.

Refs: VIK-1721

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): serialize schema migrations with an advisory lock
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
68891e44ee
The migration runner checked schema_migrations outside each migration's
transaction and held no lock across the loop. Two ploegd processes
starting together could both decide a migration was unapplied, and one
failed startup (in the regression test, already on the concurrent
CREATE TABLE IF NOT EXISTS schema_migrations).

Migrate now acquires one pool connection, takes a session
pg_advisory_lock keyed on hashtextextended('ploeg.schema-migrations', 0)
and runs the whole create-check-apply loop on that connection. The lock
is released in a deferred unlock that also runs on error, with a
context detached from cancellation; if the unlock fails the connection
is hijacked and closed so the pool never hands out a connection that
still holds the lock. A test runs two migrators concurrently against a
fresh database in the embedded PostgreSQL: both return without error,
each migration is recorded once and no advisory lock remains.

VIK-1726

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
feat(ploeg): let registered GitLab targets pass the readiness gate
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
58ae4d38d3
The readiness gate is built only from forges that implement
RepositoryInspector, and a target whose forge has no inspector is never
ready. The GitLab provider did not implement it, so every registered
GitLab target was refused at config load, at ingest and at claim, even
when the project was healthy.

The GitLab provider now implements InspectRepository with the Forgejo
semantics. It reads the project by its URL-encoded full path, so
subgroups work, and reports archived, pull-mirror and default-branch
state. It checks AGENTS.md on the base branch, or the default branch
when none is set, with a HEAD on the repository files API. A missing
file is a not-ready verdict; a missing project or any other forge
failure is an error, which the gate reports as unknown and retries. A
project with no default branch has no AGENTS.md. The routing how-to and
ADR-0038's history record the change.

VIK-1753

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): close a Shift and settle its Work Item in one transaction
Some checks failed
[Workflow] On Pull Request / release-policy (pull_request) Failing after 33s
[Workflow] On Pull Request / checks (pull_request) Successful in 5m20s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
98f58d4bb1
The shift engine called Store.CloseShift and then Store.SettleItem in
separate transactions. A crash between them left a leased Work Item
behind a closed Shift. EvaluateAll only repairs queued items without a
Shift and live Shifts, so nothing ever recovered it.

Store.CloseShiftAndSettle now closes the Shift, cancels its pending Runs
and settles the item in one transaction; CloseShift and SettleItem share
the same transactional steps. Only the call that wins the close settles:
replaying an already-closed Shift, including one a withdrawal closed,
changes nothing, and the engine returns before any remand or tracker
notification. Notifications stay outside the transaction.

Tests inject a fault at the close, the settle and the settle's audit
row, then sweep: each ends with the Shift closed once, the item in
awaiting_review and the ledger unchanged.

VIK-1734

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): show verification from the worker's record, not agent prose
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
7bfd8a34fb
The worker reported its verification only as prose: a
"[Ploeg verification passed|failed|incomplete]" marker appended to the
agent-written summary and a "### Ploeg verification" section appended to
the findings. The usage report took the first marker in the summary and
the first commit hash in the findings, so an agent whose own summary said
"[Ploeg verification passed]" made a failed check render as passed, on
whatever commit the agent named.

The worker now also sends a structured verification record on the
OutcomeReport: the result, the full commit, the dirty-tree flag, why
checks stopped, start and finish times, and each check with its result,
exit status and times. resolveOutcome discards any verification an
adapter or agent reported, so only the worker's own run sets it. ploegd
validates the record, stores it in agent_runs.verification (migration
0034) for writing Runs only, and RoundReports returns it. parseEvidence
uses the record whenever the last writing Run has one; summary and
findings text cannot change it. Dirty, failed, incomplete and unknown
render distinctly.

Runs reported by an older worker have no record and still render from
the prose, now taking the last marker and the last section, which are
the ones the worker appended. The field is optional on the wire, so
older payloads decode unchanged and an older ploegd ignores it. The
outcome report schema and contract docs describe the new field.

VIK-1733

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'fix(ploeg): show verification from the worker's record, not agent prose' (#152) from ryangr0/ploeg-structured-verification into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Successful in 4m55s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
855022c634
Reviewed-on: #152
Merge pull request 'fix(vloer): keep health probes constant-cost and page event replay' (#147) from ryangr0/vloer-bounded-health-and-replay into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Successful in 5m32s
[Workflow] On Source Change / warnings (push) Successful in 1s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
9f8d272402
Reviewed-on: #147
Merge pull request 'fix(ploeg): bound request body reads and idle connections in ploegd' (#148) from ryangr0/ploegd-http-timeouts into development
All checks were successful
[Workflow] On Source Change / checks (push) Successful in 7m0s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
f34cb24f99
Reviewed-on: #148
Merge pull request 'fix(ploeg): serialize schema migrations with an advisory lock' (#149) from ryangr0/ploeg-migration-advisory-lock into development
All checks were successful
[Workflow] On Source Change / checks (push) Successful in 4m15s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
a45390d005
Reviewed-on: #149
fix(vloer): match checkouts by forge host and full repo path
Some checks failed
[Workflow] On Pull Request / release-policy (pull_request) Failing after 18s
[Workflow] On Pull Request / checks (pull_request) Successful in 8m3s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
255dbf4a37
remoteMatches in the VS Code extension kept only the last two path parts of
a git remote and compared them to owner/repo, so a clone of the same-named
repository on another forge, or under another GitLab group, was offered as
the Work Item's checkout. decodeURIComponent ran outside the try/catch, so a
badly encoded remote threw instead of not matching.

parseRemote now reads HTTPS, ssh:// (any port) and SCP-style remotes into a
lowercase host and the full path, keeping nested groups and stripping .git;
a malformed or badly encoded remote yields no identity and no match. The
checkout builds the expected repository from the host of the Work Item's
pull request link, the same link the clone offer already uses, and owner/repo
from Ploeg's target. Ploeg's target carries only a forge registry id, so
without a pull request link the full path is matched on any host.

A forge's SSH host often differs from its web host, so hosts also match when
both have at least three labels and share the parent domain below their
first label (forgejo-ssh.webgrip.dev and forgejo.webgrip.dev), or when the
new application-scoped setting vloer.remoteHostAliases maps one to the other.

Refs: VIK-1722

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): fail a reviewer that cannot fetch the branch under review
Some checks failed
[Workflow] On Pull Request / release-policy (pull_request) Failing after 43s
[Workflow] On Pull Request / checks (pull_request) Successful in 8m16s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
1b0b544e1c
A reading Run treated every error fetching the Shift's branch as "no
branch under review yet" and reviewed the base branch instead. A network
outage, a 401 or a 5xx from the forge looked the same as a genuinely
absent branch, and the later pull request poll still linked the writer's
pull request, so an approval of the wrong checkout counted.

The worker now probes the branch with `git ls-remote --exit-code`, which
separates "the forge answered: no such branch" (exit 2) from every
transport or authentication failure, and bounds the probe and fetch with
a timeout. ploegd tells the worker whether the reader is pre-author: the
claim carries preAuthor when no writing Run precedes the reader's Round
in its Shift. Only a pre-author reader with a genuinely absent branch
reviews the base. A forge failure ends the Run failed/infra_node before
the harness starts; an absent branch after a writer ends it stuck. The
commit a reader checked out is recorded on its first checkpoint and on
the outcome's checkpoint (new optional `commit` field).

VIK-1736

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'fix(ploeg): read every page of Forgejo pull requests and GitLab checks' (#150) from ryangr0/ploeg-forge-pagination into development
All checks were successful
[Workflow] On Source Change / checks (push) Successful in 5m4s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
0f7fbad64b
Reviewed-on: #150
Merge pull request 'feat(ploeg): let registered GitLab targets pass the readiness gate' (#151) from ryangr0/ploeg-gitlab-repository-inspector into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Successful in 5m7s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
1303bfb436
Reviewed-on: #151
Merge pull request 'fix(ploeg): keep the legacy claim off Work Items a live Shift owns' (#153) from ryangr0/ploeg-legacy-claim-respects-shifts into development
All checks were successful
[Workflow] On Source Change / checks (push) Successful in 4m25s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
e5180daf4c
Reviewed-on: #153
Merge pull request 'fix(ploeg): close a Shift and settle its Work Item in one transaction' (#154) from ryangr0/ploeg-close-and-settle-atomically into development
All checks were successful
[Workflow] On Source Change / checks (push) Successful in 4m21s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
4023462ed0
Reviewed-on: #154
Merge pull request 'fix(ploeg): fail a reviewer that cannot fetch the branch under review' (#158) from ryangr0/ploeg-reviewer-fetch-failure into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Successful in 4m30s
[Workflow] On Source Change / warnings (push) Successful in 1s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
5bd8d1a11a
Reviewed-on: #158
Merge pull request 'fix(site): cap the sign-up body by bytes read, not Content-Length' (#155) from ryangr0/site-signup-byte-cap into development
All checks were successful
[Workflow] On Source Change / checks (push) Successful in 4m48s
[Workflow] On Source Change / warnings (push) Successful in 1s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
33b54bfd82
Reviewed-on: #155
Merge pull request 'fix(vloer): send the ClickUp token exchange in the request body' (#156) from ryangr0/vloer-clickup-token-exchange-body into development
All checks were successful
[Workflow] On Source Change / checks (push) Successful in 4m2s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
e809348aa7
Reviewed-on: #156
Merge pull request 'fix(vloer): match checkouts by forge host and full repo path' (#157) from ryangr0/vloer-extension-remote-identity into development
All checks were successful
[Workflow] On Docs Change / authorize-publication (push) Successful in 0s
[Workflow] On Docs Change / techdocs (push) Successful in 1m21s
[Workflow] On Docs Change / generate-documentation (push) Successful in 0s
[Workflow] On Docs Change / Build (Zensical) + sync to Garage web (push) Successful in 16s
[Workflow] On Docs Change / deploy-docs-site (push) Successful in 0s
[Workflow] On Docs Change / verify-publication (push) Has been skipped
[Workflow] On Source Change / checks (push) Successful in 3m54s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
01ec821d0c
Reviewed-on: #157
fix(ci): read the UNFOLD_ release and docs gates
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Failing after 2m49s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 11s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
5b48b2acee
The repository variables were renamed to UNFOLD_RELEASES_ENABLED and
UNFOLD_DOCS_PUBLISH_ENABLED on 2026-10-01, but the workflows still read
the GLIDE_ names. Every development push since run 604 skipped the
release and site-release jobs, and docs publication resolved to false.

Also expect cloudflare-deploy at v2.7.7, the pin Renovate moved to.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(vloer): accept the editor's synced core imports in the source check
All checks were successful
[Workflow] On Pull Request / release-policy (pull_request) Successful in 18s
[Workflow] On Pull Request / checks (pull_request) Successful in 2m50s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
de477de995
media/task.js imports ./core/*.js, which the extension build copies from
public/core/ into a gitignored directory. On a clean checkout the check
reported them missing; CI passed only on a cached gate result.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'fix(ci): read the UNFOLD_ release and docs gates' (#161) from ryangr0/ci/unfold-repo-variables into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Successful in 0s
[Workflow] On Docs Change / techdocs (push) Successful in 1m12s
[Workflow] On Docs Change / generate-documentation (push) Successful in 0s
[Workflow] On Source Change / checks (push) Successful in 4m41s
[Workflow] On Docs Change / Build (Zensical) + sync to Garage web (push) Successful in 3m41s
[Workflow] On Docs Change / deploy-docs-site (push) Successful in 0s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Source Change / release (push) Successful in 1m27s
[Workflow] On Source Change / site-release (push) Successful in 35s
cba7c7ee2a
Reviewed-on: #161
chore(release): unfold-v0.4.0-rc.33 [skip ci]
Some checks failed
[Workflow] On Release Published / parse-release-tag (release) Successful in 0s
[Workflow] On Release Published / site-release-tag (release) Successful in 0s
[Workflow] On Release Published / Vloer - Publish Helm chart (Harbor) (release) Successful in 11s
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor) (release) Successful in 5m6s
[Workflow] On Release Published / Vloer - Publish VS Code extension (release) Successful in 42s
[Workflow] On Release Published / Ploeg - Publish Helm chart (Harbor) (release) Successful in 7s
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor)-1 (release) Failing after 11m27s
[Workflow] On Release Published / Deploy preview (release) Has been skipped
[Workflow] On Release Published / Deploy production (release) Has been skipped
[Workflow] On Release Published / site-deploy (release) Successful in 0s
[Workflow] On Release Published / Ploeg - Distribute (Harbor) (release) Successful in 8m27s
[Workflow] On Release Published / Vloer - Verify and publish all destinations (release) Has been skipped
[Workflow] On Release Published / Ploeg - Sign & Attest (Harbor) (release) Successful in 31s
[Workflow] On Release Published / Ploeg - Verify and publish all destinations (release) Has been skipped
7258f08926
## [unfold-v0.4.0-rc.33](https://forgejo.webgrip.dev/webgrip/unfold/compare/unfold-v0.4.0-rc.32...unfold-v0.4.0-rc.33) (2026-10-03)

### Dependencies

* **deps:** update node.js ( 98fb607 ➔ baf0afb ) ([1131b48](1131b48c21))

### Added

* **deps:** update dependency three ( 0.165.0 ➔ 0.186.1 ) ([df8a166](df8a16685e))
* **ploeg:** crack and mend run cards through human-confirmed attribution ([cce1dd2](cce1dd2d3d))
* **ploeg:** give Run cards a rarity measured by their challenge ([d98d4d7](d98d4d7986))
* **ploeg:** keep one run card comment with a card image on the pull request ([897a087](897a087aac))
* **ploeg:** let registered GitLab targets pass the readiness gate ([58ae4d3](58ae4d38d3))
* **ploeg:** list run cards by roster login for binders and packs ([4cf6713](4cf6713bb5))
* **ploeg:** put pull request, CI and change-shape figures on the Run card ([764feda](764feda1cd))
* **ploeg:** put tracker flow, queue and delivery timings on the Run card ([65afed7](65afed7fdb))
* **ploeg:** read epics from tracker relations and give run cards a set ([fec31b4](fec31b4b8a))
* **site:** serve the recorded Vloer replay at /demo ([66ffe3f](66ffe3faa7)), references [#app](https://forgejo.webgrip.dev/webgrip/unfold/issues/app)
* **site:** turn the site into a landing page with sign-ups, pricing and the demo ([3c19c4d](3c19c4d349))
* **vloer:** add card themes and a card designer with generated art ([f5977db](f5977dbd72))
* **vloer:** add holo, loot, arcade, ticker and patch card skins ([b9725a3](b9725a3476))
* **vloer:** add recording seams to the deterministic demo ([611972d](611972dccc))
* **vloer:** collect run cards in a private binder and rip sprint packs ([d86acab](d86acab5c7)), references [#binder](https://forgejo.webgrip.dev/webgrip/unfold/issues/binder) [#packs](https://forgejo.webgrip.dev/webgrip/unfold/issues/packs) [#season](https://forgejo.webgrip.dev/webgrip/unfold/issues/season)
* **vloer:** give Run cards an inner world you can tilt, flatten and decorate ([c38e543](c38e543374))
* **vloer:** link a Run by URL, show Grafana links, announce the URL to Ploeg ([65b1e71](65b1e71702))
* **vloer:** play run card moments through an effects director ([84f850b](84f850b01d))
* **vloer:** record the deterministic demo and replay it in the browser ([ffe10ed](ffe10ed454))
* **vloer:** render run cards in 3D with the forge skin ([2a50752](2a507527d2))
* **vloer:** show flow, review, CI and change KPIs on Run cards ([4a70b87](4a70b8786f))
* **vloer:** show gates, cracks and sets on run cards and trace bugs ([7004c6f](7004c6ff82))
* **vloer:** show Run card rarity with frame metal and a reveal ceremony ([9c912ae](9c912aee7d)), references [#121](#121)

### Fixed

* **agent:** update opencode ( 1.18.33 ➔ 1.18.34 ) ([86006b3](86006b306a))
* **deps:** update dependency @types/node ( 24.19.0 ➔ 24.19.1 ) ([4c72d18](4c72d18c79))
* **ploeg:** bound request body reads and idle connections in ploegd ([fdb59cb](fdb59cbe62))
* **ploeg:** close a Shift and settle its Work Item in one transaction ([98f58d4](98f58d4bb1))
* **ploeg:** fail a reviewer that cannot fetch the branch under review ([1b0b544](1b0b544e1c))
* **ploeg:** keep a valid Claude review when its stdout is malformed ([4497c86](4497c86164))
* **ploeg:** keep the legacy claim off Work Items a live Shift owns ([c31b804](c31b804c2b))
* **ploeg:** read every page of Forgejo pull requests and GitLab checks ([1d6e004](1d6e0045e4))
* **ploeg:** reject tracker webhooks when no signing secret is set ([347bc77](347bc775c4))
* **ploeg:** serialize schema migrations with an advisory lock ([68891e4](68891e44ee))
* **ploeg:** show verification from the worker's record, not agent prose ([7bfd8a3](7bfd8a34fb))
* **ploeg:** verify forge webhook signatures before recording the delivery ([86477f7](86477f7ded))
* **site:** cap the sign-up body by bytes read, not Content-Length ([4769b7a](4769b7a35c))
* **site:** link the replay at /demo/ once its recording exists, and renumber the sign-up ADR ([dfd0f28](dfd0f2832d)), references [#122](#122)
* **site:** set the sign-up database id ([00b36b6](00b36b66f8))
* **vloer:** accept the editor's synced core imports in the source check ([de477de](de477de995))
* **vloer:** bind OIDC sign-in and account links to the starting browser ([9998836](999883628f))
* **vloer:** clear a stale Run notice on every Work navigation ([fac09aa](fac09aa2ca))
* **vloer:** keep health probes constant-cost and page event replay ([850c3fb](850c3fbba7))
* **vloer:** let the forge stage fill the Run card page head ([b20d049](b20d0493dc))
* **vloer:** match checkouts by forge host and full repo path ([255dbf4](255dbf4a37))
* **vloer:** pin a cold sandbox's base and require a healthy workspace ([84f4dd9](84f4dd980a))
* **vloer:** pin three back to the vendored 0.165.0 and keep Renovate off it ([7baa63a](7baa63afe9)), references [#119](#119)
* **vloer:** pin three to the vendored 0.186.1 ([10bb764](10bb76477d)), closes [#119](#119) [#126](#126), references [#122](#122)
* **vloer:** refuse the inner world's route in the hosted demo replay ([7897f61](7897f61e21)), references [#122](#122)
* **vloer:** send the ClickUp token exchange in the request body ([0f827b3](0f827b3e0d))
* **vloer:** vendor three.js 0.186.1 to match the pinned devDependency ([23e63c2](23e63c24e7)), references [#119](#119)

### Changed

* **vloer:** resolve static assets relative to the page ([3465022](34650223b1))

### Docs

* date the hosted replay pages in UTC ([60340b1](60340b1603))
* point commit references at the rewritten history ([2bb2e6b](2bb2e6b359))
* **vloer:** describe the hosted replay of the demo ([b642cd5](b642cd528a))

### Tests

* **vloer:** match the work page browser checks to the Run card page head ([a58e682](a58e68293b))

### CI

* **deps:** update all non-major dependencies ([6ca1a9a](6ca1a9a143))

### Internal

* neutralise employer-specific fixtures and docs ([09a54f7](09a54f7ca0))
* **site:** re-record the demo replay with Run card KPIs ([39e500c](39e500c64c))
chore(release): unfold-site-v0.1.0-rc.8 [skip ci]
All checks were successful
[Workflow] On Release Published / parse-release-tag (release) Has been skipped
[Workflow] On Release Published / site-release-tag (release) Successful in 3s
[Workflow] On Release Published / Vloer - Publish Helm chart (Harbor) (release) Has been skipped
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor) (release) Has been skipped
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor)-1 (release) Has been skipped
[Workflow] On Release Published / Vloer - Publish VS Code extension (release) Has been skipped
[Workflow] On Release Published / Ploeg - Publish Helm chart (Harbor) (release) Has been skipped
[Workflow] On Release Published / Ploeg - Distribute (Harbor) (release) Has been skipped
[Workflow] On Release Published / Deploy preview (release) Has been skipped
[Workflow] On Release Published / Deploy production (release) Successful in 4m43s
[Workflow] On Release Published / site-deploy (release) Successful in 0s
[Workflow] On Release Published / Vloer - Verify and publish all destinations (release) Has been skipped
[Workflow] On Release Published / Ploeg - Sign & Attest (Harbor) (release) Has been skipped
[Workflow] On Release Published / Ploeg - Verify and publish all destinations (release) Has been skipped
54f7336814
## [unfold-site-v0.1.0-rc.8](https://forgejo.webgrip.dev/webgrip/unfold/compare/unfold-site-v0.1.0-rc.7...unfold-site-v0.1.0-rc.8) (2026-10-03)

### Added

* **site:** serve the recorded Vloer replay at /demo ([66ffe3f](66ffe3faa7)), references [#app](https://forgejo.webgrip.dev/webgrip/unfold/issues/app)
* **site:** turn the site into a landing page with sign-ups, pricing and the demo ([3c19c4d](3c19c4d349))

### Fixed

* **site:** cap the sign-up body by bytes read, not Content-Length ([4769b7a](4769b7a35c))
* **site:** link the replay at /demo/ once its recording exists, and renumber the sign-up ADR ([dfd0f28](dfd0f2832d)), references [#122](#122)
* **site:** set the sign-up database id ([00b36b6](00b36b66f8))
* **vloer:** refuse the inner world's route in the hosted demo replay ([7897f61](7897f61e21)), references [#122](#122)

### Docs

* point commit references at the rewritten history ([2bb2e6b](2bb2e6b359))

### Internal

* **site:** re-record the demo replay with Run card KPIs ([39e500c](39e500c64c))
fix(ploeg): keep a writer's problem and solution when Claude stdout is malformed
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
f77d8d3ac3
Writers put only problem and solution in PLOEG_OUTCOME_FILE (ADR-0042).
The Claude adapter forgave an undecodable result envelope only when the
drop box carried an outcome or a verdict, so a compliant writer's account
came back with a parse error and harness.RunCommand discarded it.

The envelope and the drop box are now independent sources. Any agent
report in the box (outcome, verdict, findings, problem, solution or
created Work Items) survives a malformed envelope; usage comes only from
the envelope and stays nil when it cannot be decoded, and a usage figure
the agent wrote into the box is discarded. The process exit error stays
authoritative, and the shared runner's parse-error contract is unchanged.

A new conformance property runs every adapter against garbage stdout with
a writer's box; Claude failed it, ACP, exec and OpenHands already passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The replay records GET /api/health, whose body carries Vloer's version,
so each release commit made replay:check fail on development (run 742,
rc.32 recorded, rc.33 now). The check now masks each side's own version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(vloer): record CVE-2026-93748 as not affecting the agent image
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
211e857e40
rc.33's de-vloer-agent failed its CVE budget on http-cache-semantics
4.2.0, bundled by npm. No fixed release exists. The flaw needs a shared
cache serving several users; npm's private cache in one session's
container serves only npm. The OpenVEX statement covers the CVE and GHSA
ids, and a build check fails if any other copy of the package appears.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'fix(ploeg): keep a writer's problem and solution when Claude stdout is malformed' (#162) from ryangr0/ploeg-writer-narrative-malformed-stdout into development
Some checks failed
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
619d838985
Reviewed-on: #162
Merge pull request 'fix(vloer): keep the demo replay green across releases and unblock the agent image' (#163) from ryangr0/fix/replay-version-and-agent-cve into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Successful in 8m9s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Successful in 38s
[Workflow] On Source Change / site-release (push) Successful in 15s
89452dd4fd
Reviewed-on: #163
chore(release): unfold-v0.4.0-rc.34 [skip ci]
Some checks failed
[Workflow] On Release Published / parse-release-tag (release) Successful in 0s
[Workflow] On Release Published / site-release-tag (release) Successful in 0s
[Workflow] On Release Published / Vloer - Publish Helm chart (Harbor) (release) Successful in 10s
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor) (release) Successful in 4m30s
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor)-1 (release) Successful in 7m1s
[Workflow] On Release Published / Vloer - Publish VS Code extension (release) Successful in 32s
[Workflow] On Release Published / Ploeg - Publish Helm chart (Harbor) (release) Successful in 8s
[Workflow] On Release Published / Deploy preview (release) Has been skipped
[Workflow] On Release Published / Deploy production (release) Has been skipped
[Workflow] On Release Published / site-deploy (release) Successful in 0s
[Workflow] On Release Published / Ploeg - Distribute (Harbor) (release) Successful in 5m24s
[Workflow] On Release Published / Ploeg - Sign & Attest (Harbor) (release) Successful in 15s
[Workflow] On Release Published / Vloer - Verify and publish all destinations (release) Failing after 30m0s
[Workflow] On Release Published / Ploeg - Verify and publish all destinations (release) Has been skipped
9c8b7407ba
## [unfold-v0.4.0-rc.34](https://forgejo.webgrip.dev/webgrip/unfold/compare/unfold-v0.4.0-rc.33...unfold-v0.4.0-rc.34) (2026-10-03)

### Fixed

* **ploeg:** keep a writer's problem and solution when Claude stdout is malformed ([f77d8d3](f77d8d3ac3))
* **vloer:** record CVE-2026-93748 as not affecting the agent image ([211e857](211e857e40))
* **vloer:** stop every release from staling the demo replay ([6af67f3](6af67f3932))

### Internal

* **release:** unfold-site-v0.1.0-rc.8 [skip ci] ([54f7336](54f7336814))
Lead with a recorded Shift, then connect its evidence to managed execution and human review. Preserve the established brand and qualify current capabilities and planned pricing in both languages.

Add accessible navigation, manual and pausable exploration, reduced-motion support and responsive text scaling. Validate all repository gates, 65 site tests, the production build and ten-page accessibility scan.
fix(release): publish to webgrip/unfold and fail fast on dead addresses
Some checks failed
[Workflow] On Pull Request / release-policy (pull_request) Successful in 25s
[Workflow] On Pull Request / checks (pull_request) Successful in 6m37s
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
4637099afb
rc.34's Vloer distribution hung for its 30-minute timeout. The publisher
still named webgrip/glide: its evidence POST was answered 301 by the
renamed Forgejo repository and urllib replayed it as a GET, so nothing
was attached, and git ls-remote against the deleted GitHub webgrip/glide
waited on a credential prompt. Package linking also unlinked packages
already linked to webgrip/unfold and got HTTP 500.

The release scripts, preflight, image source labels and chart metadata
now name webgrip/unfold. A redirected write raises instead of turning
into a read, and the publisher's git never prompts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vloer's engine completed every session with "The crew finished and all
required reviewers explicitly approved", including when the candidate
could not be captured and when a read-only crew's reviewer returned
inconclusive or requested changes (read-only crews are not governed, so
such a verdict still completes). The message claimed approvals and a
reviewable change that did not exist.

On completion the session now records an outcome: whether the work was a
change or an investigation, whether the candidate was captured, that
Vloer performed no independent verification, and the reviewer's actual
conclusion (approved, changes requested, inconclusive, not required).
The session.completed event carries the same outcome and a message built
from it. A retry clears the outcome. Completion rules are unchanged, so
investigations still complete. The hosted demo replay is re-recorded.

VIK-1761

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
chore(site): re-record the demo replay with the truthful completion message
Some checks failed
[Workflow] On Pull Request / release-policy (pull_request) Successful in 29s
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
26b8d85f15
The deterministic demo's session.completed event now carries the outcome
and the message that says what actually happened.

VIK-1761

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): replay a finished outcome that carries a checkpoint
Some checks failed
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
ed241eed7a
A worker that lost the response to its outcome report and retried the
same report with an inline checkpoint got 404 instead of the original
success. handleOutcome wrote the checkpoint before ReportOutcome's
digest-based replay check, and Store.Checkpoint needs a Lease or a
running Run, which the first report had already removed.

The handler now builds the report first and, when a checkpoint rides
along, asks Store.IsOutcomeReplay whether the report matches the digest
that finished the run. A replay skips the checkpoint write and goes
straight to ReportOutcome, which answers with the original success, so no
second checkpoint or created Work Item appears. A different report for a
finished run still fails the checkpoint write and is rejected as before.
ReportOutcome and IsOutcomeReplay share one digest computation.

VIK-1757

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs(adr): propose ADR-0017 a Tenant above Teams as the access boundary
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
0315881d42
Ploeg and Vloer scope access by Team, but a Team is a capability pool:
created work moves between Teams, the root budget pool is locked per
source Team, the deploy token is deployment-wide, scope pins collide,
operator admission trusts request repository ids, and Vloer shows every
source and repository to every signed-in user.

ADR-0017 proposes a Tenant (ADR-0009's agency tenant) above Teams that
owns users via SSO groups, tracker sources, repositories, Teams, root
budgets, deploy identities and Work Items, with enforcement points in
both services, 404 for forbidden objects, the follow-up enforcement
tickets and isolation tests as Confirmation. Open owner questions are
listed in the ADR. Registered in the system index, the decision register
and as an ambiguity in the product domain model.

VIK-1741

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'docs(adr): propose ADR-0017 a Tenant above Teams as the access boundary' (#168) from ryangr0/adr-tenant-boundary into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
875fbdefe5
Reviewed-on: #168
docs(kpis): count approvals as clean in K5 and drop the skip-review advice
Some checks failed
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
d9d0d7b0ee
K5 treated every submitted review as a change, so an approval lowered the
clean-merge rate, and the page told the owner to merge without a formal
approval to keep the proxy honest. That rewarded skipping review. Ploeg
already stores each review's verdict, reviewer, head SHA and time
(pull_request_reviews, migration 0023) and each push with its pusher
(pull_request_events, migration 0033).

K5 now counts a pull request as changed only when a review requested
changes, or someone other than the pull request's author pushed to the
branch, between the awaiting_review settle and the merge. Approvals and
comment-only reviews never lower it. The skip-review sentence is gone, the
data gap lists only what is still missing (GitLab pushes name no pusher,
the 500-event activity bound, a missing author, ForgeBots being
configuration), and D1 is narrowed to the GitLab pusher.

VIK-1750

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): route ClickUp work by tags and withdraw it when the task closes
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
0330f0f503
The ClickUp provider dropped tags when it decoded a task, so a fetched
Work Item had no labels. Because the read succeeded, the resolver took the
empty list as authoritative and chose the board default, ignoring a
repo/* tag. ClickUp also normalized every taskStatusUpdated delivery to
an update, so closing a task never withdrew queued work that had not
started, unlike Vikunja.

FetchItem, FetchExecutionItem and BoardStatus now share one task read
that carries the tag names as labels, so routing, operator source checks
and gate tracking see the same tags and apply the Vikunja label rules.
A taskStatusUpdated delivery becomes a closure only when an authoritative
read of the task finds a status of type closed or done; with no API
token, or when the read fails, it stays an update. Closure then follows
the existing policy: unstarted queued work is withdrawn as
withdrawn_closed and started work runs on.

VIK-1754

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): decode forge proxy paths and limit them to what the Role needs
Some checks failed
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
1be4a650c6
The forge token proxy checked the escaped path for a literal "/../", a
trailing "/.." and "%2e%2e" only, so mixed forms such as
"/api/v1/repos/o/r/%2e./other/pulls" passed and were forwarded with the
Run's real token. Every API subpath under the repository was allowed, so a
writer harness could merge, delete, change settings, hooks, collaborators,
branch protection or keys.

The proxy now decodes each path segment and refuses any dot segment,
encoded separator (%2f, %5c), double encoding, control character or empty
segment. The only encoded separator accepted is GitLab's project ID, which
must equal the Run's project path exactly. It then enforces a per-Role
allowlist of methods and endpoints: smart-HTTP git fetch (and push for a
writer), reading and opening or updating the pull or merge request, reading
and posting comments, and reading commit statuses. Everything else gets 403
and is never forwarded.

VIK-1748

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'docs(kpis): count approvals as clean in K5 and drop the skip-review advice' (#169) from ryangr0/kpi-k5-counts-approvals into development
Some checks are pending
[Workflow] On Docs Change / authorize-publication (push) Waiting to run
[Workflow] On Docs Change / generate-documentation (push) Blocked by required conditions
[Workflow] On Docs Change / deploy-docs-site (push) Blocked by required conditions
[Workflow] On Docs Change / verify-publication (push) Blocked by required conditions
[Workflow] On Docs Change / techdocs (push) Waiting to run
[Workflow] On Source Change / checks (push) Waiting to run
[Workflow] On Source Change / warnings (push) Blocked by required conditions
[Workflow] On Source Change / release (push) Blocked by required conditions
[Workflow] On Source Change / site-release (push) Blocked by required conditions
ea7155c75e
Reviewed-on: #169
Merge pull request 'fix(ploeg): replay a finished outcome that carries a checkpoint' (#167) from ryangr0/ploeg-outcome-replay-with-checkpoint into development
Some checks failed
[Workflow] On Source Change / release (push) Blocked by required conditions
[Workflow] On Source Change / site-release (push) Blocked by required conditions
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
fedd3c010d
Reviewed-on: #167
docs(ploeg): propose ADR-0059 delivery facts come from the forge
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
6169e403a8
An agent can decide today whether its Run delivered and where: the drop
box's outcome, links and checkpoint survive a clean exit, a failed forge
read reads as "no pull request", pr_updated needs no push, and
publication and the review watch take the PR number from any link and
pair it with the Work Item's repository.

The proposed record gives every OutcomeReport and Checkpoint field one
owner. The agent keeps its narrative (summary, findings, problem,
solution, verdict, created Work Item proposals); the worker alone sets
pr_opened/pr_updated, links, checkpoint, failure reason and a new
delivery field from forge reads bound to the Run's forge, repository,
branch and head commit, which ploegd checks against the claim. A failed
forge read is an explicit unknown that routes to needs_human, and
reports from older workers are accepted for one release under a link
binding check. It follows the worker-owned verification of PR #152.

VIK-1732

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'fix(release): publish to webgrip/unfold and fail fast on dead addresses' (#164) from ryangr0/fix/release-publisher-unfold-identity into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Successful in 1s
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Successful in 6m5s
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
60df87a74b
Reviewed-on: #164
Merge pull request 'chore(site): re-record the demo replay with the truthful completion message' (#166) from ryangr0/vloer-honest-completion-message into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Successful in 7m58s
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
00e0ee559e
Reviewed-on: #166
Merge pull request 'fix(ploeg): decode forge proxy paths and limit them to what the Role needs' (#171) from ryangr0/ploeg-forge-proxy-paths into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
7ed6a2d9d0
Reviewed-on: #171
Merge pull request 'fix(ploeg): route ClickUp work by tags and withdraw it when the task closes' (#170) from ryangr0/ploeg-clickup-tags-and-close into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Successful in 9m33s
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
808cffc10b
Reviewed-on: #170
Merge pull request 'docs(ploeg): propose ADR-0059 delivery facts come from the forge' (#173) from ryangr0/ploeg-adr-outcome-field-ownership into development
Some checks failed
[Workflow] On Source Change / site-release (push) Blocked by required conditions
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Successful in 7m28s
[Workflow] On Source Change / warnings (push) Waiting to run
[Workflow] On Source Change / release (push) Waiting to run
4ccdeb4304
Reviewed-on: #173
fix(ploeg): tie per-Run forge tokens to a live Lease and always revoke them
All checks were successful
[Workflow] On Pull Request / checks (pull_request) Successful in 7m1s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 18s
[Workflow] On Pull Request / warnings (pull_request) Successful in 1s
da2f3a2240
A writing claim minted a push token, then recorded its id on the Lease while
ignoring the affected-row count. If the Run had been withdrawn or its Lease
had expired while the mint was in flight, or the database write failed, the
error was only logged and the worker still got a token that nothing was
recorded to revoke. The forge orphan sweep read the live Lease ids before it
listed the forge's tokens, so a token minted or not yet recorded during a
sweep looked orphaned and a running worker lost its push rights. The sweep
also read only the first page of the token list and stopped at the first
failed revoke. MintRequest.TTL promised a time limit that Forgejo's token API
does not support, and the broker never sent one.

RecordForgeToken now returns ErrLeaseLost unless the Run is running and its
Lease is unexpired. On any record failure the claim answers 204, revokes the
token with a context that is not cancelled and is limited to 30 seconds, and
releases the Run when the Lease is still there. Report and withdraw revokes
use the same kind of context. The forge sweep lists every page of the bot's
tokens first, then reads which runs hold a Lease. It keeps every token whose
name carries a leased run, revokes the rest, and keeps going after a failed
revoke. Because the token name carries the run, the forge itself records
every token: after a restart, a token that was minted but not recorded, or
whose revoke failed, is revoked once its Run holds no Lease. The TTL field is
removed. The package documentation and the Push Credential glossary entry
now say that the token does not expire and revocation is its only limit.

VIK-1739

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'fix(ploeg): tie per-Run forge tokens to a live Lease and always revoke them' (#174) from ryangr0/ploeg-forge-token-lifecycle into development
Some checks failed
[Workflow] On Source Change / checks (push) Waiting to run
[Workflow] On Source Change / warnings (push) Blocked by required conditions
[Workflow] On Source Change / release (push) Blocked by required conditions
[Workflow] On Source Change / site-release (push) Blocked by required conditions
[Workflow] On Docs Change / authorize-publication (push) Successful in 0s
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
77f2bdaee0
Reviewed-on: #174
fix(ploeg): settle late gateway charges after the first settlement
All checks were successful
[Workflow] On Pull Request / checks (pull_request) Successful in 8m16s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 2m42s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
746ad48209
The settlement sweep read an account's LiteLLM spend logs once, after the
quiet period, and never looked at a reconciled account again. A spend log
written after that read was missing from the Run's and the Shift's cost,
and a minted key with no spend-log entries settled at zero and was shown
as a zero cost.

A spend-log settlement now stays provisional for
PLOEG_LLM_CORRECTION_WINDOW (default 24h, 0 makes it final). Every 15
minutes the correction sweep reads the spend logs of reconciled accounts
in their window again. A higher total charges only the difference to the
Shift, raises reconciled_spend and is recorded as a run_llm_adjustments
row and an llm.reconciled audit event marked adjustment. The first
settlement stays in settled_at, settled_spend and its original evidence.
An unchanged re-read writes nothing.

A minted account settled from no spend-log entries records cost_known
false: the operator API shows its settledUsd as null and its Run
costStatus as unknown until an entry arrives; an entry costing zero
confirms zero. The Run list and Run detail gain settledAt, costFinalAt
and costFinal, so a provisional cost is distinguishable from a final one.

Migration 0036 adds the columns and table and backfills existing
reconciled accounts as final, marking spend-log settlements with
entries=0 as unknown.

VIK-1755

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Now read only the first page (25) of each team's awaiting_review,
needs_human, leased and queued Work Items and dropped the cursor, and it
discarded the proposed list's truncation flag. A team with 26 items
awaiting review silently lost one from the Now page and the VS Code Now
view.

Now pages each team and state to the end, up to 200 per team and state,
and the response gains truncatedStates naming every state that holds
more than it lists, proposals included. The browser shows "N+" counts
and "Show N+ more in Work/Proposed" when a state is truncated; the VS
Code Now view shows "N+" and a row that opens the full list.

VIK-1760

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
chore(site): re-record the demo replay with complete Now waiting lists
Some checks failed
[Workflow] On Pull Request / release-policy (pull_request) Successful in 21s
[Workflow] On Pull Request / checks (pull_request) Successful in 7m2s
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
f29fb236f9
The Now API response in the deterministic demo now carries truncatedStates.

VIK-1760

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(vloer): serve the agent host's WebSocket through ws
Some checks are pending
[Workflow] On Pull Request / checks (pull_request) Successful in 6m40s
[Workflow] On Pull Request / warnings (pull_request) Waiting to run
[Workflow] On Pull Request / release-policy (pull_request) Successful in 31s
5b20156eae
The agent host's hand-written RFC 6455 parser accepted a text frame
with a reserved bit set, invalid UTF-8, a fragmented ping and an orphan
continuation frame without closing, and send() ignored socket.write()
backpressure, so a peer that stopped reading grew server memory.

websocket.ts now completes the upgrade through a ws WebSocketServer in
noServer mode (no compression, no subprotocol, maxPayload 16 MiB,
one-second closeTimeout) and keeps the connection interface the host
uses. Protocol violations close with 1002/1007/1009, and a send to a
peer with more than 16 MiB unread disconnects it. The token check on
the upgrade is unchanged. ws 8.22.0 is pinned as the one runtime
dependency (ADR 0036, amending ADR 0002 and 0012); check.mjs accepts
only ADR-mapped, exact-pinned runtime dependencies, license:check
requires a permitted licence, a NOTICE line and an npm ci --omit=dev
in the Dockerfile, and the image now installs it.

VIK-1723

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Make real work and evidence the visual centerpiece with monumental type, a connected paper scene, a dark technical chapter and a red human-control statement.

Keep the existing brand geometry and truthful product boundaries. Add keyboard-operated mobile paper views, preserve focus across reflow, and respect reduced motion. Validate repository gates, 65 site tests, ten-page axe scans and desktop/mobile Lighthouse.
fix(ploeg): keep edited but unpublished writer work from counting as done
Some checks are pending
[Workflow] On Pull Request / checks (pull_request) Successful in 6m53s
[Workflow] On Pull Request / warnings (pull_request) Waiting to run
[Workflow] On Pull Request / release-policy (pull_request) Successful in 23s
2392960e1a
A writer that changed files and published nothing completed its Work
Item. resolveOutcome maps a clean exit with no pull request to
no_change_needed, and the ACP adapter itself reports no_change_needed
when no edit tool call completed, so an agent that edited through a
shell command looked like it had nothing to do. BuildMutatedWithoutPR,
which would have marked the run stuck, was never called.

The worker now records the commit a writer starts from and, when the
writer's outcome resolves to no_change_needed, compares the checkout
with it: uncommitted changes (git status, honouring .gitignore) or
commits on any local branch that are not on a remote end the Run stuck
with the paths and commits named. An unchanged checkout still completes.
The check sees edits from any tool, so BuildMutatedWithoutPR and the
changedFiles helper only it used are removed.

VIK-1737

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): back off managed accounts the sweep cannot resolve
Some checks are pending
[Workflow] On Pull Request / checks (pull_request) Successful in 7m2s
[Workflow] On Pull Request / warnings (pull_request) Waiting to run
[Workflow] On Pull Request / release-policy (pull_request) Successful in 27s
15924f7f9f
The block and settlement sweeps retried every stuck account on every
tick and logged it without saying which account or why. On the homelab
four unknown accounts from 2026-09-28 and 2026-09-29, whose mint never
produced a gateway key, logged "managed key block retry unresolved"
about 11,500 times a day, and one blocked account logged "managed
settlement unresolved" every 30 seconds.

Each sweep now retries a failing account after a wait that starts at
the sweep interval and doubles up to an hour, and a success clears it.
The block log names the run and the error, and both logs say when the
next attempt is. Account states and holds are unchanged: an account
that needs reconciliation still needs it, it is just reported at a
readable rate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(site): preserve design research and reusable product-site skill
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
0d3e30d5d3
docs(site): clarify missing evidence in the design skill
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Successful in 7m37s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 1m24s
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
2db23f0c34
Merge pull request 'feat(site): make Unfold work inspectable through an expressive redesign' (#179) from ryangr0/site-redesign into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
d7ee7a5465
Reviewed-on: #179
Merge pull request 'chore(site): re-record the demo replay with complete Now waiting lists' (#172) from ryangr0/vloer-now-waiting-truncation into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
5b71f0e31c
Reviewed-on: #172
Merge pull request 'fix(ploeg): settle late gateway charges after the first settlement' (#175) from ryangr0/ploeg-late-gateway-charges into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
0686130824
Reviewed-on: #175
fix(release): stop release commands from hanging on a prompt or a stuck tool
All checks were successful
[Workflow] On Pull Request / release-policy (pull_request) Successful in 1m45s
[Workflow] On Pull Request / checks (pull_request) Successful in 8m37s
[Workflow] On Pull Request / warnings (pull_request) Successful in 1s
25374d8b92
rc.34's Vloer publish sat silent for 29 minutes: git ls-remote against the
deleted GitHub webgrip/glide waited on a credential prompt. development
already points the publisher at webgrip/unfold and refuses redirected
writes; this closes the remaining gap for every subprocess, not just git.

command() now runs each tool with GIT_TERMINAL_PROMPT=0, a closed stdin
and a 600-second limit, so a prompt or a stuck tool fails the job with a
message. docs/operations/artifacts.md links the current repositories.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(site): serve the site on unfoldhq.dev
All checks were successful
[Workflow] On Pull Request / release-policy (pull_request) Successful in 55s
[Workflow] On Pull Request / checks (pull_request) Successful in 8m16s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
8a3915b474
Route unfoldhq.dev/* to the unfold-site Worker, the way twente.dev is
served: the apex already has a proxied record, so the route needs no DNS
change. site-release-tag now names the origin in SITE_ORIGIN instead of
reading the workers.dev subdomain from Cloudflare, so release builds
canonicalise to unfoldhq.dev and are indexable. workers_dev stays on.

www is deliberately not routed; its 301 to the apex belongs in
webgrip/cloudflare once the zone is managed there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge pull request 'fix(release): publish to the renamed unfold repositories without hanging' (#165) from ryangr0/release-publish-unfold-repo into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
e845695832
Reviewed-on: #165
Merge pull request 'fix(vloer): serve the agent host's WebSocket through ws' (#176) from ryangr0/vloer-websocket-ws into development
Some checks failed
[Workflow] On Source Change / site-release (push) Blocked by required conditions
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
f55ee16676
Reviewed-on: #176
chore(ploeg): retire the legacy Compose demo
Some checks failed
[Workflow] On Pull Request / release-policy (pull_request) Successful in 1m32s
[Workflow] On Pull Request / checks (pull_request) Failing after 5m31s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
58beb86f86
The Compose fixture, demo.sh and probe.sh predate managed worker
authentication: ploegd now refuses to start with their settings, and they
bound a known webhook secret on all interfaces. The unified local demo
(mise run demo-unified) is the qualified path; point the README and the
executor contract at it.

VIK-1759

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
test(ploeg): make the claimable-index plan test independent of statistics
All checks were successful
[Workflow] On Pull Request / release-policy (pull_request) Successful in 38s
[Workflow] On Pull Request / checks (pull_request) Successful in 11m2s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
0d70c43441
TestClaim_StillUsesClaimableIndex asked the planner for its preferred plan
on a work_items table holding one seeded row. Whether Postgres preferred
work_items_claimable or a sequential scan depended on the table's
statistics, which autoanalyze refreshes on its own schedule after the other
store tests insert and delete rows. Under parallel load the refresh landed
before the EXPLAIN often enough to flake: with accurate statistics for a
one-row table a sequential scan is genuinely cheaper.

The test now runs the EXPLAIN in a transaction with SET LOCAL
enable_seqscan = off. That removes the statistics dependency and asks the
question the test exists for: can the claim use the partial index at all?
If the query stops implying the index predicate (state = 'queued'), the
planner falls back to another index and the test fails.

The test also EXPLAINed a hand-copied query that had already drifted from
the real one (it lacked NOT operator_owned). The candidate subselect is now
the nextClaimableQuery constant, used by ClaimWithin and EXPLAINed by the
test, so a change to the claim query is what the test checks.

VIK-1824

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'fix(ploeg): keep edited but unpublished writer work from counting as done' (#177) from ryangr0/ploeg-acp-unpublished-edits into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
44164b8375
Reviewed-on: #177
Merge pull request 'fix(ploeg): back off managed accounts the sweep cannot resolve' (#178) from ryangr0/ploeg-sweep-retry-backoff into development
Some checks failed
[Workflow] On Source Change / site-release (push) Blocked by required conditions
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
6403e0ec44
Reviewed-on: #178
Merge pull request 'feat(site): serve the site on unfoldhq.dev' (#180) from ryangr0/site-unfoldhq-domain into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
72e1cafb2d
Reviewed-on: #180
Merge pull request 'chore(ploeg): retire the legacy Compose demo' (#181) from ryangr0/ploeg-retire-legacy-compose-demo into development
Some checks failed
[Workflow] On Source Change / warnings (push) Blocked by required conditions
[Workflow] On Source Change / release (push) Blocked by required conditions
[Workflow] On Source Change / site-release (push) Blocked by required conditions
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
c4a87486ce
Reviewed-on: #181
Merge pull request 'test(ploeg): make the claimable-index plan test independent of statistics' (#182) from ryangr0/ploeg-claimable-index-deterministic into development
Some checks failed
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
cb8267d9ca
Reviewed-on: #182
docs(adr): propose ADR-0018 Ploeg releases on its own schedule behind a tested contract version
Some checks failed
[Workflow] On Pull Request / release-policy (pull_request) Successful in 26s
[Workflow] On Pull Request / checks (pull_request) Successful in 10m42s
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
883985c025
The owner allowed Ploeg to release independently on 2026-10-03 and asked
what happens with incompatibility. ADR-0018 proposes to supersede ADR-0004's
single version: Ploeg and Vloer get their own release trains, Ploeg's
operator API carries a MAJOR.MINOR contract version (a /version route and a
Ploeg-Contract header), and Vloer declares the range it supports. Outside
that range, Vloer shows a blocking banner and makes no managed calls.
Contract changes are expand and contract, with a proposed 60-day
deprecation window. A cross-version qualification of real Ploeg and real
Vloer gates releases. An unfold-v bundle pins a qualified pair, and the
joint draft barrier from #146 moves to that bundle. The ADR lists five
owner questions and eight follow-up tickets. No code changes.

VIK-1756

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(vloer): stop the whole process tree before capturing a candidate
Some checks failed
[Workflow] On Pull Request / release-policy (pull_request) Successful in 23s
[Workflow] On Pull Request / checks (pull_request) Failing after 5m57s
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
4d279abbc1
The relay worker signalled only the direct harness child, replied
stopped:true without waiting after SIGKILL, and did not track
/__vloer/exec subprocesses at all. Grandchildren and exec subprocesses
kept running after a stop or a cancelled request, so files could still
change while captureInPlace exported the candidate.

The worker now starts the harness and every exec subprocess in its own
process group. A stop sends SIGTERM to the group, SIGKILL after a grace
period, and replies stopped:true only once no live member of the group
remains (zombies excluded, read from /proc). Otherwise it replies
stopped:false, /__vloer/run refuses to start a new child, and
captureInPlace refuses the export as stop_unconfirmed. Exec groups are
stopped on request cancellation, on an optional timeoutMs (the warm
sandbox clone now passes one), when the leader exits, and when the
worker shuts down.

VIK-1743

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): read a writer's branch on the forge before it counts as no change or updated
Some checks failed
[Workflow] On Pull Request / release-policy (pull_request) Successful in 34s
[Workflow] On Pull Request / checks (pull_request) Successful in 10m21s
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
7fc6f8d988
The unpublished-work guard from #177 compared a writer's checkout with
its starting commit, but only when the Run resolved to no_change_needed,
and it trusted an empty pull request lookup. Four cases still ended as
complete:

- a writer whose open pull request already existed was credited
  pr_updated while its edits or commits stayed in the clone;
- a failed pull request lookup read as "no pull request", so a clean
  checkout became no_change_needed and the Work Item done;
- a writer that pushed its branch but opened no pull request was
  reported with a wrong reason (its pushed commits counted as not on
  the forge, because the depth-limited clone tracks only the base);
- nothing distinguished "the branch moved during the Run" from "the
  branch was already there".

The worker now records where the Run's branch stands on the forge
(git ls-remote) next to the starting commit, before the harness runs.
When a writer would end no_change_needed or pr_updated it reads the
branch again. A failed pull request lookup or branch read is stuck
("unknown"); a branch that moved without a pull request is stuck; a
pr_updated whose branch moved keeps pr_updated; otherwise uncommitted
paths (anything git status lists, so the repository's .gitignore
decides what is build output) or local commits that neither the remote
branch nor the base contains are stuck, naming them. pr_opened,
failed and stuck outcomes, and readers, are untouched.

Tests drive the real worker with a fake ACP agent against a
git-http-backend forge: edit-tool, shell and base-branch commits, a
pushed branch without a pull request, local-only changes under an open
pull request, a real push to it, a failed forge read, untracked output
the repository does not ignore, a reader that leaves files, and a
harness failure or pod termination that also edited the checkout.

VIK-1737

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'docs(adr): propose ADR-0018 Ploeg releases on its own schedule behind a tested contract version' (#183) from ryangr0/adr-ploeg-independent-releases into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
0abd6906f0
Reviewed-on: #183
docs(ploeg): propose ADR-0060 durable webhook inbox and publication outbox
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Successful in 11m37s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 33s
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
28876b5513
Forge dedup marks a delivery seen before its effects run, so a crash or
error after the insert loses the effects and the honest redelivery is
ignored. GitLab, Vikunja and ClickUp webhooks have no dedup at all, and
requested-changes rows have no unique key. Review findings, budget-stop
notices and tracker write-backs are published best-effort, after the
transition, and nothing retries them once the Shift is closed.

The record proposes a webhook_inbox table (verify, insert, 202; a
SKIP LOCKED worker with backoff and dead letter; content keys for
providers without delivery ids; conflicts surfaced as 409) with
idempotent effects per event kind, and a publication_outbox written in
the lifecycle transaction, delivered with deterministic logical ids and
a remote marker so a crash between send and ack does not duplicate.
It adds metrics and operator API fields for stuck rows, and lists six
owner questions.

VIK-1727
VIK-1728
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each claim the marketing site makes about credentials and execution gets a
row: the settings it needs, whether they are the chart default, and the test
or source that shows it. Per-Run Forgejo push tokens need botPasswordSecret,
GitLab writers always share one token, key and token isolation are off by
default, Forgejo tokens never expire, and Vloer still executes the Runs it
starts in shared mode.

VIK-1749

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(site): name the configuration each security and execution claim needs
Some checks are pending
[Workflow] On Pull Request / checks (pull_request) Waiting to run
[Workflow] On Pull Request / warnings (pull_request) Blocked by required conditions
[Workflow] On Pull Request / release-policy (pull_request) Waiting to run
3ddf79e5a2
The site said every Run has credentials only for its own work and that they
expire, that Ploeg executes managed Runs and that every Vloer Run goes
through Ploeg. By default writers share one push token that never expires,
GitLab has no per-Run token, key isolation is off, Ploeg's executor is off,
and Vloer still executes the Runs it starts. Each claim now names the
setting it holds for or is labelled planned, in English and Dutch, and the
guardrails section links the capability matrix.

VIK-1749

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge pull request 'fix(vloer): stop the whole process tree before capturing a candidate' (#184) from ryangr0/vloer-stop-process-tree into development
Some checks failed
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
25f025253b
Reviewed-on: #184
Merge pull request 'docs(ploeg): propose ADR-0060 durable webhook inbox and publication outbox' (#185) from ryangr0/ploeg-adr-webhook-inbox-and-outbox into development
Some checks failed
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
[Workflow] On Docs Change / authorize-publication (push) Successful in 0s
[Workflow] On Docs Change / techdocs (push) Successful in 2m29s
[Workflow] On Docs Change / generate-documentation (push) Successful in 0s
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / Build (Zensical) + sync to Garage web (push) Has been cancelled
bc4bd3ad45
Reviewed-on: #185
fix(ploeg): correct first deploys reported out of order and finish deploy checks without a new deploy
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
50ab325bb5
Deploy candidates excluded every pull request already marked for the
environment, so when deploy D1 (t1) was reported after D2 (t2 > t1) the
pull request kept t2 as its first deploy: MarkDeployed could replace it
with the earlier time, but the candidate was never offered. One report
also stopped after 50 candidates or 20 seconds, and failed comparisons
waited for the next deploy of that environment.

A pull request is now a candidate unless the environment already has a
deploy of it at or before this deploy's time, so an earlier deploy
reported late corrects the first-deploy time. Migration 0037 records each
conclusive comparison per (deploy, pull request) in deployment_checks, so
repeated reports compare nothing again, and keeps a deploy pending until
every candidate was compared. ploegd sweeps pending deploys every minute
under a five-minute claim: a pass that compared something resumes at
once, a pass where every comparison failed waits one minute doubling to
an hour, and twelve failed passes in a row give the deploy up. ADR-0047,
the deploy-api contract and the pipeline how-to describe the new
behavior.

VIK-1763

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Formula 2026.2 took 1 off review per changes-requested review and 0.5 per
human review round beyond the first, including approved and comment-only
rounds, so a more careful review lowered the card. A cost that was not
reported, nothing authorized, or a board without gates left budgetShare or
defectBounces null and cost nothing, so a card with unknown delivery facts
could score and label as well as one with known clean facts. The concept
page promised that a formula change never re-grades old cards, while Ploeg
computes every grade on read under the current formula.

Formula 2026.3 (ADR-0061, proposed) takes 1 off review per rework round, a
distinct (play, head commit) on which a human requested changes; approvals,
comments and extra rounds cost nothing. inputs.missing names each input
Ploeg can collect but has no fact for on the card; such a subgrade is at
most 9 and the card earns no label. inputs.review.reworkRounds is new. The
grade stays computed on read with no stored snapshot, and the docs now say
so: the card names the formula, and a formula change re-grades every card
read after it. The operator-api schema, contracts README, ADR index, ADR-0050
and ADR-0052 cross-references, the Run Cards page and the glossary follow.

VIK-1751

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ploeg now grades under formula 2026.3, which this Vloer could not describe:
the Grade tab fell back to "cannot describe", the condition rows only read
"No confirmed crack" for 2026.2, and the new reworkRounds and missing
inputs were dropped by the card proxy.

The proxy passes review.reworkRounds (null when an older Ploeg did not send
it) and inputs.missing (absent when not sent). The Grade tab describes
2026.3, lists rework rounds, marks each missing input as "Missing for this
card" with its cap, and shows an Evidence row, complete or incomplete, only
when Ploeg sent a missing list. Condition treats 2026.2 and 2026.3 alike.
The demo's illustrative grades stay on formula 2026.2, so their rework
rounds are null.

VIK-1751

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
chore(site): re-record the demo replay with the grade formula 2026.3 card model
Some checks failed
[Workflow] On Pull Request / release-policy (pull_request) Successful in 1m2s
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
20be1721c2
Vloer's card model now describes grade formula 2026.3 and the inputs a
grade missed, and the demo's illustrative 2026.2 grades carry a null
reworkRounds, so the hosted replay no longer matched what Vloer serves.
Re-recorded with mise run demo-record: the card model hash, the replay
hash, the commit, reworkRounds on the demo grades and recorded test
timings change.

VIK-1751

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A read-only audit of every Ploeg and Vloer destination after the history
rewrite and the move to webgrip/unfold. Every version up to 0.4.0-rc.34
is taken somewhere, and Ploeg's withdrawn 1.0.0-rc.1 is still published
in the Go module proxy, GHCR and Forgejo.

Of 66 public Go module versions, 35 still download from the origin with
the recorded bytes, 14 now differ from the checksum database (a direct
download fails with SECURITY ERROR) and 17 exist only in the proxy.
@latest resolves to v0.2.0. The record keeps the pre-rewrite to current
source and export mapping, the joint train's completion state and the
canonical identities, including the OIDC claim of the rc.34 signing job.

ADR-0018 gets a dated note: the highest Go version is v0.4.0-rc.32, not
rc.34, and 0.5.0-rc.1 clears every floor. The cutover playbook's dead
GitHub link and signing row now name webgrip/unfold.

VIK-1794
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
New component trains would restart from ploeg-v0.3.0-rc.7 and
vloer-v0.3.0-rc.16 and compute versions the joint train already
published. An orphaned release tag also let semantic-release recompute
an existing version and push a duplicate release commit before the tag
step failed.

scripts/release-floors.json records each application's floor
(0.4.0-rc.34 for both), the withdrawn versions above it and the tag
prefixes that carried it. The release policy now refuses a computed
version at or below either floor or any existing tag, including tags no
longer reachable from development. Both publishers refuse a version at
or below their floor before reading a registry or the forge. Python and
JavaScript share one table of cases.

VIK-1794
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The preflight read the deleted Forgejo repositories webgrip/ploeg and
webgrip/de-vloer and raised on their 404, so the next release preview
would have stopped there. A rename redirect also passed as the
canonical repository, because the API read followed it.

The preflight now requires the Forgejo and GitHub APIs to answer as
webgrip/unfold itself. A retired name the signing role still binds
(webgrip/glide, webgrip/ploeg, webgrip/de-vloer) passes when it is gone
or redirects, and stops the preflight when it exists again with Actions
enabled. The checks run in main(), so the script is testable.

VIK-1794
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(vloer): point the extension's repository links at webgrip/unfold
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
43e07e927f
Every published VSIX names the deleted webgrip/de-vloer as its homepage,
repository, issue tracker and Q&A page, so those links on Open VSX are
dead. They now name webgrip/unfold and the extension's directory. The
extension's identity (webgrip.de-vloer) is unchanged.

VIK-1794
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(ploeg): settle stopped Work Items whose tracker task was closed
Some checks are pending
[Workflow] On Pull Request / checks (pull_request) Waiting to run
[Workflow] On Pull Request / warnings (pull_request) Blocked by required conditions
[Workflow] On Pull Request / release-policy (pull_request) Waiting to run
4dd9849767
A task closed in the tracker withdrew its Work Item only while the item
was queued and no Run had started. A needs_human or awaiting_review item
whose task a person closed by hand stayed in Vloer's "Needs you" list
indefinitely: VIK-1279 was done and merged, yet still showed "Needs you".

The close webhook now settles a stopped item to withdrawn with reason
withdrawn_closed, audited as webhook:<provider>. An item with a running
Run, or one owned by an operator execution, is left alone. Ploeg posts
no tracker comment for this and leaves any open pull request open.

The Shift sweep catches missed close webhooks: it re-reads the tracker
task of each stopped item at most once per 15 minutes (migration 0036
adds work_items.tracker_checked_at) and settles the ones the tracker
reports done. FetchItem now reports that through WorkItem.Closed for
Vikunja and ClickUp. A re-opened task does not bring the item back;
assigning it again starts new work as before.

VIK-1615
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The operator API's pullRequest now carries changesRequestedBy: each
distinct forge login that asked for changes, ordered by its newest
request first, and an empty array when nobody did. It is aggregated in
the same SQL statement as humanChangesRequested, which stays unchanged
for older consumers. operator-api.v1 declares the field optional.

VIK-1601
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
style(ploeg): gofmt the changesRequestedBy query concatenation
Some checks failed
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
25c0d268e0
VIK-1601
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
feat(site): deploy candidates to staging.unfoldhq.dev and manage the zone as code
Some checks are pending
[Workflow] On Pull Request / checks (pull_request) Waiting to run
[Workflow] On Pull Request / warnings (pull_request) Blocked by required conditions
[Workflow] On Pull Request / release-policy (pull_request) Waiting to run
f6c5e9e590
Release candidates from development now deploy to staging.unfoldhq.dev
(Worker unfold-site-staging, wrangler env staging); stable releases deploy
to unfoldhq.dev, ready for when Unfold gets a main branch. This is the
twente.dev split. Staging builds are noindex. Both Workers bind the
existing sign-up database, because staging is the only live site.

The unfoldhq.dev zone lives in apps/site/ops/dns as DNSControl config,
as twente.dev keeps its zone: a proxied AAAA 100:: for staging, a www to
apex 301, and IGNOREs for the apex and www placeholders the zone came
with. on_dns_change.yml previews and pushes from development and checks
drift daily, all skipped until CLOUDFLARE_DNS_TOKEN exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge pull request 'feat(site): deploy candidates to staging.unfoldhq.dev and manage the zone as code' (#196) from ryangr0/site-staging-and-dns into development
Some checks failed
[Workflow] On Docs Change / verify-publication (push) Blocked by required conditions
[Workflow] On Docs Change / techdocs (push) Waiting to run
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On DNS Change / authorize-dns (push) Has been cancelled
[Workflow] On DNS Change / dns-preview (push) Has been cancelled
[Workflow] On DNS Change / dns-push (push) Has been cancelled
[Workflow] On DNS Change / dns-drift (push) Has been cancelled
[Workflow] On Source Change / checks (push) Successful in 6m40s
[Workflow] On Source Change / warnings (push) Successful in 1s
[Workflow] On Source Change / release (push) Successful in 42s
[Workflow] On Source Change / site-release (push) Successful in 27s
c4241d6f0f
Reviewed-on: #196
chore(release): unfold-v0.4.0-rc.35 [skip ci]
All checks were successful
[Workflow] On Release Published / parse-release-tag (release) Successful in 1s
[Workflow] On Release Published / site-release-tag (release) Successful in 1s
[Workflow] On Release Published / Vloer - Publish Helm chart (Harbor) (release) Successful in 10s
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor) (release) Successful in 6m3s
[Workflow] On Release Published / Vloer - Publish VS Code extension (release) Successful in 45s
[Workflow] On Release Published / Ploeg - Publish Helm chart (Harbor) (release) Successful in 10s
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor)-1 (release) Successful in 7m36s
[Workflow] On Release Published / site-deploy-staging (release) Successful in 0s
[Workflow] On Release Published / Deploy preview (release) Has been skipped
[Workflow] On Release Published / Deploy production (release) Has been skipped
[Workflow] On Release Published / site-deploy-production (release) Successful in 0s
[Workflow] On Release Published / Vloer - Verify and publish all destinations (release) Successful in 2m21s
[Workflow] On Release Published / Ploeg - Distribute (Harbor) (release) Successful in 6m6s
[Workflow] On Release Published / Ploeg - Sign & Attest (Harbor) (release) Successful in 14s
[Workflow] On Release Published / Ploeg - Verify and publish all destinations (release) Successful in 1m26s
c2344c64b7
## [unfold-v0.4.0-rc.35](https://forgejo.webgrip.dev/webgrip/unfold/compare/unfold-v0.4.0-rc.34...unfold-v0.4.0-rc.35) (2026-10-03)

### Added

* **site:** deploy candidates to staging.unfoldhq.dev and manage the zone as code ([f6c5e9e](f6c5e9e590))
* **site:** give Unfold an expressive folded-paper identity ([5a16666](5a166663a9))
* **site:** redesign Unfold around inspectable agent work ([5e75e71](5e75e7195f))
* **site:** serve the site on unfoldhq.dev ([8a3915b](8a3915b474))

### Fixed

* **ploeg:** back off managed accounts the sweep cannot resolve ([15924f7](15924f7f9f))
* **ploeg:** decode forge proxy paths and limit them to what the Role needs ([1be4a65](1be4a650c6))
* **ploeg:** keep edited but unpublished writer work from counting as done ([2392960](2392960e1a))
* **ploeg:** replay a finished outcome that carries a checkpoint ([ed241ee](ed241eed7a))
* **ploeg:** route ClickUp work by tags and withdraw it when the task closes ([0330f0f](0330f0f503))
* **ploeg:** settle late gateway charges after the first settlement ([746ad48](746ad48209))
* **ploeg:** tie per-Run forge tokens to a live Lease and always revoke them ([da2f3a2](da2f3a2240))
* **release:** publish to webgrip/unfold and fail fast on dead addresses ([4637099](4637099afb))
* **vloer:** list every waiting Work Item on Now or say how many are hidden ([7d5331a](7d5331a7cb))
* **vloer:** say what actually happened when a session completes ([04e6ccf](04e6ccf10b))
* **vloer:** serve the agent host's WebSocket through ws ([5b20156](5b20156eae))
* **vloer:** stop the whole process tree before capturing a candidate ([4d279ab](4d279abbc1))

### Docs

* **ploeg:** propose ADR-0059 delivery facts come from the forge ([6169e40](6169e403a8)), references [#152](#152)
* **ploeg:** propose ADR-0060 durable webhook inbox and publication outbox ([28876b5](28876b5513))
* **site:** preserve design research and reusable product-site skill ([0d3e30d](0d3e30d5d3))

### Tests

* **ploeg:** make the claimable-index plan test independent of statistics ([0d70c43](0d70c43441))

### Internal

* **ploeg:** retire the legacy Compose demo ([58beb86](58beb86f86))
* **site:** re-record the demo replay with complete Now waiting lists ([f29fb23](f29fb236f9))
* **site:** re-record the demo replay with the truthful completion message ([26b8d85](26b8d85f15))
chore(release): unfold-site-v0.1.0-rc.9 [skip ci]
Some checks failed
[Workflow] On Release Published / parse-release-tag (release) Has been skipped
[Workflow] On Release Published / site-release-tag (release) Successful in 1s
[Workflow] On Release Published / Vloer - Publish Helm chart (Harbor) (release) Has been skipped
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor) (release) Has been skipped
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor)-1 (release) Has been skipped
[Workflow] On Release Published / Vloer - Publish VS Code extension (release) Has been skipped
[Workflow] On Release Published / Ploeg - Publish Helm chart (Harbor) (release) Has been skipped
[Workflow] On Release Published / Ploeg - Distribute (Harbor) (release) Has been skipped
[Workflow] On Release Published / Deploy preview (release) Has been skipped
[Workflow] On Release Published / site-deploy-production (release) Successful in 0s
[Workflow] On Release Published / Vloer - Verify and publish all destinations (release) Has been skipped
[Workflow] On Release Published / Ploeg - Sign & Attest (Harbor) (release) Has been skipped
[Workflow] On Release Published / Ploeg - Verify and publish all destinations (release) Has been skipped
[Workflow] On Release Published / site-deploy-staging (release) Failing after 0s
[Workflow] On Release Published / Deploy production (release) Failing after 4m23s
13b17d5c4e
## [unfold-site-v0.1.0-rc.9](https://forgejo.webgrip.dev/webgrip/unfold/compare/unfold-site-v0.1.0-rc.8...unfold-site-v0.1.0-rc.9) (2026-10-03)

### Added

* **site:** deploy candidates to staging.unfoldhq.dev and manage the zone as code ([f6c5e9e](f6c5e9e590))
* **site:** give Unfold an expressive folded-paper identity ([5a16666](5a166663a9))
* **site:** redesign Unfold around inspectable agent work ([5e75e71](5e75e7195f))
* **site:** serve the site on unfoldhq.dev ([8a3915b](8a3915b474))

### Docs

* **site:** preserve design research and reusable product-site skill ([0d3e30d](0d3e30d5d3))

### Internal

* **site:** re-record the demo replay with complete Now waiting lists ([f29fb23](f29fb236f9))
* **site:** re-record the demo replay with the truthful completion message ([26b8d85](26b8d85f15))
feat(deps): update all non-major dependencies
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Failing after 38s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 17s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
renovate/stability-days Updates have met minimum release age requirement
renovate/artifacts Artifact file update failure
711a481495
Merge pull request 'feat(deps): update all non-major dependencies' (#160) from renovate/all-non-major into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Successful in 0s
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Failing after 46s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
e5c57a1695
Reviewed-on: #160
Merge pull request 'fix(site): name the configuration each security and execution claim needs' (#186) from ryangr0/site-claims-name-their-profile into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Failing after 48s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
f25b83d9a4
Reviewed-on: #186
Merge pull request 'fix(ploeg): correct first deploys reported out of order and finish deploy checks without a new deploy' (#187) from ryangr0/ploeg-out-of-order-deploys into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Failing after 48s
[Workflow] On Source Change / warnings (push) Successful in 1s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
23581922f8
Reviewed-on: #187
Merge pull request 'fix(ploeg): read a writer's branch on the forge before it counts as no change or updated' (#188) from ryangr0/ploeg-writer-branch-delivery-guard into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
45ebdce5ce
Reviewed-on: #188
Merge pull request 'chore(site): re-record the demo replay with the grade formula 2026.3 card model' (#193) from ryangr0/ploeg-card-grade-review-rounds into development
Some checks failed
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
366484b566
Reviewed-on: #193
Merge pull request 'fix(release): record release floors and refuse versions at or below them' (#197) from ryangr0/release-floors-and-identity into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Successful in 0s
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
188f3aab0d
Reviewed-on: #197
Merge pull request 'fix(ploeg): settle stopped Work Items whose tracker task was closed' (#200) from ryangr0/ploeg-settle-closed-stopped-items into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Successful in 0s
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
91a0bce473
Reviewed-on: #200
Merge pull request 'feat(ploeg): expose who asked for changes on a pull request' (#203) from ryangr0/ploeg-changes-requested-by into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Successful in 1s
[Workflow] On Docs Change / techdocs (push) Successful in 1m38s
[Workflow] On Docs Change / generate-documentation (push) Successful in 0s
[Workflow] On Source Change / checks (push) Failing after 47s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
[Workflow] On Docs Change / Build (Zensical) + sync to Garage web (push) Successful in 2m16s
[Workflow] On Docs Change / deploy-docs-site (push) Successful in 0s
[Workflow] On Docs Change / verify-publication (push) Successful in 29s
9c1d53f01f
Reviewed-on: #203
711a4814 moved uv to 0.12.22 in mise.toml but left the lockfile at
0.12.21. mise-action runs `mise install --locked`, which refuses a
version the lockfile does not hold, so the checks job has failed before
any gate since that commit, on development and on every pull request.

`mise lock uv` (mise 2026.9.18) records 0.12.22 for all seven platforms.
The openspec lock files it deletes under .mise/locks are kept.

Refs: https://github.com/webgrip/unfold/issues/2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the vendored apps/ploeg tree with a gitlink to
https://github.com/ploeg-hq/ploeg.git at v0.1.0
(87f8dc45a0ea768c6ab95196d8b99d481df10c65), which was extracted from
this repository at 9c1d53f.

- mise run setup, the verify, docs and demo checkouts and the TechDocs
  prepare commands initialise the submodule.
- scripts/ploeg-pin.mjs refuses vendored source, another repository and
  an uninitialised or modified checkout. The ploeg-pin job also requires
  the pinned commit on Ploeg's main, and the release waits for it.
- verify runs Ploeg's own scripts/verify.sh at the pin and compiles the
  unified demo helper, which now imports github.com/ploeg-hq/ploeg.
- The docs build still renders Ploeg's pinned pages, but no longer
  regenerates or validates Ploeg's configuration reference, domain pages
  or decision ledger, and it links Ploeg's source files on GitHub at the
  pinned commit. The combined glossary keeps a decision that a pinned
  model cites by URL instead of mangling it into a relative path.
- Renovate ignores apps/ploeg, drops the Go overlay and leaves the pin
  to people. CI no longer builds the ploegd image context.

Refs: https://github.com/webgrip/unfold/issues/2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unfold's train now versions Vloer only; github.com/ploeg-hq/ploeg
versions and publishes Ploeg with GitHub Actions.

- on_release_published.yml drops the Ploeg chart, image, signing and
  distribution jobs. The Vloer publisher is the only one, so it takes
  the GitHub release out of draft itself.
- publish_release.py and publish_chart.py refuse ploeg before any Git,
  network or file access. The Go module export to github.com/webgrip/ploeg
  is gone, including the call that disabled GitHub Actions there.
- release-prepare.mjs and apps/.releaserc.cjs touch only Vloer's chart,
  and a commit scoped ploeg never releases Unfold.
- release-floors.json keeps Ploeg's floor and withdrawn 1.0.0-rc.1, marks
  the component retired after 0.4.0-rc.35, and both loaders refuse a
  train that versions a retired component.
- The release preflight no longer checks registry access for ploegd or
  charts/ploeg.

Refs: https://github.com/webgrip/unfold/issues/2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs: record that Unfold pins Ploeg and releases only Vloer
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 42s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 16s
[Workflow] On Pull Request / checks (pull_request) Successful in 7m8s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
1bca2ac69b
ADR-0019 records the consumer side of the separation approved in
webgrip/unfold#1: Ploeg lives in github.com/ploeg-hq/ploeg, Unfold pins
it as a submodule, and the unfold-v train versions Vloer only. It
supersedes ADR-0004; ADR-0001 and ADR-0018 get dated notes.

README, AGENTS.md, NOTICE, the team-silver skill and the current pages
now say where Ploeg lives, how the pin moves, what Unfold releases and
where Ploeg's artifacts come from.

Refs: https://github.com/webgrip/unfold/issues/2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build(docs): treat Ploeg's archived history pages as records
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 24s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 29s
[Workflow] On Pull Request / checks (pull_request) Successful in 2m22s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
3870a8b560
Ploeg's main keeps its pre-separation release history in
docs/history/legacy-changelog.md, a record no current page links. Unfold
renders Ploeg's docs from the pinned commit, so any pin past v0.1.0 failed
the docs build with that page as an orphan. ploeg/history now joins
ploeg/backlog as a record path: kept, marked "not current guidance" and
left out of the nav and search.

Refs: https://github.com/webgrip/unfold/issues/2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge pull request 'build(ploeg): consume Ploeg from ploeg-hq/ploeg and stop publishing it from Unfold' (#206) from ryangr0/chore/ploeg-submodule-cutover into development
All checks were successful
[Workflow] On Docs Change / authorize-publication (push) Successful in 1s
[Workflow] On Docs Change / techdocs (push) Successful in 1m8s
[Workflow] On Docs Change / generate-documentation (push) Successful in 0s
[Workflow] On Source Change / ploeg-pin (push) Successful in 34s
[Workflow] On Docs Change / Build (Zensical) + sync to Garage web (push) Successful in 3m59s
[Workflow] On Docs Change / deploy-docs-site (push) Successful in 0s
[Workflow] On Source Change / checks (push) Successful in 8m6s
[Workflow] On Source Change / warnings (push) Successful in 1s
[Workflow] On Docs Change / verify-publication (push) Successful in 1m37s
[Workflow] On Source Change / release (push) Successful in 1m58s
[Workflow] On Source Change / site-release (push) Successful in 47s
fd3099214d
Reviewed-on: #206
chore(release): unfold-v0.4.0-rc.36 [skip ci]
All checks were successful
[Workflow] On Release Published / parse-release-tag (release) Successful in 0s
[Workflow] On Release Published / site-release-tag (release) Successful in 1s
[Workflow] On Release Published / Vloer - Publish Helm chart (Harbor) (release) Successful in 15s
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor) (release) Successful in 5m54s
[Workflow] On Release Published / Vloer - Publish VS Code extension (release) Successful in 55s
[Workflow] On Release Published / site-deploy-staging (release) Successful in 0s
[Workflow] On Release Published / Deploy preview (release) Has been skipped
[Workflow] On Release Published / Deploy production (release) Has been skipped
[Workflow] On Release Published / site-deploy-production (release) Successful in 0s
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor)-1 (release) Successful in 9m45s
[Workflow] On Release Published / Vloer - Verify and publish all destinations (release) Successful in 2m30s
f15b3c87af
## [unfold-v0.4.0-rc.36](https://forgejo.webgrip.dev/webgrip/unfold/compare/unfold-v0.4.0-rc.35...unfold-v0.4.0-rc.36) (2026-10-04)

### Added

* **deps:** update all non-major dependencies ([711a481](711a481495))
* **ploeg:** expose who asked for changes on a pull request ([b3a3a5c](b3a3a5c766))

### Fixed

* **ploeg:** correct first deploys reported out of order and finish deploy checks without a new deploy ([50ab325](50ab325bb5))
* **ploeg:** grade rework rather than review and say which inputs a grade missed ([8182249](8182249e38))
* **ploeg:** read a writer's branch on the forge before it counts as no change or updated ([7fc6f8d](7fc6f8d988)), references [#177](#177)
* **ploeg:** settle stopped Work Items whose tracker task was closed ([4dd9849](4dd9849767))
* **site:** name the configuration each security and execution claim needs ([3ddf79e](3ddf79e5a2))
* **vloer:** describe grade formula 2026.3 and show the inputs a grade missed ([4a81f47](4a81f472e6))
* **vloer:** point the extension's repository links at webgrip/unfold ([43e07e9](43e07e927f))

### Docs

* record that Unfold pins Ploeg and releases only Vloer ([1bca2ac](1bca2ac69b))

### Build

* **ploeg:** consume Ploeg from ploeg-hq/ploeg as a submodule pinned at v0.1.0 ([26a27b6](26a27b683d))

### CI

* **release:** stop versioning and publishing Ploeg from Unfold ([52a7c89](52a7c89f35))

### Style

* **ploeg:** gofmt the changesRequestedBy query concatenation ([25c0d26](25c0d268e0))

### Internal

* **release:** unfold-site-v0.1.0-rc.9 [skip ci] ([13b17d5](13b17d5c4e))
* **site:** re-record the demo replay with the grade formula 2026.3 card model ([20be172](20be1721c2))
chore(release): unfold-site-v0.1.0-rc.10 [skip ci]
Some checks failed
[Workflow] On Release Published / parse-release-tag (release) Has been skipped
[Workflow] On Release Published / site-release-tag (release) Successful in 0s
[Workflow] On Release Published / Vloer - Publish Helm chart (Harbor) (release) Has been skipped
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor) (release) Has been skipped
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor)-1 (release) Has been skipped
[Workflow] On Release Published / Vloer - Publish VS Code extension (release) Has been skipped
[Workflow] On Release Published / Deploy preview (release) Has been skipped
[Workflow] On Release Published / site-deploy-production (release) Successful in 0s
[Workflow] On Release Published / Vloer - Verify and publish all destinations (release) Has been skipped
[Workflow] On Release Published / site-deploy-staging (release) Failing after 0s
[Workflow] On Release Published / Deploy production (release) Has been skipped
59823dd6bb
## [unfold-site-v0.1.0-rc.10](https://forgejo.webgrip.dev/webgrip/unfold/compare/unfold-site-v0.1.0-rc.9...unfold-site-v0.1.0-rc.10) (2026-10-04)

### Added

* **deps:** update all non-major dependencies ([711a481](711a481495))

### Fixed

* **site:** name the configuration each security and execution claim needs ([3ddf79e](3ddf79e5a2))

### Internal

* **site:** re-record the demo replay with the grade formula 2026.3 card model ([20be172](20be1721c2))
feat(deps): update all non-major dependencies
Some checks failed
renovate/stability-days Updates have met minimum release age requirement
renovate/artifacts Artifact file update failure
[Workflow] On Pull Request / checks (pull_request) Failing after 40s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 22s
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 27s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
dbf40833cd
Moves apps/ploeg from v0.1.0 to 611b3f18 on ploeg-hq/ploeg main. It
merged ploeg-hq/ploeg#47, the Ploeg side of Unfold PR 195,
together with the other Ploeg pull requests ported from Unfold's open
pull requests (ploeg-hq/ploeg#47 to ploeg-hq/ploeg#54). Every
replacement pins this same commit, so they merge in any order and the
first one to land brings all of them.

Refs: https://github.com/ploeg-hq/ploeg/pull/47
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moves apps/ploeg from v0.1.0 to 611b3f18 on ploeg-hq/ploeg main. It
merged ploeg-hq/ploeg#52, the Ploeg side of Unfold PR 191,
together with the other Ploeg pull requests ported from Unfold's open
pull requests (ploeg-hq/ploeg#47 to ploeg-hq/ploeg#54). Every
replacement pins this same commit, so they merge in any order and the
first one to land brings all of them.

Refs: https://github.com/ploeg-hq/ploeg/pull/52
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Unfold side of "fix(ploeg): retry a failed reviewer and close
review_failed when no review came" (ploeg-hq/ploeg#47): the review guide
names the new close reason and says it still settles a delivered pull
request as awaiting_review.

Refs VIK-1304

Replaces-commit: bb9b3861fa (#195)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Moves apps/ploeg from v0.1.0 to 611b3f18 on ploeg-hq/ploeg main. It
merged ploeg-hq/ploeg#48, the Ploeg side of Unfold PR 198,
together with the other Ploeg pull requests ported from Unfold's open
pull requests (ploeg-hq/ploeg#47 to ploeg-hq/ploeg#54). Every
replacement pins this same commit, so they merge in any order and the
first one to land brings all of them.

Refs: https://github.com/ploeg-hq/ploeg/pull/48
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moves apps/ploeg from v0.1.0 to 611b3f18 on ploeg-hq/ploeg main. It
merged ploeg-hq/ploeg#50, the Ploeg side of Unfold PR 201,
together with the other Ploeg pull requests ported from Unfold's open
pull requests (ploeg-hq/ploeg#47 to ploeg-hq/ploeg#54). Every
replacement pins this same commit, so they merge in any order and the
first one to land brings all of them.

Refs: https://github.com/ploeg-hq/ploeg/pull/50
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Anyone could start an editor sign-in and collect a full workbench session
for whoever signed in through the link, which made the flow a phishing kit.
ADR-0037 records the approval page with a shared code, a separate
editor-scoped bearer credential with its own lifetime that the person can
see and revoke, the rate limits, the threat model (RFC 8628 remote phishing
by analogy) and the options considered. The owner approved the approach on
2026-10-03.

VIK-1718
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Moves apps/ploeg from v0.1.0 to 611b3f18 on ploeg-hq/ploeg main. It
merged ploeg-hq/ploeg#53, the Ploeg side of Unfold PR 204,
together with the other Ploeg pull requests ported from Unfold's open
pull requests (ploeg-hq/ploeg#47 to ploeg-hq/ploeg#54). Every
replacement pins this same commit, so they merge in any order and the
first one to land brings all of them.

Refs: https://github.com/ploeg-hq/ploeg/pull/53
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moves apps/ploeg from v0.1.0 to 611b3f18 on ploeg-hq/ploeg main. It
merged ploeg-hq/ploeg#49, the Ploeg side of Unfold PR 199,
together with the other Ploeg pull requests ported from Unfold's open
pull requests (ploeg-hq/ploeg#47 to ploeg-hq/ploeg#54). Every
replacement pins this same commit, so they merge in any order and the
first one to land brings all of them.

Refs: https://github.com/ploeg-hq/ploeg/pull/49
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chart pulled de-vloer and de-vloer-agent from Harbor and the
Dockerfiles pulled their hardened base through Harbor's proxy. The chart
now defaults to the ghcr.io release copies and the Dockerfiles to dhi.io;
CI and the release workflow still pass REGISTRY_DHI explicitly. The chart
README lists the images and the services live mode needs.

VIK-1758

Replaces-commit: 3fa37ead76 (#191)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
test(vloer): give the relay test's timed exec a load-scaled budget
All checks were successful
[Workflow] On Pull Request / checks (pull_request) Successful in 6m33s
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 34s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 21s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
a05d94726f
"an exec subprocess group is stopped when its relay request is cancelled
or its timeout passes" ran the timed exec with a fixed 500 ms budget, then
required both the child and the grandchild to have written before the
timeout stopped them. On a loaded runner a second Node process can take
longer than that to start, so the check saw 1 writer instead of 2 and
failed Unfold #207 and #212. Every other bound in this file scales with
VLOER_TEST_TIMEOUT_SCALE; this one now does too, from a 2 s base.

The assertion is unchanged. Pinned to one core at nice 10 next to a busy
loop with CI's scale of 2, the old test failed 4 of 4 runs (0 !== 2) and
the new one passed 6 of 6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ci(site): record the /demo replay in the site build instead of committing it
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 37s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 55s
[Workflow] On Pull Request / checks (pull_request) Successful in 8m34s
[Workflow] On Pull Request / warnings (pull_request) Successful in 1s
b10b6c0108
The committed recording conflicted between every two open pull requests
that touched Vloer, and a Vloer change failed verify until someone
re-recorded it. The site build now records the replay from the same
checkout, and every Unfold release candidate redeploys staging, so
staging's /demo follows the latest Vloer candidate. Production still
changes only with a stable site release. ADR-0012 and ADR-0015 record
the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(vloer): mark a pull request whose reviewer kept failing as unreviewed
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 1m13s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 15s
[Workflow] On Pull Request / checks (pull_request) Successful in 10m6s
[Workflow] On Pull Request / warnings (pull_request) Successful in 1s
d84285390c
Ploeg now closes a Shift review_failed when its reviewer Run failed and
its retries ran out. The pull request still waits for review, so the
item sits in Ready for review, where its row read "No agent verdict".

The review lane chip now reads "Agent review unavailable" with the
reason in its title, the Work Item page says no agent reviewed the pull
request, and the close reason reads "No agent reviewed it: the reviewer
kept failing" wherever Shift close reasons are listed.

VIK-1304

Replaces-commit: 146cf69fee (#195)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(vloer): say the agent stopped responding when an ACP watchdog stopped it
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 37s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 42s
[Workflow] On Pull Request / checks (pull_request) Successful in 7m1s
[Workflow] On Pull Request / warnings (pull_request) Successful in 1s
d263ce6de9
A Run whose summary says an ACP watchdog stopped it (the idle watchdog, the
prompt wall, or the older "acp agent stopped responding") now reads "The agent
stopped responding" instead of "The agent harness exited with an error ...
Read its log tail", and its stderr tail is shown under "Last lines it
printed". The cause comes from the summary before the failure_reason table;
other agent_error Runs keep their wording, and failure_reason stays
agent_error.

VIK-1595

Replaces-commit: 3077289fbc (#198)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
feat(vloer): warn when Runs hold budget Ploeg cannot release
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 33s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 40s
[Workflow] On Pull Request / checks (pull_request) Successful in 6m29s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
46819e9723
The summary now also reads Ploeg's unsettled-accounts list in its own
try, keeps only the rows of Teams the user may read and sums the count
and held total from those rows. A failure never fails the summary: it
reports "Could not be loaded", and an older Ploeg that answers 404
reports "Not reported by this Ploeg". The demo reports zero without
asking Ploeg.

Now shows a severe callout, "3 Runs hold budget Ploeg cannot release",
with the held total, a "Show the 3 Runs" disclosure linking each Work
Item and Ploeg's "Reconcile uncertainty" runbook. Insights shows the
same count and total in a "Cannot release" tile. Vloer offers no settle,
release or retry action.

VIK-1635

Replaces-commit: 8de8151274 (#201)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(vloer): make editor sign-in need approval and issue its own credential
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 36s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 40s
[Workflow] On Pull Request / checks (pull_request) Successful in 5m0s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
9ada416ab3
Signing in through an editor's link no longer completes the ticket. The
callback records who signed in and lands on an approval page that names the
request ("A VS Code editor is asking to sign in to <workbench> as you"),
shows the short code the editor also shows, and offers Approve and Deny.
Polling stays pending until approval; a denied, expired, consumed or
mismatched ticket yields nothing.

Approval issues a vle_ bearer token, stored only as a hash in
editor_credentials, scoped to keep it from approving or managing editors,
valid for thirty days, and listed under Settings > Signed-in editors where
the person can sign it out together with the Agent Host tokens it issued.
Ticket starts are limited per address and polls answer slow_down when they
come too fast. The extension shows the code, honours slow_down and sends
the credential as Authorization: Bearer.

VIK-1718
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs: say a Run's verification comes only from the worker's record
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 32s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 37s
[Workflow] On Pull Request / checks (pull_request) Successful in 5m55s
[Workflow] On Pull Request / warnings (pull_request) Successful in 1s
8c32a31bbd
The Unfold side of "fix(ploeg): never read a current Run's verification
from agent prose" (ploeg-hq/ploeg#53): the review guide says a missing
record reads "not recorded", including checks skipped because the Run was
cancelled or opened no pull request, and that only Runs stored before
VIK-1780 show their verification prose, labelled historical and unverified.

Refs VIK-1780

Replaces-commit: 4d79debc13 (#204)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs: say that only the worker reports whether a Run delivered
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 44s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 50s
[Workflow] On Pull Request / checks (pull_request) Successful in 6m14s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
59b9591fa6
The Unfold side of "fix(ploeg): only the worker says whether a Run
delivered a pull request" (ploeg-hq/ploeg#49): inside-a-run says the
worker reads delivery on the forge and drops delivery claims from the
agent's drop box. ADR-0059 is still proposed.

Refs VIK-1732

Replaces-commit: abb2e2ea2d (#199)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
build: keep Renovate off Vloer's chart image tag at its ghcr.io default
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 42s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 41s
[Workflow] On Pull Request / checks (pull_request) Successful in 5m39s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
4261f449ef
Vloer's chart now defaults to ghcr.io/webgrip/de-vloer, so the rule that
keeps Renovate from digest-pinning the empty, release-filled tag names that
image. This is the Unfold part of "fix(ploeg): make the chart install on a
cluster outside the homelab" that still applies: its Ploeg rules and its
verify.mjs Helm change moved to ploeg-hq/ploeg#52 with Ploeg's chart.

VIK-1758

Replaces-commit: d48e3ce377 (#191)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
feat(vloer): show tasks Ploeg could not start under Needs you
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 38s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 41s
[Workflow] On Pull Request / checks (pull_request) Successful in 6m47s
[Workflow] On Pull Request / warnings (pull_request) Successful in 1s
501a018f54
Now reads Ploeg's route-refusals list and shows a "Could not start"
group first inside Needs you. Each row names the tracker task, says in
plain words why routing refused it, with the repo/ labels its board
allows, and opens the task in its tracker. Vloer builds the link from
the task connection whose provider and project match, else the
configured tracker root, else offers no button.

A failed read shows an inline notice with Try again and leaves the other
sections alone; an older Ploeg that answers 404 shows nothing. The demo
shows one illustrative refusal and calls nothing.

VIK-1633

Replaces-commit: 3958fc77c7 (#202)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
build: lock openspec 1.14.0 in the root mise.lock
Some checks failed
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 27s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 15s
[Workflow] On Pull Request / checks (pull_request) Failing after 1m49s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
3c77f568d3
Renovate raised openspec in mise.toml but could not relock: its runner
has mise 2026.7.7, below this repository's min_version 2026.9.17, so
`mise lock` refused and CI's `mise install --locked` failed. Renovate's
npm manager also rewrote the generated 1.13.2 lock directory under
.mise/locks as if it were a package. mise 2026.9.18 relocked openspec
and pruned that stale directory; a cold `mise install --locked` passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unfold's documentation build checked every page under apps/ploeg/docs
as if Unfold owned it: links, anchors, nav reachability, line anchors
and front matter, plus lychee and Vale. Any new or renamed page in Ploeg
therefore broke Unfold's docs build when the pin moved, as the archived
history pages and the charter page already did.

Ploeg's own docs-check owns those pages now. Unfold still renders them
from the pin; a link that does not resolve in a Ploeg page points at the
pinned file on GitHub, so mkdocs --strict stays green. The nav names only
Ploeg's overview, runbook index and configuration reference. Unfold's own
pages, the combined glossary and the decision register still read Ploeg
at the pin. The site and source links name webgrip/unfold, and the
migration guide stops describing Ploeg's old distribution as current.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build(ploeg): let Renovate propose the Ploeg pin once a week
All checks were successful
[Workflow] On Pull Request / checks (pull_request) Successful in 5m45s
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 33s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 26s
[Workflow] On Pull Request / warnings (pull_request) Successful in 1s
d0cbb3b6cd
The pin moved only by hand. Renovate's git-submodules manager now opens
a weekly build(ploeg) pull request to the newest commit on Ploeg's main,
which .gitmodules names. It never merges one itself, since each move
changes the engine Vloer runs on, and Ploeg's CI-gated commits get no
soak. Its body says how to regenerate the combined glossary and the
decision register when Ploeg's model or ADR ledger changed.

Renovate also skips mise's generated .mise lock directories, whose
package.json it bumped as if it were a package in Unfold #205.

The runner must list the manager; homelab-cluster enables it for
repositories that opt in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build(site): lock pnpm 12.8.2 in the site's pnpm-lock.yaml
All checks were successful
[Workflow] On Pull Request / checks (pull_request) Successful in 10m1s
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 48s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 23s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
c707135ef1
pnpm 12 records the packageManager version in pnpm-lock.yaml. Renovate
raised packageManager to 12.8.2 without relocking, so CI's
`pnpm install --frozen-lockfile` refused the outdated lockfile.
`pnpm install --lockfile-only` with pnpm 12.8.2 changes only pnpm's own
entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge pull request 'test(vloer): give the relay test's timed exec a load-scaled budget' (#214) from ryangr0/fix/vloer-relay-timeout-headroom into development
Some checks failed
[Workflow] On Source Change / ploeg-pin (push) Successful in 42s
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
fc6fc9f41b
Reviewed-on: #214
Merge pull request 'ci(site): record the /demo replay in the site build instead of committing it' (#192) from ryangr0/site-record-demo-replay-at-build into development
Some checks failed
[Workflow] On Source Change / checks (push) Waiting to run
[Workflow] On Source Change / warnings (push) Blocked by required conditions
[Workflow] On Source Change / ploeg-pin (push) Waiting to run
[Workflow] On Source Change / release (push) Blocked by required conditions
[Workflow] On Source Change / site-release (push) Blocked by required conditions
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
9f2a5608c2
Reviewed-on: #192
Merge pull request 'fix(vloer): mark a pull request whose reviewer kept failing as unreviewed' (#207) from ryangr0/replace/195-reviewer-retry into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / ploeg-pin (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
7da69f5be7
Reviewed-on: #207
Merge pull request 'fix(vloer): say the agent stopped responding when an ACP watchdog stopped it' (#209) from ryangr0/replace/198-acp-watchdog-reason into development
Some checks failed
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / ploeg-pin (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
c6211dcbe0
Reviewed-on: #209
Merge pull request 'feat(vloer): warn when Runs hold budget Ploeg cannot release' (#211) from ryangr0/replace/201-unsettled-accounts into development
Some checks failed
[Workflow] On Source Change / checks (push) Waiting to run
[Workflow] On Source Change / warnings (push) Blocked by required conditions
[Workflow] On Source Change / ploeg-pin (push) Waiting to run
[Workflow] On Source Change / release (push) Blocked by required conditions
[Workflow] On Source Change / site-release (push) Blocked by required conditions
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
27d61c4e01
Reviewed-on: #211
Merge pull request 'chore(site): re-record the demo replay for the editor sign-in pages' (#194) from ryangr0/vloer-editor-sign-in-approval into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / ploeg-pin (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
d1506e055c
Reviewed-on: #194
Merge pull request 'docs: say a Run's verification comes only from the worker's record' (#208) from ryangr0/replace/204-verification-provenance into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / ploeg-pin (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
60596e1771
Reviewed-on: #208
Merge pull request 'docs: say that only the worker reports whether a Run delivered' (#210) from ryangr0/replace/199-delivery-from-worker into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Successful in 1s
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / ploeg-pin (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
db7e50e63b
Reviewed-on: #210
Merge pull request 'fix(vloer): default the chart and image builds to public registries' (#213) from ryangr0/replace/191-portable-chart into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Successful in 0s
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / ploeg-pin (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
[Workflow] On Source Change / checks (push) Successful in 9m29s
7c24aec610
Reviewed-on: #213
Merge pull request 'feat(vloer): show tasks Ploeg could not start under Needs you' (#212) from ryangr0/replace/202-route-refusals into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Successful in 0s
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / ploeg-pin (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
90d9fdeca7
Reviewed-on: #212
Merge pull request 'build: stop checking Ploeg's pinned pages in Unfold and let Renovate propose the pin' (#215) from ryangr0/chore/ploeg-docs-and-renovate into development
Some checks failed
[Workflow] On Source Change / warnings (push) Blocked by required conditions
[Workflow] On Source Change / ploeg-pin (push) Waiting to run
[Workflow] On Source Change / release (push) Blocked by required conditions
[Workflow] On Source Change / site-release (push) Blocked by required conditions
[Workflow] On Docs Change / authorize-publication (push) Successful in 3s
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
71ff5558b1
Reviewed-on: #215
Merge pull request 'feat(deps): update all non-major dependencies' (#205) from renovate/all-non-major into development
All checks were successful
[Workflow] On Docs Change / authorize-publication (push) Successful in 2s
[Workflow] On Docs Change / techdocs (push) Successful in 2m25s
[Workflow] On Docs Change / generate-documentation (push) Successful in 0s
[Workflow] On Source Change / ploeg-pin (push) Successful in 30s
[Workflow] On Source Change / checks (push) Successful in 9m54s
[Workflow] On Source Change / warnings (push) Successful in 1s
[Workflow] On Source Change / release (push) Successful in 1m0s
[Workflow] On Source Change / site-release (push) Successful in 40s
[Workflow] On Docs Change / Build (Zensical) + sync to Garage web (push) Successful in 2m53s
[Workflow] On Docs Change / deploy-docs-site (push) Successful in 0s
[Workflow] On Docs Change / verify-publication (push) Successful in 24s
1f27158eff
Reviewed-on: #205
chore(release): unfold-v0.4.0-rc.37 [skip ci]
Some checks failed
[Workflow] On Release Published / parse-release-tag (release) Successful in 0s
[Workflow] On Release Published / site-release-tag (release) Successful in 1s
[Workflow] On Release Published / Vloer - Publish Helm chart (Harbor) (release) Successful in 23s
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor) (release) Successful in 6m16s
[Workflow] On Release Published / Vloer - Publish VS Code extension (release) Successful in 50s
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor)-1 (release) Successful in 9m19s
[Workflow] On Release Published / Deploy preview (release) Has been skipped
[Workflow] On Release Published / site-deploy-production (release) Successful in 0s
[Workflow] On Release Published / site-deploy-staging (release) Failing after 0s
[Workflow] On Release Published / Vloer - Verify and publish all destinations (release) Successful in 1m49s
[Workflow] On Release Published / Deploy production (release) Failing after 3m40s
2a5ceb7cce
## [unfold-v0.4.0-rc.37](https://forgejo.webgrip.dev/webgrip/unfold/compare/unfold-v0.4.0-rc.36...unfold-v0.4.0-rc.37) (2026-10-04)

### Added

* **deps:** update all non-major dependencies ([dbf4083](dbf40833cd))
* **vloer:** show tasks Ploeg could not start under Needs you ([501a018](501a018f54))
* **vloer:** warn when Runs hold budget Ploeg cannot release ([46819e9](46819e9723))

### Fixed

* **vloer:** default the chart and image builds to public registries ([14e7581](14e7581ab9))
* **vloer:** make editor sign-in need approval and issue its own credential ([9ada416](9ada416ab3))
* **vloer:** mark a pull request whose reviewer kept failing as unreviewed ([d842853](d84285390c))
* **vloer:** say the agent stopped responding when an ACP watchdog stopped it ([d263ce6](d263ce6de9))

### Docs

* **vloer:** accept ADR-0037 an editor signs in only after its person approves it ([072e3ee](072e3ee340))

### Tests

* **vloer:** give the relay test's timed exec a load-scaled budget ([a05d947](a05d94726f)), references [#207](#207) [#212](#212)

### Build

* **ploeg:** pin Ploeg main at 611b3f18 to keep verification provenance ([9f3e9e9](9f3e9e9263)), references [ploeg-hq/ploeg#53](ploeg-hq/ploeg#53) [ploeg-hq/ploeg#47](ploeg-hq/ploeg#47) [ploeg-hq/ploeg#54](ploeg-hq/ploeg#54)
* **ploeg:** pin Ploeg main at 611b3f18 to let only the worker report delivery ([047d605](047d605894)), references [ploeg-hq/ploeg#49](ploeg-hq/ploeg#49) [ploeg-hq/ploeg#47](ploeg-hq/ploeg#47) [ploeg-hq/ploeg#54](ploeg-hq/ploeg#54)
* **ploeg:** pin Ploeg main at 611b3f18 to list Runs whose spend cannot settle ([721af5b](721af5b867)), references [ploeg-hq/ploeg#50](ploeg-hq/ploeg#50) [ploeg-hq/ploeg#47](ploeg-hq/ploeg#47) [ploeg-hq/ploeg#54](ploeg-hq/ploeg#54)
* **ploeg:** pin Ploeg main at 611b3f18 to make the chart portable ([800abf7](800abf77d9)), references [ploeg-hq/ploeg#52](ploeg-hq/ploeg#52) [ploeg-hq/ploeg#47](ploeg-hq/ploeg#47) [ploeg-hq/ploeg#54](ploeg-hq/ploeg#54)
* **ploeg:** pin Ploeg main at 611b3f18 to name the ACP watchdog that stopped a Run ([94b5e81](94b5e815c7)), references [ploeg-hq/ploeg#48](ploeg-hq/ploeg#48) [ploeg-hq/ploeg#47](ploeg-hq/ploeg#47) [ploeg-hq/ploeg#54](ploeg-hq/ploeg#54)
* **ploeg:** pin Ploeg main at 611b3f18 to retry a failed reviewer ([fe47d57](fe47d57523)), references [ploeg-hq/ploeg#47](ploeg-hq/ploeg#47) [ploeg-hq/ploeg#47](ploeg-hq/ploeg#47) [ploeg-hq/ploeg#54](ploeg-hq/ploeg#54)
* **site:** lock pnpm 12.8.2 in the site's pnpm-lock.yaml ([c707135](c707135ef1))

### CI

* **site:** record the /demo replay in the site build instead of committing it ([b10b6c0](b10b6c0108))

### Internal

* **release:** unfold-site-v0.1.0-rc.10 [skip ci] ([59823dd](59823dd6bb))
chore(release): unfold-site-v0.1.0-rc.11 [skip ci]
Some checks failed
[Workflow] On Release Published / parse-release-tag (release) Has been skipped
[Workflow] On Release Published / site-release-tag (release) Successful in 0s
[Workflow] On Release Published / Vloer - Publish Helm chart (Harbor) (release) Has been skipped
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor) (release) Has been skipped
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor)-1 (release) Has been skipped
[Workflow] On Release Published / Vloer - Publish VS Code extension (release) Has been skipped
[Workflow] On Release Published / Deploy preview (release) Has been skipped
[Workflow] On Release Published / site-deploy-production (release) Successful in 0s
[Workflow] On Release Published / Vloer - Verify and publish all destinations (release) Has been skipped
[Workflow] On Release Published / site-deploy-staging (release) Failing after 0s
[Workflow] On Release Published / Deploy production (release) Failing after 3m20s
9de2dab432
## [unfold-site-v0.1.0-rc.11](https://forgejo.webgrip.dev/webgrip/unfold/compare/unfold-site-v0.1.0-rc.10...unfold-site-v0.1.0-rc.11) (2026-10-04)

### Added

* **deps:** update all non-major dependencies ([dbf4083](dbf40833cd))

### Build

* **site:** lock pnpm 12.8.2 in the site's pnpm-lock.yaml ([c707135](c707135ef1))

### CI

* **site:** record the /demo replay in the site build instead of committing it ([b10b6c0](b10b6c0108))
build(ploeg): pin Ploeg v0.2.0-rc.1 and follow its releases
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 26s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 24s
[Workflow] On Pull Request / checks (pull_request) Successful in 5m51s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
5b3073e9ca
The owner wants Unfold on the latest Ploeg release, release candidates
included, instead of a commit on Ploeg's main. Ploeg now tags candidates
on its development trunk (Ploeg ADR 0064) and published v0.2.0-rc.1, which
holds every change Unfold's replacements needed.

.gitmodules names the release tag (branch = v0.2.0-rc.1). The ploeg-pin
job requires the gitlink to be that tag's commit on github.com/ploeg-hq/
ploeg; locally the check requires a release tag name. Renovate reads the
tag, compares release tags as semver with ignoreUnstable off, and proposes
each newer release, candidates included, without a schedule. ADR-0019
records the change; the decision register picks up Ploeg ADR 0064.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge pull request 'build(ploeg): pin Ploeg v0.2.0-rc.1 and follow its releases' (#216) from ryangr0/build/ploeg-pin-release into development
All checks were successful
[Workflow] On Docs Change / authorize-publication (push) Successful in 1s
[Workflow] On Docs Change / techdocs (push) Successful in 3m44s
[Workflow] On Docs Change / generate-documentation (push) Successful in 0s
[Workflow] On Source Change / ploeg-pin (push) Successful in 3m24s
[Workflow] On Source Change / checks (push) Successful in 9m45s
[Workflow] On Source Change / warnings (push) Successful in 1s
[Workflow] On Docs Change / Build (Zensical) + sync to Garage web (push) Successful in 2m16s
[Workflow] On Docs Change / deploy-docs-site (push) Successful in 0s
[Workflow] On Source Change / release (push) Successful in 26s
[Workflow] On Docs Change / verify-publication (push) Successful in 31s
[Workflow] On Source Change / site-release (push) Successful in 20s
299f0a6a5e
Reviewed-on: #216
build(ploeg): pin Ploeg v0.2.0-rc.2
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 34s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 1m6s
[Workflow] On Pull Request / checks (pull_request) Successful in 9m39s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
8ee276497a
v0.2.0-rc.2 adds the pull request merge state (ploeg-hq/ploeg#55), the
changed paths on a card's play (#56), and the settlement patience before
a held pool parks (#59, Ploeg ADR 0048, accepted). Two migrations run on
start; the new operator fields are optional. The decision register picks
up ADR 0048's acceptance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Work Item closed with "budget exhausted: pool 8.00, spent 0.00, reserved
8.00" read "Budget ran out" over a 0% meter and told the operator to raise
the budget, while four Runs had failed at the model gateway. A budget stop
with more held than spent now reads "Budget held, not spent", names the
failure that came first, says whether Ploeg released the hold since, and
points the fix at that failure.

- Repeated Runs read "Run 4 of 5 · after 3 infrastructure failures"; the
  header and rows drop the attempt counter for the Shift's Run count.
- A Round cell with several Runs counts them by result and shows the
  newest failure, not the Run Ploeg closed before it started; such a Run
  has no cost to report.
- Cancel shows only when Ploeg would stop something (VIK-1594).
- The page no longer quotes Ploeg's budget sentence, apologises for a
  missing task link, or advertises proposed Ploeg work.
- plan_exhausted reads "Every Round ran, no result"; a group header keeps
  its fix as a tooltip while a Work Item is open beside the list.

VIK-1594

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ploeg ADR-0048 closes a Shift whose holds never settled with
'budget held by unsettled runs: pool P, spent S, held H'
(ploeg-hq/ploeg#59, Ploeg v0.2.0-rc.2). Vloer shows it as Budget held,
not spent, with the held amount.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fix(vloer): drop the roadmap note from the VS Code task view too
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 35s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 38s
[Workflow] On Pull Request / checks (pull_request) Successful in 9m53s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
70db898714
The budget-held change removed requeueNote, the "Starting again from
Vloer is proposed Ploeg work" line, so no surface advertises the roadmap.
The VS Code task view and the Work Item page started importing it after
that change was written; both stop now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(vloer): show when a waiting pull request conflicts with its base
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 38s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 50s
[Workflow] On Pull Request / checks (pull_request) Successful in 9m54s
[Workflow] On Pull Request / warnings (pull_request) Successful in 1s
21eb72db68
Ploeg's Work Item list now reports each pull request's merge state (Ploeg
ADR-0040, ploeg-hq/ploeg#55). Vloer passes it through as `pullRequest` on
the Now items, marks a conflicted pull request on its row and in the
Work Item's decision box, and notifies once per head commit when a waiting
pull request starts to conflict. A Ploeg that reports no merge state shows
nothing new.

Written on 2026-10-03 as uncommitted work in a worktree and kept as
"wip vloer"; ported onto development after the Ploeg separation, with the
Now item's type merged with the routing refusals that landed since.

VIK-1598
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge pull request 'build(ploeg): pin Ploeg v0.2.0-rc.2' (#217) from ryangr0/build/ploeg-rc2 into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Successful in 1s
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Successful in 7m28s
[Workflow] On Source Change / ploeg-pin (push) Successful in 30s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Successful in 52s
[Workflow] On Source Change / site-release (push) Successful in 18s
80b4ff9965
Reviewed-on: #217
Merge pull request 'fix(vloer): say when a budget stop was only held, not spent' (#218) from ryangr0/fix/vloer-budget-held into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / ploeg-pin (push) Successful in 39s
[Workflow] On Source Change / checks (push) Successful in 7m50s
[Workflow] On Source Change / warnings (push) Failing after 0s
[Workflow] On Source Change / release (push) Successful in 24s
[Workflow] On Source Change / site-release (push) Successful in 19s
cd1f3bc966
Reviewed-on: #218
Merge pull request 'feat(vloer): show when a waiting pull request conflicts with its base' (#219) from ryangr0/feat/vloer-merge-state into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Successful in 0s
[Workflow] On Docs Change / techdocs (push) Successful in 1m26s
[Workflow] On Docs Change / generate-documentation (push) Successful in 0s
[Workflow] On Docs Change / Build (Zensical) + sync to Garage web (push) Successful in 2m8s
[Workflow] On Docs Change / deploy-docs-site (push) Successful in 0s
[Workflow] On Docs Change / verify-publication (push) Successful in 1m11s
[Workflow] On Source Change / checks (push) Failing after 6m10s
[Workflow] On Source Change / ploeg-pin (push) Successful in 24s
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
91448d30de
Reviewed-on: #219
chore(release): unfold-v0.4.0-rc.38 [skip ci]
Some checks failed
[Workflow] On Release Published / parse-release-tag (release) Successful in 1s
[Workflow] On Release Published / site-release-tag (release) Successful in 0s
[Workflow] On Release Published / Vloer - Publish Helm chart (Harbor) (release) Successful in 15s
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor) (release) Successful in 6m31s
[Workflow] On Release Published / Vloer - Publish VS Code extension (release) Successful in 54s
[Workflow] On Release Published / site-deploy-production (release) Successful in 0s
[Workflow] On Release Published / Deploy preview (release) Has been skipped
[Workflow] On Release Published / Vloer - Build, gate and sign images (Harbor)-1 (release) Successful in 6m25s
[Workflow] On Release Published / site-deploy-staging (release) Failing after 0s
[Workflow] On Release Published / Vloer - Verify and publish all destinations (release) Successful in 1m52s
[Workflow] On Release Published / Deploy production (release) Failing after 4m5s
87b2088ff8
## [unfold-v0.4.0-rc.38](https://forgejo.webgrip.dev/webgrip/unfold/compare/unfold-v0.4.0-rc.37...unfold-v0.4.0-rc.38) (2026-10-04)

### Added

* **vloer:** read Ploeg's budget held by unsettled runs close reason ([df1452f](df1452f370)), references [ploeg-hq/ploeg#59](ploeg-hq/ploeg#59)
* **vloer:** show when a waiting pull request conflicts with its base ([21eb72d](21eb72db68)), references [ploeg-hq/ploeg#55](ploeg-hq/ploeg#55)

### Fixed

* **vloer:** drop the roadmap note from the VS Code task view too ([70db898](70db898714))
* **vloer:** say when a budget stop was only held, and count work in Runs ([47849bc](47849bc401))

### Docs

* **vloer:** describe held budget stops, Run counts and when Cancel shows ([8932639](8932639c4c))

### Build

* **ploeg:** pin Ploeg v0.2.0-rc.1 and follow its releases ([5b3073e](5b3073e9ca))
* **ploeg:** pin Ploeg v0.2.0-rc.2 ([8ee2764](8ee276497a)), references [ploeg-hq/ploeg#55](ploeg-hq/ploeg#55) [#56](#56) [#59](#59)

### Internal

* **release:** unfold-site-v0.1.0-rc.11 [skip ci] ([9de2dab](9de2dab432))
refactor(unfold)!: retire the name Vloer; the application is Unfold
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 35s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 47s
[Workflow] On Pull Request / checks (pull_request) Successful in 8m52s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
fa7ebda02a
The front end was called Vloer while it and Ploeg were separate products.
Since Ploeg moved to its own repository, the unfold-v train ships only this
application, so the owner decided to call it Unfold everywhere, inside and
out (design review Q1, VIK-1831; ADR-0020).

- apps/vloer moves to apps/unfold; package @webgrip/unfold, images unfold
  and unfold-agent, chart unfold, extension webgrip.unfold, settings
  section unfold, environment variables UNFOLD_*.
- Existing installs carry over: the server adopts vloer.sqlite, the browser
  moves vloer.* local storage before first paint, the extension copies
  vloer.* settings. People sign in once more.
- Ploeg's wire names stay until Ploeg renames them: vloerUrl, the
  vloer-native skin (read as unfold-native) and PLOEG_REPORT_VLOER_URL.
- Records keep their wording: ADRs, changelogs, research and evidence and
  the planning export; only their repository paths moved. Release guards
  keep the retired de-vloer repository and vloer-v tags.
- References to the root ADR ledger now say "system ADR" so they cannot be
  confused with the application's own ledger.

BREAKING CHANGE: deployments must switch to the unfold image and chart and
rename VLOER_* environment variables to UNFOLD_* (homelab: VIK-1856). The
default OIDC groups are now unfold-admins, unfold-operators and
unfold-viewers.

Refs: VIK-1855, VIK-1831
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ci(site): preview unfoldhq.dev DNS with a read-only token from OpenBao
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 37s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 41s
[Workflow] On Pull Request / checks (pull_request) Successful in 6m27s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
1b8453deda
on_dns_change.yml now calls webgrip/workflows dnscontrol.yml v2.8.0 on
the OpenBao role ci-unfold: each run exchanges its Forgejo OIDC token
for a ten-minute read of secret/cloudflare/dns/unfoldhq-dev-ro, a
token scoped to this one zone. The workflow previews on development
and checks drift daily; the push job and the CLOUDFLARE_DNS_TOKEN gate
are gone, because CI no longer holds a DNS write token
(homelab-cluster ADR-0061). Applying moves to an in-cluster
reconciler that is not running yet; the site deploy guide says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(unfold): show Unfold's brand and a Work Item's stages and delivery
All checks were successful
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 4m11s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 1m5s
[Workflow] On Pull Request / checks (pull_request) Successful in 11m56s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
bd777a5a1e
The application still drew its former identity and grouped its sidebar by
part. It now carries Unfold's brand and is organised around work (ADR 0038,
design review answers of 2026-10-04).

- Brand: the root generator writes the mark the application draws
  (public/core/brand.js, public/favicon.svg) and checks the boot mark;
  scripts/build-icons.mjs rasterises the favicons, app icons, link preview
  and editor icon from docs/brand. The attention favicon draws the same
  paths. The application's separate brand kit is removed.
- Tokens: Unfold's neutrals; primary actions, checkboxes and switches in
  ink (inverted on dark grounds); Baken for links, focus, the current-stage
  crease and graphics at measured contrast; neutral selection.
- Navigation: Now; Work, Proposed, Tasks; Runs, Activity, Insights,
  Sessions; Settings. No group is named after a part. Binder, Packs and
  Season move to "Your cards" in the account menu.
- Work Item page: Define, Execute, Review and Deliver as one ordered list
  under the header (core/stages.js), a delivery track from the Run card
  (core/delivery-track.js) that never claims a deploy Ploeg did not
  report, and "Execution details" for Ploeg's records.

Refs: VIK-1842, VIK-1843, VIK-1844
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
refactor(unfold): stop announcing a URL Ploeg never accepted
All checks were successful
[Workflow] On Pull Request / checks (pull_request) Successful in 7m37s
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 31s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 27s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
33004aa49d
PloegClient.announce sent PUT /api/v1/operator/consumer {"vloerUrl"} with
backoff until Ploeg answered. No Ploeg release, branch or commit ever had
that route, so every live workbench logged consumer_unsupported once and
the call was dead. It was also the last place Unfold used the former name
on the wire. Ploeg now links reports through deployment-set URL templates
(ploeg-hq/ploeg#62), so nothing needs the announcement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge pull request 'ci(site): preview unfoldhq.dev DNS with a read-only token from OpenBao' (#222) from ryangr0/dns-preview-over-openbao into development
Some checks failed
[Workflow] On DNS Change / DNS preview (push) Failing after 15s
[Workflow] On DNS Change / dns-preview (push) Failing after 0s
[Workflow] On DNS Change / DNS drift (push) Has been skipped
[Workflow] On DNS Change / dns-drift (push) Successful in 0s
[Workflow] On Docs Change / authorize-publication (push) Successful in 0s
[Workflow] On Docs Change / techdocs (push) Successful in 1m16s
[Workflow] On Docs Change / generate-documentation (push) Successful in 0s
[Workflow] On Source Change / ploeg-pin (push) Successful in 35s
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / Build (Zensical) + sync to Garage web (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
cb65305275
Reviewed-on: #222
Merge pull request 'refactor(unfold)!: retire the name Vloer; the application is Unfold' (#223) from ryangr0/vloer-is-unfold into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Successful in 2s
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / ploeg-pin (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
a61d4abf65
Reviewed-on: #223
Merge pull request 'feat(unfold): show Unfold's brand and a Work Item's stages and delivery' (#224) from ryangr0/unfold-work-item-sheet into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Successful in 4s
[Workflow] On Docs Change / techdocs (push) Successful in 2m4s
[Workflow] On Docs Change / generate-documentation (push) Successful in 0s
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / Build (Zensical) + sync to Garage web (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / ploeg-pin (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
ea25b6fd4b
Reviewed-on: #224
Merge pull request 'refactor(unfold): stop announcing a URL Ploeg never accepted' (#226) from ryangr0/drop-consumer-announce into development
Some checks failed
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / ploeg-pin (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
29dc7b799e
Reviewed-on: #226
fix(site): run the DNS preview in a direct job so it gets an OIDC token
Some checks failed
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 32s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 22s
[Workflow] On Pull Request / checks (pull_request) Successful in 7m59s
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
92ecf1e928
The first run after #222 failed with "no OIDC token endpoint": Forgejo
15 does not pass enable-openid-connect to the jobs it expands from a
reusable workflow, set on either side. Release signing works because
its job is declared directly. on_dns_change.yml now runs one job of
its own: checkout, webgrip/workflows openbao-read v2.8.0 on ci-unfold,
then dnscontrol check and preview, or drift on the schedule.

A local preview with the minted read-only token succeeds and lists
the expected corrections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge pull request 'fix(site): run the DNS preview in a direct job so it gets an OIDC token' (#227) from ryangr0/dns-preview-direct-job into development
Some checks failed
[Workflow] On DNS Change / DNS preview (push) Successful in 10s
[Workflow] On Docs Change / authorize-publication (push) Successful in 0s
[Workflow] On Docs Change / techdocs (push) Successful in 1m24s
[Workflow] On Docs Change / generate-documentation (push) Successful in 0s
[Workflow] On Source Change / ploeg-pin (push) Successful in 32s
[Workflow] On Source Change / checks (push) Failing after 7m49s
[Workflow] On Docs Change / Build (Zensical) + sync to Garage web (push) Successful in 2m44s
[Workflow] On Docs Change / deploy-docs-site (push) Successful in 0s
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
b5e198a314
Reviewed-on: #227
rc.3 delivers context files people attach to a Work Item (Ploeg #61,
ADR-0067 and ADR-0068) and names none of its consumers (Ploeg #62,
ADR-0069). Ploeg's domain model now defines Model itself, so Unfold's model
drops its own Model term and the references to it; the combined glossary
takes Ploeg's definition. The decision register and landscape follow.

VIK-1858
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two RFCs and their decisions: system ADR-0022 and Unfold ADR-0039 (context
files at the start and while steering, accepted 2026-10-04) and system
ADR-0021 (agents briefed from a per-Tenant knowledge base exchanged as OKF,
proposed until the measurement spike reports). Numbered after ADR-0020 and
Unfold ADR-0038, which took the earlier numbers.

VIK-1858
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Work Item page gets a Context card: an "Add context" dialog sends a
file and an optional note to POST /api/ploeg/work-items/{id}/context, which
forwards the bytes to Ploeg's operator API as the signed-in person and keeps
no copy. The list shows each item's size, file count, who added it and when,
marks items added while steering, and says which Runs receive them. The
application's engine never reads context, and the demo refuses uploads.
Needs Ploeg 0.2.0-rc.3 or later.

VIK-1858
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(knowledge): add an OKF knowledge bundle about the Unfold repository
Some checks failed
[Workflow] On Pull Request / ploeg-pin (pull_request) Waiting to run
[Workflow] On Pull Request / release-policy (pull_request) Waiting to run
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
6ff22e0975
Eight concepts taken from CLAUDE.md, docs/documentation.md and the system
ADRs, for Ploeg's worker to brief Runs on this repository (ADR-0021,
proposed).

VIK-1858
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(adr): accept ADR-0017 with Clients, SSO groups and a default Tenant
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / ploeg-pin (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
bfc64190a8
The owner answered ADR-0017's open questions on 2026-10-04: Teams are per
Tenant; every install has a Tenant, a default one when self-hosted; a
person may belong to several Tenants and switch; Unfold staff never see
Tenant content; SSO groups unfold/<tenant>/<role> and
unfold/<tenant>/client/<client> carry memberships. A Client owns sources
and repositories inside a Tenant and client users reach only those Work
Items. Tenancy and client access move into milestone M1. The domain model's
Tenant and Client entries follow, marked as decided and not implemented.

VIK-1741
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(unfold): space editor polls from the answer, not the request
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / ploeg-pin (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
fb252a2878
The phishing-flow test failed on a loaded runner (run 962) with 429
slow_down instead of 403. The helper stamped its last poll before
sending, while the server stamps on arrival, so a delayed poll followed
by a prompt one landed under the server's one-second floor. Stamping
after the response guarantees the server sees at least 1050 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(site): unfoldhq.dev handles no mail; drop the registrar's forwarding
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Failing after 3m27s
[Workflow] On Pull Request / ploeg-pin (pull_request) Successful in 30s
[Workflow] On Pull Request / release-policy (pull_request) Successful in 17s
[Workflow] On Pull Request / warnings (pull_request) Successful in 0s
28264b5d4b
The zone still carried Namecheap's email forwarding (five eforward MX
records and its SPF include), which the config never declared. The
owner dropped them on 2026-10-04. The config now says the domain
handles no mail: a null MX (RFC 7505), SPF -all and DMARC p=reject, so
mail to it bounces at once and nobody can send as @unfoldhq.dev.

The homelab dns-reconciler applies development hourly and refuses
deletions without a trailer; this commit carries it. Preview with the
read-only token: two MX and the SPF TXT modified, three MX deleted,
_dmarc, the www redirect and staging created.

DNS-Allow-Delete: unfoldhq.dev
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge pull request 'test(unfold): space editor polls from the answer, not the request' (#229) from ryangr0/fix/editor-poll-flake into development
Some checks are pending
[Workflow] On Source Change / checks (push) Waiting to run
[Workflow] On Source Change / warnings (push) Blocked by required conditions
[Workflow] On Source Change / ploeg-pin (push) Waiting to run
[Workflow] On Source Change / release (push) Blocked by required conditions
[Workflow] On Source Change / site-release (push) Blocked by required conditions
0260f4fee7
Reviewed-on: #229
Merge pull request 'docs(adr): accept ADR-0017 with Clients, SSO groups and a default Tenant' (#225) from ryangr0/docs/accept-adr-0017 into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / ploeg-pin (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
34ad98da77
Reviewed-on: #225
Merge pull request 'feat(site): unfoldhq.dev handles no mail; drop the registrar's forwarding' (#230) from ryangr0/dns-no-mail into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / ploeg-pin (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
[Workflow] On DNS Change / DNS preview (push) Has been cancelled
3585f470d5
Reviewed-on: #230
fix(unfold): follow Ploeg's operator/<session> branches and report link templates
Some checks failed
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On Pull Request / warnings (pull_request) Has been cancelled
[Workflow] On Pull Request / ploeg-pin (pull_request) Has been cancelled
[Workflow] On Pull Request / release-policy (pull_request) Has been cancelled
2f4cfc92d3
Ploeg names none of its consumers (ploeg-hq/ploeg#62, Ploeg ADR-0069):
operator execution Shifts use operator/<session>, and report links are
deployment-set templates. The engine's session branch matches, and the
review how-to names the new settings. Lands together with the pin of the
Ploeg release that contains #62; not before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge pull request 'fix(unfold): follow Ploeg's operator/<session> branches and report link templates' (#231) from ryangr0/build/ploeg-no-downstream into development
Some checks failed
[Workflow] On Docs Change / authorize-publication (push) Has been cancelled
[Workflow] On Docs Change / generate-documentation (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / techdocs (push) Has been cancelled
[Workflow] On Source Change / checks (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Has been cancelled
[Workflow] On Source Change / ploeg-pin (push) Has been cancelled
[Workflow] On Source Change / release (push) Has been cancelled
[Workflow] On Source Change / site-release (push) Has been cancelled
fbef0a669d
Reviewed-on: #231
feat(site): release the site to unfoldhq.dev from main
Some checks failed
[Workflow] On Pull Request / warnings (pull_request) Blocked by required conditions
[Workflow] On Pull Request / ploeg-pin (pull_request) Waiting to run
[Workflow] On Pull Request / release-policy (pull_request) Waiting to run
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
6c724a2834
The site has a production Worker and route but nothing could cut a
stable unfold-site release: the release jobs ran only on development,
which cuts candidates. on_source_change.yml now also runs on main,
where only site-release versions (Unfold's release job stays limited
to development), so promoting development to main cuts
unfold-site-vX.Y.Z and on_release_published deploys unfoldhq.dev.

Staging gets its own EU D1 database, unfold-site-signups-staging
(b8b9dea7…, created 2026-10-04); production keeps unfold-site-signups
with the sign-ups staging collected so far. The apex and www lose the
registrar's parking records and carry the same proxied 100:: as
staging. ADR-0012, the deploy guide and the CI table record it.

DNS-Allow-Delete: unfoldhq.dev
DNS-Allow-Delete: www.unfoldhq.dev
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge pull request 'feat(site): release the site to unfoldhq.dev from main' (#233) from ryangr0/site-production into development
Some checks failed
[Workflow] On Pull Request / ploeg-pin (pull_request) Waiting to run
[Workflow] On Pull Request / release-policy (pull_request) Waiting to run
[Workflow] On Pull Request / warnings (pull_request) Blocked by required conditions
[Workflow] On Pull Request / checks (pull_request) Has been cancelled
[Workflow] On DNS Change / DNS preview (push) Successful in 12s
[Workflow] On Docs Change / authorize-publication (push) Successful in 0s
[Workflow] On Docs Change / techdocs (push) Successful in 1m55s
[Workflow] On Docs Change / generate-documentation (push) Successful in 0s
[Workflow] On Source Change / ploeg-pin (push) Successful in 33s
[Workflow] On Source Change / checks (push) Failing after 3m54s
[Workflow] On Docs Change / verify-publication (push) Has been cancelled
[Workflow] On Docs Change / deploy-docs-site (push) Has been cancelled
[Workflow] On Docs Change / Build (Zensical) + sync to Garage web (push) Has been cancelled
[Workflow] On Source Change / warnings (push) Successful in 0s
[Workflow] On Source Change / release (push) Has been skipped
[Workflow] On Source Change / site-release (push) Has been skipped
bd244709b5
Reviewed-on: #233
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
webgrip/unfold!234
No description provided.