Skip to content

v27 B3: a publishing workflow with no pull-request trigger still counts as CI when it runs a check before it publishes - #620

Closed
houman44 wants to merge 5 commits into
mainfrom
stream/yellow/20261031-171953-goal_cejel_v27_b3_publishing_with_checks
Closed

houman44 wants to merge 5 commits into
mainfrom
stream/yellow/20261031-171953-goal_cejel_v27_b3_publishing_with_checks

Conversation

@houman44

@houman44 houman44 commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

A v27 rubric correction to #517, on the operator's ruling of 1 November: under witan-rubric-v27-prospective-2026-10-19, B3 excludes a publishing workflow with no pull-request trigger only when it publishes without checking. A workflow that runs a check before it publishes is default-branch CI that also publishes, and is counted again. v17 and every other rubric are unchanged.

CONSTRAINTS-VERSION: 2026-08-01.5

Hand back

  • Done: A. Count a publishing workflow that checks before it publishes. checksBeforeEveryPublish in src/witan/repo-signals.ts reads the workflow v27 already reads as publishing. It counts the workflow when every publishing step has a check before it, either earlier in the same job or in a job that job needs, directly or transitively (workflowJobs now reads each job's id and needs: as a name, flow list or block list). A check is a command in the vocabulary the publish-only reader already refuses on. namesVerificationCommand now holds that vocabulary, shared by both readers, so no second list exists and no workflow can be both publish-only and checked. isCheckCommand reads the command from command position, so echo npm test and pip install pytest are not checks, and tsc-alias is not tsc. The check must be in an unconditional step with a non-conditional shell, on a line where failureStopsJobAt holds. These do not count: continue-on-error, || true, an if: that can be false (including if: false), and --if-present. Any always(), cancelled() or failure() in the file refuses outright. A job calling a reusable workflow must need a checking job. Scope limit: check actions (uses: steps) are not recognised, because no shared action list exists and the card forbids inventing one. Such a workflow stays excluded, as before.
  • Done: B. What still excludes. A publishing workflow with no check before its publish step is excluded exactly as before. Its note sentence, disclosure and metric condition are unchanged. The fixtures for unverified build plus gh-pages deploy, continue-on-error, || true, check after publish, check in a job not needed, if: always() publish, echoed test word and if: false all keep the old note and disclosure byte for byte.
  • Done: C. Notes. A counted publishing workflow gets one sentence in B3's note: <path> is counted as CI verification because a check runs before it publishes; it <F3-b trigger and environment clause>. The sentence comes after any unchanged uncredited note and fits the 1000-character limit, falling back to a shorter form and then to a count.
  • Done: D. B2 agrees. B2 never reads the B3-only publishes reason. A workflow counted under A is credited by both criteria, which the new test asserts for every fixture. The existing "B2 and B3 never disagree" test now accepts B3's counted sentence, and its credited-count equality still holds on every fixture.

Tests (red first)

  • 0c75f58d8 committed b3-publishing-with-checks.test.ts red. It covered these cases: npm test then npm publish on push to main; the test in a needed job; the test in a transitively needed job; pytest before a twine upload on a release tag. Nine tests failed under v27. The excluded cases passed.
  • d3f3517fc is the fix. c0c9a76bf updates existing v27 expectations. cf062bc4b adds edge cases: a reusable publish counted only through a needed test job; pip install pytest, tsc-alias and npm test --if-present are not checks.
  • v17 is identical: fixtures/b3-publishing-with-checks-v17.json pins v17's full per-criterion output for every card fixture. Scores, statuses, notes, disclosures, metric values and finding summaries were scored at 206074f1f, before the change, and v17 still matches after it.
  • No rise against v17 on every new fixture (checkNoRise, lostFindings).

v27 expectations that moved, and pins

  • Two existing v27 expectations moved, both on workflows that run npm test before npm publish. That is the ruling's case:
    • In ci-claims-publish-and-lockfile.test.ts, the npm pair is now counted, gated or not, with the counted note. The PyPI pair, which builds and publishes without a check, keeps its exclusion test.
    • false-assurance-v27.test.ts b3-test-then-publish-workflow is now counted, at v17's depth, with the counted note. I also updated that fixture's purpose string.
  • No v27 behaviour fingerprint, generated-suite pin or ci-claims-main-v27.json fixture pin moved. Every later: B3 credits an ungated publish workflow as CI; removing the human gate raises the score #517 evasion and control fixture builds and publishes without a check.
  • For review: I changed the shared PR-gate CI count glossary sentence in certificate-presentation.ts, which describes v27, so it no longer says "whatever else it runs". It now says a check before every publish keeps the workflow counted. That glossary is rendered on any certificate showing default_branch_ci_depth, v17 included, so this is a text change visible under v17. It changes no score, note or metric. The precedent is earlier v27 glossary edits (3b9d0bdef, c9e30ecfe). If v17 certificate bytes must not move at all, revert that one hunk; nothing else depends on it.

Verification

  • pnpm typecheck: clean.
  • pnpm test (full suite, 172 files): 6107 passed, 2 failed, 2 skipped. Neither failure comes from this change:
    • maeve-lesson-delivery.test.ts: a seed already on main, PENDING_cejel_name_the_repo_on_external_commits_in_preregistrations_2026-10-23.json, is 8 days old against the 7-day bound. That directory's open retire PRs are maeve: retire 11 delivered holding-dir seed file(s) #613 and maeve: retire 11 delivered holding-dir seed file(s) #615.
    • ingest.test.ts "rejects a non-regular explicit ingest candidate": this also fails when run alone. On this macOS host tmpdir() sits under a symlinked /var, so the code throws "refusing symlinked ingest directory" before the non-regular check. This branch does not touch the ingest code.
  • Lint: this repository has no lint script or linter configuration, and CI runs typecheck, build and tests, so no lint ran.

Lesson

Staged as docs/orchestration/maeve-unanchored-lessons/PENDING_cejel_exclude-only-what-carries-no-evidence_2026-10-31.json, validated by the pre-commit hook. In prose: a closure that stopped crediting any release workflow once a step published, "whatever else it runs", removed the false assurance of an unchecked publish. It also removed the real evidence of the checks those workflows run first. When a closure removes credit for one bad property, exclude only what carries no evidence of the good one, and pin both sides with a counted fixture and an excluded fixture.

Dunstan handback

No tests entry is declared. This repository is a single root package with no turbo, so a JUnit run matching CI's test job would be the full suite.

{"dunstan": "0.1", "headCommit": "9c00569adccf97af2d76ab6a1c8cd2fbc31e891a", "filesChanged": ["docs/orchestration/maeve-unanchored-lessons/PENDING_cejel_exclude-only-what-carries-no-evidence_2026-10-31.json", "src/witan/__tests__/b3-publishing-with-checks.test.ts", "src/witan/__tests__/ci-claims-publish-and-lockfile.test.ts", "src/witan/__tests__/false-assurance-v27.test.ts", "src/witan/__tests__/fixtures/b3-publishing-with-checks-fixtures.ts", "src/witan/__tests__/fixtures/b3-publishing-with-checks-v17.json", "src/witan/__tests__/fixtures/false-assurance-fixtures.ts", "src/witan/certificate-presentation.ts", "src/witan/repo-signals.ts", "src/witan/rubric-version.ts"], "references": [{"issue": "#517", "relation": "cites"}]}

Land with a merge commit. Propose-only: do not merge from this session.

🤖 Generated with Claude Code

houman44 and others added 5 commits October 31, 2026 18:27
…at checks before it publishes is counted; one that does not stays excluded; v17 pinned

Counted under v27 (fail before the fix): the npm test script then npm
publish in one job on push to main; the test in a job the publishing job
needs, directly or transitively; pytest before a twine upload on a release
tag. Still excluded (pass): setup, an unverified build and a gh-pages
deploy; a check with continue-on-error, || true or if: false; a check after
the publish; a check in a job not needed; a publish that runs if: always();
a test command named in echo text. v17's output for every fixture is pinned
from 206074f, before the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…heck before it publishes is counted by B3; one that publishes without checking stays excluded

Operator's ruling of 1 November 2026. Under v27 only, a workflow
publishingWorkflowDetail reads as publishing is counted when a check runs
before every publish, in the publishing job or in a job it needs, directly
or transitively (checksBeforeEveryPublish). A check is a command in the
verification vocabulary the publish-only reader already uses
(namesVerificationCommand, now shared by both), read from command position
(isCheckCommand), in an unconditional step whose shell is not conditional
and whose failure stops the job (failureStopsJobAt). continue-on-error,
|| true, a condition that can be false, a publish after the check, and any
always()/cancelled()/failure() in the file do not count. B3's note names a
counted workflow in one clause and keeps the F3-b trigger and environment
text. v17 and every other rubric are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…en-publish workflows, B2/B3 agreement, glossary and v27 description

The npm release workflow in ci-claims-publish-and-lockfile and the
b3-test-then-publish-workflow fixture run npm test before npm publish, so
under the correction v27 counts them as v17 does; their v27 expectations
move to the counted note, and the PyPI pair, which builds and publishes
without a check, keeps its exclusion test. The B2/B3 agreement test admits
B3's counted sentence after the shared ones; the credited counts still
agree. The PR-gate CI count glossary and v27's description say a check
before every publish keeps the workflow counted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ted only through a needed test job; pip install pytest, tsc-alias and npm test --if-present are not checks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e bad property excludes only what carries no evidence of the good one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@houman44

houman44 commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

Review at head 9c00569

This is an independent adversarial pass: 39 probe workflows scored through scoreRepoWithPublicCejel with this PR's fixture harness. I reproduced finding 1 myself. One blocker: the new credit can be earned by a publish that runs after a failed check.

Blocker

  1. success() || … re-credits a publish that ignores the check.
    • STATUS_FUNCTION_NOT_SUCCESS_PATTERN (repo-signals.ts ~7351) refuses only always, cancelled and failure.

    • isAlwaysTrueCondition (~7075) reads success() || true as always true, so the publish counts as unconditional.

    • In GitHub Actions any status function in an if: drops the implicit success() &&. So these publishes run when the check fails: the same case as the excluded if: always(), written differently.

    • Reproduced on a one-job push: main release (npm ci, npm test, then the guarded npm publish), under v27:

      publish guard result B3
      if: always() not credited 1.0
      if: success() || true credited 1.6
      if: success() || github.event_name == 'push' credited 1.6
    • The job-level form needs: test plus if: ${{ success() || true }} is also credited (reviewer's probe).

    • Suggest refusing any if: that contains a status function together with another disjunct, with excluded fixtures for each form.

Should-fix

  1. A run: key that is not a step is read as a check. The step reader matches run: at any indent inside a step item. Three cases were credited:

    • a uses: action whose with: has run: npm test;
    • actions/github-script with with: { run: npm test, … };
    • an env var named run.

    The reader predates this PR, but this PR is the first to grant credit through it.

  2. failureStopsJobAt in firstCheckLineByJob is untested. With it removed, all three PR test files still pass, and run: npm test; true becomes credited. Suggested excluded fixtures: npm test; true, npm test | tee x, npm test &. Four other mutants were killed: step order, the status-function refusal, needs always satisfied, and the reusable-workflow needs requirement.

  3. The 1000-character note fallback drops workflows without saying so (b3NoteWithCheckedPublishing, ~6680).

    • It fills greedily and never reserves room for the closing "N more … are counted" sentence.
    • With 12 checked publishing workflows and long names, the note is 903 characters, names 4, and says nothing of the other 8.
    • There is no test of the limit or the fallback.
  4. The check vocabulary reads only the command word. All of these were credited:

    • pytest --version;
    • npx tsc --version;
    • an eslint call with --max-warnings 9999 --no-error-on-unmatched-pattern nothing;
    • npm test where "test": "true".

    Existing pull-request B3 has the same blindness, so it is consistent, but this is a cheap way back to the credit. At least refuse --version and --help.

  5. No RUBRIC_CHANGELOG.md entry. leaderboard/RUBRIC_CHANGELOG.md says a change to how Cejel scores that ships without an entry "is a bug, not a release". This PR changes v27 B3 scoring, and CHANGELOG.md has no entry either. v27 on a shallow clone gives no composite or verdict and says how to fetch the history, instead of scoring without A2 #619 recorded its v27 change in both.

Nits

  • Workflows GitHub would reject still earn credit. A self-needs or a two-job needs cycle is credited, because needsCheck counts the job itself.
  • An unseen publish rides on a checked one. A publish the reader does not see, in a job with no needs, is credited alongside a checked one. Examples: a flow-style - {run: npm publish}, or ./scripts/pub.sh that publishes.
  • The BASH_ENV refusal can be dodged by quoting. It is a literal match, so "BASH_EN""V=..." gets past it. Suspected only; GitHub runtime not exercised.
  • The v27 behaviour fingerprint does not move. The corpus has no check-then-publish workflow; this is the same gap as later: the rubric behaviour-fingerprint corpus has no shallow clone, so v27 builds that differ on shallow clones share one fingerprint #622.
  • For the operator: v17 certificate text changes. The default_branch_ci_depth glossary sentence in certificate-presentation.ts is rendered on v17 certificates too. The PR offers to revert that hunk.

Holds

  • Correctly excluded:
    • continue-on-error, on the step or on the needed job;
    • || true, || :, ; true and set +e;
    • !cancelled(), and success() || failure();
    • a check after the publish, a check in a job that is not needed, or a check in another file;
    • a needed job skipped by if, or only one of two publishing jobs checked;
    • an anchor on an if: false job.
  • Correctly credited: a fail-fast: false matrix, a multi-line check under bash -e, an action-based release after a test, and gh release upload or cargo publish after a check.
  • Every other rubric is unchanged. v17 to v26 are byte-identical across every probe apart from the stated glossary sentence.
  • Tests and public surface. The three PR test files pass (986 passed, 1 skipped). There are no internal names in the added lines.
  • CI. build-test fails only on the stale lesson seed, which maeve: retire 11 delivered holding-dir seed file(s) #615 clears.

Merging is the operator's call. I'd hold it for finding 1.

@houman44

houman44 commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

Held for the blocker in comment 6562959964; superseded by goal_cejel_v27_b3_publishing_with_checks_v2_2026-11-01, which starts from this branch.

@houman44 houman44 closed this Nov 1, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 1, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant