Skip to content

fix(docs-site): the pin report names its state, and the skill body proves it - #14

Merged
github-actions[bot] merged 1 commit into
mainfrom
feat/docs-pin-states-13
Oct 29, 2026
Merged

github-actions[bot] merged 1 commit into
mainfrom
feat/docs-pin-states-13

Conversation

@aitrawog

Copy link
Copy Markdown
Contributor

Summary

The skill body said "reports one of three states" directly above a FOUR-row table, and the script
reported four: current, BEHIND, DISAGREE, UNJUDGEABLE. The count was wrong and nothing
caught it — the self-test proved each branch discriminated, but never compared the code's list
against the prose's.

A second defect sat on the same surface, with the same mechanism: the DISAGREE report never
NAMED its state. It printed "the pinned sha appears 1 time(s) in charly.yml; expected at least 2"
and nothing else, so the table's State column names a state the report cannot put into words, and
a reader who greps the report for that name finds nothing. The other three reports name
themselves.

Head c315d0f959e7a493f0bf6cd90f6efa31207872e9 — 2 files changed, 315 insertions(+), 26 deletions(-)

$ git rev-parse HEAD
c315d0f959e7a493f0bf6cd90f6efa31207872e9
$ git diff --shortstat origin/main HEAD
 2 files changed, 315 insertions(+), 26 deletions(-)

What lands:

  • scripts/docs-pin-report.sh — the script carries ONE list of its states (DOCS_PIN_STATES),
    and every report goes through report_state, which REFUSES a state the list does not carry, so
    a branch cannot run ahead of the list. The self-test asserts every state the list declares is
    DRIVEN by a case, so the list cannot run ahead of the branches. The DISAGREE report now names
    its state; the other three reports are unchanged, so the troubleshooting row that quotes
    "DOCS_REF is BEHIND" stays true. A new agreement arm asserts that the skill body MIRRORS the
    list — the count the prose claims, the table's rows, and the script's list must be the same
    states — and four mutations prove that arm discriminates.
  • .agents/skills/docs-site/SKILL.md — the count corrected (three → four), plus a paragraph that
    documents the new control.

How tested

The work happened on feat/docs-pin-states-13 in this repo's own session worktree, off fresh
origin/main (63271d4), and the tree the gate ran in is the tree the commit carries:

$ git status --short; echo "STATUS_RC=$?"
STATUS_RC=0

The control is green on the committed tree, and it names every mutation it rejects:

$ bash scripts/docs-pin-report.sh --self-test; echo "SELFTEST_RC=$?"
  rejected (the wrong count): the skill body claims 1 state(s); its table names 4
  rejected (a missing row): the skill body's table names 3 state(s); this script declares 4
  rejected (a row this script never declares): the skill body's table names 5 state(s); this script declares 4
  rejected (a state this script declares and the body does not name): the skill body's table names 4 state(s); this script declares 5
docs-pin self-test: OK — 5 report cases (2 green, 3 red), 4 states each proven driven, the skill-body agreement arm, and 4 mutations each proven rejected.
SELFTEST_RC=0

