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

View file

@ -45,9 +45,10 @@ jobs:
# named `labels` (self-labels.yml).
uses: ./.github/workflows/labels-sweep.yml
with:
# The dispatch input crosses the workflow_call boundary HERE, or not at
# all: the called workflow cannot read this caller's dispatch inputs on
# this forge (ceremony#215). On `schedule` the top-level context is
# The dispatch input crosses the workflow_call boundary HERE, or not
# at all: on this instance the called workflow did not see this
# 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
# the failure kimi named before it could exist.
bootstrap: ${{ inputs.bootstrap || 'no' }}

View file

@ -1,8 +1,9 @@
### Fixed
- The sweep's `bootstrap` value crosses the `workflow_call` boundary as a
declared input passed by the caller — a called workflow cannot read the
caller's dispatch inputs on this forge (#215).
declared input, explicitly passed by the caller — the one channel measured
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
workflow, so every dispatch-woken sweep bootstrapped: ~20 label upserts on

View file

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