Skip to content

docs: record the LDE promotion, and what closing its main gap taught - #89

Merged
sgort merged 1 commit into
accfrom
docs/ci-posture-lde-promotion
Sep 9, 2026
Merged

sgort merged 1 commit into
accfrom
docs/ci-posture-lde-promotion

Conversation

@sgort

@sgort sgort commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Second half of this revision. The ttl-editor edits already in the tree covered
that repository's production run, its mirror divergence and the CI-skip finding;
this adds what Linked Data Explorer did on the same day, and updates the claims
about it those edits left standing.

Verified head moves afb182e36c4246.

Build provenance — two of three, not one

The section read "still wired and unexercised in the other two". Linked Data
Explorer ran its production workflow hours later, promoting acc to main and
publishing nineteen changelog entries — ten weeks, 1.9.92026.09.2.
Its production changelog now reads build 007b350 · #39, confirmed by eye.

Two things that run adds to ttl-editor's:

  • The pair is what makes it a build id. 007b350 is the promotion's merge
    commit; #39 is the run that built it. A redeploy of that same commit gives
    #40 — the whole argument for carrying both.
  • Reading the changelog is the check. The workflow succeeds identically
    whether the env: block reached the artifact or not.

Only ronl-business-api is now wired-and-unexercised.

The ruleset question, asked of the second repository

The ttl-editor edits ended on "the same question is worth asking of the other
two: a ruleset naming one branch says nothing about any other."

Asked here, the answer was worse: main had no ruleset at all — not even the
pull request ttl-editor's main requires. Anyone could have pushed to it
directly, or squashed a promotion and orphaned every SHA cited across all
seventy-five changelog entries.

Closed before the promotion. Three findings worth more than the fix:

  • A required check must actually reach the branch. Requiring audit on
    main works only because zizmor.yml triggers on a bare pull_request:
    with no branch filter — a fix that landed in the same release. Before it, this
    would have deadlocked the promotion permanently.
  • Omitting a ruleset parameter is not setting it false. The create call left
    require_extra_approval_for_unattributed_changes out; GitHub stored it as
    true. With 206 commits, zero required approvals and no second maintainer,
    that would have blocked the very pull request the ruleset existed to protect.
    Found by reading the stored ruleset back rather than trusting the write.
  • The promotion pull request is itself the proof, and costs nothing — it
    reported BLOCKED on the pending audit. Do not test a branch ruleset by
    pushing to the branch
    : if it is misconfigured the push succeeds, and the test
    was the promotion.

Two corrections to what production actually does

A protected environment protects the jobs that declare it. Of three
production workflows here, only the backend names environment: production;
frontend and ropa-site ship unattended. Confirmed on the promotion — the backend
paused and recorded an approval while the other two deployed straight through.
The asymmetry is deliberate, but "production requires reviewers" describes one
third of what deploys.

