First-class placeholder sentinel: a placeholder value must never win a merge, and ward doctor fails on a surviving one #1122

Closed
opened 2026-07-12 00:51:34 +00:00 by coilyco-ops · 2 comments
Member

The idea (Kai)

The example values in the generic guardfiles are not leftovers - they are deliberate sentinels, there so ward doctor fails when a deployment has not melded its own values in. That intent is right. The bug is that ward has to guess they are sentinels (containsExamplePlaceholder just looks for the substring "example"), and worse: a sentinel currently wins the merge over a real value.

Make it first class:

  • a placeholder marker on a value,
  • precedence: a real value always beats a placeholder, regardless of file order,
  • doctor: fails if an effective value is still a placeholder (nothing overrode it),
  • ward CI accepts placeholders (its baked surface is all sentinels, by design - ward must not embed coilyco config); aos CI does not (its bundle is a real deployment).

The bug this fixes (verified today)

.ward/ops.forgejo.kdl and .ward/guardfile.forgejo.kdl both declare wrap ward-kdl ops forgejo, and ward merges them. cli-guard's singletons are last-wins (applyBaseURL assigns the last non-empty; gf.Auth = a overwrites). Measured on the live bundle:

  • ward doctor resolves the effective base-url to git.example.com/api/v1 (the sentinel, from ops.forgejo.kdl)
  • runtime ward ops forgejo get repo ... actually requests ssm get-parameter --name /forgejo/coilyco-ops/api-token (the real token, from guardfile.forgejo.kdl)

So the merged ops surface pairs the sentinel host with the real credential. The sentinel is overriding a real deployment value instead of yielding to it. A placeholder that can win a merge is worse than no sentinel at all.

