0.194.1: the two new gates resolve like the old ones (runnable binary… #587
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Validate Template Integrity | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'plugins/mycelium/**' | |
| - '.claude-plugin/**' | |
| - '.claude/**' | |
| - 'tests/**' | |
| - 'docs/**' | |
| - '*.md' | |
| - 'requirements-ci.txt' | |
| - '.github/workflows/validate.yml' | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - 'plugins/mycelium/**' | |
| - '.claude-plugin/**' | |
| - '.claude/**' | |
| - 'tests/**' | |
| - 'docs/**' | |
| - '*.md' | |
| - 'requirements-ci.txt' | |
| - '.github/workflows/validate.yml' | |
| jobs: | |
| validate: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read # security review DL-1262: the default token scope was unspecified | |
| steps: | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 | |
| with: | |
| python-version: '3.x' | |
| cache: 'pip' | |
| cache-dependency-path: requirements-ci.txt | |
| - name: Install CI dependencies | |
| run: pip install -r requirements-ci.txt | |
| # Supply-chain gates (security review DL-1262, finding 4). Both are pinned in | |
| # requirements-ci.txt so the auditors are themselves audited. | |
| - name: Audit CI dependencies for known vulnerabilities (pip-audit) | |
| run: pip-audit -r requirements-ci.txt --strict | |
| - name: Lint the workflows themselves (zizmor) | |
| run: zizmor --no-progress --min-severity medium .github/workflows | |
| # Check 33 (plugin tree must not contain unconsented personal identifiers) | |
| # fails open here by design — MYCELIUM_ATTRIBUTION_REGISTRY is intentionally | |
| # unset in CI. The registry lives in a private companion repo to avoid | |
| # publishing the very names whose private-channel attribution it tracks; | |
| # CI on this public repo has no credentials to fetch it. The check's real | |
| # enforcement point is the maintainer's pre-push validation; CI fail-open | |
| # is the accepted residual. If a backstop becomes necessary (e.g., a real | |
| # incident where a leak slipped past pre-push), revisit by piping a | |
| # names-only subset via a GitHub Actions secret. | |
| - name: Run structural integrity validation (includes Check 17 — ruff/shellcheck/pytest with baseline-aware logic) | |
| # Output is teed so the warning-ingestion step below can classify it. | |
| # pipefail is required: without it the step would report tee's exit code | |
| # and a validator failure would pass silently. | |
| run: | | |
| set -o pipefail | |
| bash tests/validate-template.sh 2>&1 | tee validator-output.txt | |
| - name: Ingest CI warnings into the learning loop (classify against warning-handbook) | |
| # Closes the loop this script was shipped for in v0.16.0 and never wired | |
| # into: it was documented as "auto-updated from CI signals" while being | |
| # invoked by nothing for ~2.5 months. Dry-run because a CI runner's | |
| # filesystem is ephemeral — accumulation into .claude/memory/warnings-log.md | |
| # happens on the maintainer's local/pre-push run, where the file persists. | |
| # What CI adds is the live wire: every run exercises the classifier against | |
| # real validator output, so a matcher that stops matching (the v0.60.1 | |
| # verify_citations failure mode) surfaces here instead of reading green. | |
| # Non-blocking on findings, blocking on a crash — an ingestor that cannot | |
| # run is a broken wire, which is the whole point of this step. | |
| run: python3 plugins/mycelium/scripts/ingest_warnings.py --dry-run -f validator-output.txt | |
| - name: Run wiring guard (no orphan scripts; plugin-root refs resolve; one path per state file; automation claims kept) | |
| run: python3 plugins/mycelium/scripts/check_wiring.py --root . | |
| - name: Run negative-control guard (every guard's test proves the guard BITES) | |
| # Check 37 asks "does every check have a test?" — never whether that test | |
| # would FAIL if the check broke. verify_citations.py shipped 14 green tests | |
| # while matching 0% of real citations for ~2.5 months. This gates the | |
| # difference between "we tested the sad path" and "we only tested green". | |
| run: python3 plugins/mycelium/scripts/check_negative_control.py --root . | |
| - name: Run test-authenticity guard (every test reaches real production code) | |
| # The third question in the series. check_wiring asks "does this mechanism | |
| # have a caller?"; check_negative_control asks "can this guard fail?"; | |
| # this asks "when the test runs, does production code run?" A test can | |
| # import nothing, assert a tautology, or mock away the thing it names and | |
| # still be green AND covered — coverage measures which lines ran, never | |
| # whether the assertion after them meant anything. | |
| run: python3 plugins/mycelium/scripts/check_test_authenticity.py --root . | |
| - name: Run wiring-contract guard (project's own integration conventions hold) | |
| # check_wiring.py enforces Mycelium's OWN hard-coded wiring rules. This | |
| # enforces the contract DERIVED from a repo's majority convention, so it | |
| # works in a consumer project whose joins the framework cannot know. | |
| # Ungoverned files are reported, never silently excluded. | |
| run: python3 plugins/mycelium/scripts/check_wiring_contract.py --root . | |
| - name: Run canvas schema + trace validation | |
| run: python3 plugins/mycelium/scripts/validate_canvas.py | |
| - name: No shipped check reports success over empty input | |
| run: python3 plugins/mycelium/scripts/check_empty_input_honesty.py --root . | |
| - name: Run dead-reference doc check (markdown links resolve in either tree) | |
| run: python3 plugins/mycelium/scripts/check_doc_references.py --root . | |
| - name: Run legacy-path doc check (no stale .claude/{engine,orchestration,schemas}/ refs) | |
| run: python3 plugins/mycelium/scripts/check_legacy_paths.py --root . | |
| - name: Evidence landed — was the confidence it bears on ever looked at again? | |
| # Never demands that a number MOVE. Considered-and-unchanged is a pass; unexamined is not. | |
| # A check that pushed confidence upward would be an inflation engine pointed at the one | |
| # value a project must not inflate. | |
| run: python3 plugins/mycelium/scripts/check_confidence_managed.py --root . | |
| - name: Run purpose-stance check (no sub-element silently contradicts the why/how/what) | |
| # Advisory by default: a project without `purpose_properties` never opted in and | |
| # must not break on a field it does not have. --strict belongs at a diamond | |
| # transition, not here. | |
| run: python3 plugins/mycelium/scripts/check_purpose_stance.py | |
| - name: Run theory-fidelity structural guard (theories.md mechanism refs resolve; gates grounded; no name-only theory) | |
| run: python3 plugins/mycelium/scripts/check_theory_fidelity.py --root . | |
| - name: Promise-registry sweep freshness (did anyone look?) | |
| # ADVISORY here on purpose. The registry gained no row for ten weeks while the | |
| # failure class kept occurring, because its sweep is prose in a skill. This makes | |
| # "nobody looked" visible without breaking a build over a date. | |
| run: python3 plugins/mycelium/scripts/check_promise_registry_swept.py --root . | |
| - name: Enforce CI/local gate parity (no gate may run only in CI) | |
| # A gate that runs only in CI can go red only AFTER a push. Measured | |
| # 2026-08-09 the workflow ran 11 gates and the shipped pre-push hook ran | |
| # 4 — the 2026-06-18 "local validation != CI gates" correction recurring, | |
| # because the fix was a second hand-maintained list rather than one list. | |
| # `local-gate-set.txt` is now the single source and this asserts CI never | |
| # outruns it. Add a step here without adding it there and this fails. | |
| run: python3 plugins/mycelium/scripts/check_gate_parity.py --root . | |
| - name: Fail-open scan (anti-pattern #9 signature) - REPORT ONLY | |
| # Flags silent-default read chains: an `except` whose handler returns a | |
| # benign value and says nothing, so the caller cannot tell "checked, fine" | |
| # from "could not look". Reports rather than fails, because this tree has | |
| # 42 such sites (31 silent) and NO baseline was seeded — a tolerated-debt | |
| # baseline is exactly what ruff.toml forbids. Each needs a written verdict | |
| # in harness/fail-open-reviewed.yml; add --strict once they exist. | |
| # --strict as of 2026-08-26, when every one of the 43 sites got a written | |
| # verdict. It fails ONLY on an UNREVIEWED handler, so the cost of a new | |
| # default-on-exception site is one judgement in fail-open-reviewed.yml — | |
| # which is the point. No baseline was ever seeded. | |
| run: python3 plugins/mycelium/scripts/check_fail_open.py --strict | |
| - name: Field-wiring scan (does a promise-shaped field have a consumer?) | |
| # A field can be blessed by a schema, filled by a skill, and read by NOTHING — | |
| # populated to a human, inert to the machine. Measured 2026-08-31: of 38 | |
| # promise-shaped fields, SIX had no consumer, and one of them was added HOURS | |
| # EARLIER in the release that fixed this very class. Care did not survive an | |
| # afternoon, so it is a gate. | |
| # The rule is the founder's and is about PURPOSE, not mechanism: if any writer | |
| # intends the field to be read as part of the pipeline — INCLUDING being rendered | |
| # into a humane form such as a mermaid chart — it is wired. A renderer is a | |
| # consumer. `human` is a legal answer once DECLARED. Only the undeclared case fails. | |
| # --strict from the start: the five existing unwired fields are baselined in | |
| # harness/field-consumers.yml, so this fails only on a NEW one. | |
| run: python3 plugins/mycelium/scripts/check_field_wiring.py --strict | |
| - name: Target progress (is anything comparing an actual against the target?) | |
| # `target_value` sat beside `current_value` and NOTHING read either — the two halves | |
| # of a measurement, adjacent in the same object, never compared. Founder-ruled | |
| # 2026-08-31 to wire it. REPORT-ONLY (no --strict): the canvas legitimately holds | |
| # aspirational targets, and failing a build over an unmeasured one teaches people to | |
| # delete the target rather than measure it, which is the inversion the field exists | |
| # to prevent. | |
| run: python3 plugins/mycelium/scripts/check_target_progress.py --canvas-dir .claude/canvas | |
| - name: touch_log entries are in ascending date order | |
| # Every reader treats touch_log[-1] as "what happened last". An out-of-order | |
| # log looks wrong to nobody and parses cleanly; it just returns the wrong | |
| # answer to the question the file exists to answer. Surfaced in dogfood | |
| # 2026-08-18, where it reported two already-sent replies as unsent. | |
| # Zero files scanned exits 1 — an empty scan is UNKNOWN, never a pass. | |
| run: python3 plugins/mycelium/scripts/check_touch_log_order.py --root . | |
| - name: Run surface-registry guard (every declared reader actually reads its surface) | |
| # right-content-wrong-surface, criterion met 2026-07-26. An artifact written | |
| # to a surface nothing reads is invisible by construction. This verifies the | |
| # WIRING CLAIM -- that each declared reader genuinely references its declared | |
| # surface -- not that any given artifact landed. Open rows (a class with no | |
| # authoritative surface yet) are reported, not failed: they are a recorded gap | |
| # with a decision attached, not a regression. | |
| run: python3 plugins/mycelium/scripts/check_surface_registry.py | |
| - name: Run source-class fidelity guard (the label must match the source beside it) | |
| # Dogfood 2026-08-08: five founder-sourced canvas entries were classed | |
| # `external_human` -- the field meaning a human OUTSIDE the project said it -- | |
| # and in three of them the record's own prose already said otherwise. It is | |
| # machine-consumed: check_source_independence.py counts distinct source_class | |
| # values as method diversity, so a mislabel manufactures corroboration that | |
| # does not exist. Every mislabel found pointed the same way: toward more | |
| # external evidence than exists. Also fails on misaligned | |
| # evidence_sources/source_classes arrays, since unequal lengths make every | |
| # index pairing meaningless. | |
| run: python3 plugins/mycelium/scripts/check_source_class_fidelity.py --root . | |
| - name: Run instrument-contract guard (the header is an agent interface, not paperwork) | |
| # THE HEADER IS READ BY MACHINES, WHICH IS WHY THIS IS A GATE AND NOT A REPORT. | |
| # An instrument's contract block is how a later agent FINDS a frozen prediction, | |
| # decides whether it is still live, and knows whether the block it is reading | |
| # was written before the data. A human skims past a malformed header. An agent | |
| # greps for `status: live`, gets nothing, and concludes there are no live | |
| # instruments -- which is the false-absence failure this project logs most. | |
| # Same reasoning as the source-class fidelity guard above: machine-consumed | |
| # fields get gated, because the cost of a wrong one is silent. | |
| run: python3 plugins/mycelium/scripts/check_instrument_contract.py --root . | |
| - name: Run Python unit tests with coverage (verbose output for CI logs) | |
| run: | | |
| pytest tests/python/ -v \ | |
| --cov=plugins/mycelium/scripts \ | |
| --cov=plugins/mycelium/integrations \ | |
| --cov-report=term \ | |
| --cov-report=xml \ | |
| --cov-report=json \ | |
| --cov-fail-under=85 | |
| - name: Enforce per-file coverage floor (every shipped script must be tested) | |
| # --cov-fail-under gates the TOTAL; this gates EACH file so a new untested | |
| # script can't hide under the average (the v0.49.6 check_legacy_paths gap). | |
| run: python3 plugins/mycelium/scripts/check_coverage_floor.py --root . --floor 70 | |
| # Note: standalone ruff and shellcheck checks are not run here — Check 17 | |
| # in validate-template.sh runs both with the correct baseline-aware | |
| # thresholds (pre-existing warnings tolerated; regression above baseline | |
| # fails). Single source of truth. | |
| # | |
| # Path layout (post-2026-05-09 legacy cleanup): | |
| # tests/validate-template.sh — validator (was .claude/tests/) | |
| # tests/python/ — Python unit tests | |
| # plugins/mycelium/scripts/ — framework scripts (validate_canvas.py, | |
| # parse_manifest.py, ingest_warnings.py, | |
| # framework_guard.py, scope_check.py, | |
| # _manifest_lib.py, upgrade.sh) | |
| # .claude/ — project state only (canvas, diamonds, | |
| # memory, evals, harness/decision-log.md, | |
| # jit-tooling/active-metrics.yml, | |
| # manifest.yml, settings.json) |