From 4f178ef4126659272bb1f1c3977fb89a93fb6819 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 22 Sep 2026 21:49:49 +0100 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20make=20pons=20a=20working=20tool=20?= =?UTF-8?q?=E2=80=94=20CLI,=20honest=20messages,=20real=20e2e,=20live=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 0 of the v0.1.0 gate. The engine, 8 T0 rules, 5 grammars and the falsifier gate already worked on main; what did not work was everything around them. This fixes that, and makes the tests capable of noticing. CLI - `pons --version` existed nowhere: clap had no `version` attribute, so `--version` was an unexpected argument and exited 2. It now reports `pons 0.1.0` and exits 0. Rule messages - Five T0 rules passed their own rule id as the human message, so output read `[WARN] self-assignment: self-assignment`. Each now carries prose: empty-effect-loop, self-assignment, string-concat-in-loop, unreachable-after-jump, while-true-no-break. tests/e2e.sh — rewritten, 16 pass/3 fail -> 35 pass/0 fail - The old suite never executed the binary. It asserted documents existed, announced a "planning phase" that ended in PR #19, referenced GOVERNANCE.md/CODE_OF_CONDUCT.md (this repo ships .adoc) and asserted a 0-AI-MANIFEST.a2ml. It was a vacuous gate. - The suite now has two halves: repository shape, and binary behaviour. 16 assertions execute the binary — the known-answer positive corpus, the falsification invariant across all 8 negative corpora, a clean tree, and the full ADR-0005 exit-code matrix (0 / 1 under --fail-on / 2). - It FAILS rather than skips when no binary is found: a skip is not a pass. - Exit codes compare numerically; `grep -q "1"` also matches rc=12. - Non-vacuity is now itself gated. `just e2e-mutant` runs the suite against PONS_BIN=/bin/true and fails if it passes. rust-ci runs the same check, so the suite cannot quietly become vacuous again. - tests/e2e/template_instantiation_test.sh is removed with its caller. Workflows — both reds on main - codeql.yml was pinned to 29b1f65c, a sha that resolves to nothing (422), which is why every CodeQL run died; and its matrix listed `actions` only, so CodeQL had never once opened crates/. Repointed to 1c5b6756 (the commit v4.38.1 dereferences to, verified via the commits API) and Rust added to the matrix. - main-estate-audit.yml called a cicd-suite branch that 404s, so it died at startup with jobs=0 rather than failing a job. Repointed at the pinned reusable 3b4afafa. See the PR for why repointing beats vendoring here. Docs — they were lying - README, ARCHITECTURE and EXPLAINME claimed planning was complete and implementation not started, months after M0-M2 merged. ARCHITECTURE also listed 4 ADRs when 5 exist and labelled tests/ and benches/ as planning. - wiki/ converted from AsciiDoc to metadatastician/berrywiki Markdown: nine pages with the berrywiki metadata comment block, _Sidebar and _Footer deliberately without one. docs/wiki.adoc documents the format and why wiki content is the estate's documented .md exception. Governance - dependabot open-pull-requests-limit and the two ruleset JSONs taken from the 49774e8 spike, keeping every SHA pin; its actions/checkout de-pin is discarded, not merged. Verified: fmt clean, clippy -D warnings clean, 75 tests pass, falsifier green, e2e 35/35, mutant dies, all 26 estate gates pass locally. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WRvDivYwLSeVCJUrfjic3f --- .github/dependabot.yml | 1 + .github/rulesets/Immutable-Tags.json | 19 + .github/rulesets/Optimus-Branch.json | 34 ++ .github/workflows/codeql.yml | 27 +- .github/workflows/main-estate-audit.yml | 18 +- .github/workflows/rust-ci.yml | 11 + ARCHITECTURE.adoc | 73 ++-- EXPLAINME.adoc | 49 ++- Justfile | 17 +- MAINTAINERS | 48 +-- README.adoc | 15 +- crates/pons-cli/src/main.rs | 1 + crates/pons-rules/src/t0/empty_effect_loop.rs | 2 +- crates/pons-rules/src/t0/self_assignment.rs | 2 +- .../src/t0/string_concat_in_loop.rs | 2 +- .../src/t0/unreachable_after_jump.rs | 2 +- .../pons-rules/src/t0/while_true_no_break.rs | 2 +- docs/wiki.adoc | 63 ++++ tests/e2e.sh | 329 +++++++++--------- tests/e2e/template_instantiation_test.sh | 258 -------------- wiki/Evidence-Tiers.asciidoc | 90 ----- wiki/Evidence-Tiers.md | 79 +++++ ...-Developers.asciidoc => For-Developers.md} | 121 +++---- ...s.asciidoc => For-Platform-Maintainers.md} | 90 ++--- wiki/{For-Users.asciidoc => For-Users.md} | 128 ++++--- wiki/Home.asciidoc | 68 ---- wiki/Home.md | 79 +++++ wiki/README.adoc | 19 - wiki/Roadmap.asciidoc | 71 ---- wiki/Roadmap.md | 67 ++++ wiki/Rule-Catalogue.asciidoc | 117 ------- wiki/Rule-Catalogue.md | 63 ++++ wiki/_Footer.asciidoc | 4 - wiki/_Footer.md | 5 + wiki/_Sidebar.asciidoc | 20 -- wiki/_Sidebar.md | 22 ++ 36 files changed, 960 insertions(+), 1056 deletions(-) create mode 100644 .github/rulesets/Immutable-Tags.json create mode 100644 .github/rulesets/Optimus-Branch.json create mode 100644 docs/wiki.adoc delete mode 100755 tests/e2e/template_instantiation_test.sh delete mode 100644 wiki/Evidence-Tiers.asciidoc create mode 100644 wiki/Evidence-Tiers.md rename wiki/{For-Developers.asciidoc => For-Developers.md} (51%) rename wiki/{For-Platform-Maintainers.asciidoc => For-Platform-Maintainers.md} (61%) rename wiki/{For-Users.asciidoc => For-Users.md} (50%) delete mode 100644 wiki/Home.asciidoc create mode 100644 wiki/Home.md delete mode 100644 wiki/README.adoc delete mode 100644 wiki/Roadmap.asciidoc create mode 100644 wiki/Roadmap.md delete mode 100644 wiki/Rule-Catalogue.asciidoc create mode 100644 wiki/Rule-Catalogue.md delete mode 100644 wiki/_Footer.asciidoc create mode 100644 wiki/_Footer.md delete mode 100644 wiki/_Sidebar.asciidoc create mode 100644 wiki/_Sidebar.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 380481e..f9067b5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,6 +13,7 @@ updates: actions: patterns: - "*" + open-pull-requests-limit: 2 # Rust/Cargo - package-ecosystem: "cargo" diff --git a/.github/rulesets/Immutable-Tags.json b/.github/rulesets/Immutable-Tags.json new file mode 100644 index 0000000..53739af --- /dev/null +++ b/.github/rulesets/Immutable-Tags.json @@ -0,0 +1,19 @@ +{ + "name": "Immutable-Tags", + "target": "tag", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["~ALL"], + "exclude": [] + } + }, + "bypass_actors": [], + "rules": [ + {"type": "creation"}, + {"type": "deletion"}, + {"type": "non_fast_forward"}, + {"type": "update"}, + {"type": "required_signatures"} + ] +} diff --git a/.github/rulesets/Optimus-Branch.json b/.github/rulesets/Optimus-Branch.json new file mode 100644 index 0000000..8dcde39 --- /dev/null +++ b/.github/rulesets/Optimus-Branch.json @@ -0,0 +1,34 @@ +{ + "name": "Optimus-Branch", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["~DEFAULT_BRANCH"], + "exclude": [] + } + }, + "bypass_actors": [], + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + }, + { + "type": "required_signatures" + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 2, + "dismiss_stale_reviews_on_push": true, + "require_code_owner_review": true, + "require_last_push_approval": true, + "required_review_thread_resolution": true, + "allowed_merge_methods": ["squash"] + } + } + ] +} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e1b3498..8619285 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,7 +16,7 @@ permissions: jobs: analyze: - name: CodeQL Analysis + name: CodeQL Analysis (${{ matrix.language }}) runs-on: ubuntu-latest permissions: actions: read @@ -26,20 +26,33 @@ jobs: fail-fast: false matrix: include: + # `actions` scans the workflows themselves. - language: actions build-mode: none + # `rust` is the point of this repository. It was never scanned before: + # the matrix listed `actions` only, so CodeQL had never once opened + # crates/. Rust is public preview from CodeQL 2.22.1; the bundle + # carried by codeql-action v4.38.1 is newer, so no experimental + # feature flag is needed. build-mode is deliberately omitted — the + # upstream Rust check (pr-checks/checks/rust.yml) passes only + # `languages: rust`, and an empty input reads as unset. + - language: rust steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + # Pinned to the COMMIT this tag dereferences to, not the tag object's own + # sha. The previous pin (29b1f65c…) resolved to nothing at all — GitHub + # returns 422 for it — which is why every CodeQL run died. - name: Initialize CodeQL - uses: github/codeql-action/init@29b1f65c1f735799893313399435a59f54045865 # v3 + uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - - name: Autobuild - uses: github/codeql-action/autobuild@29b1f65c1f735799893313399435a59f54045865 # v3 - + # No Autobuild step: both matrix entries are buildless. Autobuild under + # build-mode `none` is a no-op, and upstream's Rust check omits it. - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@29b1f65c1f735799893313399435a59f54045865 # v3 + uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/main-estate-audit.yml b/.github/workflows/main-estate-audit.yml index 9ca1f93..9a16a26 100644 --- a/.github/workflows/main-estate-audit.yml +++ b/.github/workflows/main-estate-audit.yml @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: MPL-2.0 name: Central Estate CI/CD Audit on: @@ -7,6 +8,21 @@ on: branches: [ "main" ] workflow_call: +permissions: + contents: read + jobs: + # Was pinned to @feat/cicd-workflow-call, a branch that no longer exists on + # cicd-suite. An unresolvable ref is not a failing job — the run dies at + # startup with jobs=0, so this audit has been reporting nothing at all. + # + # Repointed at a full commit SHA on cicd-suite main. That reusable workflow + # invokes all 26 estate gates from hyperpolymath/cicd-suite/actions/*, which + # is the canonical copy: vendoring those composites into this repo would fork + # a snapshot that is already stale (cicd-suite has since replaced + # zig-hexadeca-check with zig-unified-api-adapter-check). + # + # The SHA below is cicd-suite main as of 2026-09-21 and is the head of a + # green run of that workflow. Re-pin when the gate set changes. call-estate-audit: - uses: hyperpolymath/cicd-suite/.github/workflows/main-estate-audit.yml@feat/cicd-workflow-call + uses: hyperpolymath/cicd-suite/.github/workflows/main-estate-audit.yml@3b4afafa969103279ee580572626a175ec9dc0d0 diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index c34f7a3..bd0cdf7 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -26,3 +26,14 @@ jobs: RUSTFLAGS: "-D warnings" - name: test run: cargo test --workspace + - name: build + run: cargo build --release + - name: e2e + run: bash tests/e2e.sh + - name: e2e non-vacuity (stub binary must fail the suite) + run: | + if PONS_BIN=/bin/true bash tests/e2e.sh >/dev/null 2>&1; then + echo "::error::e2e suite passes against a stub binary — it is vacuous" + exit 1 + fi + echo "e2e suite correctly rejects a stub binary" diff --git a/ARCHITECTURE.adoc b/ARCHITECTURE.adoc index 74bb193..a07daca 100644 --- a/ARCHITECTURE.adoc +++ b/ARCHITECTURE.adoc @@ -35,22 +35,23 @@ semantics .... . ├── docs/ # Architecture Decision Records (ADRs) and planning -│ └── adr/ # ADR-0001 through ADR-0004 +│ └── adr/ # ADR-0001 through ADR-0005 │ ├── 0001-substrate.adoc │ ├── 0002-t1-language-and-cfg.adoc │ ├── 0003-protocol-spec-and-typestate.adoc -│ └── 0004-companion-to-panic-attack.adoc +│ ├── 0004-companion-to-panic-attack.adoc +│ └── 0005-cli-exit-code-contract.adoc ├── docs/PLAN.adoc # Milestone-by-milestone implementation plan ├── docs/pons-kickoff.adoc # Mission, species, decidability wall, evidence tiers ├── .machine_readable/ # RSR compliance infrastructure │ ├── contractiles/ # Machine-readable contracts │ ├── descriptiles/ # Machine-readable descriptions │ └── scripts/ # Verification and lifecycle scripts -├── tests/ # Test suites (planning phase) +├── tests/ # Shell test suites (e2e runs in CI) │ ├── e2e.sh # End-to-end validation of artefacts │ ├── aspect_tests.sh # Cross-cutting architectural invariants │ └── workflows/ # CI workflow validation -├── benches/ # Benchmarks (planning phase) +├── benches/ # Benchmarks (not yet wired into CI) │ └── pons_bench.sh # Performance benchmarks ├── LICENSE # MPL-2.0 for source code ├── LICENSE.adoc # Licence documentation @@ -65,38 +66,50 @@ semantics === Component Architecture -==== Planned Implementation (Post-Planning Phase) - -Once implementation begins (post-v0.1.0 planning), the architecture will -include: +Components marked *built* exist on `main` and are exercised by the test suite. +Components marked *planned* carry the milestone that delivers them; see +`docs/PLAN.adoc`. [arabic] -. *Parser Layer* -* tree-sitter grammars for Python, JavaScript/TypeScript, Rust -* Grammar version pinning via workspace dependencies -* Exact grammar crate versions pinned (PLAN Appendix F) -. *CFG + Dataflow Engine* -* Per-language CFG extraction (Python first, per ADR-0002) -* Reaching definitions for `+read-before-init+` -* Liveness analysis for `+dead-store+` -* Typestate semantics (ADR-0003) -. *Rule Engine* -* Evidence classes: PROTOCOL, DATAFLOW, HEURISTIC, SPECULATIVE -* Negative corpus for each rule (falsification testing) -* Automatic demotion/removal of rules firing on negative corpus +. *Parser Layer* — *built* +* tree-sitter grammars for Python, JavaScript, TypeScript, TSX and Rust +* Grammar crate versions pinned exactly (PLAN Appendix F) +* `.jsx` maps to the JavaScript grammar; `.tsx` to the TSX grammar +. *CFG + Dataflow Engine* — *planned (M4, ADR-0002)* +* Per-language CFG extraction, Python first +* Forward may-be-unbound analysis for `+read-before-init+` +* Backward live-variables analysis for `+dead-store+` +* The OPAQUE hatch: no T1 finding is reported for a function that can + rewrite its own scope (`exec`, `eval`, `locals`, wildcard import) +. *Typestate Engine* — *planned (M5, ADR-0003)* +* Forward per-path may-analysis over the CFG, one instance key at a time +. *Rule Engine* — *built (T0 catalogue; higher tiers planned)* +* Evidence class is fixed by tier and cannot be overridden per finding +* Negative corpus for every rule (falsification testing) +* A rule that fires anywhere in its own negative corpus is falsified, and is + demoted or removed . *Reporter* -* Evidence class visualization -* SPECULATIVE findings visually demoted -* Multiple output formats (JSON, SARIF, human-readable) +* Human-readable output with SPECULATIVE visually demoted — *built* +* JSON and SARIF output — *planned (M3)* === Evidence Classes -The core architectural invariant is the evidence class taxonomy: - -* *PROTOCOL*: Findings with mathematical certainty -* *DATAFLOW*: Findings from dataflow analysis -* *HEURISTIC*: Pattern-based findings -* *SPECULATIVE*: Weakest evidence, visually demoted +The core architectural invariant is the evidence class taxonomy. Evidence class +is a function of the analysis tier that produced the finding, and is never set +per finding: + +* *PROTOCOL* (T2): the typestate analysis found a path on which the protocol is + violated. This is a *may* result over an approximation of the real control + flow — it means "a violating path exists in the model", not "this will + happen". It is the strongest class pons emits, and it is still not a proof. +* *DATAFLOW* (T1): supported by an intraprocedural dataflow analysis over a + real CFG, within that analysis's disclosed limits. +* *HEURISTIC* (T0): a syntactic pattern match. No flow analysis stands behind it. +* *SPECULATIVE* (T3): the underlying question is undecidable or the signal is + weak. Always visually demoted, never counted toward a `--fail-on` threshold. + +*Never dress a heuristic up as a proof.* No class above claims certainty, +because no analysis in pons delivers certainty. Every finding carries its evidence class, and the reporter enforces visual distinction between classes, especially demoting SPECULATIVE diff --git a/EXPLAINME.adoc b/EXPLAINME.adoc index 93da63e..90c9b88 100644 --- a/EXPLAINME.adoc +++ b/EXPLAINME.adoc @@ -7,9 +7,15 @@ pons (after the _pons asinorum_ / Euclid I.5, the "bridge of asses") is a lightweight, multi-language static scanner that flags three categories of critical code issues: -* **Dead work** — Code that does nothing, is unreachable, or produces no effect -* **Self-contradiction** — Inconsistent logic or state that cannot be reconciled -* **Missing escape hatches** — Error paths that don't properly handle failures +* **Wasted work** — the program computes something it then discards. Dead + stores, a loop whose result is never read, accidental super-linear cost. + "Labour for nothing." +* **Contradiction** — the program asserts two incompatible things at once. + Divide by zero asserts `a/0` is defined. Suppress-output-then-prompt asserts + both "stay silent" and "speak". An empty-effect loop asserts "this iteration + matters" while producing nothing. +* **Missing escape hatches** — a third, smaller shape: a long operation with no + way to interrupt it. Not waste, not contradiction, but a real defect. == Design Philosophy @@ -24,20 +30,39 @@ pons is the depth companion to link:https://github.com/hyperpolymath/panic-attac Every finding carries an explicit evidence class: -* `PROTOCOL` — Mathematically certain, based on formal specifications -* `DATAFLOW` — High confidence, based on concrete dataflow analysis -* `HEURISTIC` — Moderate confidence, based on pattern matching -* `SPECULATIVE` — Low confidence, visually demoted in all output formats +* `PROTOCOL` (T2) — a typestate analysis found a path on which the protocol is + violated. A *may* result over an approximation of the real control flow: it + says "a violating path exists in the model", not "this will happen". The + strongest class pons emits, and still not a proof. +* `DATAFLOW` (T1) — supported by an intraprocedural dataflow analysis over a + real control-flow graph, within that analysis's disclosed limits. +* `HEURISTIC` (T0) — a syntactic pattern match. No flow analysis stands behind it. +* `SPECULATIVE` (T3) — the underlying question is undecidable, or the signal is + weak. Visually demoted in every output format and never counted toward a + `--fail-on` threshold. + +Evidence class is fixed by the tier that produced the finding. A rule cannot +choose its own class, and no class claims certainty — because no analysis in +pons delivers certainty. **Key principle:** Never dress a heuristic up as a proof. == Current Status -*Planning complete; implementation not started.* This repository contains the ratified v0.1.0 plan. +*Implementation in progress toward v0.1.0.* Milestones M0-M2 are merged: the +Rust workspace, the scanning engine, five tree-sitter grammars, the eight-rule +T0 catalogue and the falsifier gate all build, test and run. `pons scan ` +works today. + +Milestones M3-M8 remain: JSON and SARIF output, the Python CFG and dataflow +engine (T1), typestate and protocols (T2), speculative rules (T3), suppression, +and the v0.1.0 acceptance sweep. Until a tier ships, no finding carries its +evidence class: every rule on `main` today is T0/`HEURISTIC`. == Repository Structure -include::docs/structure.adoc[] +See link:ARCHITECTURE.adoc[ARCHITECTURE.adoc] for the directory layout and the +component breakdown. == Quick Start @@ -45,11 +70,11 @@ See link:.github/CONTRIBUTING.md[CONTRIBUTING.md] for development setup. == License -Code: MPL-2.0-or-later (see LICENSE) +Code: MPL-2.0 (see LICENSE) Docs: CC-BY-SA-4.0 == Contact -* Issues: https://github.com/hyperpolymath/pons/issues -* Discussions: https://github.com/hyperpolymath/pons/discussions +* Issues: https://github.com/hyperpolymath/pons-asinorum/issues +* Discussions: https://github.com/hyperpolymath/pons-asinorum/discussions * Email: j.d.a.jewell@open.ac.uk diff --git a/Justfile b/Justfile index 34a95f8..13ffabb 100644 --- a/Justfile +++ b/Justfile @@ -15,4 +15,19 @@ lint: falsify: cargo test -p pons-rules --test falsifier -check: fmt lint test +# End-to-end: builds the binary first, because tests/e2e.sh executes it and +# deliberately FAILS rather than skips when it is absent. +e2e: + cargo build --release + bash tests/e2e.sh + +# Proves the e2e suite is not vacuous: against a stub binary it must go red. +e2e-mutant: + #!/usr/bin/env bash + if PONS_BIN=/bin/true bash tests/e2e.sh >/dev/null 2>&1; then + echo "e2e suite passed against a STUB binary — it is vacuous"; exit 1 + else + echo "e2e suite correctly rejects a stub binary" + fi + +check: fmt lint test e2e diff --git a/MAINTAINERS b/MAINTAINERS index 37f6411..c43143f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,43 +1,45 @@ # Maintainers -This file lists the current maintainers of this project. +Maintainers of `hyperpolymath/pons-asinorum`. ## Active Maintainers | Name | GitHub | Role | Since | |------|--------|------|-------| -| Metadatastician | @metadatastician | Primary | Project Start | +| Jonathan D.A. Jewell | @hyperpolymath | Primary | Project start | -## Emeritus Maintainers +This repository is solo-maintained, which is why `.github/CODEOWNERS` carries +no owner lines (CODEOWNERS policy, Rule 1). -None at this time. +## Emeritus Maintainers -## Becoming a Maintainer +None. -To become a maintainer: +## Decision record -1. Demonstrate consistent, high-quality contributions -2. Show understanding of the project's goals and architecture -3. Be active in code reviews and community discussions -4. Be nominated by an existing maintainer -5. Be approved by consensus of existing maintainers +Design decisions are not made in this file. They are recorded as ADRs under +`docs/adr/`, and owner decisions in `docs/OWNER-DECISIONS.adoc`. A maintainer +may not silently reverse a ratified ADR; reversing one means superseding it +with a new ADR that says what changed and why. -## Maintainer Responsibilities +## Maintainer responsibilities - Reviewing and merging pull requests -- Managing releases +- Managing releases and tags - Triaging issues -- Enforcing code standards -- Mentoring new contributors -- Participating in decision-making +- Upholding the honesty invariant: evidence class is fixed by analysis tier and + is never overridden per finding, per `docs/PLAN.adoc` Appendix B +- Upholding the falsification gate: a rule that fires anywhere in its own + negative corpus is falsified, and is demoted or removed rather than excused -## Maintainer Expectations +## Becoming a maintainer -- Respond to issues and PRs in a timely manner -- Follow the code of conduct -- Be transparent in decision-making -- Communicate clearly and respectfully +1. Demonstrate consistent, high-quality contributions +2. Show understanding of the project's goals and architecture +3. Be active in code reviews and discussion +4. Be nominated by an existing maintainer ---- +## Contact -*Last updated: 2026-07-18* +Issues: https://github.com/hyperpolymath/pons-asinorum/issues +Email: j.d.a.jewell@open.ac.uk diff --git a/README.adoc b/README.adoc index 7fa9cb3..0c3ca0e 100644 --- a/README.adoc +++ b/README.adoc @@ -27,9 +27,18 @@ link:docs/adr/0004-companion-to-panic-attack.adoc[ADR-0004]. == Status -*Planning complete; implementation not started.* This repository currently -contains the ratified v0.1.0 plan, written so that implementation agents can -execute it without re-opening any design question. +*Implementation in progress toward v0.1.0.* Milestones M0-M2 are merged: the +Rust workspace, the scanning engine, the tree-sitter substrate for five +languages, the eight-rule T0 catalogue and the falsifier gate all build, test +and run. `pons scan ` works today and reports real findings. + +Milestones M3-M8 remain: machine-readable output (JSON + SARIF), the Python +CFG and dataflow engine (T1), typestate and protocols (T2), speculative rules +(T3), suppression, and the v0.1.0 acceptance sweep. + +The ratified plan is still the authority for everything not yet built, and is +written so that implementation agents can execute it without re-opening any +design question. Read in this order: diff --git a/crates/pons-cli/src/main.rs b/crates/pons-cli/src/main.rs index afe5ffd..f3721d5 100644 --- a/crates/pons-cli/src/main.rs +++ b/crates/pons-cli/src/main.rs @@ -12,6 +12,7 @@ use pons_rules::registry::RuleRegistry; #[derive(Parser)] #[command( name = "pons", + version, about = "pons asinorum — a falsifier-first static analyzer" )] struct Cli { diff --git a/crates/pons-rules/src/t0/empty_effect_loop.rs b/crates/pons-rules/src/t0/empty_effect_loop.rs index c82d731..a037094 100644 --- a/crates/pons-rules/src/t0/empty_effect_loop.rs +++ b/crates/pons-rules/src/t0/empty_effect_loop.rs @@ -115,7 +115,7 @@ impl Rule for EmptyEffectLoop { Tier::T0, Severity::Warn, Location::from_node(ctx.path.display().to_string(), &stmt), - "empty-effect-loop", + "loop body has no observable effect", "loop body is empty or a no-op — entered and exited for nothing".to_string(), Some("a deliberate spin-wait on a volatile/side-effecting condition".to_string()), )); diff --git a/crates/pons-rules/src/t0/self_assignment.rs b/crates/pons-rules/src/t0/self_assignment.rs index 297bc6f..79e3236 100644 --- a/crates/pons-rules/src/t0/self_assignment.rs +++ b/crates/pons-rules/src/t0/self_assignment.rs @@ -103,7 +103,7 @@ impl Rule for SelfAssignment { Tier::T0, Severity::Warn, Location::from_node(ctx.path.display().to_string(), &stmt), - "self-assignment", + "variable is assigned to itself", format!("`{left_name}` is assigned to itself — this has no effect"), Some( "a property/attribute setter with side effects, or a volatile read \ diff --git a/crates/pons-rules/src/t0/string_concat_in_loop.rs b/crates/pons-rules/src/t0/string_concat_in_loop.rs index adfc460..d53a2b7 100644 --- a/crates/pons-rules/src/t0/string_concat_in_loop.rs +++ b/crates/pons-rules/src/t0/string_concat_in_loop.rs @@ -338,7 +338,7 @@ impl Rule for StringConcatInLoop { Tier::T0, Severity::Warn, Location::from_node(ctx.path.display().to_string(), &assign), - "string-concat-in-loop", + "string built by repeated concatenation in a loop", "string accumulation inside a loop rebuilds the whole string each iteration \ (quadratic)" .to_string(), diff --git a/crates/pons-rules/src/t0/unreachable_after_jump.rs b/crates/pons-rules/src/t0/unreachable_after_jump.rs index ceddc9f..043e0aa 100644 --- a/crates/pons-rules/src/t0/unreachable_after_jump.rs +++ b/crates/pons-rules/src/t0/unreachable_after_jump.rs @@ -109,7 +109,7 @@ impl Rule for UnreachableAfterJump { Tier::T0, Severity::Warn, Location::from_node(ctx.path.display().to_string(), &next), - "unreachable-after-jump", + "statement is unreachable after an unconditional jump", format!("unreachable code — this can never run after `{keyword}`"), Some("a label or fallthrough construct the walker doesn't see".to_string()), )); diff --git a/crates/pons-rules/src/t0/while_true_no_break.rs b/crates/pons-rules/src/t0/while_true_no_break.rs index 5d4854b..a90dd1a 100644 --- a/crates/pons-rules/src/t0/while_true_no_break.rs +++ b/crates/pons-rules/src/t0/while_true_no_break.rs @@ -158,7 +158,7 @@ impl Rule for WhileTrueNoBreak { Tier::T0, Severity::Warn, Location::from_node(ctx.path.display().to_string(), &stmt), - "while-true-no-break", + "no way to interrupt this loop", "infinite loop with no reachable break/return/throw".to_string(), Some( "an intentional daemon/event loop that exits via an external \ diff --git a/docs/wiki.adoc b/docs/wiki.adoc new file mode 100644 index 0000000..1cbe517 --- /dev/null +++ b/docs/wiki.adoc @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 += The pons wiki: source, format, and publishing + +The `wiki/` directory at the repository root holds the source pages for the +project wiki at https://github.com/hyperpolymath/pons-asinorum/wiki. They live +in the repository as well as on the wiki tab so the content is versioned under +CC-BY-SA-4.0 and reviewable by pull request. + +== Format: berrywiki + +Pages are Markdown in the https://github.com/metadatastician/berrywiki[berrywiki] +format. Two properties matter: + +* *Every page is a plain, GitHub-renderable Markdown file.* Nothing about the + format requires a tool to read the page — open it on the wiki tab, in the + repository browser, or in an editor, and it reads correctly. +* *Hierarchy is carried in metadata, not in the filename.* Filenames are flat, + with `--` separating the levels for human legibility + (`Weeks--Week-01.md`); the hierarchy of record is the `parent` id chain. + +Each content page opens with a metadata block in an HTML comment: + +[source,markdown] +---- + +---- + +The block is an HTML comment rather than YAML frontmatter deliberately: GitHub +Wiki renders frontmatter *visibly*, at the top of the page, which would put +machine metadata in the reader's face on every page. An HTML comment renders as +nothing. + +The navigation pages `_Sidebar.md` and `_Footer.md` carry *no* metadata block. +They are chrome, not notebook nodes. + +Cross-page links use the wiki convention `+[[Page Name]]+`, which resolves on +the wiki tab. + +== Pages + +`Home` · `Evidence-Tiers` (read first) · `For-Users` · `For-Developers` · +`For-Platform-Maintainers` · `Rule-Catalogue` · `Roadmap`, plus `_Sidebar` and +`_Footer` navigation. + +== Publishing + +To publish to the GitHub *wiki tab*, the wiki must be initialised once by +creating any first page through the web UI. After that, `pons.wiki.git` can be +pushed and will replace it. Until then, this directory is the canonical copy. + +== Why not AsciiDoc + +The rest of this repository's prose is AsciiDoc, and that is the estate default. +Wiki content is the documented exception: GitHub Wiki is a Markdown surface, and +berrywiki builds on that. The estate formatting gate encodes the same rule — +`.adoc` everywhere, `.md` for wiki content. diff --git a/tests/e2e.sh b/tests/e2e.sh index 5888c3c..3bb0f40 100755 --- a/tests/e2e.sh +++ b/tests/e2e.sh @@ -4,15 +4,24 @@ # # pons-asinorum — End-to-End Tests # -# Since pons is currently in planning phase (implementation not started), -# these tests validate the documentation and design artefacts. +# Two halves, and the second is the one that matters: +# +# 1. Repository shape — the governance and documentation artefacts exist and +# say what they are supposed to say. +# 2. Binary behaviour — the `pons` binary is actually executed and its +# observable contract asserted: --version, the ADR-0005 exit-code matrix, +# a known-answer positive corpus, and the falsification invariant. +# +# Half 1 alone is a vacuous gate: it can pass on a tree where the tool does not +# build. Half 2 is what stops that. If you add checks, add them to half 2. # # Usage: -# bash tests/e2e.sh -# just e2e +# bash tests/e2e.sh # uses target/release/pons, else target/debug/pons +# PONS_BIN=/path/to/pons bash tests/e2e.sh +# just e2e # builds first, then runs this # -# Merge requirements (STANDING): All 6 test categories must pass before merge: -# P2P, E2E (this file), aspect, execution, lifecycle, benchmarks +# Non-vacuity self-check (run it after changing half 2 — it MUST go red): +# PONS_BIN=/bin/true bash tests/e2e.sh set -euo pipefail @@ -31,22 +40,61 @@ bold() { printf '\033[1m%s\033[0m\n' "$*"; } # ─── Assertion helpers ─────────────────────────────────────────────── +ok() { green " PASS: $1"; PASS=$((PASS + 1)); } +bad() { red " FAIL: $1"; FAIL=$((FAIL + 1)); } + # check