Deleting ops.forgejo.kdl is not the fix - it carries the curated verb surface (the ward#107/#109 hardening). Dropping it surfaces a stale op (repoGetCombinedStatusByRef not found in spec). The two files are different surfaces, not duplicates.

Sketch

KDL type annotations give this for free, so the dialect change is small:

wrap ward-kdl ops forgejo {
    spec forgejo.swagger.v1.json
    base-url (placeholder)"git.example.com/api/v1"
    auth header-token {
        value ssm (placeholder)"/example/forgejo/api-token"
    }
    restrict owner matches (placeholder)"example*"
    // verbs, which are NOT deployment specific, stay
}
  • cli-guard - surface the annotation (Value.TypeAnnotation() already exists) so a consumer can ask "is this a placeholder?".
  • ward - in the bundle merge, a placeholder-annotated singleton yields to any non-placeholder one for the same key, whatever the file order. doctor then fails only on a surviving placeholder, and takes a flag (or env) so ward's own CI can validate its baked, all-sentinel surface.
  • aos - annotate the sentinels in ops.forgejo.kdl.

This also lets containsExamplePlaceholder retire: today any real value containing the word "example" would false-positive, and any sentinel not containing it is invisible.

Why it matters right now

This is the last ward doctor failure gating the dev-base image. Doctor gates core (docker/dev-base/core/Dockerfile:182), so publish-dev-base stays red and the published image stays stale at v0.245.0 (ward v0.529.0 inside).

Done so far: ward#1121 (cli-guard unstranding + burndown) and aos#472 (stop shadowing ward's smart-defaults) took doctor from 3 failures to 1. This is the one.

After it: release ward, then bump ARG WARD_VERSION in aos and the hard-pinned literal in tests/test_dev_base_image.py (aos#452).

Refs ward#1120, aos#452, aos#472.

## The idea (Kai) The `example` values in the generic guardfiles are **not leftovers** - they are deliberate **sentinels**, there so `ward doctor` fails when a deployment has not melded its own values in. That intent is right. The bug is that ward has to *guess* they are sentinels (`containsExamplePlaceholder` just looks for the substring `"example"`), and worse: **a sentinel currently wins the merge over a real value.** Make it first class: * a `placeholder` marker on a value, * **precedence**: a real value always beats a placeholder, *regardless of file order*, * **doctor**: fails if an effective value is *still* a placeholder (nothing overrode it), * **ward CI accepts placeholders** (its baked surface is all sentinels, by design - ward must not embed coilyco config); **aos CI does not** (its bundle is a real deployment). ## The bug this fixes (verified today) `.ward/ops.forgejo.kdl` and `.ward/guardfile.forgejo.kdl` both declare `wrap ward-kdl ops forgejo`, and ward merges them. cli-guard's singletons are **last-wins** (`applyBaseURL` assigns the last non-empty; `gf.Auth = a` overwrites). Measured on the live bundle: * `ward doctor` resolves the effective `base-url` to **`git.example.com/api/v1`** (the sentinel, from `ops.forgejo.kdl`) * runtime `ward ops forgejo get repo ...` actually requests **`ssm get-parameter --name /forgejo/coilyco-ops/api-token`** (the **real** token, from `guardfile.forgejo.kdl`) So the merged ops surface pairs the **sentinel host** with the **real credential**. The sentinel is overriding a real deployment value instead of yielding to it. A placeholder that can win a merge is worse than no sentinel at all. Deleting `ops.forgejo.kdl` is **not** the fix - it carries the curated verb surface (the ward#107/#109 hardening). Dropping it surfaces a stale op (`repoGetCombinedStatusByRef not found in spec`). The two files are different surfaces, not duplicates. ## Sketch KDL type annotations give this for free, so the dialect change is small: ```kdl wrap ward-kdl ops forgejo { spec forgejo.swagger.v1.json base-url (placeholder)"git.example.com/api/v1" auth header-token { value ssm (placeholder)"/example/forgejo/api-token" } restrict owner matches (placeholder)"example*" // verbs, which are NOT deployment specific, stay } ``` * **cli-guard** - surface the annotation (`Value.TypeAnnotation()` already exists) so a consumer can ask "is this a placeholder?". * **ward** - in the bundle merge, a placeholder-annotated singleton yields to any non-placeholder one for the same key, whatever the file order. doctor then fails only on a *surviving* placeholder, and takes a flag (or env) so ward's own CI can validate its baked, all-sentinel surface. * **aos** - annotate the sentinels in `ops.forgejo.kdl`. This also lets `containsExamplePlaceholder` retire: today any real value containing the word "example" would false-positive, and any sentinel *not* containing it is invisible. ## Why it matters right now This is the **last** `ward doctor` failure gating the dev-base image. Doctor gates `core` (`docker/dev-base/core/Dockerfile:182`), so `publish-dev-base` stays red and the published image stays stale at v0.245.0 (ward v0.529.0 inside). Done so far: ward#1121 (cli-guard unstranding + `burndown`) and aos#472 (stop shadowing ward's smart-defaults) took doctor from **3 failures to 1**. This is the one. After it: release ward, then bump `ARG WARD_VERSION` in aos **and** the hard-pinned literal in `tests/test_dev_base_image.py` (aos#452). Refs ward#1120, aos#452, aos#472.
Author
Member

WARD-RESERVATION: held 🔒

reservation details

Holder: container engineer-codex-ward-1122 on host KAI-DESKTOP-TOWER.

Reserved by ward agent --harness codex (reserved 2026-07-13T03:45:33Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (3h TTL). --override-reservation overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh. That is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

run seed context — what this run is carrying (ward#609)
  • Resolved: coilyco-flight-deck/ward#1122 · branch issue-1122 · harness codex · workflow pull-request-and-merge
  • Run: engineer-codex-ward-1122 · ward v0.633.0 · dispatched 2026-07-13T03:45:33Z
  • Comment thread: 0 included in the pre-flight read, 0 stripped (ward's own automated comments).

Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.633.0).

— Codex, via ward agent

<!-- ward-agent-reservation --> WARD-RESERVATION: held 🔒 <details><summary>reservation details</summary> Holder: container `engineer-codex-ward-1122` on host `KAI-DESKTOP-TOWER`. Reserved by `ward agent --harness codex` (reserved 2026-07-13T03:45:33Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (3h TTL). `--override-reservation` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh**. That is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <details><summary>run seed context — what this run is carrying (ward#609)</summary> - **Resolved:** `coilyco-flight-deck/ward#1122` · branch `issue-1122` · harness `codex` · workflow `pull-request-and-merge` - **Run:** `engineer-codex-ward-1122` · ward `v0.633.0` · dispatched `2026-07-13T03:45:33Z` - **Comment thread:** 0 included in the pre-flight read, 0 stripped (ward's own automated comments). Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.633.0). </details> </details> <!-- ward-agent-signature --> — Codex, via `ward agent`
Author
Member

WARD-OUTCOME: submitted

details

workflow: pull-request-and-merge; review summary: skipped (in-container review gate intentionally skipped while brokered QA is pending)
felt: straightforward once the sentinel merge stayed on KDL values instead of guessed substrings.
confidence: high
surprise: the repo-wide comment hook also needed a small broker comment trim.
follow-up: none

WARD-OUTCOME: submitted <details><summary>details</summary> workflow: pull-request-and-merge; review summary: skipped (in-container review gate intentionally skipped while brokered QA is pending) felt: straightforward once the sentinel merge stayed on KDL values instead of guessed substrings. confidence: high surprise: the repo-wide comment hook also needed a small broker comment trim. follow-up: none </details>
Sign in to join this conversation.
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
coilyco-flight-deck/ward#1122
No description provided.