Repository navigation
fix(gate): the candy gate's activation precondition must name the LIVE blocker, not the discharged one - #260
Conversation
…E blocker (#259) The reusable's header and the README both stated ONE activation precondition for `candy-validate.yml`: the org pin must name a release carrying the schema-versioning removal, "the pre-removal pin still requires the retired stamp". That blocker is DISCHARGED — `#162` moved the pin past the stamp retirement, and the stamp-less manifest now validates green. A reader taking the header at its word would activate the gate and get a red on every repo declaring the routing-only `skill:` shape, for a reason the header does not name. The prose now names the LIVE condition and marks the discharged one as discharged: - the retired `version:` stamp — DISCHARGED (measured 2026-10-27: the knob's pre-#162 value v2026.271.0950 fails a stamp-less manifest with exactly the quoted error, rc=1; the knob's value of that day v2026.278.0912 accepts the same content, rc=0); - the routing-only `skill:` shape — THE LIVE CONDITION (measured 2026-10-27 on a pristine copy of a shipping manifest: v2026.278.0912 rejects it, rc=1, and a release carrying plugin-harness-kind v0.2026288.1214 accepts the SAME content, rc=0). So the knob must name a release whose plugin-harness-kind is v0.2026288.1214 or later. The knob is one and it is also the org PR-validator's engine pin, so the remedy is one deliberate variable change — the operator's, and named as such in both surfaces. The correction is enforced, not just written: four rows in `scripts/governance-claims.tsv` assert the LIVE condition is named on both surfaces and forbid the discharged one from being cited as the reason again. - `dotgithub/.github/workflows/candy-validate.yml` — header prose only; the executable payload is byte-identical (sha256 03bb1307… over the comment-stripped file, both sides). - `dotgithub/README.md` — the "Activation is deliberately deferred" section and the file list bullet. - `dotgithub/scripts/governance-claims.tsv` — the four new claim rows. Refs #259 Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)
Review — PASSVerification performed1. Diff matches the body's claims (correctness & honesty).
2. The coverage is real and discriminating (tests).
3. Body arithmetic and evidence are internally consistent. Baseline 4. Security & scope. No secret, network write, dependency, or credential introduced. The added 5. Comments. The thread is empty (0 comments), so there is no maintainer sign-off to weigh and none is required; the change stands on its own evidence, which it does. Minor, non-blocking observations
Neither observation contradicts the shipped change or its evidence, and neither warrants a block. Verdict: PASS ollama-cloud/deepseek-v4.1-flash — action-review. |
|
STATUS — landed and tagged; the validator's two observations are answered with the evidence that closes them. The landing
Observation 1 — the relaxation boundary is not a conservative minimum; it is exactThe review noted that "the exact relaxation boundary … is inferred from two measured points; it is a conservative minimum". The third point exists, and so does the schema that decides it. The complete set of the harness's Go-module tags: There is exactly one version between the rejecting pin and the accepting one — $ for t in v0.2026272.143 v0.2026279.629 v0.2026288.1214; do
gh api "repos/opencharly/plugin-harness-kind/contents/candy/plugin-harness-kind/schema/skill.cue?ref=candy/plugin-harness-kind/$t" --jq .content | base64 -d \
| grep -nE 'description|content:' | head -3
doneSo the bound the prose names — "the knob must name a release whose Observation 2 — the
|
…#733) (#734) opencharly/.github#260 corrected a live, org-wide misstatement: the candy validate gate's header named only the DISCHARGED activation blocker (the retired `version:` stamp) and not the LIVE one (the routing-only `skill:` shape), and the correction is enforced by four new rows in the org's own `scripts/governance-claims.tsv`. The umbrella's `dotgithub` gitlink recorded `.github`'s main at `d256175` — the previous release's tip — so it did not carry the correction. This advances it to `ff88be2c39`, the tip that does, through the sanctioned verb: `charly task sync` wrote the pin, `charly task init` materialized the checkout at it, and `charly task verify` judged the tree. The currency question, asked with the one verb that answers it, named exactly one pin of 411 as not current — `dotgithub`, `behind` — and none after the wave. The advance is a fast-forward against its own remote (ahead=2, behind=0), and the recorded pin is an ancestor of the new one in the pinned checkout itself. The producer merged and was tagged before this PR was opened (squash 784c530a, tag v2026.300.0808, validator PASS): producer-first order holds. Closes #733 Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)
Summary
The org's candy-manifest gate (
.github/workflows/candy-validate.yml, thecandy / validatereusable) states one activation precondition: "Do NOT activate it as a required gate untilvars.CHARLY_VERSIONnames a release that carries the schema-versioning removal — the pre-removal pin still requires the retired stamp and would fail the very PRs this leg unblocks." The same claim is repeated inREADME.md.That blocker is DISCHARGED.
opencharly/.github#162moved the org knob fromv2026.271.0950tov2026.278.0912, past the2026.275.2112stamp retirement, and a stamp-less manifest now validates green under that release. So the header, read at its word, licenses activating the gate — and activating it today would redden every repo that declares the routing-onlyskill:shape, for a reason the header does not name: the knob's engine still rejects that shape.This change corrects the two prose surfaces to name the LIVE condition and to mark the discharged one as discharged, and enforces the correction with four new rows in the org's cross-surface claims gate.
.github/workflows/candy-validate.yml03bb1307…, below)README.mdscripts/governance-claims.tsvrequire(the routing-only shape must be NAMED on each surface) and twoforbid(the discharged stamp must no longer be cited as the reason)The claim is measured, not inherited. Each release's own binary and plugins, the same manifest content:
version:stamp — DISCHARGED:v2026.271.0950fails a stamp-less manifest with exactlyschema 2026.261.1747 is required (found ""). Run: charly migrate(rc=1);v2026.278.0912accepts the same content (rc=0, "0 warnings, 0 errors").skill:shape — THE LIVE CONDITION: on a pristine copy of a shipping manifest,v2026.278.0912rejects it withnode "debian-debootstrap-vm-skill": plugin kind:skill: plugin_input fails #SkillInput: #SkillInput.description: incomplete value !=""(rc=1);v2026.300.0235accepts the same content (rc=0).The engines differ in exactly one pin, and it is the one the prose names:
plugin-harness-kindv2026.271.0950v0.2026254.600v2026.278.0912(the knob today)v0.2026272.143v2026.300.0235v0.2026288.1214So the knob must name a release whose
plugin-harness-kindisv0.2026288.1214or later. The knob is one — and it is also the org PR-validator's engine pin (pr-validator.ymlwelds it), so the remedy is one deliberate variable change, the operator's, named as such in both surfaces:T4: not triggered. T4 fires on a change touching the T3 "weakened guardrails" list. This diff touches three files, and it is on none of that list: the pre-push gate (the clone-level hook, installed from the
charlyrepo — this repo carries no hook script: no.githooks/here, andcore.hooksPathis unset), the pre-commit gate (same), the branch-protection config (scripts/org-ruleset.sh, untouched), this validator's spec (.github/workflows/pr-validator.yml, untouched), and the project rulebook's landing / attribution / no-force-push / no-direct-push rules (this repo'sAGENTS.mdand the umbrella's, both untouched). The one gate file this diff does touch,candy-validate.yml, is not the validator's spec and not a ruleset: its change is comment-only, and the executable payload is provably byte-identical (the sha256 pair below). The other T3 bullets: scope match — the table above accounts for every line of the diff, and the body describes the change's real content (two prose surfaces and four enforcing rows); secrets & exfiltration — none (no write outside the tree, no network, no credential); weakened guardrails — none: the diff adds four failure conditions and touches no guardrail's behaviour; the gate's own step list is byte-identical; supply chain — no new dependency, no version pin, no@githubref change, and the CI steps that run these scripts are unchanged. This repo's ownAGENTS.mdcalls the gate, the ruleset and the template self-modifying-security surfaces that must be strengthened or preserved — this change strengthens the governance gate and preserves the manifest gate byte-for-byte. The precedent in this repo is exact: #218, #242, #252, #254 and #258 all changed this repo's own gate and merged on that ground.Change classification
hook/workflow — the diff touches a GitHub Actions workflow file and the data of the org's cross-surface governance gate (
scripts/governance-claims.tsv), both executed by this repo's CI. It is notdocs-only: two of the three files are not prose (candy-validate.ymlis a gate; the claims manifest is the gate's data), so the tierdocumentation reviewedis forbidden here and the gate below is the workflow class's gate.Closes / relates to: relates to #259 — this is that issue's second arm (the prose). The issue stays open: its first arm is the org variable, which is the operator's to set (an org variable is not an agent's to set), and the prose here does not discharge it.
Breaking change + rollback: none — no public surface is removed or renamed, no schema field changes, and the gate's executable behaviour is unchanged. If this lands and proves wrong, the remedy is a revert PR (the squash lands on
mainand the tag is immutable, so a tag move is never the remedy).How tested
.github/workflows/workflow-checks.yml(theworkflow-checksjob) — all 11 offline steps, run on the committed tree (c01152445a5e71192c2f7dc2a4d4d2e8c033214b,git status --porcelainempty). The twouses:runtime steps (which need GitHub's own runners) run in this PR's CI. Output in block 1.charly check run <bed>beds: it is the org's GitHub-configuration repo (workflows, shell and Python owner scripts), not a deployable, and it contains nodisposable: truetarget. The gate above is the class's gate.charly versionand the run-calver do not apply.charlybinary, socharly versiondoes not apply to it. The engines measured below are each published release's own binary and plugins (fetched by tag), not a local build.scripts/governance-reconcile.sh— it ran live on the committed tree, asserted the four new rows, and was run as a negative control against the OLD prose, where exactly those four rows fail and the run exits 1. The prose's own claim (which engine reads which shape) was proven by a live 3×3 engine × case matrix, each cell run with that release's own binary and plugins on identical content. The workflow's executable payload was proven unchanged by hashing the comment-stripped file on both sides. All in block 2.1. Fresh-rebuild gate output — the change-class gate, run on the committed tree:
Zero warnings: the only
warn/errorstrings in the whole log are inside the fixture outputs of the self-tests (the strings the tests assert must appear in a negative case) and inside theskiplines, which are the sibling surfaces absent from a standalone.githubcheckout (the umbrella'sAGENTS.md, thegit-workflowskill source, the marketplace projection,action-review/charly.yml) — each skip is visible, and the run is not silent.checked=31is up from the baselinechecked=27: the four new rows, allok.2. Each changed code path executing
(a) The four new claim rows, on the committed tree — the changed path executing:
(b) The negative control (R7 — the coverage fails without the change) — the old prose (
HEAD~1) restored against the new manifest, same tree, same command:Exactly the four new rows turn red — no other row is disturbed, and the summary's
fail=is a boolean flag (scripts/governance-reconcile.sh:185), sofail=1means "at least one failure" and the exit code is 1. The tree was restored to the committed state afterwards (git checkout HEAD -- …;git status --porcelainempty).(c) The claim the prose states, proven live — the 3×3 engine × case matrix, each cell run with that release's own binary and plugins against identical manifest content (
caseA= a stamp-less copy of the action-review manifest,caseB= the same with a top-levelversion:stamp,caseC= a copy of the distro-debian manifest with its routing-onlyskill:node):The engines' own pins (
charly/go.modat each tag), which is the property the prose now names:and the knob's value today (read-only), which is the pin the prose measures against:
(d) The gate workflow's executable payload is unchanged — the comment-stripped file, both sides:
3. Docs-only evidence — N/A — the diff is not prose-only:
scripts/governance-claims.tsvis the governance gate's data andcandy-validate.ymlis a gate workflow, so the R5 claim-keyed sweep is not a documentation exercise. It is pasted in the R5 line below and enforced by the two newforbidrows.Attribution tier
fully tested and validated — the changed path (the claims manifest consumed by
scripts/governance-reconcile.sh) executed live on the committed tree with the new rows asserted (ok×4,checked=31 skipped=55 fail=0, exit 0), and the coverage is proven discriminating by the negative control (the old prose → those same four rowsFAIL, exit 1). The prose's own factual claim was proven by a live 3×3 engine × case matrix and by the published releases' owngo.modpins, not by reading a document. The gate's own workflow file is proven byte-identical in its executable payload.Rulebook compliance
/charly-internals:repo-setup) and "Git/ghworkflow … thepr-validatorgate … PR-only landing" (/charly-internals:git-workflow) were read before the first edit, together with this repo's ownAGENTS.md(Part II feat: centralize PR validator status migration #4). The repo-setup skill's landing chain — PR →validate / validate→ native auto-merge squash →tag-on-mergewrites the CHANGELOG — is what this PR follows; no CHANGELOG file is staged and no CalVer is minted here.skill:/agent:node — 10 repos / 40 nodes measured over the umbrella's materialized set, ~376 org-wide) are all carried inopencharly/.github#259, from which this work was taken. The gate output has zero warnings; the twouses:steps are the repo's own CI (run there, not here). Every blocking finding is fixed here (the prose on both surfaces); the one non-blocking finding — the org variable, which is the operator's to set — is filed inopencharly/.github#259and listed under## Filed issuesbelow. No finding is dismissed as "pre-existing", "flake" or "follow-up".gh variable set CHARLY_VERSION …) rather than a per-repo sweep; the enforcement is four rows in the one existing claims manifest, not a second manifest.podman/systemctl/ghsubstitute. The remedy named is the org variable the product already reads.go.modpins), and the prose is corrected to it. The prose's own claim — that a required gate at such a pin would redden every repo declaring that shape — is now backed by the error text the engine actually emits. Every command this change names works with thecharlybinary alone (the engines are each release's own binary; the gate is this repo's own shell/Python scripts).CHANGELOG/), which the rulebook keeps. Every live surface is clean, and the two newforbidrows now make the sweep enforced rather than a one-off. The retired form is not retained as a dual path: there is no fallback branch and no alternative activation route.git status --porcelainwas run before the first edit and before the commit (it listed exactly the three files of this change, and the untrackedlayer-charly-*/pod-charly-core/vm-charly-vmdirectories of other sessions were never touched). No destructive action: no reset, no force-push, no--no-verify, nocore.hooksPath, no direct push tomain, no amend after the push. The onegit checkout HEAD -- <two files>was the negative control's restore, inside this session's own worktree, of files this change itself owns, immediately verified clean (git status --porcelainempty). No submodule was edited (Part II docs(pr-template): explicit eval-bed list + per-bed results in the R10 section #1): this repo is a plain checkout, and the worktree is this session's own (.worktrees/gh259/dotgithub)..githubcheckout skip visibly (named, one line each) — they are not mocked and not silently passed.charlybinary, so no binary is built or deployed by this change; the engines measured are each published release's own binary, and no new runtime OS dependency is introduced.c011524), every steprc=0, zero warnings, and the coverage that would fail without the change is shipped (block 2b). Nodisposable: truetarget is involved — this repo has no deploy and no bed.description:/plan:changes. SDD: N/A — no schema or.cueedit, and no generated Go.opencharly/.github#259is this PR's tracking issue and carries aSTATUSat the commit and again at the PR open, naming this PR; the issue stays open because its other arm is the operator's. The open sibling PR fix(ci): re-pin actions/setup-go to a resolvable REF so validate/validate can start (#206) #207 (feat/repin-setup-go) touches the same file (.github/workflows/candy-validate.yml) in different hunks — its change is theactions/setup-gopin, mine is the header comment block — and it is untouched; aSTATUSthere says so.Agent:precedes theAssisted-by:trailer and the trailer is this body's LAST line.sdk, no plugin capability: this repo holds the org's GitHub configuration, not product code./tmpcopies and this repo's own tree.gofmt/golangci-lint/go vet/go testdo not apply to this diff.Filed issues
skill:shape — the candy gate's stated activation precondition is discharged, but a second blocker is un-named #259 — the org variable arm:vars.CHARLY_VERSIONmust name a release whoseplugin-harness-kindisv0.2026288.1214or later (today it namesv2026.278.0912→v0.2026272.143, which rejects the routing-only shape). The remedy is the operator's one line:gh variable set CHARLY_VERSION --org opencharly --body v2026.300.0235 --visibility all. It is not fixed here — an org variable is not an agent's to set — and the issue stays open until the knob moves. This PR lands the issue's prose arm; the same knob is also the org PR-validator's engine pin, so the change is made deliberately.Agent: charly
Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)