A workflow that names its own file in its paths: filter is a trigger.
ropa-site was predicted not to deploy — no packages/ropa-site/** content
differed — and deployed twice. Its filter's second entry is itself, and the
supply-chain work had changed it by 21 lines. A repository-wide pass over
workflow files rearms every such filter at once.

Mirror — behind is not diverged

ttl-editor's mirror needed tree comparison, an archive branch and a
--force-with-lease. This one needed one push per branch: GitLab was 114 and 253
commits behind and zero ahead, so both were strict ancestors.

git merge-base --is-ancestor is the one command separating the two cases before
anything is pushed. Commit counts do not — "253 behind" and "18 ahead and 306
behind" both read as stale.

Also recorded: push the remote-tracking ref, not the local branch. Local
main was still a four-month-old merge node origin/main had never contained;
git push gitlab main would have sent that tree to the mirror.

Open work

Closed for this repository: the unexercised build id, the unprotected main, and
the stale mirror. ttl-editor's own main row survives — closing a gap in one
repository says nothing about another, which is why the rows are per repository.

Two new rows, both noticed while doing something else:

  • chore(deps): update dependency node to v24 #80 sets engines.node >=24.20.0 while pinning 24.19.0 in all four
    workflows
  • changelog entry 1.9.12 still carries the legacy Latest status, now
    rendering that badge on a July entry below 2026.09.2 in production

The closing checklist gains three items: enumerate the branches a ruleset does
not name, read a ruleset back after writing it, and predict deploys from the
whole paths: list.

Scope

Documentation only — one file, +350/−25. No workflow, config or source change.
Prettier-clean.

Second half of this revision. The ttl-editor edits already in the working tree
covered that repository's production run, its mirror divergence and the CI-skip
finding; this adds what Linked Data Explorer did on the same day, and updates
the claims about it that those edits left standing.

Verified head moves afb182e -> 36c4246.

── Build provenance: two of three, not one ──

The section read "still wired and unexercised in the other two". Linked Data
Explorer ran its production workflow hours later, promoting acc to main and
publishing nineteen changelog entries — ten weeks, 1.9.9 to 2026.09.2 — and its
production changelog now reads `build 007b350 · #39`, confirmed by eye.

Two things that run adds to ttl-editor's: the SHA is the promotion's merge
commit while the run number is what a redeploy would change, which is the whole
argument for carrying both; and reading the changelog is the check, because the
workflow succeeds identically whether the env: block reached the artifact or not.

Only ronl-business-api is now wired-and-unexercised.

── The ruleset question, asked of the second repository ──

The ttl-editor edits ended on "the same question is worth asking of the other
two: a ruleset naming one branch says nothing about any other." Asked here, the
answer was worse: linked-data-explorer's main had no ruleset at all, not even
the pull request ttl-editor's main requires. Anyone could have pushed to it, or
squashed a promotion and orphaned every SHA cited across seventy-five changelog
entries.

Closed before the promotion, with three findings worth more than the fix:

  - A required check must actually reach the branch. Requiring `audit` on main
    works only because zizmor.yml triggers on a bare pull_request: with no
    branch filter — a fix that landed in the same release. Before it, this would
    have deadlocked the promotion permanently.

  - Omitting a ruleset parameter is not setting it false. The create call left
    require_extra_approval_for_unattributed_changes out; GitHub stored it as
    true. With 206 commits, zero required approvals and no second maintainer,
    that would have blocked the pull request the ruleset existed to protect.
    Found by reading the stored ruleset back rather than trusting the write.

  - The promotion pull request is itself the proof, and costs nothing: it
    reported BLOCKED on the pending audit. Do not test a branch ruleset by
    pushing to the branch — if it is misconfigured the push succeeds, and the
    test was the promotion.

── Two corrections to what production actually does ──

A protected environment protects the jobs that declare it. Of three production
workflows here, only the backend names `environment: production`; frontend and
ropa-site ship unattended. Confirmed on the promotion — the backend paused and
recorded an approval while the other two deployed straight through. The
asymmetry is deliberate, but "production requires reviewers" describes one third
of what deploys.

And a workflow that names its own file in its paths: filter fires when only that
file changed. ropa-site was predicted not to deploy — no packages/ropa-site/**
content differed — and deployed twice. Its filter's second entry is itself, and
the supply-chain work had changed it by 21 lines. A repository-wide pass over
workflow files rearms every such filter at once.

── Mirror: behind is not diverged ──

ttl-editor's mirror needed tree comparison, an archive branch and a
force-with-lease. This one needed one push per branch: gitlab was 114 and 253
commits behind and zero ahead, so both were strict ancestors. `git merge-base
--is-ancestor` is the one command that separates the two cases before anything
is pushed; commit counts do not, because "253 behind" and "18 ahead and 306
behind" both read as stale.

Also recorded: push the remote-tracking ref, not the local branch. Local main
was still a four-month-old merge node origin/main had never contained, and
`git push gitlab main` would have sent that tree to the mirror.

── Open work ──

Closed for this repository: the unexercised build id, the unprotected main, and
the stale mirror. ttl-editor's own main row survives — closing a gap in one
repository says nothing about another, which is why the rows are per repository.

Two new rows, both noticed while doing something else: #80 sets engines.node to
>=24.20.0 while pinning 24.19.0 in all four workflows, and changelog entry
1.9.12 still carries the legacy "Latest" status, now rendering that badge on a
July entry below 2026.09.2 in production.

The closing checklist gains three items: enumerate the branches a ruleset does
not name, read a ruleset back after writing it, and predict deploys from the
whole paths: list.
@sgort
sgort merged commit cfa6b40 into acc Sep 9, 2026
1 check passed
@sgort
sgort deleted the docs/ci-posture-lde-promotion branch September 9, 2026 11:02
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