Skip to content

docs(policy): yq for reading now, KYAML sequenced behind a proof - #1009

Merged
hyperpolymath merged 2 commits into
mainfrom
policy/yaml-kyaml-and-yq
Sep 22, 2026
Merged

hyperpolymath merged 2 commits into
mainfrom
policy/yaml-kyaml-and-yq

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Answers the owner's question of 2026-09-22:

"is it sensible to make the estate rule kyaml not yaml? this seems very
sensible, as well as making sure we invest in using yq wherever it comes
into things?"

Short answer: yes to yq, and yes to KYAML — but not in that order, and not
in one step.
This adds 3-practice/YAML-POLICY.adoc recording exactly which
part is in force today and which is sequenced behind a proof.

The three rules

Rule Status Substance
Y-1 — gates READ YAML with yq, never grep IN FORCE A grep-read config key is not what the parser sees. Three recorded defect instances, plus a landed exemplar (cicd-suite #32).
Y-2 — yq for WRITING NOT in force, deferred behind a proof The pin comment is load-bearing; a reformat that drops it is a silent security regression.
Y-3 — KYAML as target authoring dialect SEQUENCED, scope deliberately undecided Three preconditions must hold first; per the owner's ruling, GitHub Actions is probed first, then scope is decided.

Why Y-2 and Y-3 share one proof obligation

This was the finding that changed the document. Grounding it in the primary
sources (KEP-5295, kubernetes.io) rather than recollection turned up that
KYAML's own specification carries the same caveat as the yq-writing risk:

Unlike JSON, KYAML allows comments (when authored by users) — but
automated reformatting is lossy; go-yaml "does not always handle comments
properly", and comments may be "formatted wrongly, or lost entirely".

So "can a tool rewrite our YAML without destroying the comments?" is a single
question sitting under two rules, not two questions. §4 states it once, with
the acceptance criterion "parse-clean is not the bar": the proof must show
every comment preserved, comment association to the right line preserved,
idempotence under cmp, and a dropped-comment mutant must die. A round-trip
that merely parses proves nothing.

What the owner already ruled

  • KYAML scope: "2 with 3 planned as an issue with those components noted down
    as separate issues with an 'order' to them"
    — probe GitHub Actions first,
    then decide scope; estate-wide adoption booked as ordered issues. §5 carries
    that six-step dependency-ordered table; the issues are the follow-up.
  • yq: "going for 1 now 2 and we'll sort proof as part of this" — Y-1 in
    force now, Y-2 next, proof in scope. That is exactly §1/§2/§4.

What it deliberately does not do

§6: it does not ban YAML, does not apply to lockfiles or third-party files, and
does not change LANGUAGE-POLICY.adoc.

Registry

standards-map.toml gains the matching [[entry]] and entry_count goes
123 → 124. Gate D caught the stale count on the first run and passed once
corrected — recorded in the commit message as the gate doing its job.

Refs: #787

🤖 Generated with Claude Code

https://claude.ai/code/session_01WRvDivYwLSeVCJUrfjic3f

hyperpolymath and others added 2 commits September 22, 2026 21:55
Owner ruling 2026-09-22, in response to "is it sensible to make the estate
rule kyaml not yaml, and invest in using yq wherever it comes into things?"

Measured first. 23,199 YAML files across the estate at depth 6 (excluding
node_modules/target/.git/vendor): 9,261 in .github/workflows, 11,238 elsewhere
under .github, 2,700 outside it. The split that matters is that the 9,261 are
written by BOTS — dependabot and gh actions-lock — so a style rule imposed on
them is violated on a schedule, by us, forever.

Y-1, IN FORCE: every gate that READS YAML parses it with yq, never grep/sed.
This is not hygiene, it is a measured recurring defect class — a grep-read key
is not what the parser sees. Recorded instances: a name/uuid nested under a
[package] table that grep read correctly while the loader returned nothing;
gh actions-lock's extractor seeing step-level `uses:` only and calling 71
job-level reusable refs fatal orphans, turning a green repo red; allowlist
globs keyed to a depth a sparse checkout had changed. Enforceable today —
yq v4.53.3 is already installed.

Y-2, SEQUENCED: yq for WRITING waits on a comment-preservation proof. The
estate pins actions as `uses: <sha> # v7.0.1` and that trailing comment is
load-bearing — a bare 40-hex string is unreviewable, and the comment is the
only place the tag survives. A rewrite that strips it still parses, still
runs, and is no longer reviewable, so no parse check would catch it.

Y-3, SEQUENCED, scope deliberately undecided: KYAML as target authoring
dialect. It is genuinely attractive — a strict subset of YAML per KEP-5295,
so no new parser anywhere, and flow style with quoted values kills the Norway
problem, sexagesimal coercion, 1.0-becomes-float and indentation ambiguity at
source. Three things must be true first: GitHub Actions must be PROVEN to
parse KYAML workflows (assuming it does is how workflows here die at startup
with jobs=0, which is not a failing job and shows no honest red square); a
formatter must exist, as none does outside `kubectl -o kyaml`; and comment
handling must be proven — KEP-5295 is explicit that automated reformatting is
lossy, go-yaml "does not always handle comments properly" so comments may be
"formatted wrongly, or lost entirely".

The structural point: that last precondition and Y-2's obstacle are the SAME
problem. An automated rewrite risks destroying load-bearing comments whether
the rewriter is `yq -i` or a KYAML formatter. So the proof is written once,
against the real pin-comment corpus, and satisfies both. It also fixes the
acceptance bar for both: parse-clean is not the bar — a migration producing
valid, running, comment-stripped workflows has failed.

Adoption order is a dependency order, six steps, one issue each: probe ->
comment proof -> formatter -> owner scope ruling -> estate-authored YAML ->
workflows (only if ruled in scope, and only once bot drift is resolved).

Nothing in the estate is out of compliance with Y-2 or Y-3 today, because
neither imposes anything today. Gates must not cite them yet.

standards-map.toml gains the entry and entry_count goes 123 -> 124; Gate D
caught the stale count before this commit, which is the gate working.

Refs: #787

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WRvDivYwLSeVCJUrfjic3f
cicd-suite PR #32 extracts a composite's embedded shell script with
`yq -r '.runs.steps[0].run'` in order to execute it under the real CI
shell. A grep/sed extraction would have had to guess the block scalar's
indentation and could silently have produced a different script from
the one GitHub runs — the exact failure class Y-1 exists to prevent.

The defect that test was written for belongs to the same family: the
gate's behaviour depended on `-e`, supplied by the harness and absent
from the file, so reading the file alone gave a confident verdict in
the wrong direction.

Refs: #787

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WRvDivYwLSeVCJUrfjic3f
@coderabbitai

coderabbitai Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 31 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: ba7f9110-2898-492c-84d5-a55c64a18673

📥 Commits

Reviewing files that changed from the base of the PR and between a55935e and c07e523.

📒 Files selected for processing (2)
  • 3-practice/YAML-POLICY.adoc
  • standards-map.toml

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

@hyperpolymath
hyperpolymath merged commit 48dc0ad into main Sep 22, 2026
48 checks passed
@hyperpolymath
hyperpolymath deleted the policy/yaml-kyaml-and-yq branch September 22, 2026 21:20
hyperpolymath added a commit that referenced this pull request Sep 22, 2026
…1026)

