From 0ce29e38d767ba6fc6c5b8edc9a824d172281193 Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Sun, 7 Jun 2026 23:08:06 +0100 Subject: [PATCH 1/6] Apply estate standardization: governance docs, contractiles, CI/CD cleanup --- .machine_readable/ADJUST.contractile | 126 -- .machine_readable/INTENT.contractile | 72 - .machine_readable/MUST.contractile | 91 - .machine_readable/TRUST.contractile | 80 - .../contractiles/Adjustfile.a2ml | 72 + .../contractiles/Intentfile.a2ml | 99 ++ .machine_readable/contractiles/Justfile | 1544 +++++++++++++++++ .machine_readable/contractiles/Mustfile.a2ml | 102 ++ .machine_readable/contractiles/Trustfile.a2ml | 88 + .../contractiles/dust/Dustfile.a2ml | 44 - .../contractiles/intend/Intentfile.a2ml | 37 - .../contractiles/must/Mustfile.a2ml | 69 - .../contractiles/trust/Trustfile.a2ml | 74 - GOVERNANCE.adoc | 162 ++ MAINTAINERS.adoc | 65 + 15 files changed, 2132 insertions(+), 593 deletions(-) delete mode 100644 .machine_readable/ADJUST.contractile delete mode 100644 .machine_readable/INTENT.contractile delete mode 100644 .machine_readable/MUST.contractile delete mode 100644 .machine_readable/TRUST.contractile create mode 100644 .machine_readable/contractiles/Adjustfile.a2ml create mode 100644 .machine_readable/contractiles/Intentfile.a2ml create mode 100644 .machine_readable/contractiles/Justfile create mode 100644 .machine_readable/contractiles/Mustfile.a2ml create mode 100644 .machine_readable/contractiles/Trustfile.a2ml delete mode 100644 .machine_readable/contractiles/dust/Dustfile.a2ml delete mode 100644 .machine_readable/contractiles/intend/Intentfile.a2ml delete mode 100644 .machine_readable/contractiles/must/Mustfile.a2ml delete mode 100644 .machine_readable/contractiles/trust/Trustfile.a2ml create mode 100644 GOVERNANCE.adoc create mode 100644 MAINTAINERS.adoc diff --git a/.machine_readable/ADJUST.contractile b/.machine_readable/ADJUST.contractile deleted file mode 100644 index d9c798f..0000000 --- a/.machine_readable/ADJUST.contractile +++ /dev/null @@ -1,126 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 -; ADJUST.contractile — Accessibility invariants for rsr-template-repo -; "ADJUST" = Accessibility & Digital Justice for Universal Software & Technology -; -; Part of the contractile family: MUST, TRUST, DUST, INTENT, ADJUST -; This file is machine-readable. LLM/SLM agents MUST NOT violate these invariants. - -; ── Definitions ────────────────────────────────────────────────── -; -; ADJUST (noun/verb) -; The accessibility contractile. Defines how software must adapt to serve -; all users regardless of ability, device, or context. Named for the verb -; "adjust" — to make suitable, to adapt, to accommodate — which is the -; core action of accessible design. -; -; Scope: -; ADJUST governs all user-facing interfaces: GUI, TUI, CLI, web, mobile, -; documentation, error messages, and installation flows. It applies to -; both human users and assistive technologies (screen readers, switch -; devices, braille displays, voice control). -; -; Relationship to other contractiles: -; - MUST: ADJUST invariants are a subset of MUST — violating ADJUST -; is a MUST violation. ADJUST exists separately because accessibility -; rules are numerous enough to warrant their own file, and because -; LLMs frequently forget accessibility unless explicitly reminded. -; - TRUST: ADJUST does not affect trust levels. All trust tiers must -; respect ADJUST invariants equally. -; - DUST: Deprecating a feature does not exempt it from ADJUST until -; it is fully removed. Deprecated UI must remain accessible. -; - INTENT: ADJUST supports the anti-purpose "this software is NOT -; only for able-bodied users with modern hardware." -; -; Standard: WCAG 2.2 Level AA (minimum) -; https://www.w3.org/WAI/WCAG22/quickref/?levels=aaa -; -; Why a separate file: -; Experience shows LLMs and developers alike treat accessibility as an -; afterthought. By placing invariants in a contractile that is loaded -; at session start, we make it structurally impossible to forget. -; -; ── End Definitions ────────────────────────────────────────────── - -(adjust-contractile - (version "1.0.0") - (full-name "Accessibility & Digital Justice for Universal Software & Technology") - (standard "WCAG-2.2-AA") - (repo "rsr-template-repo") - - (invariants - ; ── Visual ── - (adjust "colour-contrast-ratio >= 4.5:1 for normal text") - (adjust "colour-contrast-ratio >= 3:1 for large text (18pt+ or 14pt+ bold)") - (adjust "no information conveyed by colour alone") - (adjust "no flashing or strobing content (3 flashes/second max)") - (adjust "text resizable to 200% without loss of content or function") - (adjust "focus indicators visible on all interactive elements") - - ; ── Keyboard ── - (adjust "all interactive elements reachable via keyboard (Tab/Shift+Tab)") - (adjust "no keyboard traps — user can always Tab away") - (adjust "skip navigation link present on pages with repeated blocks") - (adjust "logical focus order follows visual reading order") - - ; ── Screen reader ── - (adjust "all images have meaningful alt text (or alt='' if decorative)") - (adjust "all form inputs have associated labels") - (adjust "ARIA landmarks used for page regions (main, nav, banner, etc.)") - (adjust "dynamic content updates announced via aria-live regions") - (adjust "semantic HTML used (headings, lists, tables) — not div soup") - - ; ── Interactive ── - (adjust "touch targets minimum 44x44px on mobile/touch interfaces") - (adjust "error messages identify the field and describe the error") - (adjust "error messages not conveyed by colour or position alone") - (adjust "form validation provides suggestions for correction") - - ; ── Media ── - (adjust "video has captions (closed or open)") - (adjust "audio-only content has text transcript") - (adjust "no autoplay of media with sound") - - ; ── Motion ── - (adjust "animations respect prefers-reduced-motion media query") - (adjust "no content depends on motion to convey meaning") - - ; ── CLI/TUI ── - (adjust "CLI output must not rely solely on colour (use symbols: [OK] [FAIL])") - (adjust "TUI must support high-contrast mode") - (adjust "all CLI commands support --help with plain-text output") - (adjust "error messages written in plain language, not jargon or codes alone") - - ; ── Documentation ── - (adjust "docs use clear language, short sentences, logical structure") - (adjust "code examples include comments explaining non-obvious steps") - (adjust "diagrams have text descriptions or alt text") - - ; ── Internationalisation (i18n) ── - (adjust "all user-facing strings externalisable for translation") - (adjust "no hardcoded English in error messages — use message keys") - (adjust "date/time/number formats locale-aware") - (adjust "RTL (right-to-left) layout support where applicable") - (adjust "Unicode handled correctly throughout (UTF-8 everywhere)") - ) - - (related-resources - ; LOL — super-parallel corpus crawler for 1500+ languages - ; Use for linguistic data, translation coverage, and i18n validation - (lol "standards/lol — multilingual NLP corpus, see README.adoc") - (polyglot-i18n "polyglot-i18n — i18n framework and WASM translation engine") - ) - - (enforcement - (ci "accessibility linting in quality.yml workflow") - (pr-block "PR blocked if accessibility regression detected") - (tool "axe-core or pa11y for automated checks on web UI") - (tool "CLI output inspected for colour-only signalling") - (manual "manual screen reader test before major releases") - ) - - (notes - "These are MINIMUM requirements. Exceeding them (AAA) is encouraged." - "When in doubt about an accessibility decision, ask — don't guess." - "Accessibility is not optional polish — it is a structural requirement." - ) -) diff --git a/.machine_readable/INTENT.contractile b/.machine_readable/INTENT.contractile deleted file mode 100644 index c899f0e..0000000 --- a/.machine_readable/INTENT.contractile +++ /dev/null @@ -1,72 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 -; INTENT.contractile — Purpose and scope for rsr-template-repo -; Helps LLM/SLM agents understand what this repo IS and IS NOT. -; -; Part of the contractile family: MUST, TRUST, DUST, INTENT, ADJUST - -; ── Definitions ────────────────────────────────────────────────── -; -; INTENT (noun) -; The purpose contractile. Defines what this repository IS, what it is -; NOT (anti-purpose), and which architectural decisions are load-bearing. -; Without INTENT, LLMs drift into scope creep, reverse key decisions, -; or add features that belong in a different repo. -; -; Scope: -; INTENT governs the conceptual boundaries of the project — its reason -; for existing, its domain, and its relationship to the ecosystem. -; It does NOT specify implementation details (that's MUST and code). -; -; Relationship to other contractiles: -; - MUST: INTENT explains WHY certain MUSTs exist. If you don't -; understand a MUST, read INTENT first. -; - TRUST: The "ask-before-touching" section in INTENT maps directly -; to TRUST.trust-deny for the most sensitive areas. -; - ADJUST: INTENT's anti-purpose should include "this software is -; NOT only for users with perfect vision/hearing/mobility." -; - DUST: When INTENT changes (repo pivots), related DUST entries -; should be created for the abandoned direction. -; -; ── End Definitions ────────────────────────────────────────────── - -(intent-contractile - (version "1.0.0") - (repo "rsr-template-repo") - - ; === Purpose (what this repo IS) === - (purpose - "{{ONE_PARAGRAPH_PURPOSE}}" - ) - - ; === Anti-Purpose (what this repo is NOT — prevents scope creep) === - (anti-purpose - "{{ONE_PARAGRAPH_ANTI_PURPOSE}}" - ; Examples: - ; "This is NOT a general-purpose database — it solves one specific problem." - ; "This is NOT a framework — it is a library with a focused API." - ; "This does NOT handle authentication — that is delegated to [other repo]." - ) - - ; === Key Architectural Decisions That Must Not Be Reversed === - (architectural-invariants - ; *REMINDER: List the foundational decisions* - ; ("Idris2 for ABI definitions — dependent types prove interface correctness") - ; ("Zig for FFI — zero-cost C ABI compatibility") - ; ("Elixir for supervision — OTP fault tolerance") - ) - - ; === Sensitive Areas (if in doubt, ask) === - (ask-before-touching - ; *REMINDER: List areas where LLMs should check before modifying* - ; "src/abi/ — formal proofs, changes require re-verification" - ; "ffi/zig/ — C ABI boundary, changes affect all language bindings" - ; ".machine_readable/ — checkpoint files, format is specified" - ) - - ; === Ecosystem Position === - (ecosystem - (belongs-to "{{MONOREPO_OR_STANDALONE}}") - (depends-on ("{{DEP1}}" "{{DEP2}}")) - (depended-on-by ("{{CONSUMER1}}" "{{CONSUMER2}}")) - ) -) diff --git a/.machine_readable/MUST.contractile b/.machine_readable/MUST.contractile deleted file mode 100644 index 48ffff1..0000000 --- a/.machine_readable/MUST.contractile +++ /dev/null @@ -1,91 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 -; MUST.contractile — Baseline invariants for rsr-template-repo -; These constraints MUST NOT be violated. K9 validators enforce them. -; -; Part of the contractile family: MUST, TRUST, DUST, INTENT, ADJUST - -; ── Definitions ────────────────────────────────────────────────── -; -; MUST (noun/verb) -; The hard-constraint contractile. Defines invariants that are structurally -; required for the repository to function correctly and safely. Violating -; a MUST is always a bug — there are no "soft" MUSTs. -; -; Scope: -; MUST governs code, configuration, CI, and structure. It does NOT govern -; style, preference, or approach — those belong in CLAUDE.md or coding -; standards. MUST is for things that break the project if violated. -; -; Relationship to other contractiles: -; - TRUST: MUST is enforced regardless of trust level. Even maximal-trust -; agents cannot violate MUST constraints. -; - ADJUST: All ADJUST invariants are implicitly MUST invariants too. -; ADJUST exists separately for visibility. -; - INTENT: MUST protects the architectural decisions described in INTENT. -; - DUST: When a feature enters DUST (deprecation), its MUST constraints -; remain active until the feature is fully removed. -; -; Enforcement: -; K9 validators in contractiles/self-validating/ machine-check MUST constraints. -; CI runs these on every PR. Violations block merge. -; -; ── End Definitions ────────────────────────────────────────────── - -(must-contractile - (version "1.0.0") - (repo "rsr-template-repo") - - ; === Universal Invariants (apply to ALL repos) === - - (invariants - ; Paths - (must "no hardcoded absolute paths (/home/*, /mnt/*, /var/mnt/*)") - (must "all paths use env vars, XDG dirs, or relative references") - - ; Language policy - (must "no new TypeScript files") - (must "no new Python files") - (must "no new Go files") - (must "no npm/bun/yarn/pnpm dependencies — Deno only") - - ; Dangerous patterns - (must "no believe_me (Idris2)") - (must "no assert_total (Idris2)") - (must "no Admitted (Coq)") - (must "no sorry (Lean)") - (must "no unsafeCoerce (Haskell)") - (must "no Obj.magic (OCaml)") - (must "no unsafe {} blocks without safety comment (Rust)") - - ; License - (must "SPDX-License-Identifier header on every source file") - (must "no removal or modification of LICENSE file") - - ; Structure - (must ".machine_readable/ directory preserved") - (must "0-AI-MANIFEST.a2ml preserved") - (must "no SCM files in repo root — only in .machine_readable/") - - ; CI - (must "no removal of CI workflows without explicit approval") - (must "all GitHub Actions SHA-pinned") - - ; Code quality - (must "tests must not be deleted or weakened") - (must "generated code in generated/ directory only") - (must "no introduction of OWASP top 10 vulnerabilities") - - ; ABI/FFI (if applicable) - (must "no modification of ABI contracts without proof update") - (must "no removal of formal verification proofs") - ) - - ; === Project-Specific Invariants === - ; *REMINDER: Add invariants specific to this repo* - ; (must "# Add project-specific invariants here") - - (enforcement - (k9-validator "contractiles/self-validating/must-check.k9.ncl") - (ci "quality.yml runs must-check on every PR") - ) -) diff --git a/.machine_readable/TRUST.contractile b/.machine_readable/TRUST.contractile deleted file mode 100644 index 52fd1f0..0000000 --- a/.machine_readable/TRUST.contractile +++ /dev/null @@ -1,80 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 -; TRUST.contractile — Trust boundaries for rsr-template-repo -; Defines what LLM/SLM agents are trusted to do without asking. -; -; Part of the contractile family: MUST, TRUST, DUST, INTENT, ADJUST - -; ── Definitions ────────────────────────────────────────────────── -; -; TRUST (noun/verb) -; The permission contractile. Defines the boundary between what an AI -; agent may do autonomously and what requires human approval. Trust is -; graduated — not binary — with four levels from minimal to maximal. -; -; Trust levels: -; - maximal: Agent may read, build, test, lint, format, heal freely. -; Only destructive/external actions require approval. -; - standard: Agent may read and build. Test/lint need approval. -; - restricted: Agent may read only. All modifications need approval. -; - minimal: Agent may read specific files only. Everything else blocked. -; -; Scope: -; TRUST governs AI agent behaviour only. It does not affect human -; contributors — humans follow CONTRIBUTING.md and GOVERNANCE.adoc. -; -; Relationship to other contractiles: -; - MUST: Trust never overrides MUST. Even at maximal trust, MUST -; violations are blocked. -; - ADJUST: Trust does not exempt from ADJUST. All trust tiers must -; produce accessible output. -; - INTENT: TRUST.trust-deny protects the sensitive areas listed in -; INTENT.ask-before-touching. -; - DUST: Deprecated features have the same trust rules as active ones. -; -; ── End Definitions ────────────────────────────────────────────── - -(trust-contractile - (version "1.0.0") - (repo "rsr-template-repo") - - (trust-level "maximal") ; maximal | standard | restricted | minimal - - ; === Maximal Trust (default) === - ; LLM may freely do these without asking: - (trust-actions - "read" ; Read any file in the repo - "build" ; Run build commands - "test" ; Run test suites - "lint" ; Run linters and formatters - "format" ; Auto-format code - "doctor" ; Run self-diagnostics - "heal" ; Attempt automatic repair - "git-status" ; Check git status - "git-diff" ; View diffs - "git-log" ; View history - ) - - ; === Denied Actions (always require human approval) === - (trust-deny - "delete-branch" ; Could lose work - "force-push" ; Overwrites history - "modify-ci-secrets" ; Security sensitive - "publish" ; External visibility - "push-to-main" ; Protected branch - "delete-files-bulk" ; More than 5 files at once - "modify-license" ; Legal implications - "modify-security-policy" ; Security implications - "remove-proofs" ; Formal verification regression - "disable-ci-checks" ; Safety regression - ) - - ; === Trust Boundary === - (trust-boundary "repo") ; LLM confined to this repo unless explicitly told otherwise - - ; === Override === - ; Repos requiring tighter trust override these settings with justification: - ; (override - ; (trust-level "restricted") - ; (reason "Contains production secrets / handles PII / etc.") - ; ) -) diff --git a/.machine_readable/contractiles/Adjustfile.a2ml b/.machine_readable/contractiles/Adjustfile.a2ml new file mode 100644 index 0000000..6f01e89 --- /dev/null +++ b/.machine_readable/contractiles/Adjustfile.a2ml @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: MPL-2.0 +# Adjustfile — Drift-tolerance contract for rsr-template-repo +# Author: Jonathan D.A. Jewell +# +# Cumulative-drift catchment: tolerance bands + corrective actions. +# Authority: advisory (Yard) — continue-with-warnings; auto_fix where deterministic. +# Run with: adjust check +# Fix with: adjust fix (applies deterministic patches; advisory otherwise) + +@abstract: +Drift tolerances and corrective actions for rsr-template-repo. Unlike +MUST (hard gate), ADJUST tracks cumulative drift against tolerance bands +and proposes corrective actions. Advisory — it warns and trends, it does +not block. +@end + +## Template Drift + +### placeholder-drift +- description: Template placeholders should be replaced when copied +- tolerance: 0 placeholder markers in copied repos +- corrective: Search and replace all {{PLACEHOLDER}} markers +- severity: advisory +- notes: This check only applies to repos that copied from this template + +### template-version-drift +- description: Template version should match RSR spec version +- tolerance: Template version matches current RSR spec +- corrective: Update template to match latest RSR spec +- severity: advisory + +## Documentation Drift + +### readme-completeness +- description: README should document all template features +- tolerance: README covers all contractiles and directory structure +- corrective: Update README.adoc with missing sections +- severity: advisory + +### example-accuracy +- description: Examples in documentation should match actual template content +- tolerance: All code examples in docs are accurate +- corrective: Audit and fix examples in documentation +- severity: advisory + +## Structural Drift + +### contractile-sync +- description: All contractiles should have matching a2ml and ncl implementations +- tolerance: Every .a2ml has a corresponding .ncl +- corrective: Generate missing .ncl files from .a2ml +- severity: advisory + +### no-broken-symlinks +- description: No broken symbolic links in template structure +- tolerance: 0 broken symlinks +- corrective: Run symlink-check script +- severity: advisory + +## Accessibility Drift + +### adoc-not-md +- description: Template docs should prefer AsciiDoc +- tolerance: New prose docs are *.adoc +- corrective: Convert any new *.md to *.adoc +- severity: advisory + +### spdx-header-consistency +- description: All template files have correct SPDX headers +- tolerance: 0 files missing SPDX-License-Identifier +- corrective: Add SPDX headers to files that need them +- severity: advisory diff --git a/.machine_readable/contractiles/Intentfile.a2ml b/.machine_readable/contractiles/Intentfile.a2ml new file mode 100644 index 0000000..ef74f45 --- /dev/null +++ b/.machine_readable/contractiles/Intentfile.a2ml @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: MPL-2.0 +# Intentfile (A2ML Canonical) — north-star contractile for rsr-template-repo +# Author: Jonathan D.A. Jewell +# +# Paired runner: intend.ncl +# Verb: intend +# +# Semantics: North-star contractile. Declares BOTH concrete committed +# next-actions AND horizon aspirations the project wishes to +# become. Two sections share one file because they answer +# the same question at different ranges: +# [[intents]] — "we WILL do this; track progress" +# status: declared → in_progress → done | +# deferred | retired +# [[wishes]] — "we WISH this were true; revisit later" +# status: declared → in_progress → achieved | +# abandoned +# grouped by horizon: near / mid / far. +# Non-gating — this is a report, not a gate. See the `must` +# contractile for hard gates. + +@abstract: +North-star contractile for rsr-template-repo. This repository is the +canonical template for Rhodium Standard Repository compliance. It provides +the scaffold that all hyperpolymath repos should copy and customize. +@end + +## Purpose + +The rsr-template-repo serves as the master template for all hyperpolymath +repositories. It contains the complete set of contractile files, machine-readable +specifications, and governance documentation that define the Rhodium Standard. + +Every new repository in the hyperpolymath estate should be initialized by +copying this template and substituting the placeholder values with +repo-specific content. + +## Anti-Purpose + +This repository is NOT: +- A general-purpose project scaffold for external use (hyperpolymath-only) +- A replacement for per-repo customization (all files must be bespoke) +- A static template that never changes (evolves with RSR spec) +- A runtime library or framework (build-time only) + +## If In Doubt + +If you are unsure whether a change is in scope, ask. Sensitive areas: +- .machine_readable/ contractile definitions +- RSR specification files +- Governance templates +- License policy documents + +## Committed Next-Actions + +### repo-initialization +- description: Provide just copy-and-substitute template for new repos +- probe: test -f scripts/init-repo.sh +- status: done +- notes: Run with source scripts/init-repo.sh + +### contractile-completeness +- description: Every RSR contractile has an a2ml and ncl implementation +- probe: ls .machine_readable/contractiles/*.a2ml | wc -l | grep -q "^6$" +- status: in_progress +- notes: Currently 6 contractile verbs: intend, must, trust, adjust, bust, dust + +### automation-scripts +- description: All repetitive tasks have just recipes +- probe: grep -c "^# " Justfile | grep -q "^[6-9][0-9]*$" +- status: in_progress + +## Wishes + +### Near Horizon + +#### cross-repo-validation +- description: Tooling to validate all repos against RSR spec +- horizon: near +- status: declared + +#### automated-substitution +- description: Script to automate repo-specific substitution in template +- horizon: near +- status: declared + +### Mid Horizon + +#### formal-verification +- description: Idris2 proofs for all critical contractile invariants +- horizon: mid +- status: declared + +### Far Horizon + +#### ecosystem-visualization +- description: Interactive graph of all hyperpolymath repos and dependencies +- horizon: far +- status: declared diff --git a/.machine_readable/contractiles/Justfile b/.machine_readable/contractiles/Justfile new file mode 100644 index 0000000..8134631 --- /dev/null +++ b/.machine_readable/contractiles/Justfile @@ -0,0 +1,1544 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# RSR Standard Justfile Template +# https://just.systems/man/en/ +# +# Copy this file to new projects and customize the placeholder values. +# +# Run `just` to see all available recipes +# Run `just cookbook` to generate docs/just-cookbook.adoc +# Run `just combinations` to see matrix recipe options + +set shell := ["bash", "-uc"] +set dotenv-load := true +set positional-arguments := true + +# Import auto-generated contractile recipes (must-check, trust-verify, etc.) +# Re-generate with: contractile gen-just +import? "contractile.just" + +# Project metadata — customize these +project := "rsr-template-repo" +OWNER := "hyperpolymath" +REPO := "rsr-template-repo" +version := "0.1.0" +tier := "infrastructure" # 1 | 2 | infrastructure + +# ═══════════════════════════════════════════════════════════════════════════════ +# DEFAULT & HELP +# ═══════════════════════════════════════════════════════════════════════════════ + +# Show all available recipes with descriptions +default: + @just --list --unsorted + +# Show detailed help for a specific recipe +help recipe="": + #!/usr/bin/env bash + if [ -z "{{recipe}}" ]; then + just --list --unsorted + echo "" + echo "Usage: just help " + echo " just cookbook # Generate full documentation" + echo " just combinations # Show matrix recipes" + else + just --show "{{recipe}}" 2>/dev/null || echo "Recipe '{{recipe}}' not found" + fi + +# Show this project's info +info: + @echo "Project: {{project}}" + @echo "Version: {{version}}" + @echo "RSR Tier: {{tier}}" + @echo "Recipes: $(just --summary | wc -w)" + @[ -f ".machine_readable/STATE.a2ml" ] && grep -oP 'phase\s*=\s*"\K[^"]+' .machine_readable/STATE.a2ml | head -1 | xargs -I{} echo "Phase: {}" || true + +# Run Invariant Path overlay tools for this repository +invariant-path *ARGS: + ./scripts/invariant-path.sh {{ARGS}} + +# ═══════════════════════════════════════════════════════════════════════════════ +# INIT — Bootstrap a new project from this template +# ═══════════════════════════════════════════════════════════════════════════════ + +# Interactive project bootstrap — replaces all {{PLACEHOLDER}} tokens +init: + #!/usr/bin/env bash + set -euo pipefail + + echo "═══════════════════════════════════════════════════" + echo " RSR Project Bootstrap" + echo "═══════════════════════════════════════════════════" + echo "" + + # --- Load defaults from config (if exists) --- + # Create yours: ~/.config/rsr/defaults + # Format: OWNER=myorg AUTHOR="My Name" AUTHOR_EMAIL=me@example.org ... + DEFAULTS="${XDG_CONFIG_HOME:-$HOME/.config}/rsr/defaults" + if [ -f "$DEFAULTS" ]; then + echo "Loading defaults from $DEFAULTS" + # shellcheck source=/dev/null + source "$DEFAULTS" + echo "" + fi + + # --- Required values (pre-filled from defaults if available) --- + read -rp "Project name (human-readable, e.g. My Project): " PROJECT_NAME + [ -z "$PROJECT_NAME" ] && echo "Error: project name required" && exit 1 + + read -rp "Repository slug (e.g. my-project): " REPO + [ -z "$REPO" ] && echo "Error: repo slug required" && exit 1 + + read -rp "Owner [${OWNER:-}]: " _OWNER + OWNER="${_OWNER:-${OWNER:-}}" + [ -z "$OWNER" ] && echo "Error: owner required" && exit 1 + + read -rp "Author full name [${AUTHOR:-}]: " _AUTHOR + AUTHOR="${_AUTHOR:-${AUTHOR:-}}" + [ -z "$AUTHOR" ] && echo "Error: author name required" && exit 1 + + read -rp "Author email [${AUTHOR_EMAIL:-}]: " _AUTHOR_EMAIL + AUTHOR_EMAIL="${_AUTHOR_EMAIL:-${AUTHOR_EMAIL:-}}" + [ -z "$AUTHOR_EMAIL" ] && echo "Error: email required" && exit 1 + + # --- Optional values (pre-filled from defaults if available) --- + read -rp "Author organization [${AUTHOR_ORG:-none}]: " _AUTHOR_ORG + AUTHOR_ORG="${_AUTHOR_ORG:-${AUTHOR_ORG:-}}" + + read -rp "Previous/alt email [${AUTHOR_EMAIL_ALT:-none}]: " _AUTHOR_EMAIL_ALT + AUTHOR_EMAIL_ALT="${_AUTHOR_EMAIL_ALT:-${AUTHOR_EMAIL_ALT:-}}" + + read -rp "Project description []: " PROJECT_DESCRIPTION + + read -rp "Forge domain [${FORGE:-github.com}]: " _FORGE + FORGE="${_FORGE:-${FORGE:-github.com}}" + + read -rp "Security contact email [${SECURITY_EMAIL:-$AUTHOR_EMAIL}]: " _SECURITY_EMAIL + SECURITY_EMAIL="${_SECURITY_EMAIL:-${SECURITY_EMAIL:-$AUTHOR_EMAIL}}" + + read -rp "Conduct contact email [${CONDUCT_EMAIL:-$AUTHOR_EMAIL}]: " _CONDUCT_EMAIL + CONDUCT_EMAIL="${_CONDUCT_EMAIL:-${CONDUCT_EMAIL:-$AUTHOR_EMAIL}}" + + read -rp "Project type (library|binary|monorepo|service|website) [library]: " PROJECT_TYPE + PROJECT_TYPE="${PROJECT_TYPE:-library}" + + read -rp "Website URL [https://${FORGE}/${OWNER}/${REPO}]: " WEBSITE + WEBSITE="${WEBSITE:-https://${FORGE}/${OWNER}/${REPO}}" + + # --- Container values (optional — only relevant if container/ exists) --- + if [ -d "container" ]; then + echo "" + echo "── Container configuration (optional) ─────────" + read -rp "Service name [${REPO}]: " _SERVICE_NAME + SERVICE_NAME="${_SERVICE_NAME:-${REPO}}" + read -rp "Primary port [8080]: " _PORT + PORT="${_PORT:-8080}" + read -rp "Container registry [ghcr.io/${OWNER}]: " _REGISTRY + REGISTRY="${_REGISTRY:-ghcr.io/${OWNER}}" + else + SERVICE_NAME="${REPO}" + PORT="8080" + REGISTRY="ghcr.io/${OWNER}" + fi + + # --- Derived values --- + PROJECT_UPPER=$(echo "$REPO" | tr '[:lower:]-' '[:upper:]_') + PROJECT_LOWER=$(echo "$REPO" | tr '[:upper:]-' '[:lower:]_') + CURRENT_YEAR=$(date +%Y) + CURRENT_DATE=$(date +%Y-%m-%d) + VERSION="0.1.0" + + # Derive citation name parts (best-effort split on last space) + AUTHOR_LAST="${AUTHOR##* }" + AUTHOR_FIRST="${AUTHOR% *}" + FIRST_INITIAL="${AUTHOR_FIRST:0:1}." + if [ "$AUTHOR_LAST" = "$AUTHOR_FIRST" ]; then + AUTHOR_FIRST="$AUTHOR" + AUTHOR_LAST="" + FIRST_INITIAL="" + fi + + echo "" + echo "── Summary ──────────────────────────────────────" + echo " Project: $PROJECT_NAME" + echo " Repo: $REPO" + echo " Owner: $OWNER" + echo " Author: $AUTHOR <$AUTHOR_EMAIL>" + [ -n "$AUTHOR_ORG" ] && echo " Organization: $AUTHOR_ORG" + echo " Forge: $FORGE" + echo " Year: $CURRENT_YEAR" + echo "────────────────────────────────────────────────" + echo "" + read -rp "Proceed? [Y/n] " CONFIRM + [[ "${CONFIRM:-Y}" =~ ^[Nn] ]] && echo "Aborted." && exit 0 + + echo "" + echo "Replacing placeholders..." + + # Brace tokens as variables (hex avoids just interpolation) + LB=$(printf '\x7b\x7b') + RB=$(printf '\x7d\x7d') + + # Build the sed expression list + # Note: using | as delimiter since URLs contain / + SED_ARGS=( + -e "s|${LB}PROJECT_NAME${RB}|${PROJECT_NAME}|g" + -e "s|${LB}PROJECT_DESCRIPTION${RB}|${PROJECT_DESCRIPTION}|g" + -e "s|${LB}PROJECT${RB}|${PROJECT_UPPER}|g" + -e "s|${LB}project${RB}|${PROJECT_LOWER}|g" + -e "s|${LB}REPO${RB}|${REPO}|g" + -e "s|${LB}OWNER${RB}|${OWNER}|g" + -e "s|${LB}AUTHOR${RB}|${AUTHOR}|g" + -e "s|${LB}AUTHOR_EMAIL${RB}|${AUTHOR_EMAIL}|g" + -e "s|${LB}AUTHOR_ORG${RB}|${AUTHOR_ORG}|g" + -e "s|${LB}AUTHOR_LAST${RB}|${AUTHOR_LAST}|g" + -e "s|${LB}AUTHOR_FIRST${RB}|${AUTHOR_FIRST}|g" + -e "s|${LB}AUTHOR_INITIALS${RB}|${FIRST_INITIAL}|g" + -e "s|${LB}FORGE${RB}|${FORGE}|g" + -e "s|${LB}CURRENT_YEAR${RB}|${CURRENT_YEAR}|g" + -e "s|${LB}CURRENT_DATE${RB}|${CURRENT_DATE}|g" + -e "s|${LB}DATE${RB}|${CURRENT_DATE}|g" + -e "s|${LB}SECURITY_EMAIL${RB}|${SECURITY_EMAIL}|g" + -e "s|${LB}CONDUCT_EMAIL${RB}|${CONDUCT_EMAIL}|g" + -e "s|${LB}LICENSE${RB}|PMPL-1.0-or-later|g" + -e "s|${LB}CONDUCT_TEAM${RB}|Code of Conduct Committee|g" + -e "s|${LB}RESPONSE_TIME${RB}|48 hours|g" + -e "s|${LB}MAIN_BRANCH${RB}|main|g" + -e "s|${LB}PROJECT_PURPOSE${RB}|${PROJECT_DESCRIPTION}|g" + -e "s|${LB}PROJECT_ROLE${RB}|${PROJECT_TYPE}|g" + -e "s|${LB}PROJECT_TYPE${RB}|${PROJECT_TYPE}|g" + -e "s|${LB}WEBSITE${RB}|${WEBSITE}|g" + -e "s|${LB}SERVICE_NAME${RB}|${SERVICE_NAME}|g" + -e "s|${LB}PORT${RB}|${PORT}|g" + -e "s|${LB}REGISTRY${RB}|${REGISTRY}|g" + -e "s|${LB}IMAGE${RB}|${REGISTRY}/${SERVICE_NAME}|g" + -e "s|${LB}VERSION${RB}|${VERSION}|g" + -e "s|${LB}EMAIL${RB}|${AUTHOR_EMAIL}|g" + ) + [ -n "$AUTHOR_EMAIL_ALT" ] && SED_ARGS+=(-e "s|${LB}AUTHOR_EMAIL_ALT${RB}|${AUTHOR_EMAIL_ALT}|g") + + # Replace in all text files (skip .git, LICENSE text, and binaries) + find . -type f \ + -not -path './.git/*' \ + -not -name 'PMPL-1.0-or-later.txt' \ + -not -name '*.png' -not -name '*.jpg' -not -name '*.gif' \ + -not -name '*.woff' -not -name '*.woff2' \ + | while read -r file; do + if file --brief "$file" | grep -qi 'text\|ascii\|utf'; then + sed -i "${SED_ARGS[@]}" "$file" + fi + done + + # Also replace [YOUR-REPO-NAME] and [YOUR-NAME/ORG] in AI manifest + sed -i "s|\[YOUR-REPO-NAME\]|${PROJECT_NAME}|g" 0-AI-MANIFEST.a2ml 2>/dev/null || true + sed -i "s|\[YOUR-NAME/ORG\]|${OWNER}|g" 0-AI-MANIFEST.a2ml 2>/dev/null || true + + echo "" + echo "── Validation ───────────────────────────────────" + + # Check for remaining placeholders + PATTERN="${LB}[A-Z_]*${RB}" + REMAINING=$(grep -rl "$PATTERN" . --include='*.md' --include='*.adoc' --include='*.yml' --include='*.yaml' --include='*.a2ml' --include='*.toml' --include='*.scm' --include='*.ncl' --include='*.nix' --include='*.json' --include='*.sh' 2>/dev/null | grep -v '.git/' | grep -v '.machine_readable/ai/PLACEHOLDERS.adoc' || true) + if [ -n "$REMAINING" ]; then + echo "WARNING: Remaining placeholders in:" + echo "$REMAINING" | sed 's/^/ /' + echo "" + echo "Run: grep -rn '$LB' . --include='*.md' to inspect" + else + echo "All placeholders replaced successfully!" + fi + + # K9-SVC validation (if available) + if command -v k9-svc >/dev/null 2>&1; then + echo "" + echo "Running k9-svc validation..." + k9-svc validate . 2>/dev/null || true + fi + + echo "" + echo "Running OpenSSF compliance verification..." + just verify + + echo "" + echo "Done! Next steps:" + echo " 1. Review changes: git diff" + echo " 2. Remove template cruft: rm .machine_readable/ai/PLACEHOLDERS.adoc" + echo " 3. Customize README.adoc for your project" + echo " 4. Commit: git add -A && git commit -m 'feat: initialize from RSR template'" + echo " 5. Push: git remote add origin git@${FORGE}:${OWNER}/${REPO}.git && git push -u origin main" + +# ═══════════════════════════════════════════════════════════════════════════════ +# GROOVE & V-TRIPLE SETUP +# ═══════════════════════════════════════════════════════════════════════════════ + +# Configure Groove protocol manifest (port assignment, API surfaces) +groove-setup: + #!/usr/bin/env bash + set -euo pipefail + MANIFEST=".machine_readable/integrations/groove.a2ml" + if [ ! -f "$MANIFEST" ]; then + echo "Error: $MANIFEST not found. Run 'just init' first." + exit 1 + fi + + echo "═══════════════════════════════════════════════════" + echo " Groove Protocol Setup" + echo "═══════════════════════════════════════════════════" + echo "" + echo "Check PORT-REGISTRY.md before assigning a port:" + echo " https://github.com/hyperpolymath/standards/blob/main/PORT-REGISTRY.md" + echo "" + + read -rp "Primary port for this service: " PORT + [ -z "$PORT" ] && echo "Error: port required" && exit 1 + + echo "" + echo "Which API surfaces does this project expose?" + read -rp " REST API? [Y/n]: " REST + read -rp " gRPC? [y/N]: " GRPC + read -rp " GraphQL? [y/N]: " GRAPHQL + read -rp " WebSocket? [y/N]: " WS + read -rp " SSE (Server-Sent Events)? [y/N]: " SSE + + # Update port in manifest + sed -i "s/(port 0)/(port ${PORT})/" "$MANIFEST" + + # Update API surface flags + [[ "${GRPC,,}" == "y" ]] && sed -i 's/(grpc.*enabled false)/(grpc (enabled true)/' "$MANIFEST" + [[ "${GRAPHQL,,}" == "y" ]] && sed -i 's/(graphql.*enabled false)/(graphql (enabled true)/' "$MANIFEST" + [[ "${WS,,}" == "y" ]] && sed -i 's/(websocket.*enabled false)/(websocket (enabled true)/' "$MANIFEST" + [[ "${SSE,,}" == "y" ]] && sed -i 's/(sse.*enabled false)/(sse (enabled true)/' "$MANIFEST" + + echo "" + echo "Groove manifest updated: $MANIFEST" + echo "Port ${PORT} assigned. Add to PORT-REGISTRY.md if not already there." + +# Check for template placeholders that haven't been replaced +verify-template: + #!/usr/bin/env bash + set -euo pipefail + echo "Checking for unreplaced template placeholders..." + FOUND=0 + + # Check for double-brace placeholder patterns + HITS=$(grep -rn '{{'{{'}}[A-Z_]*{{'}}'}}' --include="*.adoc" --include="*.md" --include="*.a2ml" \ + --include="*.scm" --include="*.toml" --include="*.yml" --include="*.yaml" \ + . 2>/dev/null | grep -v 'node_modules\|\.git/' | grep -v 'PLACEHOLDERS.adoc' || true) + if [ -n "$HITS" ]; then + echo "" + echo "⚠ Unreplaced placeholders found:" + echo "$HITS" | head -20 + FOUND=1 + fi + + # Check for template defaults still present + if grep -q 'rsr-template-repo' Justfile 2>/dev/null; then + echo "⚠ Justfile still references 'rsr-template-repo' — update project name" + FOUND=1 + fi + + # Check for port 0 in Groove manifest + if grep -q '(port 0)' .machine_readable/integrations/groove.a2ml 2>/dev/null; then + echo "⚠ Groove manifest has port 0 — run 'just groove-setup' to assign a port" + FOUND=1 + fi + + # Check for empty SCM files + for f in .machine_readable/6a2/STATE.a2ml .machine_readable/6a2/META.a2ml .machine_readable/6a2/ECOSYSTEM.a2ml; do + if [ -f "$f" ] && grep -q '{{'{{'}}' "$f" 2>/dev/null; then + echo "⚠ $f still has template placeholders" + FOUND=1 + fi + done + + if [ $FOUND -eq 0 ]; then + echo "✓ No template placeholders found — project is properly customised." + else + echo "" + echo "Run 'just init' to replace placeholders, or edit files manually." + exit 1 + fi + +# Analyse this project and advise what to keep, remove, or leave for later. +# Does NOT modify any files — only prints recommendations. +self-assess: + #!/usr/bin/env bash + set -euo pipefail + + echo "═══════════════════════════════════════════════════" + echo " RSR Project Self-Assessment" + echo "═══════════════════════════════════════════════════" + echo "" + echo "Scanning project structure to identify what's" + echo "relevant, removable, or worth keeping for later..." + echo "" + + # Detect project characteristics + HAS_RUST=false; [ -f "Cargo.toml" ] && HAS_RUST=true + HAS_ELIXIR=false; [ -f "mix.exs" ] && HAS_ELIXIR=true + HAS_RESCRIPT=false; [ -f "rescript.json" ] || [ -f "bsconfig.json" ] && HAS_RESCRIPT=true + HAS_IDRIS=false; ls *.ipkg >/dev/null 2>&1 && HAS_IDRIS=true + HAS_ZIG=false; [ -f "build.zig" ] || [ -d "ffi/zig" ] && HAS_ZIG=true + HAS_GLEAM=false; [ -f "gleam.toml" ] && HAS_GLEAM=true + HAS_CONTAINER=false; [ -f "Containerfile" ] || [ -f "container/Containerfile" ] && HAS_CONTAINER=true + HAS_TESTS=false; [ -d "test" ] || [ -d "tests" ] || [ -d "__tests__" ] && HAS_TESTS=true + HAS_API=false; grep -rq 'port\|listen\|endpoint' --include="*.exs" --include="*.rs" --include="*.toml" . 2>/dev/null && HAS_API=true + IS_LIBRARY=false; [ -f "Cargo.toml" ] && grep -q '\[lib\]' Cargo.toml 2>/dev/null && IS_LIBRARY=true + + echo "Detected: Rust=$HAS_RUST Elixir=$HAS_ELIXIR ReScript=$HAS_RESCRIPT" + echo " Idris=$HAS_IDRIS Zig=$HAS_ZIG Gleam=$HAS_GLEAM" + echo " Container=$HAS_CONTAINER Tests=$HAS_TESTS API=$HAS_API" + echo "" + + # ── ESSENTIAL (removing these breaks RSR compliance) ────────── + echo "── ESSENTIAL (removing breaks Rhodium Standard) ──────────" + echo "" + + for f in LICENSE SECURITY.md CODE_OF_CONDUCT.md CONTRIBUTING.md .editorconfig .gitignore; do + if [ -f "$f" ]; then + echo " ✓ $f — KEEP (RSR required)" + else + echo " ✗ $f — MISSING (RSR violation!)" + fi + done + + if [ -d ".machine_readable/6a2" ]; then + echo " ✓ .machine_readable/6a2/ — KEEP (SCM checkpoint files)" + else + echo " ✗ .machine_readable/6a2/ — MISSING (RSR violation!)" + fi + + if [ -d ".github/workflows" ]; then + WF_COUNT=$(ls .github/workflows/*.yml 2>/dev/null | wc -l) + echo " ✓ .github/workflows/ — KEEP ($WF_COUNT workflows, RSR CI/CD)" + fi + echo "" + + # ── RELEVANT (useful for your project type) ─────────────────── + echo "── RELEVANT (matches your project) ───────────────────────" + echo "" + + if $HAS_IDRIS && { [ -d "src/interface/Abi" ] || [ -d "src/interface/abi" ]; }; then + echo " ✓ src/interface/Abi|abi/ — KEEP (Idris2 ABI definitions)" + elif ! $HAS_IDRIS && { [ -d "src/interface/Abi" ] || [ -d "src/interface/abi" ]; }; then + echo " ? src/interface/Abi|abi/ — No Idris2 detected." + echo " → KEEP if you plan to add formal verification later." + echo " → SAFE TO REMOVE if this project will never use Idris2." + echo " ⚠ Consequence: no formally verified interface definitions." + fi + + if $HAS_ZIG && [ -d "src/interface/ffi" ]; then + echo " ✓ src/interface/ffi/ — KEEP (Zig FFI bridge)" + elif ! $HAS_ZIG && [ -d "src/interface/ffi" ]; then + echo " ? src/interface/ffi/ — No Zig detected." + echo " → KEEP if you plan C ABI interop later." + echo " → SAFE TO REMOVE if this is a pure web/scripting project." + echo " ⚠ Consequence: no C-compatible FFI bridge." + fi + + if $HAS_API && [ -f ".machine_readable/integrations/groove.a2ml" ]; then + PORT=$(grep '(port ' .machine_readable/integrations/groove.a2ml 2>/dev/null | sed 's/.*(port \([0-9]*\)).*/\1/') + if [ "$PORT" = "0" ]; then + echo " ⚠ groove.a2ml — Port not assigned. Run 'just groove-setup'." + else + echo " ✓ groove.a2ml — KEEP (Groove discovery on port $PORT)" + fi + elif $HAS_API; then + echo " ✗ groove.a2ml — MISSING. Your project has an API but no Groove manifest." + echo " → Run 'just groove-setup' to enable snap-on/snap-off discovery." + fi + + if $HAS_CONTAINER && [ -d "container" ]; then + echo " ✓ container/ — KEEP (Containerfile + compose)" + elif ! $HAS_CONTAINER && [ -d "container" ]; then + echo " ? container/ — No Containerfile detected in use." + echo " → KEEP if you plan to containerise later." + echo " → SAFE TO REMOVE for libraries and CLI tools." + fi + + echo "" + + # ── SAFE TO REMOVE (not relevant, no consequences) ──────────── + echo "── SAFE TO REMOVE (no RSR consequences) ──────────────────" + echo "" + + if ! $HAS_RESCRIPT && [ -d "examples" ] && ls examples/*.res >/dev/null 2>&1; then + echo " ○ examples/*.res — Template ReScript examples. Not your code." + fi + + if [ -f ".machine_readable/ai/PLACEHOLDERS.adoc" ]; then + echo " ○ .machine_readable/ai/PLACEHOLDERS.adoc — Template doc. Remove after init." + fi + + if [ -f "flake.nix" ] && ! command -v nix >/dev/null 2>&1; then + echo " ○ flake.nix — Nix flake. Safe to remove if you don't use Nix." + echo " → KEEP if others might build with Nix." + fi + + if [ -f "guix.scm" ] && ! command -v guix >/dev/null 2>&1; then + echo " ○ guix.scm — Guix package. Safe to remove if you don't use Guix." + echo " → KEEP if others might build with Guix." + fi + + echo "" + + # ── FUTURE VALUE (not needed now, worth keeping) ────────────── + echo "── KEEP FOR FUTURE (not active, but valuable later) ──────" + echo "" + + if [ -d ".machine_readable/contractiles" ]; then + echo " ◆ contractiles/ — Must/Trust/Dust/Lust contracts." + echo " Not enforced until you configure them, but ready when you need" + echo " automated compliance checking. Zero cost to keep." + fi + + if [ -d ".machine_readable/bot_directives" ]; then + echo " ◆ bot_directives/ — Gitbot fleet configuration." + echo " Not active until gitbot-fleet is connected. Keeps your repo" + echo " ready for automated maintenance when the fleet arrives." + fi + + if [ -d ".machine_readable/bot_directives" ]; then + echo " ◆ bot_directives/ — AI agent methodology config." + echo " Guides Claude/Gemini/etc on how to work in this repo." + echo " No cost to keep. Improves AI assistance quality." + fi + + if [ -d "docs/governance" ]; then + echo " ◆ docs/governance/ — TSDM, CRG, maintenance checklists." + echo " Not needed for solo projects. Essential when you add contributors." + fi + + if [ -d "verification" ]; then + echo " ◆ verification/ — Proofs, benchmarks, fuzzing, safety case." + echo " Empty scaffolds until you add formal verification." + echo " Worth keeping for any project that claims safety properties." + fi + + echo "" + echo "═══════════════════════════════════════════════════" + echo " Assessment complete. No files were modified." + echo "═══════════════════════════════════════════════════" + +# ═══════════════════════════════════════════════════════════════════════════════ +# OPENSSF COMPLIANCE VERIFICATION +# ═══════════════════════════════════════════════════════════════════════════════ + +# Verify OpenSSF Best Practices prerequisites — fails if any required file is missing +verify: + #!/usr/bin/env bash + set -euo pipefail + + echo "=== OpenSSF Best Practices Verification ===" + ERRORS=0 + + check_file() { + if [ ! -f "$1" ]; then + echo " FAIL: $1 missing" + ERRORS=$((ERRORS + 1)) + else + echo " OK: $1" + fi + } + + # Accept either .md or .adoc for documentation files + check_either() { + if [ ! -f "$1" ] && [ ! -f "$2" ]; then + echo " FAIL: $1 (or $2) missing" + ERRORS=$((ERRORS + 1)) + else + local found="$1" + [ -f "$2" ] && found="$2" + [ -f "$1" ] && found="$1" + echo " OK: $found" + fi + } + + check_either "SECURITY.md" "SECURITY.adoc" + check_file "LICENSE" + check_either "CONTRIBUTING.md" "CONTRIBUTING.adoc" + check_either "README.adoc" "README.md" + check_file ".machine_readable/STATE.a2ml" + check_file ".machine_readable/META.a2ml" + check_file ".machine_readable/ECOSYSTEM.a2ml" + check_either "CHANGELOG.md" "CHANGELOG.adoc" + + # Check at least 1 workflow exists + WORKFLOW_COUNT=$(find .github/workflows -name '*.yml' -o -name '*.yaml' 2>/dev/null | wc -l) + if [ "$WORKFLOW_COUNT" -eq 0 ]; then + echo " FAIL: No workflows in .github/workflows/" + ERRORS=$((ERRORS + 1)) + else + echo " OK: .github/workflows/ ($WORKFLOW_COUNT workflows)" + fi + + echo "" + if [ "$ERRORS" -gt 0 ]; then + echo "FAIL: $ERRORS OpenSSF prerequisites missing — repo cannot ship." + exit 1 + fi + echo "PASS: All OpenSSF Best Practices prerequisites satisfied." + +# ═══════════════════════════════════════════════════════════════════════════════ +# BUILD & COMPILE +# ═══════════════════════════════════════════════════════════════════════════════ + +# Build the project (debug mode) +build *args: + @echo "Building {{project}} (debug)..." + # TODO: Replace with your build command + # Examples: + # cargo build {{args}} # Rust + # mix compile {{args}} # Elixir + # zig build {{args}} # Zig + # deno task build {{args}} # Deno/ReScript + @echo "Build complete" + +# Build in release mode with optimizations +build-release *args: + @echo "Building {{project}} (release)..." + # TODO: Replace with your release build command + # Examples: + # cargo build --release {{args}} + # MIX_ENV=prod mix compile {{args}} + # zig build -Doptimize=ReleaseFast {{args}} + @echo "Release build complete" + +# Build and watch for changes (requires entr or similar) +build-watch: + @echo "Watching for changes..." + # TODO: Customize file patterns for your language + # Examples: + # find src -name '*.rs' | entr -c just build + # mix compile --force --warnings-as-errors + # deno task dev + +# Clean build artifacts [reversible: rebuild with `just build`] +clean: + @echo "Cleaning..." + # TODO: Customize for your build system + rm -rf target/ _build/ build/ dist/ out/ obj/ bin/ + +# Deep clean including caches [reversible: rebuild] +clean-all: clean + rm -rf .cache .tmp + +# ═══════════════════════════════════════════════════════════════════════════════ +# TEST & QUALITY +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run all tests +test *args: + @echo "Running tests..." + # TODO: Replace with your test command + # Examples: + # cargo test {{args}} + # mix test {{args}} + # zig build test {{args}} + # deno test {{args}} + @echo "Tests passed!" + +# Run tests with verbose output +test-verbose: + @echo "Running tests (verbose)..." + # TODO: Replace with verbose test command + +# Smoke test +test-smoke: + @echo "Smoke test..." + # TODO: Add basic sanity checks + +# Run end-to-end tests (full pipeline: build → run → verify) +e2e: + @echo "Running E2E tests..." + # TODO: Replace with your E2E test command. Examples: + # bash tests/e2e.sh # Shell-based E2E + # npx playwright test # Browser E2E + # mix test test/integration/e2e_test.exs # Elixir E2E + # cargo test --test end_to_end # Rust E2E + @echo "E2E tests passed!" + +# Run aspect tests (cross-cutting concern validation) +aspect: + @echo "Running aspect tests..." + # TODO: Replace with your aspect test command. Examples: + # bash tests/aspect_tests.sh # Shell-based aspect tests + # cargo test --test aspects # Rust aspect tests + # Aspect tests validate architectural invariants: + # - Thread safety (mutex in FFI modules) + # - ABI/FFI contract (declarations match exports) + # - SPDX compliance (all files have license headers) + # - No dangerous patterns (believe_me, assert_total, etc.) + @echo "Aspect tests passed!" + +# Run benchmarks (performance regression detection) +bench: + @echo "Running benchmarks..." + # TODO: Replace with your benchmark command. Examples: + # cargo bench # Rust criterion + # zig build bench # Zig benchmarks + # mix run bench/benchmarks.exs # Elixir benchee + # deno bench # Deno bench + @echo "Benchmarks complete!" + +# Run readiness tests (Component Readiness Grade: D/C/B) +readiness: + @echo "Running readiness tests..." + # TODO: Replace with your readiness test command. Examples: + # cargo test --test readiness -- --nocapture + @echo "Readiness tests complete!" + +# Print the current CRG grade (reads from READINESS.md '**Current Grade:** X' line) +crg-grade: + @grade=$$(grep -oP '(?<=\*\*Current Grade:\*\* )[A-FX]' READINESS.md 2>/dev/null | head -1); \ + [ -z "$$grade" ] && grade="X"; \ + echo "$$grade" + +# Print a shields.io CRG badge for embedding in README files +# Looks for '**Current Grade:** X' in READINESS.md; falls back to X +crg-badge: + @grade=$$(grep -oP '(?<=\*\*Current Grade:\*\* )[A-FX]' READINESS.md 2>/dev/null | head -1); \ + [ -z "$$grade" ] && grade="X"; \ + case "$$grade" in \ + A) color="brightgreen" ;; \ + B) color="green" ;; \ + C) color="yellow" ;; \ + D) color="orange" ;; \ + E) color="red" ;; \ + F) color="critical" ;; \ + *) color="lightgrey" ;; \ + esac; \ + echo "[![CRG $$grade](https://img.shields.io/badge/CRG-$$grade-$$color?style=flat-square)](https://github.com/hyperpolymath/standards/tree/main/component-readiness-grades)" + +# Run the full merge-requirement test suite (ALL categories) +# Per STANDING rule: P2P + E2E + aspect + execution + lifecycle + bench +test-all: test e2e aspect bench readiness + @echo "All test categories passed — safe to merge!" + +# Run all quality checks +quality: fmt-check lint test + @echo "All quality checks passed!" + +# Fix all auto-fixable issues [reversible: git checkout] +fix: fmt + @echo "Fixed all auto-fixable issues" + +# ═══════════════════════════════════════════════════════════════════════════════ +# LINT & FORMAT +# ═══════════════════════════════════════════════════════════════════════════════ + +# Format all source files [reversible: git checkout] +fmt: + @echo "Formatting source files..." + # TODO: Replace with your formatter + # Examples: + # cargo fmt + # mix format + # gleam format + # deno fmt + +# Check formatting without changes +fmt-check: + @echo "Checking formatting..." + # TODO: Replace with your format check + # Examples: + # cargo fmt --check + # mix format --check-formatted + # gleam format --check + +# Run linter +lint: + @echo "Linting source files..." + # TODO: Replace with your linter + # Examples: + # cargo clippy -- -D warnings + # mix credo --strict + # gleam check + +# ═══════════════════════════════════════════════════════════════════════════════ +# RUN & EXECUTE +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run the application +run *args: build + # TODO: Replace with your run command + echo "Run not configured yet" + +# Run with verbose output +run-verbose *args: build + # TODO: Replace with verbose run command + echo "Run not configured yet" + +# Install to user path +install: build-release + @echo "Installing {{project}}..." + # TODO: Replace with your install command + +# ═══════════════════════════════════════════════════════════════════════════════ +# DEPENDENCIES +# ═══════════════════════════════════════════════════════════════════════════════ + +# Install/check all dependencies +deps: + @echo "Checking dependencies..." + # TODO: Replace with your dependency check + # Examples: + # cargo check + # mix deps.get + # gleam deps download + @echo "All dependencies satisfied" + +# Audit dependencies for vulnerabilities +deps-audit: + @echo "Auditing for vulnerabilities..." + # TODO: Replace with your audit command + # Examples: + # cargo audit + # mix audit + @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL --quiet . || true + @command -v gitleaks >/dev/null && gitleaks detect --source . --no-git --quiet || true + @echo "Audit complete" + +# ═══════════════════════════════════════════════════════════════════════════════ +# DOCUMENTATION +# ═══════════════════════════════════════════════════════════════════════════════ + +# Generate all documentation +docs: + @mkdir -p docs/generated docs/man + just cookbook + just man + @echo "Documentation generated in docs/" + +# Generate justfile cookbook documentation +cookbook: + #!/usr/bin/env bash + mkdir -p docs + OUTPUT="docs/just-cookbook.adoc" + echo "= {{project}} Justfile Cookbook" > "$OUTPUT" + echo ":toc: left" >> "$OUTPUT" + echo ":toclevels: 3" >> "$OUTPUT" + echo "" >> "$OUTPUT" + echo "Generated: $(date -Iseconds)" >> "$OUTPUT" + echo "" >> "$OUTPUT" + echo "== Recipes" >> "$OUTPUT" + echo "" >> "$OUTPUT" + just --list --unsorted | while read -r line; do + if [[ "$line" =~ ^[[:space:]]+([a-z_-]+) ]]; then + recipe="${BASH_REMATCH[1]}" + echo "=== $recipe" >> "$OUTPUT" + echo "" >> "$OUTPUT" + echo "[source,bash]" >> "$OUTPUT" + echo "----" >> "$OUTPUT" + echo "just $recipe" >> "$OUTPUT" + echo "----" >> "$OUTPUT" + echo "" >> "$OUTPUT" + fi + done + echo "Generated: $OUTPUT" + +# Generate man page +man: + #!/usr/bin/env bash + mkdir -p docs/man + cat > docs/man/{{project}}.1 << EOF + .TH {{project}} 1 "$(date +%Y-%m-%d)" "{{version}}" "{{project}} Manual" + .SH NAME + {{project}} \- RSR-compliant project + .SH SYNOPSIS + .B just + [recipe] [args...] + .SH DESCRIPTION + RSR (Rhodium Standard Repository) project managed with just. + .SH AUTHOR + $(git config user.name 2>/dev/null || echo "Author") <$(git config user.email 2>/dev/null || echo "email")> + EOF + echo "Generated: docs/man/{{project}}.1" + +# ═══════════════════════════════════════════════════════════════════════════════ +# CONTAINERS (stapeln ecosystem — Podman + Chainguard Wolfi) +# ═══════════════════════════════════════════════════════════════════════════════ + +# Initialise container templates — substitute placeholders with project values +container-init: + #!/usr/bin/env bash + set -euo pipefail + + if [ ! -d "container" ]; then + echo "Error: container/ directory not found." + echo "This repo may not have been created from rsr-template-repo." + exit 1 + fi + + echo "=== Container Template Initialisation ===" + echo "" + + # Load RSR defaults if available + DEFAULTS="${XDG_CONFIG_HOME:-$HOME/.config}/rsr/defaults" + if [ -f "$DEFAULTS" ]; then + echo "Loading defaults from $DEFAULTS" + # shellcheck source=/dev/null + source "$DEFAULTS" + echo "" + fi + + # Prompt for container-specific values + read -rp "Service name (e.g. my-api) [{{project}}]: " _SERVICE_NAME + SERVICE_NAME="${_SERVICE_NAME:-{{project}}}" + + read -rp "Primary port [8080]: " _PORT + PORT="${_PORT:-8080}" + + read -rp "Container registry [ghcr.io/${OWNER:-{{OWNER}}}]: " _REGISTRY + REGISTRY="${_REGISTRY:-ghcr.io/${OWNER:-{{OWNER}}}}" + + echo "" + echo " Service: $SERVICE_NAME" + echo " Port: $PORT" + echo " Registry: $REGISTRY" + echo "" + read -rp "Proceed? [Y/n] " CONFIRM + [[ "${CONFIRM:-Y}" =~ ^[Nn] ]] && echo "Aborted." && exit 0 + + echo "" + echo "Replacing container placeholders..." + + # Brace tokens as variables (hex escapes avoid just interpolation) + LB=$(printf '\x7b\x7b') + RB=$(printf '\x7d\x7d') + + SED_ARGS=( + -e "s|${LB}SERVICE_NAME${RB}|${SERVICE_NAME}|g" + -e "s|${LB}PORT${RB}|${PORT}|g" + -e "s|${LB}REGISTRY${RB}|${REGISTRY}|g" + ) + + find container/ -type f | while read -r file; do + if file --brief "$file" | grep -qi 'text\|ascii\|utf'; then + sed -i "${SED_ARGS[@]}" "$file" + fi + done + + echo "Container templates initialised." + echo "" + echo "Next steps:" + echo " 1. Edit container/Containerfile — add your build commands" + echo " 2. Edit container/entrypoint.sh — set your application binary" + echo " 3. Review container/compose.toml — adjust services and volumes" + echo " 4. Build: just container-build" + +# Build container image via cerro-torre pipeline +container-build *args: + #!/usr/bin/env bash + if [ -f "container/ct-build.sh" ]; then + cd container && ./ct-build.sh {{args}} + elif [ -f "container/Containerfile" ]; then + podman build -t {{project}}:latest -f container/Containerfile . + elif [ -f "Containerfile" ]; then + podman build -t {{project}}:latest -f Containerfile . + else + echo "No Containerfile found in container/ or project root" + exit 1 + fi + +# Verify compose configuration +container-verify: + #!/usr/bin/env bash + if [ ! -f "container/compose.toml" ]; then + echo "No container/compose.toml found" + exit 1 + fi + cd container + if command -v selur-compose &>/dev/null; then + selur-compose verify + else + echo "selur-compose not found, falling back to podman compose" + podman compose --file compose.toml config + fi + +# Start container stack +container-up *args: + #!/usr/bin/env bash + if [ ! -f "container/compose.toml" ]; then + echo "No container/compose.toml found" + exit 1 + fi + cd container + if command -v selur-compose &>/dev/null; then + selur-compose up {{args}} + else + podman compose --file compose.toml up {{args}} + fi + +# Stop container stack +container-down: + #!/usr/bin/env bash + cd container 2>/dev/null || { echo "No container/ directory"; exit 1; } + if command -v selur-compose &>/dev/null; then + selur-compose down + else + podman compose --file compose.toml down + fi + +# Sign and verify container bundle (build + pack + sign + verify) +container-sign: + #!/usr/bin/env bash + if [ -f "container/ct-build.sh" ]; then + cd container && ./ct-build.sh + else + echo "No container/ct-build.sh found" + exit 1 + fi + +# Push signed bundle to registry +container-push: + #!/usr/bin/env bash + if [ -f "container/ct-build.sh" ]; then + cd container && ./ct-build.sh --push + else + echo "No container/ct-build.sh found — falling back to podman push" + podman push {{project}}:latest + fi + +# Run container interactively (for debugging) +container-run *args: + podman run --rm -it {{project}}:latest {{args}} + +# ═══════════════════════════════════════════════════════════════════════════════ +# CI & AUTOMATION +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run full CI pipeline locally +ci: deps quality + @echo "CI pipeline complete!" + +# Install git hooks +install-hooks: + @mkdir -p .git/hooks + @cat > .git/hooks/pre-commit << 'HOOKEOF' + #!/bin/bash + just fmt-check || exit 1 + just lint || exit 1 + just assail || exit 1 + HOOKEOF + @chmod +x .git/hooks/pre-commit + @echo "Git hooks installed" + +# ═══════════════════════════════════════════════════════════════════════════════ +# SECURITY +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run security audit +security: deps-audit + @echo "=== Security Audit ===" + @command -v gitleaks >/dev/null && gitleaks detect --source . --verbose || true + @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL . || true + @echo "Security audit complete" + +# Generate SBOM +sbom: + @mkdir -p docs/security + @command -v syft >/dev/null && syft . -o spdx-json > docs/security/sbom.spdx.json || echo "syft not found" + +# ═══════════════════════════════════════════════════════════════════════════════ +# VALIDATION & COMPLIANCE +# ═══════════════════════════════════════════════════════════════════════════════ + +# Validate RSR compliance +validate-rsr: + #!/usr/bin/env bash + echo "=== RSR Compliance Check ===" + MISSING="" + for f in .editorconfig .gitignore Justfile README.adoc LICENSE 0-AI-MANIFEST.a2ml; do + [ -f "$f" ] || MISSING="$MISSING $f" + done + for f in .machine_readable/STATE.a2ml .machine_readable/META.a2ml .machine_readable/ECOSYSTEM.a2ml .machine_readable/anchors/ANCHOR.a2ml .machine_readable/policies/MAINTENANCE-AXES.a2ml .machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml .machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml; do + [ -f "$f" ] || MISSING="$MISSING $f" + done + for f in licensing/exhibits/EXHIBIT-A-ETHICAL-USE.txt licensing/exhibits/EXHIBIT-B-QUANTUM-SAFE.txt licensing/texts/PMPL-1.0-or-later.txt; do + [ -f "$f" ] || MISSING="$MISSING $f" + done + if [ ! -d "src/interface/Abi" ] && [ ! -d "src/interface/abi" ]; then + MISSING="$MISSING src/interface/Abi" + fi + for f in src/interface/ffi src/interface/generated; do + [ -d "$f" ] || MISSING="$MISSING $f" + done + for f in docs/governance/MAINTENANCE-CHECKLIST.adoc docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc; do + [ -f "$f" ] || MISSING="$MISSING $f" + done + if [ -f ".machine_readable/META.a2ml" ]; then + grep -q 'axis-1 = "must > intend > like"' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:axis-1" + grep -q 'axis-2 = "corrective > adaptive > perfective"' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:axis-2" + grep -q 'axis-3 = "systems > compliance > effects"' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:axis-3" + grep -q 'scoping-first = true' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:scoping-first" + grep -q 'idris-unsound-scan = "believe_me/assert_total"' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:idris-unsound-scan" + grep -q 'audit-focus = "systems in place, documentation explains actual state, safety/security accounted for, observed effects reviewed"' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:audit-focus" + grep -q 'compliance-focus = "seams/compromises/exception register, bounded exceptions, anti-drift checks"' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:compliance-focus" + grep -q 'effects-evidence = "benchmark execution/results and maintainer status dialogue/review"' .machine_readable/META.a2ml || MISSING="$MISSING META.a2ml:effects-evidence" + grep -q 'compliance-tooling = "panic-attack"' .machine_readable/policies/MAINTENANCE-AXES.a2ml || MISSING="$MISSING MAINTENANCE-AXES.a2ml:compliance-tooling" + grep -q 'effects-tooling = "ecological checking with sustainabot guidance"' .machine_readable/policies/MAINTENANCE-AXES.a2ml || MISSING="$MISSING MAINTENANCE-AXES.a2ml:effects-tooling" + grep -q 'source-human = "docs/governance/MAINTENANCE-CHECKLIST.adoc"' .machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml || MISSING="$MISSING MAINTENANCE-CHECKLIST.a2ml:source-human" + grep -q 'source-human = "docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc"' .machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml || MISSING="$MISSING SOFTWARE-DEVELOPMENT-APPROACH.a2ml:source-human" + fi + if [ -n "$MISSING" ]; then + echo "MISSING:$MISSING" + exit 1 + fi + echo "RSR compliance: PASS" + +# Validate STATE.a2ml syntax +validate-state: + @if [ -f ".machine_readable/STATE.a2ml" ]; then \ + grep -q '^\[metadata\]' .machine_readable/STATE.a2ml && \ + grep -q 'project\s*=' .machine_readable/STATE.a2ml && \ + echo "STATE.a2ml: valid" || echo "STATE.a2ml: INVALID (missing required sections)"; \ + else \ + echo "No .machine_readable/STATE.a2ml found"; \ + fi + +# Validate AI installation guide completeness (finishbot pre-release check) +validate-ai-install: + #!/usr/bin/env bash + echo "=== AI Installation Guide Check ===" + GUIDE="docs/AI_INSTALLATION_GUIDE.adoc" + README="README.adoc" + ERRORS=0 + + # Check guide exists + if [ ! -f "$GUIDE" ]; then + echo "MISSING: $GUIDE (create from template: docs/AI_INSTALLATION_GUIDE.adoc)" + ERRORS=$((ERRORS + 1)) + else + # Check for unfilled TODO markers + TODOS=$(grep -c '\[TODO-AI-INSTALL' "$GUIDE" 2>/dev/null || true) + if [ "$TODOS" -gt 0 ]; then + echo "INCOMPLETE: $GUIDE has $TODOS unfilled [TODO-AI-INSTALL] markers:" + grep -n '\[TODO-AI-INSTALL' "$GUIDE" | head -10 + ERRORS=$((ERRORS + 1)) + else + echo "$GUIDE: complete (no TODO markers)" + fi + + # Check AI implementation section exists + if ! grep -q 'ai-implementation' "$GUIDE" 2>/dev/null; then + echo "MISSING: [[ai-implementation]] anchor in $GUIDE" + ERRORS=$((ERRORS + 1)) + fi + + # Check privacy notice exists + if ! grep -qi 'privacy' "$GUIDE" 2>/dev/null; then + echo "MISSING: Privacy notice in $GUIDE" + ERRORS=$((ERRORS + 1)) + fi + + # Check install commands exist (not just placeholders) + if ! grep -q 'git clone' "$GUIDE" 2>/dev/null; then + echo "WARNING: No git clone command found in $GUIDE -- install commands may be incomplete" + fi + fi + + # Check README has AI install section + if [ -f "$README" ]; then + if ! grep -qi 'AI-Assisted Installation' "$README" 2>/dev/null; then + echo "MISSING: AI-Assisted Installation section in $README" + echo " Copy from docs/AI-INSTALL-README-SECTION.adoc" + ERRORS=$((ERRORS + 1)) + fi + + # Check README for unfilled TODO markers + README_TODOS=$(grep -c '\[TODO-AI-INSTALL' "$README" 2>/dev/null || true) + if [ "$README_TODOS" -gt 0 ]; then + echo "INCOMPLETE: $README has $README_TODOS unfilled [TODO-AI-INSTALL] markers" + ERRORS=$((ERRORS + 1)) + fi + fi + + if [ "$ERRORS" -gt 0 ]; then + echo "" + echo "AI install guide: FAIL ($ERRORS issues)" + exit 1 + fi + echo "AI install guide: PASS" + +# Full validation suite +validate: validate-rsr validate-state validate-ai-install + @echo "All validations passed!" + +# ═══════════════════════════════════════════════════════════════════════════════ +# STATE MANAGEMENT +# ═══════════════════════════════════════════════════════════════════════════════ + +# Update STATE.a2ml timestamp +state-touch: + @if [ -f ".machine_readable/STATE.a2ml" ]; then \ + sed -i 's/last-updated = "[^"]*"/last-updated = "'"$(date +%Y-%m-%d)"'"/' .machine_readable/STATE.a2ml && \ + echo "STATE.a2ml timestamp updated"; \ + fi + +# Show current phase from STATE.a2ml +state-phase: + @grep -oP 'phase\s*=\s*"\K[^"]+' .machine_readable/STATE.a2ml 2>/dev/null | head -1 || echo "unknown" + +# ═══════════════════════════════════════════════════════════════════════════════ +# GUIX & NIX +# ═══════════════════════════════════════════════════════════════════════════════ + +# Enter Guix development shell (primary) +guix-shell: + guix shell -D -f guix.scm + +# Build with Guix +guix-build: + guix build -f guix.scm + +# Enter Nix development shell (fallback) +nix-shell: + @if [ -f "flake.nix" ]; then nix develop; else echo "No flake.nix"; fi + +# ═══════════════════════════════════════════════════════════════════════════════ +# HYBRID AUTOMATION +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run local automation tasks +automate task="all": + #!/usr/bin/env bash + case "{{task}}" in + all) just fmt && just lint && just test && just docs && just state-touch ;; + cleanup) just clean && find . -name "*.orig" -delete && find . -name "*~" -delete ;; + update) just deps && just validate ;; + *) echo "Unknown: {{task}}. Use: all, cleanup, update" && exit 1 ;; + esac + +# ═══════════════════════════════════════════════════════════════════════════════ +# COMBINATORIC MATRIX RECIPES +# ═══════════════════════════════════════════════════════════════════════════════ + +# Build matrix: [debug|release] x [target] x [features] +build-matrix mode="debug" target="" features="": + @echo "Build matrix: mode={{mode}} target={{target}} features={{features}}" + +# Test matrix: [unit|integration|e2e|all] x [verbosity] x [parallel] +test-matrix suite="unit" verbosity="normal" parallel="true": + @echo "Test matrix: suite={{suite}} verbosity={{verbosity}} parallel={{parallel}}" + +# Container matrix: [build|run|push|shell|scan] x [registry] x [tag] +container-matrix action="build" registry="ghcr.io/{{OWNER}}" tag="latest": + @echo "Container matrix: action={{action}} registry={{registry}} tag={{tag}}" + +# CI matrix: [lint|test|build|security|all] x [quick|full] +ci-matrix stage="all" depth="quick": + @echo "CI matrix: stage={{stage}} depth={{depth}}" + +# Show all matrix combinations +combinations: + @echo "=== Combinatoric Matrix Recipes ===" + @echo "" + @echo "Build Matrix: just build-matrix [debug|release] [target] [features]" + @echo "Test Matrix: just test-matrix [unit|integration|e2e|all] [verbosity] [parallel]" + @echo "Container: just container-matrix [build|run|push|shell|scan] [registry] [tag]" + @echo "CI Matrix: just ci-matrix [lint|test|build|security|all] [quick|full]" + +# ═══════════════════════════════════════════════════════════════════════════════ +# VERSION CONTROL +# ═══════════════════════════════════════════════════════════════════════════════ + +# Show git status +status: + @git status --short + +# Show recent commits +log count="20": + @git log --oneline -{{count}} + +# Generate CHANGELOG.md with git-cliff +changelog: + @command -v git-cliff >/dev/null || { echo "git-cliff not found — install: cargo install git-cliff"; exit 1; } + git cliff --config .machine_readable/configs/git-cliff/cliff.toml --output CHANGELOG.md + @echo "Generated CHANGELOG.md" + +# Preview changelog for unreleased commits (does not write) +changelog-preview: + @command -v git-cliff >/dev/null || { echo "git-cliff not found — install: cargo install git-cliff"; exit 1; } + git cliff --config .machine_readable/configs/git-cliff/cliff.toml --unreleased --strip header + +# Tag a new release (usage: just release-tag 1.2.3) +release-tag version: + #!/usr/bin/env bash + TAG="v{{version}}" + if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "Tag $TAG already exists" + exit 1 + fi + just changelog + git add CHANGELOG.md + git commit -m "chore(release): prepare $TAG" + git tag -a "$TAG" -m "Release $TAG" + echo "Created tag $TAG — push with: git push origin main --tags" + +# ═══════════════════════════════════════════════════════════════════════════════ +# UTILITIES +# ═══════════════════════════════════════════════════════════════════════════════ + +# Count lines of code +loc: + @find . \( -name "*.rs" -o -name "*.ex" -o -name "*.exs" -o -name "*.res" -o -name "*.gleam" -o -name "*.zig" -o -name "*.idr" -o -name "*.hs" -o -name "*.ncl" -o -name "*.scm" -o -name "*.adb" -o -name "*.ads" \) -not -path './target/*' -not -path './_build/*' 2>/dev/null | xargs wc -l 2>/dev/null | tail -1 || echo "0" + +# Show TODO comments +todos: + @grep -rn "TODO\|FIXME\|HACK\|XXX" --include="*.rs" --include="*.ex" --include="*.res" --include="*.gleam" --include="*.zig" --include="*.idr" --include="*.hs" . 2>/dev/null || echo "No TODOs" + +# Open in editor +edit: + ${EDITOR:-code} . + +# Run high-rigor security assault using panic-attacker +maint-assault: + @./.machine_readable/scripts/maintenance/maint-assault.sh + +# Run panic-attacker pre-commit scan (foundational floor-raise requirement) +assail: + @command -v panic-attack >/dev/null 2>&1 && panic-attack assail . || echo "WARN: panic-attack not found — install from https://github.com/hyperpolymath/panic-attacker" + + +# Self-diagnostic — checks dependencies, permissions, paths +doctor: + @echo "Running diagnostics for rsr-template-repo..." + @echo "Checking required tools..." + @command -v just >/dev/null 2>&1 && echo " [OK] just" || echo " [FAIL] just not found" + @command -v git >/dev/null 2>&1 && echo " [OK] git" || echo " [FAIL] git not found" + @echo "Checking for hardcoded paths..." + @grep -rn '$HOME\|$ECLIPSE_DIR' --include='*.rs' --include='*.ex' --include='*.res' --include='*.gleam' --include='*.sh' . 2>/dev/null | head -5 || echo " [OK] No hardcoded paths" + @echo "Diagnostics complete." + +# Guided tour of key features +tour: + @echo "=== rsr-template-repo Tour ===" + @echo "" + @echo "1. Project structure:" + @ls -la + @echo "" + @echo "2. Available commands: just --list" + @echo "" + @echo "3. Read README.adoc for full overview" + @echo "4. Read EXPLAINME.adoc for architecture decisions" + @echo "5. Run 'just doctor' to check your setup" + @echo "" + @echo "Tour complete! Try 'just --list' to see all available commands." + +# Open feedback channel with diagnostic context +help-me: + @echo "=== rsr-template-repo Help ===" + @echo "Platform: $(uname -s) $(uname -m)" + @echo "Shell: $SHELL" + @echo "" + @echo "To report an issue:" + @echo " https://github.com/hyperpolymath/rsr-template-repo/issues/new" + @echo "" + @echo "Include the output of 'just doctor' in your report." + +# ═══════════════════════════════════════════════════════════════════════════════ +# FORMAL VERIFICATION (PROOFS) +# ═══════════════════════════════════════════════════════════════════════════════ + +# Check all formal proofs (Idris2 + Lean4 + Agda + Coq) +proof-check-all: proof-check-idris2 proof-check-lean4 proof-check-agda proof-check-coq proof-scan-dangerous + @echo "=== All proof checks complete ===" + +# Check Idris2 proofs (ABI, types, dependent type proofs) +proof-check-idris2: + #!/usr/bin/env bash + set -euo pipefail + echo "=== Checking Idris2 proofs ===" + if ! command -v idris2 &>/dev/null; then + echo "SKIP: idris2 not installed" + exit 0 + fi + ERRORS=0 + for f in $(find verification/proofs/idris2 -name '*.idr' 2>/dev/null); do + echo -n " Checking $f ... " + if idris2 --check "$f" 2>/dev/null; then + echo "OK" + else + echo "FAIL" + ERRORS=$((ERRORS + 1)) + fi + done + if [ "$ERRORS" -gt 0 ]; then + echo "FAIL: $ERRORS Idris2 proof(s) failed" + exit 1 + fi + echo "PASS: All Idris2 proofs verified" + +# Check Lean4 proofs +proof-check-lean4: + #!/usr/bin/env bash + set -euo pipefail + echo "=== Checking Lean4 proofs ===" + if ! command -v lean &>/dev/null; then + echo "SKIP: lean not installed" + exit 0 + fi + ERRORS=0 + for f in $(find verification/proofs/lean4 -name '*.lean' 2>/dev/null); do + echo -n " Checking $f ... " + if lean "$f" 2>/dev/null; then + echo "OK" + else + echo "FAIL" + ERRORS=$((ERRORS + 1)) + fi + done + if [ "$ERRORS" -gt 0 ]; then + echo "FAIL: $ERRORS Lean4 proof(s) failed" + exit 1 + fi + echo "PASS: All Lean4 proofs verified" + +# Check Agda proofs +proof-check-agda: + #!/usr/bin/env bash + set -euo pipefail + echo "=== Checking Agda proofs ===" + if ! command -v agda &>/dev/null; then + echo "SKIP: agda not installed" + exit 0 + fi + ERRORS=0 + for f in $(find verification/proofs/agda -name '*.agda' 2>/dev/null); do + echo -n " Checking $f ... " + if agda --safe "$f" 2>/dev/null; then + echo "OK" + else + echo "FAIL" + ERRORS=$((ERRORS + 1)) + fi + done + if [ "$ERRORS" -gt 0 ]; then + echo "FAIL: $ERRORS Agda proof(s) failed" + exit 1 + fi + echo "PASS: All Agda proofs verified" + +# Check Coq proofs +proof-check-coq: + #!/usr/bin/env bash + set -euo pipefail + echo "=== Checking Coq proofs ===" + if ! command -v coqc &>/dev/null; then + echo "SKIP: coqc not installed" + exit 0 + fi + ERRORS=0 + for f in $(find verification/proofs/coq -name '*.v' 2>/dev/null); do + echo -n " Checking $f ... " + if coqc "$f" 2>/dev/null; then + echo "OK" + else + echo "FAIL" + ERRORS=$((ERRORS + 1)) + fi + done + if [ "$ERRORS" -gt 0 ]; then + echo "FAIL: $ERRORS Coq proof(s) failed" + exit 1 + fi + echo "PASS: All Coq proofs verified" + +# Scan for dangerous patterns in proof files (believe_me, sorry, Admitted, etc.) +proof-scan-dangerous: + #!/usr/bin/env bash + set -euo pipefail + echo "=== Scanning for dangerous patterns in proofs ===" + DANGEROUS=0 + PATTERNS="believe_me|assert_total|postulate|sorry|Admitted|unsafeCoerce|Obj\.magic" + for f in $(find verification/proofs -name '*.idr' -o -name '*.lean' -o -name '*.agda' -o -name '*.v' 2>/dev/null); do + MATCHES=$(grep -nE "$PATTERNS" "$f" 2>/dev/null || true) + if [ -n "$MATCHES" ]; then + echo " DANGEROUS: $f" + echo "$MATCHES" | sed 's/^/ /' + DANGEROUS=$((DANGEROUS + 1)) + fi + done + if [ "$DANGEROUS" -gt 0 ]; then + echo "FAIL: $DANGEROUS file(s) contain dangerous patterns" + exit 1 + fi + echo "PASS: No dangerous patterns found in proofs" + +# Show proof status summary +proof-status: + #!/usr/bin/env bash + echo "=== Proof Status ===" + echo "" + echo "Idris2: $(find verification/proofs/idris2 -name '*.idr' 2>/dev/null | wc -l) files" + echo "Lean4: $(find verification/proofs/lean4 -name '*.lean' 2>/dev/null | wc -l) files" + echo "Agda: $(find verification/proofs/agda -name '*.agda' 2>/dev/null | wc -l) files" + echo "Coq: $(find verification/proofs/coq -name '*.v' 2>/dev/null | wc -l) files" + echo "TLA+: $(find verification/proofs/tlaplus -name '*.tla' 2>/dev/null | wc -l) files" + echo "" + if [ -f PROOF-STATUS.md ]; then + grep -E "^\| \*\*Total\*\*" PROOF-STATUS.md 2>/dev/null || echo "(No summary row in PROOF-STATUS.md)" + else + echo "(No PROOF-STATUS.md found)" + fi + +# ═══════════════════════════════════════════════════════════════════════════════ +# SESSION MANAGEMENT (THIN BINDINGS TO CENTRAL STANDARDS) +# ═══════════════════════════════════════════════════════════════════════════════ + +# Show canonical session-management command model +session-help: + @echo "Canonical command model:" + @echo " intake repo " + @echo " checkpoint change " + @echo " verify maintenance " + @echo " verify substantial " + @echo " verify release " + @echo " close planned " + @echo " close urgent " + @echo " recover repo " + @echo " handover full " + @echo " handover split " + @echo " handover model " + @echo " handover human " + @echo "" + @echo "Use Just aliases below (thin wrappers around ./session/dispatch.sh)." + +# Canonical aliases (friendly recipe names that map to canonical commands) +intake-repo path=".": + @./session/dispatch.sh intake repo "{{path}}" + +checkpoint-change path=".": + @./session/dispatch.sh checkpoint change "{{path}}" + +verify-maintenance path=".": + @./session/dispatch.sh verify maintenance "{{path}}" + +verify-substantial path=".": + @./session/dispatch.sh verify substantial "{{path}}" + +verify-release path=".": + @./session/dispatch.sh verify release "{{path}}" + +close-planned path=".": + @./session/dispatch.sh close planned "{{path}}" + +close-urgent path=".": + @./session/dispatch.sh close urgent "{{path}}" + +recover-repo path=".": + @./session/dispatch.sh recover repo "{{path}}" + +handover-full path=".": + @./session/dispatch.sh handover full "{{path}}" + +handover-split path=".": + @./session/dispatch.sh handover split "{{path}}" + +handover-model path=".": + @./session/dispatch.sh handover model "{{path}}" + +handover-human path=".": + @./session/dispatch.sh handover human "{{path}}" diff --git a/.machine_readable/contractiles/Mustfile.a2ml b/.machine_readable/contractiles/Mustfile.a2ml new file mode 100644 index 0000000..55f8ab4 --- /dev/null +++ b/.machine_readable/contractiles/Mustfile.a2ml @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: MPL-2.0 +# Mustfile — Physical state contract for rsr-template-repo +# Author: Jonathan D.A. Jewell +# +# What MUST be true about this repository. Hard requirements. +# Run with: must check +# Fix with: must fix (where a deterministic fix exists) + +@abstract: +Physical-state invariants for rsr-template-repo. This is the canonical +RSR template repository. These are hard requirements — CI and pre-commit +hooks fail if any check fails. +@end + +## File Presence + +### license-present +- description: LICENSE file must exist +- run: test -f LICENSE +- severity: critical + +### readme-present +- description: README.adoc must exist +- run: test -f README.adoc +- severity: critical + +### security-policy +- description: SECURITY.md must exist +- run: test -f SECURITY.md +- severity: critical + +### ai-manifest +- description: 0-AI-MANIFEST.a2ml must exist +- run: test -f 0-AI-MANIFEST.a2ml +- severity: critical + +### governance-docs +- description: GOVERNANCE.adoc, MAINTAINERS.adoc, CODEOWNERS must exist +- run: test -f GOVERNANCE.adoc && test -f MAINTAINERS.adoc && test -f .github/CODEOWNERS +- severity: critical + +### machine-readable-dir +- description: .machine_readable/ directory must exist +- run: test -d .machine_readable +- severity: critical + +## Directory Structure + +### contractiles-complete +- description: All required contractile directories exist +- run: test -d .machine_readable/contractiles && test -d .machine_readable/contractiles/bust && test -d .machine_readable/contractiles/dust +- severity: critical + +### contractiles-files-present +- description: All four primary contractile files exist +- run: test -f .machine_readable/contractiles/Intentfile.a2ml && test -f .machine_readable/contractiles/Mustfile.a2ml && test -f .machine_readable/contractiles/Trustfile.a2ml && test -f .machine_readable/contractiles/Adjustfile.a2ml +- severity: critical + +### bust-dust-files-present +- description: Bustfile and Dustfile exist in their directories +- run: test -f .machine_readable/contractiles/bust/Bustfile.a2ml && test -f .machine_readable/contractiles/dust/Dustfile.a2ml +- severity: critical + +### six-directory-present +- description: 6a2 directory exists with required files +- run: test -d .machine_readable/6a2 && test -f .machine_readable/6a2/META.a2ml && test -f .machine_readable/6a2/ECOSYSTEM.a2ml && test -f .machine_readable/6a2/STATE.a2ml && test -f .machine_readable/6a2/PLAYBOOK.a2ml && test -f .machine_readable/6a2/AGENTIC.a2ml && test -f .machine_readable/6a2/NEUROSYM.a2ml +- severity: critical + +### anchors-directory +- description: anchors directory exists in 6a2 +- run: test -d .machine_readable/6a2/anchors +- severity: warning + +### self-validating-structure +- description: self-validating directory has k9-svc and examples +- run: test -d .machine_readable/self-validating && test -d .machine_readable/self-validating/k9-svc && test -d .machine_readable/self-validating/examples +- severity: warning + +## Template Integrity + +### no-placeholder-values +- description: No placeholder values remain in template files +- run: test -z "$(grep -r '{{' .machine_readable/contractiles/ 2>/dev/null)" +- severity: critical +- notes: All placeholders must be substituted when copying this template + +### template-readonly +- description: Template marker files are not modified +- run: grep -q 'RSR_TEMPLATE_DO_NOT_EDIT' .machine_readable/0.1-AI-MANIFEST.a2ml +- severity: warning + +## Git State + +### no-untracked-contractiles +- description: All contractile files are tracked in git +- run: test -z "$(git ls-files -o --exclude-standard .machine_readable/contractiles/ 2>/dev/null)" +- severity: critical + +### signed-commits +- description: All commits must be signed +- run: git verify-commit HEAD +- severity: critical diff --git a/.machine_readable/contractiles/Trustfile.a2ml b/.machine_readable/contractiles/Trustfile.a2ml new file mode 100644 index 0000000..e2028b5 --- /dev/null +++ b/.machine_readable/contractiles/Trustfile.a2ml @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: MPL-2.0 +# Trustfile — Trust boundaries and integrity invariants for rsr-template-repo +# Author: Jonathan D.A. Jewell +# +# Defines what LLM/SLM agents are trusted to do without asking, and +# integrity invariants that verify the repo has not been tampered with. + +@abstract: +Trust boundaries and integrity checks for rsr-template-repo. This file +combines the trust-level definitions from the original TRUST.contractile +with the integrity invariants from the old Trustfile.a2ml. It defines +what AI agents may do autonomously and what requires human approval, +plus checks that verify repository integrity. +@end + +## Trust Levels + +The rsr-template-repo operates at trust level: maximal + +Trust levels: +- maximal: Agent may read, build, test, lint, format, heal freely. + Only destructive/external actions require approval. +- standard: Agent may read and build. Test/lint need approval. +- restricted: Agent may read only. All modifications need approval. +- minimal: Agent may read specific files only. Everything else blocked. + +Current trust level: maximal + +## Integrity Invariants + +### Secrets + +#### no-secrets-committed +- description: No credential files in repo +- run: test ! -f .env && test ! -f credentials.json && test ! -f .env.local && test ! -f .env.production +- severity: critical + +#### no-private-keys +- description: No private key files committed +- run: "! find . -name '*.pem' -o -name '*.key' -o -name 'id_rsa' -o -name 'id_ed25519' 2>/dev/null | grep -v node_modules | head -1 | grep -q ." +- severity: critical + +#### no-tokens-in-source +- description: No hardcoded API tokens in source +- run: "! grep -rE '(api[_-]?key|secret|token|password)\s*[:=]\s*[\"'\\''][A-Za-z0-9]{16,}' --include='*.js' --include='*.ts' --include='*.res' --include='*.py' . 2>/dev/null | grep -v node_modules | head -1 | grep -q ." +- severity: critical + +## Provenance + +#### author-correct +- description: Git author matches expected identity +- run: "git log -1 --format='%ae' | grep -qE '(hyperpolymath|j\\.d\\.a\\.jewell)'" +- severity: warning + +#### license-content +- description: LICENSE contains expected identifier +- run: grep -q 'PMPL\|MPL\|MIT\|Apache\|LGPL' LICENSE +- severity: warning + +## Template-Specific Trust + +### template-files-readonly +- description: Template scaffold files should not be modified except by maintainer +- run: test -z "$(git status --short .machine_readable/ 2>/dev/null | grep -v '^??' || true)" +- severity: advisory +- notes: Changes to template files require careful review + +### trust-deny-areas +- description: Sensitive areas from INTENT.contractile require explicit approval +- run: echo "Check .machine_readable/ contractiles and governance docs" +- severity: advisory +- areas: + - .machine_readable/ + - GOVERNANCE.adoc + - MAINTAINERS.adoc + - .github/CODEOWNERS + +## Container Security + +#### container-images-pinned +- description: Containerfile uses pinned base images +- run: test ! -f Containerfile || grep -q 'cgr.dev\|@sha256:' Containerfile +- severity: warning + +#### no-dockerfile +- description: No Dockerfile (use Containerfile) +- run: test ! -f Dockerfile +- severity: warning diff --git a/.machine_readable/contractiles/dust/Dustfile.a2ml b/.machine_readable/contractiles/dust/Dustfile.a2ml deleted file mode 100644 index be38a8c..0000000 --- a/.machine_readable/contractiles/dust/Dustfile.a2ml +++ /dev/null @@ -1,44 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Dustfile — Cleanup and hygiene contract -# Author: Jonathan D.A. Jewell - -@abstract: -What should be cleaned up or removed from this repository. -These are housekeeping items, not blockers. -@end - -## Stale Files - -### no-stale-snapshots -- description: No dated status/completion files in root -- run: "! ls *-STATUS-*.md *-COMPLETION-*.md *-COMPLETE.md *-VERIFIED-*.md 2>/dev/null | head -1 | grep -q ." -- severity: info - -### no-ai-djot -- description: AI.djot is superseded by 0-AI-MANIFEST.a2ml -- run: test ! -f AI.djot -- severity: warning - -### no-next-steps -- description: NEXT_STEPS.md superseded by ROADMAP -- run: test ! -f NEXT_STEPS.md -- severity: info - -## Build Artifacts - -### no-tracked-artifacts -- description: No build artifacts tracked in git -- run: "! git ls-files lib/bs/ lib/ocaml/ target/release/ _build/ 2>/dev/null | head -1 | grep -q ." -- severity: warning - -## Format Duplicates - -### no-duplicate-contributing -- description: Only one CONTRIBUTING format (keep .md) -- run: "! (test -f CONTRIBUTING.md && test -f CONTRIBUTING.adoc)" -- severity: warning - -### no-duplicate-readme -- description: Only one README format -- run: "! (test -f README.md && test -f README.adoc && [ $(wc -l < README.md) -gt 5 ])" -- severity: warning diff --git a/.machine_readable/contractiles/intend/Intentfile.a2ml b/.machine_readable/contractiles/intend/Intentfile.a2ml deleted file mode 100644 index f75d38e..0000000 --- a/.machine_readable/contractiles/intend/Intentfile.a2ml +++ /dev/null @@ -1,37 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Intentfile — Design intent and aspirations -# Author: Jonathan D.A. Jewell - -@abstract: -What this repository INTENDS to become. Aspirational goals and -design philosophy — not current state, but target state. -@end - -## Architecture Intent - -### formal-verification -- description: All critical code paths should have formal proofs -- target: Idris2 dependent types for ABI, Coq/Lean for algorithms -- status: aspiration - -### reproducible-builds -- description: Builds should be bit-for-bit reproducible -- target: Guix + Nix + Containerfile -- status: aspiration - -### zero-dangerous-patterns -- description: No believe_me, sorry, Admitted, unsafeCoerce in any code -- target: All proofs completed, no escape hatches -- status: in-progress - -## Quality Intent - -### comprehensive-testing -- description: 80%+ code coverage with meaningful tests -- target: Unit + integration + conformance + property-based -- status: aspiration - -### documentation-complete -- description: Every public API documented, every directory has README -- target: Full API reference + architecture guide -- status: in-progress diff --git a/.machine_readable/contractiles/must/Mustfile.a2ml b/.machine_readable/contractiles/must/Mustfile.a2ml deleted file mode 100644 index 215c510..0000000 --- a/.machine_readable/contractiles/must/Mustfile.a2ml +++ /dev/null @@ -1,69 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Mustfile — Physical state contract -# Author: Jonathan D.A. Jewell - -@abstract: -What MUST be true about this repository's files and configuration. -These are hard requirements — CI fails if any check fails. -@end - -## File Presence - -### license-present -- description: LICENSE file must exist -- run: test -f LICENSE -- severity: critical - -### readme-present -- description: README.adoc or README.md must exist -- run: test -f README.adoc || test -f README.md -- severity: critical - -### security-policy -- description: SECURITY.md must exist -- run: test -f SECURITY.md -- severity: critical - -### ai-manifest -- description: 0-AI-MANIFEST.a2ml must exist -- run: test -f 0-AI-MANIFEST.a2ml -- severity: critical - -### contributing -- description: CONTRIBUTING.md must exist (GitHub community health) -- run: test -f CONTRIBUTING.md -- severity: warning - -### editorconfig -- description: .editorconfig must exist -- run: test -f .editorconfig -- severity: warning - -## SPDX Compliance - -### spdx-headers -- description: All source files must have SPDX-License-Identifier -- run: "! find src/ -name '*.rs' -o -name '*.res' -o -name '*.idr' -o -name '*.zig' 2>/dev/null | head -20 | xargs grep -L 'SPDX-License-Identifier' 2>/dev/null | head -1 | grep -q ." -- severity: warning - -### no-agpl -- description: No AGPL-3.0 references in dotfiles -- run: "! grep -r 'AGPL-3.0' .gitignore .gitattributes .editorconfig 2>/dev/null | head -1 | grep -q ." -- severity: critical - -## Dangerous Patterns - -### no-believe-me -- description: No believe_me in Idris2 code -- run: "! grep -r 'believe_me' --include='*.idr' . 2>/dev/null | grep -v node_modules | head -1 | grep -q ." -- severity: critical - -### no-sorry -- description: No sorry in Lean code -- run: "! grep -r 'sorry' --include='*.lean' . 2>/dev/null | grep -v node_modules | head -1 | grep -q ." -- severity: critical - -### no-admitted -- description: No Admitted in Coq code -- run: "! grep -r 'Admitted' --include='*.v' . 2>/dev/null | grep -v node_modules | head -1 | grep -q ." -- severity: critical diff --git a/.machine_readable/contractiles/trust/Trustfile.a2ml b/.machine_readable/contractiles/trust/Trustfile.a2ml deleted file mode 100644 index 2e583a3..0000000 --- a/.machine_readable/contractiles/trust/Trustfile.a2ml +++ /dev/null @@ -1,74 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Trustfile — Integrity and provenance verification -# Author: Jonathan D.A. Jewell - -@abstract: -Integrity invariants for this repository. These verify that the repo -has not been tampered with, secrets are not leaked, and provenance -is traceable. -@end - -## Secrets - -### no-secrets-committed -- description: No credential files in repo -- run: test ! -f .env && test ! -f credentials.json && test ! -f .env.local && test ! -f .env.production -- severity: critical - -### no-private-keys -- description: No private key files committed -- run: "! find . -name '*.pem' -o -name '*.key' -o -name 'id_rsa' -o -name 'id_ed25519' 2>/dev/null | grep -v node_modules | head -1 | grep -q ." -- severity: critical - -### no-tokens-in-source -- description: No hardcoded API tokens in source -- run: "! grep -rE '(api[_-]?key|secret|token|password)\s*[:=]\s*[\"'\\''][A-Za-z0-9]{16,}' --include='*.js' --include='*.ts' --include='*.res' --include='*.py' . 2>/dev/null | grep -v node_modules | head -1 | grep -q ." -- severity: critical - -## Provenance - -### author-correct -- description: Git author matches expected identity -- run: "git log -1 --format='%ae' | grep -qE '(hyperpolymath|j\\.d\\.a\\.jewell)'" -- severity: warning - -### license-content -- description: LICENSE contains expected identifier -- run: grep -q 'PMPL\|MPL\|MIT\|Apache\|LGPL' LICENSE -- severity: warning - -## Container Security - -### container-images-pinned -- description: Containerfile uses pinned base images -- run: test ! -f Containerfile || grep -q 'cgr.dev\|@sha256:' Containerfile -- severity: warning - -### no-dockerfile -- description: No Dockerfile (use Containerfile) -- run: test ! -f Dockerfile -- severity: warning - -## Dangerous Patterns - -### no-believe-me -- description: No believe_me, assert_total, Admitted, sorry, unsafeCoerce, Obj.magic -- run: "! grep -rE 'believe_me|assert_total|Admitted|sorry|unsafeCoerce|Obj\\.magic' --include='*.idr' --include='*.lean' --include='*.v' --include='*.ml' --include='*.hs' . 2>/dev/null | head -1 | grep -q ." -- severity: critical - -### no-unsafe-without-comment -- description: All unsafe blocks in Rust must have SAFETY comments -- run: "! grep -B1 'unsafe {' --include='*.rs' -r . 2>/dev/null | grep -v SAFETY | grep 'unsafe {' | head -1 | grep -q ." -- severity: warning - -## Service Security (if applicable) - -### localhost-only-bindings -- description: Backend services bind to 127.0.0.1 only -- run: "! grep -rE 'bind\\(\"0\\.0\\.0\\.0' --include='*.rs' --include='*.ex' --include='*.ts' . 2>/dev/null | head -1 | grep -q ." -- severity: critical - -### coordination-file-permissions -- description: Coordination/session files are owner-only (0600) -- run: test ! -d ~/.claude/coordination || find ~/.claude/coordination -type f ! -perm 600 2>/dev/null | wc -l | grep -q '^0$' -- severity: warning diff --git a/GOVERNANCE.adoc b/GOVERNANCE.adoc new file mode 100644 index 0000000..8bbf167 --- /dev/null +++ b/GOVERNANCE.adoc @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Governance Model +:toc: preamble + +This document describes the governance model for this repository. + +== Overview + +This repository follows a **Sole Maintainer Governance Model**: + +* Single maintainer (@hyperpolymath) has full authority over the project +* All contributions are welcome and reviewed by the maintainer +* Decisions are made transparently through GitHub issues and discussions +* The project adheres to the hyperpolymath estate policies where applicable + +== Core Principles + +[cols="1,2"] +|=== +| Principle | Description + +| **Benevolent Dictatorship** | Maintainer has final decision authority but seeks community input + +| **Meritocracy** | Contributions are judged on technical merit, not contributor identity + +| **Transparency** | All significant decisions are documented publicly + +| **Consensus-Seeking** | Maintainer prefers consensus but will decide when necessary + +| **Open Contribution** | Anyone can contribute via fork and pull request + +|=== + +== Roles and Permissions + +[cols="1,2,2"] +|=== +| Role | Permissions | Assignment + +| **Maintainer** | Write access, merge rights, admin | @hyperpolymath +| **Contributors** | Read access, fork, submit PRs | All GitHub users +| **Users** | Use the software, report issues | All GitHub users + +|=== + +== Decision Making Framework + +=== Routine Decisions + +* Bug fixes +* Documentation improvements +* Minor feature additions +* Dependency updates + +**Process**: Maintainer reviews and merges PRs that meet quality standards. + +=== Significant Changes + +* New major features +* API changes +* Architecture modifications +* Breaking changes + +**Process**: +. Open issue describing the change +. Discuss with community (minimum 72 hours) +. Maintainer makes final decision +. Document rationale in issue/PR + +=== Structural Decisions + +* Repository purpose/renaming +* License changes +* Ownership transfer +* Deprecation/archival + +**Process**: +. Extended discussion (minimum 1 week) +. Maintainer makes final decision +. Document in CHANGELOG and governance docs + +== Contribution Lifecycle + +[cols="1,2"] +|=== +| Stage | Process + +| **Ideation** | Open issue, discuss feasibility + +| **Development** | Fork, implement, test thoroughly + +| **Review** | Submit PR, maintainer reviews within 7 days + +| **Merge** | Maintainer merges or requests changes + +| **Release** | Maintainer publishes according to project conventions + +|=== + +== Conflict Resolution + +In case of disagreements: + +. Discuss in the relevant GitHub issue or PR +. Provide technical justification for positions +. Maintainer mediates and makes final decision +. Decision is documented and can be revisited later + +== Project Policies + +This repository adheres to hyperpolymath estate-wide policies: + +* **License**: MPL-2.0 for code, CC-BY-SA-4.0 for prose (per standards/LICENCE-POLICY.adoc) +* **Code of Conduct**: Follows hyperpolymath CODE_OF_CONDUCT.md +* **Security**: Follows hyperpolymath SECURITY.md +* **Contributing**: Follows hyperpolymath CONTRIBUTING.adoc conventions + +== Repository-Specific Conventions + +[cols="1,2"] +|=== +| Convention | Description + +| **Signing** | All commits must be signed (SSH or GPG) + +| **SPDX Headers** | All source files must have SPDX license identifiers + +| **Contractiles** | Mustfile, Trustfile, Intendfile, Adjustfile in root + +| **Machine Readable** | META.a2ml in .machine_readable/6a2/ + +| **CI/CD** | GitHub Actions workflows in .github/workflows/ + +|=== + +== Governance Evolution + +As the project grows, this governance model may evolve: + +* **Adding Co-Maintainers**: When contribution volume warrants it +* **Forming a Team**: For complex multi-maintainer projects +* **Adopting TPCF**: For large, multi-repository projects (see rhodium-standard-repositories) + +Changes to this document require the same process as Significant Changes above. + +== See Also + +* link:MAINTAINERS.adoc[Maintainers] +* link:CODE_OF_CONDUCT.md[Code of Conduct] +* link:CONTRIBUTING.adoc[Contributing Guide] +* link:https://github.com/hyperpolymath/standards/blob/main/LICENCE-POLICY.adoc[Estate License Policy] +* link:https://github.com/hyperpolymath/standards[rhodium-standard-repositories (TPCF)] + +== Changelog + +[cols="1,1,1"] +|=== +| Date | Change | By + +| 2026-06-07 | Initial governance model established | @hyperpolymath +|=== diff --git a/MAINTAINERS.adoc b/MAINTAINERS.adoc new file mode 100644 index 0000000..9910dd8 --- /dev/null +++ b/MAINTAINERS.adoc @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Maintainers +:toc: preamble + +== Current Maintainers + +[cols="2,3,2",options="header"] +|=== +| Name | Role | Contact + +| Jonathan D.A. Jewell | Sole Maintainer | https://github.com/hyperpolymath[@hyperpolymath] +|=== + +== Maintainer Responsibilities + +As the sole maintainer, all responsibilities apply to @hyperpolymath: + +* Reviewing and merging pull requests +* Triaging issues and feature requests +* Ensuring code quality and security standards +* Managing releases and versioning +* Upholding the project's Code of Conduct +* Maintaining documentation and examples +* Responding to security vulnerabilities + +== Contribution Process + +This is a sole-maintainer project. All contributions are welcome via: + +1. **Issues**: Report bugs, request features, ask questions +2. **Pull Requests**: Submit improvements for review +3. **Discussions**: Engage in community discussions + +All contributions will be reviewed by the maintainer. + +== Decision Making + +* Routine decisions (bug fixes, minor improvements): Made by maintainer +* Significant changes: Discussed in issues before implementation +* Breaking changes: Announced in advance with migration path + +== Becoming a Maintainer + +This project currently has a single maintainer. If you're interested in becoming a co-maintainer: + +1. Demonstrate consistent, high-quality contributions +2. Show understanding of project goals and standards +3. Participate constructively in discussions +4. Express interest to the current maintainer + +Co-maintainers may be added at the discretion of the current maintainer. + +== Contact + +For questions about project governance: + +* Open a GitHub issue in this repository +* Contact: https://github.com/hyperpolymath + +== See Also + +* link:GOVERNANCE.adoc[Governance Model] +* link:CODE_OF_CONDUCT.md[Code of Conduct] +* link:CONTRIBUTING.adoc[Contributing Guide] From e4c7fb63203aedac4f6533ebd132171b46acb71c Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 8 Jun 2026 00:59:41 +0100 Subject: [PATCH 2/6] Add missing bust/dust contractiles --- .../contractiles/bust/Bustfile.a2ml | 52 ++++++++++++++++ .../contractiles/dust/Dustfile.a2ml | 62 +++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 .machine_readable/contractiles/bust/Bustfile.a2ml create mode 100644 .machine_readable/contractiles/dust/Dustfile.a2ml diff --git a/.machine_readable/contractiles/bust/Bustfile.a2ml b/.machine_readable/contractiles/bust/Bustfile.a2ml new file mode 100644 index 0000000..acd8c91 --- /dev/null +++ b/.machine_readable/contractiles/bust/Bustfile.a2ml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: MPL-2.0 +# Bustfile — failure mode contractile for rattlescript +# Author: Jonathan D.A. Jewell +# +# Paired runner: bust.ncl +# Verb: bust +# Semantics: Every declared failure mode must have a working recovery path +# that has been exercised. Status moves: +# declared → drilled (probe run) → verified (recovery confirmed) +# or → failing (recovery broken) +# +# CLI: +# contractile bust check → list failure modes + recovery status +# contractile bust drill → inject failures, verify recovery paths +# +# This repository: rattlescript is the canonical template for RSR compliance. +# Failure modes here relate to template distribution and substitution. + +@abstract: +Bustfile for rattlescript. Lists failure modes specific to the template +repository itself, particularly around template distribution, substitution, +and synchronization across the hyperpolymath estate. +@end + +## Failure Modes + +### template-substitution-failure +- class: template_processing +- description: Template substitution fails when initializing a new repo from this template +- injection_probe: "cp -r rattlescript test-repo && cd test-repo && sed -i 's/rattlescript/TEST/g' .machine_readable/contractiles/Intentfile.a2ml && grep -q 'TEST' .machine_readable/contractiles/Intentfile.a2ml" +- recovery_probe: "git -C test-repo diff --quiet .machine_readable/contractiles/Intentfile.a2ml" +- expected_recovery_time_seconds: 10 +- status: declared +- notes: Verify that substitution scripts handle all placeholder replacements correctly + +### sync-drift-between-repos +- class: synchronization +- description: Drift occurs between rattlescript and other repos after template updates +- injection_probe: "echo 'template_updated' > /tmp/test_drift_marker" +- recovery_probe: "test -f /tmp/test_drift_marker && rm /tmp/test_drift_marker" +- expected_recovery_time_seconds: 60 +- status: declared +- notes: The estate-wide sync scripts (see scripts/) should prevent this; verify with scripts/verify-sync.sh + +### contractile-parse-error +- class: contractile_format +- description: A contractile file fails to parse due to syntax errors +- injection_probe: "echo 'invalid syntax' >> rattlescript/.machine_readable/contractiles/Intentfile.a2ml" +- recovery_probe: "git checkout rattlescript/.machine_readable/contractiles/Intentfile.a2ml" +- expected_recovery_time_seconds: 5 +- status: declared +- notes: All .a2ml files should be valid A2ML; use a2ml-validate runner diff --git a/.machine_readable/contractiles/dust/Dustfile.a2ml b/.machine_readable/contractiles/dust/Dustfile.a2ml new file mode 100644 index 0000000..3272fc2 --- /dev/null +++ b/.machine_readable/contractiles/dust/Dustfile.a2ml @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: MPL-2.0 +# Dustfile — Cleanup and hygiene contract for rattlescript +# Author: Jonathan D.A. Jewell +# +# Paired runner: dust.ncl +# Verb: dust +# Semantics: What should be cleaned up. Housekeeping, not blockers. +# +# This repository: rattlescript is the canonical template. +# Cleanup items here ensure the template itself remains pristine. + +@abstract: +Cleanup and hygiene items for rattlescript. These are maintenance tasks +that ensure the template repository remains clean and ready for distribution +to new repositories. +@end + +## Stale Files + +### no-template-artifacts +- description: No generated files from template testing in root +- run: test -z "$(ls template-test-* 2>/dev/null)" +- severity: info +- notes: Template testing should use /tmp or dedicated test directories + +### no-example-placeholders +- description: No example placeholder files (EXAMPLE-, SAMPLE-) in contractiles/ +- run: test -z "$(find .machine_readable/contractiles/ -name 'EXAMPLE-*' -o -name 'SAMPLE-*' 2>/dev/null)" +- severity: warning +- notes: All placeholders should be replaced with actual content or removed + +### no-old-contractile-formats +- description: No old .contractile or .hs files remaining +- run: test -z "$(find .machine_readable/contractiles/ \( -name '*.contractile' -o -name '*.hs' \) 2>/dev/null)" +- severity: warning +- notes: All contractiles should be .a2ml format + +## Format Duplicates + +### no-duplicate-justfile +- description: Only one Justfile (hardlinked from root to .machine_readable/contractiles/) +- run: test $(stat -c '%i' Justfile) = $(stat -c '%i' .machine_readable/contractiles/Justfile 2>/dev/null) +- severity: warning +- notes: Justfile should be hardlinked, not copied + +### no-duplicate-readme-format +- description: Only one README format in contractiles/ (.adoc canonical) +- run: test ! -f .machine_readable/contractiles/README.md +- severity: info + +## Template Hygiene + +### no-stale-template-references +- description: No references to rattlescript in generic template files +- run: test -z "$(grep -r 'rattlescript' machine-readable-design/ 2>/dev/null)" +- severity: warning +- notes: Generic templates should use {{PROJECT_NAME}} or similar placeholders + +### version-sync-checked +- description: Version in canonical-directory-structure matches .machine_readable/contractiles +- verification: compare version identifiers in both locations +- severity: info From 8fb66b837754e09d80aa43161dc57ab8b809ff37 Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:16:35 +0100 Subject: [PATCH 3/6] security: standardize secret scanning on TruffleHog --- .github/workflows/boj-build.yml | 4 - .github/workflows/codeql.yml | 7 - .github/workflows/e2e.yml | 306 ++++++++++----------- .github/workflows/governance.yml | 4 - .github/workflows/hypatia-scan.yml | 103 +------ .github/workflows/instant-sync.yml | 4 - .github/workflows/mirror.yml | 24 -- .github/workflows/openssf-compliance.yml | 11 - .github/workflows/release.yml | 25 +- .github/workflows/rhodibot.yml | 54 +--- .github/workflows/rust-ci.yml | 15 - .github/workflows/scorecard-enforcer.yml | 14 +- .github/workflows/scorecard.yml | 5 - .github/workflows/secret-scanner.yml | 22 +- .github/workflows/static-analysis-gate.yml | 40 +-- .gitlab-ci.yml | 31 +-- .machine_readable/contractiles/Justfile | 5 +- .pre-commit-config.yaml | 2 - Justfile | 5 +- 19 files changed, 184 insertions(+), 497 deletions(-) diff --git a/.github/workflows/boj-build.yml b/.github/workflows/boj-build.yml index 20848c4..b203334 100644 --- a/.github/workflows/boj-build.yml +++ b/.github/workflows/boj-build.yml @@ -6,15 +6,12 @@ # To enable: set BOJ_SERVER_URL as a repository secret or variable. # To disable: delete this file or leave BOJ_SERVER_URL unset. name: BoJ Server Build Trigger - on: push: branches: [main, master] workflow_dispatch: - permissions: contents: read - jobs: trigger-boj: runs-on: ubuntu-latest @@ -23,7 +20,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Trigger BoJ Server (Casket/ssg-mcp) env: BOJ_URL: ${{ secrets.BOJ_SERVER_URL || vars.BOJ_SERVER_URL }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e547933..64a6a75 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 name: CodeQL Security Analysis - on: push: branches: [main, master] @@ -8,7 +7,6 @@ on: branches: [main, master] schedule: - cron: '0 6 * * 1' - # Estate guardrail: cancel superseded runs so re-pushes / rebased PR # updates do not pile up queued runs against the shared account-wide # Actions concurrency pool. Applied only to read-only check workflows @@ -16,10 +14,8 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - permissions: contents: read - jobs: analyze: runs-on: ubuntu-latest @@ -33,17 +29,14 @@ jobs: include: - language: javascript-typescript build-mode: none - steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Initialize CodeQL uses: github/codeql-action/init@c6f931105cb2c34c8f901cc885ba1e2e259cf745 # v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@c6f931105cb2c34c8f901cc885ba1e2e259cf745 # v3 with: diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 0b68a13..940c70a 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -13,7 +13,6 @@ # Delete sections that don't apply. See examples in each job. name: E2E + Aspect + Bench - on: push: branches: [main, master, develop] @@ -29,162 +28,159 @@ on: - 'ffi/**' - 'tests/**' workflow_dispatch: - permissions: read-all - concurrency: group: e2e-${{ github.ref }} cancel-in-progress: true - jobs: - # ─── End-to-End Tests ────────────────────────────────────────────── - # Uncomment ONE of the following e2e job blocks matching your stack. - - ## === RUST E2E === - # e2e: - # name: E2E — Full Pipeline - # runs-on: ubuntu-latest - # timeout-minutes: 15 - # steps: - # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - # - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable - # - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 - # - run: cargo build --release - # - run: bash tests/e2e.sh - # # OR: cargo test --test end_to_end -- --nocapture - - ## === ZIG FFI E2E === - # e2e: - # name: E2E — FFI Pipeline - # runs-on: ubuntu-latest - # timeout-minutes: 15 - # steps: - # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - # - uses: goto-bus-stop/setup-zig@7ab2955eb728f5440978d7b4f723a50dea1f3608 # v2 - # with: - # version: 0.15.0 - # - run: cd ffi/zig && zig build test - # - run: bash tests/e2e.sh - - ## === ELIXIR E2E === - # e2e: - # name: E2E — Full Pipeline - # runs-on: ubuntu-latest - # timeout-minutes: 15 - # steps: - # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - # - uses: erlef/setup-beam@5a67e1a1dd86cae5e5bef84e2da5060406a66c07 # v1 - # with: - # otp-version: '27.0' - # elixir-version: '1.17' - # - run: mix deps.get && mix compile --warnings-as-errors - # - run: mix test test/integration/e2e_test.exs --trace - - ## === DENO/RESCRIPT E2E === - # e2e: - # name: E2E — Full Pipeline - # runs-on: ubuntu-latest - # timeout-minutes: 15 - # steps: - # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - # - uses: denoland/setup-deno@5fae568d37c3b73e0e4ca63d4e2c4e324a2b3497 # v2 - # with: - # deno-version: v2.x - # - run: deno install --node-modules-dir=auto - # - run: deno task res:build # ReScript compile - # - run: deno test tests/e2e/ - - ## === PLAYWRIGHT (Browser E2E) === - # e2e-playwright: - # name: Playwright — ${{ matrix.project }} - # runs-on: ubuntu-latest - # timeout-minutes: 20 - # strategy: - # fail-fast: false - # matrix: - # project: [chromium-1080p, firefox-1080p, webkit-1080p] - # steps: - # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - # - uses: denoland/setup-deno@5fae568d37c3b73e0e4ca63d4e2c4e324a2b3497 # v2 - # with: - # deno-version: v2.x - # - run: deno install --node-modules-dir=auto - # - run: npx playwright install --with-deps - # - run: npx playwright test --project=${{ matrix.project }} - # - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - # if: failure() - # with: - # name: playwright-traces-${{ matrix.project }} - # path: test-results/**/trace.zip - # retention-days: 7 - - ## === HASKELL E2E === - # e2e: - # name: E2E — Full Pipeline - # runs-on: ubuntu-latest - # timeout-minutes: 15 - # steps: - # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - # - uses: haskell-actions/setup@dd344bc1cec854a9b55c2b857c28b688010e4fce # v2 - # with: - # ghc-version: '9.6' - # cabal-version: '3.10' - # - run: cabal build all - # - run: bash tests/integration-test.sh - - # ─── Aspect Tests ────────────────────────────────────────────────── - # Cross-cutting concerns: thread safety, ABI contracts, SPDX, dangerous patterns - # Uncomment and customise: - - # aspect-tests: - # name: Aspect — Architectural Invariants - # runs-on: ubuntu-latest - # timeout-minutes: 10 - # steps: - # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - # - run: bash tests/aspect_tests.sh - - # ─── Benchmarks ──────────────────────────────────────────────────── - # Performance regression detection. Uncomment matching stack: - - ## === RUST BENCH === - # benchmarks: - # name: Bench — Performance Regression - # runs-on: ubuntu-latest - # timeout-minutes: 15 - # steps: - # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - # - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable - # - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 - # - run: cargo bench 2>&1 | tee /tmp/bench-results.txt - # - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - # if: always() - # with: - # name: benchmark-results - # path: /tmp/bench-results.txt - # retention-days: 30 - - ## === ZIG BENCH === - # benchmarks: - # name: Bench — Performance Regression - # runs-on: ubuntu-latest - # timeout-minutes: 15 - # steps: - # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - # - uses: goto-bus-stop/setup-zig@7ab2955eb728f5440978d7b4f723a50dea1f3608 # v2 - # with: - # version: 0.15.0 - # - run: cd ffi/zig && zig build bench - - # ─── Readiness (CRG) ────────────────────────────────────────────── - # Component Readiness Grade: D (runs) → C (correct) → B (edge cases) - - # readiness: - # name: Readiness — Grade D/C/B - # runs-on: ubuntu-latest - # timeout-minutes: 10 - # steps: - # - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - # - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable - # - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 - # - run: cargo test --test readiness -- --nocapture +# ─── End-to-End Tests ────────────────────────────────────────────── +# Uncomment ONE of the following e2e job blocks matching your stack. + +## === RUST E2E === +# e2e: +# name: E2E — Full Pipeline +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable +# - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 +# - run: cargo build --release +# - run: bash tests/e2e.sh +# # OR: cargo test --test end_to_end -- --nocapture + +## === ZIG FFI E2E === +# e2e: +# name: E2E — FFI Pipeline +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: goto-bus-stop/setup-zig@7ab2955eb728f5440978d7b4f723a50dea1f3608 # v2 +# with: +# version: 0.15.0 +# - run: cd ffi/zig && zig build test +# - run: bash tests/e2e.sh + +## === ELIXIR E2E === +# e2e: +# name: E2E — Full Pipeline +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: erlef/setup-beam@5a67e1a1dd86cae5e5bef84e2da5060406a66c07 # v1 +# with: +# otp-version: '27.0' +# elixir-version: '1.17' +# - run: mix deps.get && mix compile --warnings-as-errors +# - run: mix test test/integration/e2e_test.exs --trace + +## === DENO/RESCRIPT E2E === +# e2e: +# name: E2E — Full Pipeline +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: denoland/setup-deno@5fae568d37c3b73e0e4ca63d4e2c4e324a2b3497 # v2 +# with: +# deno-version: v2.x +# - run: deno install --node-modules-dir=auto +# - run: deno task res:build # ReScript compile +# - run: deno test tests/e2e/ + +## === PLAYWRIGHT (Browser E2E) === +# e2e-playwright: +# name: Playwright — ${{ matrix.project }} +# runs-on: ubuntu-latest +# timeout-minutes: 20 +# strategy: +# fail-fast: false +# matrix: +# project: [chromium-1080p, firefox-1080p, webkit-1080p] +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: denoland/setup-deno@5fae568d37c3b73e0e4ca63d4e2c4e324a2b3497 # v2 +# with: +# deno-version: v2.x +# - run: deno install --node-modules-dir=auto +# - run: npx playwright install --with-deps +# - run: npx playwright test --project=${{ matrix.project }} +# - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +# if: failure() +# with: +# name: playwright-traces-${{ matrix.project }} +# path: test-results/**/trace.zip +# retention-days: 7 + +## === HASKELL E2E === +# e2e: +# name: E2E — Full Pipeline +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: haskell-actions/setup@dd344bc1cec854a9b55c2b857c28b688010e4fce # v2 +# with: +# ghc-version: '9.6' +# cabal-version: '3.10' +# - run: cabal build all +# - run: bash tests/integration-test.sh + +# ─── Aspect Tests ────────────────────────────────────────────────── +# Cross-cutting concerns: thread safety, ABI contracts, SPDX, dangerous patterns +# Uncomment and customise: + +# aspect-tests: +# name: Aspect — Architectural Invariants +# runs-on: ubuntu-latest +# timeout-minutes: 10 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - run: bash tests/aspect_tests.sh + +# ─── Benchmarks ──────────────────────────────────────────────────── +# Performance regression detection. Uncomment matching stack: + +## === RUST BENCH === +# benchmarks: +# name: Bench — Performance Regression +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable +# - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 +# - run: cargo bench 2>&1 | tee /tmp/bench-results.txt +# - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +# if: always() +# with: +# name: benchmark-results +# path: /tmp/bench-results.txt +# retention-days: 30 + +## === ZIG BENCH === +# benchmarks: +# name: Bench — Performance Regression +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: goto-bus-stop/setup-zig@7ab2955eb728f5440978d7b4f723a50dea1f3608 # v2 +# with: +# version: 0.15.0 +# - run: cd ffi/zig && zig build bench + +# ─── Readiness (CRG) ────────────────────────────────────────────── +# Component Readiness Grade: D (runs) → C (correct) → B (edge cases) + +# readiness: +# name: Readiness — Grade D/C/B +# runs-on: ubuntu-latest +# timeout-minutes: 10 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable +# - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 +# - run: cargo test --test readiness -- --nocapture diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 1b4e269..e0c379b 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -11,13 +11,11 @@ # (rust-ci, codeql, dependabot, release, scan/mirror/pages plumbing). name: Governance - on: push: branches: [main, master] pull_request: workflow_dispatch: - # Estate guardrail: cancel superseded runs so re-pushes / rebased PR # updates do not pile up queued runs against the shared account-wide # Actions concurrency pool. Applied only to read-only check workflows @@ -25,10 +23,8 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - permissions: contents: read - jobs: governance: uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@861b5e911d9e5dcfb3c0ab3dd2a9a3c8fd0a1613 diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 127905d..e1e3879 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1,14 +1,13 @@ # SPDX-License-Identifier: MPL-2.0 # Hypatia Neurosymbolic CI/CD Security Scan name: Hypatia Security Scan - on: push: - branches: [ main, master, develop ] + branches: [main, master, develop] pull_request: - branches: [ main, master ] + branches: [main, master] schedule: - - cron: '0 0 * * 0' # Weekly on Sunday + - cron: '0 0 * * 0' # Weekly on Sunday workflow_dispatch: # Estate guardrail: cancel superseded runs so re-pushes don't pile up # queued runs across the estate. Safe here because this workflow only @@ -16,7 +15,6 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - permissions: contents: read # security-events: write serves two purposes (write implies read): @@ -38,31 +36,26 @@ permissions: # "Resource not accessible by integration" and (absent continue-on-error) # hard-fails the scan — exactly what the gate-decoupling design forbids. pull-requests: write - jobs: scan: name: Hypatia Neurosymbolic Analysis runs-on: ubuntu-latest timeout-minutes: 15 - steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - fetch-depth: 0 # Full history for better pattern analysis - + fetch-depth: 0 # Full history for better pattern analysis - name: Setup Elixir for Hypatia scanner uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.18.2 with: elixir-version: '1.18' otp-version: '27' - - name: Clone Hypatia run: | if [ ! -d "$HOME/hypatia" ]; then git clone https://github.com/hyperpolymath/hypatia.git "$HOME/hypatia" fi - - name: Build Hypatia scanner (if needed) run: | cd "$HOME/hypatia" @@ -71,7 +64,6 @@ jobs: mix deps.get mix escript.build fi - - name: Run Hypatia scan id: scan env: @@ -104,14 +96,12 @@ jobs: echo "- Critical: $CRITICAL" >> $GITHUB_STEP_SUMMARY echo "- High: $HIGH" >> $GITHUB_STEP_SUMMARY echo "- Medium: $MEDIUM" >> $GITHUB_STEP_SUMMARY - - name: Upload findings artifact uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: hypatia-findings path: hypatia-findings.json retention-days: 90 - - name: Convert Hypatia findings to SARIF # Always runs (no findings_count guard): an EMPTY SARIF run is # valid and intentional — uploading it clears stale Hypatia @@ -227,7 +217,6 @@ jobs: console.log(`hypatia.sarif written: ${results.length} result(s).`); CJS node "$RUNNER_TEMP/hypatia-sarif.cjs" - - name: Upload SARIF to GitHub code scanning # Fork PRs get a read-only GITHUB_TOKEN, so security-events:write # is unavailable and upload-sarif cannot publish — skip there @@ -239,8 +228,8 @@ jobs: # exists to end). The empty-SARIF "clear stale alerts" path is # handled in the converter above and does not error here. if: >- - always() && - (github.event_name != 'pull_request' || + always() && (github.event_name != 'pull_request' || + github.event.pull_request.head.repo.fork != true) uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.28.1 with: @@ -248,7 +237,6 @@ jobs: # Distinct category so Hypatia results coexist with CodeQL's # (codeql.yml) instead of overwriting them on the same surface. category: hypatia - - name: Submit findings to gitbot-fleet (Phase 2) if: steps.scan.outputs.findings_count > 0 # Phase 2 is the collaborative LEARNING side-channel ("bots share @@ -272,52 +260,7 @@ jobs: GITHUB_REPOSITORY: ${{ github.repository }} GITHUB_SHA: ${{ github.sha }} FINDINGS_COUNT: ${{ steps.scan.outputs.findings_count }} - run: | - echo "📤 Submitting $FINDINGS_COUNT findings to gitbot-fleet..." - - # Clone gitbot-fleet to temp directory. A clone failure (network, - # repo gone) is non-fatal: learning submission is best-effort. - FLEET_DIR="/tmp/gitbot-fleet-$$" - if ! git clone --depth 1 https://github.com/hyperpolymath/gitbot-fleet.git "$FLEET_DIR"; then - echo "::warning::Could not clone gitbot-fleet — skipping Phase 2 learning submission (non-fatal)." - exit 0 - fi - - # The submission script's location in gitbot-fleet has drifted - # before (it was absent from the default branch, which exit-127'd - # every consuming repo's scan). Probe known locations rather than - # hard-coding one path, and skip gracefully if none is present. - SUBMIT_SCRIPT="" - for cand in \ - "$FLEET_DIR/scripts/submit-finding.sh" \ - "$FLEET_DIR/scripts/submit_finding.sh" \ - "$FLEET_DIR/bin/submit-finding.sh" \ - "$FLEET_DIR/submit-finding.sh"; do - if [ -f "$cand" ]; then - SUBMIT_SCRIPT="$cand" - break - fi - done - - if [ -z "$SUBMIT_SCRIPT" ]; then - echo "::warning::gitbot-fleet submit-finding script not found at any known path — skipping Phase 2 learning submission (non-fatal). Findings are still uploaded as an artifact and gated below." - rm -rf "$FLEET_DIR" - exit 0 - fi - - # Run submission script. Pass the findings path as ABSOLUTE — - # the script cd's into its own working dir before reading the - # file, so a relative path would resolve to the wrong place. - # A submission-script failure is logged but non-fatal. - if bash "$SUBMIT_SCRIPT" "$GITHUB_WORKSPACE/hypatia-findings.json"; then - echo "✅ Finding submission complete" - else - echo "::warning::gitbot-fleet submission script exited non-zero — Phase 2 learning submission skipped (non-fatal)." - fi - - # Cleanup - rm -rf "$FLEET_DIR" - + run: "echo \"\U0001F4E4 Submitting $FINDINGS_COUNT findings to gitbot-fleet...\"\n\n# Clone gitbot-fleet to temp directory. A clone failure (network,\n# repo gone) is non-fatal: learning submission is best-effort.\nFLEET_DIR=\"/tmp/gitbot-fleet-$$\"\nif ! git clone --depth 1 https://github.com/hyperpolymath/gitbot-fleet.git \"$FLEET_DIR\"; then\n echo \"::warning::Could not clone gitbot-fleet — skipping Phase 2 learning submission (non-fatal).\"\n exit 0\nfi\n\n# The submission script's location in gitbot-fleet has drifted\n# before (it was absent from the default branch, which exit-127'd\n# every consuming repo's scan). Probe known locations rather than\n# hard-coding one path, and skip gracefully if none is present.\nSUBMIT_SCRIPT=\"\"\nfor cand in \\\n \"$FLEET_DIR/scripts/submit-finding.sh\" \\\n \"$FLEET_DIR/scripts/submit_finding.sh\" \\\n \"$FLEET_DIR/bin/submit-finding.sh\" \\\n \"$FLEET_DIR/submit-finding.sh\"; do\n if [ -f \"$cand\" ]; then\n SUBMIT_SCRIPT=\"$cand\"\n break\n fi\ndone\n\nif [ -z \"$SUBMIT_SCRIPT\" ]; then\n echo \"::warning::gitbot-fleet submit-finding script not found at any known path — skipping Phase 2 learning submission (non-fatal). Findings are still uploaded as an artifact and gated below.\"\n rm -rf \"$FLEET_DIR\"\n exit 0\nfi\n\n# Run submission script. Pass the findings path as ABSOLUTE —\n# the script cd's into its own working dir before reading the\n# file, so a relative path would resolve to the wrong place.\n# A submission-script failure is logged but non-fatal.\nif bash \"$SUBMIT_SCRIPT\" \"$GITHUB_WORKSPACE/hypatia-findings.json\"; then\n echo \"✅ Finding submission complete\"\nelse\n echo \"::warning::gitbot-fleet submission script exited non-zero — Phase 2 learning submission skipped (non-fatal).\"\nfi\n\n# Cleanup\nrm -rf \"$FLEET_DIR\"\n" - name: Check for critical issues if: steps.scan.outputs.critical > 0 # GATING POLICY (explicit, by design — not an oversight): @@ -335,7 +278,6 @@ jobs: echo "::warning::Hypatia found critical security issue(s) — advisory." echo "See the Security → Code scanning page (category: hypatia)" echo "and the hypatia-findings.json artifact for details." - - name: Generate scan report run: | cat << EOF > hypatia-report.md @@ -374,7 +316,6 @@ jobs: EOF cat hypatia-report.md >> $GITHUB_STEP_SUMMARY - - name: Comment on PR with findings if: github.event_name == 'pull_request' && steps.scan.outputs.findings_count > 0 # Advisory only — posting findings as a PR comment must never gate @@ -384,32 +325,4 @@ jobs: continue-on-error: true uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v7 with: - script: | - const fs = require('fs'); - const findings = JSON.parse(fs.readFileSync('hypatia-findings.json', 'utf8')); - - const critical = findings.filter(f => f.severity === 'critical').length; - const high = findings.filter(f => f.severity === 'high').length; - - let comment = `## 🔍 Hypatia Security Scan\n\n`; - comment += `**Findings:** ${findings.length} issues detected\n\n`; - comment += `| Severity | Count |\n|----------|-------|\n`; - comment += `| 🔴 Critical | ${critical} |\n`; - comment += `| 🟠 High | ${high} |\n`; - comment += `| 🟡 Medium | ${findings.length - critical - high} |\n\n`; - - if (critical > 0) { - comment += `⚠️ **Action Required:** Critical security issues found!\n\n`; - } - - comment += `
View findings\n\n`; - comment += `\`\`\`json\n${JSON.stringify(findings.slice(0, 10), null, 2)}\n\`\`\`\n`; - comment += `
\n\n`; - comment += `*Powered by Hypatia Neurosymbolic CI/CD Intelligence*`; - - github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: comment - }); \ No newline at end of file + script: "const fs = require('fs');\nconst findings = JSON.parse(fs.readFileSync('hypatia-findings.json', 'utf8'));\n\nconst critical = findings.filter(f => f.severity === 'critical').length;\nconst high = findings.filter(f => f.severity === 'high').length;\n\nlet comment = `## \U0001F50D Hypatia Security Scan\\n\\n`;\ncomment += `**Findings:** ${findings.length} issues detected\\n\\n`;\ncomment += `| Severity | Count |\\n|----------|-------|\\n`;\ncomment += `| \U0001F534 Critical | ${critical} |\\n`;\ncomment += `| \U0001F7E0 High | ${high} |\\n`;\ncomment += `| \U0001F7E1 Medium | ${findings.length - critical - high} |\\n\\n`;\n\nif (critical > 0) {\n comment += `⚠️ **Action Required:** Critical security issues found!\\n\\n`;\n}\n\ncomment += `
View findings\\n\\n`;\ncomment += `\\`\\`\\`json\\n${JSON.stringify(findings.slice(0, 10), null, 2)}\\n\\`\\`\\`\\n`;\ncomment += `
\\n\\n`;\ncomment += `*Powered by Hypatia Neurosymbolic CI/CD Intelligence*`;\n\ngithub.rest.issues.createComment({\n owner: context.repo.owner,\n repo: context.repo.repo,\n issue_number: context.issue.number,\n body: comment\n});" diff --git a/.github/workflows/instant-sync.yml b/.github/workflows/instant-sync.yml index e1d2d9d..0994325 100644 --- a/.github/workflows/instant-sync.yml +++ b/.github/workflows/instant-sync.yml @@ -1,16 +1,13 @@ # SPDX-License-Identifier: MPL-2.0 # Instant Forge Sync - Triggers propagation to all forges on push/release name: Instant Sync - on: push: branches: [main, master] release: types: [published] - permissions: contents: read - jobs: dispatch: runs-on: ubuntu-latest @@ -29,7 +26,6 @@ jobs: "sha": "${{ github.sha }}", "forges": "" } - - name: Confirm env: REPO_NAME: ${{ github.event.repository.name }} diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index f86bc9c..241942c 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -1,15 +1,12 @@ # SPDX-License-Identifier: MPL-2.0 # SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell name: Mirror to Git Forges - on: push: branches: [main] workflow_dispatch: - permissions: contents: read - jobs: mirror-gitlab: runs-on: ubuntu-latest @@ -19,17 +16,14 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 with: ssh-private-key: ${{ secrets.GITLAB_SSH_KEY }} - - name: Mirror to GitLab run: | ssh-keyscan -t ed25519 gitlab.com >> ~/.ssh/known_hosts git remote add gitlab git@gitlab.com:${{ vars.GITLAB_ORG || vars.MIRROR_ORG || github.repository_owner }}/${{ github.event.repository.name }}.git || true git push --force gitlab main - mirror-bitbucket: runs-on: ubuntu-latest timeout-minutes: 15 @@ -38,17 +32,14 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 with: ssh-private-key: ${{ secrets.BITBUCKET_SSH_KEY }} - - name: Mirror to Bitbucket run: | ssh-keyscan -t ed25519 bitbucket.org >> ~/.ssh/known_hosts git remote add bitbucket git@bitbucket.org:${{ vars.BITBUCKET_ORG || vars.MIRROR_ORG || github.repository_owner }}/${{ github.event.repository.name }}.git || true git push --force bitbucket main - mirror-codeberg: runs-on: ubuntu-latest timeout-minutes: 15 @@ -57,17 +48,14 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 with: ssh-private-key: ${{ secrets.CODEBERG_SSH_KEY }} - - name: Mirror to Codeberg run: | ssh-keyscan -t ed25519 codeberg.org >> ~/.ssh/known_hosts git remote add codeberg git@codeberg.org:${{ vars.CODEBERG_ORG || vars.MIRROR_ORG || github.repository_owner }}/${{ github.event.repository.name }}.git || true git push --force codeberg main - mirror-sourcehut: runs-on: ubuntu-latest timeout-minutes: 15 @@ -76,17 +64,14 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 with: ssh-private-key: ${{ secrets.SOURCEHUT_SSH_KEY }} - - name: Mirror to SourceHut run: | ssh-keyscan -t ed25519 git.sr.ht >> ~/.ssh/known_hosts git remote add sourcehut git@git.sr.ht:~${{ vars.SOURCEHUT_ORG || vars.MIRROR_ORG || github.repository_owner }}/${{ github.event.repository.name }} || true git push --force sourcehut main - mirror-disroot: runs-on: ubuntu-latest timeout-minutes: 15 @@ -95,17 +80,14 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 with: ssh-private-key: ${{ secrets.DISROOT_SSH_KEY }} - - name: Mirror to Disroot run: | ssh-keyscan -t ed25519 git.disroot.org >> ~/.ssh/known_hosts git remote add disroot git@git.disroot.org:${{ vars.DISROOT_ORG || vars.MIRROR_ORG || github.repository_owner }}/${{ github.event.repository.name }}.git || true git push --force disroot main - mirror-gitea: runs-on: ubuntu-latest timeout-minutes: 15 @@ -114,17 +96,14 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 with: ssh-private-key: ${{ secrets.GITEA_SSH_KEY }} - - name: Mirror to Gitea run: | ssh-keyscan -t ed25519 ${{ vars.GITEA_HOST }} >> ~/.ssh/known_hosts git remote add gitea git@${{ vars.GITEA_HOST }}:${{ vars.GITEA_ORG || vars.MIRROR_ORG || github.repository_owner }}/${{ github.event.repository.name }}.git || true git push --force gitea main - mirror-radicle: runs-on: ubuntu-latest timeout-minutes: 15 @@ -133,18 +112,15 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - name: Setup Rust uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable with: toolchain: stable - - name: Install Radicle run: | # Install via cargo (safer than curl|sh) cargo install radicle-cli --locked echo "$HOME/.cargo/bin" >> $GITHUB_PATH - - name: Mirror to Radicle run: | echo "${{ secrets.RADICLE_KEY }}" > ~/.radicle/keys/radicle diff --git a/.github/workflows/openssf-compliance.yml b/.github/workflows/openssf-compliance.yml index 6f00e2a..abb7c1e 100644 --- a/.github/workflows/openssf-compliance.yml +++ b/.github/workflows/openssf-compliance.yml @@ -2,17 +2,14 @@ # OpenSSF Best Practices compliance gate — blocks PRs and pushes that lack # required files or still contain unfilled placeholder tokens. name: OpenSSF Compliance - on: push: branches: [main] pull_request: branches: [main] workflow_dispatch: - permissions: contents: read - jobs: openssf-compliance: runs-on: ubuntu-latest @@ -23,7 +20,6 @@ jobs: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - - name: Check SECURITY.md exists and has substance run: | SECFILE="" @@ -42,7 +38,6 @@ jobs: exit 1 fi echo "SECURITY file: OK ($SECFILE, $LINES lines)" - - name: Check LICENSE exists run: | if [ ! -f "LICENSE" ] && [ ! -f "LICENSE.txt" ] && [ ! -f "LICENSE.md" ]; then @@ -50,7 +45,6 @@ jobs: exit 1 fi echo "LICENSE: OK" - - name: Check CONTRIBUTING exists run: | if [ ! -f "CONTRIBUTING.md" ] && [ ! -f "CONTRIBUTING.adoc" ]; then @@ -58,7 +52,6 @@ jobs: exit 1 fi echo "CONTRIBUTING: OK" - - name: Check README exists run: | if [ ! -f "README.md" ] && [ ! -f "README.adoc" ] && [ ! -f "README.rst" ] && [ ! -f "README.txt" ] && [ ! -f "README" ]; then @@ -66,7 +59,6 @@ jobs: exit 1 fi echo "README: OK" - - name: Check .machine_readable directory and STATE.a2ml run: | if [ ! -d ".machine_readable" ]; then @@ -79,7 +71,6 @@ jobs: exit 1 fi echo ".machine_readable/STATE.a2ml: OK" - - name: Check CHANGELOG exists run: | if [ ! -f "CHANGELOG.md" ] && [ ! -f "CHANGELOG.adoc" ] && [ ! -f "CHANGES.md" ]; then @@ -87,7 +78,6 @@ jobs: exit 1 fi echo "CHANGELOG: OK" - - name: Check no unfilled placeholder tokens in required files run: | ERRORS=0 @@ -117,7 +107,6 @@ jobs: exit 1 fi echo "Placeholder check: OK (no unfilled tokens in required files)" - - name: Summary run: | echo "=== OpenSSF Best Practices Compliance: PASS ===" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f18a11..0b50929 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,15 +5,12 @@ # Builds artifacts, generates changelog via git-cliff, creates a GitHub Release, # and produces SLSA provenance attestations. name: Release - on: push: tags: - 'v*' - permissions: contents: read - jobs: build: name: Build Artifacts @@ -23,7 +20,6 @@ jobs: contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Detect project type and build id: build run: | @@ -69,13 +65,11 @@ jobs: echo "Expected one of: mix.exs, Cargo.toml, build.zig, deno.json, gossamer.conf.json, gleam.toml, rebar.config, Justfile" exit 1 fi - - # TODO: Upload build artifacts if needed - # - uses: actions/upload-artifact@v4 - # with: - # name: release-artifacts - # path: target/release/ - + # TODO: Upload build artifacts if needed + # - uses: actions/upload-artifact@v4 + # with: + # name: release-artifacts + # path: target/release/ changelog: name: Generate Changelog runs-on: ubuntu-latest @@ -89,16 +83,13 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - name: Extract version from tag id: version run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" - - name: Install git-cliff run: | curl -sSfL https://github.com/orhun/git-cliff/releases/latest/download/git-cliff-$(uname -m)-unknown-linux-gnu.tar.gz \ | tar -xz --strip-components=1 -C /usr/local/bin/ git-cliff-*/git-cliff - - name: Generate changelog for this release id: cliff run: | @@ -110,18 +101,15 @@ jobs: echo "$CHANGELOG" echo "CLIFF_EOF" } >> "$GITHUB_OUTPUT" - - name: Update full CHANGELOG.md run: | git cliff --output CHANGELOG.md - - name: Upload updated CHANGELOG.md uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: changelog path: CHANGELOG.md retention-days: 5 - release: name: Create GitHub Release needs: [build, changelog] @@ -131,13 +119,11 @@ jobs: contents: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - # TODO: Download build artifacts if uploading to the release # - uses: actions/download-artifact@v4 # with: # name: release-artifacts # path: artifacts/ - - name: Create GitHub Release uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 with: @@ -150,7 +136,6 @@ jobs: # artifacts/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - provenance: name: SLSA Provenance needs: [build] diff --git a/.github/workflows/rhodibot.yml b/.github/workflows/rhodibot.yml index 682a2c6..46ddb53 100644 --- a/.github/workflows/rhodibot.yml +++ b/.github/workflows/rhodibot.yml @@ -10,20 +10,17 @@ # # Runs weekly and on Hypatia scan completion. -name: "🤖 Rhodibot — RSR Auto-Fix" - +name: "\U0001F916 Rhodibot — RSR Auto-Fix" on: schedule: - - cron: '0 6 * * 1' # Every Monday at 06:00 UTC - workflow_dispatch: # Manual trigger + - cron: '0 6 * * 1' # Every Monday at 06:00 UTC + workflow_dispatch: # Manual trigger workflow_run: workflows: ["Hypatia Neurosymbolic Analysis"] types: [completed] - permissions: contents: write pull-requests: write - jobs: rhodibot: runs-on: ubuntu-latest @@ -33,7 +30,6 @@ jobs: uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 1 - - name: Rhodibot — Scan and Fix id: fix run: | @@ -174,53 +170,11 @@ jobs: echo -e "$DANGEROUS" echo "EOF" } >> $GITHUB_OUTPUT - - name: Create PR with fixes if: steps.fix.outputs.CHANGED == 'true' - run: | - git config user.name "rhodibot" - git config user.email "rhodibot@hyperpolymath.dev" - BRANCH="rhodibot/rsr-compliance-$(date +%Y%m%d)" - git checkout -b "$BRANCH" - git add -A - git commit -m "fix(rhodibot): automated RSR compliance fixes - - ${{ steps.fix.outputs.FIXES }} - - Co-Authored-By: rhodibot " - - git push origin "$BRANCH" - - BODY="## 🤖 Rhodibot — RSR Compliance Fixes - - ### Changes Made - ${{ steps.fix.outputs.FIXES }} - " - - if [ -n "${{ steps.fix.outputs.ISSUES }}" ]; then - BODY="$BODY - ### Issues Found (manual fix needed) - ${{ steps.fix.outputs.ISSUES }} - " - fi - - if [ -n "${{ steps.fix.outputs.DANGEROUS }}" ]; then - BODY="$BODY - ### ⚠️ Dangerous Patterns Detected - ${{ steps.fix.outputs.DANGEROUS }} - - _These bypass formal verification. See \`proven\` repo for alternatives._ - " - fi - - gh pr create \ - --title "🤖 Rhodibot: RSR compliance fixes" \ - --body "$BODY" \ - --base main \ - --head "$BRANCH" + run: "git config user.name \"rhodibot\"\ngit config user.email \"rhodibot@hyperpolymath.dev\"\nBRANCH=\"rhodibot/rsr-compliance-$(date +%Y%m%d)\"\ngit checkout -b \"$BRANCH\"\ngit add -A\ngit commit -m \"fix(rhodibot): automated RSR compliance fixes\n\n${{ steps.fix.outputs.FIXES }}\n\nCo-Authored-By: rhodibot \"\n\ngit push origin \"$BRANCH\"\n\nBODY=\"## \U0001F916 Rhodibot — RSR Compliance Fixes\n\n### Changes Made\n${{ steps.fix.outputs.FIXES }}\n\"\n\nif [ -n \"${{ steps.fix.outputs.ISSUES }}\" ]; then\n BODY=\"$BODY\n### Issues Found (manual fix needed)\n${{ steps.fix.outputs.ISSUES }}\n\"\nfi\n\nif [ -n \"${{ steps.fix.outputs.DANGEROUS }}\" ]; then\n BODY=\"$BODY\n### ⚠️ Dangerous Patterns Detected\n${{ steps.fix.outputs.DANGEROUS }}\n\n_These bypass formal verification. See \\`proven\\` repo for alternatives._\n\"\nfi\n\ngh pr create \\\n --title \"\U0001F916 Rhodibot: RSR compliance fixes\" \\\n --body \"$BODY\" \\\n --base main \\\n --head \"$BRANCH\"\n" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Report (no changes needed) if: steps.fix.outputs.CHANGED != 'true' run: | diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 607820d..c277568 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -4,64 +4,49 @@ # rust-ci.yml — Cargo build, test, clippy, and fmt for Rust projects. # Only runs if Cargo.toml exists in the repo root. name: Rust CI - on: pull_request: branches: ['**'] push: branches: [main, master] - permissions: contents: read - jobs: check: name: Cargo check + clippy + fmt runs-on: ubuntu-latest timeout-minutes: 15 if: hashFiles('Cargo.toml') != '' - steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Install Rust toolchain uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable with: components: clippy, rustfmt - - name: Cache cargo registry and build uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 - - name: Cargo check run: cargo check --all-targets 2>&1 - - name: Cargo fmt run: cargo fmt --all -- --check - - name: Cargo clippy run: cargo clippy --all-targets -- -D warnings - test: name: Cargo test runs-on: ubuntu-latest timeout-minutes: 15 needs: check if: hashFiles('Cargo.toml') != '' - steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Install Rust toolchain uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable - - name: Cache cargo registry and build uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 - - name: Run tests run: cargo test --all-targets - - name: Write summary if: always() run: | diff --git a/.github/workflows/scorecard-enforcer.yml b/.github/workflows/scorecard-enforcer.yml index 57535d0..f5fb110 100644 --- a/.github/workflows/scorecard-enforcer.yml +++ b/.github/workflows/scorecard-enforcer.yml @@ -1,14 +1,12 @@ # SPDX-License-Identifier: MPL-2.0 # Prevention workflow - runs OpenSSF Scorecard and fails on low scores name: OpenSSF Scorecard Enforcer - on: push: branches: [main] schedule: - - cron: '0 6 * * 1' # Weekly on Monday + - cron: '0 6 * * 1' # Weekly on Monday workflow_dispatch: - # Estate guardrail: cancel superseded runs so re-pushes / rebased PR # updates do not pile up queued runs against the shared account-wide # Actions concurrency pool. Applied only to read-only check workflows @@ -16,34 +14,29 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - permissions: contents: read - jobs: scorecard: runs-on: ubuntu-latest timeout-minutes: 15 permissions: security-events: write - id-token: write # For OIDC + id-token: write # For OIDC steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - name: Run Scorecard uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif publish_results: true - - name: Upload SARIF uses: github/codeql-action/upload-sarif@c6f931105cb2c34c8f901cc885ba1e2e259cf745 # v4 with: sarif_file: results.sarif - - name: Check minimum score run: | # Parse score from results @@ -58,21 +51,18 @@ jobs: echo "::error::Scorecard score $SCORE is below minimum $MIN_SCORE" exit 1 fi - # Check specific high-priority items check-critical: runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Check SECURITY.md exists run: | if [ ! -f "SECURITY.md" ]; then echo "::error::SECURITY.md is required" exit 1 fi - - name: Check for pinned dependencies run: | # Check workflows for unpinned actions diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 80c6768..b625dc3 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -6,7 +6,6 @@ on: schedule: - cron: '0 4 * * *' workflow_dispatch: - # Estate guardrail: cancel superseded runs so re-pushes / rebased PR # updates do not pile up queued runs against the shared account-wide # Actions concurrency pool. Applied only to read-only check workflows @@ -14,10 +13,8 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - permissions: contents: read - jobs: analysis: runs-on: ubuntu-latest @@ -29,13 +26,11 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - name: Run Scorecard uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.3.1 with: results_file: results.sarif results_format: sarif - - name: Upload results uses: github/codeql-action/upload-sarif@c6f931105cb2c34c8f901cc885ba1e2e259cf745 # v3.31.8 with: diff --git a/.github/workflows/secret-scanner.yml b/.github/workflows/secret-scanner.yml index 9c7159e..2e06322 100644 --- a/.github/workflows/secret-scanner.yml +++ b/.github/workflows/secret-scanner.yml @@ -1,12 +1,10 @@ # SPDX-License-Identifier: PMPL-1.0 # Prevention workflow - scans for hardcoded secrets before they reach main name: Secret Scanner - on: pull_request: push: branches: [main] - # Estate guardrail: cancel superseded runs so re-pushes / rebased PR # updates do not pile up queued runs against the shared account-wide # Actions concurrency pool. Applied only to read-only check workflows @@ -14,10 +12,8 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - permissions: contents: read - jobs: trufflehog: runs-on: ubuntu-latest @@ -25,35 +21,19 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 with: - fetch-depth: 0 # Full history for scanning - + fetch-depth: 0 # Full history for scanning - name: TruffleHog Secret Scan uses: trufflesecurity/trufflehog@6c05c4a00b91aa542267d8e32a8254774799d68d # v3 with: # The v3 action injects --fail automatically on pull_request events. # Passing --fail here triggers "flag 'fail' cannot be repeated". extra_args: --only-verified - - gitleaks: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - with: - fetch-depth: 0 - - - name: Gitleaks Secret Scan - uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Rust-specific: Check for hardcoded crypto values rust-secrets: runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - - name: Check for hardcoded secrets in Rust run: | if ! find . -name Cargo.toml -not -path './target/*' -print -quit | grep -q .; then diff --git a/.github/workflows/static-analysis-gate.yml b/.github/workflows/static-analysis-gate.yml index 740ef5a..485a1bb 100644 --- a/.github/workflows/static-analysis-gate.yml +++ b/.github/workflows/static-analysis-gate.yml @@ -2,16 +2,13 @@ # Static Analysis Gate — Required by branch protection rules. # Runs panic-attack and hypatia, deposits findings for gitbot-fleet learning. name: Static Analysis Gate - on: pull_request: branches: ['**'] push: branches: [main, master] - permissions: contents: read - jobs: # --------------------------------------------------------------------------- # Job 1: panic-attack assail @@ -20,13 +17,11 @@ jobs: name: panic-attack assail runs-on: ubuntu-latest timeout-minutes: 15 - steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - name: Install panic-attack (if available) id: install run: | @@ -40,7 +35,6 @@ jobs: echo "::notice::panic-attack binary not available — skipping assail" echo "installed=false" >> "$GITHUB_OUTPUT" fi - - name: Run panic-attack assail id: assail if: steps.install.outputs.installed == 'true' @@ -67,7 +61,6 @@ jobs: echo "medium=$MEDIUM" >> "$GITHUB_OUTPUT" echo "low=$LOW" >> "$GITHUB_OUTPUT" echo "exit_code=$PA_EXIT" >> "$GITHUB_OUTPUT" - - name: Emit check annotations if: steps.install.outputs.installed == 'true' run: | @@ -81,7 +74,6 @@ jobs: "::warning file=\(.file),line=\(.line // 1)::[panic-attack] \(.message)" end ' panic-attack-findings.json || true - - name: Write step summary if: steps.install.outputs.installed == 'true' run: | @@ -96,7 +88,6 @@ jobs: | Low | ${{ steps.assail.outputs.low }} | | **Total**| ${{ steps.assail.outputs.total }} | EOF - - name: Create stub findings (when panic-attack unavailable) if: steps.install.outputs.installed != 'true' run: | @@ -104,20 +95,17 @@ jobs: echo "## panic-attack assail" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo "Skipped: panic-attack not available in this environment." >> "$GITHUB_STEP_SUMMARY" - - name: Upload panic-attack findings uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: panic-attack-findings path: panic-attack-findings.json retention-days: 90 - - name: Fail on critical findings if: steps.install.outputs.installed == 'true' && steps.assail.outputs.critical > 0 run: | echo "::error::panic-attack found ${{ steps.assail.outputs.critical }} critical issue(s) — blocking merge" exit 1 - # --------------------------------------------------------------------------- # Job 2: hypatia-scan # --------------------------------------------------------------------------- @@ -125,13 +113,11 @@ jobs: name: Hypatia neurosymbolic scan runs-on: ubuntu-latest timeout-minutes: 15 - steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - name: Setup Elixir for Hypatia scanner id: beam continue-on-error: true @@ -139,7 +125,6 @@ jobs: with: elixir-version: '1.19.4' otp-version: '28.3' - - name: Clone and build Hypatia id: build continue-on-error: true @@ -157,7 +142,6 @@ jobs: echo "::notice::Hypatia scanner not available — skipping scan" echo "ready=false" >> "$GITHUB_OUTPUT" fi - - name: Run Hypatia scan id: scan if: steps.build.outputs.ready == 'true' @@ -182,7 +166,6 @@ jobs: echo "high=$HIGH" >> "$GITHUB_OUTPUT" echo "medium=$MEDIUM" >> "$GITHUB_OUTPUT" echo "low=$LOW" >> "$GITHUB_OUTPUT" - - name: Emit check annotations if: steps.build.outputs.ready == 'true' run: | @@ -195,7 +178,6 @@ jobs: "::warning file=\(.file),line=\(.line // 1)::[hypatia] \(.message)" end ' hypatia-findings.json || true - - name: Write step summary if: steps.build.outputs.ready == 'true' run: | @@ -210,7 +192,6 @@ jobs: | Low | ${{ steps.scan.outputs.low }} | | **Total**| ${{ steps.scan.outputs.total }} | EOF - - name: Create stub findings (when Hypatia unavailable) if: steps.build.outputs.ready != 'true' run: | @@ -218,20 +199,17 @@ jobs: echo "## Hypatia Scan" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo "Skipped: Hypatia scanner not available in this environment." >> "$GITHUB_STEP_SUMMARY" - - name: Upload hypatia findings uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: hypatia-findings path: hypatia-findings.json retention-days: 90 - - name: Fail on critical security findings if: steps.build.outputs.ready == 'true' && steps.scan.outputs.critical > 0 run: | echo "::error::Hypatia found ${{ steps.scan.outputs.critical }} critical security issue(s) — blocking merge" exit 1 - # --------------------------------------------------------------------------- # Job 3: patch-bridge triage (CVE contextual assessment) # --------------------------------------------------------------------------- @@ -239,13 +217,11 @@ jobs: name: Patch Bridge CVE triage runs-on: ubuntu-latest timeout-minutes: 15 - steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - name: Install panic-attack (if available) id: install run: | @@ -258,7 +234,6 @@ jobs: echo "::notice::panic-attack binary not available — skipping Patch Bridge" echo "installed=false" >> "$GITHUB_OUTPUT" fi - - name: Run Patch Bridge triage id: triage if: steps.install.outputs.installed == 'true' @@ -281,7 +256,6 @@ jobs: echo "mitigated=$MITIGATED" >> "$GITHUB_OUTPUT" echo "concatenative=$CONCATENATIVE" >> "$GITHUB_OUTPUT" echo "informational=$INFORMATIONAL" >> "$GITHUB_OUTPUT" - - name: Write step summary if: steps.install.outputs.installed == 'true' run: | @@ -299,7 +273,6 @@ jobs: Mitigated CVEs have active controls with soundness proofs. Concatenative risks are CVE combinations that multiply severity. EOF - - name: Create stub report (when unavailable) if: steps.install.outputs.installed != 'true' run: | @@ -307,21 +280,18 @@ jobs: echo "## Patch Bridge CVE Triage" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo "Skipped: panic-attack not available in this environment." >> "$GITHUB_STEP_SUMMARY" - - name: Upload bridge report uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: bridge-report path: bridge-report.json retention-days: 90 - - name: Fail on unmitigable CVEs in critical paths if: steps.install.outputs.installed == 'true' && steps.triage.outputs.unmitigable > 0 run: | echo "::warning::Patch Bridge found ${{ steps.triage.outputs.unmitigable }} unmitigable CVE(s) — review required" # Warning only, not blocking. Unmitigable means the developer needs # to make an architectural decision, not that the PR is wrong. - # --------------------------------------------------------------------------- # Job 4: deposit-findings (combines + archives for gitbot-fleet) # --------------------------------------------------------------------------- @@ -331,26 +301,22 @@ jobs: timeout-minutes: 15 needs: [panic-attack-assail, hypatia-scan, patch-bridge-triage] if: always() - steps: - name: Download panic-attack findings uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 with: name: panic-attack-findings path: findings/ - - name: Download hypatia findings uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 with: name: hypatia-findings path: findings/ - - name: Download bridge report uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 with: name: bridge-report path: findings/ - - name: Combine findings into unified report id: combine run: | @@ -406,16 +372,14 @@ jobs: echo "high=$HIGH" >> "$GITHUB_OUTPUT" echo "medium=$MEDIUM" >> "$GITHUB_OUTPUT" echo "low=$LOW" >> "$GITHUB_OUTPUT" - - name: Upload unified findings (fleet scanner picks these up) uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: unified-findings path: findings/unified-findings.json retention-days: 90 - - name: Write deposit summary - run: | + run: |- cat <> "$GITHUB_STEP_SUMMARY" ## Unified Findings Deposit @@ -433,4 +397,4 @@ jobs: Findings saved as \`unified-findings\` artifact. The gitbot-fleet scanner will ingest these on its next pass. - EOF \ No newline at end of file + EOF diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7309fa9..b08314a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,20 +6,16 @@ stages: - lint - test - build - variables: CARGO_HOME: ${CI_PROJECT_DIR}/.cargo - cache: key: ${CI_COMMIT_REF_SLUG} paths: - .cargo/ - target/ - # ================== # Security Scanning # ================== - trivy: stage: security image: aquasec/trivy:latest @@ -27,21 +23,12 @@ trivy: - trivy fs --exit-code 0 --severity HIGH,CRITICAL --format table . - trivy fs --exit-code 1 --severity CRITICAL . allow_failure: false - -gitleaks: - stage: security - image: zricethezav/gitleaks:latest - script: - - gitleaks detect --source . --verbose --redact - allow_failure: false - semgrep: stage: security image: returntocorp/semgrep script: - semgrep --config auto --error . allow_failure: true - cargo-audit: stage: security image: rust:latest @@ -51,7 +38,6 @@ cargo-audit: rules: - exists: - Cargo.toml - cargo-deny: stage: security image: rust:latest @@ -62,7 +48,6 @@ cargo-deny: - exists: - Cargo.toml allow_failure: true - mix-audit: stage: security image: elixir:latest @@ -75,11 +60,9 @@ mix-audit: - exists: - mix.exs allow_failure: true - # ================== # Linting # ================== - rustfmt: stage: lint image: rust:latest @@ -89,7 +72,6 @@ rustfmt: rules: - exists: - Cargo.toml - clippy: stage: lint image: rust:latest @@ -100,7 +82,6 @@ clippy: - exists: - Cargo.toml allow_failure: true - mix-format: stage: lint image: elixir:latest @@ -109,7 +90,6 @@ mix-format: rules: - exists: - mix.exs - credo: stage: lint image: elixir:latest @@ -121,11 +101,9 @@ credo: - exists: - mix.exs allow_failure: true - # ================== # Testing # ================== - cargo-test: stage: test image: rust:latest @@ -134,7 +112,6 @@ cargo-test: rules: - exists: - Cargo.toml - mix-test: stage: test image: elixir:latest @@ -145,11 +122,9 @@ mix-test: rules: - exists: - mix.exs - # ================== # Build # ================== - cargo-build: stage: build image: rust:latest @@ -162,7 +137,6 @@ cargo-build: rules: - exists: - Cargo.toml - mix-build: stage: build image: elixir:latest @@ -173,3 +147,8 @@ mix-build: rules: - exists: - mix.exs +trufflehog: + stage: security + image: trufflesecurity/trufflehog:latest + script: + - trufflehog git file://. --only-verified --fail diff --git a/.machine_readable/contractiles/Justfile b/.machine_readable/contractiles/Justfile index 8134631..9898da9 100644 --- a/.machine_readable/contractiles/Justfile +++ b/.machine_readable/contractiles/Justfile @@ -797,7 +797,6 @@ deps-audit: # cargo audit # mix audit @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL --quiet . || true - @command -v gitleaks >/dev/null && gitleaks detect --source . --no-git --quiet || true @echo "Audit complete" # ═══════════════════════════════════════════════════════════════════════════════ @@ -1032,7 +1031,6 @@ install-hooks: # Run security audit security: deps-audit @echo "=== Security Audit ===" - @command -v gitleaks >/dev/null && gitleaks detect --source . --verbose || true @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL . || true @echo "Security audit complete" @@ -1542,3 +1540,6 @@ handover-model path=".": handover-human path=".": @./session/dispatch.sh handover human "{{path}}" + +secret-scan-trufflehog: + @command -v trufflehog >/dev/null && trufflehog filesystem . --only-verified || true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 29d0fef..b048d1c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,7 +46,5 @@ repos: exclude: '(\.git|node_modules|target|_build|deps|\.deno|external_corpora|\.lake)/' # --- Secret detection --- - - repo: https://github.com/gitleaks/gitleaks rev: v8.24.3 hooks: - - id: gitleaks diff --git a/Justfile b/Justfile index 8134631..9898da9 100644 --- a/Justfile +++ b/Justfile @@ -797,7 +797,6 @@ deps-audit: # cargo audit # mix audit @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL --quiet . || true - @command -v gitleaks >/dev/null && gitleaks detect --source . --no-git --quiet || true @echo "Audit complete" # ═══════════════════════════════════════════════════════════════════════════════ @@ -1032,7 +1031,6 @@ install-hooks: # Run security audit security: deps-audit @echo "=== Security Audit ===" - @command -v gitleaks >/dev/null && gitleaks detect --source . --verbose || true @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL . || true @echo "Security audit complete" @@ -1542,3 +1540,6 @@ handover-model path=".": handover-human path=".": @./session/dispatch.sh handover human "{{path}}" + +secret-scan-trufflehog: + @command -v trufflehog >/dev/null && trufflehog filesystem . --only-verified || true From 85b2e7402323a0e1e484f85ed31ea2dc5a3efea7 Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Sun, 21 Jun 2026 01:00:38 +0100 Subject: [PATCH 4/6] ci: adopt standards reusable workflows for Scorecard, Hypatia, and Governance --- .github/workflows/governance.yml | 27 +- .github/workflows/hypatia-scan.yml | 323 +---------------------- .github/workflows/scorecard-enforcer.yml | 73 ----- .github/workflows/scorecard.yml | 33 +-- 4 files changed, 19 insertions(+), 437 deletions(-) delete mode 100644 .github/workflows/scorecard-enforcer.yml diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index e0c379b..2674263 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -1,31 +1,16 @@ -# SPDX-License-Identifier: MPL-2.0 -# governance.yml — single wrapper calling the shared estate governance bundle -# in hyperpolymath/standards instead of carrying per-repo copies. -# -# Replaces the per-repo governance scaffolding removed in the same commit: -# quality.yml, guix-nix-policy.yml, npm-bun-blocker.yml, ts-blocker.yml, -# security-policy.yml, rsr-antipattern.yml, wellknown-enforcement.yml, -# workflow-linter.yml -# -# Load-bearing build/security workflows stay standalone in the repo -# (rust-ci, codeql, dependabot, release, scan/mirror/pages plumbing). - +# SPDX-License-Identifier: PMPL-1.0-or-later name: Governance + on: push: branches: [main, master] pull_request: + branches: [main, master] workflow_dispatch: -# Estate guardrail: cancel superseded runs so re-pushes / rebased PR -# updates do not pile up queued runs against the shared account-wide -# Actions concurrency pool. Applied only to read-only check workflows -# (no publish/mutation), so cancelling a superseded run is always safe. -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + permissions: contents: read + jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@861b5e911d9e5dcfb3c0ab3dd2a9a3c8fd0a1613 - timeout-minutes: 10 + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@b89b2ef1e98928fce53a85e83c37f23a1d99f6d3 diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index e1e3879..736b63f 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1,328 +1,19 @@ -# SPDX-License-Identifier: MPL-2.0 -# Hypatia Neurosymbolic CI/CD Security Scan +# SPDX-License-Identifier: PMPL-1.0-or-later name: Hypatia Security Scan + on: push: branches: [main, master, develop] pull_request: branches: [main, master] schedule: - - cron: '0 0 * * 0' # Weekly on Sunday + - cron: '0 0 * * 0' workflow_dispatch: -# Estate guardrail: cancel superseded runs so re-pushes don't pile up -# queued runs across the estate. Safe here because this workflow only -# performs read-only checks/lint/test/scan with no publish or mutation. -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + permissions: contents: read - # security-events: write serves two purposes (write implies read): - # 1. read — lets the built-in GITHUB_TOKEN query this repo's own - # Dependabot alerts via the Hypatia DependabotAlerts rule - # (DA001-DA004). Without read, `scan_from_path` gets HTTP 403 - # and the rule silently returns no findings. - # See 007-lang/audits/audit-dependabot-automation-gap-2026-04-17.md. - # 2. write — lets the "Upload SARIF to code scanning" step publish - # Hypatia findings to the Security → Code scanning page so they - # are triaged/deduplicated like CodeQL alerts instead of living - # only in a build artifact nobody is required to look at. - # See hyperpolymath/burble#35 (SARIF integration). - # This is a single-job workflow, so job-level scoping would not - # narrow the grant further; it stays workflow-level and documented. - security-events: write - # pull-requests: write lets the advisory "Comment on PR with findings" - # step post its summary. Without it the built-in GITHUB_TOKEN gets - # "Resource not accessible by integration" and (absent continue-on-error) - # hard-fails the scan — exactly what the gate-decoupling design forbids. - pull-requests: write + security-events: read + jobs: scan: - name: Hypatia Neurosymbolic Analysis - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 # Full history for better pattern analysis - - name: Setup Elixir for Hypatia scanner - uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.18.2 - with: - elixir-version: '1.18' - otp-version: '27' - - name: Clone Hypatia - run: | - if [ ! -d "$HOME/hypatia" ]; then - git clone https://github.com/hyperpolymath/hypatia.git "$HOME/hypatia" - fi - - name: Build Hypatia scanner (if needed) - run: | - cd "$HOME/hypatia" - if [ ! -f hypatia ]; then - echo "Building hypatia scanner..." - mix deps.get - mix escript.build - fi - - name: Run Hypatia scan - id: scan - env: - # Pass the built-in Actions token through to Hypatia so the - # DependabotAlerts rule can query this repo's own alerts. - # For cross-repo scanning (fleet-coordinator scan-supervised), - # a PAT with `security_events` scope is required instead. - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - echo "Scanning repository: ${{ github.repository }}" - - # Run scanner (exits non-zero when findings exist — suppress to continue) - HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . --exit-zero > hypatia-findings.json || true - - # Count findings - FINDING_COUNT=$(jq '. | length' hypatia-findings.json 2>/dev/null || echo 0) - echo "findings_count=$FINDING_COUNT" >> $GITHUB_OUTPUT - - # Extract severity counts - CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' hypatia-findings.json) - HIGH=$(jq '[.[] | select(.severity == "high")] | length' hypatia-findings.json) - MEDIUM=$(jq '[.[] | select(.severity == "medium")] | length' hypatia-findings.json) - - echo "critical=$CRITICAL" >> $GITHUB_OUTPUT - echo "high=$HIGH" >> $GITHUB_OUTPUT - echo "medium=$MEDIUM" >> $GITHUB_OUTPUT - - echo "## Hypatia Scan Results" >> $GITHUB_STEP_SUMMARY - echo "- Total findings: $FINDING_COUNT" >> $GITHUB_STEP_SUMMARY - echo "- Critical: $CRITICAL" >> $GITHUB_STEP_SUMMARY - echo "- High: $HIGH" >> $GITHUB_STEP_SUMMARY - echo "- Medium: $MEDIUM" >> $GITHUB_STEP_SUMMARY - - name: Upload findings artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: hypatia-findings - path: hypatia-findings.json - retention-days: 90 - - name: Convert Hypatia findings to SARIF - # Always runs (no findings_count guard): an EMPTY SARIF run is - # valid and intentional — uploading it clears stale Hypatia - # alerts from the code-scanning page when a repo goes clean. - # The converter is dependency-free Node (Node ships on - # ubuntu-latest; no npm install — estate npm ban respected) and - # is hardened against the heterogeneous Hypatia JSON schema: - # most findings are {rule_module,severity,type,file,reason, - # action}; only some carry an integer `line`; `file` may be - # empty or absolute. See lib/hypatia/cli.ex (collect_findings). - run: | - cat > "$RUNNER_TEMP/hypatia-sarif.cjs" <<'CJS' - const fs = require('fs'); - const path = require('path'); - const crypto = require('crypto'); - - const ws = process.env.GITHUB_WORKSPACE || process.cwd(); - - let findings = []; - try { - const parsed = JSON.parse(fs.readFileSync('hypatia-findings.json', 'utf8')); - if (Array.isArray(parsed)) findings = parsed; - } catch (_) { - // Scanner unavailable / empty / malformed -> empty SARIF. - // Intentionally clears stale alerts rather than erroring. - findings = []; - } - - // Mirrors Hypatia's own "github" annotation mapping - // (lib/hypatia/cli.ex output/2): critical|high -> error, - // medium -> warning, everything else -> note. - const levelFor = (sev) => { - switch (String(sev || '').toLowerCase()) { - case 'critical': - case 'high': return 'error'; - case 'medium': return 'warning'; - default: return 'note'; - } - }; - - // SARIF artifactLocation.uri must be a repo-relative POSIX - // path. Hypatia may emit absolute paths (scanned under - // $GITHUB_WORKSPACE) or "" / "." for repo-level findings. - const relUri = (file) => { - if (!file) return '.'; - let f = String(file); - if (path.isAbsolute(f)) { - const rel = path.relative(ws, f); - f = (rel && !rel.startsWith('..')) ? rel : path.basename(f); - } - f = f.replace(/\\/g, '/').replace(/^\.\//, ''); - return f || '.'; - }; - - const rules = new Map(); - const results = findings.map((f) => { - const mod = String(f.rule_module || 'hypatia'); - const type = String(f.type || 'finding'); - const ruleId = `hypatia/${mod}/${type}`; - const level = levelFor(f.severity); - if (!rules.has(ruleId)) { - rules.set(ruleId, { - id: ruleId, - name: `${mod}.${type}`, - shortDescription: { text: `Hypatia ${mod}: ${type}` }, - defaultConfiguration: { level } - }); - } - const uri = relUri(f.file); - const msg = String(f.reason || f.type || 'Hypatia finding'); - const startLine = - Number.isInteger(f.line) && f.line > 0 ? f.line : 1; - // Stable cross-run fingerprint for dedupe (no line, so a - // moved finding in the same file/rule stays one alert). - const fp = crypto - .createHash('sha256') - .update([ruleId, uri, type, msg].join('|')) - .digest('hex'); - return { - ruleId, - level, - message: { text: msg }, - locations: [ - { - physicalLocation: { - artifactLocation: { uri }, - region: { startLine } - } - } - ], - partialFingerprints: { 'hypatiaFindingHash/v1': fp } - }; - }); - - const sarif = { - $schema: 'https://json.schemastore.org/sarif-2.1.0.json', - version: '2.1.0', - runs: [ - { - tool: { - driver: { - name: 'Hypatia', - informationUri: 'https://github.com/hyperpolymath/hypatia', - rules: Array.from(rules.values()) - } - }, - results - } - ] - }; - - fs.writeFileSync('hypatia.sarif', JSON.stringify(sarif, null, 2)); - console.log(`hypatia.sarif written: ${results.length} result(s).`); - CJS - node "$RUNNER_TEMP/hypatia-sarif.cjs" - - name: Upload SARIF to GitHub code scanning - # Fork PRs get a read-only GITHUB_TOKEN, so security-events:write - # is unavailable and upload-sarif cannot publish — skip there - # rather than hard-fail (the push/schedule run on the default - # branch is the authoritative upload). Same-repo PRs and pushes - # do upload. This step is deliberately NOT continue-on-error: - # if the security-surface integration breaks we want a loud red, - # not a silently-ungated scanner (the exact failure mode #35 - # exists to end). The empty-SARIF "clear stale alerts" path is - # handled in the converter above and does not error here. - if: >- - always() && (github.event_name != 'pull_request' || - - github.event.pull_request.head.repo.fork != true) - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.28.1 - with: - sarif_file: hypatia.sarif - # Distinct category so Hypatia results coexist with CodeQL's - # (codeql.yml) instead of overwriting them on the same surface. - category: hypatia - - name: Submit findings to gitbot-fleet (Phase 2) - if: steps.scan.outputs.findings_count > 0 - # Phase 2 is the collaborative LEARNING side-channel ("bots share - # findings via gitbot-fleet"), not the security gate. The gate is - # the baseline-aware "Check for critical or high-severity issues" - # step below. A fleet-side regression (e.g. the submit script being - # moved/removed) must NEVER hard-fail every consuming repo's scan. - # Same reasoning as the "Comment on PR with findings" step. - # See hyperpolymath/hypatia#213 (gate decoupling) and the exit-127 - # estate-wide breakage when gitbot-fleet/scripts/submit-finding.sh - # no longer existed on the default branch. - continue-on-error: true - env: - # All GitHub context values surface as env vars so the run - # block never interpolates `${{ … }}` inline (closes the - # workflow_audit/unsafe_curl_payload + actions_expression_injection - # findings). - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - FLEET_PUSH_TOKEN: ${{ secrets.HYPATIA_DISPATCH_PAT }} - FLEET_DISPATCH_TOKEN: ${{ secrets.HYPATIA_DISPATCH_PAT }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_SHA: ${{ github.sha }} - FINDINGS_COUNT: ${{ steps.scan.outputs.findings_count }} - run: "echo \"\U0001F4E4 Submitting $FINDINGS_COUNT findings to gitbot-fleet...\"\n\n# Clone gitbot-fleet to temp directory. A clone failure (network,\n# repo gone) is non-fatal: learning submission is best-effort.\nFLEET_DIR=\"/tmp/gitbot-fleet-$$\"\nif ! git clone --depth 1 https://github.com/hyperpolymath/gitbot-fleet.git \"$FLEET_DIR\"; then\n echo \"::warning::Could not clone gitbot-fleet — skipping Phase 2 learning submission (non-fatal).\"\n exit 0\nfi\n\n# The submission script's location in gitbot-fleet has drifted\n# before (it was absent from the default branch, which exit-127'd\n# every consuming repo's scan). Probe known locations rather than\n# hard-coding one path, and skip gracefully if none is present.\nSUBMIT_SCRIPT=\"\"\nfor cand in \\\n \"$FLEET_DIR/scripts/submit-finding.sh\" \\\n \"$FLEET_DIR/scripts/submit_finding.sh\" \\\n \"$FLEET_DIR/bin/submit-finding.sh\" \\\n \"$FLEET_DIR/submit-finding.sh\"; do\n if [ -f \"$cand\" ]; then\n SUBMIT_SCRIPT=\"$cand\"\n break\n fi\ndone\n\nif [ -z \"$SUBMIT_SCRIPT\" ]; then\n echo \"::warning::gitbot-fleet submit-finding script not found at any known path — skipping Phase 2 learning submission (non-fatal). Findings are still uploaded as an artifact and gated below.\"\n rm -rf \"$FLEET_DIR\"\n exit 0\nfi\n\n# Run submission script. Pass the findings path as ABSOLUTE —\n# the script cd's into its own working dir before reading the\n# file, so a relative path would resolve to the wrong place.\n# A submission-script failure is logged but non-fatal.\nif bash \"$SUBMIT_SCRIPT\" \"$GITHUB_WORKSPACE/hypatia-findings.json\"; then\n echo \"✅ Finding submission complete\"\nelse\n echo \"::warning::gitbot-fleet submission script exited non-zero — Phase 2 learning submission skipped (non-fatal).\"\nfi\n\n# Cleanup\nrm -rf \"$FLEET_DIR\"\n" - - name: Check for critical issues - if: steps.scan.outputs.critical > 0 - # GATING POLICY (explicit, by design — not an oversight): - # Hypatia is ADVISORY here. Critical findings are surfaced - # (step annotation + SARIF alert on the code-scanning page + - # PR comment) but do NOT fail this check. Enforcement is - # delegated to the code-scanning surface: tighten by adding a - # branch-protection "required" status on the `hypatia` SARIF - # category, not by reintroducing an `exit 1` here. This keeps - # the gate decision in one auditable place (hypatia#213 gate - # decoupling) and lets a repo opt into fail-on-critical without - # editing this canonical workflow. To change the policy, change - # branch protection — deliberately no commented-out `exit 1`. - run: | - echo "::warning::Hypatia found critical security issue(s) — advisory." - echo "See the Security → Code scanning page (category: hypatia)" - echo "and the hypatia-findings.json artifact for details." - - name: Generate scan report - run: | - cat << EOF > hypatia-report.md - # Hypatia Security Scan Report - - **Repository:** ${{ github.repository }} - **Scan Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") - **Commit:** ${{ github.sha }} - - ## Summary - - | Severity | Count | - |----------|-------| - | Critical | ${{ steps.scan.outputs.critical }} | - | High | ${{ steps.scan.outputs.high }} | - | Medium | ${{ steps.scan.outputs.medium }} | - | **Total**| ${{ steps.scan.outputs.findings_count }} | - - ## Next Steps - - 1. Triage findings on the **Security → Code scanning** page - (SARIF category \`hypatia\`) — dismiss/track them there like - CodeQL alerts. - 2. The full finding set is also attached as the - \`hypatia-findings.json\` build artifact for offline review. - 3. Findings are **advisory** today (surfaced, not gated); the - gating policy is documented in the workflow's "Check for - critical issues" step. - - ## Learning - - These findings feed Hypatia's learning engine to improve future rules. - - --- - *Powered by [Hypatia](https://github.com/hyperpolymath/hypatia) - Neurosymbolic CI/CD Intelligence* - EOF - - cat hypatia-report.md >> $GITHUB_STEP_SUMMARY - - name: Comment on PR with findings - if: github.event_name == 'pull_request' && steps.scan.outputs.findings_count > 0 - # Advisory only — posting findings as a PR comment must never gate - # the scan (hypatia#213 gate decoupling). Belt-and-braces alongside - # the pull-requests: write permission above: a token/API hiccup or - # a fork PR (read-only token) skips the comment, not the check. - continue-on-error: true - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v7 - with: - script: "const fs = require('fs');\nconst findings = JSON.parse(fs.readFileSync('hypatia-findings.json', 'utf8'));\n\nconst critical = findings.filter(f => f.severity === 'critical').length;\nconst high = findings.filter(f => f.severity === 'high').length;\n\nlet comment = `## \U0001F50D Hypatia Security Scan\\n\\n`;\ncomment += `**Findings:** ${findings.length} issues detected\\n\\n`;\ncomment += `| Severity | Count |\\n|----------|-------|\\n`;\ncomment += `| \U0001F534 Critical | ${critical} |\\n`;\ncomment += `| \U0001F7E0 High | ${high} |\\n`;\ncomment += `| \U0001F7E1 Medium | ${findings.length - critical - high} |\\n\\n`;\n\nif (critical > 0) {\n comment += `⚠️ **Action Required:** Critical security issues found!\\n\\n`;\n}\n\ncomment += `
View findings\\n\\n`;\ncomment += `\\`\\`\\`json\\n${JSON.stringify(findings.slice(0, 10), null, 2)}\\n\\`\\`\\`\\n`;\ncomment += `
\\n\\n`;\ncomment += `*Powered by Hypatia Neurosymbolic CI/CD Intelligence*`;\n\ngithub.rest.issues.createComment({\n owner: context.repo.owner,\n repo: context.repo.repo,\n issue_number: context.issue.number,\n body: comment\n});" + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@b89b2ef1e98928fce53a85e83c37f23a1d99f6d3 diff --git a/.github/workflows/scorecard-enforcer.yml b/.github/workflows/scorecard-enforcer.yml deleted file mode 100644 index f5fb110..0000000 --- a/.github/workflows/scorecard-enforcer.yml +++ /dev/null @@ -1,73 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Prevention workflow - runs OpenSSF Scorecard and fails on low scores -name: OpenSSF Scorecard Enforcer -on: - push: - branches: [main] - schedule: - - cron: '0 6 * * 1' # Weekly on Monday - workflow_dispatch: -# Estate guardrail: cancel superseded runs so re-pushes / rebased PR -# updates do not pile up queued runs against the shared account-wide -# Actions concurrency pool. Applied only to read-only check workflows -# (no publish/mutation), so cancelling a superseded run is always safe. -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - contents: read -jobs: - scorecard: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - security-events: write - id-token: write # For OIDC - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - name: Run Scorecard - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 - with: - results_file: results.sarif - results_format: sarif - publish_results: true - - name: Upload SARIF - uses: github/codeql-action/upload-sarif@c6f931105cb2c34c8f901cc885ba1e2e259cf745 # v4 - with: - sarif_file: results.sarif - - name: Check minimum score - run: | - # Parse score from results - SCORE=$(jq -r '.runs[0].tool.driver.properties.score // 0' results.sarif 2>/dev/null || echo "0") - - echo "OpenSSF Scorecard Score: $SCORE" - - # Minimum acceptable score (0-10 scale) - MIN_SCORE=5 - - if [ "$(echo "$SCORE < $MIN_SCORE" | bc -l)" = "1" ]; then - echo "::error::Scorecard score $SCORE is below minimum $MIN_SCORE" - exit 1 - fi - # Check specific high-priority items - check-critical: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Check SECURITY.md exists - run: | - if [ ! -f "SECURITY.md" ]; then - echo "::error::SECURITY.md is required" - exit 1 - fi - - name: Check for pinned dependencies - run: | - # Check workflows for unpinned actions - unpinned=$(grep -r "uses:.*@v[0-9]" .github/workflows/*.yml 2>/dev/null | grep -v "#" | head -5 || true) - if [ -n "$unpinned" ]; then - echo "::warning::Found unpinned actions:" - echo "$unpinned" - fi diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index b625dc3..0c31a3e 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,37 +1,16 @@ -# SPDX-License-Identifier: PMPL-1.0 +# SPDX-License-Identifier: PMPL-1.0-or-later name: OSSF Scorecard + on: push: branches: [main, master] schedule: - cron: '0 4 * * *' workflow_dispatch: -# Estate guardrail: cancel superseded runs so re-pushes / rebased PR -# updates do not pile up queued runs against the shared account-wide -# Actions concurrency pool. Applied only to read-only check workflows -# (no publish/mutation), so cancelling a superseded run is always safe. -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + permissions: contents: read + jobs: - analysis: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - security-events: write - id-token: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - name: Run Scorecard - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.3.1 - with: - results_file: results.sarif - results_format: sarif - - name: Upload results - uses: github/codeql-action/upload-sarif@c6f931105cb2c34c8f901cc885ba1e2e259cf745 # v3.31.8 - with: - sarif_file: results.sarif + scorecard: + uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@b89b2ef1e98928fce53a85e83c37f23a1d99f6d3 From 1682baaff6b414743a395cd7362c0c3e02b9d150 Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Sun, 21 Jun 2026 01:15:42 +0100 Subject: [PATCH 5/6] ci: adopt standards reusable workflows for Scorecard, Hypatia, and Governance --- .github/workflows/governance.yml | 2 +- .github/workflows/hypatia-scan.yml | 2 +- .github/workflows/scorecard.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 2674263..31d497f 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -13,4 +13,4 @@ permissions: jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@b89b2ef1e98928fce53a85e83c37f23a1d99f6d3 + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@5a93d9da1bbf6ca9eb4eec89e900c733f114c995 diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 736b63f..ce9ce4c 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -16,4 +16,4 @@ permissions: jobs: scan: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@b89b2ef1e98928fce53a85e83c37f23a1d99f6d3 + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@5a93d9da1bbf6ca9eb4eec89e900c733f114c995 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 0c31a3e..987033d 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -13,4 +13,4 @@ permissions: jobs: scorecard: - uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@b89b2ef1e98928fce53a85e83c37f23a1d99f6d3 + uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@5a93d9da1bbf6ca9eb4eec89e900c733f114c995 From 1beb7e71dee986befd591cd006fde7183dfe7f00 Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Sun, 21 Jun 2026 01:20:30 +0100 Subject: [PATCH 6/6] ci: adopt standards reusable workflows for Scorecard, Hypatia, and Governance --- .github/workflows/governance.yml | 2 +- .github/workflows/hypatia-scan.yml | 2 +- .github/workflows/scorecard.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 31d497f..8161ec2 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -13,4 +13,4 @@ permissions: jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@5a93d9da1bbf6ca9eb4eec89e900c733f114c995 + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@5a93d9d57cc04de4002d6d0ecd336fc7a8698910 diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index ce9ce4c..e715848 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -16,4 +16,4 @@ permissions: jobs: scan: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@5a93d9da1bbf6ca9eb4eec89e900c733f114c995 + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@5a93d9d57cc04de4002d6d0ecd336fc7a8698910 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 987033d..47acbb5 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -13,4 +13,4 @@ permissions: jobs: scorecard: - uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@5a93d9da1bbf6ca9eb4eec89e900c733f114c995 + uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@5a93d9d57cc04de4002d6d0ecd336fc7a8698910