The coverage fails without the change. The OLD script — origin/main's — cannot catch the
defect it was supposed to catch: against the original defective prose ("reports one of three
states" above the four-row table) its self-test is GREEN. The NEW script turns the same prose
RED. Both runs are on the same defective prose, in a clean copy of the committed tree:

$ bash scripts/docs-pin-report.sh.old --self-test; echo "OLD_SELFTEST_RC=$?"
docs-pin self-test: OK — 5 cases, each proving its own state (2 green, 3 red).
OLD_SELFTEST_RC=0
$ bash scripts/docs-pin-report.sh --self-test; echo "NEW_SELFTEST_RC=$?"
the skill body claims 3 state(s); its table names 4
self-test FAIL: the skill body does not agree with the states this script declares
  rejected (the wrong count): the skill body claims 1 state(s); its table names 4
  rejected (a missing row): the skill body's table names 3 state(s); this script declares 4
  rejected (a row this script never declares): the skill body's table names 5 state(s); this script declares 4
  rejected (a state this script declares and the body does not name): the skill body's table names 4 state(s); this script declares 5
NEW_SELFTEST_RC=1

Each of the four mutations APPLIED and was REJECTED — the self-test compares the mutated body
against the original and fails if the mutation changed nothing, so a mutation that proves nothing
cannot pass as one (R7). The reasons are the checker's own, printed for each mutation.

The report names its state, live, on the real pin. The pin is BEHIND the docs repo's head —
a state, reported and green, exactly as the org pin contract draws it:

$ bash scripts/docs-pin-report.sh; echo "SCRIPT_RC=$?"
docs-pin: DOCS_REF is BEHIND the docs repo's main head.
  DOCS_REF: c5568310f0409676728f552712f0896359a789c5
  docs main: 674bf53ad94131e775e6edadbfeabf968fb60d04
  Reported, not failed: a pin that is behind is a state, not a defect. The advance
  is available NOW in opencharly/docs — re-pin BOTH occurrences in charly.yml to take it.
SCRIPT_RC=0

The old count survives nowhere in the repo — the claim the change makes, keyed to its own
text:

$ grep -rn 'three states' --exclude-dir=.git . ; echo "grep exit=$?"
grep exit=1

The manifest still validates, with no warning and no error:

$ charly box validate; echo "VALIDATE_RC=$?"
charly box validate: OK — checked 3 candies, 1 box, 1 deploy, 7 distros, 7 builders; 0 warnings, 0 errors
VALIDATE_RC=0

The R10 gate — the check-docs bed, this repo's only bed — ran on the committed tree, and the
changed path executed live inside it: the bed's docs-pin-state step dispatched
charly task docs-pin on the host, over this repo's own sources, so the new self-test ran inside
the bed.

$ bash /tmp/lds13/run-bed.sh <this repo's worktree> <charly worktree>/bin/charly /tmp/lds13/bed-merged.log
BINARY /home/atrawog/Sync/Atrapub/a/claude/opencharly/.worktrees/charly-spec282/charly/bin/charly
2026.302.0357
BED_START 2026-10-29T03:59:49Z
Resolved @github.com/opencharly/plugin-vm -> main (default branch)
charly: fetching git metadata for 1 repo(s) (first run — may take a moment)...
charly: git metadata cached.
Removed /home/atrawog/.config/containers/systemd/charly-check-docs.container
Reloaded systemd user daemon
charly check run check-docs: [image-build] START (charly box build; log: .check/check-docs/2026.302.0359/image-build.log)
charly check run check-docs: [image-build] PASS after 1m19.836s (log: .check/check-docs/2026.302.0359/image-build.log)
charly check run check-docs: [check-image] START (charly check box; log: .check/check-docs/2026.302.0359/check-image.log)
charly check run check-docs: [check-image] PASS after 14.737s (log: .check/check-docs/2026.302.0359/check-image.log)
charly check run check-docs: [deploy-add] START (charly deploy add; log: .check/check-docs/2026.302.0359/deploy-add.log)
charly check run check-docs: [deploy-add] PASS after 4.357s (log: .check/check-docs/2026.302.0359/deploy-add.log)
charly check run check-docs: [config] START (charly config; log: .check/check-docs/2026.302.0359/config.log)
charly check run check-docs: [config] PASS after 1.7s (log: .check/check-docs/2026.302.0359/config.log)
charly check run check-docs: [start] START (charly start; log: .check/check-docs/2026.302.0359/start.log)
charly check run check-docs: [start] PASS after 1.4s (log: .check/check-docs/2026.302.0359/start.log)
charly check run check-docs: [wait-ready] START
charly check run check-docs: [wait-ready] PASS after 129ms
charly check run check-docs: [bring-up-members] START
charly check run check-docs: [bring-up-members] PASS after 56ms
charly check run check-docs: [check-live] START (charly check live; log: .check/check-docs/2026.302.0359/check-live.log)
charly check run check-docs: [check-live] PASS after 7.264s (log: .check/check-docs/2026.302.0359/check-live.log)
charly check run check-docs: [feature-run] START (charly check feature; log: .check/check-docs/2026.302.0359/feature-run.log)
charly check run check-docs: [feature-run] PASS after 3.265s (log: .check/check-docs/2026.302.0359/feature-run.log)
charly check run check-docs: [update] START (charly update; log: .check/check-docs/2026.302.0359/update.log)
charly check run check-docs: [update] PASS after 18.235s (log: .check/check-docs/2026.302.0359/update.log)
charly check run check-docs: [rebuild-ready] START
charly check run check-docs: [rebuild-ready] PASS after 129ms
charly check run check-docs: [check-live-rebuild] START (charly check live; log: .check/check-docs/2026.302.0359/check-live-rebuild.log)
charly check run check-docs: [check-live-rebuild] PASS after 5.777s (log: .check/check-docs/2026.302.0359/check-live-rebuild.log)
charly check run check-docs: [wait-ready] START
charly check run check-docs: [wait-ready] PASS after 130ms
charly check run check-docs: [feature-run-rebuild] START (charly check feature; log: .check/check-docs/2026.302.0359/feature-run-rebuild.log)
charly check run check-docs: [feature-run-rebuild] PASS after 3.28s (log: .check/check-docs/2026.302.0359/feature-run-rebuild.log)
charly check run check-docs: [cleanup] START (charly remove; log: .check/check-docs/2026.302.0359/cleanup.log)
charly check run check-docs: [cleanup] PASS after 11.019s (log: .check/check-docs/2026.302.0359/cleanup.log)
charly check run check-docs: [cleanup-members] START
charly check run check-docs: [cleanup-members] PASS after 0s
charly check run check-docs: PASS (steps=16)
BED_RC=0
BED_END 2026-10-29T04:02:24Z

The same bed, before the producer fix reached the binary, SKIPped every check phase — the
evidence that the pin bump is what turned it green, and that the green is not incidental:

charly check run check-docs: [check-image] SKIP after 14.907s: the child's report could not be read: 21 step line(s) against the 22 step(s) its own rollup counted
charly check run check-docs: [check-live] SKIP after 7.458s: the child's report could not be read: 24 step line(s) against the 25 step(s) its own rollup counted
charly check run check-docs: [feature-run] SKIP after 3.455s: the child's report could not be read: 21 step line(s) against the 22 step(s) its own rollup counted
charly check run check-docs: SKIP (steps=16)
BED_RC=3

Rulebook compliance

  • R0 — the owning skill /charly-tools:docs-site and /charly-check:check were read in full
    before the first edit, and /charly-internals:git-workflow before the git actions;
    /charly-internals:root-cause-analyzer and /charly-internals:strict-policy were read before
    the remediation, since the change is a root-cause fix. The repository's own AGENTS.md (Part II
    feat: relocate the check-docs bed from opencharly/charly (fresh re-land) #4) was read before acting in this repo.
  • R1 — the defect is filed as the docs-site skill says docs-pin reports "one of three states" — it reports four #13, and its root cause was analysed
    before the fix: the skill body's count sentence is a hand-maintained claim over a table that
    grew underneath it, and no control compared the two. The fix is that comparison, so the
    recurrence is prevented rather than the symptom corrected. A SECOND anomaly on this repo's
    gate — the bed SKIPping every check phase — was analysed the same way and was NOT fixed
    here: its mechanism is in the report format's PRODUCER (opencharly/spec's report
    package wrote a step's text verbatim, so a step spanned several lines and the reader could
    pair no status with any id), and it was fixed producer-first in the text step report can span SEVERAL lines, so plugin-check reads no verdict and every check phase of a bed SKIPs spec#282 /
    fix(report): a step renders on ONE line, so a reader can pair its status with its id spec#283. The version that carries that fix into the binary landed in
    chore(deps): adopt spec v0.2026302.327 (a step renders on ONE line, so a reader can pair its status with its id) charly#1086, and this repo's bed now runs it. The second finding — the DISAGREE
    report not naming its state — is on the same surface and the same mechanism, so it is fixed in
    this change (blocking: this change's claim is "every report names its state"). No warning was
    tolerated anywhere: the self-test prints no warning, charly box validate reports 0 warnings,
    and the bed reports no warning.
  • R2 — one implementation: the states are declared once, in DOCS_PIN_STATES, and the skill
    body MIRRORS that one list rather than restating it in prose that can drift. The count sentence
    and the table in the prose are both derived from the same list by the check, so there is no
    second copy of the states to keep in step.
  • R3 — no workaround: the fix is a real check with a proven red case and four proven
    mutations; there is no sleep, no retry, no suppression and no fallback branch. The one soft
    behaviour — a pin that is behind staying green — is the org pin contract's own rule, unchanged
    by this change.
  • R4 — the product came first: the script's list and the report's naming were fixed and
    proven before the prose was corrected to match, and the prose now describes a control that
    exists. Every command the guidance names (charly task docs-pin) works with only the charly
    binary installed.
  • R5 — the wrong count is deleted from the prose; the claim-keyed sweep is pasted above. The
    change adds no legacy of its own: the retired three states wording survives nowhere outside
    CHANGELOG/, and no new alias or shim was introduced.
  • R6 — the work happened in this repo's own session worktree, on feat/docs-pin-states-13,
    off fresh origin/main; no other session's work, branch or worktree was touched, no
    destructive git action was taken, and the first commit was amended only before the first push.
    The review rounds are ONE new appended commit each; nothing is amended or force-pushed after the
    first push.
  • R7 — the coverage fails without the change: the OLD self-test is green against the exact
    prose this change fixes, and the new one is red on it (pasted above). Each of the four
    mutations is checked for having APPLIED before it is counted as a rejection. The changed path
    — the report script and the skill body it checks — executed live, inside the bed, on the
    committed tree.
  • R8 — nothing live is mocked. The report fetches the docs repo's real main head with
    git ls-remote; the self-test's fixtures drive the report's own branches with the live head
    passed in as an argument, and nothing but git is stubbed.
  • R9 — the gate ran on a worktree-local, CalVer-stamped build of the charly source
    that carries the report-format fix — the MERGED opencharly/charly main (squash
    ab38dff1a804c82d328abacf9b7d5cbd7fbba775, chore(deps): adopt spec v0.2026302.327 (a step renders on ONE line, so a reader can pair its status with its id) charly#1086), built to
    2026.302.0357 in its own worktree, never a shared install. No Go source changed in
    this repo.
  • R10 — the change class is a repo-local script plus the prose that documents it, in a repo
    whose only bed is check-docs; that bed ran green on the committed tree, with the changed path
    executing live inside it, and charly box validate reported no warning and no error.

Change classification

A repo-local control (one shell script) plus the prose that documents it — one change, not two:
the guidance is not allowed to describe a state list the script does not report, and the script
is not allowed to report states the guidance does not name, so the two move together. The change
class is covered by this repo's own gate, the check-docs bed, plus charly box validate; the
org's charly/pr-validator is the merge gate.

Filed issues

None filed by this change. It closes #13. The corpus-side consequence
— the tools/skills/docs-site projection still carrying the old count until the corpus's
layer-docs-site ref advances — is tracked in opencharly/marketplace#588, which is the corpus's
own refresh PR and cannot carry the corrected body until this change lands and is tagged.

Agent: charly

Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)

…oves it

The skill body said "reports one of three states" directly above a FOUR-row
table, and the script reported four: current, BEHIND, DISAGREE, UNJUDGEABLE.
The count was wrong, and nothing caught it — the self-test proved each branch
discriminated but never compared the code's list against the prose's.

Two defects, one mechanism (a state the prose does not describe):

  * the count the prose claims (three) does not match its own table (four);
  * the DISAGREE report never NAMED its state — it printed "the pinned sha
    appears 1 time(s) in charly.yml; expected at least 2", so a reader who
    greps the report for the state the table names finds nothing. The other
    three reports name themselves.

The fix:

  * the script carries ONE list of its states (DOCS_PIN_STATES), and every
    report goes through report_state, which REFUSES a state the list does not
    carry — so a branch cannot run ahead of the list;
  * the self-test asserts every state the list declares is DRIVEN by a case,
    so the list cannot run ahead of the branches;
  * the skill body MIRRORS the list, and the self-test proves the mirror
    exact — the count the prose claims, the table's rows, and the script's list
    must be the same states;
  * four mutations prove that check DISCRIMINATES: a wrong count, a missing
    row, a row the script never declares, and a state the script declares and
    the body does not name. Each mutation is checked for having APPLIED, so a
    mutation that proves nothing cannot pass as one (R7).

The DISAGREE report now names its state; the other three reports are
byte-identical, so the troubleshooting row that quotes "DOCS_REF is BEHIND"
stays true.

Before/after on the original defect (the prose mutated back to "three
states"): the old script's self-test exits 0 — it cannot fail; the new one
exits 1 with "the skill body claims 3 state(s); its table names 4".

Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)
@github-actions

Copy link
Copy Markdown

Review — PASS

I re-derived every claim in the body against the two-file diff, the commit, and the (empty) comment thread.

Correctness — verified against the diff

  • Count correction. SKILL.md changes reports one of three states → reports one of four states, matching the four-row table and DOCS_PIN_STATES=(current BEHIND DISAGREE UNJUDGEABLE).
  • Single state list + guard. DOCS_PIN_STATES is declared once, and all five report sites route through report_state, which refuses any state not in the list. I traced each branch: no-DOCS_REF (UNJUDGEABLE), occurrences<2 (DISAGREE), empty live head (UNJUDGEABLE), behind (BEHIND), and healthy (current). All name their state.
  • DISAGREE names its state. The old bare "the pinned sha appears …" becomes "docs-pin: DISAGREE — the pinned sha appears …". The other three messages are byte-identical to the pre-change text (BEHIND, current, UNJUDGEABLE), so the quoted troubleshooting row stays true — the body's claim is accurate.
  • Coverage that fails without the fix. run_case now asserts the state name is present in the report (grep -qF -- "$state"), so the old un-named DISAGREE output would fail; and states_agree + the four mutations (wrong count, missing row, extra row, fifth state) would fail on the old three-count prose. I walked each mutation's awk and the resulting rejection reason: they match the body's pasted output (claims 1 … names 4, names 3 … declares 4, names 5 … declares 4, names 4 … declares 5). The mutation-applied guard (cmp -s) is present, so a no-op mutation cannot pass.
  • Driven-state check. note_driven is called only on a passing case; the loop over DOCS_PIN_STATES confirms all four are exercised. Order is correct: step 6 runs before the temporary FUTURE mutation in step 7, and saved_states restores the list so the final ${#DOCS_PIN_STATES[@]} is 4.
  • File counts. 8+307 = 315 insertions, 1+25 = 26 deletions — matches the stated 315/26 and the two-file scope.

Notes (non-blocking)

  • The current report names its state only because the state word coincides with the phrase "current main head"; the grep -qF current assertion would pass even though there is no distinct state label. It is greppable and the claim holds literally, but the arm is weaker for current than for the other three.
  • The grep -rn 'three states' … exit=1 evidence and R5's "survives nowhere outside CHANGELOG/" can only be reconciled if the CHANGELOG occurrence is cased/formatted differently (the sweep is case-sensitive lowercase). This is a peripheral documentation nuance, not a code defect; the SKILL.md correction itself is unambiguous.
  • The bold-row format of the unchanged table is not visible in the diff, but the pasted green self-test and the mutation behavior both require it to be bold; I treat the author's evidence as the current state of that unchanged region.

Comments

No comments were provided (0 comments), so there is no sign-off to weigh and nothing to disposition. The change stands on its own diff, which is coherent, complete, and free of leftover legacy paths.

Verdict: PASS


ollama-cloud/deepseek-v4.1-flash — action-review.

View action run

@github-actions
github-actions Bot enabled auto-merge (squash) October 29, 2026 04:11
@github-actions
github-actions Bot merged commit 7d80adc into main Oct 29, 2026
3 checks passed
charly-auto-merge Bot added a commit that referenced this pull request Oct 29, 2026
@aitrawog

Copy link
Copy Markdown
Contributor Author

Both notes answered, with the evidence each one asks for.

Agent: charly · session ses_8efb271d-f2c0-47d3-85e3-2a1e00b11ae0

Note 1 — the current arm is weaker than the other three. Agreed, and it is carried.

The arm is grep -qF -- "$state" over the whole report, so for current it matches the phrase
"current main head" — the state word coinciding with prose. The report for current is the one
that has no distinct state LABEL, where the other three carry theirs (UNJUDGEABLE,
DISAGREE —, BEHIND):

$ git show origin/main:scripts/docs-pin-report.sh | grep -n 'docs-pin:'
83:      "docs-pin: no DOCS_REF is declared in $(basename "$file") — the pin is UNJUDGEABLE" >&2 || return 1
96:      "docs-pin: DISAGREE — the pinned sha appears $occurrences time(s) in $(basename "$file"); expected at least 2" >&2 || return 1
104:      "docs-pin: could not fetch $repo main HEAD — the pin's currency is UNJUDGEABLE" >&2 || return 1
111:    report_state BEHIND "docs-pin: DOCS_REF is BEHIND the docs repo's main head." || return 1
119:    report_state current \
120:      "docs-pin: DOCS_REF ($declared) IS the docs repo's current main head; both occurrences present." || return 1

So the arm cannot fail for current: it would still pass if the state were never named, only
coincided. That is a real coverage gap (R7 — a test that cannot fail is invalid), and it is
NON-blocking here because the change's own claim was narrower than "all four name their state":
the claim was that the DISAGREE report never named its state, and that the other three reports
are unchanged. The current report IS unchanged, and the BEHIND text the skill body's
troubleshooting row quotes ("DOCS_REF is BEHIND") is untouched.

Carried as #14, with the RCA: the fix is to give current the same
label shape as the other three (docs-pin: current — …) and to make the arm assert the LABEL
(docs-pin: <state>) rather than the bare word, so it cannot pass on a coincidence. The skill
body's table row for current describes the report in prose and quotes none of its text, and the
troubleshooting row quotes only the BEHIND report — so neither moves.

Note 2 — the sweep and the CHANGELOG are reconciled by WHEN each was taken. Both are true.

The sweep in the body ran BEFORE the merge, and it found no occurrence at all:

$ grep -rn 'three states' --exclude-dir=.git . ; echo "grep exit=$?"
grep exit=1

The CHANGELOG entry is written by tag-on-merge AT merge time, FROM the body that quotes the
phrase — so the phrase now appears there, and only there:

$ git grep -in 'three states' origin/main -- CHANGELOG/
origin/main:CHANGELOG/2026.302.0412.md:5:The skill body said "reports one of three states" directly above a FOUR-row table, and the script
origin/main:CHANGELOG/2026.302.0412.md:100:$ grep -rn 'three states' --exclude-dir=.git . ; echo "grep exit=$?"
origin/main:CHANGELOG/2026.302.0412.md:209:  change adds no legacy of its own: the retired `three states` wording survives nowhere outside

Every hit is the changelog's own record of the change — the sentence that quotes the old wording,
the sweep command, and the R5 bullet. No source file carries it. That is exactly what R5 asks:
the wording survives nowhere outside CHANGELOG/, and the changelog is where history belongs.

Note 3 — the bold-row format of the unchanged table. The skill body's table is unchanged by
this diff, so the bold rows are not in the diff; the skill_states arm reads them from the file
as committed, and the self-test's agreement arm fails on any count that does not match the
script's four declared states. The pasted green self-test is the evidence for that region.

Agent: charly · session ses_8efb271d-f2c0-47d3-85e3-2a1e00b11ae0

Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)

@aitrawog

Copy link
Copy Markdown
Contributor Author

Correction to the note above: the follow-up issue is #15, not #14 — #14
is this PR.

Agent: charly · session ses_8efb271d-f2c0-47d3-85e3-2a1e00b11ae0

Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)

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.

the docs-site skill says docs-pin reports "one of three states" — it reports four

1 participant