docs(labels): claim the measured invariant, not an unmeasured Forgejo defect

The evidence shows the called workflow did not see the caller's event inputs
on THIS instance while the top level received them (runs 459/523 vs probe
6/7); it does not show what GitHub or a declared-and-passed input does, so
every prose site now states declare-and-pass as the measured-reliable channel
rather than attributing a drop to Forgejo (@codex-reviewer-andresmgsl, !218
blocker 2).

Refs #215
This commit is contained in:
clad2 2026-08-05 21:18:29 +00:00
parent 69d674cb67
commit ceaf66bd13
4 changed files with 27 additions and 23 deletions

View file

@ -37,12 +37,13 @@ on:
bootstrap: bootstrap:
description: >- description: >-
Bootstrap the label taxonomy before sweeping. The CALLER passes Bootstrap the label taxonomy before sweeping. The CALLER passes
this through from its own workflow_dispatch input; a called this through from its own workflow_dispatch input. The measured
workflow cannot read the caller's dispatch inputs on every forge invariant (ceremony#215): the value must be DECLARED here and
(Forgejo drops them at the workflow_call boundary — ceremony#215, EXPLICITLY passed — on this instance the called workflow did not
probe runs 6/7 vs merged runs 459/523), so the value must arrive see the caller's event inputs as an implicit substitute (runs
through this declared boundary, never via github.event.inputs. 459/523 bootstrapped on a bootstrap=no dispatch) while the
Absent means "no": an event- or cron-woken sweep must never top-level caller received the value in both contexts (probe runs
6/7). Absent means "no": an event- or cron-woken sweep must never
re-upsert ~20 labels. re-upsert ~20 labels.
type: string type: string
required: false required: false
@ -101,14 +102,14 @@ jobs:
# #
# bootstrap: read from the DECLARED workflow_call input and nothing # bootstrap: read from the DECLARED workflow_call input and nothing
# else. The old gate read `github.event.inputs.bootstrap` from inside # else. The old gate read `github.event.inputs.bootstrap` from inside
# this called workflow — which Forgejo leaves empty at the # this called workflow, and on this instance that context arrived
# workflow_call boundary even though the top-level caller receives the # empty (runs 459/523: every dispatch-woken sweep bootstrapped on a
# value in both contexts (ceremony#215; probe runs 6/7 measured the # bootstrap=no body) while the top-level caller received the value in
# boundary, merged runs 459/523 paid for it: every dispatch-woken # both contexts (probe runs 6/7) — ceremony#215. The reliable channel
# sweep bootstrapped). The caller passes the value through # is declare-and-pass, so that is the only one used. The caller passes
# `with.bootstrap`; anything not exactly "yes" is fed through for # the value through `with.bootstrap`; anything not exactly yes|no is
# labels-reconcile's own yes|no validation to judge, so a typo refuses # fed through for labels-reconcile's own validation to judge, so a
# loudly instead of silently bootstrapping. # typo refuses loudly instead of silently bootstrapping.
- name: reconcile state + stale - name: reconcile state + stale
if: github.repository != 'heavy-duty/ceremony' if: github.repository != 'heavy-duty/ceremony'
uses: ./.ceremony-src/actions/labels-reconcile uses: ./.ceremony-src/actions/labels-reconcile

View file

@ -45,9 +45,10 @@ jobs:
# named `labels` (self-labels.yml). # named `labels` (self-labels.yml).
uses: ./.github/workflows/labels-sweep.yml uses: ./.github/workflows/labels-sweep.yml
with: with:
# The dispatch input crosses the workflow_call boundary HERE, or not at # The dispatch input crosses the workflow_call boundary HERE, or not
# all: the called workflow cannot read this caller's dispatch inputs on # at all: on this instance the called workflow did not see this
# this forge (ceremony#215). On `schedule` the top-level context is # caller's event inputs implicitly (ceremony#215), so declare-and-pass
# is the only channel used. On `schedule` the top-level context is
# empty, and empty maps to "no" EXPLICITLY — a cron that bootstraps is # empty, and empty maps to "no" EXPLICITLY — a cron that bootstraps is
# the failure kimi named before it could exist. # the failure kimi named before it could exist.
bootstrap: ${{ inputs.bootstrap || 'no' }} bootstrap: ${{ inputs.bootstrap || 'no' }}

View file

@ -1,8 +1,9 @@
### Fixed ### Fixed
- The sweep's `bootstrap` value crosses the `workflow_call` boundary as a - The sweep's `bootstrap` value crosses the `workflow_call` boundary as a
declared input passed by the caller — a called workflow cannot read the declared input, explicitly passed by the caller — the one channel measured
caller's dispatch inputs on this forge (#215). to work; the called workflow did not see the caller's event inputs as an
implicit substitute on this instance (#215).
- Before the bridge, `github.event.inputs` was empty inside the called - Before the bridge, `github.event.inputs` was empty inside the called
workflow, so every dispatch-woken sweep bootstrapped: ~20 label upserts on workflow, so every dispatch-woken sweep bootstrapped: ~20 label upserts on

View file

@ -459,10 +459,11 @@ jobs:
sweep: sweep:
uses: heavy-duty/ceremony/.github/workflows/labels-sweep.yml@<pinned-tag> uses: heavy-duty/ceremony/.github/workflows/labels-sweep.yml@<pinned-tag>
with: with:
# Pass the dispatch input through the workflow_call boundary — the # Pass the dispatch input through the workflow_call boundary
# called workflow cannot read this caller's dispatch inputs on every # explicitly — a called workflow must not rely on reading the caller's
# forge (ceremony#215). Empty (schedule) maps to "no" explicitly, so a # event inputs (ceremony#215 measured that failing). Empty (schedule)
# cron-woken sweep never re-upserts the taxonomy. # maps to "no" explicitly, so a cron-woken sweep never re-upserts the
# taxonomy.
bootstrap: ${{ inputs.bootstrap || 'no' }} bootstrap: ${{ inputs.bootstrap || 'no' }}
# If this repo's PR-facing labels caller is named anything but `labels`, # If this repo's PR-facing labels caller is named anything but `labels`,
# pass that name alongside: `pr_workflow_name: <name>`. The sweep exports # pass that name alongside: `pr_workflow_name: <name>`. The sweep exports