Follow-up to #1009, which landed `3-practice/YAML-POLICY.adoc`.

§5 fixed a six-step dependency order and stated *"Each step is a
separate issue"* — but carried no issue numbers, so a reader of the
policy had no way to find them. This adds the mapping and records step
1's result.

## What changed

Two additions, both inside `3-practice/YAML-POLICY.adoc`:

1. §5 gains the issue mapping (#1020 → #1025) in dependency order.
2. §8 gains a changelog **row** recording step 1's discharge.

No rule text is altered. Nothing is mandated that was not mandated
before.

## Step 1 is discharged — measured, not assumed

§3.3 precondition 1 warned this must be probed because *"the parser will
surely accept it" is exactly how workflows in this estate die at startup
with `jobs=0`*. It was probed, on `hyperpolymath/pons-asinorum` branch
`probe/kyaml-parse` (`ac2c614`).

The specific failure mode tested: `kubectl -o kyaml` emits `"on":
"push"` — it **quotes** the trigger key, because YAML 1.1 reads bare
`on` as boolean `true`. Had GitHub looked up the normalised key `true`,
a quoted `"on"` would be a different key ⇒ no trigger ⇒ `jobs=0`.

| arm | syntax | run | jobs | step marker in log |
|---|---|---|---|---|
| control | plain YAML | 35794781121 | **1** | `PROBE_YAML_ARM_EXECUTED`
found |
| subject | KYAML, `kubectl` dialect | 35794781018 | **1** |
`PROBE_KYAML_ARM_EXECUTED` found |

Both arms were in **one commit** and are semantically identical
(verified by diffing both through `yq -o=json -P 'sort_keys(..)'`, equal
after normalising only the arm labels), so any difference would be
attributable to syntax alone.

**Confounds excluded by construction:** pons has no `actions.lock` and
neither arm carries a `uses:`, so the absent-from-lock startup death
could not have produced the result. The plain-YAML control is what makes
a non-zero meaningful — a bare `jobs=0` would have been unattributable.

**Why the log marker rather than the green square:**
`conclusion=success` alone proves nothing, since a green run can execute
nothing at all. Each arm echoes a unique string, and both were grepped
out of the run logs.

## What this deliberately does not do

It does not advance the scope question, and the added text says so. §5
step 4 (#1023) requires an **owner ruling** and explicitly forbids
inferring scope from the probe. The probe settles the *parser* dimension
only — `actionlint`, `gh actions-lock`'s extractor against flow-style
mappings, the estate's yq-based gates and reviewability all remain open,
and are booked in #1022 and #1025.

## Note on how this was edited

The shared `standards` checkout is currently mid-flight on another
actor's branch (`secqual/resync-actions-lock-after-dependabot-bump`,
with staged changes), so this was written through the contents API
rather than that worktree — no concurrent actor's work was touched or
overridden.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01WRvDivYwLSeVCJUrfjic3f

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant